@inkandswitch/patchwork-bootloader 0.2.5 → 0.2.6
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 +8 -0
- package/dist/service-worker.js +92 -25
- package/dist/site.d.ts +9 -0
- package/dist/site.js +68 -29
- package/dist/vite/importmap-plugin.js +9 -3
- package/package.json +7 -6
- package/src/service-worker.ts +127 -27
- package/src/site.ts +94 -34
- package/src/vite/importmap-plugin.ts +13 -3
package/CHANGELOG.md
CHANGED
package/dist/service-worker.js
CHANGED
|
@@ -16,7 +16,7 @@ import { resolvePath } from "@inkandswitch/patchwork-filesystem";
|
|
|
16
16
|
// Small adapters — bundled directly into the SW
|
|
17
17
|
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
|
|
18
18
|
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel";
|
|
19
|
-
import {
|
|
19
|
+
import { initializeAutomergeRepoKeyhiveRust, initKeyhiveWasm, } from "@automerge/automerge-repo-keyhive";
|
|
20
20
|
// TEMPORARY: enable debug npm module in SW context (no localStorage available)
|
|
21
21
|
let cachename = "default";
|
|
22
22
|
let debugging = false;
|
|
@@ -48,6 +48,7 @@ const slog = SwLogger.open().then((logger) => {
|
|
|
48
48
|
logger.info("sw-logger initialized");
|
|
49
49
|
return logger;
|
|
50
50
|
});
|
|
51
|
+
const siteName = typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
51
52
|
const cacheableStatuses = [200, 203, 204, 206];
|
|
52
53
|
function log(...args) {
|
|
53
54
|
if (!debugging)
|
|
@@ -69,10 +70,11 @@ self.addEventListener("activate", async () => {
|
|
|
69
70
|
await clearOldCaches();
|
|
70
71
|
clients.claim();
|
|
71
72
|
});
|
|
72
|
-
let
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
let repoHivePromise = null;
|
|
74
|
+
const useKeyhive = typeof __KEYHIVE__ !== "undefined" && __KEYHIVE__;
|
|
75
|
+
function getRepoHive() {
|
|
76
|
+
if (!repoHivePromise) {
|
|
77
|
+
repoHivePromise = (async () => {
|
|
76
78
|
const logger = await slog;
|
|
77
79
|
logger.info("getRepo: starting");
|
|
78
80
|
logger.info("fetching wasm modules");
|
|
@@ -83,45 +85,106 @@ function getRepo() {
|
|
|
83
85
|
initSubductionSync(new Uint8Array(sdnWasmBuf));
|
|
84
86
|
await initializeWasm(new Uint8Array(amWasmBuf));
|
|
85
87
|
logger.info("wasm initialized");
|
|
86
|
-
|
|
88
|
+
if (!useKeyhive) {
|
|
89
|
+
const signer = await WebCryptoSigner.setup();
|
|
90
|
+
const repo = new Repo({
|
|
91
|
+
storage: new IndexedDBStorageAdapter(),
|
|
92
|
+
signer,
|
|
93
|
+
peerId: ("service-worker-" +
|
|
94
|
+
Math.random().toString(36).slice(2)),
|
|
95
|
+
async sharePolicy(peerId) {
|
|
96
|
+
return peerId.includes("storage-server");
|
|
97
|
+
},
|
|
98
|
+
enableRemoteHeadsGossiping: true,
|
|
99
|
+
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
100
|
+
});
|
|
101
|
+
self.repo = repo;
|
|
102
|
+
logger.info("repo constructed (no keyhive), waiting for network subsystem");
|
|
103
|
+
repo.networkSubsystem.whenReady().then(() => {
|
|
104
|
+
logger.info("repo network subsystem ready");
|
|
105
|
+
});
|
|
106
|
+
return { repo };
|
|
107
|
+
}
|
|
108
|
+
initKeyhiveWasm();
|
|
109
|
+
const keyhiveStorage = new IndexedDBStorageAdapter(`${siteName}-keyhive`);
|
|
110
|
+
// Keyhive bootstrap needs to run before Repo creation but
|
|
111
|
+
// the adapter needs the subduction instance from the Repo.
|
|
112
|
+
// A deferred promise breaks the cycle.
|
|
113
|
+
let resolveRepoSubduction;
|
|
114
|
+
const repoSubductionPromise = new Promise((resolve) => {
|
|
115
|
+
resolveRepoSubduction = resolve;
|
|
116
|
+
});
|
|
117
|
+
// We use the Rust variant of Keyhive initialization to talk
|
|
118
|
+
// to the Rust keyhive-enabled subduction sync server.
|
|
119
|
+
const hive = await initializeAutomergeRepoKeyhiveRust({
|
|
120
|
+
storage: keyhiveStorage,
|
|
121
|
+
peerIdSuffix: `${siteName}-worker` + Math.random().toString(36).slice(2),
|
|
122
|
+
subduction: repoSubductionPromise,
|
|
123
|
+
automaticArchiveIngestion: true,
|
|
124
|
+
cachingMode: "periodic",
|
|
125
|
+
});
|
|
126
|
+
const signer = await hive.constructSubductionSigner();
|
|
87
127
|
const repo = new Repo({
|
|
88
128
|
storage: new IndexedDBStorageAdapter(),
|
|
89
129
|
signer,
|
|
90
|
-
peerId: ("service-worker-" +
|
|
91
|
-
(Math.random() * 10000).toString(36).slice(2)),
|
|
92
|
-
async sharePolicy(peerId) {
|
|
93
|
-
return peerId.includes("storage-server");
|
|
94
|
-
},
|
|
95
|
-
enableRemoteHeadsGossiping: true,
|
|
96
130
|
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
97
|
-
|
|
131
|
+
peerId: hive.peerId,
|
|
132
|
+
enableRemoteHeadsGossiping: true,
|
|
133
|
+
idFactory: hive.idFactory,
|
|
134
|
+
//network: [new WebSocketClientAdapter("wss://sync3.automerge.org")],
|
|
98
135
|
});
|
|
136
|
+
repo.subduction.then(resolveRepoSubduction);
|
|
137
|
+
hive.linkRepo(repo);
|
|
99
138
|
self.repo = repo;
|
|
139
|
+
self.hive = hive;
|
|
100
140
|
logger.info("repo constructed, waiting for network subsystem");
|
|
101
|
-
// Don't block
|
|
141
|
+
// Don't block getRepoHive() on whenReady() — the network subsystem starts
|
|
102
142
|
// with only the subduction adapter, and the MessageChannel adapter is
|
|
103
|
-
// added later via connectPort (which awaits
|
|
143
|
+
// added later via connectPort (which awaits getRepoHive). Blocking here
|
|
104
144
|
// would deadlock that path and starve the fetch handler.
|
|
105
145
|
repo.networkSubsystem.whenReady().then(() => {
|
|
106
146
|
logger.info("repo network subsystem ready");
|
|
107
147
|
});
|
|
108
|
-
|
|
148
|
+
hive.networkAdapter.whenReady().then(() => {
|
|
149
|
+
hive.networkAdapter.syncKeyhive();
|
|
150
|
+
});
|
|
151
|
+
return { hive, repo };
|
|
109
152
|
})();
|
|
110
153
|
// If construction fails (e.g. wasm fetch errors out because the SW was
|
|
111
154
|
// terminated mid-flight), don't permanently cache the rejection — clear
|
|
112
155
|
// the slot so the next caller can retry from scratch.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
repoPromise = null;
|
|
156
|
+
repoHivePromise.catch(() => {
|
|
157
|
+
repoHivePromise = null;
|
|
116
158
|
});
|
|
117
|
-
repoPromise = p;
|
|
118
159
|
}
|
|
119
|
-
return
|
|
160
|
+
return repoHivePromise;
|
|
120
161
|
}
|
|
121
162
|
// Connect client MessagePorts to the repo for sync
|
|
122
163
|
async function connectPort(port) {
|
|
123
|
-
const repo = await
|
|
124
|
-
|
|
164
|
+
const { hive, repo } = await getRepoHive();
|
|
165
|
+
const networkAdapter = new MessageChannelNetworkAdapter(port, { useWeakRef: true });
|
|
166
|
+
if (!hive) {
|
|
167
|
+
repo.networkSubsystem.addNetworkAdapter(networkAdapter);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const onlyShareWithHardcodedServerPeerId = false;
|
|
171
|
+
const periodicallyRequestKeyhiveSync = false;
|
|
172
|
+
const keyhiveNetworkAdapter = hive.createKeyhiveNetworkAdapter(networkAdapter, onlyShareWithHardcodedServerPeerId, periodicallyRequestKeyhiveSync, 2000);
|
|
173
|
+
keyhiveNetworkAdapter.on("message", async (msg) => {
|
|
174
|
+
if ((msg.type === "sync" || msg.type === "request") && msg.documentId) {
|
|
175
|
+
const handle = repo.handles[msg.documentId];
|
|
176
|
+
if (!handle || handle.state === "unavailable") {
|
|
177
|
+
const url = `automerge:${msg.documentId}`;
|
|
178
|
+
repo.findWithProgress(url);
|
|
179
|
+
repo.shareConfigChanged();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
keyhiveNetworkAdapter.on("ingest-remote", () => {
|
|
184
|
+
hive.networkAdapter.syncKeyhive?.();
|
|
185
|
+
repo.shareConfigChanged();
|
|
186
|
+
});
|
|
187
|
+
repo.networkSubsystem.addNetworkAdapter(keyhiveNetworkAdapter);
|
|
125
188
|
}
|
|
126
189
|
self.addEventListener("message", async (event) => {
|
|
127
190
|
if (event.data.type == "ping") {
|
|
@@ -178,7 +241,7 @@ self.addEventListener("message", async (event) => {
|
|
|
178
241
|
});
|
|
179
242
|
// ── Automerge URL resolution ───────────────────────────────────────────
|
|
180
243
|
async function resolveAutomergeUrl(automergeURL) {
|
|
181
|
-
const repo = await
|
|
244
|
+
const { repo } = await getRepoHive();
|
|
182
245
|
const href = automergeURL.href;
|
|
183
246
|
const [maybeAutomergeUrl, ...path] = href.split("/");
|
|
184
247
|
if (!isValidAutomergeUrl(maybeAutomergeUrl)) {
|
|
@@ -286,7 +349,11 @@ self.addEventListener("fetch", (fetchEvent) => {
|
|
|
286
349
|
const message = error instanceof Error
|
|
287
350
|
? `${error.message}\n\n${error.stack}`
|
|
288
351
|
: String(error);
|
|
289
|
-
|
|
352
|
+
const logger = await slog;
|
|
353
|
+
logger.error(`service worker error resolving ${request.url}${specialURL ? ` (for: ${specialURL})` : ""}`, {
|
|
354
|
+
message: error instanceof Error ? error.message : String(error),
|
|
355
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
356
|
+
});
|
|
290
357
|
if (match)
|
|
291
358
|
return match;
|
|
292
359
|
return new Response(message, {
|
package/dist/site.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* only does SW registration and port handoff).
|
|
13
13
|
*/
|
|
14
14
|
import { type DocHandle, Repo, type AutomergeUrl, type StorageId } from "@automerge/vanillajs/slim";
|
|
15
|
+
import { type AutomergeRepoKeyhive } from "@automerge/automerge-repo-keyhive";
|
|
15
16
|
import { ModuleWatcher } from "@inkandswitch/patchwork-filesystem";
|
|
16
17
|
import { type AccountDoc } from "@inkandswitch/patchwork-plugins";
|
|
17
18
|
import * as plugins from "@inkandswitch/patchwork-plugins";
|
|
@@ -23,6 +24,8 @@ declare global {
|
|
|
23
24
|
Automerge: typeof import("@automerge/automerge");
|
|
24
25
|
AutomergeRepo: typeof import("@automerge/automerge-repo");
|
|
25
26
|
repo: Repo;
|
|
27
|
+
hive?: AutomergeRepoKeyhive;
|
|
28
|
+
getRepoChannel: () => MessagePort;
|
|
26
29
|
patchwork: {
|
|
27
30
|
repo: Repo;
|
|
28
31
|
packages: ModuleWatcher;
|
|
@@ -72,6 +75,12 @@ export interface SiteConfig {
|
|
|
72
75
|
* Ink & Switch's production Subduction storage.
|
|
73
76
|
*/
|
|
74
77
|
remoteStorageIds?: StorageId[];
|
|
78
|
+
/**
|
|
79
|
+
* When true, initialize keyhive for access control.
|
|
80
|
+
* The Repo will use keyhive's network adapter, peerId, and idFactory
|
|
81
|
+
* instead of a sharePolicy.
|
|
82
|
+
*/
|
|
83
|
+
keyhive?: boolean;
|
|
75
84
|
}
|
|
76
85
|
export interface BootResult {
|
|
77
86
|
repo: Repo;
|
package/dist/site.js
CHANGED
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
import { IndexedDBStorageAdapter, initializeWasm, isValidAutomergeUrl, isValidDocumentId, MessageChannelNetworkAdapter, parseAutomergeUrl, Repo, stringifyAutomergeUrl, } from "@automerge/vanillajs/slim";
|
|
15
15
|
import * as Automerge from "@automerge/automerge/slim";
|
|
16
16
|
import * as AutomergeRepo from "@automerge/automerge-repo/slim";
|
|
17
|
+
import { initKeyhiveWasm, initializeAutomergeRepoKeyhive, } from "@automerge/automerge-repo-keyhive";
|
|
17
18
|
// eslint-disable-next-line
|
|
18
19
|
// @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
|
|
19
20
|
import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
|
|
21
|
+
const siteName = typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
20
22
|
import { ModuleWatcher } from "@inkandswitch/patchwork-filesystem";
|
|
21
23
|
import { openDocument, registerPatchworkViewElement, } from "@inkandswitch/patchwork-elements";
|
|
22
|
-
import {
|
|
24
|
+
import { registerRepoProviderElement } from "@inkandswitch/patchwork-providers";
|
|
23
25
|
import { getRegistry, registerPlugins, resolveAccountHandle, unregisterPlugins, } from "@inkandswitch/patchwork-plugins";
|
|
24
26
|
import * as plugins from "@inkandswitch/patchwork-plugins";
|
|
25
27
|
import setupServiceWorker from "./setup.js";
|
|
@@ -48,43 +50,71 @@ export async function bootPatchworkSite(config) {
|
|
|
48
50
|
log(`booting`, config);
|
|
49
51
|
await initializeWasm(automergeWasm);
|
|
50
52
|
initSubductionSync(subductionWasm);
|
|
51
|
-
const repo = new Repo({
|
|
52
|
-
storage: new IndexedDBStorageAdapter(),
|
|
53
|
-
async sharePolicy(peerId) {
|
|
54
|
-
return peerId.includes("service-worker");
|
|
55
|
-
},
|
|
56
|
-
enableRemoteHeadsGossiping: true,
|
|
57
|
-
peerId: `${config.titleSuffix}-tab-${crypto.randomUUID()}`,
|
|
58
|
-
});
|
|
59
|
-
repo.subscribeToRemotes(config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]);
|
|
60
53
|
const sw = await setupServiceWorker();
|
|
61
54
|
if (!sw)
|
|
62
55
|
throw new Error("Failed to set up service worker");
|
|
63
|
-
let
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
let hive;
|
|
57
|
+
if (config.keyhive) {
|
|
58
|
+
initKeyhiveWasm();
|
|
59
|
+
// Get the initial SW port via subscribeToRepoChannel, then pass it
|
|
60
|
+
// to keyhive init which wraps it in its own network adapter.
|
|
61
|
+
let resolvePort;
|
|
62
|
+
const portPromise = new Promise((r) => { resolvePort = r; });
|
|
63
|
+
sw.subscribeToRepoChannel((port) => { resolvePort(port); });
|
|
64
|
+
const swPort = await portPromise;
|
|
65
|
+
hive = await initializeAutomergeRepoKeyhive({
|
|
66
|
+
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
67
|
+
peerIdSuffix: siteName +
|
|
68
|
+
Math.random().toString(36).slice(2),
|
|
69
|
+
networkAdapter: new MessageChannelNetworkAdapter(swPort),
|
|
70
|
+
automaticArchiveIngestion: true,
|
|
71
|
+
cachingMode: "periodic",
|
|
72
|
+
onlyShareWithHardcodedServerPeerId: false,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const repo = hive
|
|
76
|
+
? new Repo({
|
|
77
|
+
storage: new IndexedDBStorageAdapter(),
|
|
78
|
+
enableRemoteHeadsGossiping: true,
|
|
79
|
+
network: [hive.networkAdapter],
|
|
80
|
+
peerId: hive.peerId,
|
|
81
|
+
idFactory: hive.idFactory,
|
|
82
|
+
})
|
|
83
|
+
: new Repo({
|
|
84
|
+
storage: new IndexedDBStorageAdapter(),
|
|
85
|
+
async sharePolicy(peerId) {
|
|
86
|
+
return peerId.includes("service-worker");
|
|
87
|
+
},
|
|
88
|
+
enableRemoteHeadsGossiping: true,
|
|
89
|
+
peerId: `${config.titleSuffix}-tab-${crypto.randomUUID()}`,
|
|
90
|
+
});
|
|
91
|
+
repo.subscribeToRemotes(config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]);
|
|
92
|
+
if (hive) {
|
|
93
|
+
await repo.networkSubsystem.whenReady();
|
|
94
|
+
hive.networkAdapter.syncKeyhive?.();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
let activeServiceWorkerPort;
|
|
98
|
+
const connectServiceWorkerPort = async (port) => {
|
|
99
|
+
const previousPort = activeServiceWorkerPort;
|
|
100
|
+
activeServiceWorkerPort = port;
|
|
101
|
+
const net = new MessageChannelNetworkAdapter(port);
|
|
102
|
+
repo.networkSubsystem.addNetworkAdapter(net);
|
|
103
|
+
await net.whenReady();
|
|
104
|
+
previousPort?.close();
|
|
105
|
+
};
|
|
106
|
+
await sw.subscribeToRepoChannel(connectServiceWorkerPort);
|
|
107
|
+
}
|
|
108
|
+
installDevConsoleGlobals(repo, hive);
|
|
74
109
|
registerRepoProviderElement(repo);
|
|
75
|
-
registerFallbackProviderElement();
|
|
76
110
|
const rootElement = document.getElementById(config.rootElementId ?? "root");
|
|
77
111
|
if (!rootElement) {
|
|
78
112
|
throw new Error(`bootPatchworkSite: no element with id="${config.rootElementId ?? "root"}"`);
|
|
79
113
|
}
|
|
80
114
|
const repoProvider = document.createElement("repo-provider");
|
|
81
|
-
|
|
82
|
-
rootElement.parentElement.insertBefore(fallbackProvider, rootElement);
|
|
83
|
-
fallbackProvider.appendChild(repoProvider);
|
|
115
|
+
rootElement.parentElement.insertBefore(repoProvider, rootElement);
|
|
84
116
|
repoProvider.appendChild(rootElement);
|
|
85
|
-
|
|
86
|
-
// delegates to in one go.
|
|
87
|
-
registerPatchworkViewElement();
|
|
117
|
+
registerPatchworkViewElement(hive ? { hive } : {});
|
|
88
118
|
// The watcher is started with the site's default-tools bundle alone so that
|
|
89
119
|
// `resolveAccountHandle` below has something to await on (the `account`
|
|
90
120
|
// datatype lives in that bundle today). The user's own module-settings URL
|
|
@@ -92,6 +122,7 @@ export async function bootPatchworkSite(config) {
|
|
|
92
122
|
const moduleWatcher = new ModuleWatcher(repo, { system: defaultModulesUrl }, onModuleLoaded, unregisterPlugins);
|
|
93
123
|
const accountDocHandle = await resolveAccountHandle(repo, {
|
|
94
124
|
storageKey: config.accountStorageKey,
|
|
125
|
+
hive,
|
|
95
126
|
});
|
|
96
127
|
window.accountDocHandle = accountDocHandle;
|
|
97
128
|
wireModuleSettingsWhenReady(accountDocHandle, moduleWatcher);
|
|
@@ -131,10 +162,18 @@ function resolveDefaultModulesUrl(builtin) {
|
|
|
131
162
|
console.warn(`ignoring invalid defaultToolsUrl in localStorage: ${override}; using built-in default`);
|
|
132
163
|
return builtin;
|
|
133
164
|
}
|
|
134
|
-
function installDevConsoleGlobals(repo) {
|
|
165
|
+
function installDevConsoleGlobals(repo, hive) {
|
|
135
166
|
window.repo = repo;
|
|
136
167
|
window.Automerge = Automerge;
|
|
137
168
|
window.AutomergeRepo = AutomergeRepo;
|
|
169
|
+
if (hive) {
|
|
170
|
+
window.hive = hive;
|
|
171
|
+
}
|
|
172
|
+
window.getRepoChannel = () => {
|
|
173
|
+
const { port1, port2 } = new MessageChannel();
|
|
174
|
+
navigator.serviceWorker.controller.postMessage({ type: "port" }, [port2]);
|
|
175
|
+
return port1;
|
|
176
|
+
};
|
|
138
177
|
}
|
|
139
178
|
function onModuleLoaded(name, mod) {
|
|
140
179
|
if (Array.isArray(mod.plugins)) {
|
|
@@ -30,12 +30,18 @@ export function importmap(options) {
|
|
|
30
30
|
preserveSignature: "strict",
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
// Emit automerge wasm so the service worker can fetch
|
|
34
|
-
const
|
|
33
|
+
// Emit automerge, keyhive, and subduction wasm so the service worker can fetch them
|
|
34
|
+
const automergeWasmPath = require.resolve("@automerge/automerge/automerge.wasm");
|
|
35
35
|
this.emitFile({
|
|
36
36
|
type: "asset",
|
|
37
37
|
fileName: "automerge.wasm",
|
|
38
|
-
source: readFileSync(
|
|
38
|
+
source: readFileSync(automergeWasmPath),
|
|
39
|
+
});
|
|
40
|
+
const keyhiveWasmPath = require.resolve("@keyhive/keyhive/keyhive_wasm.wasm");
|
|
41
|
+
this.emitFile({
|
|
42
|
+
type: "asset",
|
|
43
|
+
fileName: "keyhive_wasm.wasm",
|
|
44
|
+
source: readFileSync(keyhiveWasmPath),
|
|
39
45
|
});
|
|
40
46
|
// Emit subduction wasm so the service worker can fetch it
|
|
41
47
|
const subdWasmPath = require.resolve("@automerge/automerge-subduction/wasm");
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkandswitch/patchwork-bootloader",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"author": "chee",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@automerge/automerge-repo-keyhive": "0.
|
|
8
|
+
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.1",
|
|
9
9
|
"esbuild": "^0.23.1",
|
|
10
10
|
"rollup": "^4.53.3"
|
|
11
11
|
},
|
|
@@ -42,25 +42,26 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@automerge/automerge": "3.3.0-fragments.1",
|
|
44
44
|
"@automerge/automerge-repo": "2.6.0-subduction.23",
|
|
45
|
-
"@automerge/automerge-subduction": "0.
|
|
45
|
+
"@automerge/automerge-subduction": "0.13.0",
|
|
46
46
|
"@automerge/automerge-repo-network-messagechannel": "2.6.0-subduction.23",
|
|
47
47
|
"@automerge/automerge-repo-network-websocket": "2.6.0-subduction.23",
|
|
48
48
|
"@automerge/automerge-repo-storage-indexeddb": "2.6.0-subduction.23",
|
|
49
49
|
"@automerge/vanillajs": "2.6.0-subduction.23",
|
|
50
|
+
"@keyhive/keyhive": "0.0.0-alpha.56",
|
|
50
51
|
"@types/debug": "^4.1.12",
|
|
51
52
|
"debug": "^4.4.3",
|
|
52
53
|
"resolve.exports": "^2.0.3",
|
|
53
54
|
"service-worker-types": "npm:@types/serviceworker@^0.0.153",
|
|
54
55
|
"tinyargs": "^0.1.4",
|
|
55
|
-
"@inkandswitch/patchwork-elements": "^0.
|
|
56
|
+
"@inkandswitch/patchwork-elements": "^1.0.0",
|
|
56
57
|
"@inkandswitch/patchwork-filesystem": "^0.0.8",
|
|
57
58
|
"@inkandswitch/patchwork-plugins": "^0.0.11",
|
|
58
|
-
"@inkandswitch/patchwork-providers": "^0.
|
|
59
|
+
"@inkandswitch/patchwork-providers": "^0.2.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"@automerge/automerge": "3.3.0-fragments.1",
|
|
62
63
|
"@automerge/automerge-repo": "2.6.0-subduction.23",
|
|
63
|
-
"@automerge/automerge-repo-keyhive": "0.
|
|
64
|
+
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.1",
|
|
64
65
|
"@automerge/vanillajs": "2.6.0-subduction.23"
|
|
65
66
|
},
|
|
66
67
|
"scripts": {
|
package/src/service-worker.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isValidAutomergeUrl,
|
|
20
20
|
parseAutomergeUrl,
|
|
21
21
|
stringifyAutomergeUrl,
|
|
22
|
-
type
|
|
22
|
+
type AutomergeUrl,
|
|
23
23
|
} from "@automerge/automerge-repo/slim";
|
|
24
24
|
import { resolvePath } from "@inkandswitch/patchwork-filesystem";
|
|
25
25
|
|
|
@@ -27,8 +27,17 @@ import { resolvePath } from "@inkandswitch/patchwork-filesystem";
|
|
|
27
27
|
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
|
|
28
28
|
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel";
|
|
29
29
|
import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
30
|
+
import {
|
|
31
|
+
initializeAutomergeRepoKeyhiveRust,
|
|
32
|
+
initKeyhiveWasm,
|
|
33
|
+
type AutomergeRepoKeyhiveRust,
|
|
34
|
+
} from "@automerge/automerge-repo-keyhive";
|
|
35
|
+
|
|
36
|
+
declare const __SITE_NAME__: string;
|
|
37
|
+
declare const __KEYHIVE__: boolean;
|
|
30
38
|
|
|
31
39
|
// TEMPORARY: enable debug npm module in SW context (no localStorage available)
|
|
40
|
+
|
|
32
41
|
let cachename = "default";
|
|
33
42
|
let debugging = false;
|
|
34
43
|
const workerInstanceId = crypto.randomUUID();
|
|
@@ -64,6 +73,8 @@ const slog = SwLogger.open().then((logger) => {
|
|
|
64
73
|
return logger;
|
|
65
74
|
});
|
|
66
75
|
|
|
76
|
+
const siteName = typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
77
|
+
|
|
67
78
|
const cacheableStatuses = [200, 203, 204, 206];
|
|
68
79
|
|
|
69
80
|
function log(...args: any[]) {
|
|
@@ -95,11 +106,16 @@ self.addEventListener("activate", async () => {
|
|
|
95
106
|
clients.claim();
|
|
96
107
|
});
|
|
97
108
|
|
|
98
|
-
let
|
|
109
|
+
let repoHivePromise: Promise<{
|
|
110
|
+
repo: Repo;
|
|
111
|
+
hive?: AutomergeRepoKeyhiveRust;
|
|
112
|
+
}> | null = null;
|
|
113
|
+
|
|
114
|
+
const useKeyhive = typeof __KEYHIVE__ !== "undefined" && __KEYHIVE__;
|
|
99
115
|
|
|
100
|
-
function
|
|
101
|
-
if (!
|
|
102
|
-
|
|
116
|
+
function getRepoHive() {
|
|
117
|
+
if (!repoHivePromise) {
|
|
118
|
+
repoHivePromise = (async () => {
|
|
103
119
|
const logger = await slog;
|
|
104
120
|
logger.info("getRepo: starting");
|
|
105
121
|
|
|
@@ -112,51 +128,130 @@ function getRepo() {
|
|
|
112
128
|
await initializeWasm(new Uint8Array(amWasmBuf));
|
|
113
129
|
logger.info("wasm initialized");
|
|
114
130
|
|
|
115
|
-
|
|
131
|
+
if (!useKeyhive) {
|
|
132
|
+
const signer = await WebCryptoSigner.setup();
|
|
133
|
+
|
|
134
|
+
const repo = new Repo({
|
|
135
|
+
storage: new IndexedDBStorageAdapter(),
|
|
136
|
+
signer,
|
|
137
|
+
peerId: ("service-worker-" +
|
|
138
|
+
Math.random().toString(36).slice(2)) as import("@automerge/automerge-repo/slim").PeerId,
|
|
139
|
+
async sharePolicy(peerId) {
|
|
140
|
+
return peerId.includes("storage-server");
|
|
141
|
+
},
|
|
142
|
+
enableRemoteHeadsGossiping: true,
|
|
143
|
+
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
(self as any).repo = repo;
|
|
147
|
+
logger.info("repo constructed (no keyhive), waiting for network subsystem");
|
|
148
|
+
|
|
149
|
+
repo.networkSubsystem.whenReady().then(() => {
|
|
150
|
+
logger.info("repo network subsystem ready");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return { repo };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
initKeyhiveWasm();
|
|
157
|
+
const keyhiveStorage = new IndexedDBStorageAdapter(
|
|
158
|
+
`${siteName}-keyhive`
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Keyhive bootstrap needs to run before Repo creation but
|
|
162
|
+
// the adapter needs the subduction instance from the Repo.
|
|
163
|
+
// A deferred promise breaks the cycle.
|
|
164
|
+
let resolveRepoSubduction!: (s: any) => void;
|
|
165
|
+
const repoSubductionPromise = new Promise((resolve) => {
|
|
166
|
+
resolveRepoSubduction = resolve;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// We use the Rust variant of Keyhive initialization to talk
|
|
170
|
+
// to the Rust keyhive-enabled subduction sync server.
|
|
171
|
+
const hive = await initializeAutomergeRepoKeyhiveRust({
|
|
172
|
+
storage: keyhiveStorage,
|
|
173
|
+
peerIdSuffix:
|
|
174
|
+
`${siteName}-worker` + Math.random().toString(36).slice(2),
|
|
175
|
+
subduction: repoSubductionPromise as any,
|
|
176
|
+
automaticArchiveIngestion: true,
|
|
177
|
+
cachingMode: "periodic",
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const signer = await hive.constructSubductionSigner();
|
|
116
181
|
|
|
117
182
|
const repo = new Repo({
|
|
118
183
|
storage: new IndexedDBStorageAdapter(),
|
|
119
184
|
signer,
|
|
120
|
-
peerId: ("service-worker-" +
|
|
121
|
-
(Math.random() * 10000).toString(36).slice(2)) as PeerId,
|
|
122
|
-
async sharePolicy(peerId) {
|
|
123
|
-
return peerId.includes("storage-server");
|
|
124
|
-
},
|
|
125
|
-
enableRemoteHeadsGossiping: true,
|
|
126
185
|
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
127
|
-
|
|
186
|
+
peerId: hive.peerId,
|
|
187
|
+
enableRemoteHeadsGossiping: true,
|
|
188
|
+
idFactory: hive.idFactory,
|
|
189
|
+
//network: [new WebSocketClientAdapter("wss://sync3.automerge.org")],
|
|
128
190
|
});
|
|
129
191
|
|
|
192
|
+
repo.subduction.then(resolveRepoSubduction);
|
|
193
|
+
|
|
194
|
+
hive.linkRepo(repo);
|
|
195
|
+
|
|
130
196
|
(self as any).repo = repo;
|
|
197
|
+
(self as any).hive = hive;
|
|
131
198
|
logger.info("repo constructed, waiting for network subsystem");
|
|
132
199
|
|
|
133
|
-
// Don't block
|
|
200
|
+
// Don't block getRepoHive() on whenReady() — the network subsystem starts
|
|
134
201
|
// with only the subduction adapter, and the MessageChannel adapter is
|
|
135
|
-
// added later via connectPort (which awaits
|
|
202
|
+
// added later via connectPort (which awaits getRepoHive). Blocking here
|
|
136
203
|
// would deadlock that path and starve the fetch handler.
|
|
137
204
|
repo.networkSubsystem.whenReady().then(() => {
|
|
138
205
|
logger.info("repo network subsystem ready");
|
|
139
206
|
});
|
|
140
207
|
|
|
141
|
-
|
|
208
|
+
hive.networkAdapter.whenReady().then(() => {
|
|
209
|
+
(hive.networkAdapter as any).syncKeyhive();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return { hive, repo };
|
|
142
213
|
})();
|
|
143
214
|
// If construction fails (e.g. wasm fetch errors out because the SW was
|
|
144
215
|
// terminated mid-flight), don't permanently cache the rejection — clear
|
|
145
216
|
// the slot so the next caller can retry from scratch.
|
|
146
|
-
|
|
147
|
-
|
|
217
|
+
repoHivePromise.catch(() => {
|
|
218
|
+
repoHivePromise = null;
|
|
148
219
|
});
|
|
149
|
-
repoPromise = p;
|
|
150
220
|
}
|
|
151
|
-
return
|
|
221
|
+
return repoHivePromise;
|
|
152
222
|
}
|
|
153
223
|
|
|
154
224
|
// Connect client MessagePorts to the repo for sync
|
|
155
225
|
async function connectPort(port: MessagePort) {
|
|
156
|
-
const repo = await
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
226
|
+
const { hive, repo } = await getRepoHive();
|
|
227
|
+
const networkAdapter = new MessageChannelNetworkAdapter(port, { useWeakRef: true });
|
|
228
|
+
|
|
229
|
+
if (!hive) {
|
|
230
|
+
repo.networkSubsystem.addNetworkAdapter(networkAdapter);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const onlyShareWithHardcodedServerPeerId = false;
|
|
235
|
+
const periodicallyRequestKeyhiveSync = false;
|
|
236
|
+
const keyhiveNetworkAdapter = hive.createKeyhiveNetworkAdapter(networkAdapter, onlyShareWithHardcodedServerPeerId, periodicallyRequestKeyhiveSync, 2000);
|
|
237
|
+
|
|
238
|
+
keyhiveNetworkAdapter.on("message", async (msg: any) => {
|
|
239
|
+
if ((msg.type === "sync" || msg.type === "request") && msg.documentId) {
|
|
240
|
+
const handle = repo.handles[msg.documentId];
|
|
241
|
+
if (!handle || handle.state === "unavailable") {
|
|
242
|
+
const url = `automerge:${msg.documentId}` as AutomergeUrl;
|
|
243
|
+
repo.findWithProgress(url);
|
|
244
|
+
repo.shareConfigChanged();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
(keyhiveNetworkAdapter as any).on("ingest-remote", () => {
|
|
250
|
+
(hive.networkAdapter as any).syncKeyhive?.();
|
|
251
|
+
repo.shareConfigChanged();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
repo.networkSubsystem.addNetworkAdapter(keyhiveNetworkAdapter);
|
|
160
255
|
}
|
|
161
256
|
|
|
162
257
|
self.addEventListener("message", async (event) => {
|
|
@@ -219,7 +314,7 @@ self.addEventListener("message", async (event) => {
|
|
|
219
314
|
// ── Automerge URL resolution ───────────────────────────────────────────
|
|
220
315
|
|
|
221
316
|
async function resolveAutomergeUrl(automergeURL: URL): Promise<Response> {
|
|
222
|
-
const repo = await
|
|
317
|
+
const { repo } = await getRepoHive();
|
|
223
318
|
const href = automergeURL.href;
|
|
224
319
|
const [maybeAutomergeUrl, ...path] = href.split("/");
|
|
225
320
|
|
|
@@ -362,8 +457,13 @@ self.addEventListener("fetch", (fetchEvent: FetchEvent) => {
|
|
|
362
457
|
error instanceof Error
|
|
363
458
|
? `${error.message}\n\n${error.stack}`
|
|
364
459
|
: String(error);
|
|
365
|
-
|
|
366
|
-
|
|
460
|
+
const logger = await slog;
|
|
461
|
+
logger.error(
|
|
462
|
+
`service worker error resolving ${request.url}${specialURL ? ` (for: ${specialURL})` : ""}`,
|
|
463
|
+
{
|
|
464
|
+
message: error instanceof Error ? error.message : String(error),
|
|
465
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
466
|
+
}
|
|
367
467
|
);
|
|
368
468
|
if (match) return match;
|
|
369
469
|
|
package/src/site.ts
CHANGED
|
@@ -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,
|
|
@@ -64,6 +70,8 @@ declare global {
|
|
|
64
70
|
Automerge: typeof import("@automerge/automerge");
|
|
65
71
|
AutomergeRepo: typeof import("@automerge/automerge-repo");
|
|
66
72
|
repo: Repo;
|
|
73
|
+
hive?: AutomergeRepoKeyhive;
|
|
74
|
+
getRepoChannel: () => MessagePort;
|
|
67
75
|
patchwork: {
|
|
68
76
|
repo: Repo;
|
|
69
77
|
packages: ModuleWatcher;
|
|
@@ -120,6 +128,13 @@ export interface SiteConfig {
|
|
|
120
128
|
* Ink & Switch's production Subduction storage.
|
|
121
129
|
*/
|
|
122
130
|
remoteStorageIds?: StorageId[];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* When true, initialize keyhive for access control.
|
|
134
|
+
* The Repo will use keyhive's network adapter, peerId, and idFactory
|
|
135
|
+
* instead of a sharePolicy.
|
|
136
|
+
*/
|
|
137
|
+
keyhive?: boolean;
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
export interface BootResult {
|
|
@@ -158,37 +173,74 @@ export async function bootPatchworkSite(
|
|
|
158
173
|
await initializeWasm(automergeWasm);
|
|
159
174
|
initSubductionSync(subductionWasm);
|
|
160
175
|
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
176
|
+
const sw = await setupServiceWorker();
|
|
177
|
+
if (!sw) throw new Error("Failed to set up service worker");
|
|
178
|
+
|
|
179
|
+
let hive: AutomergeRepoKeyhive | undefined;
|
|
180
|
+
if (config.keyhive) {
|
|
181
|
+
initKeyhiveWasm();
|
|
182
|
+
|
|
183
|
+
// Get the initial SW port via subscribeToRepoChannel, then pass it
|
|
184
|
+
// to keyhive init which wraps it in its own network adapter.
|
|
185
|
+
let resolvePort!: (port: MessagePort) => void;
|
|
186
|
+
const portPromise = new Promise<MessagePort>((r) => { resolvePort = r; });
|
|
187
|
+
sw.subscribeToRepoChannel((port) => { resolvePort(port); });
|
|
188
|
+
const swPort = await portPromise;
|
|
189
|
+
|
|
190
|
+
hive = await initializeAutomergeRepoKeyhive({
|
|
191
|
+
storage: new IndexedDBStorageAdapter(
|
|
192
|
+
`${siteName}-keyhive`
|
|
193
|
+
),
|
|
194
|
+
peerIdSuffix:
|
|
195
|
+
siteName +
|
|
196
|
+
Math.random().toString(36).slice(2),
|
|
197
|
+
networkAdapter: new MessageChannelNetworkAdapter(swPort),
|
|
198
|
+
automaticArchiveIngestion: true,
|
|
199
|
+
cachingMode: "periodic",
|
|
200
|
+
onlyShareWithHardcodedServerPeerId: false,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
170
203
|
|
|
204
|
+
const repo = hive
|
|
205
|
+
? new Repo({
|
|
206
|
+
storage: new IndexedDBStorageAdapter(),
|
|
207
|
+
enableRemoteHeadsGossiping: true,
|
|
208
|
+
network: [hive.networkAdapter],
|
|
209
|
+
peerId: hive.peerId,
|
|
210
|
+
idFactory: hive.idFactory,
|
|
211
|
+
})
|
|
212
|
+
: new Repo({
|
|
213
|
+
storage: new IndexedDBStorageAdapter(),
|
|
214
|
+
async sharePolicy(peerId) {
|
|
215
|
+
return peerId.includes("service-worker");
|
|
216
|
+
},
|
|
217
|
+
enableRemoteHeadsGossiping: true,
|
|
218
|
+
peerId:
|
|
219
|
+
`${config.titleSuffix}-tab-${crypto.randomUUID()}` as AutomergeRepo.PeerId,
|
|
220
|
+
});
|
|
171
221
|
repo.subscribeToRemotes(
|
|
172
222
|
config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]
|
|
173
223
|
);
|
|
174
224
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
225
|
+
if (hive) {
|
|
226
|
+
await repo.networkSubsystem.whenReady();
|
|
227
|
+
(hive.networkAdapter as any).syncKeyhive?.();
|
|
228
|
+
} else {
|
|
229
|
+
let activeServiceWorkerPort: MessagePort | undefined;
|
|
230
|
+
const connectServiceWorkerPort = async (port: MessagePort) => {
|
|
231
|
+
const previousPort = activeServiceWorkerPort;
|
|
232
|
+
activeServiceWorkerPort = port;
|
|
233
|
+
const net = new MessageChannelNetworkAdapter(port);
|
|
234
|
+
repo.networkSubsystem.addNetworkAdapter(net);
|
|
235
|
+
await net.whenReady();
|
|
236
|
+
previousPort?.close();
|
|
237
|
+
};
|
|
238
|
+
await sw.subscribeToRepoChannel(connectServiceWorkerPort);
|
|
239
|
+
}
|
|
187
240
|
|
|
188
|
-
installDevConsoleGlobals(repo);
|
|
241
|
+
installDevConsoleGlobals(repo, hive);
|
|
189
242
|
|
|
190
243
|
registerRepoProviderElement(repo);
|
|
191
|
-
registerFallbackProviderElement();
|
|
192
244
|
|
|
193
245
|
const rootElement = document.getElementById(config.rootElementId ?? "root");
|
|
194
246
|
if (!rootElement) {
|
|
@@ -197,14 +249,10 @@ export async function bootPatchworkSite(
|
|
|
197
249
|
);
|
|
198
250
|
}
|
|
199
251
|
const repoProvider = document.createElement("repo-provider");
|
|
200
|
-
|
|
201
|
-
rootElement.parentElement!.insertBefore(fallbackProvider, rootElement);
|
|
202
|
-
fallbackProvider.appendChild(repoProvider);
|
|
252
|
+
rootElement.parentElement!.insertBefore(repoProvider, rootElement);
|
|
203
253
|
repoProvider.appendChild(rootElement);
|
|
204
254
|
|
|
205
|
-
|
|
206
|
-
// delegates to in one go.
|
|
207
|
-
registerPatchworkViewElement();
|
|
255
|
+
registerPatchworkViewElement(hive ? { hive } : {});
|
|
208
256
|
|
|
209
257
|
// The watcher is started with the site's default-tools bundle alone so that
|
|
210
258
|
// `resolveAccountHandle` below has something to await on (the `account`
|
|
@@ -219,6 +267,7 @@ export async function bootPatchworkSite(
|
|
|
219
267
|
|
|
220
268
|
const accountDocHandle = await resolveAccountHandle(repo, {
|
|
221
269
|
storageKey: config.accountStorageKey,
|
|
270
|
+
hive,
|
|
222
271
|
});
|
|
223
272
|
|
|
224
273
|
window.accountDocHandle = accountDocHandle;
|
|
@@ -270,10 +319,21 @@ function resolveDefaultModulesUrl(builtin: AutomergeUrl): AutomergeUrl {
|
|
|
270
319
|
return builtin;
|
|
271
320
|
}
|
|
272
321
|
|
|
273
|
-
function installDevConsoleGlobals(
|
|
322
|
+
function installDevConsoleGlobals(
|
|
323
|
+
repo: Repo,
|
|
324
|
+
hive: AutomergeRepoKeyhive | undefined
|
|
325
|
+
): void {
|
|
274
326
|
window.repo = repo;
|
|
275
327
|
window.Automerge = Automerge;
|
|
276
328
|
window.AutomergeRepo = AutomergeRepo;
|
|
329
|
+
if (hive) {
|
|
330
|
+
window.hive = hive;
|
|
331
|
+
}
|
|
332
|
+
window.getRepoChannel = () => {
|
|
333
|
+
const { port1, port2 } = new MessageChannel();
|
|
334
|
+
navigator.serviceWorker.controller!.postMessage({ type: "port" }, [port2]);
|
|
335
|
+
return port1;
|
|
336
|
+
};
|
|
277
337
|
}
|
|
278
338
|
|
|
279
339
|
function onModuleLoaded(name: string, mod: any): void {
|
|
@@ -45,12 +45,22 @@ export function importmap(options?: PatchworkVitePluginOptions): Plugin {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Emit automerge wasm so the service worker can fetch
|
|
49
|
-
const
|
|
48
|
+
// Emit automerge, keyhive, and subduction wasm so the service worker can fetch them
|
|
49
|
+
const automergeWasmPath = require.resolve(
|
|
50
|
+
"@automerge/automerge/automerge.wasm"
|
|
51
|
+
);
|
|
50
52
|
this.emitFile({
|
|
51
53
|
type: "asset",
|
|
52
54
|
fileName: "automerge.wasm",
|
|
53
|
-
source: readFileSync(
|
|
55
|
+
source: readFileSync(automergeWasmPath),
|
|
56
|
+
});
|
|
57
|
+
const keyhiveWasmPath = require.resolve(
|
|
58
|
+
"@keyhive/keyhive/keyhive_wasm.wasm"
|
|
59
|
+
);
|
|
60
|
+
this.emitFile({
|
|
61
|
+
type: "asset",
|
|
62
|
+
fileName: "keyhive_wasm.wasm",
|
|
63
|
+
source: readFileSync(keyhiveWasmPath),
|
|
54
64
|
});
|
|
55
65
|
|
|
56
66
|
// Emit subduction wasm so the service worker can fetch it
|