@automerge/automerge-repo 1.1.0-alpha.1 → 1.1.0-alpha.13
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/README.md +12 -7
- package/dist/AutomergeUrl.js +2 -2
- package/dist/RemoteHeadsSubscriptions.d.ts +1 -0
- package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
- package/dist/RemoteHeadsSubscriptions.js +76 -16
- package/dist/Repo.d.ts +23 -10
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +103 -54
- package/dist/helpers/debounce.js +1 -1
- package/dist/helpers/pause.d.ts.map +1 -1
- package/dist/helpers/pause.js +2 -0
- package/dist/helpers/throttle.js +1 -1
- package/dist/helpers/withTimeout.d.ts.map +1 -1
- package/dist/helpers/withTimeout.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/network/NetworkAdapter.d.ts +14 -7
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.js +3 -3
- package/dist/network/NetworkSubsystem.d.ts +4 -8
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +12 -13
- package/dist/network/messages.d.ts +48 -38
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/network/messages.js +7 -9
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +7 -2
- package/dist/storage/keyHash.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +5 -3
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +20 -8
- package/dist/synchronizer/Synchronizer.d.ts +12 -3
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/AutomergeUrl.ts +2 -2
- package/src/RemoteHeadsSubscriptions.ts +85 -16
- package/src/Repo.ts +131 -68
- package/src/helpers/debounce.ts +1 -1
- package/src/helpers/pause.ts +4 -0
- package/src/helpers/throttle.ts +1 -1
- package/src/helpers/withTimeout.ts +2 -0
- package/src/index.ts +2 -1
- package/src/network/NetworkAdapter.ts +18 -12
- package/src/network/NetworkSubsystem.ts +23 -24
- package/src/network/messages.ts +77 -68
- package/src/storage/StorageSubsystem.ts +7 -2
- package/src/storage/keyHash.ts +2 -0
- package/src/synchronizer/CollectionSynchronizer.ts +7 -4
- package/src/synchronizer/DocSynchronizer.ts +27 -15
- package/src/synchronizer/Synchronizer.ts +13 -3
- package/test/RemoteHeadsSubscriptions.test.ts +34 -24
- package/test/Repo.test.ts +57 -2
- package/test/StorageSubsystem.test.ts +1 -1
- package/test/helpers/waitForMessages.ts +22 -0
- package/test/remoteHeads.test.ts +197 -72
- package/.eslintrc +0 -28
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge"
|
|
2
2
|
import assert from "assert"
|
|
3
3
|
import { describe, it } from "vitest"
|
|
4
|
+
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
4
5
|
import { RemoteHeadsSubscriptions } from "../src/RemoteHeadsSubscriptions.js"
|
|
5
6
|
import { PeerId, StorageId } from "../src/index.js"
|
|
6
|
-
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
7
|
-
import { pause } from "../src/helpers/pause.js"
|
|
8
|
-
import { EventEmitter } from "eventemitter3"
|
|
9
7
|
import {
|
|
10
8
|
RemoteHeadsChanged,
|
|
11
9
|
RemoteSubscriptionControlMessage,
|
|
12
10
|
} from "../src/network/messages.js"
|
|
11
|
+
import { waitForMessages } from "./helpers/waitForMessages.js"
|
|
13
12
|
|
|
14
13
|
describe("RepoHeadsSubscriptions", () => {
|
|
15
14
|
const storageA = "remote-a" as StorageId
|
|
@@ -224,6 +223,8 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
224
223
|
remoteHeadsSubscriptions,
|
|
225
224
|
"notify-remote-heads"
|
|
226
225
|
)
|
|
226
|
+
remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docA)
|
|
227
|
+
remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docC)
|
|
227
228
|
|
|
228
229
|
// change message for docA in storageB
|
|
229
230
|
remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
|
|
@@ -260,7 +261,33 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
260
261
|
assert.strictEqual(messages.length, 0)
|
|
261
262
|
})
|
|
262
263
|
|
|
263
|
-
it("should
|
|
264
|
+
it("should not send remote heads for docs that the peer is not subscribed to", async () => {
|
|
265
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
266
|
+
remoteHeadsSubscriptions.subscribeToRemotes([storageB])
|
|
267
|
+
|
|
268
|
+
// subscribe peer c to storage b
|
|
269
|
+
remoteHeadsSubscriptions.handleControlMessage(subscribePeerCToStorageB)
|
|
270
|
+
const messagesAfterSubscribePromise = waitForMessages(
|
|
271
|
+
remoteHeadsSubscriptions,
|
|
272
|
+
"notify-remote-heads"
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
// change message for docA in storageB
|
|
276
|
+
remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
|
|
277
|
+
|
|
278
|
+
// change heads directly
|
|
279
|
+
remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
|
|
280
|
+
docC,
|
|
281
|
+
storageB,
|
|
282
|
+
[]
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// expect peer c to be notified both changes
|
|
286
|
+
let messages = await messagesAfterSubscribePromise
|
|
287
|
+
assert.strictEqual(messages.length, 0)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it("should only notify of sync states with a more recent timestamp", async () => {
|
|
264
291
|
const remoteHeadsSubscription = new RemoteHeadsSubscriptions()
|
|
265
292
|
|
|
266
293
|
const messagesPromise = waitForMessages(
|
|
@@ -271,6 +298,9 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
271
298
|
remoteHeadsSubscription.subscribeToRemotes([storageB])
|
|
272
299
|
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
|
|
273
300
|
|
|
301
|
+
// send same message
|
|
302
|
+
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
|
|
303
|
+
|
|
274
304
|
// send message with old heads
|
|
275
305
|
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB)
|
|
276
306
|
|
|
@@ -321,23 +351,3 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
321
351
|
assert.deepStrictEqual(messages[2].peers, [])
|
|
322
352
|
})
|
|
323
353
|
})
|
|
324
|
-
|
|
325
|
-
async function waitForMessages(
|
|
326
|
-
emitter: EventEmitter,
|
|
327
|
-
event: string,
|
|
328
|
-
timeout: number = 100
|
|
329
|
-
): Promise<any[]> {
|
|
330
|
-
const messages = []
|
|
331
|
-
|
|
332
|
-
const onEvent = message => {
|
|
333
|
-
messages.push(message)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
emitter.on(event, onEvent)
|
|
337
|
-
|
|
338
|
-
await pause(timeout)
|
|
339
|
-
|
|
340
|
-
emitter.off(event)
|
|
341
|
-
|
|
342
|
-
return messages
|
|
343
|
-
}
|
package/test/Repo.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { next as A } from "@automerge/automerge"
|
|
2
|
-
import { MessageChannelNetworkAdapter } from "
|
|
2
|
+
import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/src/index.js"
|
|
3
3
|
import assert from "assert"
|
|
4
4
|
import * as Uuid from "uuid"
|
|
5
|
-
import { describe, it } from "vitest"
|
|
5
|
+
import { describe, expect, it } from "vitest"
|
|
6
6
|
import { READY } from "../src/DocHandle.js"
|
|
7
7
|
import { parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
8
8
|
import {
|
|
@@ -13,6 +13,7 @@ import { Repo } from "../src/Repo.js"
|
|
|
13
13
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
14
14
|
import { pause } from "../src/helpers/pause.js"
|
|
15
15
|
import {
|
|
16
|
+
AnyDocumentId,
|
|
16
17
|
AutomergeUrl,
|
|
17
18
|
DocHandle,
|
|
18
19
|
DocumentId,
|
|
@@ -31,6 +32,15 @@ import { TestDoc } from "./types.js"
|
|
|
31
32
|
import { StorageId } from "../src/storage/types.js"
|
|
32
33
|
|
|
33
34
|
describe("Repo", () => {
|
|
35
|
+
describe("constructor", () => {
|
|
36
|
+
it("can be instantiated without network adapters", () => {
|
|
37
|
+
const repo = new Repo({
|
|
38
|
+
network: [],
|
|
39
|
+
})
|
|
40
|
+
expect(repo).toBeInstanceOf(Repo)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
34
44
|
describe("local only", () => {
|
|
35
45
|
const setup = ({ startReady = true } = {}) => {
|
|
36
46
|
const storageAdapter = new DummyStorageAdapter()
|
|
@@ -321,6 +331,27 @@ describe("Repo", () => {
|
|
|
321
331
|
repo.delete(handle.documentId)
|
|
322
332
|
}))
|
|
323
333
|
|
|
334
|
+
it("exports a document", async () => {
|
|
335
|
+
const { repo } = setup()
|
|
336
|
+
const handle = repo.create<TestDoc>()
|
|
337
|
+
handle.change(d => {
|
|
338
|
+
d.foo = "bar"
|
|
339
|
+
})
|
|
340
|
+
assert.equal(handle.isReady(), true)
|
|
341
|
+
|
|
342
|
+
const exported = await repo.export(handle.documentId)
|
|
343
|
+
const loaded = A.load(exported)
|
|
344
|
+
const doc = await handle.doc()
|
|
345
|
+
assert.deepEqual(doc, loaded)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it("rejects when exporting a document that does not exist", async () => {
|
|
349
|
+
const { repo } = setup()
|
|
350
|
+
assert.rejects(async () => {
|
|
351
|
+
await repo.export("foo" as AnyDocumentId)
|
|
352
|
+
})
|
|
353
|
+
})
|
|
354
|
+
|
|
324
355
|
it("storage state doesn't change across reloads when the document hasn't changed", async () => {
|
|
325
356
|
const storage = new DummyStorageAdapter()
|
|
326
357
|
|
|
@@ -396,6 +427,30 @@ describe("Repo", () => {
|
|
|
396
427
|
const storageKeyTypes = storageAdapter.keys().map(k => k.split(".")[1])
|
|
397
428
|
assert(storageKeyTypes.filter(k => k === "snapshot").length === 1)
|
|
398
429
|
})
|
|
430
|
+
|
|
431
|
+
it("can import an existing document", async () => {
|
|
432
|
+
const { repo } = setup()
|
|
433
|
+
const doc = A.init<TestDoc>()
|
|
434
|
+
const updatedDoc = A.change(doc, d => {
|
|
435
|
+
d.foo = "bar"
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
const saved = A.save(updatedDoc)
|
|
439
|
+
|
|
440
|
+
const handle = repo.import<TestDoc>(saved)
|
|
441
|
+
assert.equal(handle.isReady(), true)
|
|
442
|
+
const v = await handle.doc()
|
|
443
|
+
assert.equal(v?.foo, "bar")
|
|
444
|
+
|
|
445
|
+
expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it("throws an error if we try to import an invalid document", async () => {
|
|
449
|
+
const { repo } = setup()
|
|
450
|
+
expect(() => {
|
|
451
|
+
repo.import<TestDoc>(A.init<TestDoc> as unknown as Uint8Array)
|
|
452
|
+
}).toThrow()
|
|
453
|
+
})
|
|
399
454
|
})
|
|
400
455
|
|
|
401
456
|
describe("with peers (linear network)", async () => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EventEmitter } from "eventemitter3"
|
|
2
|
+
import { pause } from "../../src/helpers/pause.js"
|
|
3
|
+
|
|
4
|
+
export async function waitForMessages(
|
|
5
|
+
emitter: EventEmitter,
|
|
6
|
+
event: string,
|
|
7
|
+
timeout: number = 100
|
|
8
|
+
): Promise<any[]> {
|
|
9
|
+
const messages = []
|
|
10
|
+
|
|
11
|
+
const onEvent = message => {
|
|
12
|
+
messages.push(message)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
emitter.on(event, onEvent)
|
|
16
|
+
|
|
17
|
+
await pause(timeout)
|
|
18
|
+
|
|
19
|
+
emitter.off(event)
|
|
20
|
+
|
|
21
|
+
return messages
|
|
22
|
+
}
|
package/test/remoteHeads.test.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/dist/index.js"
|
|
1
2
|
import * as A from "@automerge/automerge/next"
|
|
2
3
|
import assert from "assert"
|
|
3
|
-
import {
|
|
4
|
+
import { setTimeout } from "timers/promises"
|
|
4
5
|
import { describe, it } from "vitest"
|
|
5
6
|
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
6
7
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
7
|
-
import { pause } from "../src/helpers/pause.js"
|
|
8
8
|
import {
|
|
9
9
|
DocHandle,
|
|
10
10
|
DocHandleRemoteHeadsPayload,
|
|
11
11
|
PeerId,
|
|
12
12
|
Repo,
|
|
13
13
|
} from "../src/index.js"
|
|
14
|
-
import { TestDoc } from "./types.js"
|
|
15
|
-
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
|
|
16
|
-
import { setTimeout } from "timers/promises"
|
|
17
14
|
import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
|
|
15
|
+
import { waitForMessages } from "./helpers/waitForMessages.js"
|
|
16
|
+
import { TestDoc } from "./types.js"
|
|
18
17
|
|
|
19
18
|
describe("DocHandle.remoteHeads", () => {
|
|
20
19
|
const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId
|
|
@@ -25,6 +24,7 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
25
24
|
peerId: "bob" as PeerId,
|
|
26
25
|
network: [],
|
|
27
26
|
storage: new DummyStorageAdapter(),
|
|
27
|
+
enableRemoteHeadsGossiping: true,
|
|
28
28
|
})
|
|
29
29
|
const bobStorageId = await bobRepo.storageId()
|
|
30
30
|
|
|
@@ -40,86 +40,211 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
40
40
|
assert.deepStrictEqual(handle.getRemoteHeads(bobStorageId), [])
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
describe("multi hop sync", () => {
|
|
44
|
+
async function setup() {
|
|
45
|
+
// setup topology: tab -> service worker -> sync server <- service worker <- tab
|
|
46
|
+
const leftTab1 = new Repo({
|
|
47
|
+
peerId: "left-tab-1" as PeerId,
|
|
48
|
+
network: [],
|
|
49
|
+
sharePolicy: async () => true,
|
|
50
|
+
enableRemoteHeadsGossiping: true,
|
|
51
|
+
})
|
|
52
|
+
const leftTab2 = new Repo({
|
|
53
|
+
peerId: "left-tab-2" as PeerId,
|
|
54
|
+
network: [],
|
|
55
|
+
sharePolicy: async () => true,
|
|
56
|
+
enableRemoteHeadsGossiping: true,
|
|
57
|
+
})
|
|
58
|
+
const leftServiceWorker = new Repo({
|
|
59
|
+
peerId: "left-service-worker" as PeerId,
|
|
60
|
+
network: [],
|
|
61
|
+
sharePolicy: async peer => peer === "sync-server",
|
|
62
|
+
storage: new DummyStorageAdapter(),
|
|
63
|
+
isEphemeral: false,
|
|
64
|
+
enableRemoteHeadsGossiping: true,
|
|
65
|
+
})
|
|
66
|
+
const syncServer = new Repo({
|
|
67
|
+
peerId: "sync-server" as PeerId,
|
|
68
|
+
network: [],
|
|
69
|
+
isEphemeral: false,
|
|
70
|
+
sharePolicy: async () => false,
|
|
71
|
+
storage: new DummyStorageAdapter(),
|
|
72
|
+
enableRemoteHeadsGossiping: true,
|
|
73
|
+
})
|
|
74
|
+
const rightServiceWorker = new Repo({
|
|
75
|
+
peerId: "right-service-worker" as PeerId,
|
|
76
|
+
network: [],
|
|
77
|
+
sharePolicy: async peer => peer === "sync-server",
|
|
78
|
+
isEphemeral: false,
|
|
79
|
+
storage: new DummyStorageAdapter(),
|
|
80
|
+
enableRemoteHeadsGossiping: true,
|
|
81
|
+
})
|
|
82
|
+
const rightTab = new Repo({
|
|
83
|
+
peerId: "right-tab" as PeerId,
|
|
84
|
+
network: [],
|
|
85
|
+
sharePolicy: async () => true,
|
|
86
|
+
enableRemoteHeadsGossiping: true,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// connect them all up
|
|
90
|
+
connectRepos(leftTab1, leftServiceWorker)
|
|
91
|
+
connectRepos(leftTab2, leftServiceWorker)
|
|
92
|
+
connectRepos(leftServiceWorker, syncServer)
|
|
93
|
+
connectRepos(syncServer, rightServiceWorker)
|
|
94
|
+
connectRepos(rightServiceWorker, rightTab)
|
|
95
|
+
|
|
96
|
+
await setTimeout(100)
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
leftTab1,
|
|
100
|
+
leftTab2,
|
|
101
|
+
leftServiceWorker,
|
|
102
|
+
syncServer,
|
|
103
|
+
rightServiceWorker,
|
|
104
|
+
rightTab,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
it("should report remoteHeads for peers", async () => {
|
|
109
|
+
const { rightTab, rightServiceWorker, leftServiceWorker, leftTab1 } =
|
|
110
|
+
await setup()
|
|
111
|
+
|
|
112
|
+
// subscribe to the left service worker storage ID on the right tab
|
|
113
|
+
rightTab.subscribeToRemotes([await leftServiceWorker.storageId()!])
|
|
114
|
+
|
|
115
|
+
await setTimeout(100)
|
|
116
|
+
|
|
117
|
+
// create a doc in the left tab
|
|
118
|
+
const leftTabDoc = leftTab1.create<TestDoc>()
|
|
119
|
+
leftTabDoc.change(d => (d.foo = "bar"))
|
|
120
|
+
|
|
121
|
+
// wait for the document to arrive on the right tab
|
|
122
|
+
const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
|
|
123
|
+
await rightTabDoc.whenReady()
|
|
124
|
+
|
|
125
|
+
// wait for the document to arrive in the left service worker
|
|
126
|
+
const leftServiceWorkerDoc = leftServiceWorker.find(leftTabDoc.documentId)
|
|
127
|
+
await leftServiceWorkerDoc.whenReady()
|
|
128
|
+
|
|
129
|
+
const leftServiceWorkerStorageId = await leftServiceWorker.storageId()
|
|
130
|
+
let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
|
|
131
|
+
resolve => {
|
|
132
|
+
rightTabDoc.on("remote-heads", message => {
|
|
133
|
+
if (message.storageId === leftServiceWorkerStorageId) {
|
|
134
|
+
resolve(message)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
// make a change on the right
|
|
141
|
+
rightTabDoc.change(d => (d.foo = "baz"))
|
|
142
|
+
|
|
143
|
+
// wait for the change to be acknolwedged by the left
|
|
144
|
+
const leftSeenByRight = await leftSeenByRightPromise
|
|
145
|
+
|
|
146
|
+
assert.deepStrictEqual(
|
|
147
|
+
leftSeenByRight.heads,
|
|
148
|
+
A.getHeads(leftServiceWorkerDoc.docSync())
|
|
149
|
+
)
|
|
70
150
|
})
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
151
|
+
|
|
152
|
+
it("should report remoteHeads only for documents the subscriber has open", async () => {
|
|
153
|
+
const { leftTab1, rightTab, rightServiceWorker } = await setup()
|
|
154
|
+
|
|
155
|
+
// subscribe leftTab to storageId of rightServiceWorker
|
|
156
|
+
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
157
|
+
|
|
158
|
+
await setTimeout(100)
|
|
159
|
+
|
|
160
|
+
// create 2 docs in right tab
|
|
161
|
+
const rightTabDocA = rightTab.create<TestDoc>()
|
|
162
|
+
rightTabDocA.change(d => (d.foo = "A"))
|
|
163
|
+
|
|
164
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
165
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
166
|
+
|
|
167
|
+
// open doc b in left tab 1
|
|
168
|
+
const leftTabDocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
169
|
+
|
|
170
|
+
const remoteHeadsChangedMessages = (
|
|
171
|
+
await waitForMessages(leftTab1.networkSubsystem, "message")
|
|
172
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
173
|
+
|
|
174
|
+
// we should only be notified of the head changes of doc A
|
|
175
|
+
const docIds = remoteHeadsChangedMessages.map(d => d.documentId)
|
|
176
|
+
const uniqueDocIds = [...new Set(docIds)]
|
|
177
|
+
assert.deepStrictEqual(uniqueDocIds, [leftTabDocA.documentId])
|
|
75
178
|
})
|
|
76
179
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
connectRepos(leftServiceWorker, syncServer)
|
|
80
|
-
connectRepos(syncServer, rightServiceWorker)
|
|
81
|
-
connectRepos(rightServiceWorker, rightTab)
|
|
180
|
+
it("should report remote heads for doc on subscribe if peer already knows them", async () => {
|
|
181
|
+
const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
|
|
82
182
|
|
|
83
|
-
|
|
183
|
+
// create 2 docs in right tab
|
|
184
|
+
const rightTabDocA = rightTab.create<TestDoc>()
|
|
185
|
+
rightTabDocA.change(d => (d.foo = "A"))
|
|
84
186
|
|
|
85
|
-
|
|
86
|
-
|
|
187
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
188
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
87
189
|
|
|
88
|
-
|
|
190
|
+
// open docs in left tab 1
|
|
191
|
+
const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
192
|
+
const leftTab1DocB = leftTab1.find<TestDoc>(rightTabDocB.url)
|
|
89
193
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
leftTabDoc.change(d => (d.foo = "bar"))
|
|
194
|
+
// subscribe leftTab 1 to storageId of rightServiceWorker
|
|
195
|
+
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
93
196
|
|
|
94
|
-
|
|
95
|
-
const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
|
|
96
|
-
await rightTabDoc.whenReady()
|
|
197
|
+
await setTimeout(200)
|
|
97
198
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await leftServiceWorkerDoc.whenReady()
|
|
199
|
+
// now the left service worker has the remote heads of the right service worker for both doc A and doc B
|
|
200
|
+
// if we subscribe from left tab 1 the left service workers should send it's stored remote heads immediately
|
|
101
201
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
202
|
+
// open doc and subscribe leftTab 2 to storageId of rightServiceWorker
|
|
203
|
+
const leftTab2DocA = leftTab2.find<TestDoc>(rightTabDocA.url)
|
|
204
|
+
leftTab2.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
205
|
+
|
|
206
|
+
const remoteHeadsChangedMessages = (
|
|
207
|
+
await waitForMessages(leftTab2.networkSubsystem, "message")
|
|
208
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
209
|
+
|
|
210
|
+
// we should only be notified of the head changes of doc A
|
|
211
|
+
assert.strictEqual(remoteHeadsChangedMessages.length, 1)
|
|
212
|
+
assert.strictEqual(
|
|
213
|
+
remoteHeadsChangedMessages[0].documentId,
|
|
214
|
+
leftTab1DocA.documentId
|
|
215
|
+
)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("should report remote heads for subscribed storage id once we open a new doc", async () => {
|
|
219
|
+
const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
|
|
112
220
|
|
|
113
|
-
|
|
114
|
-
|
|
221
|
+
// create 2 docs in right tab
|
|
222
|
+
const rightTabDocA = rightTab.create<TestDoc>()
|
|
223
|
+
rightTabDocA.change(d => (d.foo = "A"))
|
|
115
224
|
|
|
116
|
-
|
|
117
|
-
|
|
225
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
226
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
118
227
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
228
|
+
await setTimeout(200)
|
|
229
|
+
|
|
230
|
+
// subscribe leftTab 1 to storageId of rightServiceWorker
|
|
231
|
+
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
232
|
+
|
|
233
|
+
// in leftTab 1 open doc A
|
|
234
|
+
const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
235
|
+
|
|
236
|
+
const remoteHeadsChangedMessages = (
|
|
237
|
+
await waitForMessages(leftTab1.networkSubsystem, "message")
|
|
238
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
239
|
+
|
|
240
|
+
// console.log(JSON.stringify(remoteHeadsChangedMessages, null, 2))
|
|
241
|
+
|
|
242
|
+
assert.strictEqual(remoteHeadsChangedMessages.length, 1)
|
|
243
|
+
assert.strictEqual(
|
|
244
|
+
remoteHeadsChangedMessages[0].documentId,
|
|
245
|
+
leftTab1DocA.documentId
|
|
246
|
+
)
|
|
247
|
+
})
|
|
123
248
|
})
|
|
124
249
|
})
|
|
125
250
|
|
package/.eslintrc
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": true,
|
|
4
|
-
"es2021": true
|
|
5
|
-
},
|
|
6
|
-
"extends": [
|
|
7
|
-
"eslint:recommended",
|
|
8
|
-
"plugin:@typescript-eslint/eslint-recommended",
|
|
9
|
-
"plugin:@typescript-eslint/recommended"
|
|
10
|
-
],
|
|
11
|
-
"ignorePatterns": ["dist/**", "test/**", "node_modules/**"],
|
|
12
|
-
"parser": "@typescript-eslint/parser",
|
|
13
|
-
"plugins": ["@typescript-eslint"],
|
|
14
|
-
"parserOptions": {
|
|
15
|
-
"project": "./tsconfig.json",
|
|
16
|
-
"ecmaVersion": "latest",
|
|
17
|
-
"sourceType": "module"
|
|
18
|
-
},
|
|
19
|
-
"rules": {
|
|
20
|
-
"semi": ["error", "never"],
|
|
21
|
-
"import/extensions": 0,
|
|
22
|
-
"lines-between-class-members": 0,
|
|
23
|
-
"@typescript-eslint/no-floating-promises": "error",
|
|
24
|
-
"@typescript-eslint/no-empty-function": ["warn", { "allow": ["methods"] }],
|
|
25
|
-
"no-param-reassign": 0,
|
|
26
|
-
"no-use-before-define": 0
|
|
27
|
-
}
|
|
28
|
-
}
|