@excom/service-worker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.rush/temp/chunked-rush-logs/service-worker.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/service-worker.build_docs.chunks.jsonl +1 -0
  3. package/.rush/temp/chunked-rush-logs/service-worker.build_package-metas.chunks.jsonl +1 -0
  4. package/.rush/temp/operation/apply-exports/all.log +1 -0
  5. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  6. package/.rush/temp/operation/apply-exports/state.json +3 -0
  7. package/.rush/temp/operation/build_docs/all.log +1 -0
  8. package/.rush/temp/operation/build_docs/log-chunks.jsonl +1 -0
  9. package/.rush/temp/operation/build_docs/state.json +3 -0
  10. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  11. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  12. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  13. package/.rush/temp/shrinkwrap-deps.json +3 -0
  14. package/config/rig.json +5 -0
  15. package/index.ts +17 -0
  16. package/package.json +43 -0
  17. package/rush-logs/service-worker.apply-exports.cache.log +1 -0
  18. package/rush-logs/service-worker.apply-exports.log +1 -0
  19. package/rush-logs/service-worker.build_docs.cache.log +1 -0
  20. package/rush-logs/service-worker.build_docs.log +1 -0
  21. package/rush-logs/service-worker.build_package-metas.cache.log +1 -0
  22. package/rush-logs/service-worker.build_package-metas.log +1 -0
  23. package/service-worker.ts +217 -0
  24. package/support/custom-elements.json +162 -0
  25. package/support/demos/simple.html +3 -0
  26. package/support/dist-docs/service-worker.md +111 -0
  27. package/support/docs/INTERNAL.md +13 -0
  28. package/support/docs/README.md +55 -0
  29. package/support/package-meta.json +104 -0
  30. package/support/tests/service-worker.test.ts +525 -0
  31. package/support/tests/simple.view.test.ts +19 -0
  32. package/tsconfig.json +5 -0
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 36.01658100000009
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 39.918969999999945
3
+ }
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1 @@
1
+ {"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "nonCachedDurationMs": 106.58452799999986
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "../../packages/service-worker": "../../packages/service-worker:CAW4rIBJAvF1JRyDvlLmOCQFV0F55i1bqdXVJbjgKa4=:"
3
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
3
+ "rigPackageName": "@excom/heft-rig",
4
+ "rigProfile": "default"
5
+ }
package/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { ServiceWorker } from "./service-worker";
2
+
3
+ ServiceWorker.define();
4
+
5
+ export { ServiceWorker };
6
+
7
+ type T_HTMLServiceWorkerElement = typeof ServiceWorker.CustomElement;
8
+ declare global {
9
+ interface HTMLServiceWorkerElement extends T_HTMLServiceWorkerElement {}
10
+ interface Window {
11
+ HTMLServiceWorkerElement: HTMLServiceWorkerElement;
12
+ }
13
+ interface HTMLElementTagNameMap {
14
+ "service-worker": HTMLServiceWorkerElement;
15
+ }
16
+ }
17
+ export type { HTMLServiceWorkerElement };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@excom/service-worker",
3
+ "version": "0.1.0",
4
+ "description": "<service-worker> custom element",
5
+ "license": "MIT",
6
+ "engines": {
7
+ "node": ">=24.13.0"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@excom/neutron": "^0.1.0"
12
+ },
13
+ "peerDependencies": {},
14
+ "devDependencies": {
15
+ "@excom/heft-rig": "^0.1.0",
16
+ "@excom/kit-utils": "^0.1.0"
17
+ },
18
+ "repository": {
19
+ "url": "excom-dev/nucleus",
20
+ "directory": "packages/service-worker"
21
+ },
22
+ "homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/service-worker/support/docs/README.md",
23
+ "bugs": "https://github.com/excom-dev/nucleus/issues",
24
+ "keywords": [
25
+ "service-worker",
26
+ "neutron",
27
+ "custom-elements"
28
+ ],
29
+ "excom": {
30
+ "packageType": "kit-element"
31
+ },
32
+ "scripts": {
33
+ "build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
34
+ "build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
35
+ "format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
36
+ "test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
37
+ "coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
38
+ "dev": "node node_modules/@excom/heft-rig/scripts/vite-dev.mjs",
39
+ "preview": "node node_modules/@excom/heft-rig/scripts/vite-preview.mjs",
40
+ "build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs",
41
+ "build:docs": "node node_modules/@excom/heft-rig/scripts/build-docs.mjs"
42
+ }
43
+ }
@@ -0,0 +1 @@
1
+ Caching has been disabled for this project's "apply-exports" command.
@@ -0,0 +1 @@
1
+ Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:docs" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-docs.mjs
@@ -0,0 +1 @@
1
+ This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
@@ -0,0 +1 @@
1
+ Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
@@ -0,0 +1,217 @@
1
+ import {
2
+ ConstructorType,
3
+ Neutron,
4
+ TEvent,
5
+ TokenList,
6
+ } from "@excom/neutron";
7
+
8
+ const SW_EVENTS = ["message", "messageerror", "controllerchange"];
9
+
10
+ export type ServiceWorkerMessageEvent = TEvent & {
11
+ type: "message";
12
+ detail: unknown;
13
+ };
14
+
15
+ export type ServiceWorkerMessageErrorEvent = TEvent & {
16
+ type: "messageerror";
17
+ detail: unknown;
18
+ };
19
+
20
+ export type ServiceWorkerControllerChangeEvent = TEvent & {
21
+ type: "controllerchange";
22
+ detail: void;
23
+ };
24
+
25
+ /** What this element knows about `navigator.serviceWorker`. */
26
+ export interface ServiceWorkerProvision {
27
+ /** The Service Worker API is available. */
28
+ isSupported: boolean;
29
+ /** `navigator.serviceWorker.ready` has resolved. */
30
+ isReady: boolean;
31
+ /** A worker currently controls this page (`navigator.serviceWorker.controller`). */
32
+ hasController: boolean;
33
+ /** The ready registration's scope; `null` until `ready` resolves. */
34
+ scope: string | null;
35
+ }
36
+
37
+ const hasController = () => !!navigator.serviceWorker?.controller;
38
+
39
+ /**
40
+ * Observes an already-registered Service Worker. This element does **not**
41
+ * register a Service Worker itself — your app must call
42
+ * `navigator.serviceWorker.register(...)` separately; this element only
43
+ * reports on `navigator.serviceWorker` and, optionally, relays its events
44
+ * onto itself as plain DOM events for Quark / `<event-handler>` to react
45
+ * to.
46
+ *
47
+ * `.provision` holds `{ isSupported, isReady, hasController, scope }`, kept
48
+ * current on connect, when `ready` resolves, and on every
49
+ * `controllerchange` — whether or not that event is relayed.
50
+ *
51
+ * @summary Observes `navigator.serviceWorker` — does not register a SW.
52
+ *
53
+ * @fires message - Relayed verbatim from `navigator.serviceWorker`'s
54
+ * `message` event when `message` is included in `relay-events`. Not
55
+ * tag-prefixed. `event.detail` is `event.data` from the original
56
+ * message.
57
+ * @type ServiceWorkerMessageEvent
58
+ * @fires messageerror - Relayed verbatim from `navigator.serviceWorker`'s
59
+ * `messageerror` event when `messageerror` is included in
60
+ * `relay-events`. Not tag-prefixed.
61
+ * @type ServiceWorkerMessageErrorEvent
62
+ * @fires controllerchange - Relayed verbatim from
63
+ * `navigator.serviceWorker`'s `controllerchange` event when
64
+ * `controllerchange` is included in `relay-events`. Not tag-prefixed.
65
+ * @type ServiceWorkerControllerChangeEvent
66
+ */
67
+ export const ServiceWorker = Neutron({
68
+ tag: "service-worker",
69
+ reflectDefaultProps: ["isMounted"],
70
+ props: {
71
+ // options
72
+ /**
73
+ * @option
74
+ * Space-separated `navigator.serviceWorker` events to relay onto this
75
+ * element. Bare attribute (no value) relays all three. Event names
76
+ * are relayed as-is — not prefixed with the tag.
77
+ * @values message | messageerror | controllerchange
78
+ */
79
+ relayEvents: TokenList,
80
+ // state
81
+ /**
82
+ * @state
83
+ * `navigator.serviceWorker.ready` has resolved — an active worker is
84
+ * controlling the page.
85
+ */
86
+ isReady: Boolean,
87
+ /**
88
+ * @state
89
+ * The Service Worker API is available (`'serviceWorker' in
90
+ * navigator`).
91
+ */
92
+ isSupported: Boolean,
93
+ /**
94
+ * @provision
95
+ * `{ isSupported, isReady, hasController, scope }` — set on connect,
96
+ * when `ready` resolves (`scope` comes from the registration), and on
97
+ * every `controllerchange`. Not reflected as an attribute.
98
+ * @type ServiceWorkerProvision
99
+ */
100
+ provision: Object as unknown as ConstructorType<ServiceWorkerProvision>,
101
+ // private
102
+ swContainer: Object as unknown as typeof ServiceWorkerContainer,
103
+ },
104
+ })
105
+ .defineMethods({
106
+ messageCallback: (_, event: MessageEvent) => ({
107
+ emit: [event.type, { detail: event.data }],
108
+ }),
109
+ // Method so it reads the element when `ready` settles, and no-ops if
110
+ // the element has since left the document.
111
+ readyCallback: (
112
+ { isConnected, isSupported, provision },
113
+ registration?: ServiceWorkerRegistration
114
+ ) =>
115
+ isConnected && {
116
+ isReady: true,
117
+ provision: {
118
+ isSupported: !!isSupported,
119
+ isReady: true,
120
+ hasController: hasController(),
121
+ scope: registration?.scope ?? provision?.scope ?? null,
122
+ },
123
+ },
124
+ // Internal `controllerchange` listener, independent of `relay-events`
125
+ _handleControllerChange: ({ isSupported, isReady, provision }) => ({
126
+ provision: {
127
+ isSupported: !!isSupported,
128
+ isReady: !!isReady,
129
+ hasController: hasController(),
130
+ scope: provision?.scope ?? null,
131
+ },
132
+ }),
133
+ /**
134
+ * Apply `relay-events` to `navigator.serviceWorker`: drop the
135
+ * `previous` names (or all three when relaying stops) and add the
136
+ * current set. Also on a real reconnect, since `onDisconnected`
137
+ * removes them.
138
+ */
139
+ // @ts-expect-error - TODO: method typing
140
+ _applyRelay: ({ relayEvents, messageCallback }, previous?: string[]) => {
141
+ if (!navigator.serviceWorker) return;
142
+ previous?.forEach((eventName) =>
143
+ navigator.serviceWorker.removeEventListener(eventName, messageCallback)
144
+ );
145
+ if (!relayEvents) {
146
+ SW_EVENTS.forEach((eventName) =>
147
+ navigator.serviceWorker.removeEventListener(
148
+ eventName,
149
+ messageCallback
150
+ )
151
+ );
152
+ } else {
153
+ // Add the current set. Bare `relay-events` (no value) means all.
154
+ const events = relayEvents?.length > 0 ? relayEvents : SW_EVENTS;
155
+ events.forEach((eventName) =>
156
+ navigator.serviceWorker.addEventListener(eventName, messageCallback)
157
+ );
158
+ }
159
+ },
160
+ })
161
+ .onConnected(
162
+ ({
163
+ readyCallback,
164
+ _handleControllerChange,
165
+ isMoving,
166
+ wasMounted,
167
+ isReady,
168
+ provision,
169
+ }) => {
170
+ if (isMoving) return;
171
+ if ("serviceWorker" in navigator) {
172
+ navigator.serviceWorker.ready.then(readyCallback);
173
+ return [
174
+ {
175
+ isSupported: true,
176
+ swContainer: navigator.serviceWorker,
177
+ // Tracked by Neutron: dropped on disconnect, restored on reconnect
178
+ addListener: [
179
+ "controllerchange",
180
+ _handleControllerChange,
181
+ { target: navigator.serviceWorker },
182
+ ],
183
+ provision: {
184
+ isSupported: true,
185
+ isReady: !!isReady,
186
+ hasController: hasController(),
187
+ scope: provision?.scope ?? null,
188
+ },
189
+ },
190
+ /* Relay listeners are plain `navigator.serviceWorker` listeners,
191
+ removed in `onDisconnected`. Re-apply after a real reconnect
192
+ (first mount applies them via `relayEvents`). */
193
+ ...(wasMounted ? [{ _applyRelay: [] }] : []),
194
+ ];
195
+ }
196
+ return {
197
+ provision: {
198
+ isSupported: false,
199
+ isReady: false,
200
+ hasController: false,
201
+ scope: null,
202
+ },
203
+ };
204
+ }
205
+ )
206
+ .onPropChanged("relayEvents", (_, previous) => ({
207
+ _applyRelay: [previous?.relayEvents],
208
+ }))
209
+ .onDisconnected(({ messageCallback, isMoving }) => {
210
+ if (isMoving) return;
211
+ // Drop relay listeners. Neutron removes the tracked `controllerchange`.
212
+ if (navigator.serviceWorker) {
213
+ SW_EVENTS.forEach((eventName) =>
214
+ navigator.serviceWorker.removeEventListener(eventName, messageCallback)
215
+ );
216
+ }
217
+ });
@@ -0,0 +1,162 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "modules": [
4
+ {
5
+ "kind": "javascript-module",
6
+ "path": "service-worker.ts",
7
+ "declarations": [
8
+ {
9
+ "kind": "class",
10
+ "name": "ServiceWorker",
11
+ "customElement": true,
12
+ "tagName": "service-worker",
13
+ "summary": "Observes `navigator.serviceWorker` — does not register a SW.",
14
+ "description": "What this element knows about `navigator.serviceWorker`.",
15
+ "attributes": [
16
+ {
17
+ "name": "relay-events",
18
+ "type": {
19
+ "text": "tokenlist"
20
+ },
21
+ "description": "Space-separated `navigator.serviceWorker` events to relay onto this element. Bare attribute (no value) relays all three. Event names are relayed as-is — not prefixed with the tag.",
22
+ "fieldName": "relayEvents",
23
+ "values": [
24
+ "message",
25
+ "messageerror",
26
+ "controllerchange"
27
+ ]
28
+ },
29
+ {
30
+ "name": "is-ready",
31
+ "type": {
32
+ "text": "boolean"
33
+ },
34
+ "description": "`navigator.serviceWorker.ready` has resolved — an active worker is controlling the page.",
35
+ "fieldName": "isReady"
36
+ },
37
+ {
38
+ "name": "is-supported",
39
+ "type": {
40
+ "text": "boolean"
41
+ },
42
+ "description": "The Service Worker API is available (`'serviceWorker' in navigator`).",
43
+ "fieldName": "isSupported"
44
+ }
45
+ ],
46
+ "members": [
47
+ {
48
+ "kind": "field",
49
+ "name": "relayEvents",
50
+ "type": {
51
+ "text": "tokenlist"
52
+ },
53
+ "privacy": "public",
54
+ "readonly": false,
55
+ "description": "Space-separated `navigator.serviceWorker` events to relay onto this element. Bare attribute (no value) relays all three. Event names are relayed as-is — not prefixed with the tag.",
56
+ "_neutron": {
57
+ "surface": "option"
58
+ }
59
+ },
60
+ {
61
+ "kind": "field",
62
+ "name": "isReady",
63
+ "type": {
64
+ "text": "boolean"
65
+ },
66
+ "privacy": "public",
67
+ "readonly": true,
68
+ "description": "`navigator.serviceWorker.ready` has resolved — an active worker is controlling the page.",
69
+ "_neutron": {
70
+ "surface": "state"
71
+ }
72
+ },
73
+ {
74
+ "kind": "field",
75
+ "name": "isSupported",
76
+ "type": {
77
+ "text": "boolean"
78
+ },
79
+ "privacy": "public",
80
+ "readonly": true,
81
+ "description": "The Service Worker API is available (`'serviceWorker' in navigator`).",
82
+ "_neutron": {
83
+ "surface": "state"
84
+ }
85
+ },
86
+ {
87
+ "kind": "field",
88
+ "name": "provision",
89
+ "type": {
90
+ "text": "ServiceWorkerProvision",
91
+ "expanded": "{ isSupported: boolean; isReady: boolean; hasController: boolean; scope: string | null; }"
92
+ },
93
+ "privacy": "public",
94
+ "readonly": false,
95
+ "description": "`{ isSupported, isReady, hasController, scope }` — set on connect, when `ready` resolves (`scope` comes from the registration), and on every `controllerchange`. Not reflected as an attribute.",
96
+ "_neutron": {
97
+ "surface": "option"
98
+ }
99
+ }
100
+ ],
101
+ "events": [
102
+ {
103
+ "name": "message",
104
+ "description": "Relayed verbatim from `navigator.serviceWorker`'s `message` event when `message` is included in `relay-events`. Not tag-prefixed. `event.detail` is `event.data` from the original message.",
105
+ "type": {
106
+ "text": "ServiceWorkerMessageEvent",
107
+ "expanded": "CustomEvent & { type: \"message\"; detail: unknown; bubbles: true; cancelable: true; composed: true }"
108
+ }
109
+ },
110
+ {
111
+ "name": "messageerror",
112
+ "description": "Relayed verbatim from `navigator.serviceWorker`'s `messageerror` event when `messageerror` is included in `relay-events`. Not tag-prefixed.",
113
+ "type": {
114
+ "text": "ServiceWorkerMessageErrorEvent",
115
+ "expanded": "CustomEvent & { type: \"messageerror\"; detail: unknown; bubbles: true; cancelable: true; composed: true }"
116
+ }
117
+ },
118
+ {
119
+ "name": "controllerchange",
120
+ "description": "Relayed verbatim from `navigator.serviceWorker`'s `controllerchange` event when `controllerchange` is included in `relay-events`. Not tag-prefixed.",
121
+ "type": {
122
+ "text": "ServiceWorkerControllerChangeEvent",
123
+ "expanded": "CustomEvent & { type: \"controllerchange\"; detail: void; bubbles: true; cancelable: true; composed: true }"
124
+ }
125
+ }
126
+ ],
127
+ "_neutron": {
128
+ "provisions": [
129
+ {
130
+ "name": "provision",
131
+ "type": {
132
+ "text": "ServiceWorkerProvision",
133
+ "expanded": "{ isSupported: boolean; isReady: boolean; hasController: boolean; scope: string | null; }"
134
+ },
135
+ "description": "`{ isSupported, isReady, hasController, scope }` — set on connect, when `ready` resolves (`scope` comes from the registration), and on every `controllerchange`. Not reflected as an attribute.",
136
+ "fieldName": "provision"
137
+ }
138
+ ]
139
+ }
140
+ }
141
+ ],
142
+ "exports": [
143
+ {
144
+ "kind": "js",
145
+ "name": "ServiceWorker",
146
+ "declaration": {
147
+ "name": "ServiceWorker",
148
+ "module": "service-worker.ts"
149
+ }
150
+ },
151
+ {
152
+ "kind": "custom-element-definition",
153
+ "name": "service-worker",
154
+ "declaration": {
155
+ "name": "ServiceWorker",
156
+ "module": "service-worker.ts"
157
+ }
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ }
@@ -0,0 +1,3 @@
1
+ <div>
2
+ <service-worker></service-worker>
3
+ </div>
@@ -0,0 +1,111 @@
1
+ # service-worker
2
+
3
+ Observes `navigator.serviceWorker` and optionally relays its events — zero app JS required.
4
+
5
+
6
+ ```html
7
+ <div>
8
+ <service-worker></service-worker>
9
+ </div>
10
+ ```
11
+
12
+
13
+ ## Features
14
+
15
+ - **Observation only** Reports on an existing Service Worker; never registers one
16
+ - **Support detection** `is-supported` reflects API availability
17
+ - **Ready state** `is-ready` reflects once an active worker controls the page
18
+ - **Event relay** `relay-events` forwards `message` / `messageerror` / `controllerchange` as plain DOM events
19
+ - **Bindable state** `.provision` is `{ isSupported, isReady, hasController, scope }` — kept current on connect, `ready`, and every `controllerchange`; read it from Quark with `prop("provision")`
20
+
21
+ ## Installation
22
+
23
+
24
+ `@excom/service-worker` v0.1.0
25
+
26
+ ```bash
27
+ pnpm add @excom/service-worker
28
+ ```
29
+
30
+ ```bash
31
+ npm install @excom/service-worker
32
+ ```
33
+
34
+ ```bash
35
+ yarn add @excom/service-worker
36
+ ```
37
+
38
+ ### Import
39
+
40
+ ```ts
41
+ import "@excom/service-worker";
42
+ ```
43
+
44
+
45
+
46
+ ## Usage
47
+
48
+ This element only *observes* an already-registered Service Worker — it does not call `navigator.serviceWorker.register(...)` itself. Register your Service Worker separately (in app code, or your build tool), then drop this element anywhere to expose its state as attributes and, optionally, relay its events.
49
+
50
+ ```html
51
+ <service-worker relay-events></service-worker>
52
+ <event-handler listen-for="message" fire-event="sw-message-received">
53
+ ...
54
+ </event-handler>
55
+ ```
56
+
57
+ Relayed events (`message`, `messageerror`, `controllerchange`) are dispatched with their original names — they are **not** prefixed with `service-worker-`.
58
+
59
+ ### API Reference
60
+
61
+
62
+ #### Attributes
63
+
64
+ | Name | Surface | Type | Default | Values | Description |
65
+ | --- | --- | --- | --- | --- | --- |
66
+ | `relay-events` | option | `tokenlist` | | `"message"` \| `"messageerror"` \| `"controllerchange"` | Space-separated `navigator.serviceWorker` events to relay onto this element. Bare attribute (no value) relays all three. Event names are relayed as-is — not prefixed with the tag. |
67
+ | `is-ready` | state | `boolean` | | | `navigator.serviceWorker.ready` has resolved — an active worker is controlling the page. |
68
+ | `is-supported` | state | `boolean` | | | The Service Worker API is available (`'serviceWorker' in navigator`). |
69
+
70
+ #### Provision
71
+
72
+ | Name | Type | Description |
73
+ | --- | --- | --- |
74
+ | `provision` | `ServiceWorkerProvision` (`{ isSupported: boolean; isReady: boolean; hasController: boolean; scope: string \| null; }`) | `{ isSupported, isReady, hasController, scope }` — set on connect, when `ready` resolves (`scope` comes from the registration), and on every `controllerchange`. Not reflected as an attribute. |
75
+
76
+ #### Fires
77
+
78
+ | Name | Type | Description |
79
+ | --- | --- | --- |
80
+ | `message` | `ServiceWorkerMessageEvent` (`CustomEvent & { type: "message"; detail: unknown; bubbles: true; cancelable: true; composed: true }`) | Relayed verbatim from `navigator.serviceWorker`'s `message` event when `message` is included in `relay-events`. Not tag-prefixed. `event.detail` is `event.data` from the original message. |
81
+ | `messageerror` | `ServiceWorkerMessageErrorEvent` (`CustomEvent & { type: "messageerror"; detail: unknown; bubbles: true; cancelable: true; composed: true }`) | Relayed verbatim from `navigator.serviceWorker`'s `messageerror` event when `messageerror` is included in `relay-events`. Not tag-prefixed. |
82
+ | `controllerchange` | `ServiceWorkerControllerChangeEvent` (`CustomEvent & { type: "controllerchange"; detail: void; bubbles: true; cancelable: true; composed: true }`) | Relayed verbatim from `navigator.serviceWorker`'s `controllerchange` event when `controllerchange` is included in `relay-events`. Not tag-prefixed. |
83
+
84
+
85
+
86
+ ### Examples
87
+
88
+ #### Support / mount / ready state
89
+
90
+ `is-supported`, `is-mounted` (reflected by default), and `is-ready` are all plain attributes — style or branch on them with CSS. `is-ready` needs an app-registered Service Worker to ever resolve, so it will likely stay unset in this docs site.
91
+
92
+
93
+ ```html
94
+ <div>
95
+ <service-worker></service-worker>
96
+ </div>
97
+ ```
98
+
99
+
100
+ #### Relay messages from your Service Worker
101
+
102
+ Relaying `message` / `messageerror` / `controllerchange` requires a Service Worker that your app has already registered and that is actively posting messages — this is not runnable in this docs site, but works like so once wired up:
103
+
104
+ ```html
105
+ <service-worker relay-events="message"></service-worker>
106
+ <script>
107
+ document
108
+ .querySelector("service-worker")
109
+ .addEventListener("message", (e) => console.log(e.detail));
110
+ </script>
111
+ ```
@@ -0,0 +1,13 @@
1
+ # Internal notes
2
+
3
+ ## TODO: re-listen on reconnect
4
+
5
+ `onDisconnected` removes the `navigator.serviceWorker` listeners added for
6
+ `relay-events`, but if the element is later reconnected (moved in the DOM
7
+ counts as a disconnect + connect unless `isMoving` is set), `onConnected`
8
+ never re-attaches them — only `onPropChanged("relayEvents", ...)` does, and
9
+ that only fires on an actual attribute change, not on reconnect.
10
+
11
+ Fix: either re-run the `relayEvents` listener setup from `onConnected` (skip
12
+ when `wasMounted` is true, mirroring the `swContainer` re-detection guard),
13
+ or track listener state independently of the prop-change handler.