@automerge/automerge-repo 1.1.0-alpha.6 → 1.1.0

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.
Files changed (59) hide show
  1. package/README.md +12 -7
  2. package/dist/AutomergeUrl.js +2 -2
  3. package/dist/DocHandle.d.ts +10 -4
  4. package/dist/DocHandle.d.ts.map +1 -1
  5. package/dist/DocHandle.js +17 -8
  6. package/dist/RemoteHeadsSubscriptions.js +3 -3
  7. package/dist/Repo.d.ts +23 -6
  8. package/dist/Repo.d.ts.map +1 -1
  9. package/dist/Repo.js +104 -71
  10. package/dist/helpers/debounce.js +1 -1
  11. package/dist/helpers/pause.d.ts +0 -1
  12. package/dist/helpers/pause.d.ts.map +1 -1
  13. package/dist/helpers/pause.js +2 -8
  14. package/dist/helpers/throttle.js +1 -1
  15. package/dist/helpers/withTimeout.d.ts.map +1 -1
  16. package/dist/helpers/withTimeout.js +2 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  21. package/dist/network/NetworkAdapter.js +2 -1
  22. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  23. package/dist/network/NetworkSubsystem.js +5 -3
  24. package/dist/network/messages.d.ts +43 -38
  25. package/dist/network/messages.d.ts.map +1 -1
  26. package/dist/network/messages.js +7 -9
  27. package/dist/storage/StorageSubsystem.js +1 -1
  28. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  29. package/dist/synchronizer/CollectionSynchronizer.js +1 -0
  30. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  31. package/dist/synchronizer/DocSynchronizer.js +13 -5
  32. package/dist/synchronizer/Synchronizer.d.ts +11 -3
  33. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  34. package/package.json +4 -4
  35. package/src/AutomergeUrl.ts +2 -2
  36. package/src/DocHandle.ts +34 -12
  37. package/src/RemoteHeadsSubscriptions.ts +3 -3
  38. package/src/Repo.ts +130 -81
  39. package/src/helpers/debounce.ts +1 -1
  40. package/src/helpers/pause.ts +3 -11
  41. package/src/helpers/throttle.ts +1 -1
  42. package/src/helpers/withTimeout.ts +2 -0
  43. package/src/index.ts +1 -1
  44. package/src/network/NetworkAdapter.ts +5 -3
  45. package/src/network/NetworkSubsystem.ts +5 -4
  46. package/src/network/messages.ts +60 -63
  47. package/src/storage/StorageSubsystem.ts +1 -1
  48. package/src/synchronizer/CollectionSynchronizer.ts +2 -1
  49. package/src/synchronizer/DocSynchronizer.ts +19 -11
  50. package/src/synchronizer/Synchronizer.ts +11 -3
  51. package/test/CollectionSynchronizer.test.ts +7 -5
  52. package/test/DocHandle.test.ts +11 -2
  53. package/test/RemoteHeadsSubscriptions.test.ts +53 -50
  54. package/test/Repo.test.ts +64 -2
  55. package/test/StorageSubsystem.test.ts +1 -1
  56. package/test/helpers/collectMessages.ts +19 -0
  57. package/test/remoteHeads.test.ts +141 -120
  58. package/.eslintrc +0 -28
  59. package/test/helpers/waitForMessages.ts +0 -22
package/test/Repo.test.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { next as A } from "@automerge/automerge"
2
- import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
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()
@@ -57,6 +67,13 @@ describe("Repo", () => {
57
67
  assert.equal(handle.isReady(), true)
58
68
  })
59
69
 
