@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.
@@ -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,UASvD,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"}
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"}
@@ -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
- if (binaryDocumentID === undefined)
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;
@@ -1 +1 @@
1
- {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
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
- /** By default, we share generously with all peers. */
38
- /** @hidden */
39
- sharePolicy = async () => true;
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
- constructor({ storage, network = [], peerId = randomPeerId(), sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, denylist = [], saveDebounceRate = 100, } = {}) {
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.sharePolicy = sharePolicy ?? this.sharePolicy;
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.sharePolicy(peerId)
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
- const { documentId } = parseAutomergeUrl(generateAutomergeUrl());
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
- let nextDoc;
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;;IASrD,OAAO;IAIP,SAAS;IAYT,UAAU;gBAIE,IAAI,GAAE,OAA8B;IAQhD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,UAAU;IAEV,aAAa,CAAC,MAAM,EAAE,MAAM;IAInB,IAAI,CAAC,OAAO,EAAE,OAAO;IAI9B,OAAO,CAAC,OAAO,EAAE,OAAO;IAIxB,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"}
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;IAyCxC;;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;CASF"}
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.repo.sharePolicy(peerId, documentId);
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.repo.sharePolicy(peerId, documentId).then(okToShare => {
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.1",
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": "b4b18a23f7a2806fd8b259520b5444bea0ed8153"
62
+ "gitHead": "a2d02ab1494cd2d47825844316fff9b3c43951c1"
63
63
  }
@@ -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
- if (binaryDocumentID === undefined) return false // invalid base58check encoding
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
- /** By default, we share generously with all peers. */
89
- /** @hidden */
90
- sharePolicy: SharePolicy = async () => true
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
- this.sharePolicy = sharePolicy ?? this.sharePolicy
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.sharePolicy(peerId)
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
- const { documentId } = parseAutomergeUrl(generateAutomergeUrl())
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
- let nextDoc: Automerge.Doc<T>
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.repo.sharePolicy(peerId, documentId)
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.repo.sharePolicy(peerId, documentId).then(okToShare => {
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