@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 +12 -0
- package/dist/automerge-protocol-handler-worker.js +9 -6
- package/dist/signer.d.ts +17 -0
- package/dist/signer.js +27 -0
- package/package.json +6 -2
- package/src/automerge-protocol-handler-worker.ts +13 -6
- package/src/signer.ts +32 -0
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:
|
|
69
|
-
storage
|
|
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
|
-
|
|
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,
|
package/dist/signer.d.ts
ADDED
|
@@ -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.
|
|
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:
|
|
108
|
-
storage
|
|
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
|
-
|
|
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
|
+
}
|