@automerge/automerge-repo 2.3.1 → 2.4.0-alpha.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/dist/AutomergeUrl.d.ts.map +1 -1
- package/dist/AutomergeUrl.js +1 -5
- package/dist/Repo.d.ts +45 -4
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +55 -16
- package/dist/helpers/DummyNetworkAdapter.d.ts.map +1 -1
- package/dist/helpers/DummyNetworkAdapter.js +11 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +19 -2
- package/package.json +2 -2
- package/src/AutomergeUrl.ts +1 -5
- package/src/Repo.ts +103 -14
- package/src/helpers/DummyNetworkAdapter.ts +11 -1
- package/src/synchronizer/CollectionSynchronizer.ts +24 -2
- package/test/AutomergeUrl.test.ts +10 -5
- package/test/Repo.test.ts +215 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutomergeUrl.d.ts","sourceRoot":"","sources":["../src/AutomergeUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAExE,eAAO,MAAM,SAAS,eAAe,CAAA;AAErC,UAAU,kBAAkB;IAC1B,2BAA2B;IAC3B,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sGAAsG;AACtG,eAAO,MAAM,iBAAiB,GAAI,KAAK,YAAY,KAAG,kBAsBrD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,KAAK,UAAU,GAAG,UAAU,GAAG,gBAAgB,KAC9C,YAgCF,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,GAAI,KAAK,YAAY,KAAG,MAAM,EAAE,GAAG,SAG9D,CAAA;AAED,eAAO,MAAM,2BAA2B,GAAI,IAAI,aAAa,6BAO9C,CAAA;AAEf;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,YAsBzD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"AutomergeUrl.d.ts","sourceRoot":"","sources":["../src/AutomergeUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAExE,eAAO,MAAM,SAAS,eAAe,CAAA;AAErC,UAAU,kBAAkB;IAC1B,2BAA2B;IAC3B,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sGAAsG;AACtG,eAAO,MAAM,iBAAiB,GAAI,KAAK,YAAY,KAAG,kBAsBrD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,KAAK,UAAU,GAAG,UAAU,GAAG,gBAAgB,KAC9C,YAgCF,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,GAAI,KAAK,YAAY,KAAG,MAAM,EAAE,GAAG,SAG9D,CAAA;AAED,eAAO,MAAM,2BAA2B,GAAI,IAAI,aAAa,6BAO9C,CAAA;AAEf;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,YAsBzD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,UAKvD,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,gBACH,CAAA;AAE/C;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAO,YAGvC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KACjB,gBAAgB,GAAG,SAAS,CAAA;AAE/D,eAAO,MAAM,kBAAkB,GAAI,OAAO,gBAAgB,KAC7B,UAAU,CAAA;AAEvC,eAAO,MAAM,WAAW,GAAI,OAAO,cAAc,KAAG,QACsB,CAAA;AAE1E,eAAO,MAAM,WAAW,GAAI,OAAO,QAAQ,KAAG,cACgC,CAAA;AAE9E,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,6BAI1C,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,IAAI,aAAa,eAqBtD,CAAA;AAID,KAAK,UAAU,GAAG;IAChB,UAAU,EAAE,UAAU,GAAG,gBAAgB,CAAA;IACzC,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB,CAAA"}
|
package/dist/AutomergeUrl.js
CHANGED
|
@@ -103,11 +103,7 @@ export const isValidDocumentId = (str) => {
|
|
|
103
103
|
return false;
|
|
104
104
|
// try to decode from base58
|
|
105
105
|
const binaryDocumentID = documentIdToBinary(str);
|
|
106
|
-
|
|
107
|
-
return false; // invalid base58check encoding
|
|
108
|
-
// confirm that the document ID is a valid UUID
|
|
109
|
-
const documentId = Uuid.stringify(binaryDocumentID);
|
|
110
|
-
return Uuid.validate(documentId);
|
|
106
|
+
return binaryDocumentID !== undefined;
|
|
111
107
|
};
|
|
112
108
|
export const isValidUuid = (str) => typeof str === "string" && Uuid.validate(str);
|
|
113
109
|
/**
|
package/dist/Repo.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Heads } from "@automerge/automerge/slim";
|
|
1
2
|
import { EventEmitter } from "eventemitter3";
|
|
2
3
|
import { DocHandle } from "./DocHandle.js";
|
|
3
4
|
import { NetworkAdapterInterface, type PeerMetadata } from "./network/NetworkAdapterInterface.js";
|
|
@@ -36,17 +37,23 @@ export declare class Repo extends EventEmitter<RepoEvents> {
|
|
|
36
37
|
storageSubsystem?: StorageSubsystem;
|
|
37
38
|
/** @hidden */
|
|
38
39
|
synchronizer: CollectionSynchronizer;
|
|
39
|
-
/** By default, we share generously with all peers. */
|
|
40
|
-
/** @hidden */
|
|
41
|
-
sharePolicy: SharePolicy;
|
|
42
40
|
/** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
|
|
43
41
|
/** @hidden */
|
|
44
42
|
peerMetadataByPeerId: Record<PeerId, PeerMetadata>;
|
|
45
|
-
constructor({ storage, network, peerId, sharePolicy, isEphemeral, enableRemoteHeadsGossiping, denylist, saveDebounceRate, }?: RepoConfig);
|
|
43
|
+
constructor({ storage, network, peerId, sharePolicy, shareConfig, isEphemeral, enableRemoteHeadsGossiping, denylist, saveDebounceRate, idFactory, }?: RepoConfig);
|
|
46
44
|
/** Returns all the handles we have cached. */
|
|
47
45
|
get handles(): Record<DocumentId, DocHandle<any>>;
|
|
48
46
|
/** Returns a list of all connected peer ids */
|
|
49
47
|
get peers(): PeerId[];
|
|
48
|
+
get peerId(): PeerId;
|
|
49
|
+
/** @hidden */
|
|
50
|
+
get sharePolicy(): SharePolicy;
|
|
51
|
+
/** @hidden */
|
|
52
|
+
set sharePolicy(policy: SharePolicy);
|
|
53
|
+
/** @hidden */
|
|
54
|
+
get shareConfig(): ShareConfig;
|
|
55
|
+
/** @hidden */
|
|
56
|
+
set shareConfig(config: ShareConfig);
|
|
50
57
|
getStorageIdOfPeer(peerId: PeerId): StorageId | undefined;
|
|
51
58
|
/**
|
|
52
59
|
* Creates a new document and returns a handle to it. The initial value of the document is an
|
|
@@ -145,6 +152,16 @@ export interface RepoConfig {
|
|
|
145
152
|
* all peers). A server only syncs documents that a peer explicitly requests by ID.
|
|
146
153
|
*/
|
|
147
154
|
sharePolicy?: SharePolicy;
|
|
155
|
+
/**
|
|
156
|
+
* Whether to share documents with other peers. By default we announce new
|
|
157
|
+
* documents to everyone and allow everyone access to documents, see the
|
|
158
|
+
* documentation for {@link ShareConfig} to override this
|
|
159
|
+
*
|
|
160
|
+
* Note that this is currently an experimental API and will very likely change
|
|
161
|
+
* without a major release.
|
|
162
|
+
* @experimental
|
|
163
|
+
*/
|
|
164
|
+
shareConfig?: ShareConfig;
|
|
148
165
|
/**
|
|
149
166
|
* Whether to enable the experimental remote heads gossiping feature
|
|
150
167
|
*/
|
|
@@ -159,6 +176,10 @@ export interface RepoConfig {
|
|
|
159
176
|
* The debounce rate in milliseconds for saving documents. Defaults to 100ms.
|
|
160
177
|
*/
|
|
161
178
|
saveDebounceRate?: number;
|
|
179
|
+
/**
|
|
180
|
+
* @hidden
|
|
181
|
+
*/
|
|
182
|
+
idFactory?: (initialHeads: Heads) => Uint8Array;
|
|
162
183
|
}
|
|
163
184
|
/** A function that determines whether we should share a document with a peer
|
|
164
185
|
*
|
|
@@ -169,6 +190,26 @@ export interface RepoConfig {
|
|
|
169
190
|
* document with the peer given by `peerId`.
|
|
170
191
|
* */
|
|
171
192
|
export type SharePolicy = (peerId: PeerId, documentId?: DocumentId) => Promise<boolean>;
|
|
193
|
+
/**
|
|
194
|
+
* A type which determines whether we should share a document with a peer
|
|
195
|
+
* */
|
|
196
|
+
export type ShareConfig = {
|
|
197
|
+
/**
|
|
198
|
+
* Whether we should actively announce a document to a peer
|
|
199
|
+
|
|
200
|
+
* @remarks
|
|
201
|
+
* This functions is called after checking the `access` policy to determine
|
|
202
|
+
* whether we should announce a document to a connected peer. For example, a
|
|
203
|
+
* tab connected to a sync server might want to announce every document to the
|
|
204
|
+
* sync server, but the sync server would not want to announce every document
|
|
205
|
+
* to every connected peer
|
|
206
|
+
*/
|
|
207
|
+
announce: SharePolicy;
|
|
208
|
+
/**
|
|
209
|
+
* Whether a peer should have access to the document
|
|
210
|
+
*/
|
|
211
|
+
access: (peer: PeerId, doc: DocumentId) => Promise<boolean>;
|
|
212
|
+
};
|
|
172
213
|
export interface RepoEvents {
|
|
173
214
|
/** A new document was created or discovered */
|
|
174
215
|
document: (arg: DocumentPayload) => void;
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
|
package/dist/Repo.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { next as Automerge } from "@automerge/automerge/slim";
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import { EventEmitter } from "eventemitter3";
|
|
4
|
-
import { encodeHeads, generateAutomergeUrl, interpretAsDocumentId, isValidAutomergeUrl, parseAutomergeUrl, } from "./AutomergeUrl.js";
|
|
4
|
+
import { binaryToDocumentId, encodeHeads, generateAutomergeUrl, interpretAsDocumentId, isValidAutomergeUrl, parseAutomergeUrl, } from "./AutomergeUrl.js";
|
|
5
5
|
import { DELETED, DocHandle, READY, UNAVAILABLE, UNLOADED, } from "./DocHandle.js";
|
|
6
6
|
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
|
|
7
7
|
import { headsAreSame } from "./helpers/headsAreSame.js";
|
|
@@ -34,9 +34,10 @@ export class Repo extends EventEmitter {
|
|
|
34
34
|
#handleCache = {};
|
|
35
35
|
/** @hidden */
|
|
36
36
|
synchronizer;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
#shareConfig = {
|
|
38
|
+
announce: async () => true,
|
|
39
|
+
access: async () => true,
|
|
40
|
+
};
|
|
40
41
|
/** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
|
|
41
42
|
/** @hidden */
|
|
42
43
|
peerMetadataByPeerId = {};
|
|
@@ -44,11 +45,25 @@ export class Repo extends EventEmitter {
|
|
|
44
45
|
#remoteHeadsGossipingEnabled = false;
|
|
45
46
|
#progressCache = {};
|
|
46
47
|
#saveFns = {};
|
|
47
|
-
|
|
48
|
+
#idFactory;
|
|
49
|
+
constructor({ storage, network = [], peerId = randomPeerId(), sharePolicy, shareConfig, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, denylist = [], saveDebounceRate = 100, idFactory, } = {}) {
|
|
48
50
|
super();
|
|
49
51
|
this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
|
|
50
52
|
this.#log = debug(`automerge-repo:repo`);
|
|
51
|
-
this
|
|
53
|
+
this.#idFactory = idFactory || null;
|
|
54
|
+
// Handle legacy sharePolicy
|
|
55
|
+
if (sharePolicy != null && shareConfig != null) {
|
|
56
|
+
throw new Error("cannot provide both sharePolicy and shareConfig at once");
|
|
57
|
+
}
|
|
58
|
+
if (sharePolicy) {
|
|
59
|
+
this.#shareConfig = {
|
|
60
|
+
announce: sharePolicy,
|
|
61
|
+
access: async () => true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (shareConfig) {
|
|
65
|
+
this.#shareConfig = shareConfig;
|
|
66
|
+
}
|
|
52
67
|
this.on("delete-document", ({ documentId }) => {
|
|
53
68
|
this.synchronizer.removeDocument(documentId);
|
|
54
69
|
if (storageSubsystem) {
|
|
@@ -110,7 +125,8 @@ export class Repo extends EventEmitter {
|
|
|
110
125
|
if (peerMetadata) {
|
|
111
126
|
this.peerMetadataByPeerId[peerId] = { ...peerMetadata };
|
|
112
127
|
}
|
|
113
|
-
this
|
|
128
|
+
this.#shareConfig
|
|
129
|
+
.announce(peerId)
|
|
114
130
|
.then(shouldShare => {
|
|
115
131
|
if (shouldShare && this.#remoteHeadsGossipingEnabled) {
|
|
116
132
|
this.#remoteHeadsSubscriptions.addGenerousPeer(peerId);
|
|
@@ -258,6 +274,25 @@ export class Repo extends EventEmitter {
|
|
|
258
274
|
get peers() {
|
|
259
275
|
return this.synchronizer.peers;
|
|
260
276
|
}
|
|
277
|
+
get peerId() {
|
|
278
|
+
return this.networkSubsystem.peerId;
|
|
279
|
+
}
|
|
280
|
+
/** @hidden */
|
|
281
|
+
get sharePolicy() {
|
|
282
|
+
return this.#shareConfig.announce;
|
|
283
|
+
}
|
|
284
|
+
/** @hidden */
|
|
285
|
+
set sharePolicy(policy) {
|
|
286
|
+
this.#shareConfig.announce = policy;
|
|
287
|
+
}
|
|
288
|
+
/** @hidden */
|
|
289
|
+
get shareConfig() {
|
|
290
|
+
return this.#shareConfig;
|
|
291
|
+
}
|
|
292
|
+
/** @hidden */
|
|
293
|
+
set shareConfig(config) {
|
|
294
|
+
this.#shareConfig = config;
|
|
295
|
+
}
|
|
261
296
|
getStorageIdOfPeer(peerId) {
|
|
262
297
|
return this.peerMetadataByPeerId[peerId]?.storageId;
|
|
263
298
|
}
|
|
@@ -267,21 +302,25 @@ export class Repo extends EventEmitter {
|
|
|
267
302
|
* system. we emit a `document` event to advertise interest in the document.
|
|
268
303
|
*/
|
|
269
304
|
create(initialValue) {
|
|
305
|
+
let initialDoc;
|
|
306
|
+
if (initialValue) {
|
|
307
|
+
initialDoc = Automerge.from(initialValue);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
initialDoc = Automerge.emptyChange(Automerge.init());
|
|
311
|
+
}
|
|
270
312
|
// Generate a new UUID and store it in the buffer
|
|
271
|
-
|
|
313
|
+
let { documentId } = parseAutomergeUrl(generateAutomergeUrl());
|
|
314
|
+
if (this.#idFactory) {
|
|
315
|
+
const rawDocId = this.#idFactory(Automerge.getHeads(initialDoc));
|
|
316
|
+
documentId = binaryToDocumentId(rawDocId);
|
|
317
|
+
}
|
|
272
318
|
const handle = this.#getHandle({
|
|
273
319
|
documentId,
|
|
274
320
|
});
|
|
275
321
|
this.#registerHandleWithSubsystems(handle);
|
|
276
322
|
handle.update(() => {
|
|
277
|
-
|
|
278
|
-
if (initialValue) {
|
|
279
|
-
nextDoc = Automerge.from(initialValue);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
nextDoc = Automerge.emptyChange(Automerge.init());
|
|
283
|
-
}
|
|
284
|
-
return nextDoc;
|
|
323
|
+
return initialDoc;
|
|
285
324
|
});
|
|
286
325
|
handle.doneLoading();
|
|
287
326
|
return handle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DummyNetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/helpers/DummyNetworkAdapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEpE,qBAAa,mBAAoB,SAAQ,cAAc;;
|
|
1
|
+
{"version":3,"file":"DummyNetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/helpers/DummyNetworkAdapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEpE,qBAAa,mBAAoB,SAAQ,cAAc;;IAUrD,OAAO;IAIP,SAAS;IAYT,UAAU;gBAIE,IAAI,GAAE,OAA8B;IAQhD,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,UAAU;IAIV,aAAa,CAAC,MAAM,EAAE,MAAM;IAInB,IAAI,CAAC,OAAO,EAAE,OAAO;IAO9B,OAAO,CAAC,OAAO,EAAE,OAAO;IAOxB,MAAM,CAAC,mBAAmB,CAAC,EAAE,OAAY,EAAE,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO;CAcvE;AAED,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;AAE/C,KAAK,OAAO,GAAG;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,aAAa,CAAA;CAC5B,CAAA"}
|
|
@@ -2,6 +2,7 @@ import { pause } from "../../src/helpers/pause.js";
|
|
|
2
2
|
import { NetworkAdapter } from "../../src/index.js";
|
|
3
3
|
export class DummyNetworkAdapter extends NetworkAdapter {
|
|
4
4
|
#sendMessage;
|
|
5
|
+
#connected = false;
|
|
5
6
|
#ready = false;
|
|
6
7
|
#readyResolver;
|
|
7
8
|
#readyPromise = new Promise(resolve => {
|
|
@@ -31,16 +32,25 @@ export class DummyNetworkAdapter extends NetworkAdapter {
|
|
|
31
32
|
this.#sendMessage = opts.sendMessage;
|
|
32
33
|
}
|
|
33
34
|
connect(peerId) {
|
|
35
|
+
this.#connected = true;
|
|
34
36
|
this.peerId = peerId;
|
|
35
37
|
}
|
|
36
|
-
disconnect() {
|
|
38
|
+
disconnect() {
|
|
39
|
+
this.#connected = false;
|
|
40
|
+
}
|
|
37
41
|
peerCandidate(peerId) {
|
|
38
42
|
this.emit("peer-candidate", { peerId, peerMetadata: {} });
|
|
39
43
|
}
|
|
40
44
|
send(message) {
|
|
45
|
+
if (!this.#connected) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
41
48
|
this.#sendMessage?.(message);
|
|
42
49
|
}
|
|
43
50
|
receive(message) {
|
|
51
|
+
if (!this.#connected) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
44
54
|
this.emit("message", message);
|
|
45
55
|
}
|
|
46
56
|
static createConnectedPair({ latency = 10 } = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAa1C,OAAO,CAAC,IAAI;IATxB,kDAAkD;IAClD,cAAc;IACd,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAK;gBAOtC,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAE,YAAY,EAAO;IAwD7D;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;
|
|
1
|
+
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAa1C,OAAO,CAAC,IAAI;IATxB,kDAAkD;IAClD,cAAc;IACd,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAK;gBAOtC,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAE,YAAY,EAAO;IAwD7D;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAuDxC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC;IAWtC,sDAAsD;IACtD,cAAc,CAAC,UAAU,EAAE,UAAU;IAUrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAED,OAAO,IAAI;QACT,CAAC,GAAG,EAAE,MAAM,GAAG;YACb,KAAK,EAAE,MAAM,EAAE,CAAA;YACf,IAAI,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,UAAU,EAAE,MAAM,CAAA;aAAE,CAAA;SAC7C,CAAA;KACF;CAiBF"}
|
|
@@ -54,7 +54,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
54
54
|
const peers = Array.from(this.#peers);
|
|
55
55
|
const generousPeers = [];
|
|
56
56
|
for (const peerId of peers) {
|
|
57
|
-
const okToShare = await this
|
|
57
|
+
const okToShare = await this.#shouldShare(peerId, documentId);
|
|
58
58
|
if (okToShare)
|
|
59
59
|
generousPeers.push(peerId);
|
|
60
60
|
}
|
|
@@ -83,6 +83,16 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
83
83
|
});
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
86
|
+
const hasAccess = await this.repo.shareConfig.access(message.senderId, documentId);
|
|
87
|
+
if (!hasAccess) {
|
|
88
|
+
log("access denied");
|
|
89
|
+
this.emit("message", {
|
|
90
|
+
type: "doc-unavailable",
|
|
91
|
+
documentId,
|
|
92
|
+
targetId: message.senderId,
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
86
96
|
this.#docSetUp[documentId] = true;
|
|
87
97
|
const handle = await this.repo.find(documentId, {
|
|
88
98
|
allowableStates: ["ready", "unavailable", "requesting"],
|
|
@@ -125,7 +135,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
125
135
|
this.#peers.add(peerId);
|
|
126
136
|
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
127
137
|
const { documentId } = docSynchronizer;
|
|
128
|
-
void this
|
|
138
|
+
void this.#shouldShare(peerId, documentId).then(okToShare => {
|
|
129
139
|
if (okToShare)
|
|
130
140
|
void docSynchronizer.beginSync([peerId]);
|
|
131
141
|
});
|
|
@@ -148,4 +158,11 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
148
158
|
return [documentId, synchronizer.metrics()];
|
|
149
159
|
}));
|
|
150
160
|
}
|
|
161
|
+
async #shouldShare(peerId, documentId) {
|
|
162
|
+
const [announce, access] = await Promise.all([
|
|
163
|
+
this.repo.shareConfig.announce(peerId, documentId),
|
|
164
|
+
this.repo.shareConfig.access(peerId, documentId),
|
|
165
|
+
]);
|
|
166
|
+
return announce && access;
|
|
167
|
+
}
|
|
151
168
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0-alpha.1",
|
|
4
4
|
"description": "A repository object to manage a collection of automerge documents",
|
|
5
5
|
"repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
|
|
6
6
|
"author": "Peter van Hardenberg <pvh@pvh.ca>",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "a2d02ab1494cd2d47825844316fff9b3c43951c1"
|
|
63
63
|
}
|
package/src/AutomergeUrl.ts
CHANGED
|
@@ -141,11 +141,7 @@ export const isValidDocumentId = (str: unknown): str is DocumentId => {
|
|
|
141
141
|
if (typeof str !== "string") return false
|
|
142
142
|
// try to decode from base58
|
|
143
143
|
const binaryDocumentID = documentIdToBinary(str as DocumentId)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// confirm that the document ID is a valid UUID
|
|
147
|
-
const documentId = Uuid.stringify(binaryDocumentID)
|
|
148
|
-
return Uuid.validate(documentId)
|
|
144
|
+
return binaryDocumentID !== undefined
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
export const isValidUuid = (str: unknown): str is LegacyDocumentId =>
|
package/src/Repo.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { next as Automerge } from "@automerge/automerge/slim"
|
|
1
|
+
import { next as Automerge, Heads } from "@automerge/automerge/slim"
|
|
2
2
|
import debug from "debug"
|
|
3
3
|
import { EventEmitter } from "eventemitter3"
|
|
4
4
|
import {
|
|
5
|
+
binaryToDocumentId,
|
|
5
6
|
encodeHeads,
|
|
6
7
|
generateAutomergeUrl,
|
|
7
8
|
interpretAsDocumentId,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
import type {
|
|
37
38
|
AnyDocumentId,
|
|
38
39
|
AutomergeUrl,
|
|
40
|
+
BinaryDocumentId,
|
|
39
41
|
DocumentId,
|
|
40
42
|
PeerId,
|
|
41
43
|
} from "./types.js"
|
|
@@ -85,9 +87,10 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
85
87
|
/** @hidden */
|
|
86
88
|
synchronizer: CollectionSynchronizer
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
#shareConfig: ShareConfig = {
|
|
91
|
+
announce: async () => true,
|
|
92
|
+
access: async () => true,
|
|
93
|
+
}
|
|
91
94
|
|
|
92
95
|
/** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
|
|
93
96
|
/** @hidden */
|
|
@@ -100,21 +103,38 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
100
103
|
DocumentId,
|
|
101
104
|
(payload: DocHandleEncodedChangePayload<any>) => void
|
|
102
105
|
> = {}
|
|
106
|
+
#idFactory: ((initialHeads: Heads) => Uint8Array) | null
|
|
103
107
|
|
|
104
108
|
constructor({
|
|
105
109
|
storage,
|
|
106
110
|
network = [],
|
|
107
111
|
peerId = randomPeerId(),
|
|
108
112
|
sharePolicy,
|
|
113
|
+
shareConfig,
|
|
109
114
|
isEphemeral = storage === undefined,
|
|
110
115
|
enableRemoteHeadsGossiping = false,
|
|
111
116
|
denylist = [],
|
|
112
117
|
saveDebounceRate = 100,
|
|
118
|
+
idFactory,
|
|
113
119
|
}: RepoConfig = {}) {
|
|
114
120
|
super()
|
|
115
121
|
this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping
|
|
116
122
|
this.#log = debug(`automerge-repo:repo`)
|
|
117
|
-
|
|
123
|
+
|
|
124
|
+
this.#idFactory = idFactory || null
|
|
125
|
+
// Handle legacy sharePolicy
|
|
126
|
+
if (sharePolicy != null && shareConfig != null) {
|
|
127
|
+
throw new Error("cannot provide both sharePolicy and shareConfig at once")
|
|
128
|
+
}
|
|
129
|
+
if (sharePolicy) {
|
|
130
|
+
this.#shareConfig = {
|
|
131
|
+
announce: sharePolicy,
|
|
132
|
+
access: async () => true,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (shareConfig) {
|
|
136
|
+
this.#shareConfig = shareConfig
|
|
137
|
+
}
|
|
118
138
|
|
|
119
139
|
this.on("delete-document", ({ documentId }) => {
|
|
120
140
|
this.synchronizer.removeDocument(documentId)
|
|
@@ -200,7 +220,8 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
200
220
|
this.peerMetadataByPeerId[peerId] = { ...peerMetadata }
|
|
201
221
|
}
|
|
202
222
|
|
|
203
|
-
this
|
|
223
|
+
this.#shareConfig
|
|
224
|
+
.announce(peerId)
|
|
204
225
|
.then(shouldShare => {
|
|
205
226
|
if (shouldShare && this.#remoteHeadsGossipingEnabled) {
|
|
206
227
|
this.#remoteHeadsSubscriptions.addGenerousPeer(peerId)
|
|
@@ -396,6 +417,30 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
396
417
|
return this.synchronizer.peers
|
|
397
418
|
}
|
|
398
419
|
|
|
420
|
+
get peerId(): PeerId {
|
|
421
|
+
return this.networkSubsystem.peerId
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** @hidden */
|
|
425
|
+
get sharePolicy(): SharePolicy {
|
|
426
|
+
return this.#shareConfig.announce
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** @hidden */
|
|
430
|
+
set sharePolicy(policy: SharePolicy) {
|
|
431
|
+
this.#shareConfig.announce = policy
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** @hidden */
|
|
435
|
+
get shareConfig(): ShareConfig {
|
|
436
|
+
return this.#shareConfig
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** @hidden */
|
|
440
|
+
set shareConfig(config: ShareConfig) {
|
|
441
|
+
this.#shareConfig = config
|
|
442
|
+
}
|
|
443
|
+
|
|
399
444
|
getStorageIdOfPeer(peerId: PeerId): StorageId | undefined {
|
|
400
445
|
return this.peerMetadataByPeerId[peerId]?.storageId
|
|
401
446
|
}
|
|
@@ -406,8 +451,19 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
406
451
|
* system. we emit a `document` event to advertise interest in the document.
|
|
407
452
|
*/
|
|
408
453
|
create<T>(initialValue?: T): DocHandle<T> {
|
|
454
|
+
let initialDoc: Automerge.Doc<T>
|
|
455
|
+
if (initialValue) {
|
|
456
|
+
initialDoc = Automerge.from(initialValue)
|
|
457
|
+
} else {
|
|
458
|
+
initialDoc = Automerge.emptyChange(Automerge.init())
|
|
459
|
+
}
|
|
460
|
+
|
|
409
461
|
// Generate a new UUID and store it in the buffer
|
|
410
|
-
|
|
462
|
+
let { documentId } = parseAutomergeUrl(generateAutomergeUrl())
|
|
463
|
+
if (this.#idFactory) {
|
|
464
|
+
const rawDocId = this.#idFactory(Automerge.getHeads(initialDoc))
|
|
465
|
+
documentId = binaryToDocumentId(rawDocId as BinaryDocumentId)
|
|
466
|
+
}
|
|
411
467
|
const handle = this.#getHandle<T>({
|
|
412
468
|
documentId,
|
|
413
469
|
}) as DocHandle<T>
|
|
@@ -415,13 +471,7 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
415
471
|
this.#registerHandleWithSubsystems(handle)
|
|
416
472
|
|
|
417
473
|
handle.update(() => {
|
|
418
|
-
|
|
419
|
-
if (initialValue) {
|
|
420
|
-
nextDoc = Automerge.from(initialValue)
|
|
421
|
-
} else {
|
|
422
|
-
nextDoc = Automerge.emptyChange(Automerge.init())
|
|
423
|
-
}
|
|
424
|
-
return nextDoc
|
|
474
|
+
return initialDoc
|
|
425
475
|
})
|
|
426
476
|
|
|
427
477
|
handle.doneLoading()
|
|
@@ -900,6 +950,17 @@ export interface RepoConfig {
|
|
|
900
950
|
*/
|
|
901
951
|
sharePolicy?: SharePolicy
|
|
902
952
|
|
|
953
|
+
/**
|
|
954
|
+
* Whether to share documents with other peers. By default we announce new
|
|
955
|
+
* documents to everyone and allow everyone access to documents, see the
|
|
956
|
+
* documentation for {@link ShareConfig} to override this
|
|
957
|
+
*
|
|
958
|
+
* Note that this is currently an experimental API and will very likely change
|
|
959
|
+
* without a major release.
|
|
960
|
+
* @experimental
|
|
961
|
+
*/
|
|
962
|
+
shareConfig?: ShareConfig
|
|
963
|
+
|
|
903
964
|
/**
|
|
904
965
|
* Whether to enable the experimental remote heads gossiping feature
|
|
905
966
|
*/
|
|
@@ -916,6 +977,13 @@ export interface RepoConfig {
|
|
|
916
977
|
* The debounce rate in milliseconds for saving documents. Defaults to 100ms.
|
|
917
978
|
*/
|
|
918
979
|
saveDebounceRate?: number
|
|
980
|
+
|
|
981
|
+
// This is hidden for now because it's an experimental API, mostly here in order
|
|
982
|
+
// for keyhive to be able to control the ID generation
|
|
983
|
+
/**
|
|
984
|
+
* @hidden
|
|
985
|
+
*/
|
|
986
|
+
idFactory?: (initialHeads: Heads) => Uint8Array
|
|
919
987
|
}
|
|
920
988
|
|
|
921
989
|
/** A function that determines whether we should share a document with a peer
|
|
@@ -931,6 +999,27 @@ export type SharePolicy = (
|
|
|
931
999
|
documentId?: DocumentId
|
|
932
1000
|
) => Promise<boolean>
|
|
933
1001
|
|
|
1002
|
+
/**
|
|
1003
|
+
* A type which determines whether we should share a document with a peer
|
|
1004
|
+
* */
|
|
1005
|
+
export type ShareConfig = {
|
|
1006
|
+
/**
|
|
1007
|
+
* Whether we should actively announce a document to a peer
|
|
1008
|
+
|
|
1009
|
+
* @remarks
|
|
1010
|
+
* This functions is called after checking the `access` policy to determine
|
|
1011
|
+
* whether we should announce a document to a connected peer. For example, a
|
|
1012
|
+
* tab connected to a sync server might want to announce every document to the
|
|
1013
|
+
* sync server, but the sync server would not want to announce every document
|
|
1014
|
+
* to every connected peer
|
|
1015
|
+
*/
|
|
1016
|
+
announce: SharePolicy
|
|
1017
|
+
/**
|
|
1018
|
+
* Whether a peer should have access to the document
|
|
1019
|
+
*/
|
|
1020
|
+
access: (peer: PeerId, doc: DocumentId) => Promise<boolean>
|
|
1021
|
+
}
|
|
1022
|
+
|
|
934
1023
|
// events & payloads
|
|
935
1024
|
export interface RepoEvents {
|
|
936
1025
|
/** A new document was created or discovered */
|
|
@@ -4,6 +4,7 @@ import { Message, NetworkAdapter, PeerId } from "../../src/index.js"
|
|
|
4
4
|
export class DummyNetworkAdapter extends NetworkAdapter {
|
|
5
5
|
#sendMessage?: SendMessageFn
|
|
6
6
|
|
|
7
|
+
#connected = false
|
|
7
8
|
#ready = false
|
|
8
9
|
#readyResolver?: () => void
|
|
9
10
|
#readyPromise: Promise<void> = new Promise<void>(resolve => {
|
|
@@ -39,20 +40,29 @@ export class DummyNetworkAdapter extends NetworkAdapter {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
connect(peerId: PeerId) {
|
|
43
|
+
this.#connected = true
|
|
42
44
|
this.peerId = peerId
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
disconnect() {
|
|
47
|
+
disconnect() {
|
|
48
|
+
this.#connected = false
|
|
49
|
+
}
|
|
46
50
|
|
|
47
51
|
peerCandidate(peerId: PeerId) {
|
|
48
52
|
this.emit("peer-candidate", { peerId, peerMetadata: {} })
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
override send(message: Message) {
|
|
56
|
+
if (!this.#connected) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
52
59
|
this.#sendMessage?.(message)
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
receive(message: Message) {
|
|
63
|
+
if (!this.#connected) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
56
66
|
this.emit("message", message)
|
|
57
67
|
}
|
|
58
68
|
|
|
@@ -71,7 +71,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
71
71
|
const peers = Array.from(this.#peers)
|
|
72
72
|
const generousPeers: PeerId[] = []
|
|
73
73
|
for (const peerId of peers) {
|
|
74
|
-
const okToShare = await this
|
|
74
|
+
const okToShare = await this.#shouldShare(peerId, documentId)
|
|
75
75
|
if (okToShare) generousPeers.push(peerId)
|
|
76
76
|
}
|
|
77
77
|
return generousPeers
|
|
@@ -108,6 +108,20 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
108
108
|
return
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const hasAccess = await this.repo.shareConfig.access(
|
|
112
|
+
message.senderId,
|
|
113
|
+
documentId
|
|
114
|
+
)
|
|
115
|
+
if (!hasAccess) {
|
|
116
|
+
log("access denied")
|
|
117
|
+
this.emit("message", {
|
|
118
|
+
type: "doc-unavailable",
|
|
119
|
+
documentId,
|
|
120
|
+
targetId: message.senderId,
|
|
121
|
+
})
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
111
125
|
this.#docSetUp[documentId] = true
|
|
112
126
|
|
|
113
127
|
const handle = await this.repo.find(documentId, {
|
|
@@ -160,7 +174,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
160
174
|
this.#peers.add(peerId)
|
|
161
175
|
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
162
176
|
const { documentId } = docSynchronizer
|
|
163
|
-
void this
|
|
177
|
+
void this.#shouldShare(peerId, documentId).then(okToShare => {
|
|
164
178
|
if (okToShare) void docSynchronizer.beginSync([peerId])
|
|
165
179
|
})
|
|
166
180
|
}
|
|
@@ -195,4 +209,12 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
195
209
|
)
|
|
196
210
|
)
|
|
197
211
|
}
|
|
212
|
+
|
|
213
|
+
async #shouldShare(peerId: PeerId, documentId: DocumentId): Promise<boolean> {
|
|
214
|
+
const [announce, access] = await Promise.all([
|
|
215
|
+
this.repo.shareConfig.announce(peerId, documentId),
|
|
216
|
+
this.repo.shareConfig.access(peerId, documentId),
|
|
217
|
+
])
|
|
218
|
+
return announce && access
|
|
219
|
+
}
|
|
198
220
|
}
|
|
@@ -2,6 +2,7 @@ import assert from "assert"
|
|
|
2
2
|
import bs58check from "bs58check"
|
|
3
3
|
import { describe, it } from "vitest"
|
|
4
4
|
import {
|
|
5
|
+
binaryToDocumentId,
|
|
5
6
|
generateAutomergeUrl,
|
|
6
7
|
getHeadsFromUrl,
|
|
7
8
|
isValidAutomergeUrl,
|
|
@@ -14,6 +15,7 @@ import type {
|
|
|
14
15
|
BinaryDocumentId,
|
|
15
16
|
DocumentId,
|
|
16
17
|
} from "../src/types.js"
|
|
18
|
+
import { interpretAsDocumentId } from "../src/AutomergeUrl.js"
|
|
17
19
|
|
|
18
20
|
const goodUrl = "automerge:4NMNnkMhL8jXrdJ9jamS58PAVdXu" as AutomergeUrl
|
|
19
21
|
const badChecksumUrl = "automerge:badbadbad" as AutomergeUrl
|
|
@@ -94,15 +96,18 @@ describe("AutomergeUrl", () => {
|
|
|
94
96
|
assert(isValidAutomergeUrl(url) === false)
|
|
95
97
|
})
|
|
96
98
|
|
|
97
|
-
it("should return false for a documentId that is not a valid UUID ", () => {
|
|
98
|
-
const url = stringifyAutomergeUrl({ documentId: badUuidDocumentId })
|
|
99
|
-
assert(isValidAutomergeUrl(url) === false)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
99
|
it("should return false for a documentId that is just some random type", () => {
|
|
103
100
|
assert(isValidAutomergeUrl({ foo: "bar" } as unknown) === false)
|
|
104
101
|
})
|
|
105
102
|
})
|
|
103
|
+
|
|
104
|
+
it("should allow arbitrary uint8array ids", () => {
|
|
105
|
+
const raw = new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0)))
|
|
106
|
+
const documentId = binaryToDocumentId(raw as BinaryDocumentId)
|
|
107
|
+
const url = stringifyAutomergeUrl({ documentId })
|
|
108
|
+
const interpreted = interpretAsDocumentId(url)
|
|
109
|
+
assert.deepStrictEqual(interpreted, documentId)
|
|
110
|
+
})
|
|
106
111
|
})
|
|
107
112
|
|
|
108
113
|
describe("AutomergeUrl with heads", () => {
|
package/test/Repo.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { next as A } from "@automerge/automerge"
|
|
1
|
+
import { next as A, Heads } from "@automerge/automerge"
|
|
2
2
|
import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/src/index.js"
|
|
3
3
|
import assert from "assert"
|
|
4
4
|
import * as Uuid from "uuid"
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
generateAutomergeUrl,
|
|
12
12
|
stringifyAutomergeUrl,
|
|
13
13
|
} from "../src/AutomergeUrl.js"
|
|
14
|
-
import { Repo } from "../src/Repo.js"
|
|
14
|
+
import { FindProgressWithMethods, Repo, ShareConfig } from "../src/Repo.js"
|
|
15
15
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
16
16
|
import { pause } from "../src/helpers/pause.js"
|
|
17
17
|
import {
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { getRandomItem } from "./helpers/getRandomItem.js"
|
|
34
34
|
import { TestDoc } from "./types.js"
|
|
35
35
|
import { StorageId, StorageKey } from "../src/storage/types.js"
|
|
36
|
+
import { FindProgress } from "../src/FindProgress.js"
|
|
36
37
|
|
|
37
38
|
describe("Repo", () => {
|
|
38
39
|
describe("constructor", () => {
|
|
@@ -1709,6 +1710,174 @@ describe("Repo", () => {
|
|
|
1709
1710
|
assert.deepEqual(openDocs, 0)
|
|
1710
1711
|
})
|
|
1711
1712
|
})
|
|
1713
|
+
|
|
1714
|
+
describe("the sharePolicy", () => {
|
|
1715
|
+
async function connect(left: Repo, right: Repo) {
|
|
1716
|
+
const [leftToRight, rightToLeft] =
|
|
1717
|
+
DummyNetworkAdapter.createConnectedPair({ latency: 0 })
|
|
1718
|
+
left.networkSubsystem.addNetworkAdapter(leftToRight)
|
|
1719
|
+
right.networkSubsystem.addNetworkAdapter(rightToLeft)
|
|
1720
|
+
leftToRight.peerCandidate(right.peerId)
|
|
1721
|
+
rightToLeft.peerCandidate(left.peerId)
|
|
1722
|
+
await Promise.all([
|
|
1723
|
+
left.networkSubsystem.whenReady(),
|
|
1724
|
+
right.networkSubsystem.whenReady(),
|
|
1725
|
+
])
|
|
1726
|
+
await pause(10)
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
async function withTimeout<T>(
|
|
1730
|
+
promise: Promise<T>,
|
|
1731
|
+
timeout: number
|
|
1732
|
+
): Promise<T | undefined> {
|
|
1733
|
+
const timeoutPromise = new Promise<T | undefined>(resolve => {
|
|
1734
|
+
setTimeout(() => resolve(undefined), timeout)
|
|
1735
|
+
})
|
|
1736
|
+
return Promise.race([promise, timeoutPromise])
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
async function awaitState(
|
|
1740
|
+
progress: FindProgress<unknown> | FindProgressWithMethods<unknown>,
|
|
1741
|
+
state: string
|
|
1742
|
+
): Promise<void> {
|
|
1743
|
+
if (progress.state == state) {
|
|
1744
|
+
return
|
|
1745
|
+
}
|
|
1746
|
+
if (!("subscribe" in progress)) {
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`expected progress in state ${state} but was in final state ${progress.state}`
|
|
1749
|
+
)
|
|
1750
|
+
}
|
|
1751
|
+
await new Promise(resolve => {
|
|
1752
|
+
const unsubscribe = progress.subscribe(progress => {
|
|
1753
|
+
if (progress.state === state) {
|
|
1754
|
+
unsubscribe()
|
|
1755
|
+
resolve(null)
|
|
1756
|
+
}
|
|
1757
|
+
})
|
|
1758
|
+
})
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// The parts of `RepoConfig` which are either the old sharePolicy API or the new shareConfig API
|
|
1762
|
+
type EitherConfig = { sharePolicy?: SharePolicy; shareConfig?: ShareConfig }
|
|
1763
|
+
|
|
1764
|
+
/// Create two connected peers with the given share configurations
|
|
1765
|
+
async function twoPeers({
|
|
1766
|
+
alice: aliceConfig,
|
|
1767
|
+
bob: bobConfig,
|
|
1768
|
+
}: {
|
|
1769
|
+
alice: EitherConfig
|
|
1770
|
+
bob: EitherConfig
|
|
1771
|
+
}): Promise<{ alice: Repo; bob: Repo }> {
|
|
1772
|
+
const alice = new Repo({
|
|
1773
|
+
peerId: "alice" as PeerId,
|
|
1774
|
+
...aliceConfig,
|
|
1775
|
+
})
|
|
1776
|
+
const bob = new Repo({
|
|
1777
|
+
peerId: "bob" as PeerId,
|
|
1778
|
+
...bobConfig,
|
|
1779
|
+
})
|
|
1780
|
+
await connect(alice, bob)
|
|
1781
|
+
return { alice, bob }
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
describe("the legacy API", () => {
|
|
1785
|
+
it("should announce documents to peers for whom the sharePolicy returns true", async () => {
|
|
1786
|
+
const { alice, bob } = await twoPeers({
|
|
1787
|
+
alice: { sharePolicy: async () => true },
|
|
1788
|
+
bob: { sharePolicy: async () => true },
|
|
1789
|
+
})
|
|
1790
|
+
const handle = alice.create({ foo: "bar" })
|
|
1791
|
+
|
|
1792
|
+
// Wait for the announcement to be synced
|
|
1793
|
+
await pause(100)
|
|
1794
|
+
|
|
1795
|
+
// Disconnect and stop alice
|
|
1796
|
+
await alice.shutdown()
|
|
1797
|
+
|
|
1798
|
+
// Bob should have the handle already because it was announced to him
|
|
1799
|
+
const bobHandle = await bob.find(handle.url)
|
|
1800
|
+
})
|
|
1801
|
+
|
|
1802
|
+
it("should not annouce documents to peers for whom the sharePolicy returns false", async () => {
|
|
1803
|
+
const { alice, bob } = await twoPeers({
|
|
1804
|
+
alice: { sharePolicy: async () => false },
|
|
1805
|
+
bob: { sharePolicy: async () => true },
|
|
1806
|
+
})
|
|
1807
|
+
const handle = alice.create({ foo: "bar" })
|
|
1808
|
+
|
|
1809
|
+
// Disconnect and stop alice
|
|
1810
|
+
await alice.shutdown()
|
|
1811
|
+
|
|
1812
|
+
// Bob should have the handle already because it was announced to him
|
|
1813
|
+
const bobHandle = await withTimeout(bob.find(handle.url), 100)
|
|
1814
|
+
assert.equal(bobHandle, null)
|
|
1815
|
+
})
|
|
1816
|
+
|
|
1817
|
+
it("should respond to direct requests for document where the sharePolicy returns false", async () => {
|
|
1818
|
+
const { alice, bob } = await twoPeers({
|
|
1819
|
+
alice: { sharePolicy: async () => false },
|
|
1820
|
+
bob: { sharePolicy: async () => true },
|
|
1821
|
+
})
|
|
1822
|
+
await connect(alice, bob)
|
|
1823
|
+
|
|
1824
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
1825
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
1826
|
+
})
|
|
1827
|
+
})
|
|
1828
|
+
|
|
1829
|
+
it("should respond to direct requests for document where the announce policy returns false but the access policy returns true", async () => {
|
|
1830
|
+
const { alice, bob } = await twoPeers({
|
|
1831
|
+
alice: {
|
|
1832
|
+
shareConfig: {
|
|
1833
|
+
announce: async () => false,
|
|
1834
|
+
access: async () => true,
|
|
1835
|
+
},
|
|
1836
|
+
},
|
|
1837
|
+
bob: { sharePolicy: async () => true },
|
|
1838
|
+
})
|
|
1839
|
+
|
|
1840
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
1841
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
1842
|
+
})
|
|
1843
|
+
|
|
1844
|
+
it("should not respond to direct requests for a document where the access policy returns false and the announce policy return trrrue", async () => {
|
|
1845
|
+
const { alice, bob } = await twoPeers({
|
|
1846
|
+
alice: {
|
|
1847
|
+
shareConfig: {
|
|
1848
|
+
announce: async () => true,
|
|
1849
|
+
access: async () => false,
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
bob: { sharePolicy: async () => true },
|
|
1853
|
+
})
|
|
1854
|
+
await connect(alice, bob)
|
|
1855
|
+
|
|
1856
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
1857
|
+
withTimeout(
|
|
1858
|
+
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
1859
|
+
500
|
|
1860
|
+
)
|
|
1861
|
+
})
|
|
1862
|
+
|
|
1863
|
+
it("should not respond to direct requests for a document where the access policy and the announce policy return false", async () => {
|
|
1864
|
+
const { alice, bob } = await twoPeers({
|
|
1865
|
+
alice: {
|
|
1866
|
+
shareConfig: {
|
|
1867
|
+
announce: async () => false,
|
|
1868
|
+
access: async () => false,
|
|
1869
|
+
},
|
|
1870
|
+
},
|
|
1871
|
+
bob: { sharePolicy: async () => false },
|
|
1872
|
+
})
|
|
1873
|
+
|
|
1874
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
1875
|
+
withTimeout(
|
|
1876
|
+
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
1877
|
+
500
|
|
1878
|
+
)
|
|
1879
|
+
})
|
|
1880
|
+
})
|
|
1712
1881
|
})
|
|
1713
1882
|
|
|
1714
1883
|
describe("Repo heads-in-URLs functionality", () => {
|
|
@@ -1858,6 +2027,50 @@ describe("Repo.find() abort behavior", () => {
|
|
|
1858
2027
|
controller.abort()
|
|
1859
2028
|
expect(handle.url).toBe(url)
|
|
1860
2029
|
})
|
|
2030
|
+
|
|
2031
|
+
describe("creating a document with a custom ID factory", () => {
|
|
2032
|
+
it("creates a document with the custom ID", async () => {
|
|
2033
|
+
const id = new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0)))
|
|
2034
|
+
const repo = new Repo({
|
|
2035
|
+
idFactory: () => id,
|
|
2036
|
+
})
|
|
2037
|
+
const handle = repo.create()
|
|
2038
|
+
expect(handle.documentId).toBe("9HUp4wuzRMx9MRvN4x")
|
|
2039
|
+
})
|
|
2040
|
+
|
|
2041
|
+
it("passes the heads of the document to the callback", async () => {
|
|
2042
|
+
const id = new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0)))
|
|
2043
|
+
let calledHeads: Heads | null = null
|
|
2044
|
+
const repo = new Repo({
|
|
2045
|
+
idFactory: (heads: Heads) => {
|
|
2046
|
+
calledHeads = heads
|
|
2047
|
+
return id
|
|
2048
|
+
},
|
|
2049
|
+
})
|
|
2050
|
+
const handle = repo.create()
|
|
2051
|
+
const actualHeads = A.getHeads(handle.doc())
|
|
2052
|
+
assert.deepStrictEqual(actualHeads, calledHeads)
|
|
2053
|
+
})
|
|
2054
|
+
|
|
2055
|
+
it("allows syncing documents with a custom ID", async () => {
|
|
2056
|
+
const [aliceToBob, bobToAlice] = DummyNetworkAdapter.createConnectedPair()
|
|
2057
|
+
const alice = new Repo({
|
|
2058
|
+
peerId: "alice" as PeerId,
|
|
2059
|
+
idFactory: () =>
|
|
2060
|
+
new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0))),
|
|
2061
|
+
network: [aliceToBob],
|
|
2062
|
+
})
|
|
2063
|
+
const bob = new Repo({ peerId: "bob" as PeerId, network: [bobToAlice] })
|
|
2064
|
+
aliceToBob.peerCandidate("bob" as PeerId)
|
|
2065
|
+
bobToAlice.peerCandidate("alice" as PeerId)
|
|
2066
|
+
|
|
2067
|
+
await pause(50)
|
|
2068
|
+
|
|
2069
|
+
const handle = alice.create({ foo: "bar" })
|
|
2070
|
+
const bobHandle = await bob.find(handle.url)
|
|
2071
|
+
assert.deepStrictEqual(bobHandle.doc(), { foo: "bar" })
|
|
2072
|
+
})
|
|
2073
|
+
})
|
|
1861
2074
|
})
|
|
1862
2075
|
|
|
1863
2076
|
const warn = console.warn
|