@inkandswitch/patchwork-bootloader 0.2.5 → 0.2.7
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 +14 -0
- package/dist/automerge-worker.d.ts +1 -0
- package/dist/automerge-worker.js +505 -0
- package/dist/externals.js +0 -1
- package/dist/service-worker.js +98 -205
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +89 -106
- package/dist/site.d.ts +12 -7
- package/dist/site.js +61 -53
- package/dist/sync-config.d.ts +8 -0
- package/dist/sync-config.js +13 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +7 -1
- package/dist/vite/importmap-plugin.js +9 -3
- package/dist/vite/service-worker-plugin.js +25 -9
- package/package.json +20 -19
- package/src/automerge-worker.ts +647 -0
- package/src/externals.ts +0 -1
- package/src/service-worker.ts +123 -248
- package/src/setup.ts +105 -118
- package/src/site.ts +89 -65
- package/src/sync-config.ts +23 -0
- package/src/types.ts +98 -0
- package/src/vite/importmap-plugin.ts +13 -3
- package/src/vite/service-worker-plugin.ts +26 -11
- package/tsconfig.json +1 -1
- package/dist/sw-logger.d.ts +0 -105
- package/dist/sw-logger.js +0 -366
- package/src/sw-logger.ts +0 -463
package/src/setup.ts
CHANGED
|
@@ -3,13 +3,17 @@ import type {
|
|
|
3
3
|
SetupServiceWorkerOptions,
|
|
4
4
|
SetupServiceWorkerResult,
|
|
5
5
|
} from "./types.js";
|
|
6
|
+
import {
|
|
7
|
+
readClassicSyncServer,
|
|
8
|
+
DEFAULT_CLASSIC_SYNC_SERVER,
|
|
9
|
+
} from "./sync-config.js";
|
|
6
10
|
import debug from "debug";
|
|
7
11
|
|
|
8
|
-
const
|
|
12
|
+
const serviceWorkerDebugging = debug.enabled("patchwork:serviceworker");
|
|
13
|
+
const workerDebugging = debug.enabled("patchwork:automergeworker");
|
|
9
14
|
|
|
10
15
|
const key = "patchworkServiceWorkerCacheVersion";
|
|
11
16
|
let nextRepoChannelId = 0;
|
|
12
|
-
let serviceWorkerInstanceId: string | undefined;
|
|
13
17
|
|
|
14
18
|
function bumpServiceWorkerCacheVersion() {
|
|
15
19
|
const version = new Date().valueOf().toString(36);
|
|
@@ -48,17 +52,69 @@ export function bumpServiceWorkerCache(
|
|
|
48
52
|
|
|
49
53
|
function configureServiceWorker(sw: ServiceWorker | null) {
|
|
50
54
|
if (!sw) return;
|
|
51
|
-
sw.postMessage({ type: "debug", debug:
|
|
55
|
+
sw.postMessage({ type: "debug", debug: serviceWorkerDebugging });
|
|
52
56
|
const cachename = getServiceWorkerCacheVersion();
|
|
53
57
|
if (cachename) sw.postMessage({ type: "cachename", cachename });
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// ── The automerge worker ───────────────────────────────────────────────
|
|
61
|
+
// The automerge repo lives in a SharedWorker (not the service worker). One
|
|
62
|
+
// instance is shared by every tab and lives exactly as long as any tab
|
|
63
|
+
// does, so there's no keepalive ping and no restart detection: if we're
|
|
64
|
+
// alive, it's alive. Repo sync ports are passed to it over its connect
|
|
65
|
+
// port; it talks to the service worker over a BroadcastChannel.
|
|
66
|
+
|
|
67
|
+
let automergeWorkerPath = "/automerge-worker.js";
|
|
68
|
+
let automergeWorker: SharedWorker | undefined;
|
|
69
|
+
|
|
70
|
+
function getAutomergeWorker(): SharedWorker {
|
|
71
|
+
if (!automergeWorker) {
|
|
72
|
+
automergeWorker = new SharedWorker(automergeWorkerPath, {
|
|
73
|
+
name: "patchwork-automerge",
|
|
74
|
+
type: "module",
|
|
75
|
+
});
|
|
76
|
+
// Control replies (port-ready &c) come back on this port, so it needs
|
|
77
|
+
// start() — we listen with addEventListener, not onmessage.
|
|
78
|
+
automergeWorker.port.start();
|
|
79
|
+
automergeWorker.port.postMessage({ type: "debug", debug: workerDebugging });
|
|
80
|
+
}
|
|
81
|
+
return automergeWorker;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function connectClassicSync(
|
|
85
|
+
server: string = readClassicSyncServer()
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const url = server.trim() || DEFAULT_CLASSIC_SYNC_SERVER;
|
|
88
|
+
if (!/^wss?:\/\//.test(url)) {
|
|
89
|
+
return Promise.reject(
|
|
90
|
+
new Error(`invalid classic sync server URL: ${server}`)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const worker = getAutomergeWorker();
|
|
95
|
+
const { port1, port2 } = new MessageChannel();
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const timeout = setTimeout(() => {
|
|
98
|
+
port1.close();
|
|
99
|
+
reject(new Error("connect-classic-sync timeout"));
|
|
100
|
+
}, 30_000);
|
|
101
|
+
port1.onmessage = (event) => {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
port1.close();
|
|
104
|
+
if (event.data?.type === "connect-classic-sync-ready") {
|
|
105
|
+
resolve();
|
|
106
|
+
} else {
|
|
107
|
+
reject(
|
|
108
|
+
new Error(
|
|
109
|
+
event.data?.error ?? "connect-classic-sync failed"
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
worker.port.postMessage({ type: "connect-classic-sync", server: url }, [
|
|
115
|
+
port2,
|
|
116
|
+
]);
|
|
117
|
+
});
|
|
62
118
|
}
|
|
63
119
|
|
|
64
120
|
/** Wait for a registration to have an active worker */
|
|
@@ -74,122 +130,69 @@ function waitForActive(reg: ServiceWorkerRegistration): Promise<ServiceWorker> {
|
|
|
74
130
|
});
|
|
75
131
|
}
|
|
76
132
|
|
|
77
|
-
async function openRepoChannel(): Promise<{
|
|
78
|
-
|
|
79
|
-
workerInstanceChanged: boolean;
|
|
80
|
-
}> {
|
|
81
|
-
const controller = navigator.serviceWorker.controller;
|
|
82
|
-
if (!controller) {
|
|
83
|
-
throw new Error("no service worker controller");
|
|
84
|
-
}
|
|
133
|
+
async function openRepoChannel(): Promise<MessagePort> {
|
|
134
|
+
const worker = getAutomergeWorker();
|
|
85
135
|
|
|
86
|
-
// Send a MessagePort so the
|
|
87
|
-
// the
|
|
136
|
+
// Send a MessagePort so the worker's repo can sync with this tab, and wait
|
|
137
|
+
// for the worker to confirm its repo is constructed before returning. The
|
|
88
138
|
// MessageChannel adapter's whenReady() force-resolves after 100ms regardless
|
|
89
139
|
// of the other end's state, so it can't be used as a real readiness signal
|
|
90
|
-
// on first
|
|
140
|
+
// on first boot (when the worker still has to fetch wasm and build its repo).
|
|
91
141
|
const id = ++nextRepoChannelId;
|
|
92
|
-
let workerInstanceChanged = false;
|
|
93
142
|
const { port1, port2 } = new MessageChannel();
|
|
94
|
-
const
|
|
143
|
+
const workerReady = new Promise<void>((resolve, reject) => {
|
|
95
144
|
let timeout: ReturnType<typeof setTimeout>;
|
|
96
145
|
const cleanup = () => {
|
|
97
146
|
clearTimeout(timeout);
|
|
98
|
-
|
|
147
|
+
worker.port.removeEventListener("message", listener);
|
|
99
148
|
};
|
|
100
149
|
const listener = (event: MessageEvent) => {
|
|
101
|
-
if (event.data?.id
|
|
150
|
+
if (event.data?.id !== id) return;
|
|
102
151
|
if (event.data?.type === "port-ready") {
|
|
103
|
-
workerInstanceChanged = updateServiceWorkerInstanceId(
|
|
104
|
-
event.data.workerInstanceId
|
|
105
|
-
);
|
|
106
152
|
cleanup();
|
|
107
153
|
resolve();
|
|
108
154
|
} else if (event.data?.type === "port-failed") {
|
|
109
|
-
workerInstanceChanged = updateServiceWorkerInstanceId(
|
|
110
|
-
event.data.workerInstanceId
|
|
111
|
-
);
|
|
112
155
|
cleanup();
|
|
113
|
-
reject(new Error(`
|
|
156
|
+
reject(new Error(`automerge worker init failed: ${event.data.error}`));
|
|
114
157
|
}
|
|
115
158
|
};
|
|
116
|
-
|
|
117
|
-
// Failsafe: don't block boot forever if the
|
|
118
|
-
// issue and let the rest of the site come up rather than hanging on a
|
|
159
|
+
worker.port.addEventListener("message", listener);
|
|
160
|
+
// Failsafe: don't block boot forever if the worker never replies. Surface
|
|
161
|
+
// the issue and let the rest of the site come up rather than hanging on a
|
|
119
162
|
// blank page.
|
|
120
163
|
timeout = setTimeout(() => {
|
|
121
164
|
cleanup();
|
|
122
|
-
reject(new Error("
|
|
165
|
+
reject(new Error("automerge worker port-ready timeout"));
|
|
123
166
|
}, 30_000);
|
|
124
167
|
});
|
|
125
|
-
|
|
168
|
+
worker.port.postMessage({ type: "port", id }, [port2]);
|
|
126
169
|
try {
|
|
127
|
-
await
|
|
170
|
+
await workerReady;
|
|
128
171
|
} catch (err) {
|
|
129
172
|
console.warn(
|
|
130
|
-
"proceeding without
|
|
173
|
+
"proceeding without worker ready ack:",
|
|
131
174
|
err instanceof Error ? err.message : err
|
|
132
175
|
);
|
|
133
176
|
}
|
|
134
|
-
return
|
|
177
|
+
return port1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Open a fresh repo sync port to the automerge worker (dev console). */
|
|
181
|
+
function getRepoChannel(): MessagePort {
|
|
182
|
+
const worker = getAutomergeWorker();
|
|
183
|
+
const { port1, port2 } = new MessageChannel();
|
|
184
|
+
worker.port.postMessage({ type: "port", id: ++nextRepoChannelId }, [port2]);
|
|
185
|
+
return port1;
|
|
135
186
|
}
|
|
136
187
|
|
|
137
188
|
export default async function setupServiceWorker(
|
|
138
189
|
options?: SetupServiceWorkerOptions
|
|
139
190
|
): Promise<SetupServiceWorkerResult> {
|
|
140
|
-
|
|
141
|
-
let reconnectPromise: Promise<void> | null = null;
|
|
191
|
+
if (options?.workerPath) automergeWorkerPath = options.workerPath;
|
|
142
192
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
);
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
+
// Start the automerge worker right away so it boots (wasm, repo) while the
|
|
194
|
+
// service worker installs.
|
|
195
|
+
getAutomergeWorker();
|
|
193
196
|
|
|
194
197
|
const path = options?.path ?? "/service-worker.js";
|
|
195
198
|
// No controller at this point means the page loaded without a service
|
|
@@ -216,20 +219,10 @@ export default async function setupServiceWorker(
|
|
|
216
219
|
});
|
|
217
220
|
}
|
|
218
221
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
setInterval(() => {
|
|
224
|
-
void pingServiceWorker();
|
|
225
|
-
}, 20_000);
|
|
226
|
-
|
|
227
|
-
// Reconnect on future SW updates (added after setup so the initial
|
|
228
|
-
// activation doesn't notify before callers subscribe).
|
|
229
|
-
navigator.serviceWorker.addEventListener("controllerchange", function () {
|
|
230
|
-
void reconnectRepoChannels("took control").catch((err) => {
|
|
231
|
-
console.error("service worker reconnect failed", err);
|
|
232
|
-
});
|
|
222
|
+
// A replacement service worker boots with the default cache name — re-send
|
|
223
|
+
// its configuration whenever a new one takes control.
|
|
224
|
+
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
|
225
|
+
configureServiceWorker(navigator.serviceWorker.controller);
|
|
233
226
|
});
|
|
234
227
|
|
|
235
228
|
console.log(
|
|
@@ -238,19 +231,13 @@ export default async function setupServiceWorker(
|
|
|
238
231
|
);
|
|
239
232
|
|
|
240
233
|
return {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
await listener(port);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
repoChannelListeners.delete(listener);
|
|
251
|
-
throw err;
|
|
252
|
-
}
|
|
253
|
-
return () => repoChannelListeners.delete(listener);
|
|
234
|
+
connectClassicSync,
|
|
235
|
+
getRepoChannel,
|
|
236
|
+
async subscribeToRepoChannel(listener: ServiceWorkerRepoChannelListener) {
|
|
237
|
+
// The automerge worker outlives the page, so unlike the old in-service-
|
|
238
|
+
// worker repo there's nothing to reconnect: one port, handed over once.
|
|
239
|
+
await listener(await openRepoChannel());
|
|
240
|
+
return () => {};
|
|
254
241
|
},
|
|
255
242
|
};
|
|
256
243
|
}
|
package/src/site.ts
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* High-level browser-app boot sequence for a Patchwork site.
|
|
3
3
|
*
|
|
4
4
|
* Layers on top of {@link setupServiceWorker} (the package default export) to
|
|
5
|
-
* construct the Repo, wire up the
|
|
5
|
+
* construct the Repo, wire up the automerge-worker port, load plugins via the
|
|
6
6
|
* ModuleWatcher, resolve the user's account document, and hand control to the
|
|
7
7
|
* configured root tool.
|
|
8
8
|
*
|
|
9
9
|
* This entry point pulls in DOM- and plugin-layer dependencies (patchwork
|
|
10
10
|
* elements, plugins, filesystem) and is intended for use only from a browser
|
|
11
11
|
* site's `main.ts`. Non-UI consumers should import the package default (which
|
|
12
|
-
* only does SW registration and
|
|
12
|
+
* only does SW registration and the automerge-worker handoff).
|
|
13
13
|
*/
|
|
14
14
|
import {
|
|
15
15
|
type DocHandle,
|
|
@@ -28,19 +28,25 @@ import {
|
|
|
28
28
|
} from "@automerge/vanillajs/slim";
|
|
29
29
|
import * as Automerge from "@automerge/automerge/slim";
|
|
30
30
|
import * as AutomergeRepo from "@automerge/automerge-repo/slim";
|
|
31
|
+
import {
|
|
32
|
+
initKeyhiveWasm,
|
|
33
|
+
initializeAutomergeRepoKeyhive,
|
|
34
|
+
type AutomergeRepoKeyhive,
|
|
35
|
+
} from "@automerge/automerge-repo-keyhive";
|
|
31
36
|
// eslint-disable-next-line
|
|
32
37
|
// @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
|
|
33
38
|
import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
|
|
34
39
|
|
|
40
|
+
declare const __SITE_NAME__: string;
|
|
41
|
+
const siteName =
|
|
42
|
+
typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
43
|
+
|
|
35
44
|
import { ModuleWatcher } from "@inkandswitch/patchwork-filesystem";
|
|
36
45
|
import {
|
|
37
46
|
openDocument,
|
|
38
47
|
registerPatchworkViewElement,
|
|
39
48
|
} from "@inkandswitch/patchwork-elements";
|
|
40
|
-
import {
|
|
41
|
-
registerFallbackProviderElement,
|
|
42
|
-
registerRepoProviderElement,
|
|
43
|
-
} from "@inkandswitch/patchwork-providers";
|
|
49
|
+
import { registerRepoProviderElement } from "@inkandswitch/patchwork-providers";
|
|
44
50
|
import {
|
|
45
51
|
type AccountDoc,
|
|
46
52
|
type DatatypeDescription,
|
|
@@ -54,7 +60,6 @@ import * as plugins from "@inkandswitch/patchwork-plugins";
|
|
|
54
60
|
|
|
55
61
|
import setupServiceWorker from "./setup.js";
|
|
56
62
|
import type { ServiceWorkerRepoChannelListener } from "./types.js";
|
|
57
|
-
import { SwLogReader } from "./sw-logger.js";
|
|
58
63
|
import debug from "debug";
|
|
59
64
|
const log = debug("patchwork:bootloader:site");
|
|
60
65
|
|
|
@@ -64,16 +69,15 @@ declare global {
|
|
|
64
69
|
Automerge: typeof import("@automerge/automerge");
|
|
65
70
|
AutomergeRepo: typeof import("@automerge/automerge-repo");
|
|
66
71
|
repo: Repo;
|
|
72
|
+
hive?: AutomergeRepoKeyhive;
|
|
73
|
+
getRepoChannel: () => MessagePort;
|
|
67
74
|
patchwork: {
|
|
68
75
|
repo: Repo;
|
|
69
76
|
packages: ModuleWatcher;
|
|
70
77
|
plugins: typeof plugins;
|
|
71
78
|
accountDocHandle: DocHandle<AccountDoc>;
|
|
72
79
|
sw: {
|
|
73
|
-
|
|
74
|
-
tailLogs: (n?: number) => ReturnType<typeof SwLogReader.tail>;
|
|
75
|
-
exportLogs: () => Promise<string>;
|
|
76
|
-
clearLogs: () => Promise<void>;
|
|
80
|
+
connectClassicSync: (server?: string) => Promise<void>;
|
|
77
81
|
subscribeToRepoChannel: (
|
|
78
82
|
listener: ServiceWorkerRepoChannelListener
|
|
79
83
|
) => Promise<() => void>;
|
|
@@ -120,6 +124,13 @@ export interface SiteConfig {
|
|
|
120
124
|
* Ink & Switch's production Subduction storage.
|
|
121
125
|
*/
|
|
122
126
|
remoteStorageIds?: StorageId[];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* When true, initialize keyhive for access control.
|
|
130
|
+
* The Repo will use keyhive's network adapter, peerId, and idFactory
|
|
131
|
+
* instead of a sharePolicy.
|
|
132
|
+
*/
|
|
133
|
+
keyhive?: boolean;
|
|
123
134
|
}
|
|
124
135
|
|
|
125
136
|
export interface BootResult {
|
|
@@ -158,37 +169,63 @@ export async function bootPatchworkSite(
|
|
|
158
169
|
await initializeWasm(automergeWasm);
|
|
159
170
|
initSubductionSync(subductionWasm);
|
|
160
171
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
const sw = await setupServiceWorker();
|
|
173
|
+
if (!sw) throw new Error("Failed to set up service worker");
|
|
174
|
+
|
|
175
|
+
let hive: AutomergeRepoKeyhive | undefined;
|
|
176
|
+
// Get the initial automerge-worker port via subscribeToRepoChannel,
|
|
177
|
+
// then pass it to keyhive init which wraps it in its own network adapter.
|
|
178
|
+
let resolvePort!: (port: MessagePort) => void;
|
|
179
|
+
const portPromise = new Promise<MessagePort>((r) => {
|
|
180
|
+
resolvePort = r;
|
|
181
|
+
});
|
|
182
|
+
await sw.subscribeToRepoChannel(resolvePort);
|
|
183
|
+
const workerPort = await portPromise;
|
|
184
|
+
|
|
185
|
+
if (config.keyhive) {
|
|
186
|
+
initKeyhiveWasm();
|
|
187
|
+
|
|
188
|
+
hive = await initializeAutomergeRepoKeyhive({
|
|
189
|
+
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
190
|
+
peerIdSuffix: siteName + Math.random().toString(36).slice(2),
|
|
191
|
+
networkAdapter: new MessageChannelNetworkAdapter(workerPort),
|
|
192
|
+
automaticArchiveIngestion: true,
|
|
193
|
+
cachingMode: "periodic",
|
|
194
|
+
onlyShareWithHardcodedServerPeerId: false,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
170
197
|
|
|
198
|
+
const repo = hive
|
|
199
|
+
? new Repo({
|
|
200
|
+
storage: new IndexedDBStorageAdapter(),
|
|
201
|
+
enableRemoteHeadsGossiping: true,
|
|
202
|
+
network: [hive.networkAdapter],
|
|
203
|
+
peerId: hive.peerId,
|
|
204
|
+
idFactory: hive.idFactory,
|
|
205
|
+
})
|
|
206
|
+
: new Repo({
|
|
207
|
+
network: [new MessageChannelNetworkAdapter(workerPort)],
|
|
208
|
+
storage: new IndexedDBStorageAdapter(),
|
|
209
|
+
async sharePolicy(peerId) {
|
|
210
|
+
return peerId.includes("automerge-worker");
|
|
211
|
+
},
|
|
212
|
+
enableRemoteHeadsGossiping: true,
|
|
213
|
+
peerId:
|
|
214
|
+
`${config.titleSuffix}-tab-${crypto.randomUUID()}` as AutomergeRepo.PeerId,
|
|
215
|
+
});
|
|
171
216
|
repo.subscribeToRemotes(
|
|
172
217
|
config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]
|
|
173
218
|
);
|
|
174
219
|
|
|
175
|
-
|
|
176
|
-
if (
|
|
177
|
-
let activeServiceWorkerPort: MessagePort | undefined;
|
|
178
|
-
const connectServiceWorkerPort = async (port: MessagePort) => {
|
|
179
|
-
const previousPort = activeServiceWorkerPort;
|
|
180
|
-
activeServiceWorkerPort = port;
|
|
181
|
-
const net = new MessageChannelNetworkAdapter(port);
|
|
182
|
-
repo.networkSubsystem.addNetworkAdapter(net);
|
|
183
|
-
await net.whenReady();
|
|
184
|
-
previousPort?.close();
|
|
185
|
-
};
|
|
186
|
-
await sw.subscribeToRepoChannel(connectServiceWorkerPort);
|
|
220
|
+
await repo.networkSubsystem.whenReady();
|
|
221
|
+
if (hive) {
|
|
187
222
|
|
|
188
|
-
|
|
223
|
+
(hive.networkAdapter as any).syncKeyhive?.();
|
|
224
|
+
}
|
|
189
225
|
|
|
190
|
-
|
|
191
|
-
|
|
226
|
+
installDevConsoleGlobals(repo, hive, sw.getRepoChannel);
|
|
227
|
+
|
|
228
|
+
registerRepoProviderElement(repo as any);
|
|
192
229
|
|
|
193
230
|
const rootElement = document.getElementById(config.rootElementId ?? "root");
|
|
194
231
|
if (!rootElement) {
|
|
@@ -197,14 +234,10 @@ export async function bootPatchworkSite(
|
|
|
197
234
|
);
|
|
198
235
|
}
|
|
199
236
|
const repoProvider = document.createElement("repo-provider");
|
|
200
|
-
|
|
201
|
-
rootElement.parentElement!.insertBefore(fallbackProvider, rootElement);
|
|
202
|
-
fallbackProvider.appendChild(repoProvider);
|
|
237
|
+
rootElement.parentElement!.insertBefore(repoProvider, rootElement);
|
|
203
238
|
repoProvider.appendChild(rootElement);
|
|
204
239
|
|
|
205
|
-
|
|
206
|
-
// delegates to in one go.
|
|
207
|
-
registerPatchworkViewElement();
|
|
240
|
+
registerPatchworkViewElement(hive ? { hive } : {});
|
|
208
241
|
|
|
209
242
|
// The watcher is started with the site's default-tools bundle alone so that
|
|
210
243
|
// `resolveAccountHandle` below has something to await on (the `account`
|
|
@@ -217,9 +250,12 @@ export async function bootPatchworkSite(
|
|
|
217
250
|
unregisterPlugins
|
|
218
251
|
);
|
|
219
252
|
|
|
220
|
-
const accountDocHandle = await resolveAccountHandle(repo, {
|
|
253
|
+
const accountDocHandle = (await resolveAccountHandle(repo, {
|
|
221
254
|
storageKey: config.accountStorageKey,
|
|
222
|
-
|
|
255
|
+
hive,
|
|
256
|
+
})) as DocHandle<AccountDoc>;
|
|
257
|
+
// TODO: something we (Orion & pvh) changed in the types made this necessary
|
|
258
|
+
// fix this before merging to main!
|
|
223
259
|
|
|
224
260
|
window.accountDocHandle = accountDocHandle;
|
|
225
261
|
|
|
@@ -234,7 +270,7 @@ export async function bootPatchworkSite(
|
|
|
234
270
|
plugins,
|
|
235
271
|
accountDocHandle,
|
|
236
272
|
sw: {
|
|
237
|
-
|
|
273
|
+
connectClassicSync: sw.connectClassicSync,
|
|
238
274
|
subscribeToRepoChannel: sw.subscribeToRepoChannel,
|
|
239
275
|
},
|
|
240
276
|
};
|
|
@@ -270,10 +306,18 @@ function resolveDefaultModulesUrl(builtin: AutomergeUrl): AutomergeUrl {
|
|
|
270
306
|
return builtin;
|
|
271
307
|
}
|
|
272
308
|
|
|
273
|
-
function installDevConsoleGlobals(
|
|
309
|
+
function installDevConsoleGlobals(
|
|
310
|
+
repo: Repo,
|
|
311
|
+
hive: AutomergeRepoKeyhive | undefined,
|
|
312
|
+
getRepoChannel: () => MessagePort
|
|
313
|
+
): void {
|
|
274
314
|
window.repo = repo;
|
|
275
315
|
window.Automerge = Automerge;
|
|
276
316
|
window.AutomergeRepo = AutomergeRepo;
|
|
317
|
+
if (hive) {
|
|
318
|
+
window.hive = hive;
|
|
319
|
+
}
|
|
320
|
+
window.getRepoChannel = getRepoChannel;
|
|
277
321
|
}
|
|
278
322
|
|
|
279
323
|
function onModuleLoaded(name: string, mod: any): void {
|
|
@@ -353,26 +397,6 @@ function logToolRegistryWhenLoaded(moduleWatcher: ModuleWatcher): void {
|
|
|
353
397
|
});
|
|
354
398
|
}
|
|
355
399
|
|
|
356
|
-
function buildSwLogApi(): Omit<
|
|
357
|
-
Window["patchwork"]["sw"],
|
|
358
|
-
"subscribeToRepoChannel"
|
|
359
|
-
> {
|
|
360
|
-
return {
|
|
361
|
-
printLogs: async (n = 200) => {
|
|
362
|
-
const entries = await SwLogReader.tail(n);
|
|
363
|
-
for (const e of entries) {
|
|
364
|
-
const prefix = `[${e.ts}] [${e.level}]`;
|
|
365
|
-
if (e.data !== undefined) log(prefix, e.msg, e.data);
|
|
366
|
-
else log(prefix, e.msg);
|
|
367
|
-
}
|
|
368
|
-
log(`--- ${entries.length} entries ---`);
|
|
369
|
-
},
|
|
370
|
-
tailLogs: (n = 200) => SwLogReader.tail(n),
|
|
371
|
-
exportLogs: () => SwLogReader.exportAll(),
|
|
372
|
-
clearLogs: () => SwLogReader.clear(),
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
400
|
const LOADING_STYLE_ID = "pw-bootloader-loading-styles";
|
|
377
401
|
const LOADING_ELEMENT_ID = "pw-bootloader-loading";
|
|
378
402
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** localStorage key: optional override for the classic sync WebSocket URL. */
|
|
2
|
+
export const CLASSIC_SYNC_SERVER_KEY = "patchworkClassicSyncServer";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_CLASSIC_SYNC_SERVER = "wss://sync3.automerge.org";
|
|
5
|
+
|
|
6
|
+
export function readClassicSyncServer(
|
|
7
|
+
storage: Pick<Storage, "getItem"> = globalThis.localStorage
|
|
8
|
+
): string {
|
|
9
|
+
const override = storage.getItem(CLASSIC_SYNC_SERVER_KEY)?.trim();
|
|
10
|
+
if (!override) return DEFAULT_CLASSIC_SYNC_SERVER;
|
|
11
|
+
if (!/^wss?:\/\//.test(override)) {
|
|
12
|
+
console.warn(
|
|
13
|
+
`ignoring invalid ${CLASSIC_SYNC_SERVER_KEY} in localStorage: ${override}; using ${DEFAULT_CLASSIC_SYNC_SERVER}`
|
|
14
|
+
);
|
|
15
|
+
return DEFAULT_CLASSIC_SYNC_SERVER;
|
|
16
|
+
}
|
|
17
|
+
return override;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ConnectClassicSyncMessage = {
|
|
21
|
+
type: "connect-classic-sync";
|
|
22
|
+
server: string;
|
|
23
|
+
};
|