@automerge/automerge-repo 2.5.0 → 2.5.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/Repo.d.ts +1 -0
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +3 -0
- package/dist/synchronizer/CollectionSynchronizer.d.ts +8 -0
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +48 -1
- package/dist/synchronizer/DocSynchronizer.js +1 -1
- package/package.json +2 -2
- package/src/Repo.ts +4 -0
- package/src/synchronizer/CollectionSynchronizer.ts +61 -1
- package/src/synchronizer/DocSynchronizer.ts +1 -1
- package/test/Repo.test.ts +47 -168
- package/test/SharePolicy.test.ts +244 -0
- package/test/helpers/awaitState.ts +24 -0
- package/test/helpers/connectRepos.ts +18 -0
- package/test/helpers/pause.ts +3 -0
- package/test/helpers/twoPeers.ts +30 -0
- package/test/helpers/withTimeout.ts +9 -0
package/dist/Repo.d.ts
CHANGED
package/dist/Repo.d.ts.map
CHANGED
|
@@ -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
|
@@ -29,6 +29,14 @@ export declare class CollectionSynchronizer extends Synchronizer {
|
|
|
29
29
|
removePeer(peerId: PeerId): void;
|
|
30
30
|
/** Returns a list of all connected peer ids */
|
|
31
31
|
get peers(): PeerId[];
|
|
32
|
+
/**
|
|
33
|
+
* Re-evaluates share policy for a document and updates sync accordingly
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* This is called when the share policy for a document has changed. It re-evaluates
|
|
37
|
+
* which peers should have access and starts/stops synchronization as needed.
|
|
38
|
+
*/
|
|
39
|
+
reevaluateDocumentShare(): Promise<void>;
|
|
32
40
|
metrics(): {
|
|
33
41
|
[key: string]: {
|
|
34
42
|
peers: PeerId[];
|
|
@@ -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;;
|
|
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;;IAc1C,OAAO,CAAC,IAAI;IAVxB,kDAAkD;IAClD,cAAc;IACd,gBAAgB,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAK;gBAQtC,IAAI,EAAE,IAAI,EAAE,QAAQ,GAAE,YAAY,EAAO;IAwD7D;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAiExC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC;IAYtC,sDAAsD;IACtD,cAAc,CAAC,UAAU,EAAE,UAAU;IAUrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IAYzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAED;;;;;;OAMG;IACG,uBAAuB;IAoC7B,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;CAmBF"}
|
|
@@ -14,6 +14,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
14
14
|
/** Used to determine if the document is know to the Collection and a synchronizer exists or is being set up */
|
|
15
15
|
#docSetUp = {};
|
|
16
16
|
#denylist;
|
|
17
|
+
#hasRequested = new Map();
|
|
17
18
|
constructor(repo, denylist = []) {
|
|
18
19
|
super();
|
|
19
20
|
this.repo = repo;
|
|
@@ -83,6 +84,15 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
83
84
|
});
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
87
|
+
// Record the request so that even if access is denied now, we know that the
|
|
88
|
+
// peer requested the document so that if the share policy changes we know
|
|
89
|
+
// to begin syncing with this peer
|
|
90
|
+
if (message.type === "request") {
|
|
91
|
+
if (!this.#hasRequested.has(documentId)) {
|
|
92
|
+
this.#hasRequested.set(documentId, new Set());
|
|
93
|
+
}
|
|
94
|
+
this.#hasRequested.get(documentId)?.add(message.senderId);
|
|
95
|
+
}
|
|
86
96
|
const hasAccess = await this.repo.shareConfig.access(message.senderId, documentId);
|
|
87
97
|
if (!hasAccess) {
|
|
88
98
|
log("access denied");
|
|
@@ -111,6 +121,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
111
121
|
if (this.#docSetUp[handle.documentId]) {
|
|
112
122
|
return;
|
|
113
123
|
}
|
|
124
|
+
this.#docSetUp[handle.documentId] = true;
|
|
114
125
|
const docSynchronizer = this.#fetchDocSynchronizer(handle);
|
|
115
126
|
void this.#documentGenerousPeers(handle.documentId).then(peers => {
|
|
116
127
|
void docSynchronizer.beginSync(peers);
|
|
@@ -145,6 +156,9 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
145
156
|
removePeer(peerId) {
|
|
146
157
|
log(`removing peer ${peerId}`);
|
|
147
158
|
this.#peers.delete(peerId);
|
|
159
|
+
for (const requested of this.#hasRequested.values()) {
|
|
160
|
+
requested.delete(peerId);
|
|
161
|
+
}
|
|
148
162
|
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
149
163
|
docSynchronizer.endSync(peerId);
|
|
150
164
|
}
|
|
@@ -153,6 +167,38 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
153
167
|
get peers() {
|
|
154
168
|
return Array.from(this.#peers);
|
|
155
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Re-evaluates share policy for a document and updates sync accordingly
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* This is called when the share policy for a document has changed. It re-evaluates
|
|
175
|
+
* which peers should have access and starts/stops synchronization as needed.
|
|
176
|
+
*/
|
|
177
|
+
async reevaluateDocumentShare() {
|
|
178
|
+
const peers = Array.from(this.#peers);
|
|
179
|
+
const docPromises = [];
|
|
180
|
+
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
181
|
+
const documentId = docSynchronizer.documentId;
|
|
182
|
+
docPromises.push((async () => {
|
|
183
|
+
for (const peerId of peers) {
|
|
184
|
+
const shouldShare = await this.#shouldShare(peerId, documentId);
|
|
185
|
+
const isAlreadySyncing = docSynchronizer.hasPeer(peerId);
|
|
186
|
+
log(`reevaluateDocumentShare: ${peerId} for ${documentId}, shouldShare: ${shouldShare}, isAlreadySyncing: ${isAlreadySyncing}`);
|
|
187
|
+
if (shouldShare && !isAlreadySyncing) {
|
|
188
|
+
log(`reevaluateDocumentShare: starting sync with ${peerId} for ${documentId}`);
|
|
189
|
+
void docSynchronizer.beginSync([peerId]);
|
|
190
|
+
}
|
|
191
|
+
else if (!shouldShare && isAlreadySyncing) {
|
|
192
|
+
log(`reevaluateDocumentShare: stopping sync with ${peerId} for ${documentId}`);
|
|
193
|
+
docSynchronizer.endSync(peerId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
})().catch(e => {
|
|
197
|
+
console.log(`error reevaluating document share for ${documentId}: ${e}`);
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
await Promise.allSettled(docPromises);
|
|
201
|
+
}
|
|
156
202
|
metrics() {
|
|
157
203
|
return Object.fromEntries(Object.entries(this.docSynchronizers).map(([documentId, synchronizer]) => {
|
|
158
204
|
return [documentId, synchronizer.metrics()];
|
|
@@ -163,6 +209,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
163
209
|
this.repo.shareConfig.announce(peerId, documentId),
|
|
164
210
|
this.repo.shareConfig.access(peerId, documentId),
|
|
165
211
|
]);
|
|
166
|
-
|
|
212
|
+
const hasRequested = this.#hasRequested.get(documentId)?.has(peerId) ?? false;
|
|
213
|
+
return announce || (access && hasRequested);
|
|
167
214
|
}
|
|
168
215
|
}
|
|
@@ -130,8 +130,8 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
130
130
|
durationMillis: end - start,
|
|
131
131
|
forPeer: peerId,
|
|
132
132
|
});
|
|
133
|
+
this.#setSyncState(peerId, newSyncState);
|
|
133
134
|
if (message) {
|
|
134
|
-
this.#setSyncState(peerId, newSyncState);
|
|
135
135
|
const isNew = A.getHeads(doc).length === 0;
|
|
136
136
|
if (!this.#handle.isReady() &&
|
|
137
137
|
isNew &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.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": "66b8457234d04468e653b85d5cf58a9a3cd4c691"
|
|
63
63
|
}
|
package/src/Repo.ts
CHANGED
|
@@ -977,6 +977,10 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
977
977
|
metrics(): { documents: { [key: string]: any } } {
|
|
978
978
|
return { documents: this.synchronizer.metrics() }
|
|
979
979
|
}
|
|
980
|
+
|
|
981
|
+
shareConfigChanged() {
|
|
982
|
+
void this.synchronizer.reevaluateDocumentShare()
|
|
983
|
+
}
|
|
980
984
|
}
|
|
981
985
|
|
|
982
986
|
export interface RepoConfig {
|
|
@@ -22,6 +22,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
22
22
|
#docSetUp: Record<DocumentId, boolean> = {}
|
|
23
23
|
|
|
24
24
|
#denylist: DocumentId[]
|
|
25
|
+
#hasRequested: Map<DocumentId, Set<PeerId>> = new Map()
|
|
25
26
|
|
|
26
27
|
constructor(private repo: Repo, denylist: AutomergeUrl[] = []) {
|
|
27
28
|
super()
|
|
@@ -108,6 +109,16 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
108
109
|
return
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
// Record the request so that even if access is denied now, we know that the
|
|
113
|
+
// peer requested the document so that if the share policy changes we know
|
|
114
|
+
// to begin syncing with this peer
|
|
115
|
+
if (message.type === "request") {
|
|
116
|
+
if (!this.#hasRequested.has(documentId)) {
|
|
117
|
+
this.#hasRequested.set(documentId, new Set())
|
|
118
|
+
}
|
|
119
|
+
this.#hasRequested.get(documentId)?.add(message.senderId)
|
|
120
|
+
}
|
|
121
|
+
|
|
111
122
|
const hasAccess = await this.repo.shareConfig.access(
|
|
112
123
|
message.senderId,
|
|
113
124
|
documentId
|
|
@@ -146,6 +157,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
146
157
|
if (this.#docSetUp[handle.documentId]) {
|
|
147
158
|
return
|
|
148
159
|
}
|
|
160
|
+
this.#docSetUp[handle.documentId] = true
|
|
149
161
|
const docSynchronizer = this.#fetchDocSynchronizer(handle)
|
|
150
162
|
void this.#documentGenerousPeers(handle.documentId).then(peers => {
|
|
151
163
|
void docSynchronizer.beginSync(peers)
|
|
@@ -184,6 +196,9 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
184
196
|
removePeer(peerId: PeerId) {
|
|
185
197
|
log(`removing peer ${peerId}`)
|
|
186
198
|
this.#peers.delete(peerId)
|
|
199
|
+
for (const requested of this.#hasRequested.values()) {
|
|
200
|
+
requested.delete(peerId)
|
|
201
|
+
}
|
|
187
202
|
|
|
188
203
|
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
189
204
|
docSynchronizer.endSync(peerId)
|
|
@@ -195,6 +210,49 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
195
210
|
return Array.from(this.#peers)
|
|
196
211
|
}
|
|
197
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Re-evaluates share policy for a document and updates sync accordingly
|
|
215
|
+
*
|
|
216
|
+
* @remarks
|
|
217
|
+
* This is called when the share policy for a document has changed. It re-evaluates
|
|
218
|
+
* which peers should have access and starts/stops synchronization as needed.
|
|
219
|
+
*/
|
|
220
|
+
async reevaluateDocumentShare() {
|
|
221
|
+
const peers = Array.from(this.#peers)
|
|
222
|
+
const docPromises = []
|
|
223
|
+
for (const docSynchronizer of Object.values(this.docSynchronizers)) {
|
|
224
|
+
const documentId = docSynchronizer.documentId
|
|
225
|
+
docPromises.push(
|
|
226
|
+
(async () => {
|
|
227
|
+
for (const peerId of peers) {
|
|
228
|
+
const shouldShare = await this.#shouldShare(peerId, documentId)
|
|
229
|
+
const isAlreadySyncing = docSynchronizer.hasPeer(peerId)
|
|
230
|
+
|
|
231
|
+
log(
|
|
232
|
+
`reevaluateDocumentShare: ${peerId} for ${documentId}, shouldShare: ${shouldShare}, isAlreadySyncing: ${isAlreadySyncing}`
|
|
233
|
+
)
|
|
234
|
+
if (shouldShare && !isAlreadySyncing) {
|
|
235
|
+
log(
|
|
236
|
+
`reevaluateDocumentShare: starting sync with ${peerId} for ${documentId}`
|
|
237
|
+
)
|
|
238
|
+
void docSynchronizer.beginSync([peerId])
|
|
239
|
+
} else if (!shouldShare && isAlreadySyncing) {
|
|
240
|
+
log(
|
|
241
|
+
`reevaluateDocumentShare: stopping sync with ${peerId} for ${documentId}`
|
|
242
|
+
)
|
|
243
|
+
docSynchronizer.endSync(peerId)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
})().catch(e => {
|
|
247
|
+
console.log(
|
|
248
|
+
`error reevaluating document share for ${documentId}: ${e}`
|
|
249
|
+
)
|
|
250
|
+
})
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
await Promise.allSettled(docPromises)
|
|
254
|
+
}
|
|
255
|
+
|
|
198
256
|
metrics(): {
|
|
199
257
|
[key: string]: {
|
|
200
258
|
peers: PeerId[]
|
|
@@ -215,6 +273,8 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
215
273
|
this.repo.shareConfig.announce(peerId, documentId),
|
|
216
274
|
this.repo.shareConfig.access(peerId, documentId),
|
|
217
275
|
])
|
|
218
|
-
|
|
276
|
+
const hasRequested =
|
|
277
|
+
this.#hasRequested.get(documentId)?.has(peerId) ?? false
|
|
278
|
+
return announce || (access && hasRequested)
|
|
219
279
|
}
|
|
220
280
|
}
|
|
@@ -201,8 +201,8 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
201
201
|
forPeer: peerId,
|
|
202
202
|
})
|
|
203
203
|
|
|
204
|
+
this.#setSyncState(peerId, newSyncState)
|
|
204
205
|
if (message) {
|
|
205
|
-
this.#setSyncState(peerId, newSyncState)
|
|
206
206
|
const isNew = A.getHeads(doc).length === 0
|
|
207
207
|
|
|
208
208
|
if (
|
package/test/Repo.test.ts
CHANGED
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
LargeObject,
|
|
36
36
|
generateLargeObject,
|
|
37
37
|
} from "./helpers/generate-large-object.js"
|
|
38
|
+
import twoPeers from "./helpers/twoPeers.js"
|
|
39
|
+
import connectRepos from "./helpers/connectRepos.js"
|
|
40
|
+
import awaitState from "./helpers/awaitState.js"
|
|
41
|
+
import withTimeout from "./helpers/withTimeout.js"
|
|
38
42
|
import { getRandomItem } from "./helpers/getRandomItem.js"
|
|
39
43
|
import { TestDoc } from "./types.js"
|
|
40
44
|
import { StorageId, StorageKey } from "../src/storage/types.js"
|
|
@@ -1182,6 +1186,49 @@ describe("Repo", () => {
|
|
|
1182
1186
|
teardown()
|
|
1183
1187
|
})
|
|
1184
1188
|
|
|
1189
|
+
it("a previously unavailable document syncs if a connected peer obtains it (but doesn't announce it)", async () => {
|
|
1190
|
+
const alice = new Repo({
|
|
1191
|
+
peerId: "alice" as PeerId,
|
|
1192
|
+
shareConfig: {
|
|
1193
|
+
announce: async peerId => peerId === "charlie",
|
|
1194
|
+
access: async () => true,
|
|
1195
|
+
},
|
|
1196
|
+
})
|
|
1197
|
+
const bob = new Repo({
|
|
1198
|
+
peerId: "bob" as PeerId,
|
|
1199
|
+
shareConfig: {
|
|
1200
|
+
announce: async () => true,
|
|
1201
|
+
access: async () => true,
|
|
1202
|
+
},
|
|
1203
|
+
})
|
|
1204
|
+
const charlie = new Repo({
|
|
1205
|
+
peerId: "charlie" as PeerId,
|
|
1206
|
+
shareConfig: {
|
|
1207
|
+
announce: async () => false,
|
|
1208
|
+
access: async () => true,
|
|
1209
|
+
},
|
|
1210
|
+
})
|
|
1211
|
+
await connectRepos(alice, bob)
|
|
1212
|
+
|
|
1213
|
+
const charlieHandle = charlie.create({ foo: "bar" })
|
|
1214
|
+
|
|
1215
|
+
// Charlie isn't connected to any peer, so we don't have the document
|
|
1216
|
+
await assert.rejects(bob.find(charlieHandle.url))
|
|
1217
|
+
|
|
1218
|
+
// Now, connect charlie to alice
|
|
1219
|
+
await connectRepos(alice, charlie)
|
|
1220
|
+
|
|
1221
|
+
// Alice should now find the document
|
|
1222
|
+
const aliceHandle = await withTimeout(alice.find(charlieHandle.url), 500)
|
|
1223
|
+
assert.deepStrictEqual(aliceHandle.doc(), { foo: "bar" })
|
|
1224
|
+
|
|
1225
|
+
await pause(150) // wait for the sync debounce rate to elapse
|
|
1226
|
+
|
|
1227
|
+
// Bob should now find the document via alice
|
|
1228
|
+
const bobHandle = await withTimeout(bob.find(charlieHandle.url), 500)
|
|
1229
|
+
assert.deepStrictEqual(bobHandle.doc(), { foo: "bar" })
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1185
1232
|
it("a previously unavailable document becomes available if the network adapter initially has no peers", async () => {
|
|
1186
1233
|
// It is possible for a network adapter to be ready without any peer
|
|
1187
1234
|
// being announced (e.g. the BroadcastChannelNetworkAdapter). In this
|
|
@@ -1716,174 +1763,6 @@ describe("Repo", () => {
|
|
|
1716
1763
|
assert.deepEqual(openDocs, 0)
|
|
1717
1764
|
})
|
|
1718
1765
|
})
|
|
1719
|
-
|
|
1720
|
-
describe("the sharePolicy", () => {
|
|
1721
|
-
async function connect(left: Repo, right: Repo) {
|
|
1722
|
-
const [leftToRight, rightToLeft] =
|
|
1723
|
-
DummyNetworkAdapter.createConnectedPair({ latency: 0 })
|
|
1724
|
-
left.networkSubsystem.addNetworkAdapter(leftToRight)
|
|
1725
|
-
right.networkSubsystem.addNetworkAdapter(rightToLeft)
|
|
1726
|
-
leftToRight.peerCandidate(right.peerId)
|
|
1727
|
-
rightToLeft.peerCandidate(left.peerId)
|
|
1728
|
-
await Promise.all([
|
|
1729
|
-
left.networkSubsystem.whenReady(),
|
|
1730
|
-
right.networkSubsystem.whenReady(),
|
|
1731
|
-
])
|
|
1732
|
-
await pause(10)
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
async function withTimeout<T>(
|
|
1736
|
-
promise: Promise<T>,
|
|
1737
|
-
timeout: number
|
|
1738
|
-
): Promise<T | undefined> {
|
|
1739
|
-
const timeoutPromise = new Promise<T | undefined>(resolve => {
|
|
1740
|
-
setTimeout(() => resolve(undefined), timeout)
|
|
1741
|
-
})
|
|
1742
|
-
return Promise.race([promise, timeoutPromise])
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
async function awaitState(
|
|
1746
|
-
progress: FindProgress<unknown> | FindProgressWithMethods<unknown>,
|
|
1747
|
-
state: string
|
|
1748
|
-
): Promise<void> {
|
|
1749
|
-
if (progress.state == state) {
|
|
1750
|
-
return
|
|
1751
|
-
}
|
|
1752
|
-
if (!("subscribe" in progress)) {
|
|
1753
|
-
throw new Error(
|
|
1754
|
-
`expected progress in state ${state} but was in final state ${progress.state}`
|
|
1755
|
-
)
|
|
1756
|
-
}
|
|
1757
|
-
await new Promise(resolve => {
|
|
1758
|
-
const unsubscribe = progress.subscribe(progress => {
|
|
1759
|
-
if (progress.state === state) {
|
|
1760
|
-
unsubscribe()
|
|
1761
|
-
resolve(null)
|
|
1762
|
-
}
|
|
1763
|
-
})
|
|
1764
|
-
})
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
// The parts of `RepoConfig` which are either the old sharePolicy API or the new shareConfig API
|
|
1768
|
-
type EitherConfig = { sharePolicy?: SharePolicy; shareConfig?: ShareConfig }
|
|
1769
|
-
|
|
1770
|
-
/// Create two connected peers with the given share configurations
|
|
1771
|
-
async function twoPeers({
|
|
1772
|
-
alice: aliceConfig,
|
|
1773
|
-
bob: bobConfig,
|
|
1774
|
-
}: {
|
|
1775
|
-
alice: EitherConfig
|
|
1776
|
-
bob: EitherConfig
|
|
1777
|
-
}): Promise<{ alice: Repo; bob: Repo }> {
|
|
1778
|
-
const alice = new Repo({
|
|
1779
|
-
peerId: "alice" as PeerId,
|
|
1780
|
-
...aliceConfig,
|
|
1781
|
-
})
|
|
1782
|
-
const bob = new Repo({
|
|
1783
|
-
peerId: "bob" as PeerId,
|
|
1784
|
-
...bobConfig,
|
|
1785
|
-
})
|
|
1786
|
-
await connect(alice, bob)
|
|
1787
|
-
return { alice, bob }
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
describe("the legacy API", () => {
|
|
1791
|
-
it("should announce documents to peers for whom the sharePolicy returns true", async () => {
|
|
1792
|
-
const { alice, bob } = await twoPeers({
|
|
1793
|
-
alice: { sharePolicy: async () => true },
|
|
1794
|
-
bob: { sharePolicy: async () => true },
|
|
1795
|
-
})
|
|
1796
|
-
const handle = alice.create({ foo: "bar" })
|
|
1797
|
-
|
|
1798
|
-
// Wait for the announcement to be synced
|
|
1799
|
-
await pause(100)
|
|
1800
|
-
|
|
1801
|
-
// Disconnect and stop alice
|
|
1802
|
-
await alice.shutdown()
|
|
1803
|
-
|
|
1804
|
-
// Bob should have the handle already because it was announced to him
|
|
1805
|
-
const bobHandle = await bob.find(handle.url)
|
|
1806
|
-
})
|
|
1807
|
-
|
|
1808
|
-
it("should not annouce documents to peers for whom the sharePolicy returns false", async () => {
|
|
1809
|
-
const { alice, bob } = await twoPeers({
|
|
1810
|
-
alice: { sharePolicy: async () => false },
|
|
1811
|
-
bob: { sharePolicy: async () => true },
|
|
1812
|
-
})
|
|
1813
|
-
const handle = alice.create({ foo: "bar" })
|
|
1814
|
-
|
|
1815
|
-
// Disconnect and stop alice
|
|
1816
|
-
await alice.shutdown()
|
|
1817
|
-
|
|
1818
|
-
// Bob should have the handle already because it was announced to him
|
|
1819
|
-
const bobHandle = await withTimeout(bob.find(handle.url), 100)
|
|
1820
|
-
assert.equal(bobHandle, null)
|
|
1821
|
-
})
|
|
1822
|
-
|
|
1823
|
-
it("should respond to direct requests for document where the sharePolicy returns false", async () => {
|
|
1824
|
-
const { alice, bob } = await twoPeers({
|
|
1825
|
-
alice: { sharePolicy: async () => false },
|
|
1826
|
-
bob: { sharePolicy: async () => true },
|
|
1827
|
-
})
|
|
1828
|
-
await connect(alice, bob)
|
|
1829
|
-
|
|
1830
|
-
const aliceHandle = alice.create({ foo: "bar" })
|
|
1831
|
-
const bobHandle = await bob.find(aliceHandle.url)
|
|
1832
|
-
})
|
|
1833
|
-
})
|
|
1834
|
-
|
|
1835
|
-
it("should respond to direct requests for document where the announce policy returns false but the access policy returns true", async () => {
|
|
1836
|
-
const { alice, bob } = await twoPeers({
|
|
1837
|
-
alice: {
|
|
1838
|
-
shareConfig: {
|
|
1839
|
-
announce: async () => false,
|
|
1840
|
-
access: async () => true,
|
|
1841
|
-
},
|
|
1842
|
-
},
|
|
1843
|
-
bob: { sharePolicy: async () => true },
|
|
1844
|
-
})
|
|
1845
|
-
|
|
1846
|
-
const aliceHandle = alice.create({ foo: "bar" })
|
|
1847
|
-
const bobHandle = await bob.find(aliceHandle.url)
|
|
1848
|
-
})
|
|
1849
|
-
|
|
1850
|
-
it("should not respond to direct requests for a document where the access policy returns false and the announce policy return trrrue", async () => {
|
|
1851
|
-
const { alice, bob } = await twoPeers({
|
|
1852
|
-
alice: {
|
|
1853
|
-
shareConfig: {
|
|
1854
|
-
announce: async () => true,
|
|
1855
|
-
access: async () => false,
|
|
1856
|
-
},
|
|
1857
|
-
},
|
|
1858
|
-
bob: { sharePolicy: async () => true },
|
|
1859
|
-
})
|
|
1860
|
-
await connect(alice, bob)
|
|
1861
|
-
|
|
1862
|
-
const aliceHandle = alice.create({ foo: "bar" })
|
|
1863
|
-
withTimeout(
|
|
1864
|
-
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
1865
|
-
500
|
|
1866
|
-
)
|
|
1867
|
-
})
|
|
1868
|
-
|
|
1869
|
-
it("should not respond to direct requests for a document where the access policy and the announce policy return false", async () => {
|
|
1870
|
-
const { alice, bob } = await twoPeers({
|
|
1871
|
-
alice: {
|
|
1872
|
-
shareConfig: {
|
|
1873
|
-
announce: async () => false,
|
|
1874
|
-
access: async () => false,
|
|
1875
|
-
},
|
|
1876
|
-
},
|
|
1877
|
-
bob: { sharePolicy: async () => false },
|
|
1878
|
-
})
|
|
1879
|
-
|
|
1880
|
-
const aliceHandle = alice.create({ foo: "bar" })
|
|
1881
|
-
withTimeout(
|
|
1882
|
-
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
1883
|
-
500
|
|
1884
|
-
)
|
|
1885
|
-
})
|
|
1886
|
-
})
|
|
1887
1766
|
})
|
|
1888
1767
|
|
|
1889
1768
|
describe("Repo heads-in-URLs functionality", () => {
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import assert from "assert"
|
|
3
|
+
import twoPeers from "./helpers/twoPeers.js"
|
|
4
|
+
import connectRepos from "./helpers/connectRepos.js"
|
|
5
|
+
import awaitState from "./helpers/awaitState.js"
|
|
6
|
+
import withTimeout from "./helpers/withTimeout.js"
|
|
7
|
+
import pause from "./helpers/pause.js"
|
|
8
|
+
import { Repo } from "../src/Repo.js"
|
|
9
|
+
import { PeerId } from "../src/types.js"
|
|
10
|
+
|
|
11
|
+
describe("the sharePolicy APIs", () => {
|
|
12
|
+
describe("the legacy API", () => {
|
|
13
|
+
it("should announce documents to peers for whom the sharePolicy returns true", async () => {
|
|
14
|
+
const { alice, bob } = await twoPeers({
|
|
15
|
+
alice: { sharePolicy: async () => true },
|
|
16
|
+
bob: { sharePolicy: async () => true },
|
|
17
|
+
})
|
|
18
|
+
const handle = alice.create({ foo: "bar" })
|
|
19
|
+
|
|
20
|
+
// Wait for the announcement to be synced
|
|
21
|
+
await pause(100)
|
|
22
|
+
|
|
23
|
+
// Disconnect and stop alice
|
|
24
|
+
await alice.shutdown()
|
|
25
|
+
|
|
26
|
+
// Bob should have the handle already because it was announced to him
|
|
27
|
+
const bobHandle = await bob.find(handle.url)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("should not annouce documents to peers for whom the sharePolicy returns false", async () => {
|
|
31
|
+
const { alice, bob } = await twoPeers({
|
|
32
|
+
alice: { sharePolicy: async () => false },
|
|
33
|
+
bob: { sharePolicy: async () => true },
|
|
34
|
+
})
|
|
35
|
+
const handle = alice.create({ foo: "bar" })
|
|
36
|
+
|
|
37
|
+
// Disconnect and stop alice
|
|
38
|
+
await alice.shutdown()
|
|
39
|
+
|
|
40
|
+
// Bob should have the handle already because it was announced to him
|
|
41
|
+
const bobHandle = await withTimeout(bob.find(handle.url), 100)
|
|
42
|
+
assert.equal(bobHandle, null)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("should respond to direct requests for document where the sharePolicy returns false", async () => {
|
|
46
|
+
const { alice, bob } = await twoPeers({
|
|
47
|
+
alice: { sharePolicy: async () => false },
|
|
48
|
+
bob: { sharePolicy: async () => true },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
52
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("should respond to direct requests for document where the announce policy returns false but the access policy returns true", async () => {
|
|
57
|
+
const { alice, bob } = await twoPeers({
|
|
58
|
+
alice: {
|
|
59
|
+
shareConfig: {
|
|
60
|
+
announce: async () => false,
|
|
61
|
+
access: async () => true,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
bob: { sharePolicy: async () => true },
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
68
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("should not respond to direct requests for a document where the access policy returns false and the announce policy return trrrue", async () => {
|
|
72
|
+
const { alice, bob } = await twoPeers({
|
|
73
|
+
alice: {
|
|
74
|
+
shareConfig: {
|
|
75
|
+
announce: async () => true,
|
|
76
|
+
access: async () => false,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
bob: { sharePolicy: async () => true },
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
83
|
+
withTimeout(
|
|
84
|
+
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
85
|
+
500
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("should not respond to direct requests for a document where the access policy and the announce policy return false", async () => {
|
|
90
|
+
const { alice, bob } = await twoPeers({
|
|
91
|
+
alice: {
|
|
92
|
+
shareConfig: {
|
|
93
|
+
announce: async () => false,
|
|
94
|
+
access: async () => false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
bob: { sharePolicy: async () => false },
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
101
|
+
withTimeout(
|
|
102
|
+
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
103
|
+
500
|
|
104
|
+
)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe("Repo.sharePolicyChanged", () => {
|
|
108
|
+
it("should respond to requests for a dochandle which was denied by the sharepolicy but then allowed", async () => {
|
|
109
|
+
const alicePolicy = { shouldShare: false }
|
|
110
|
+
const { alice, bob } = await twoPeers({
|
|
111
|
+
alice: {
|
|
112
|
+
shareConfig: {
|
|
113
|
+
announce: async () => false,
|
|
114
|
+
access: async () => alicePolicy.shouldShare,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
bob: { sharePolicy: async () => true },
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
121
|
+
await withTimeout(
|
|
122
|
+
awaitState(bob.findWithProgress(aliceHandle.url), "unavailable"),
|
|
123
|
+
500
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
// Change policy to allow sharing
|
|
127
|
+
alicePolicy.shouldShare = true
|
|
128
|
+
alice.shareConfigChanged()
|
|
129
|
+
|
|
130
|
+
// Give time for Alices syncDebounceRate to elapse to start syncing with Bob
|
|
131
|
+
await pause(150)
|
|
132
|
+
|
|
133
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
134
|
+
expect(bobHandle.doc()).toEqual({ foo: "bar" })
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it("should stop sending changes to a peer who had access but was then removed", async () => {
|
|
138
|
+
const alicePolicy = {
|
|
139
|
+
shouldShareWithBob: true,
|
|
140
|
+
}
|
|
141
|
+
const alice = new Repo({
|
|
142
|
+
peerId: "alice" as PeerId,
|
|
143
|
+
shareConfig: {
|
|
144
|
+
announce: async () => false,
|
|
145
|
+
access: async peerId => {
|
|
146
|
+
if (peerId === "bob") {
|
|
147
|
+
return alicePolicy.shouldShareWithBob
|
|
148
|
+
}
|
|
149
|
+
return true
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
const bob = new Repo({
|
|
154
|
+
peerId: "bob" as PeerId,
|
|
155
|
+
shareConfig: {
|
|
156
|
+
announce: async () => true,
|
|
157
|
+
access: async () => true,
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
const charlie = new Repo({
|
|
161
|
+
peerId: "charlie" as PeerId,
|
|
162
|
+
shareConfig: {
|
|
163
|
+
announce: async () => true,
|
|
164
|
+
access: async () => true,
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
await connectRepos(alice, charlie)
|
|
169
|
+
await connectRepos(alice, bob)
|
|
170
|
+
|
|
171
|
+
// create a handle on alice, request it on bob and charlie
|
|
172
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
173
|
+
const bobHandle = await bob.find<{ foo: string }>(aliceHandle.url)
|
|
174
|
+
const charlieHandle = await charlie.find<{ foo: string }>(aliceHandle.url)
|
|
175
|
+
|
|
176
|
+
// Now remove bobs access
|
|
177
|
+
alicePolicy.shouldShareWithBob = false
|
|
178
|
+
alice.shareConfigChanged()
|
|
179
|
+
|
|
180
|
+
// Now make a change on charlie
|
|
181
|
+
charlieHandle.change(d => (d.foo = "baz"))
|
|
182
|
+
|
|
183
|
+
// Wait for sync to propagate
|
|
184
|
+
await pause(300)
|
|
185
|
+
|
|
186
|
+
assert.deepStrictEqual(bobHandle.doc(), { foo: "bar" })
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("should not announce changes to a peer who reconnects", async () => {
|
|
190
|
+
// This test is exercising an issue where a peer who reconnects receives
|
|
191
|
+
// notifications about changes to a document they requested in a
|
|
192
|
+
// previous connection but have not requested since reconnection. This
|
|
193
|
+
// occurs because in order to calculate whether a peer has access to a
|
|
194
|
+
// document the Repo keeps track of whether the given peer has ever
|
|
195
|
+
// requested that document. If this state is not cleared on reconnection
|
|
196
|
+
// then repo will continue to announce changes to the peer in question.
|
|
197
|
+
const { alice, bob } = await twoPeers({
|
|
198
|
+
alice: {
|
|
199
|
+
shareConfig: {
|
|
200
|
+
announce: async () => false,
|
|
201
|
+
access: async () => true,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
bob: { sharePolicy: async () => true },
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const aliceHandle = alice.create({ foo: "bar" })
|
|
208
|
+
const bobHandle = await bob.find(aliceHandle.url)
|
|
209
|
+
assert(bobHandle != null)
|
|
210
|
+
|
|
211
|
+
// Disconnect everyone
|
|
212
|
+
bob.networkSubsystem.adapters[0].emit("peer-disconnected", {
|
|
213
|
+
peerId: alice.peerId,
|
|
214
|
+
})
|
|
215
|
+
alice.networkSubsystem.adapters[0].emit("peer-disconnected", {
|
|
216
|
+
peerId: bob.peerId,
|
|
217
|
+
})
|
|
218
|
+
bob.networkSubsystem.disconnect()
|
|
219
|
+
alice.networkSubsystem.disconnect()
|
|
220
|
+
|
|
221
|
+
await pause(150)
|
|
222
|
+
|
|
223
|
+
// Create a new repo with the same peer ID and reconnect
|
|
224
|
+
const bob2 = new Repo({ peerId: "bob" as PeerId })
|
|
225
|
+
await connectRepos(alice, bob2)
|
|
226
|
+
|
|
227
|
+
// Now create a third repo and connect it to alice
|
|
228
|
+
const charlie = new Repo({ peerId: "charlie" as PeerId })
|
|
229
|
+
await connectRepos(alice, charlie)
|
|
230
|
+
|
|
231
|
+
// Make a change on charlie, this will send a message to alice
|
|
232
|
+
// who will forward to anyone who she thinks has previously
|
|
233
|
+
// requested the document
|
|
234
|
+
const charlieHandle = await charlie.find<{ foo: string }>(aliceHandle.url)
|
|
235
|
+
charlieHandle.change(d => (d.foo = "baz"))
|
|
236
|
+
|
|
237
|
+
await pause(300)
|
|
238
|
+
|
|
239
|
+
// Bob should not have the handle, i.e. Alice should not have forwarded
|
|
240
|
+
// the messages from charlie
|
|
241
|
+
assert.equal(Object.entries(bob2.handles).length, 0)
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FindProgress } from "../../src/FindProgress.js"
|
|
2
|
+
import { FindProgressWithMethods } from "../../src/Repo.js"
|
|
3
|
+
|
|
4
|
+
export default async function awaitState(
|
|
5
|
+
progress: FindProgress<unknown> | FindProgressWithMethods<unknown>,
|
|
6
|
+
state: string
|
|
7
|
+
): Promise<void> {
|
|
8
|
+
if (progress.state == state) {
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
if (!("subscribe" in progress)) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`expected progress in state ${state} but was in final state ${progress.state}`
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
await new Promise(resolve => {
|
|
17
|
+
const unsubscribe = progress.subscribe(progress => {
|
|
18
|
+
if (progress.state === state) {
|
|
19
|
+
unsubscribe()
|
|
20
|
+
resolve(null)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DummyNetworkAdapter } from "../../src/helpers/DummyNetworkAdapter.js"
|
|
2
|
+
import { Repo } from "../../src/Repo.js"
|
|
3
|
+
import pause from "./pause.js"
|
|
4
|
+
|
|
5
|
+
export default async function connectRepos(left: Repo, right: Repo) {
|
|
6
|
+
const [leftToRight, rightToLeft] = DummyNetworkAdapter.createConnectedPair({
|
|
7
|
+
latency: 0,
|
|
8
|
+
})
|
|
9
|
+
left.networkSubsystem.addNetworkAdapter(leftToRight)
|
|
10
|
+
right.networkSubsystem.addNetworkAdapter(rightToLeft)
|
|
11
|
+
leftToRight.peerCandidate(right.peerId)
|
|
12
|
+
rightToLeft.peerCandidate(left.peerId)
|
|
13
|
+
await Promise.all([
|
|
14
|
+
left.networkSubsystem.whenReady(),
|
|
15
|
+
right.networkSubsystem.whenReady(),
|
|
16
|
+
])
|
|
17
|
+
await pause(10)
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DummyNetworkAdapter } from "../../src/helpers/DummyNetworkAdapter.js"
|
|
2
|
+
import { Repo, ShareConfig, SharePolicy } from "../../src/Repo.js"
|
|
3
|
+
import { PeerId } from "../../src/types.js"
|
|
4
|
+
import connectRepos from "./connectRepos.js"
|
|
5
|
+
|
|
6
|
+
// The parts of `RepoConfig` which are either the old sharePolicy API or the new shareConfig API
|
|
7
|
+
export type EitherConfig = {
|
|
8
|
+
sharePolicy?: SharePolicy
|
|
9
|
+
shareConfig?: ShareConfig
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// Create two connected peers with the given share configurations
|
|
13
|
+
export default async function twoPeers({
|
|
14
|
+
alice: aliceConfig,
|
|
15
|
+
bob: bobConfig,
|
|
16
|
+
}: {
|
|
17
|
+
alice: EitherConfig
|
|
18
|
+
bob: EitherConfig
|
|
19
|
+
}): Promise<{ alice: Repo; bob: Repo }> {
|
|
20
|
+
const alice = new Repo({
|
|
21
|
+
peerId: "alice" as PeerId,
|
|
22
|
+
...aliceConfig,
|
|
23
|
+
})
|
|
24
|
+
const bob = new Repo({
|
|
25
|
+
peerId: "bob" as PeerId,
|
|
26
|
+
...bobConfig,
|
|
27
|
+
})
|
|
28
|
+
await connectRepos(alice, bob)
|
|
29
|
+
return { alice, bob }
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default async function withTimeout<T>(
|
|
2
|
+
promise: Promise<T>,
|
|
3
|
+
timeout: number
|
|
4
|
+
): Promise<T | undefined> {
|
|
5
|
+
const timeoutPromise = new Promise<T | undefined>(resolve => {
|
|
6
|
+
setTimeout(() => resolve(undefined), timeout)
|
|
7
|
+
})
|
|
8
|
+
return Promise.race([promise, timeoutPromise])
|
|
9
|
+
}
|