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. (The hosted demo app is served from its own origin with these headers for exactly this reason.)

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 wallet routes. If only part of your site runs the wallet (for example a /wallet/* section), apply COOP/COEP on those paths rather than the entire domain. Hosts like Netlify and Cloudflare Pages read a _headers file:

public/_headers
/wallet/*
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/wallet/ | 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