Cross-origin isolation

Why

The Wavelength SDK’s default transport runs the wallet daemon in a Web Worker and stores encrypted wallet data in OPFS-backed SQLite. Both the worker’s WASM module and the SQLite stack use SharedArrayBuffer for efficient memory sharing.

Browsers gate SharedArrayBuffer behind cross-origin isolation: the page must be served with headers that prevent other origins from reading its memory. Without isolation, SharedArrayBuffer is unavailable, OPFS persistence fails, and the worker transport cannot start.

Cross-origin isolation is a web-only concern. It applies to every route in your app that loads the Wavelength SDK, including embedded demo or playground pages on the docs site.

COOP/COEP headers

These are response headers on your own HTML document, so only the server that serves your app can set them. The Wavelength SDK cannot set them for you: they are not something the SDK, the worker, or the runtime assets can emit on their own. Send them on wallet routes (and on your dev server while testing locally):

Response headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Cross-Origin-Opener-Policy: same-origin keeps other browsing contexts from holding a reference to your page’s global object.

Cross-Origin-Embedder-Policy: require-corp requires every subresource (script, stylesheet, image, font, WASM) to either be same-origin or explicitly opt in with Cross-Origin-Resource-Policy: cross-origin (or be loaded with crossorigin where applicable). Same-origin assets need no extra attribute.

Scope headers to embed routes. If only part of your site runs the wallet (for example /web/playground/* on the docs site), apply COOP/COEP on those paths rather than the entire domain. Hosts like Netlify and Cloudflare Pages read a _headers file:

public/_headers
/web/playground/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

For Vite during local development, set the same headers on the dev and preview servers:

vite.config.ts
const crossOriginIsolation = {
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Resource-Policy': 'same-origin',
};
export default defineConfig({
server: { headers: crossOriginIsolation },
preview: { headers: crossOriginIsolation },
});

Self-host third-party assets. Cross-origin Google Fonts, analytics scripts, and similar embeds are blocked under require-corp. Bundle fonts locally (for example with @fontsource) or serve them from the same origin as your app.

Optional: credentialless COEP. Some teams use Cross-Origin-Embedder-Policy: credentialless as a lighter alternative. It is not supported uniformly across browsers (Safari in particular). The Wavelength SDK targets require-corp because every major engine supports it today. The key difference: credentialless drops credentials (cookies, client certificates) on cross-origin requests instead of requiring the target origin to opt in with Cross-Origin-Resource-Policy.

Verify with curl. After deploying, confirm the headers are present:

Terminal
curl -sI https://your-app.example/web/playground/ | grep -i cross-origin

You should see both Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp (or credentialless if you chose that mode).

In the browser, open DevTools and check that crossOriginIsolated is true:

Browser console
console.log(crossOriginIsolated); // true

If isolation is missing, the wallet will fail to initialize the OPFS database. See Troubleshooting for common fixes.

See also