@inkandswitch/patchwork-bootloader 0.7.0 → 0.7.1

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @inkandswitch/patchwork-bootloader
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5462610: Move keyhive out of `syncServers` and into its own top-level site option: `keyhive?: boolean | { syncServer?, useIdFactory? }`. `keyhive: true` enables it against the `"subduction"` relay; the object form picks a different relay (or a custom `{url, contactCardJson, peerId}` identity) and turns individual behaviour off. `keyhive.useIdFactory: false` drops the `idFactory` ARK injects into the repo config, so document ids are generated the Repo's own way instead of derived from keyhive. It reaches both the tab repo and the automerge protocol handler worker through the `__SYNC_SERVER__` define.
8
+
9
+ `syncServers` is now just URLs — `subduction` and `classic`, no longer mutually exclusive with anything. Sites passing `syncServers: { keyhive: X }` should pass `keyhive: { syncServer: X }` instead; `syncServers.subduction` still overrides the URL a named relay implies.
10
+
11
+ - 5462610: Persist the Subduction signer so every context on an origin presents the same identity. The seed lives in the shared IndexedDB, so tabs and the automerge protocol handler worker adopt one signer instead of each minting a fresh `MemorySigner` on load. Generating it takes a Web Lock, so a cold profile opening two contexts at once still settles on one seed.
12
+
13
+ `@inkandswitch/patchwork-bootloader/signer` is a new export: `loadOrCreateSigner(storage)` returns the origin's `MemorySigner`, generating and storing a seed the first time.
14
+
3
15
  ## 0.7.0
4
16
 
5
17
  ### Minor Changes
@@ -13,7 +13,6 @@ import { initializeWasm, hasHeads } from "@automerge/automerge/slim";
13
13
  // eslint-disable-next-line
14
14
  // @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
15
15
  import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
