@aletheia-works/coi-serviceworker

中文文档
0.2.0发布于 2026/11/05Apache-2.0TypeScriptCommonJS零依赖构建来源已验证近 30 天下载 2826

Cross-origin isolation for static hosts through a service worker that reloads only once it controls the page

原始使用文档
文档目录10 节
  1. Usage
  2. Waiting for isolation
  3. Limiting the pages it isolates
  4. Removing the worker
  5. In an iframe
  6. What happens on a visit
  7. Differences from gzuidhof/coi-serviceworker
  8. Development
  9. Releases
  10. License

coi-serviceworker

Cross-origin isolation for static hosts that cannot set response headers, such as GitHub Pages. A service worker adds the headers (Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp) to every response it serves, so the page can use SharedArrayBuffer and other APIs that need window.crossOriginIsolated.

Usage

Copy coi-serviceworker.js next to your page and load it before any script that needs isolation:

<script src="coi-serviceworker.js"></script>

Load it as a classic script from its file. With type="module", or with the code inline, the script cannot find its own URL, so it logs a warning and leaves the page as it is.

A service worker controls only its own folder and the folders below it, so put the file at or above every page that needs it, and at or above every Worker script those pages start. The worker whose scope holds a worker script's URL serves that script, not the one controlling the page. A worker script outside the scope gets no Cross-Origin-Embedder-Policy, and the isolated page's browser blocks it while the page itself still reports crossOriginIsolated. The test a worker script outside the scope is blocked shows this.

From npm:

npm install @aletheia-works/coi-serviceworker
cp node_modules/@aletheia-works/coi-serviceworker/coi-serviceworker.js public/

The same file is on JSR as @aletheia-works/coi-serviceworker.

The page must be served over HTTPS or from localhost. Every cross-origin resource it loads must allow being embedded (CORS or Cross-Origin-Resource-Policy), as require-corp demands.

Waiting for isolation

The script sets window.coiServiceWorker as soon as it runs, so a later script can wait for it instead of guessing with a timeout:

const state = await window.coiServiceWorker.settled;
if (state.status === 'isolated') startTheWork();
else if (state.status === 'failed')
  console.error('not isolated:', state.reason);

settled resolves once the page is isolated ({ status: 'isolated' }), has failed ({ status: 'failed', reason }) or has removed the worker ({ status: 'unregistered' }, see below). It never resolves on a page that is about to reload, so nothing heavy starts there. coiServiceWorker.status is the current status: pending, reloading, isolated, failed or unregistered. Each change is also sent as a coi-serviceworker event on window, with the state as its detail. On an isolated page, and when the script fails before registering, the event is sent while the script itself runs, so a listener added by a later script misses it. Other events come later, but may still come before a slow script listens, so read status or await settled rather than rely on the event.

The npm package holds the types. Reference globals to type window.coiServiceWorker and the event, and import the rest by name:

/// <reference types="@aletheia-works/coi-serviceworker/globals" />
import type { CoiFailure } from '@aletheia-works/coi-serviceworker/coi-serviceworker.js';

The JSR package exports CoiServiceWorker, CoiState, CoiSettled and CoiFailure too, but not the global, since JSR rejects a declare global. Write one of your own there.

reasonCause
module-scriptThe script was loaded with type="module", or inline
no-service-workerNot a secure context, or no service worker support
registration-failednavigator.serviceWorker.register rejected
outside-scopeThe page is outside the worker's scope
storage-unavailableSession storage threw, so the page was not reloaded
still-not-isolatedThe page came through the worker and is still not isolated
unexpected-errorSomething else threw; the warning shows the error

Limiting the pages it isolates

Once registered, the worker isolates every page in its scope, including pages that never load the script, and their cross-origin resources must then allow being embedded. To isolate only some pages, name them with include, as a path prefix, in the script's URL. It may be repeated:

<script src="/coi-serviceworker.js?include=/app/&include=/demo.html"></script>

The worker then adds the headers only to pages under those paths, and to the requests those pages make, including the Worker scripts they start. Other pages in the scope load as if the worker were not there. A page that loads the script must itself be under a prefix: otherwise it is reloaded once and ends failed with still-not-isolated. Every page that loads the script should use the same URL, since registering another URL replaces the worker for the whole scope. A page that the old worker did not isolate is reloaded once through the new one.

