@inkandswitch/patchwork-bootloader 0.0.8 → 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.
- package/CHANGELOG.md +23 -0
- package/dist/externals.js +5 -0
- package/dist/service-worker.js +59 -74
- package/dist/setup.d.ts +2 -4
- package/dist/setup.js +147 -27
- package/dist/site.d.ts +3 -2
- package/dist/site.js +38 -26
- package/dist/types.d.ts +4 -0
- package/package.json +14 -14
- package/src/externals.ts +5 -0
- package/src/service-worker.ts +77 -103
- package/src/setup.ts +165 -33
- package/src/site.ts +54 -32
- package/src/types.ts +10 -0
package/src/setup.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ServiceWorkerRepoChannelListener,
|
|
3
|
+
SetupServiceWorkerOptions,
|
|
4
|
+
SetupServiceWorkerResult,
|
|
5
|
+
} from "./types.js";
|
|
2
6
|
import debug from "debug";
|
|
3
7
|
|
|
4
8
|
const debugging = debug.enabled("patchwork:serviceworker");
|
|
5
9
|
|
|
6
10
|
const key = "patchworkServiceWorkerCacheVersion";
|
|
11
|
+
let nextRepoChannelId = 0;
|
|
12
|
+
let serviceWorkerInstanceId: string | undefined;
|
|
7
13
|
|
|
8
14
|
function bumpServiceWorkerCacheVersion() {
|
|
9
15
|
const version = new Date().valueOf().toString(36);
|
|
@@ -40,6 +46,21 @@ export function bumpServiceWorkerCache(
|
|
|
40
46
|
|
|
41
47
|
(window as any).bumpServiceWorkerCache = bumpServiceWorkerCache;
|
|
42
48
|
|
|
49
|
+
function configureServiceWorker(sw: ServiceWorker | null) {
|
|
50
|
+
if (!sw) return;
|
|
51
|
+
sw.postMessage({ type: "debug", debug: debugging });
|
|
52
|
+
const cachename = getServiceWorkerCacheVersion();
|
|
53
|
+
if (cachename) sw.postMessage({ type: "cachename", cachename });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function updateServiceWorkerInstanceId(next: unknown) {
|
|
57
|
+
if (typeof next !== "string") return false;
|
|
58
|
+
const changed =
|
|
59
|
+
serviceWorkerInstanceId != null && serviceWorkerInstanceId !== next;
|
|
60
|
+
serviceWorkerInstanceId = next;
|
|
61
|
+
return changed;
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
/** Wait for a registration to have an active worker */
|
|
44
65
|
function waitForActive(reg: ServiceWorkerRegistration): Promise<ServiceWorker> {
|
|
45
66
|
if (reg.active) return Promise.resolve(reg.active);
|
|
@@ -53,35 +74,136 @@ function waitForActive(reg: ServiceWorkerRegistration): Promise<ServiceWorker> {
|
|
|
53
74
|
});
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
async function openRepoChannel(): Promise<{
|
|
78
|
+
port: MessagePort;
|
|
79
|
+
workerInstanceChanged: boolean;
|
|
80
|
+
}> {
|
|
81
|
+
const controller = navigator.serviceWorker.controller;
|
|
82
|
+
if (!controller) {
|
|
83
|
+
throw new Error("no service worker controller");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Send a MessagePort so the SW's repo can sync with clients, and wait for
|
|
87
|
+
// the SW to confirm its repo is constructed before returning. The
|
|
88
|
+
// MessageChannel adapter's whenReady() force-resolves after 100ms regardless
|
|
89
|
+
// of the other end's state, so it can't be used as a real readiness signal
|
|
90
|
+
// on first install (when the SW still has to fetch wasm and build its repo).
|
|
91
|
+
const id = ++nextRepoChannelId;
|
|
92
|
+
let workerInstanceChanged = false;
|
|
93
|
+
const { port1, port2 } = new MessageChannel();
|
|
94
|
+
const swReady = new Promise<void>((resolve, reject) => {
|
|
95
|
+
let timeout: ReturnType<typeof setTimeout>;
|
|
96
|
+
const cleanup = () => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
navigator.serviceWorker.removeEventListener("message", listener);
|
|
99
|
+
};
|
|
100
|
+
const listener = (event: MessageEvent) => {
|
|
101
|
+
if (event.data?.id != null && event.data.id !== id) return;
|
|
102
|
+
if (event.data?.type === "port-ready") {
|
|
103
|
+
workerInstanceChanged = updateServiceWorkerInstanceId(
|
|
104
|
+
event.data.workerInstanceId
|
|
105
|
+
);
|
|
106
|
+
cleanup();
|
|
107
|
+
resolve();
|
|
108
|
+
} else if (event.data?.type === "port-failed") {
|
|
109
|
+
workerInstanceChanged = updateServiceWorkerInstanceId(
|
|
110
|
+
event.data.workerInstanceId
|
|
111
|
+
);
|
|
112
|
+
cleanup();
|
|
113
|
+
reject(new Error(`service worker init failed: ${event.data.error}`));
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
navigator.serviceWorker.addEventListener("message", listener);
|
|
117
|
+
// Failsafe: don't block boot forever if the SW never replies. Surface the
|
|
118
|
+
// issue and let the rest of the site come up rather than hanging on a
|
|
119
|
+
// blank page.
|
|
120
|
+
timeout = setTimeout(() => {
|
|
121
|
+
cleanup();
|
|
122
|
+
reject(new Error("service worker port-ready timeout"));
|
|
123
|
+
}, 30_000);
|
|
124
|
+
});
|
|
125
|
+
controller.postMessage({ type: "port", id }, [port2]);
|
|
126
|
+
try {
|
|
127
|
+
await swReady;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.warn(
|
|
130
|
+
"proceeding without SW ready ack:",
|
|
131
|
+
err instanceof Error ? err.message : err
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return { port: port1, workerInstanceChanged };
|
|
135
|
+
}
|
|
136
|
+
|
|
56
137
|
export default async function setupServiceWorker(
|
|
57
138
|
options?: SetupServiceWorkerOptions
|
|
58
|
-
) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
139
|
+
): Promise<SetupServiceWorkerResult> {
|
|
140
|
+
const repoChannelListeners = new Set<ServiceWorkerRepoChannelListener>();
|
|
141
|
+
let reconnectPromise: Promise<void> | null = null;
|
|
142
|
+
|
|
143
|
+
const reconnectRepoChannels = (reason: string) => {
|
|
144
|
+
if (reconnectPromise) return reconnectPromise;
|
|
145
|
+
reconnectPromise = (async () => {
|
|
146
|
+
console.info(
|
|
147
|
+
`%cservice worker ${reason}, reconnecting repo channels...`,
|
|
148
|
+
"color: pink; font-weight: bold"
|
|
149
|
+
);
|
|
150
|
+
configureServiceWorker(navigator.serviceWorker.controller);
|
|
151
|
+
for (const listener of repoChannelListeners) {
|
|
152
|
+
try {
|
|
153
|
+
const { port } = await openRepoChannel();
|
|
154
|
+
await listener(port);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error("service worker repo channel listener failed", err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})().finally(() => {
|
|
160
|
+
reconnectPromise = null;
|
|
161
|
+
});
|
|
162
|
+
return reconnectPromise;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const pingServiceWorker = async () => {
|
|
166
|
+
const controller = navigator.serviceWorker.controller;
|
|
167
|
+
if (!controller) return;
|
|
168
|
+
const { port1, port2 } = new MessageChannel();
|
|
169
|
+
const pong = new Promise<unknown>((resolve, reject) => {
|
|
170
|
+
const timeout = setTimeout(() => {
|
|
171
|
+
port1.close();
|
|
172
|
+
reject(new Error("service worker pong timeout"));
|
|
173
|
+
}, 5_000);
|
|
174
|
+
port1.onmessage = (event) => {
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
port1.close();
|
|
177
|
+
resolve(event.data?.workerInstanceId);
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
controller.postMessage({ type: "ping" }, [port2]);
|
|
181
|
+
try {
|
|
182
|
+
const restarted = updateServiceWorkerInstanceId(await pong);
|
|
183
|
+
if (restarted) {
|
|
184
|
+
await reconnectRepoChannels("restarted");
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(
|
|
188
|
+
"service worker ping failed:",
|
|
189
|
+
err instanceof Error ? err.message : err
|
|
190
|
+
);
|
|
72
191
|
}
|
|
73
|
-
}
|
|
192
|
+
};
|
|
74
193
|
|
|
75
194
|
const path = options?.path ?? "/service-worker.js";
|
|
195
|
+
// No controller at this point means the page loaded without a service
|
|
196
|
+
// worker — i.e. this is a first-time install (or a hard reload). Wait for
|
|
197
|
+
// activation so the app boots with the SW in control of generated fetches.
|
|
76
198
|
const reg = await navigator.serviceWorker.register(path, { type: "module" });
|
|
77
199
|
|
|
78
200
|
// If there's an update waiting or installing, wait for it to activate
|
|
201
|
+
let active = reg.active;
|
|
79
202
|
if (reg.installing || reg.waiting) {
|
|
80
|
-
await waitForActive(reg);
|
|
203
|
+
active = await waitForActive(reg);
|
|
81
204
|
}
|
|
82
205
|
|
|
83
|
-
|
|
84
|
-
active.postMessage({ type: "debug", debug: debugging });
|
|
206
|
+
configureServiceWorker(active);
|
|
85
207
|
|
|
86
208
|
// Wait for the controller to be available
|
|
87
209
|
if (!navigator.serviceWorker.controller) {
|
|
@@ -94,25 +216,20 @@ export default async function setupServiceWorker(
|
|
|
94
216
|
});
|
|
95
217
|
}
|
|
96
218
|
|
|
97
|
-
// Send a MessagePort so the SW's repo can sync with clients
|
|
98
|
-
const { port1, port2 } = new MessageChannel();
|
|
99
|
-
navigator.serviceWorker.controller!.postMessage({ type: "port" }, [port2]);
|
|
100
|
-
|
|
101
219
|
// Keepalive — Chromium idles out service workers after ~30s of inactivity,
|
|
102
220
|
// which tears down the in-memory Repo and forces a cold restart on the next
|
|
103
|
-
// fetch.
|
|
221
|
+
// fetch. Ping through a MessageChannel so we can detect when a restarted SW
|
|
222
|
+
// has a new in-memory Repo and reconnect all repo channels.
|
|
104
223
|
setInterval(() => {
|
|
105
|
-
|
|
224
|
+
void pingServiceWorker();
|
|
106
225
|
}, 20_000);
|
|
107
226
|
|
|
108
|
-
//
|
|
109
|
-
// activation doesn't
|
|
227
|
+
// Reconnect on future SW updates (added after setup so the initial
|
|
228
|
+
// activation doesn't notify before callers subscribe).
|
|
110
229
|
navigator.serviceWorker.addEventListener("controllerchange", function () {
|
|
111
|
-
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
);
|
|
115
|
-
location.reload();
|
|
230
|
+
void reconnectRepoChannels("took control").catch((err) => {
|
|
231
|
+
console.error("service worker reconnect failed", err);
|
|
232
|
+
});
|
|
116
233
|
});
|
|
117
234
|
|
|
118
235
|
console.log(
|
|
@@ -120,5 +237,20 @@ export default async function setupServiceWorker(
|
|
|
120
237
|
"background: #fcf2f0; color: #333; border: 2px solid; border-radius: 4px"
|
|
121
238
|
);
|
|
122
239
|
|
|
123
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
async subscribeToRepoChannel(listener) {
|
|
242
|
+
const { port, workerInstanceChanged } = await openRepoChannel();
|
|
243
|
+
if (workerInstanceChanged) {
|
|
244
|
+
await reconnectRepoChannels("restarted");
|
|
245
|
+
}
|
|
246
|
+
repoChannelListeners.add(listener);
|
|
247
|
+
try {
|
|
248
|
+
await listener(port);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
repoChannelListeners.delete(listener);
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
return () => repoChannelListeners.delete(listener);
|
|
254
|
+
},
|
|
255
|
+
};
|
|
124
256
|
}
|
package/src/site.ts
CHANGED
|
@@ -44,11 +44,15 @@ import {
|
|
|
44
44
|
getRegistry,
|
|
45
45
|
registerPlugins,
|
|
46
46
|
resolveAccountHandle,
|
|
47
|
+
unregisterPlugins,
|
|
47
48
|
} from "@inkandswitch/patchwork-plugins";
|
|
48
49
|
import * as plugins from "@inkandswitch/patchwork-plugins";
|
|
49
50
|
|
|
50
51
|
import setupServiceWorker from "./setup.js";
|
|
52
|
+
import type { ServiceWorkerRepoChannelListener } from "./types.js";
|
|
51
53
|
import { SwLogReader } from "./sw-logger.js";
|
|
54
|
+
import debug from "debug";
|
|
55
|
+
const log = debug("patchwork:bootloader:site");
|
|
52
56
|
|
|
53
57
|
declare global {
|
|
54
58
|
interface Window {
|
|
@@ -56,10 +60,9 @@ declare global {
|
|
|
56
60
|
Automerge: typeof import("@automerge/automerge");
|
|
57
61
|
AutomergeRepo: typeof import("@automerge/automerge-repo");
|
|
58
62
|
repo: Repo;
|
|
59
|
-
getRepoChannel: () => MessagePort;
|
|
60
63
|
patchwork: {
|
|
61
64
|
repo: Repo;
|
|
62
|
-
|
|
65
|
+
packages: ModuleWatcher;
|
|
63
66
|
plugins: typeof plugins;
|
|
64
67
|
accountDocHandle: DocHandle<AccountDoc>;
|
|
65
68
|
sw: {
|
|
@@ -67,6 +70,9 @@ declare global {
|
|
|
67
70
|
tailLogs: (n?: number) => ReturnType<typeof SwLogReader.tail>;
|
|
68
71
|
exportLogs: () => Promise<string>;
|
|
69
72
|
clearLogs: () => Promise<void>;
|
|
73
|
+
subscribeToRepoChannel: (
|
|
74
|
+
listener: ServiceWorkerRepoChannelListener
|
|
75
|
+
) => Promise<() => void>;
|
|
70
76
|
};
|
|
71
77
|
};
|
|
72
78
|
uncache: (match: string) => Promise<void>;
|
|
@@ -125,6 +131,11 @@ const DEFAULT_REMOTE_STORAGE_ID =
|
|
|
125
131
|
const BIG_PATCHWORK_HASH_REGEX =
|
|
126
132
|
/(?<title>[A-Za-z0-9-]+)--(?<docId>[1-9A-HJ-NP-Za-km-z]+)(?<type>\?=[^&?]+)?/;
|
|
127
133
|
|
|
134
|
+
const [automergeWasm, subductionWasm] = await Promise.all([
|
|
135
|
+
fetch("/automerge.wasm?main").then((r) => r.bytes()),
|
|
136
|
+
fetch("/subduction.wasm").then((r) => r.bytes()),
|
|
137
|
+
]);
|
|
138
|
+
|
|
128
139
|
/**
|
|
129
140
|
* Boot a Patchwork browser site.
|
|
130
141
|
*
|
|
@@ -138,12 +149,8 @@ export async function bootPatchworkSite(
|
|
|
138
149
|
config: SiteConfig
|
|
139
150
|
): Promise<BootResult> {
|
|
140
151
|
const defaultModulesUrl = resolveDefaultModulesUrl(config.defaultModulesUrl);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const [automergeWasm, subductionWasm] = await Promise.all([
|
|
144
|
-
fetch("/automerge.wasm").then((r) => r.bytes()),
|
|
145
|
-
fetch("/subduction.wasm").then((r) => r.bytes()),
|
|
146
|
-
]);
|
|
152
|
+
showLoadingAnimation();
|
|
153
|
+
log(`booting`, config);
|
|
147
154
|
await initializeWasm(automergeWasm);
|
|
148
155
|
initSubductionSync(subductionWasm);
|
|
149
156
|
|
|
@@ -153,16 +160,26 @@ export async function bootPatchworkSite(
|
|
|
153
160
|
return peerId.includes("service-worker");
|
|
154
161
|
},
|
|
155
162
|
enableRemoteHeadsGossiping: true,
|
|
163
|
+
peerId:
|
|
164
|
+
`${config.titleSuffix}-tab-${crypto.randomUUID()}` as AutomergeRepo.PeerId,
|
|
156
165
|
});
|
|
166
|
+
|
|
157
167
|
repo.subscribeToRemotes(
|
|
158
168
|
config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]
|
|
159
169
|
);
|
|
160
170
|
|
|
161
171
|
const sw = await setupServiceWorker();
|
|
162
172
|
if (!sw) throw new Error("Failed to set up service worker");
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
let activeServiceWorkerPort: MessagePort | undefined;
|
|
174
|
+
const connectServiceWorkerPort = async (port: MessagePort) => {
|
|
175
|
+
const previousPort = activeServiceWorkerPort;
|
|
176
|
+
activeServiceWorkerPort = port;
|
|
177
|
+
const net = new MessageChannelNetworkAdapter(port);
|
|
178
|
+
repo.networkSubsystem.addNetworkAdapter(net);
|
|
179
|
+
await net.whenReady();
|
|
180
|
+
previousPort?.close();
|
|
181
|
+
};
|
|
182
|
+
await sw.subscribeToRepoChannel(connectServiceWorkerPort);
|
|
166
183
|
|
|
167
184
|
installDevConsoleGlobals(repo);
|
|
168
185
|
registerPatchworkViewElement({ repo });
|
|
@@ -173,8 +190,9 @@ export async function bootPatchworkSite(
|
|
|
173
190
|
// is added lazily once it appears on the account doc — see below.
|
|
174
191
|
const moduleWatcher = new ModuleWatcher(
|
|
175
192
|
repo,
|
|
176
|
-
|
|
177
|
-
onModuleLoaded
|
|
193
|
+
{ system: defaultModulesUrl },
|
|
194
|
+
onModuleLoaded,
|
|
195
|
+
unregisterPlugins
|
|
178
196
|
);
|
|
179
197
|
|
|
180
198
|
const accountDocHandle = await resolveAccountHandle(repo, {
|
|
@@ -197,10 +215,13 @@ export async function bootPatchworkSite(
|
|
|
197
215
|
|
|
198
216
|
window.patchwork = {
|
|
199
217
|
repo,
|
|
200
|
-
|
|
218
|
+
packages: moduleWatcher,
|
|
201
219
|
plugins,
|
|
202
220
|
accountDocHandle,
|
|
203
|
-
sw:
|
|
221
|
+
sw: {
|
|
222
|
+
...buildSwLogApi(),
|
|
223
|
+
subscribeToRepoChannel: sw.subscribeToRepoChannel,
|
|
224
|
+
},
|
|
204
225
|
};
|
|
205
226
|
window.uncache = uncache;
|
|
206
227
|
|
|
@@ -238,23 +259,18 @@ function installDevConsoleGlobals(repo: Repo): void {
|
|
|
238
259
|
window.repo = repo;
|
|
239
260
|
window.Automerge = Automerge;
|
|
240
261
|
window.AutomergeRepo = AutomergeRepo;
|
|
241
|
-
window.getRepoChannel = () => {
|
|
242
|
-
const { port1, port2 } = new MessageChannel();
|
|
243
|
-
navigator.serviceWorker.controller!.postMessage({ type: "port" }, [port2]);
|
|
244
|
-
return port1;
|
|
245
|
-
};
|
|
246
262
|
}
|
|
247
263
|
|
|
248
264
|
function onModuleLoaded(name: string, mod: any): void {
|
|
249
265
|
if (Array.isArray(mod.plugins)) {
|
|
250
|
-
|
|
251
|
-
`
|
|
266
|
+
log(
|
|
267
|
+
`registering ${mod.plugins.length} plugin(s) from ${name.slice(0, 30)}...`,
|
|
252
268
|
mod.plugins.map((p: any) => `${p.type}:${p.id}`)
|
|
253
269
|
);
|
|
254
270
|
registerPlugins(mod.plugins, name);
|
|
255
271
|
} else {
|
|
256
272
|
console.warn(
|
|
257
|
-
`
|
|
273
|
+
`module ${name.slice(0, 30)}... has no plugins array`,
|
|
258
274
|
Object.keys(mod)
|
|
259
275
|
);
|
|
260
276
|
}
|
|
@@ -272,7 +288,7 @@ function wireModuleSettingsWhenReady(
|
|
|
272
288
|
const wire = () => {
|
|
273
289
|
const url = accountDocHandle.doc()?.moduleSettingsUrl;
|
|
274
290
|
if (!url) return;
|
|
275
|
-
void moduleWatcher.addUrl(url);
|
|
291
|
+
void moduleWatcher.addUrl("user", url);
|
|
276
292
|
accountDocHandle.off("change", wire);
|
|
277
293
|
};
|
|
278
294
|
wire();
|
|
@@ -291,7 +307,6 @@ function primeRootElement(
|
|
|
291
307
|
accountDocHandle: DocHandle<AccountDoc>
|
|
292
308
|
): void {
|
|
293
309
|
rootElement.style.visibility = "hidden";
|
|
294
|
-
showLoadingAnimation();
|
|
295
310
|
|
|
296
311
|
const initialParams = new URLSearchParams(location.hash.slice(1));
|
|
297
312
|
if (initialParams.has("frame")) {
|
|
@@ -312,26 +327,29 @@ function logToolRegistryWhenLoaded(moduleWatcher: ModuleWatcher): void {
|
|
|
312
327
|
.then(() => {
|
|
313
328
|
const toolReg = getRegistry("patchwork:tool");
|
|
314
329
|
const tools = toolReg.all();
|
|
315
|
-
|
|
316
|
-
`
|
|
330
|
+
log(
|
|
331
|
+
`doneLoading: ${tools.length} tools registered:`,
|
|
317
332
|
tools.map((t: any) => t.id)
|
|
318
333
|
);
|
|
319
334
|
})
|
|
320
335
|
.catch((err: unknown) => {
|
|
321
|
-
console.error("
|
|
336
|
+
console.error("doneLoading rejected:", err);
|
|
322
337
|
});
|
|
323
338
|
}
|
|
324
339
|
|
|
325
|
-
function buildSwLogApi():
|
|
340
|
+
function buildSwLogApi(): Omit<
|
|
341
|
+
Window["patchwork"]["sw"],
|
|
342
|
+
"subscribeToRepoChannel"
|
|
343
|
+
> {
|
|
326
344
|
return {
|
|
327
345
|
printLogs: async (n = 200) => {
|
|
328
346
|
const entries = await SwLogReader.tail(n);
|
|
329
347
|
for (const e of entries) {
|
|
330
348
|
const prefix = `[${e.ts}] [${e.level}]`;
|
|
331
|
-
if (e.data !== undefined)
|
|
332
|
-
else
|
|
349
|
+
if (e.data !== undefined) log(prefix, e.msg, e.data);
|
|
350
|
+
else log(prefix, e.msg);
|
|
333
351
|
}
|
|
334
|
-
|
|
352
|
+
log(`--- ${entries.length} entries ---`);
|
|
335
353
|
},
|
|
336
354
|
tailLogs: (n = 200) => SwLogReader.tail(n),
|
|
337
355
|
exportLogs: () => SwLogReader.exportAll(),
|
|
@@ -363,6 +381,10 @@ function showLoadingAnimation(): void {
|
|
|
363
381
|
radial-gradient(ellipse 65% 55% at 50% 50%, #f1e6f6, transparent 80%);
|
|
364
382
|
animation: pw-bootloader-pulse 3.5s ease-in-out infinite;
|
|
365
383
|
transition: opacity 0.6s ease-out;
|
|
384
|
+
top: 0;
|
|
385
|
+
left: 0;
|
|
386
|
+
right: 0;
|
|
387
|
+
bottom: 0;
|
|
366
388
|
}
|
|
367
389
|
@media (prefers-color-scheme: dark) {
|
|
368
390
|
#${LOADING_ELEMENT_ID} {
|
package/src/types.ts
CHANGED
|
@@ -5,3 +5,13 @@ export type SetupServiceWorkerOptions = {
|
|
|
5
5
|
*/
|
|
6
6
|
path?: string;
|
|
7
7
|
};
|
|
8
|
+
|
|
9
|
+
export type ServiceWorkerRepoChannelListener = (
|
|
10
|
+
port: MessagePort
|
|
11
|
+
) => void | Promise<void>;
|
|
12
|
+
|
|
13
|
+
export type SetupServiceWorkerResult = {
|
|
14
|
+
subscribeToRepoChannel: (
|
|
15
|
+
listener: ServiceWorkerRepoChannelListener
|
|
16
|
+
) => Promise<() => void>;
|
|
17
|
+
};
|