All files / src/lib web-lock.ts

100% Statements 15/15
100% Branches 6/6
100% Functions 5/5
100% Lines 13/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34                    48x       171x   171x 5x   166x 171x     171x 164x 1x   171x 171x 165x   171x    
// navigator.locks when available; else an in-realm promise-chain mutex per
// name (jsdom + older browsers). The cross-tab guarantee comes from
// navigator.locks in real browsers; the fallback preserves in-realm
// exclusivity only — which is exactly the pre-Web-Locks status quo.
 
type LockCallback = (lock: { name: string }) => Promise<unknown>;
interface LockManagerLike {
  request(name: string, callback: LockCallback): Promise<unknown>;
}
 
const chains = new Map<string, Promise<void>>();
 
export function withWebLock<T>(name: string, fn: () => Promise<T>): Promise<T> {
  const locks = (
    globalThis.navigator as (Navigator & { locks?: LockManagerLike }) | undefined
  )?.locks;
  if (locks?.request) {
    return locks.request(name, () => fn()) as Promise<T>;
  }
  const prev = chains.get(name) ?? Promise.resolve();
  const run = prev.then(fn);
  // The chain must survive a rejected holder (swallow), and clean itself up
  // once no later waiter has been appended.
  const settled = run.then(
    () => undefined,
    () => undefined,
  );
  chains.set(name, settled);
  void settled.then(() => {
    if (chains.get(name) === settled) chains.delete(name);
  });
  return run;
}