Removing the worker

The worker stays registered after a site stops loading the script, and it keeps isolating the pages in its scope. Load the script with unregister on a page in that scope to remove it:

<script src="/coi-serviceworker.js?unregister"></script>

The script unregisters every registration whose worker is this script, in whatever scope it was registered, reloads the page if that worker served it, and sets the status to unregistered. WebKit can still serve the first reload through the removed worker, and then the page is reloaded once more.

In an iframe

A page in an iframe is cross-origin isolated only when the page that embeds it is isolated too.

  • If the parent page on the same origin loads the script, the parent and the frame are both isolated.
  • If only the frame loads it, the frame is not isolated and logs a warning. If the parent page is inside the worker's scope, both are isolated once the parent is reloaded. If it is outside, the frame is never isolated and warns on every load.
  • A page on another origin cannot embed a page this package isolates. If the parent sends no isolation headers, neither page is isolated. If it does, the browser does not load the frame.

What happens on a visit

  1. The page is already cross-origin isolated (the server sends the headers, or the worker served it): the status is isolated, and nothing else happens.
  2. Otherwise the script registers itself as a service worker and waits until the worker is active, then reloads the page once. The reloaded page comes through the worker and is isolated. If the script was loaded as a module, or the registration fails, it logs a warning and does not reload.
  3. If the reloaded page is still not isolated, the script logs a warning and does not reload again. A later hard reload, which skips the worker, is reloaded through the worker again, unless it starts within a second of the previous reload.
  4. If session storage is unavailable, the script cannot mark the reload, so it registers the worker, logs a warning and does not reload. The page is isolated from its next load.

Later visits are served by the worker from the start and need no reload.

Differences from gzuidhof/coi-serviceworker

This package is an independent implementation of the idea in gzuidhof/coi-serviceworker, whose last release, 0.1.7, is from 2023.

  • 0.1.7 reloads as soon as the worker is found installing, before it is active. When the install takes a moment, the reloaded page is loaded without the worker, is not isolated, and is never reloaded again. This package reloads only after navigator.serviceWorker.ready. The test a worker that is slow to install still isolates the page fails against 0.1.7 and passes here.
  • Cross-Origin-Embedder-Policy is always require-corp; there is no credentialless mode.
  • There is no window.coi configuration. The script's URL takes include and unregister instead, and window.coiServiceWorker only reports the status.

Development

Tools come from mise.toml (mise install).

bun install
mise run check
mise run test

mise run test serves test pages with Bun and runs the Playwright tests in Chromium, Firefox and WebKit.

Releases

Conventional Commits on main drive Release Please. Merging its release PR creates the v<version> tag, and publish.yml publishes the package to npm and JSR with OIDC trusted publishing and provenance.

License

Apache-2.0

版本与发布记录

@aletheia-works/coi-serviceworker 共发布 4 个版本,首次发布于 2026/11/03;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.0latest2026/11/053 天前
0.1.02026/11/034 天前
0.0.0-bootstrap.0bootstrap2026/11/034 天前
0.0.0-stage2026/11/034 天前

关于 @aletheia-works/coi-serviceworker 的常见问题

@aletheia-works/coi-serviceworker 是什么?

Cross-origin isolation for static hosts through a service worker that reloads only once it controls the page

如何安装 @aletheia-works/coi-serviceworker?

在项目目录运行 npm install @aletheia-works/coi-serviceworker,或使用 pnpm add / yarn add / bun add。

@aletheia-works/coi-serviceworker 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aletheia-works/coi-serviceworker 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@aletheia-works/coi-serviceworker 可以商用吗?

@aletheia-works/coi-serviceworker 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@aletheia-works/coi-serviceworker 的下载量有多少?

根据 npm 官方下载统计,@aletheia-works/coi-serviceworker 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,826 次,日均约 94 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aletheia-works/coi-serviceworker 还在维护吗?

最新版本 0.2.0 发布于 2026/11/05(3 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/11/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。