70
+ it("can create a document with an initial value", async () => {
71
+ const { repo } = setup()
72
+ const handle = repo.create({ foo: "bar" })
73
+ await handle.doc()
74
+ assert.equal(handle.docSync().foo, "bar")
75
+ })
76
+
60
77
  it("can find a document by url", () => {
61
78
  const { repo } = setup()
62
79
  const handle = repo.create<TestDoc>()
@@ -321,6 +338,27 @@ describe("Repo", () => {
321
338
  repo.delete(handle.documentId)
322
339
  }))
323
340
 
341
+ it("exports a document", async () => {
342
+ const { repo } = setup()
343
+ const handle = repo.create<TestDoc>()
344
+ handle.change(d => {
345
+ d.foo = "bar"
346
+ })
347
+ assert.equal(handle.isReady(), true)
348
+
349
+ const exported = await repo.export(handle.documentId)
350
+ const loaded = A.load(exported)
351
+ const doc = await handle.doc()
352
+ assert.deepEqual(doc, loaded)
353
+ })
354
+
355
+ it("rejects when exporting a document that does not exist", async () => {
356
+ const { repo } = setup()
357
+ assert.rejects(async () => {
358
+ await repo.export("foo" as AnyDocumentId)
359
+ })
360
+ })
361
+
324
362
  it("storage state doesn't change across reloads when the document hasn't changed", async () => {
325
363
  const storage = new DummyStorageAdapter()
326
364
 
@@ -396,6 +434,30 @@ describe("Repo", () => {
396
434
  const storageKeyTypes = storageAdapter.keys().map(k => k.split(".")[1])
397
435
  assert(storageKeyTypes.filter(k => k === "snapshot").length === 1)
398
436
  })
437
+
438
+ it("can import an existing document", async () => {
439
+ const { repo } = setup()
440
+ const doc = A.init<TestDoc>()
441
+ const updatedDoc = A.change(doc, d => {
442
+ d.foo = "bar"
443
+ })
444
+
445
+ const saved = A.save(updatedDoc)
446
+
447
+ const handle = repo.import<TestDoc>(saved)
448
+ assert.equal(handle.isReady(), true)
449
+ const v = await handle.doc()
450
+ assert.equal(v?.foo, "bar")
451
+
452
+ expect(A.getHistory(v)).toEqual(A.getHistory(updatedDoc))
453
+ })
454
+
455
+ it("throws an error if we try to import an invalid document", async () => {
456
+ const { repo } = setup()
457
+ expect(() => {
458
+ repo.import<TestDoc>(A.init<TestDoc> as unknown as Uint8Array)
459
+ }).toThrow()
460
+ })
399
461
  })
400
462
 
401
463
  describe("with peers (linear network)", async () => {
@@ -1,4 +1,4 @@
1
- import { NodeFSStorageAdapter } from "@automerge/automerge-repo-storage-nodefs"
1
+ import { NodeFSStorageAdapter } from "../../automerge-repo-storage-nodefs/src/index.js"
2
2
  import * as A from "@automerge/automerge/next"
3
3
  import assert from "assert"
4
4
  import fs from "fs"
@@ -0,0 +1,19 @@
1
+ import { EventEmitter } from "eventemitter3"
2
+ import { pause } from "../../src/helpers/pause.js"
3
+
4
+ export async function collectMessages({
5
+ emitter,
6
+ event,
7
+ until = pause(100),
8
+ }: {
9
+ emitter: EventEmitter
10
+ event: string
11
+ until?: Promise<unknown>
12
+ }): Promise<any[]> {
13
+ const messages = []
14
+ const listener = (message: unknown) => messages.push(message)
15
+ emitter.on(event, listener)
16
+ await until
17
+ emitter.off(event)
18
+ return messages
19
+ }
@@ -1,8 +1,7 @@
1
- import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
2
1
  import * as A from "@automerge/automerge/next"
3
2
  import assert from "assert"
4
- import { setTimeout } from "timers/promises"
5
3
  import { describe, it } from "vitest"
4
+ import { MessageChannelNetworkAdapter } from "../../automerge-repo-network-messagechannel/dist/index.js"
6
5
  import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
7
6
  import { eventPromise } from "../src/helpers/eventPromise.js"
8
7
  import {
@@ -12,7 +11,7 @@ import {
12
11
  Repo,
13
12
  } from "../src/index.js"
14
13
  import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
15
- import { waitForMessages } from "./helpers/waitForMessages.js"
14
+ import { collectMessages } from "./helpers/collectMessages.js"
16
15
  import { TestDoc } from "./types.js"
17
16
 
18
17
  describe("DocHandle.remoteHeads", () => {
@@ -24,6 +23,7 @@ describe("DocHandle.remoteHeads", () => {
24
23
  peerId: "bob" as PeerId,
25
24
  network: [],
26
25
  storage: new DummyStorageAdapter(),
26
+ enableRemoteHeadsGossiping: true,
27
27
  })
28
28
  const bobStorageId = await bobRepo.storageId()
29
29
 
@@ -41,23 +41,26 @@ describe("DocHandle.remoteHeads", () => {
41
41
 
42
42
  describe("multi hop sync", () => {
43
43
  async function setup() {
44
- // setup topology: tab -> service worker -> sync server <- service worker <- tab
45
- const leftTab1 = new Repo({
46
- peerId: "left-tab-1" as PeerId,
44
+ // setup topology: alice -> service worker -> sync server <- service worker <- bob
45
+ const alice = new Repo({
46
+ peerId: "alice-tab-1" as PeerId,
47
47
  network: [],
48
48
  sharePolicy: async () => true,
49
+ enableRemoteHeadsGossiping: true,
49
50
  })
50
- const leftTab2 = new Repo({
51
- peerId: "left-tab-2" as PeerId,
51
+ const alice2 = new Repo({
52
+ peerId: "alice-tab-2" as PeerId,
52
53
  network: [],
53
54
  sharePolicy: async () => true,
55
+ enableRemoteHeadsGossiping: true,
54
56
  })
55
- const leftServiceWorker = new Repo({
56
- peerId: "left-service-worker" as PeerId,
57
+ const aliceServiceWorker = new Repo({
58
+ peerId: "alice-service-worker" as PeerId,
57
59
  network: [],
58
60
  sharePolicy: async peer => peer === "sync-server",
59
61
  storage: new DummyStorageAdapter(),
60
62
  isEphemeral: false,
63
+ enableRemoteHeadsGossiping: true,
61
64
  })
62
65
  const syncServer = new Repo({
63
66
  peerId: "sync-server" as PeerId,
@@ -65,191 +68,209 @@ describe("DocHandle.remoteHeads", () => {
65
68
  isEphemeral: false,
66
69
  sharePolicy: async () => false,
67
70
  storage: new DummyStorageAdapter(),
71
+ enableRemoteHeadsGossiping: true,
68
72
  })
69
- const rightServiceWorker = new Repo({
70
- peerId: "right-service-worker" as PeerId,
73
+ const bobServiceWorker = new Repo({
74
+ peerId: "bob-service-worker" as PeerId,
71
75
  network: [],
72
76
  sharePolicy: async peer => peer === "sync-server",
73
77
  isEphemeral: false,
74
78
  storage: new DummyStorageAdapter(),
79
+ enableRemoteHeadsGossiping: true,
75
80
  })
76
- const rightTab = new Repo({
77
- peerId: "right-tab" as PeerId,
81
+ const bob = new Repo({
82
+ peerId: "bob-tab" as PeerId,
78
83
  network: [],
79
84
  sharePolicy: async () => true,
85
+ enableRemoteHeadsGossiping: true,
80
86
  })
81
87
 
82
88
  // connect them all up
83
- connectRepos(leftTab1, leftServiceWorker)
84
- connectRepos(leftTab2, leftServiceWorker)
85
- connectRepos(leftServiceWorker, syncServer)
86
- connectRepos(syncServer, rightServiceWorker)
87
- connectRepos(rightServiceWorker, rightTab)
88
-
89
- await setTimeout(100)
89
+ await Promise.all([
90
+ connectRepos(alice, aliceServiceWorker),
91
+ connectRepos(alice2, aliceServiceWorker),
92
+ connectRepos(aliceServiceWorker, syncServer),
93
+ connectRepos(syncServer, bobServiceWorker),
94
+ connectRepos(bobServiceWorker, bob),
95
+ ])
96
+
97
+ const alice1StorageId = await aliceServiceWorker.storageId()
98
+ const alice2StorageId = await aliceServiceWorker.storageId()
99
+ const aliceServiceWorkerStorageId = await aliceServiceWorker.storageId()
100
+ const syncServerStorageId = await syncServer.storageId()
101
+ const bobServiceWorkerStorageId = await bobServiceWorker.storageId()
102
+ const bobStorageId = await bobServiceWorker.storageId()
90
103
 
91
104
  return {
92
- leftTab1,
93
- leftTab2,
94
- leftServiceWorker,
105
+ alice,
106
+ alice2,
107
+ aliceServiceWorker,
95
108
  syncServer,
96
- rightServiceWorker,
97
- rightTab,
109
+ bobServiceWorker,
110
+ bob,
111
+ alice1StorageId,
112
+ alice2StorageId,
113
+ aliceServiceWorkerStorageId,
114
+ syncServerStorageId,
115
+ bobServiceWorkerStorageId,
116
+ bobStorageId,
98
117
  }
99
118
  }
100
119
 
101
120
  it("should report remoteHeads for peers", async () => {
102
- const { rightTab, rightServiceWorker, leftServiceWorker, leftTab1 } =
121
+ const { bob, aliceServiceWorkerStorageId, aliceServiceWorker, alice } =
103
122
  await setup()
104
123
 
105
- // subscribe to the left service worker storage ID on the right tab
106
- rightTab.subscribeToRemotes([await leftServiceWorker.storageId()!])
107
-
108
- await setTimeout(100)
124
+ // bob subscribes to alice's service worker's storageId
125
+ bob.subscribeToRemotes([aliceServiceWorkerStorageId])
109
126
 
110
- // create a doc in the left tab
111
- const leftTabDoc = leftTab1.create<TestDoc>()
112
- leftTabDoc.change(d => (d.foo = "bar"))
127
+ // alice creates a doc
128
+ const aliceDoc = alice.create<TestDoc>()
129
+ aliceDoc.change(d => (d.foo = "bar"))
113
130
 
114
- // wait for the document to arrive on the right tab
115
- const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
116
- await rightTabDoc.whenReady()
131
+ // bob waits for the document to arrive
132
+ const bobDoc = bob.find<TestDoc>(aliceDoc.url)
133
+ await bobDoc.whenReady()
117
134
 
118
- // wait for the document to arrive in the left service worker
119
- const leftServiceWorkerDoc = leftServiceWorker.find(leftTabDoc.documentId)
120
- await leftServiceWorkerDoc.whenReady()
135
+ // alice's service worker waits for the document to arrive
136
+ const aliceServiceWorkerDoc = aliceServiceWorker.find(aliceDoc.documentId)
137
+ await aliceServiceWorkerDoc.whenReady()
121
138
 
122
- const leftServiceWorkerStorageId = await leftServiceWorker.storageId()
123
- let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
139
+ let aliceSeenByBobPromise = new Promise<DocHandleRemoteHeadsPayload>(
124
140
  resolve => {
125
- rightTabDoc.on("remote-heads", message => {
126
- if (message.storageId === leftServiceWorkerStorageId) {
141
+ bobDoc.on("remote-heads", message => {
142
+ if (message.storageId === aliceServiceWorkerStorageId) {
127
143
  resolve(message)
128
144
  }
129
145
  })
130
146
  }
131
147
  )
132
148
 
133
- // make a change on the right
134
- rightTabDoc.change(d => (d.foo = "baz"))
149
+ // bob makes a change
150
+ bobDoc.change(d => (d.foo = "baz"))
135
151
 
136
- // wait for the change to be acknolwedged by the left
137
- const leftSeenByRight = await leftSeenByRightPromise
152
+ // wait for alice's service worker to acknowledge the change
153
+ const { heads } = await aliceSeenByBobPromise
138
154
 
139
- assert.deepStrictEqual(
140
- leftSeenByRight.heads,
141
- A.getHeads(leftServiceWorkerDoc.docSync())
142
- )
155
+ assert.deepStrictEqual(heads, A.getHeads(aliceServiceWorkerDoc.docSync()))
143
156
  })
144
157
 
145
158
  it("should report remoteHeads only for documents the subscriber has open", async () => {
146
- const { leftTab1, rightTab, rightServiceWorker } = await setup()
147
-
148
- // subscribe leftTab to storageId of rightServiceWorker
149
- leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
159
+ const { alice, bob, bobServiceWorkerStorageId } = await setup()
150
160
 
151
- await setTimeout(100)
161
+ // alice subscribes to bob's service worker
162
+ alice.subscribeToRemotes([bobServiceWorkerStorageId])
152
163
 
153
- // create 2 docs in right tab
154
- const rightTabDocA = rightTab.create<TestDoc>()
155
- rightTabDocA.change(d => (d.foo = "A"))
164
+ // bob creates two docs
165
+ const bobDocA = bob.create<TestDoc>()
166
+ bobDocA.change(d => (d.foo = "A"))
156
167
 
157
- const rightTabDocB = rightTab.create<TestDoc>()
158
- rightTabDocB.change(d => (d.foo = "B"))
168
+ const bobDocB = bob.create<TestDoc>()
169
+ bobDocB.change(d => (d.foo = "B"))
159
170
 
160
- // open doc b in left tab 1
161
- const leftTabDocA = leftTab1.find<TestDoc>(rightTabDocA.url)
171
+ // alice opens doc A
172
+ const aliceDocA = alice.find<TestDoc>(bobDocA.url)
162
173
 
163
174
  const remoteHeadsChangedMessages = (
164
- await waitForMessages(leftTab1.networkSubsystem, "message")
175
+ await collectMessages({
176
+ emitter: alice.networkSubsystem,
177
+ event: "message",
178
+ until: aliceDocA.whenReady(),
179
+ })
165
180
  ).filter(({ type }) => type === "remote-heads-changed")
166
181
 
167
182
  // we should only be notified of the head changes of doc A
168
- assert.strictEqual(remoteHeadsChangedMessages.length, 1)
169
- assert.strictEqual(
170
- remoteHeadsChangedMessages[0].documentId,
171
- leftTabDocA.documentId
183
+ assert(
184
+ remoteHeadsChangedMessages.every(
185
+ d => d.documentId === aliceDocA.documentId
186
+ )
172
187
  )
173
188
  })
174
189
 
175
190
  it("should report remote heads for doc on subscribe if peer already knows them", async () => {
176
- const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
191
+ const { alice, alice2, bob, bobServiceWorkerStorageId } = await setup()
177
192
 
178
- // create 2 docs in right tab
179
- const rightTabDocA = rightTab.create<TestDoc>()
180
- rightTabDocA.change(d => (d.foo = "A"))
193
+ // bob creates 2 docs
194
+ const bobDocA = bob.create<TestDoc>()
195
+ bobDocA.change(d => (d.foo = "A"))
181
196
 
182
- const rightTabDocB = rightTab.create<TestDoc>()
183
- rightTabDocB.change(d => (d.foo = "B"))
197
+ const bobDocB = bob.create<TestDoc>()
198
+ bobDocB.change(d => (d.foo = "B"))
184
199
 
185
- // open docs in left tab 1
186
- const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
187
- const leftTab1DocB = leftTab1.find<TestDoc>(rightTabDocB.url)
200
+ // alice opens the docs
201
+ const _aliceDocA = alice.find<TestDoc>(bobDocA.url)
202
+ const _aliceDocB = alice.find<TestDoc>(bobDocB.url)
188
203
 
189
- // subscribe leftTab 1 to storageId of rightServiceWorker
190
- leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
204
+ // alice subscribes to bob's service worker
205
+ alice.subscribeToRemotes([bobServiceWorkerStorageId])
191
206
 
192
- await setTimeout(200)
207
+ // Now alice's service worker has the remote heads of bob's service worker for both doc A and
208
+ // doc B. If alice subscribes to bob's service worker, bob's service worker should send its
209
+ // stored remote heads immediately.
193
210
 
194
- // now the left service worker has the remote heads of the right service worker for both doc A and doc B
195
- // if we subscribe from left tab 1 the left service workers should send it's stored remote heads immediately
196
-
197
- // open doc and subscribe leftTab 2 to storageId of rightServiceWorker
198
- const leftTab2DocA = leftTab2.find<TestDoc>(rightTabDocA.url)
199
- leftTab2.subscribeToRemotes([await rightServiceWorker.storageId()!])
211
+ // open doc and subscribe alice's second tab to bob's service worker
212
+ const alice2DocA = alice2.find<TestDoc>(bobDocA.url)
213
+ alice2.subscribeToRemotes([bobServiceWorkerStorageId])
200
214
 
201
215
  const remoteHeadsChangedMessages = (
202
- await waitForMessages(leftTab2.networkSubsystem, "message")
216
+ await collectMessages({
217
+ emitter: alice2.networkSubsystem,
218
+ event: "message",
219
+ until: alice2DocA.whenReady(),
220
+ })
203
221
  ).filter(({ type }) => type === "remote-heads-changed")
204
222
 
205
223
  // we should only be notified of the head changes of doc A
206
- assert.strictEqual(remoteHeadsChangedMessages.length, 1)
207
- assert.strictEqual(
208
- remoteHeadsChangedMessages[0].documentId,
209
- leftTab1DocA.documentId
224
+ assert.strictEqual(remoteHeadsChangedMessages.length, 2)
225
+ assert(
226
+ remoteHeadsChangedMessages.every(
227
+ d => d.documentId === alice2DocA.documentId
228
+ )
210
229
  )
211
230
  })
212
231
 
213
232
  it("should report remote heads for subscribed storage id once we open a new doc", async () => {
214
- const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
215
-
216
- // create 2 docs in right tab
217
- const rightTabDocA = rightTab.create<TestDoc>()
218
- rightTabDocA.change(d => (d.foo = "A"))
233
+ const { alice, bob, bobServiceWorkerStorageId } = await setup()
219
234
 
220
- const rightTabDocB = rightTab.create<TestDoc>()
221
- rightTabDocB.change(d => (d.foo = "B"))
235
+ // bob creates 2 docs
236
+ const bobDocA = bob.create<TestDoc>()
237
+ bobDocA.change(d => (d.foo = "A"))
222
238
 
223
- await setTimeout(200)
239
+ const bobDocB = bob.create<TestDoc>()
240
+ bobDocB.change(d => (d.foo = "B"))
224
241
 
225
- // subscribe leftTab 1 to storageId of rightServiceWorker
226
- leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
242
+ // alice subscribes to bob's service worker
243
+ alice.subscribeToRemotes([bobServiceWorkerStorageId])
227
244
 
228
- // in leftTab 1 open doc A
229
- const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
245
+ // alice opens doc A
246
+ const alice1DocA = alice.find<TestDoc>(bobDocA.url)
230
247
 
231
248
  const remoteHeadsChangedMessages = (
232
- await waitForMessages(leftTab1.networkSubsystem, "message")
249
+ await collectMessages({
250
+ emitter: alice.networkSubsystem,
251
+ event: "message",
252
+ until: alice1DocA.whenReady(),
253
+ })
233
254
  ).filter(({ type }) => type === "remote-heads-changed")
234
255
 
235
- console.log(JSON.stringify(remoteHeadsChangedMessages, null, 2))
236
-
237
- assert.strictEqual(remoteHeadsChangedMessages.length, 1)
238
- assert.strictEqual(
239
- remoteHeadsChangedMessages[0].documentId,
240
- leftTab1DocA.documentId
256
+ assert.strictEqual(remoteHeadsChangedMessages.length, 2)
257
+ assert(
258
+ remoteHeadsChangedMessages.every(
259
+ d => d.documentId === alice1DocA.documentId
260
+ )
241
261
  )
242
262
  })
243
263
  })
244
264
  })
245
265
 
246
- function connectRepos(repo1: Repo, repo2: Repo) {
247
- const { port1: leftToRight, port2: rightToLeft } = new MessageChannel()
248
-
249
- repo1.networkSubsystem.addNetworkAdapter(
250
- new MessageChannelNetworkAdapter(leftToRight)
251
- )
252
- repo2.networkSubsystem.addNetworkAdapter(
253
- new MessageChannelNetworkAdapter(rightToLeft)
254
- )
266
+ async function connectRepos(a: Repo, b: Repo) {
267
+ const { port1: a2b, port2: b2a } = new MessageChannel()
268
+ const aAdapter = new MessageChannelNetworkAdapter(a2b)
269
+ const bAdapter = new MessageChannelNetworkAdapter(b2a)
270
+ a.networkSubsystem.addNetworkAdapter(aAdapter)
271
+ b.networkSubsystem.addNetworkAdapter(bAdapter)
272
+ await Promise.all([
273
+ eventPromise(a.networkSubsystem, "ready"),
274
+ eventPromise(b.networkSubsystem, "ready"),
275
+ ])
255
276
  }
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
- }
@@ -1,22 +0,0 @@
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
- }