Page MenuHomePhabricator

D11113.id37777.diff
No OneTemporary

D11113.id37777.diff

diff --git a/web/web-lock.js b/web/web-lock.js
new file mode 100644
--- /dev/null
+++ b/web/web-lock.js
@@ -0,0 +1,130 @@
+// @flow
+
+import * as React from 'react';
+
+import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js';
+
+const lockStatuses = Object.freeze({
+ ACQUIRED: 'acquired',
+ SHOULD_RELEASE: 'should-release',
+ REQUESTED: 'requested',
+ RELEASED: 'released',
+});
+export type LockStatus = $Values<typeof lockStatuses>;
+const ABORT_REASON_BACKGROUNDED = 'backgrounded';
+
+function useWebLock(name: string): {
+ lockStatus: LockStatus,
+ releaseLockOrAbortRequest: () => void,
+} {
+ const [lockRequest, setLockRequest] = React.useState<null | {
+ controller: AbortController,
+ resolveCallback: ?() => void,
+ }>(null);
+
+ const releaseLockOrAbortRequest = React.useCallback(() => {
+ if (!lockRequest) {
+ return;
+ }
+
+ // Abort lock request if we don't have it
+ lockRequest.controller.abort(ABORT_REASON_BACKGROUNDED);
+ // Release lock if we have it
+ lockRequest?.resolveCallback?.();
+ // The `navigator.locks.request` promise should now resolve
+ // and the lock will be set to null
+ }, [lockRequest]);
+
+ const isVisible = useIsAppForegrounded();
+ React.useEffect(() => {
+ if (!isVisible) {
+ // If the tab isn't visible and we haven't acquired the lock
+ // we want to abort the request
+ if (lockRequest && !lockRequest.resolveCallback) {
+ releaseLockOrAbortRequest();
+ }
+ return;
+ }
+
+ if (lockRequest) {
+ return;
+ }
+
+ const controller = new AbortController();
+ setLockRequest({
+ controller,
+ resolveCallback: null,
+ });
+
+ void (async () => {
+ try {
+ const lockManager: LockManager = (navigator: any).locks;
+ // This promise will resolve after we get a lock
+ // and the inner Promise resolves. It will reject
+ // if we abort the request or there is an error.
+ await lockManager.request(
+ name,
+ { signal: controller.signal },
+ () =>
+ new Promise(resolve => {
+ setLockRequest({ controller, resolveCallback: resolve });
+ }),
+ );
+ } catch (err) {
+ if (err !== ABORT_REASON_BACKGROUNDED) {
+ console.log('Error when requesting a lock', err);
+ }
+ }
+
+ setLockRequest(null);
+ })();
+ }, [isVisible, lockRequest, name, releaseLockOrAbortRequest]);
+
+ const lockStatus = React.useMemo(() => {
+ if (!lockRequest) {
+ return lockStatuses.RELEASED;
+ }
+ if (!lockRequest?.resolveCallback) {
+ return lockStatuses.REQUESTED;
+ }
+ if (!isVisible) {
+ return lockStatuses.SHOULD_RELEASE;
+ }
+
+ return lockStatuses.ACQUIRED;
+ }, [lockRequest, isVisible]);
+
+ return { lockStatus, releaseLockOrAbortRequest };
+}
+
+type LockManager = {
+ +request: <T>(
+ name: string,
+ options: {
+ +mode?: LockMode,
+ +ifAvailable?: boolean,
+ +steal?: boolean,
+ +signal?: AbortSignal,
+ },
+ callback: (Lock) => Promise<T>,
+ ) => Promise<T>,
+ +query: () => Promise<{
+ +held: $ReadOnlyArray<LockInfo>,
+ +pending: $ReadOnly<LockInfo>,
+ }>,
+};
+
+type Lock = {
+ +name: string,
+ +mode: LockMode,
+};
+
+type LockInfo = {
+ +name: string,
+ +mode: LockMode,
+ +clientID: string,
+};
+
+type LockMode = 'exclusive' | 'shared';
+
+export { useWebLock };

File Metadata

Mime Type
text/plain
Expires
Sun, Sep 29, 10:32 PM (22 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2202566
Default Alt Text
D11113.id37777.diff (3 KB)

Event Timeline