@mrzr/api-client

Web Worker & Tabs

How @mrzr/api-client keeps access tokens inside a Web Worker away from XSS, when it falls back to the main thread, and how tabs share login and logout.

Tokens live in a Web Worker

By default every request runs inside a Web Worker. The token is stored there and attached there, so page JavaScript, including a script injected through XSS, can't read it.

Where is the token?

Log in, then search everywhere page JavaScript (or an injected script) could look for the token.

Not checked yet.

There is nothing to set up: the worker is bundled inline, with no extra file to host and no bundler plugin to add.

When it falls back

The client silently uses the main thread instead, with identical behaviour, when:

  • you pass worker: false, or the code runs on a server (SSR, Node)
  • the browser blocks the worker. Your Content Security Policy must allow worker-src 'self' blob:
  • extractTokens or buildRefreshBody is a function. Functions can't be sent into a worker; use their object forms

Check what you got with api.isWorker.

What it can't protect

An injected script can still use your session. It can call api.get(...) just like your own code, so worker isolation stops token theft, not XSS itself. Also, storage: "local", "session" and "cookie" put the token where page JavaScript can read it. Keep the default "memory" to get the full benefit.

Tabs stay in sync

Clients with the same storageKey talk to each other over BroadcastChannel:

  • Log out in one tab: every tab logs out.
  • Log in in one tab: the others pick up the session (when storage is shared: "local" or "cookie").
  • Refresh: tabs that share a session take turns through the Web Locks API. When one tab has already refreshed, the next adopts its new tokens instead of spending the refresh token again. This matters when your server rotates refresh tokens and treats reuse as theft.

Two independent apps on one origin? Give each its own storageKey:

const adminApi = createClient({ storageKey: "admin" });
const shopApi = createClient({ storageKey: "shop" });

Turn syncing off with multiTab: false.

On this page