@inkandswitch/patchwork-bootloader 0.2.7 → 0.3.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 +12 -0
- package/dist/automerge-worker.js +16 -35
- package/dist/externals.js +1 -0
- package/dist/service-worker.js +4 -2
- package/dist/site.d.ts +23 -7
- package/dist/site.js +78 -27
- package/package.json +15 -15
- package/src/automerge-worker.ts +16 -43
- package/src/externals.ts +1 -0
- package/src/service-worker.ts +4 -2
- package/src/site.ts +142 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @inkandswitch/patchwork-bootloader
|
|
2
2
|
|
|
3
|
+
## 0.2.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 48e4391: Pass the realm-local `repo` to `<patchwork-view>` registration so booted views
|
|
8
|
+
resolve their document handles through it — via the per-view `OverlayRepo` and
|
|
9
|
+
the root `<repo-provider>` fallback for `repo:handle-descriptor`.
|
|
10
|
+
- Updated dependencies [48e4391]
|
|
11
|
+
- Updated dependencies [48e4391]
|
|
12
|
+
- @inkandswitch/patchwork-elements@2.0.0
|
|
13
|
+
- @inkandswitch/patchwork-providers@0.3.0
|
|
14
|
+
|
|
3
15
|
## 0.2.7
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/automerge-worker.js
CHANGED
|
@@ -23,7 +23,7 @@ import { resolvePath } from "@inkandswitch/patchwork-filesystem";
|
|
|
23
23
|
import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb";
|
|
24
24
|
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel";
|
|
25
25
|
import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
26
|
-
import {
|
|
26
|
+
import { initializeAutomergeRepoKeyhiveRustWithRepo, initKeyhiveWasm, } from "@automerge/automerge-repo-keyhive";
|
|
27
27
|
import { HANDOFF_CHANNEL, } from "./types.js";
|
|
28
28
|
let debugging = false;
|
|
29
29
|
// Sync server selection. Sub is the default. Build with KEYHIVE_SYNC_SERVER=true
|
|
@@ -37,9 +37,6 @@ if (useKeyhiveSyncServer) {
|
|
|
37
37
|
KEYHIVE_SERVER_IDENTITY: "keyhive-sync",
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
// keyhive.sync.automerge.org's keyhive identity (issuer d7f41e6f…).
|
|
41
|
-
const KEYHIVE_SYNC_SERVER_PEER_ID = "1/Qebw9O69oH8T/ejYMhFup0tNBh69I3ytGqsmIl358=";
|
|
42
|
-
const KEYHIVE_SYNC_SERVER_CONTACT_CARD_JSON = '{"Rotate":{"payload":{"old":[73,163,230,244,111,233,153,119,133,211,134,237,111,36,52,131,22,50,54,144,150,45,227,235,128,36,33,217,190,198,55,75],"new":[109,115,204,144,178,114,182,238,113,124,4,139,249,76,220,44,128,104,194,68,187,184,82,241,94,145,104,198,159,122,186,43]},"issuer":[215,244,30,111,15,78,235,218,7,241,63,222,141,131,33,22,234,116,180,208,97,235,210,55,202,209,170,178,98,37,223,159],"signature":[178,64,85,76,51,199,196,151,129,14,191,53,127,191,34,223,97,238,95,109,118,179,152,17,205,188,204,177,116,166,147,231,192,201,48,137,19,214,180,45,108,104,34,8,14,63,115,139,215,142,4,179,233,89,150,218,174,168,107,23,8,109,228,6]}}';
|
|
43
40
|
const SUBDUCTION_ENDPOINTS = [
|
|
44
41
|
useKeyhiveSyncServer
|
|
45
42
|
? "wss://keyhive.sync.automerge.org"
|
|
@@ -79,7 +76,7 @@ async function connectClassicSyncNetwork(server) {
|
|
|
79
76
|
}
|
|
80
77
|
}
|
|
81
78
|
const siteName = typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
82
|
-
const cacheableStatuses = [200, 203, 204
|
|
79
|
+
const cacheableStatuses = [200, 203, 204];
|
|
83
80
|
function log(...args) {
|
|
84
81
|
if (!debugging)
|
|
85
82
|
return;
|
|
@@ -122,40 +119,23 @@ function getRepoHive() {
|
|
|
122
119
|
return { repo };
|
|
123
120
|
}
|
|
124
121
|
initKeyhiveWasm();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let resolveRepoSubduction;
|
|
130
|
-
const repoSubductionPromise = new Promise((resolve) => {
|
|
131
|
-
resolveRepoSubduction = resolve;
|
|
132
|
-
});
|
|
133
|
-
// We use the Rust variant of Keyhive initialization to talk
|
|
134
|
-
// to the Rust keyhive-enabled subduction sync server.
|
|
135
|
-
const hive = await initializeAutomergeRepoKeyhiveRust({
|
|
136
|
-
storage: keyhiveStorage,
|
|
122
|
+
// ARK variant for talking to the keyhive-enabled subduction sync server.
|
|
123
|
+
const { hive, repo } = await initializeAutomergeRepoKeyhiveRustWithRepo({
|
|
124
|
+
createRepo: (config) => new Repo(config),
|
|
125
|
+
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
137
126
|
peerIdSuffix: `${siteName}-worker` + Math.random().toString(36).slice(2),
|
|
138
|
-
subduction: repoSubductionPromise,
|
|
139
127
|
automaticArchiveIngestion: true,
|
|
140
128
|
cachingMode: "periodic",
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
storage: new IndexedDBStorageAdapter(),
|
|
151
|
-
signer,
|
|
152
|
-
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
153
|
-
peerId: hive.peerId,
|
|
154
|
-
enableRemoteHeadsGossiping: true,
|
|
155
|
-
idFactory: hive.idFactory,
|
|
129
|
+
// ARK selects the relay via `syncServer` ("keyhive" | "subduction"),
|
|
130
|
+
// which pairs the contact card with the matching peer id. Omitting it
|
|
131
|
+
// defaults to "subduction".
|
|
132
|
+
...(useKeyhiveSyncServer ? { syncServer: "keyhive" } : {}),
|
|
133
|
+
repo: {
|
|
134
|
+
storage: new IndexedDBStorageAdapter(),
|
|
135
|
+
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
136
|
+
enableRemoteHeadsGossiping: true,
|
|
137
|
+
},
|
|
156
138
|
});
|
|
157
|
-
repo.subduction.then(resolveRepoSubduction);
|
|
158
|
-
hive.linkRepo(repo);
|
|
159
139
|
self.repo = repo;
|
|
160
140
|
self.hive = hive;
|
|
161
141
|
log("repo constructed, waiting for network subsystem");
|
|
@@ -239,6 +219,7 @@ async function connectPort(port, connection) {
|
|
|
239
219
|
}
|
|
240
220
|
});
|
|
241
221
|
keyhiveNetworkAdapter.on("ingest-remote", () => {
|
|
222
|
+
hive.notifySameAgentKeyhiveChange();
|
|
242
223
|
hive.networkAdapter.syncKeyhive?.();
|
|
243
224
|
repo.shareConfigChanged();
|
|
244
225
|
});
|
package/dist/externals.js
CHANGED
package/dist/service-worker.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { HANDOFF_CHANNEL, } from "./types.js";
|
|
9
9
|
let cachename = "default";
|
|
10
10
|
let debugging = false;
|
|
11
|
-
const cacheableStatuses = [200, 203, 204
|
|
11
|
+
const cacheableStatuses = [200, 203, 204];
|
|
12
12
|
// The automerge worker times its own resolution out after 30s and replies
|
|
13
13
|
// with an error, so this only fires when nobody is listening at all.
|
|
14
14
|
const HANDOFF_TIMEOUT_MS = 35_000;
|
|
@@ -163,7 +163,9 @@ self.addEventListener("fetch", (fetchEvent) => {
|
|
|
163
163
|
if (response) {
|
|
164
164
|
if (cacheableStatuses.includes(response.status) &&
|
|
165
165
|
response.url.match(/^https?\:/)) {
|
|
166
|
-
await cache.put(request, response.clone())
|
|
166
|
+
await cache.put(request, response.clone()).catch((error) => {
|
|
167
|
+
log(`error caching ${request.url} in ${cachename}`, error);
|
|
168
|
+
});
|
|
167
169
|
}
|
|
168
170
|
else {
|
|
169
171
|
log(`skipping uncacheable response code from cache: ${response.status} for ${response.url}`);
|
package/dist/site.d.ts
CHANGED
|
@@ -40,16 +40,32 @@ declare global {
|
|
|
40
40
|
}
|
|
41
41
|
export interface SiteConfig {
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* The site's default tool bundle — the tools every user of this site gets
|
|
44
|
+
* out of the box. Must collectively contribute at least a
|
|
45
|
+
* `patchwork:datatype` registration for `"account"` (typically the one
|
|
46
|
+
* supplied by `@inkandswitch/patchwork-frame`).
|
|
47
|
+
*
|
|
48
|
+
* Each entry is a *module-list source* and may be either:
|
|
49
|
+
* - an Automerge module-settings doc URL (`automerge:...`), which is
|
|
50
|
+
* live-reloaded, or
|
|
51
|
+
* - an HTTP(S) URL (absolute or site-relative, e.g. `/modules.json`) to a
|
|
52
|
+
* static JSON manifest of the shape `{ modules: string[], branches? }`,
|
|
53
|
+
* fetched once at boot.
|
|
54
|
+
*
|
|
55
|
+
* The module URLs *inside* either kind of source may themselves be Automerge
|
|
56
|
+
* folder docs or plain HTTP(S) bundles, so deployment targets can be freely
|
|
57
|
+
* mixed.
|
|
47
58
|
*
|
|
48
59
|
* Can be overridden at runtime by setting `localStorage.defaultToolsUrl` to
|
|
49
|
-
* another automerge
|
|
50
|
-
* unpublished tool set.
|
|
60
|
+
* another `automerge:` URL or manifest URL — useful for local development
|
|
61
|
+
* against an unpublished tool set.
|
|
62
|
+
*/
|
|
63
|
+
defaultModules?: string | string[];
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated Use {@link SiteConfig.defaultModules}. Retained for backwards
|
|
66
|
+
* compatibility with existing sites.
|
|
51
67
|
*/
|
|
52
|
-
defaultModulesUrl
|
|
68
|
+
defaultModulesUrl?: AutomergeUrl;
|
|
53
69
|
/**
|
|
54
70
|
* `localStorage` key under which this site remembers which account document
|
|
55
71
|
* belongs to the current user. Sites sharing an origin MUST use distinct
|
package/dist/site.js
CHANGED
|
@@ -14,11 +14,12 @@
|
|
|
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,
|
|
17
|
+
import { initKeyhiveWasm, initializeAutomergeRepoKeyhiveWithRepo, } from "@automerge/automerge-repo-keyhive";
|
|
18
18
|
// eslint-disable-next-line
|
|
19
19
|
// @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
|
|
20
20
|
import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
|
|
21
21
|
const siteName = typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
22
|
+
const useKeyhiveSyncServer = typeof __KEYHIVE_SYNC_SERVER__ !== "undefined" && __KEYHIVE_SYNC_SERVER__;
|
|
22
23
|
import { ModuleWatcher } from "@inkandswitch/patchwork-filesystem";
|
|
23
24
|
import { openDocument, registerPatchworkViewElement, } from "@inkandswitch/patchwork-elements";
|
|
24
25
|
import { registerRepoProviderElement } from "@inkandswitch/patchwork-providers";
|
|
@@ -44,7 +45,7 @@ const [automergeWasm, subductionWasm] = await Promise.all([
|
|
|
44
45
|
* do additional wiring after boot.
|
|
45
46
|
*/
|
|
46
47
|
export async function bootPatchworkSite(config) {
|
|
47
|
-
const
|
|
48
|
+
const defaultModuleSources = resolveDefaultModules(config);
|
|
48
49
|
showLoadingAnimation();
|
|
49
50
|
log(`booting`, config);
|
|
50
51
|
await initializeWasm(automergeWasm);
|
|
@@ -61,26 +62,28 @@ export async function bootPatchworkSite(config) {
|
|
|
61
62
|
});
|
|
62
63
|
await sw.subscribeToRepoChannel(resolvePort);
|
|
63
64
|
const workerPort = await portPromise;
|
|
65
|
+
let repo;
|
|
64
66
|
if (config.keyhive) {
|
|
65
67
|
initKeyhiveWasm();
|
|
66
|
-
hive = await
|
|
68
|
+
({ hive, repo } = await initializeAutomergeRepoKeyhiveWithRepo({
|
|
69
|
+
createRepo: (config) => new Repo(config),
|
|
67
70
|
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
68
71
|
peerIdSuffix: siteName + Math.random().toString(36).slice(2),
|
|
69
72
|
networkAdapter: new MessageChannelNetworkAdapter(workerPort),
|
|
70
73
|
automaticArchiveIngestion: true,
|
|
71
74
|
cachingMode: "periodic",
|
|
72
75
|
onlyShareWithHardcodedServerPeerId: false,
|
|
73
|
-
|
|
76
|
+
// ARK selects the relay via `syncServer` ("keyhive" | "subduction").
|
|
77
|
+
// Defaults to "subduction".
|
|
78
|
+
...(useKeyhiveSyncServer ? { syncServer: "keyhive" } : {}),
|
|
79
|
+
repo: {
|
|
80
|
+
storage: new IndexedDBStorageAdapter(),
|
|
81
|
+
enableRemoteHeadsGossiping: true,
|
|
82
|
+
},
|
|
83
|
+
}));
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
storage: new IndexedDBStorageAdapter(),
|
|
78
|
-
enableRemoteHeadsGossiping: true,
|
|
79
|
-
network: [hive.networkAdapter],
|
|
80
|
-
peerId: hive.peerId,
|
|
81
|
-
idFactory: hive.idFactory,
|
|
82
|
-
})
|
|
83
|
-
: new Repo({
|
|
85
|
+
else {
|
|
86
|
+
repo = new Repo({
|
|
84
87
|
network: [new MessageChannelNetworkAdapter(workerPort)],
|
|
85
88
|
storage: new IndexedDBStorageAdapter(),
|
|
86
89
|
async sharePolicy(peerId) {
|
|
@@ -89,6 +92,7 @@ export async function bootPatchworkSite(config) {
|
|
|
89
92
|
enableRemoteHeadsGossiping: true,
|
|
90
93
|
peerId: `${config.titleSuffix}-tab-${crypto.randomUUID()}`,
|
|
91
94
|
});
|
|
95
|
+
}
|
|
92
96
|
repo.subscribeToRemotes(config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]);
|
|
93
97
|
await repo.networkSubsystem.whenReady();
|
|
94
98
|
if (hive) {
|
|
@@ -100,15 +104,17 @@ export async function bootPatchworkSite(config) {
|
|
|
100
104
|
if (!rootElement) {
|
|
101
105
|
throw new Error(`bootPatchworkSite: no element with id="${config.rootElementId ?? "root"}"`);
|
|
102
106
|
}
|
|
107
|
+
// `<repo-provider>` sits above the root and answers `repo:handle-descriptor`
|
|
108
|
+
// for any view outside a remapper (resolving to the requested url unchanged).
|
|
103
109
|
const repoProvider = document.createElement("repo-provider");
|
|
104
110
|
rootElement.parentElement.insertBefore(repoProvider, rootElement);
|
|
105
111
|
repoProvider.appendChild(rootElement);
|
|
106
|
-
registerPatchworkViewElement(
|
|
112
|
+
registerPatchworkViewElement({ hive, repo });
|
|
107
113
|
// The watcher is started with the site's default-tools bundle alone so that
|
|
108
114
|
// `resolveAccountHandle` below has something to await on (the `account`
|
|
109
115
|
// datatype lives in that bundle today). The user's own module-settings URL
|
|
110
116
|
// is added lazily once it appears on the account doc — see below.
|
|
111
|
-
const moduleWatcher = new ModuleWatcher(repo,
|
|
117
|
+
const moduleWatcher = new ModuleWatcher(repo, buildSystemSources(defaultModuleSources), onModuleLoaded, unregisterPlugins);
|
|
112
118
|
const accountDocHandle = (await resolveAccountHandle(repo, {
|
|
113
119
|
storageKey: config.accountStorageKey,
|
|
114
120
|
hive,
|
|
@@ -140,18 +146,56 @@ export async function bootPatchworkSite(config) {
|
|
|
140
146
|
return { repo, moduleWatcher, accountDocHandle };
|
|
141
147
|
}
|
|
142
148
|
// ─── Internals ──────────────────────────────────────────────────────────
|
|
143
|
-
|
|
149
|
+
/**
|
|
150
|
+
* A module-list source is valid if it is an Automerge URL or looks like an
|
|
151
|
+
* HTTP(S)/site-relative manifest URL.
|
|
152
|
+
*/
|
|
153
|
+
function isValidModuleSource(source) {
|
|
154
|
+
if (isValidAutomergeUrl(source))
|
|
155
|
+
return true;
|
|
156
|
+
return (source.startsWith("/") ||
|
|
157
|
+
source.startsWith("http://") ||
|
|
158
|
+
source.startsWith("https://") ||
|
|
159
|
+
source.startsWith("./"));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the site's default module-list sources, honouring the
|
|
163
|
+
* `localStorage.defaultToolsUrl` dev override (which replaces the entire
|
|
164
|
+
* built-in default bundle).
|
|
165
|
+
*/
|
|
166
|
+
function resolveDefaultModules(config) {
|
|
167
|
+
const builtin = config.defaultModules ??
|
|
168
|
+
config.defaultModulesUrl ??
|
|
169
|
+
[];
|
|
170
|
+
const builtinList = (Array.isArray(builtin) ? builtin : [builtin]).filter(Boolean);
|
|
144
171
|
const override = globalThis.localStorage?.getItem("defaultToolsUrl");
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
172
|
+
if (override) {
|
|
173
|
+
if (isValidModuleSource(override)) {
|
|
174
|
+
if (!builtinList.includes(override)) {
|
|
175
|
+
console.info(`using defaultToolsUrl override from localStorage: ${override}`);
|
|
176
|
+
}
|
|
177
|
+
return [override];
|
|
150
178
|
}
|
|
151
|
-
|
|
179
|
+
console.warn(`ignoring invalid defaultToolsUrl in localStorage: ${override}; using built-in default`);
|
|
180
|
+
}
|
|
181
|
+
if (builtinList.length === 0) {
|
|
182
|
+
throw new Error("bootPatchworkSite: no default module sources configured (set `defaultModules`)");
|
|
152
183
|
}
|
|
153
|
-
|
|
154
|
-
|
|
184
|
+
return builtinList;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Turn an ordered list of module-list sources into the name-keyed map the
|
|
188
|
+
* ModuleWatcher expects. The first source keeps the canonical `system` name;
|
|
189
|
+
* additional sources get suffixed names. None may be `user` (reserved for the
|
|
190
|
+
* per-account settings doc, which has branch-override precedence).
|
|
191
|
+
*/
|
|
192
|
+
function buildSystemSources(sources) {
|
|
193
|
+
const map = {};
|
|
194
|
+
sources.forEach((source, index) => {
|
|
195
|
+
const name = index === 0 ? "system" : `system-${index}`;
|
|
196
|
+
map[name] = source;
|
|
197
|
+
});
|
|
198
|
+
return map;
|
|
155
199
|
}
|
|
156
200
|
function installDevConsoleGlobals(repo, hive, getRepoChannel) {
|
|
157
201
|
window.repo = repo;
|
|
@@ -200,7 +244,7 @@ function primeRootElement(rootElement, accountDocHandle) {
|
|
|
200
244
|
const initialParams = new URLSearchParams(location.hash.slice(1));
|
|
201
245
|
if (initialParams.has("frame")) {
|
|
202
246
|
rootElement.setAttribute("tool-id", initialParams.get("frame"));
|
|
203
|
-
const docId = initialParams.get("doc");
|
|
247
|
+
const docId = initialParams.get("doc")?.replace(/^automerge:/, "");
|
|
204
248
|
const docUrl = docId
|
|
205
249
|
? stringifyAutomergeUrl({ documentId: docId })
|
|
206
250
|
: accountDocHandle.url;
|
|
@@ -366,15 +410,22 @@ function installHashRouting(params) {
|
|
|
366
410
|
}
|
|
367
411
|
return;
|
|
368
412
|
}
|
|
413
|
+
// Bare automerge URL in hash: /#automerge:<documentId>
|
|
414
|
+
if (isValidAutomergeUrl(hash)) {
|
|
415
|
+
const { documentId, heads } = parseAutomergeUrl(hash);
|
|
416
|
+
window.location.hash = "";
|
|
417
|
+
openDocument(rootElement, stringifyAutomergeUrl({ documentId, heads }));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
369
420
|
const params = new URLSearchParams(hash);
|
|
370
|
-
const documentId = params.get("doc");
|
|
421
|
+
const documentId = params.get("doc")?.replace(/^automerge:/, "");
|
|
371
422
|
const heads = params.get("heads")?.split("|");
|
|
372
423
|
const toolId = params.get("tool");
|
|
373
424
|
const title = params.get("title");
|
|
374
425
|
const type = params.get("type");
|
|
375
426
|
const frame = params.get("frame");
|
|
376
427
|
if (frame) {
|
|
377
|
-
const docUrl = params.get("doc") ?? accountDocHandle.url;
|
|
428
|
+
const docUrl = params.get("doc")?.replace(/^automerge:/, "") ?? accountDocHandle.url;
|
|
378
429
|
if (rootElement.getAttribute("tool-id") !== frame ||
|
|
379
430
|
rootElement.getAttribute("doc-url") !== docUrl) {
|
|
380
431
|
rootElement.setAttribute("tool-id", frame);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkandswitch/patchwork-bootloader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"author": "chee",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"devDependencies": {
|
|
8
|
-
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.
|
|
8
|
+
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.6c",
|
|
9
9
|
"esbuild": "^0.23.1",
|
|
10
10
|
"rollup": "^4.61.1"
|
|
11
11
|
},
|
|
@@ -41,28 +41,28 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@automerge/automerge": "3.3.0-fragments.1",
|
|
44
|
-
"@automerge/automerge-repo": "2.6.0-subduction.
|
|
45
|
-
"@automerge/automerge-repo-network-messagechannel": "2.6.0-subduction.
|
|
46
|
-
"@automerge/automerge-repo-network-websocket": "2.6.0-subduction.
|
|
47
|
-
"@automerge/automerge-repo-storage-indexeddb": "2.6.0-subduction.
|
|
48
|
-
"@automerge/automerge-subduction": "0.
|
|
49
|
-
"@automerge/vanillajs": "2.6.0-subduction.
|
|
50
|
-
"@keyhive/keyhive": "0.
|
|
44
|
+
"@automerge/automerge-repo": "2.6.0-subduction.34",
|
|
45
|
+
"@automerge/automerge-repo-network-messagechannel": "2.6.0-subduction.34",
|
|
46
|
+
"@automerge/automerge-repo-network-websocket": "2.6.0-subduction.34",
|
|
47
|
+
"@automerge/automerge-repo-storage-indexeddb": "2.6.0-subduction.34",
|
|
48
|
+
"@automerge/automerge-subduction": "0.16.0",
|
|
49
|
+
"@automerge/vanillajs": "2.6.0-subduction.34",
|
|
50
|
+
"@keyhive/keyhive": "0.1.0-alpha.3",
|
|
51
51
|
"@types/debug": "^4.1.13",
|
|
52
52
|
"debug": "^4.4.3",
|
|
53
53
|
"resolve.exports": "^2.0.3",
|
|
54
54
|
"service-worker-types": "npm:@types/serviceworker@^0.0.153",
|
|
55
55
|
"tinyargs": "^0.1.4",
|
|
56
|
-
"@inkandswitch/patchwork-
|
|
57
|
-
"@inkandswitch/patchwork-filesystem": "^0.0.8",
|
|
56
|
+
"@inkandswitch/patchwork-filesystem": "^0.1.0",
|
|
58
57
|
"@inkandswitch/patchwork-plugins": "^0.0.11",
|
|
59
|
-
"@inkandswitch/patchwork-providers": "^0.
|
|
58
|
+
"@inkandswitch/patchwork-providers": "^0.3.0",
|
|
59
|
+
"@inkandswitch/patchwork-elements": "^2.0.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@automerge/automerge": "3.3.0-fragments.1",
|
|
63
|
-
"@automerge/automerge-repo": "2.6.0-subduction.
|
|
64
|
-
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.
|
|
65
|
-
"@automerge/vanillajs": "2.6.0-subduction.
|
|
63
|
+
"@automerge/automerge-repo": "2.6.0-subduction.34",
|
|
64
|
+
"@automerge/automerge-repo-keyhive": "0.3.0-alpha.sub.6c",
|
|
65
|
+
"@automerge/vanillajs": "2.6.0-subduction.34"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"build": "tsc",
|
package/src/automerge-worker.ts
CHANGED
|
@@ -34,7 +34,7 @@ import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-index
|
|
|
34
34
|
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel";
|
|
35
35
|
import { WebSocketClientAdapter } from "@automerge/automerge-repo-network-websocket";
|
|
36
36
|
import {
|
|
37
|
-
|
|
37
|
+
initializeAutomergeRepoKeyhiveRustWithRepo,
|
|
38
38
|
initKeyhiveWasm,
|
|
39
39
|
type AutomergeRepoKeyhiveRust,
|
|
40
40
|
} from "@automerge/automerge-repo-keyhive";
|
|
@@ -68,12 +68,6 @@ if (useKeyhiveSyncServer) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// keyhive.sync.automerge.org's keyhive identity (issuer d7f41e6f…).
|
|
72
|
-
const KEYHIVE_SYNC_SERVER_PEER_ID =
|
|
73
|
-
"1/Qebw9O69oH8T/ejYMhFup0tNBh69I3ytGqsmIl358=";
|
|
74
|
-
const KEYHIVE_SYNC_SERVER_CONTACT_CARD_JSON =
|
|
75
|
-
'{"Rotate":{"payload":{"old":[73,163,230,244,111,233,153,119,133,211,134,237,111,36,52,131,22,50,54,144,150,45,227,235,128,36,33,217,190,198,55,75],"new":[109,115,204,144,178,114,182,238,113,124,4,139,249,76,220,44,128,104,194,68,187,184,82,241,94,145,104,198,159,122,186,43]},"issuer":[215,244,30,111,15,78,235,218,7,241,63,222,141,131,33,22,234,116,180,208,97,235,210,55,202,209,170,178,98,37,223,159],"signature":[178,64,85,76,51,199,196,151,129,14,191,53,127,191,34,223,97,238,95,109,118,179,152,17,205,188,204,177,116,166,147,231,192,201,48,137,19,214,180,45,108,104,34,8,14,63,115,139,215,142,4,179,233,89,150,218,174,168,107,23,8,109,228,6]}}';
|
|
76
|
-
|
|
77
71
|
const SUBDUCTION_ENDPOINTS = [
|
|
78
72
|
useKeyhiveSyncServer
|
|
79
73
|
? "wss://keyhive.sync.automerge.org"
|
|
@@ -121,7 +115,7 @@ async function connectClassicSyncNetwork(server: string): Promise<void> {
|
|
|
121
115
|
const siteName =
|
|
122
116
|
typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
123
117
|
|
|
124
|
-
const cacheableStatuses = [200, 203, 204
|
|
118
|
+
const cacheableStatuses = [200, 203, 204];
|
|
125
119
|
|
|
126
120
|
function log(...args: any[]) {
|
|
127
121
|
if (!debugging) return;
|
|
@@ -183,48 +177,26 @@ function getRepoHive() {
|
|
|
183
177
|
}
|
|
184
178
|
|
|
185
179
|
initKeyhiveWasm();
|
|
186
|
-
const keyhiveStorage = new IndexedDBStorageAdapter(`${siteName}-keyhive`);
|
|
187
|
-
|
|
188
|
-
// Keyhive bootstrap needs to run before Repo creation but
|
|
189
|
-
// the adapter needs the subduction instance from the Repo.
|
|
190
|
-
// A deferred promise breaks the cycle.
|
|
191
|
-
let resolveRepoSubduction!: (s: any) => void;
|
|
192
|
-
const repoSubductionPromise = new Promise((resolve) => {
|
|
193
|
-
resolveRepoSubduction = resolve;
|
|
194
|
-
});
|
|
195
180
|
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
storage:
|
|
181
|
+
// ARK variant for talking to the keyhive-enabled subduction sync server.
|
|
182
|
+
const { hive, repo } = await initializeAutomergeRepoKeyhiveRustWithRepo({
|
|
183
|
+
createRepo: (config) => new Repo(config),
|
|
184
|
+
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
200
185
|
peerIdSuffix:
|
|
201
186
|
`${siteName}-worker` + Math.random().toString(36).slice(2),
|
|
202
|
-
subduction: repoSubductionPromise as any,
|
|
203
187
|
automaticArchiveIngestion: true,
|
|
204
188
|
cachingMode: "periodic",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const repo = new Repo({
|
|
216
|
-
storage: new IndexedDBStorageAdapter(),
|
|
217
|
-
signer,
|
|
218
|
-
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
219
|
-
peerId: hive.peerId,
|
|
220
|
-
enableRemoteHeadsGossiping: true,
|
|
221
|
-
idFactory: hive.idFactory,
|
|
189
|
+
// ARK selects the relay via `syncServer` ("keyhive" | "subduction"),
|
|
190
|
+
// which pairs the contact card with the matching peer id. Omitting it
|
|
191
|
+
// defaults to "subduction".
|
|
192
|
+
...(useKeyhiveSyncServer ? { syncServer: "keyhive" as const } : {}),
|
|
193
|
+
repo: {
|
|
194
|
+
storage: new IndexedDBStorageAdapter(),
|
|
195
|
+
subductionWebsocketEndpoints: SUBDUCTION_ENDPOINTS,
|
|
196
|
+
enableRemoteHeadsGossiping: true,
|
|
197
|
+
},
|
|
222
198
|
});
|
|
223
199
|
|
|
224
|
-
repo.subduction.then(resolveRepoSubduction);
|
|
225
|
-
|
|
226
|
-
hive.linkRepo(repo);
|
|
227
|
-
|
|
228
200
|
(self as any).repo = repo;
|
|
229
201
|
(self as any).hive = hive;
|
|
230
202
|
log("repo constructed, waiting for network subsystem");
|
|
@@ -336,6 +308,7 @@ async function connectPort(port: MessagePort, connection: Connection) {
|
|
|
336
308
|
});
|
|
337
309
|
|
|
338
310
|
(keyhiveNetworkAdapter as any).on("ingest-remote", () => {
|
|
311
|
+
hive.notifySameAgentKeyhiveChange();
|
|
339
312
|
(hive.networkAdapter as any).syncKeyhive?.();
|
|
340
313
|
repo.shareConfigChanged();
|
|
341
314
|
});
|
package/src/externals.ts
CHANGED
package/src/service-worker.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
let cachename = "default";
|
|
17
17
|
let debugging = false;
|
|
18
18
|
|
|
19
|
-
const cacheableStatuses = [200, 203, 204
|
|
19
|
+
const cacheableStatuses = [200, 203, 204];
|
|
20
20
|
|
|
21
21
|
// The automerge worker times its own resolution out after 30s and replies
|
|
22
22
|
// with an error, so this only fires when nobody is listening at all.
|
|
@@ -220,7 +220,9 @@ self.addEventListener("fetch", (fetchEvent: FetchEvent) => {
|
|
|
220
220
|
cacheableStatuses.includes(response.status) &&
|
|
221
221
|
response.url.match(/^https?\:/)
|
|
222
222
|
) {
|
|
223
|
-
await cache.put(request, response.clone())
|
|
223
|
+
await cache.put(request, response.clone()).catch((error) => {
|
|
224
|
+
log(`error caching ${request.url} in ${cachename}`, error);
|
|
225
|
+
});
|
|
224
226
|
} else {
|
|
225
227
|
log(
|
|
226
228
|
`skipping uncacheable response code from cache: ${response.status} for ${response.url}`
|
package/src/site.ts
CHANGED
|
@@ -30,7 +30,7 @@ import * as Automerge from "@automerge/automerge/slim";
|
|
|
30
30
|
import * as AutomergeRepo from "@automerge/automerge-repo/slim";
|
|
31
31
|
import {
|
|
32
32
|
initKeyhiveWasm,
|
|
33
|
-
|
|
33
|
+
initializeAutomergeRepoKeyhiveWithRepo,
|
|
34
34
|
type AutomergeRepoKeyhive,
|
|
35
35
|
} from "@automerge/automerge-repo-keyhive";
|
|
36
36
|
// eslint-disable-next-line
|
|
@@ -41,6 +41,14 @@ declare const __SITE_NAME__: string;
|
|
|
41
41
|
const siteName =
|
|
42
42
|
typeof __SITE_NAME__ !== "undefined" ? __SITE_NAME__ : "tiny-patchwork";
|
|
43
43
|
|
|
44
|
+
// Sync-server selection for keyhive. Defaults to "subduction". Build with
|
|
45
|
+
// KEYHIVE_SYNC_SERVER=true to target keyhive.sync.automerge.org. This must match
|
|
46
|
+
// the automerge-worker (SharedWorker) selection so the tab and the SW grant relay
|
|
47
|
+
// access to the same server.
|
|
48
|
+
declare const __KEYHIVE_SYNC_SERVER__: boolean;
|
|
49
|
+
const useKeyhiveSyncServer =
|
|
50
|
+
typeof __KEYHIVE_SYNC_SERVER__ !== "undefined" && __KEYHIVE_SYNC_SERVER__;
|
|
51
|
+
|
|
44
52
|
import { ModuleWatcher } from "@inkandswitch/patchwork-filesystem";
|
|
45
53
|
import {
|
|
46
54
|
openDocument,
|
|
@@ -89,16 +97,33 @@ declare global {
|
|
|
89
97
|
|
|
90
98
|
export interface SiteConfig {
|
|
91
99
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
100
|
+
* The site's default tool bundle — the tools every user of this site gets
|
|
101
|
+
* out of the box. Must collectively contribute at least a
|
|
102
|
+
* `patchwork:datatype` registration for `"account"` (typically the one
|
|
103
|
+
* supplied by `@inkandswitch/patchwork-frame`).
|
|
104
|
+
*
|
|
105
|
+
* Each entry is a *module-list source* and may be either:
|
|
106
|
+
* - an Automerge module-settings doc URL (`automerge:...`), which is
|
|
107
|
+
* live-reloaded, or
|
|
108
|
+
* - an HTTP(S) URL (absolute or site-relative, e.g. `/modules.json`) to a
|
|
109
|
+
* static JSON manifest of the shape `{ modules: string[], branches? }`,
|
|
110
|
+
* fetched once at boot.
|
|
111
|
+
*
|
|
112
|
+
* The module URLs *inside* either kind of source may themselves be Automerge
|
|
113
|
+
* folder docs or plain HTTP(S) bundles, so deployment targets can be freely
|
|
114
|
+
* mixed.
|
|
96
115
|
*
|
|
97
116
|
* Can be overridden at runtime by setting `localStorage.defaultToolsUrl` to
|
|
98
|
-
* another automerge
|
|
99
|
-
* unpublished tool set.
|
|
117
|
+
* another `automerge:` URL or manifest URL — useful for local development
|
|
118
|
+
* against an unpublished tool set.
|
|
119
|
+
*/
|
|
120
|
+
defaultModules?: string | string[];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @deprecated Use {@link SiteConfig.defaultModules}. Retained for backwards
|
|
124
|
+
* compatibility with existing sites.
|
|
100
125
|
*/
|
|
101
|
-
defaultModulesUrl
|
|
126
|
+
defaultModulesUrl?: AutomergeUrl;
|
|
102
127
|
|
|
103
128
|
/**
|
|
104
129
|
* `localStorage` key under which this site remembers which account document
|
|
@@ -163,7 +188,7 @@ const [automergeWasm, subductionWasm] = await Promise.all([
|
|
|
163
188
|
export async function bootPatchworkSite(
|
|
164
189
|
config: SiteConfig
|
|
165
190
|
): Promise<BootResult> {
|
|
166
|
-
const
|
|
191
|
+
const defaultModuleSources = resolveDefaultModules(config);
|
|
167
192
|
showLoadingAnimation();
|
|
168
193
|
log(`booting`, config);
|
|
169
194
|
await initializeWasm(automergeWasm);
|
|
@@ -173,53 +198,53 @@ export async function bootPatchworkSite(
|
|
|
173
198
|
if (!sw) throw new Error("Failed to set up service worker");
|
|
174
199
|
|
|
175
200
|
let hive: AutomergeRepoKeyhive | undefined;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
201
|
+
// Get the initial automerge-worker port via subscribeToRepoChannel,
|
|
202
|
+
// then pass it to keyhive init which wraps it in its own network adapter.
|
|
203
|
+
let resolvePort!: (port: MessagePort) => void;
|
|
204
|
+
const portPromise = new Promise<MessagePort>((r) => {
|
|
205
|
+
resolvePort = r;
|
|
206
|
+
});
|
|
207
|
+
await sw.subscribeToRepoChannel(resolvePort);
|
|
208
|
+
const workerPort = await portPromise;
|
|
184
209
|
|
|
210
|
+
let repo: Repo;
|
|
185
211
|
if (config.keyhive) {
|
|
186
212
|
initKeyhiveWasm();
|
|
187
213
|
|
|
188
|
-
hive = await
|
|
214
|
+
({ hive, repo } = await initializeAutomergeRepoKeyhiveWithRepo({
|
|
215
|
+
createRepo: (config) => new Repo(config),
|
|
189
216
|
storage: new IndexedDBStorageAdapter(`${siteName}-keyhive`),
|
|
190
217
|
peerIdSuffix: siteName + Math.random().toString(36).slice(2),
|
|
191
218
|
networkAdapter: new MessageChannelNetworkAdapter(workerPort),
|
|
192
219
|
automaticArchiveIngestion: true,
|
|
193
220
|
cachingMode: "periodic",
|
|
194
221
|
onlyShareWithHardcodedServerPeerId: false,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
? new Repo({
|
|
222
|
+
// ARK selects the relay via `syncServer` ("keyhive" | "subduction").
|
|
223
|
+
// Defaults to "subduction".
|
|
224
|
+
...(useKeyhiveSyncServer ? { syncServer: "keyhive" as const } : {}),
|
|
225
|
+
repo: {
|
|
200
226
|
storage: new IndexedDBStorageAdapter(),
|
|
201
227
|
enableRemoteHeadsGossiping: true,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
230
|
+
} else {
|
|
231
|
+
repo = new Repo({
|
|
232
|
+
network: [new MessageChannelNetworkAdapter(workerPort)],
|
|
233
|
+
storage: new IndexedDBStorageAdapter(),
|
|
234
|
+
async sharePolicy(peerId) {
|
|
235
|
+
return peerId.includes("automerge-worker");
|
|
236
|
+
},
|
|
237
|
+
enableRemoteHeadsGossiping: true,
|
|
238
|
+
peerId:
|
|
239
|
+
`${config.titleSuffix}-tab-${crypto.randomUUID()}` as AutomergeRepo.PeerId,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
216
242
|
repo.subscribeToRemotes(
|
|
217
243
|
config.remoteStorageIds ?? [DEFAULT_REMOTE_STORAGE_ID]
|
|
218
244
|
);
|
|
219
245
|
|
|
220
246
|
await repo.networkSubsystem.whenReady();
|
|
221
247
|
if (hive) {
|
|
222
|
-
|
|
223
248
|
(hive.networkAdapter as any).syncKeyhive?.();
|
|
224
249
|
}
|
|
225
250
|
|
|
@@ -233,11 +258,13 @@ export async function bootPatchworkSite(
|
|
|
233
258
|
`bootPatchworkSite: no element with id="${config.rootElementId ?? "root"}"`
|
|
234
259
|
);
|
|
235
260
|
}
|
|
261
|
+
// `<repo-provider>` sits above the root and answers `repo:handle-descriptor`
|
|
262
|
+
// for any view outside a remapper (resolving to the requested url unchanged).
|
|
236
263
|
const repoProvider = document.createElement("repo-provider");
|
|
237
264
|
rootElement.parentElement!.insertBefore(repoProvider, rootElement);
|
|
238
265
|
repoProvider.appendChild(rootElement);
|
|
239
266
|
|
|
240
|
-
registerPatchworkViewElement(
|
|
267
|
+
registerPatchworkViewElement({ hive, repo });
|
|
241
268
|
|
|
242
269
|
// The watcher is started with the site's default-tools bundle alone so that
|
|
243
270
|
// `resolveAccountHandle` below has something to await on (the `account`
|
|
@@ -245,7 +272,7 @@ export async function bootPatchworkSite(
|
|
|
245
272
|
// is added lazily once it appears on the account doc — see below.
|
|
246
273
|
const moduleWatcher = new ModuleWatcher(
|
|
247
274
|
repo,
|
|
248
|
-
|
|
275
|
+
buildSystemSources(defaultModuleSources),
|
|
249
276
|
onModuleLoaded,
|
|
250
277
|
unregisterPlugins
|
|
251
278
|
);
|
|
@@ -289,21 +316,70 @@ export async function bootPatchworkSite(
|
|
|
289
316
|
|
|
290
317
|
// ─── Internals ──────────────────────────────────────────────────────────
|
|
291
318
|
|
|
292
|
-
|
|
319
|
+
/**
|
|
320
|
+
* A module-list source is valid if it is an Automerge URL or looks like an
|
|
321
|
+
* HTTP(S)/site-relative manifest URL.
|
|
322
|
+
*/
|
|
323
|
+
function isValidModuleSource(source: string): boolean {
|
|
324
|
+
if (isValidAutomergeUrl(source)) return true;
|
|
325
|
+
return (
|
|
326
|
+
source.startsWith("/") ||
|
|
327
|
+
source.startsWith("http://") ||
|
|
328
|
+
source.startsWith("https://") ||
|
|
329
|
+
source.startsWith("./")
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Resolve the site's default module-list sources, honouring the
|
|
335
|
+
* `localStorage.defaultToolsUrl` dev override (which replaces the entire
|
|
336
|
+
* built-in default bundle).
|
|
337
|
+
*/
|
|
338
|
+
function resolveDefaultModules(config: SiteConfig): string[] {
|
|
339
|
+
const builtin =
|
|
340
|
+
config.defaultModules ??
|
|
341
|
+
config.defaultModulesUrl ??
|
|
342
|
+
[];
|
|
343
|
+
const builtinList = (Array.isArray(builtin) ? builtin : [builtin]).filter(
|
|
344
|
+
Boolean
|
|
345
|
+
);
|
|
346
|
+
|
|
293
347
|
const override = globalThis.localStorage?.getItem("defaultToolsUrl");
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
348
|
+
if (override) {
|
|
349
|
+
if (isValidModuleSource(override)) {
|
|
350
|
+
if (!builtinList.includes(override)) {
|
|
351
|
+
console.info(
|
|
352
|
+
`using defaultToolsUrl override from localStorage: ${override}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return [override];
|
|
300
356
|
}
|
|
301
|
-
|
|
357
|
+
console.warn(
|
|
358
|
+
`ignoring invalid defaultToolsUrl in localStorage: ${override}; using built-in default`
|
|
359
|
+
);
|
|
302
360
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
361
|
+
|
|
362
|
+
if (builtinList.length === 0) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
"bootPatchworkSite: no default module sources configured (set `defaultModules`)"
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
return builtinList;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Turn an ordered list of module-list sources into the name-keyed map the
|
|
372
|
+
* ModuleWatcher expects. The first source keeps the canonical `system` name;
|
|
373
|
+
* additional sources get suffixed names. None may be `user` (reserved for the
|
|
374
|
+
* per-account settings doc, which has branch-override precedence).
|
|
375
|
+
*/
|
|
376
|
+
function buildSystemSources(sources: string[]): Record<string, string> {
|
|
377
|
+
const map: Record<string, string> = {};
|
|
378
|
+
sources.forEach((source, index) => {
|
|
379
|
+
const name = index === 0 ? "system" : `system-${index}`;
|
|
380
|
+
map[name] = source;
|
|
381
|
+
});
|
|
382
|
+
return map;
|
|
307
383
|
}
|
|
308
384
|
|
|
309
385
|
function installDevConsoleGlobals(
|
|
@@ -371,7 +447,7 @@ function primeRootElement(
|
|
|
371
447
|
const initialParams = new URLSearchParams(location.hash.slice(1));
|
|
372
448
|
if (initialParams.has("frame")) {
|
|
373
449
|
rootElement.setAttribute("tool-id", initialParams.get("frame")!);
|
|
374
|
-
const docId = initialParams.get("doc");
|
|
450
|
+
const docId = initialParams.get("doc")?.replace(/^automerge:/, "");
|
|
375
451
|
const docUrl = docId
|
|
376
452
|
? stringifyAutomergeUrl({ documentId: docId as DocumentId })
|
|
377
453
|
: accountDocHandle.url;
|
|
@@ -557,15 +633,26 @@ function installHashRouting(params: HashRoutingParams): void {
|
|
|
557
633
|
return;
|
|
558
634
|
}
|
|
559
635
|
|
|
636
|
+
// Bare automerge URL in hash: /#automerge:<documentId>
|
|
637
|
+
if (isValidAutomergeUrl(hash as AutomergeUrl)) {
|
|
638
|
+
const { documentId, heads } = parseAutomergeUrl(hash as AutomergeUrl);
|
|
639
|
+
window.location.hash = "";
|
|
640
|
+
openDocument(
|
|
641
|
+
rootElement,
|
|
642
|
+
stringifyAutomergeUrl({ documentId, heads })
|
|
643
|
+
);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
560
647
|
const params = new URLSearchParams(hash);
|
|
561
|
-
const documentId = params.get("doc");
|
|
648
|
+
const documentId = params.get("doc")?.replace(/^automerge:/, "");
|
|
562
649
|
const heads = params.get("heads")?.split("|") as UrlHeads | undefined;
|
|
563
650
|
const toolId = params.get("tool");
|
|
564
651
|
const title = params.get("title");
|
|
565
652
|
const type = params.get("type");
|
|
566
653
|
const frame = params.get("frame");
|
|
567
654
|
if (frame) {
|
|
568
|
-
const docUrl = params.get("doc") ?? accountDocHandle.url;
|
|
655
|
+
const docUrl = params.get("doc")?.replace(/^automerge:/, "") ?? accountDocHandle.url;
|
|
569
656
|
if (
|
|
570
657
|
rootElement.getAttribute("tool-id") !== frame ||
|
|
571
658
|
rootElement.getAttribute("doc-url") !== docUrl
|