16
- import { MemorySigner } from "@automerge/automerge-subduction/slim";
17
16
  import { Repo, isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "@automerge/automerge-repo/slim";
18
17
  import { resolvePath } from "@inkandswitch/patchwork-filesystem";
19
18
  import { IndexedDBWorkerStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb/IndexedDBWorkerStorageAdapter";
@@ -21,6 +20,7 @@ import { WebSocketWorkerClientAdapter } from "@automerge/automerge-repo-network-
21
20
  import { initializeAutomergeRepoKeyhive, initKeyhiveWasm, } from "@automerge/automerge-repo-keyhive";
22
21
  import { DEFAULT_CLASSIC_SYNC_SERVER } from "./sync-config.js";
23
22
  import { siblingAdapters } from "./siblings.js";
23
+ import { loadOrCreateSigner } from "./signer.js";
24
24
  import { keyhiveStorageName, storagePrefix } from "./storage.js";
25
25
  import { startWorkerControl } from "./worker-control.js";
26
26
  import { HANDOFF_CHANNEL, } from "./types.js";
@@ -57,16 +57,17 @@ async function buildRepo() {
57
57
  log("wasm initialized");
58
58
  const { repo, hive } = syncServer.keyhive
59
59
  ? await buildKeyhiveRepo(syncServer.keyhive)
60
- : { repo: buildPlainRepo() };
60
+ : { repo: await buildPlainRepo() };
61
61
  self.repo = repo;
62
62
  if (hive)
63
63
  self.hive = hive;
64
64
  return repo;
65
65
  }
66
- function buildPlainRepo() {
66
+ async function buildPlainRepo() {
67
+ const storage = new IndexedDBWorkerStorageAdapter();
67
68
  return new Repo({
68
- signer: new MemorySigner(),
69
- storage: new IndexedDBWorkerStorageAdapter(),
69
+ signer: await loadOrCreateSigner(storage),
70
+ storage,
70
71
  peerId: `${storagePrefix}-resolver-${Math.random().toString(36).slice(2)}`,
71
72
  subductionWebsocketEndpoints: [syncServer.url],
72
73
  subductionAdapters: siblingAdapters(),
@@ -76,7 +77,9 @@ function buildPlainRepo() {
76
77
  async function buildKeyhiveRepo(keyhiveSyncServer) {
77
78
  initKeyhiveWasm();
78
79
  const { hive, repo } = await initializeAutomergeRepoKeyhive({
79
- createRepo: (config) => new Repo(config),
80
+ // ARK injects an `idFactory` deriving document ids from keyhive. A site
81
+ // can opt out of it with `keyhive: { useIdFactory: false }`.
82
+ createRepo: ({ idFactory, ...config }) => new Repo(syncServer.useIdFactory === false ? config : { ...config, idFactory }),
80
83
  storage: new IndexedDBWorkerStorageAdapter(keyhiveStorageName),
81
84
  peerIdSuffix: `${storagePrefix}-resolver` + Math.random().toString(36).slice(2),
82
85
  automaticArchiveIngestion: true,
@@ -0,0 +1,17 @@
1
+ import type { StorageAdapterInterface } from "@automerge/automerge-repo/slim";
2
+ import { MemorySigner } from "@automerge/automerge-subduction/slim";
3
+ /**
4
+ * The Subduction identity every Repo on this origin presents: each tab and the
5
+ * automerge protocol handler worker sign as the same peer. The seed is kept in
6
+ * the same IndexedDB they already share, so it survives a reload and a new tab
7
+ * picks it up rather than minting its own.
8
+ *
9
+ * Keyhive sites don't come through here — their signer is derived from the
10
+ * keyhive active keypair, which ARK keeps in the keyhive storage those same
11
+ * contexts share.
12
+ *
13
+ * The lock is for a cold profile. Without it two contexts both find nothing,
14
+ * both generate, and both go on using the seed they made while only one of
15
+ * them is the seed in storage.
16
+ */
17
+ export declare function loadOrCreateSigner(storage: StorageAdapterInterface): Promise<MemorySigner>;
package/dist/signer.js ADDED
@@ -0,0 +1,27 @@
1
+ import { MemorySigner } from "@automerge/automerge-subduction/slim";
2
+ const SIGNER_KEY = ["patchwork", "subduction-signer"];
3
+ const SIGNER_LOCK = "patchwork-subduction-signer";
4
+ /**
5
+ * The Subduction identity every Repo on this origin presents: each tab and the
6
+ * automerge protocol handler worker sign as the same peer. The seed is kept in
7
+ * the same IndexedDB they already share, so it survives a reload and a new tab
8
+ * picks it up rather than minting its own.
9
+ *
10
+ * Keyhive sites don't come through here — their signer is derived from the
11
+ * keyhive active keypair, which ARK keeps in the keyhive storage those same
12
+ * contexts share.
13
+ *
14
+ * The lock is for a cold profile. Without it two contexts both find nothing,
15
+ * both generate, and both go on using the seed they made while only one of
16
+ * them is the seed in storage.
17
+ */
18
+ export async function loadOrCreateSigner(storage) {
19
+ return navigator.locks.request(SIGNER_LOCK, async () => {
20
+ const stored = await storage.load(SIGNER_KEY);
21
+ if (stored)
22
+ return MemorySigner.fromBytes(stored);
23
+ const seed = crypto.getRandomValues(new Uint8Array(32));
24
+ await storage.save(SIGNER_KEY, seed);
25
+ return MemorySigner.fromBytes(seed);
26
+ });
27
+ }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "git+https://github.com/inkandswitch/patchwork-system.git",
6
6
  "directory": "core/bootloader"
7
7
  },
8
- "version": "0.7.0",
8
+ "version": "0.7.1",
9
9
  "author": "chee",
10
10
  "type": "module",
11
11
  "license": "MIT",
@@ -30,6 +30,10 @@
30
30
  "import": "./dist/siblings.js",
31
31
  "types": "./dist/siblings.d.ts"
32
32
  },
33
+ "./signer": {
34
+ "import": "./dist/signer.js",
35
+ "types": "./dist/signer.d.ts"
36
+ },
33
37
  "./storage": {
34
38
  "import": "./dist/storage.js",
35
39
  "types": "./dist/storage.d.ts"
@@ -78,8 +82,8 @@
78
82
  "solid-js": "^1.9.13",
79
83
  "tinyargs": "^0.1.4",
80
84
  "@inkandswitch/patchwork-elements": "^6.0.2",
81
- "@inkandswitch/patchwork-filesystem": "^0.2.9",
82
85
  "@inkandswitch/patchwork-plugins": "^1.2.4",
86
+ "@inkandswitch/patchwork-filesystem": "^0.2.9",
83
87
  "@inkandswitch/patchwork-providers": "^0.5.2"
84
88
  },
85
89
  "peerDependencies": {
@@ -13,7 +13,6 @@ import { initializeWasm, hasHeads } from "@automerge/automerge/slim";
13
13
  // eslint-disable-next-line
14
14
  // @ts-ignore — initSync is a wasm-bindgen runtime helper not in the .d.ts
15
15
  import { initSync as initSubductionSync } from "@automerge/automerge-subduction/slim";
16
- import { MemorySigner } from "@automerge/automerge-subduction/slim";
17
16
 
18
17
  import {
19
18
  Repo,
@@ -37,6 +36,7 @@ import {
37
36
 
38
37
  import { DEFAULT_CLASSIC_SYNC_SERVER } from "./sync-config.js";
39
38
  import { siblingAdapters } from "./siblings.js";
39
+ import { loadOrCreateSigner } from "./signer.js";
40
40
  import { keyhiveStorageName, storagePrefix } from "./storage.js";
41
41
  import { startWorkerControl } from "./worker-control.js";
42
42
  import {
@@ -51,6 +51,7 @@ import {
51
51
  declare const __SYNC_SERVER__: {
52
52
  url: string;
53
53
  keyhive?: SyncServerSelection;
54
+ useIdFactory?: boolean;
54
55
  };
55
56
 
56
57
  const syncServer =
@@ -95,17 +96,18 @@ async function buildRepo(): Promise<Repo> {
95
96
 
96
97
  const { repo, hive } = syncServer.keyhive
97
98
  ? await buildKeyhiveRepo(syncServer.keyhive)
98
- : { repo: buildPlainRepo() };
99
+ : { repo: await buildPlainRepo() };
99
100
 
100
101
  (self as any).repo = repo;
101
102
  if (hive) (self as any).hive = hive;
102
103
  return repo;
103
104
  }
104
105
 
105
- function buildPlainRepo(): Repo {
106
+ async function buildPlainRepo(): Promise<Repo> {
107
+ const storage = new IndexedDBWorkerStorageAdapter();
106
108
  return new Repo({
107
- signer: new MemorySigner(),
108
- storage: new IndexedDBWorkerStorageAdapter(),
109
+ signer: await loadOrCreateSigner(storage),
110
+ storage,
109
111
  peerId:
110
112
  `${storagePrefix}-resolver-${Math.random().toString(36).slice(2)}` as PeerId,
111
113
  subductionWebsocketEndpoints: [syncServer.url],
@@ -119,7 +121,12 @@ async function buildKeyhiveRepo(
119
121
  ): Promise<{ repo: Repo; hive: AutomergeRepoKeyhive }> {
120
122
  initKeyhiveWasm();
121
123
  const { hive, repo } = await initializeAutomergeRepoKeyhive({
122
- createRepo: (config) => new Repo(config),
124
+ // ARK injects an `idFactory` deriving document ids from keyhive. A site
125
+ // can opt out of it with `keyhive: { useIdFactory: false }`.
126
+ createRepo: ({ idFactory, ...config }) =>
127
+ new Repo(
128
+ syncServer.useIdFactory === false ? config : { ...config, idFactory }
129
+ ),
123
130
  storage: new IndexedDBWorkerStorageAdapter(keyhiveStorageName),
124
131
  peerIdSuffix:
125
132
  `${storagePrefix}-resolver` + Math.random().toString(36).slice(2),
package/src/signer.ts ADDED
@@ -0,0 +1,32 @@
1
+ import type { StorageAdapterInterface } from "@automerge/automerge-repo/slim";
2
+ import { MemorySigner } from "@automerge/automerge-subduction/slim";
3
+
4
+ const SIGNER_KEY = ["patchwork", "subduction-signer"];
5
+ const SIGNER_LOCK = "patchwork-subduction-signer";
6
+
7
+ /**
8
+ * The Subduction identity every Repo on this origin presents: each tab and the
9
+ * automerge protocol handler worker sign as the same peer. The seed is kept in
10
+ * the same IndexedDB they already share, so it survives a reload and a new tab
11
+ * picks it up rather than minting its own.
12
+ *
13
+ * Keyhive sites don't come through here — their signer is derived from the
14
+ * keyhive active keypair, which ARK keeps in the keyhive storage those same
15
+ * contexts share.
16
+ *
17
+ * The lock is for a cold profile. Without it two contexts both find nothing,
18
+ * both generate, and both go on using the seed they made while only one of
19
+ * them is the seed in storage.
20
+ */
21
+ export async function loadOrCreateSigner(
22
+ storage: StorageAdapterInterface
23
+ ): Promise<MemorySigner> {
24
+ return navigator.locks.request(SIGNER_LOCK, async () => {
25
+ const stored = await storage.load(SIGNER_KEY);
26
+ if (stored) return MemorySigner.fromBytes(stored);
27
+
28
+ const seed = crypto.getRandomValues(new Uint8Array(32));
29
+ await storage.save(SIGNER_KEY, seed);
30
+ return MemorySigner.fromBytes(seed);
31
+ });
32
+ }