@automerge/automerge-repo 1.0.19 → 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/DocHandle.d.ts +6 -5
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +7 -7
- package/dist/RemoteHeadsSubscriptions.d.ts +42 -0
- package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -0
- package/dist/RemoteHeadsSubscriptions.js +284 -0
- package/dist/Repo.d.ts +29 -2
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +168 -9
- 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 +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/network/NetworkAdapter.d.ts +15 -1
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.js +3 -1
- package/dist/network/NetworkSubsystem.d.ts +4 -2
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +13 -7
- package/dist/network/messages.d.ts +68 -35
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/network/messages.js +9 -7
- package/dist/storage/StorageSubsystem.d.ts +5 -3
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +23 -5
- package/dist/storage/keyHash.d.ts.map +1 -1
- package/dist/storage/types.d.ts +4 -0
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts +2 -2
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +9 -3
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +20 -17
- 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/DocHandle.ts +10 -9
- package/src/RemoteHeadsSubscriptions.ts +375 -0
- package/src/Repo.ts +241 -16
- 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 +3 -1
- package/src/network/NetworkAdapter.ts +19 -2
- package/src/network/NetworkSubsystem.ts +21 -9
- package/src/network/messages.ts +88 -50
- package/src/storage/StorageSubsystem.ts +30 -7
- package/src/storage/keyHash.ts +2 -0
- package/src/storage/types.ts +3 -0
- package/src/synchronizer/CollectionSynchronizer.ts +13 -5
- package/src/synchronizer/DocSynchronizer.ts +27 -27
- package/src/synchronizer/Synchronizer.ts +13 -3
- package/test/DocHandle.test.ts +0 -17
- package/test/RemoteHeadsSubscriptions.test.ts +353 -0
- package/test/Repo.test.ts +108 -17
- package/test/StorageSubsystem.test.ts +29 -7
- package/test/helpers/waitForMessages.ts +22 -0
- package/test/remoteHeads.test.ts +260 -0
- package/.eslintrc +0 -28
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
import { PeerId } from "../types.js"
|
|
21
21
|
import { Synchronizer } from "./Synchronizer.js"
|
|
22
22
|
import { throttle } from "../helpers/throttle.js"
|
|
23
|
-
import { headsAreSame } from "../helpers/headsAreSame.js"
|
|
24
23
|
|
|
25
24
|
type PeerDocumentStatus = "unknown" | "has" | "unavailable" | "wants"
|
|
26
25
|
|
|
@@ -124,9 +123,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
#withSyncState(peerId: PeerId, callback: (syncState: A.SyncState) => void) {
|
|
127
|
-
|
|
128
|
-
this.#peers.push(peerId)
|
|
129
|
-
}
|
|
126
|
+
this.#addPeer(peerId)
|
|
130
127
|
|
|
131
128
|
if (!(peerId in this.#peerDocumentStatuses)) {
|
|
132
129
|
this.#peerDocumentStatuses[peerId] = "unknown"
|
|
@@ -140,15 +137,26 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
140
137
|
|
|
141
138
|
let pendingCallbacks = this.#pendingSyncStateCallbacks[peerId]
|
|
142
139
|
if (!pendingCallbacks) {
|
|
143
|
-
this.#onLoadSyncState(peerId)
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
this.#onLoadSyncState(peerId)
|
|
141
|
+
.then(syncState => {
|
|
142
|
+
this.#initSyncState(peerId, syncState ?? A.initSyncState())
|
|
143
|
+
})
|
|
144
|
+
.catch(err => {
|
|
145
|
+
this.#log(`Error loading sync state for ${peerId}: ${err}`)
|
|
146
|
+
})
|
|
146
147
|
pendingCallbacks = this.#pendingSyncStateCallbacks[peerId] = []
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
pendingCallbacks.push(callback)
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
#addPeer(peerId: PeerId) {
|
|
154
|
+
if (!this.#peers.includes(peerId)) {
|
|
155
|
+
this.#peers.push(peerId)
|
|
156
|
+
this.emit("open-doc", { documentId: this.documentId, peerId })
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
152
160
|
#initSyncState(peerId: PeerId, syncState: A.SyncState) {
|
|
153
161
|
const pendingCallbacks = this.#pendingSyncStateCallbacks[peerId]
|
|
154
162
|
if (pendingCallbacks) {
|
|
@@ -163,20 +171,8 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
#setSyncState(peerId: PeerId, syncState: A.SyncState) {
|
|
166
|
-
const previousSyncState = this.#syncStates[peerId]
|
|
167
|
-
|
|
168
174
|
this.#syncStates[peerId] = syncState
|
|
169
175
|
|
|
170
|
-
const haveTheirSyncedHeadsChanged =
|
|
171
|
-
syncState.theirHeads &&
|
|
172
|
-
(!previousSyncState ||
|
|
173
|
-
!previousSyncState.theirHeads ||
|
|
174
|
-
!headsAreSame(previousSyncState.theirHeads, syncState.theirHeads))
|
|
175
|
-
|
|
176
|
-
if (haveTheirSyncedHeadsChanged) {
|
|
177
|
-
this.#handle.setRemoteHeads(peerId, syncState.theirHeads)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
176
|
this.emit("sync-state", {
|
|
181
177
|
peerId,
|
|
182
178
|
syncState,
|
|
@@ -268,11 +264,15 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
268
264
|
)
|
|
269
265
|
this.#setSyncState(peerId, reparsedSyncState)
|
|
270
266
|
|
|
271
|
-
docPromise
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
267
|
+
docPromise
|
|
268
|
+
.then(doc => {
|
|
269
|
+
if (doc) {
|
|
270
|
+
this.#sendSyncMessage(peerId, doc)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
.catch(err => {
|
|
274
|
+
this.#log(`Error loading doc for ${peerId}: ${err}`)
|
|
275
|
+
})
|
|
276
276
|
})
|
|
277
277
|
})
|
|
278
278
|
}
|
|
@@ -334,10 +334,10 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
this.#processAllPendingSyncMessages()
|
|
337
|
-
this.#processSyncMessage(message
|
|
337
|
+
this.#processSyncMessage(message)
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
#processSyncMessage(message: SyncMessage | RequestMessage
|
|
340
|
+
#processSyncMessage(message: SyncMessage | RequestMessage) {
|
|
341
341
|
if (isRequestMessage(message)) {
|
|
342
342
|
this.#peerDocumentStatuses[message.senderId] = "wants"
|
|
343
343
|
}
|
|
@@ -396,7 +396,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
396
396
|
|
|
397
397
|
#processAllPendingSyncMessages() {
|
|
398
398
|
for (const message of this.#pendingSyncMessages) {
|
|
399
|
-
this.#processSyncMessage(message.message
|
|
399
|
+
this.#processSyncMessage(message.message)
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
this.#pendingSyncMessages = []
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3"
|
|
2
2
|
import {
|
|
3
3
|
MessageContents,
|
|
4
|
+
OpenDocMessage,
|
|
4
5
|
RepoMessage,
|
|
5
|
-
SyncStateMessage,
|
|
6
6
|
} from "../network/messages.js"
|
|
7
|
+
import { SyncState } from "@automerge/automerge"
|
|
8
|
+
import { PeerId, DocumentId } from "../types.js"
|
|
7
9
|
|
|
8
10
|
export abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
|
|
9
11
|
abstract receiveMessage(message: RepoMessage): void
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export interface SynchronizerEvents {
|
|
13
|
-
message: (
|
|
14
|
-
"sync-state": (
|
|
15
|
+
message: (payload: MessageContents) => void
|
|
16
|
+
"sync-state": (payload: SyncStatePayload) => void
|
|
17
|
+
"open-doc": (arg: OpenDocMessage) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Notify the repo that the sync state has changed */
|
|
21
|
+
export interface SyncStatePayload {
|
|
22
|
+
peerId: PeerId
|
|
23
|
+
documentId: DocumentId
|
|
24
|
+
syncState: SyncState
|
|
15
25
|
}
|
package/test/DocHandle.test.ts
CHANGED
|
@@ -303,23 +303,6 @@ describe("DocHandle", () => {
|
|
|
303
303
|
assert(wasBar, "foo should have been bar as we changed at the old heads")
|
|
304
304
|
})
|
|
305
305
|
|
|
306
|
-
it("should allow to listen for remote head changes and manually read remote heads", async () => {
|
|
307
|
-
const handle = new DocHandle<TestDoc>(TEST_ID, { isNew: true })
|
|
308
|
-
const bob = "bob" as PeerId
|
|
309
|
-
|
|
310
|
-
const remoteHeadsMessagePromise = eventPromise(handle, "remote-heads")
|
|
311
|
-
|
|
312
|
-
handle.setRemoteHeads(bob, [])
|
|
313
|
-
|
|
314
|
-
const remoteHeadsMessage = await remoteHeadsMessagePromise
|
|
315
|
-
|
|
316
|
-
assert.strictEqual(remoteHeadsMessage.peerId, bob)
|
|
317
|
-
assert.deepStrictEqual(remoteHeadsMessage.heads, [])
|
|
318
|
-
|
|
319
|
-
// read remote heads manually
|
|
320
|
-
assert.deepStrictEqual(handle.getRemoteHeads(bob), [])
|
|
321
|
-
})
|
|
322
|
-
|
|
323
306
|
describe("ephemeral messaging", () => {
|
|
324
307
|
it("can broadcast a message for the network to send out", async () => {
|
|
325
308
|
const handle = new DocHandle<TestDoc>(TEST_ID, { isNew: true })
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import * as A from "@automerge/automerge"
|
|
2
|
+
import assert from "assert"
|
|
3
|
+
import { describe, it } from "vitest"
|
|
4
|
+
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
5
|
+
import { RemoteHeadsSubscriptions } from "../src/RemoteHeadsSubscriptions.js"
|
|
6
|
+
import { PeerId, StorageId } from "../src/index.js"
|
|
7
|
+
import {
|
|
8
|
+
RemoteHeadsChanged,
|
|
9
|
+
RemoteSubscriptionControlMessage,
|
|
10
|
+
} from "../src/network/messages.js"
|
|
11
|
+
import { waitForMessages } from "./helpers/waitForMessages.js"
|
|
12
|
+
|
|
13
|
+
describe("RepoHeadsSubscriptions", () => {
|
|
14
|
+
const storageA = "remote-a" as StorageId
|
|
15
|
+
const storageB = "remote-b" as StorageId
|
|
16
|
+
const storageC = "remote-c" as StorageId
|
|
17
|
+
const storageD = "remote-d" as StorageId
|
|
18
|
+
const peerA = "peer-a" as PeerId
|
|
19
|
+
const peerB = "peer-b" as PeerId
|
|
20
|
+
const peerC = "peer-c" as PeerId
|
|
21
|
+
const peerD = "peer-d" as PeerId
|
|
22
|
+
|
|
23
|
+
const { documentId: docA } = parseAutomergeUrl(generateAutomergeUrl())
|
|
24
|
+
const { documentId: docB } = parseAutomergeUrl(generateAutomergeUrl())
|
|
25
|
+
const { documentId: docC } = parseAutomergeUrl(generateAutomergeUrl())
|
|
26
|
+
|
|
27
|
+
const docAHeadsChangedForStorageB: RemoteHeadsChanged = {
|
|
28
|
+
type: "remote-heads-changed",
|
|
29
|
+
senderId: peerD,
|
|
30
|
+
targetId: peerA,
|
|
31
|
+
documentId: docA,
|
|
32
|
+
newHeads: {
|
|
33
|
+
[storageB]: {
|
|
34
|
+
heads: [],
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const docBHeadsChangedForStorageB: RemoteHeadsChanged = {
|
|
41
|
+
type: "remote-heads-changed",
|
|
42
|
+
senderId: peerD,
|
|
43
|
+
targetId: peerA,
|
|
44
|
+
documentId: docB,
|
|
45
|
+
newHeads: {
|
|
46
|
+
[storageB]: {
|
|
47
|
+
heads: [],
|
|
48
|
+
timestamp: Date.now(),
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const docBHeads = A.getHeads(
|
|
54
|
+
A.change(A.init(), doc => {
|
|
55
|
+
;(doc as any).foo = "123"
|
|
56
|
+
})
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const docBHeadsChangedForStorageB2: RemoteHeadsChanged = {
|
|
60
|
+
type: "remote-heads-changed",
|
|
61
|
+
senderId: peerD,
|
|
62
|
+
targetId: peerA,
|
|
63
|
+
documentId: docB,
|
|
64
|
+
newHeads: {
|
|
65
|
+
[storageB]: {
|
|
66
|
+
heads: docBHeads,
|
|
67
|
+
timestamp: Date.now() + 1,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const subscribePeerCToStorageB: RemoteSubscriptionControlMessage = {
|
|
73
|
+
type: "remote-subscription-change",
|
|
74
|
+
senderId: peerC,
|
|
75
|
+
targetId: peerA,
|
|
76
|
+
add: [storageB],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const unsubscribePeerCFromStorageB: RemoteSubscriptionControlMessage = {
|
|
80
|
+
type: "remote-subscription-change",
|
|
81
|
+
senderId: peerC,
|
|
82
|
+
targetId: peerA,
|
|
83
|
+
remove: [storageB],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
it("should allow to subscribe and unsubscribe to storage ids", async () => {
|
|
87
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
88
|
+
|
|
89
|
+
const remoteHeadsMessages = waitForMessages(
|
|
90
|
+
remoteHeadsSubscriptions,
|
|
91
|
+
"remote-heads-changed"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
const changeRemoteSubsAfterSubscribe = waitForMessages(
|
|
95
|
+
remoteHeadsSubscriptions,
|
|
96
|
+
"change-remote-subs"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// subscribe to storageB and change storageB heads
|
|
100
|
+
remoteHeadsSubscriptions.subscribeToRemotes([storageB])
|
|
101
|
+
remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
|
|
102
|
+
|
|
103
|
+
// receive event for new heads of storageB
|
|
104
|
+
let messages = await remoteHeadsMessages
|
|
105
|
+
assert.strictEqual(messages.length, 1)
|
|
106
|
+
assert.strictEqual(messages[0].storageId, storageB)
|
|
107
|
+
assert.strictEqual(messages[0].documentId, docA)
|
|
108
|
+
assert.deepStrictEqual(messages[0].remoteHeads, [])
|
|
109
|
+
|
|
110
|
+
// receive event for add sub to storageB
|
|
111
|
+
messages = await changeRemoteSubsAfterSubscribe
|
|
112
|
+
assert.strictEqual(messages.length, 1)
|
|
113
|
+
assert.deepStrictEqual(messages[0].add, [storageB])
|
|
114
|
+
assert.deepStrictEqual(messages[0].remove, undefined)
|
|
115
|
+
assert.deepStrictEqual(messages[0].peers, [])
|
|
116
|
+
|
|
117
|
+
const remoteHeadsMessagesAfterUnsub = waitForMessages(
|
|
118
|
+
remoteHeadsSubscriptions,
|
|
119
|
+
"change-remote-subs"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// unsubscribe from storageB
|
|
123
|
+
remoteHeadsSubscriptions.unsubscribeFromRemotes([storageB])
|
|
124
|
+
|
|
125
|
+
// receive event for remove sub from storageB
|
|
126
|
+
messages = await remoteHeadsMessagesAfterUnsub
|
|
127
|
+
assert.strictEqual(messages.length, 1)
|
|
128
|
+
assert.deepStrictEqual(messages[0].add, undefined)
|
|
129
|
+
assert.deepStrictEqual(messages[0].remove, [storageB])
|
|
130
|
+
assert.deepStrictEqual(messages[0].peers, [])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("should forward all changes to generous peers", async () => {
|
|
134
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
135
|
+
|
|
136
|
+
const notifyRemoteHeadsMessagesPromise = waitForMessages(
|
|
137
|
+
remoteHeadsSubscriptions,
|
|
138
|
+
"notify-remote-heads"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
const changeRemoteSubsMessagesPromise = waitForMessages(
|
|
142
|
+
remoteHeadsSubscriptions,
|
|
143
|
+
"change-remote-subs"
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
remoteHeadsSubscriptions.addGenerousPeer(peerC)
|
|
147
|
+
remoteHeadsSubscriptions.subscribeToRemotes([storageB])
|
|
148
|
+
|
|
149
|
+
// change message for docA in storageB
|
|
150
|
+
remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
|
|
151
|
+
|
|
152
|
+
// change heads directly, are not forwarded
|
|
153
|
+
remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
|
|
154
|
+
docC,
|
|
155
|
+
storageB,
|
|
156
|
+
[]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// should forward remote-heads events
|
|
160
|
+
let messages = await notifyRemoteHeadsMessagesPromise
|
|
161
|
+
assert.strictEqual(messages.length, 1)
|
|
162
|
+
assert.strictEqual(messages[0].documentId, docA)
|
|
163
|
+
assert.strictEqual(messages[0].storageId, storageB)
|
|
164
|
+
assert.deepStrictEqual(messages[0].heads, [])
|
|
165
|
+
|
|
166
|
+
// should forward subscriptions to generous peer
|
|
167
|
+
messages = await changeRemoteSubsMessagesPromise
|
|
168
|
+
assert.strictEqual(messages.length, 1)
|
|
169
|
+
assert.deepStrictEqual(messages[0].add, [storageB])
|
|
170
|
+
assert.deepStrictEqual(messages[0].remove, undefined)
|
|
171
|
+
assert.deepStrictEqual(messages[0].peers, [peerC])
|
|
172
|
+
|
|
173
|
+
const changeRemoteSubsMessagesAfterUnsubPromise = waitForMessages(
|
|
174
|
+
remoteHeadsSubscriptions,
|
|
175
|
+
"change-remote-subs"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// unsubsscribe from storage B
|
|
179
|
+
remoteHeadsSubscriptions.unsubscribeFromRemotes([storageB])
|
|
180
|
+
|
|
181
|
+
// should forward unsubscribe to generous peer
|
|
182
|
+
messages = await changeRemoteSubsMessagesAfterUnsubPromise
|
|
183
|
+
assert.strictEqual(messages.length, 1)
|
|
184
|
+
assert.deepStrictEqual(messages[0].add, undefined)
|
|
185
|
+
assert.deepStrictEqual(messages[0].remove, [storageB])
|
|
186
|
+
assert.deepStrictEqual(messages[0].peers, [peerC])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("should not notify generous peers of changed remote heads, if they send the heads originally", async () => {
|
|
190
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
191
|
+
|
|
192
|
+
const messagesPromise = waitForMessages(
|
|
193
|
+
remoteHeadsSubscriptions,
|
|
194
|
+
"notify-remote-heads"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
remoteHeadsSubscriptions.addGenerousPeer(peerC)
|
|
198
|
+
remoteHeadsSubscriptions.subscribeToRemotes([storageB])
|
|
199
|
+
remoteHeadsSubscriptions.handleRemoteHeads({
|
|
200
|
+
type: "remote-heads-changed",
|
|
201
|
+
senderId: peerC,
|
|
202
|
+
targetId: peerA,
|
|
203
|
+
documentId: docA,
|
|
204
|
+
newHeads: {
|
|
205
|
+
[storageB]: {
|
|
206
|
+
heads: [],
|
|
207
|
+
timestamp: Date.now(),
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const messages = await messagesPromise
|
|
213
|
+
assert.strictEqual(messages.length, 0)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it("should allow peers to subscribe and unsubscribe to storageIds", async () => {
|
|
217
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
218
|
+
remoteHeadsSubscriptions.subscribeToRemotes([storageB])
|
|
219
|
+
|
|
220
|
+
// subscribe peer c to storage b
|
|
221
|
+
remoteHeadsSubscriptions.handleControlMessage(subscribePeerCToStorageB)
|
|
222
|
+
const messagesAfterSubscribePromise = waitForMessages(
|
|
223
|
+
remoteHeadsSubscriptions,
|
|
224
|
+
"notify-remote-heads"
|
|
225
|
+
)
|
|
226
|
+
remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docA)
|
|
227
|
+
remoteHeadsSubscriptions.subscribePeerToDoc(peerC, docC)
|
|
228
|
+
|
|
229
|
+
// change message for docA in storageB
|
|
230
|
+
remoteHeadsSubscriptions.handleRemoteHeads(docAHeadsChangedForStorageB)
|
|
231
|
+
|
|
232
|
+
// change heads directly
|
|
233
|
+
remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
|
|
234
|
+
docC,
|
|
235
|
+
storageB,
|
|
236
|
+
[]
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
// expect peer c to be notified both changes
|
|
240
|
+
let messages = await messagesAfterSubscribePromise
|
|
241
|
+
assert.strictEqual(messages.length, 2)
|
|
242
|
+
assert.strictEqual(messages[0].documentId, docA)
|
|
243
|
+
assert.strictEqual(messages[0].storageId, storageB)
|
|
244
|
+
assert.deepStrictEqual(messages[0].heads, [])
|
|
245
|
+
assert.strictEqual(messages[1].documentId, docC)
|
|
246
|
+
assert.strictEqual(messages[1].storageId, storageB)
|
|
247
|
+
assert.deepStrictEqual(messages[1].heads, [])
|
|
248
|
+
|
|
249
|
+
// unsubscribe peer C
|
|
250
|
+
remoteHeadsSubscriptions.handleControlMessage(unsubscribePeerCFromStorageB)
|
|
251
|
+
const messagesAfteUnsubscribePromise = waitForMessages(
|
|
252
|
+
remoteHeadsSubscriptions,
|
|
253
|
+
"notify-remote-heads"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
// heads of docB for storageB change
|
|
257
|
+
remoteHeadsSubscriptions.handleRemoteHeads(docBHeadsChangedForStorageB)
|
|
258
|
+
|
|
259
|
+
// expect not to be be notified
|
|
260
|
+
messages = await messagesAfteUnsubscribePromise
|
|
261
|
+
assert.strictEqual(messages.length, 0)
|
|
262
|
+
})
|
|
263
|
+
|
|
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 () => {
|
|
291
|
+
const remoteHeadsSubscription = new RemoteHeadsSubscriptions()
|
|
292
|
+
|
|
293
|
+
const messagesPromise = waitForMessages(
|
|
294
|
+
remoteHeadsSubscription,
|
|
295
|
+
"remote-heads-changed"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
remoteHeadsSubscription.subscribeToRemotes([storageB])
|
|
299
|
+
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
|
|
300
|
+
|
|
301
|
+
// send same message
|
|
302
|
+
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB2)
|
|
303
|
+
|
|
304
|
+
// send message with old heads
|
|
305
|
+
remoteHeadsSubscription.handleRemoteHeads(docBHeadsChangedForStorageB)
|
|
306
|
+
|
|
307
|
+
const messages = await messagesPromise
|
|
308
|
+
assert.strictEqual(messages.length, 1)
|
|
309
|
+
assert.strictEqual(messages[0].storageId, storageB)
|
|
310
|
+
assert.strictEqual(messages[0].documentId, docB)
|
|
311
|
+
assert.deepStrictEqual(messages[0].remoteHeads, docBHeads)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it("should remove subs of disconnected peers", async () => {
|
|
315
|
+
const remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
316
|
+
|
|
317
|
+
const messagesPromise = waitForMessages(
|
|
318
|
+
remoteHeadsSubscriptions,
|
|
319
|
+
"change-remote-subs"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
remoteHeadsSubscriptions.handleControlMessage({
|
|
323
|
+
type: "remote-subscription-change",
|
|
324
|
+
senderId: peerB,
|
|
325
|
+
targetId: peerA,
|
|
326
|
+
add: [storageA, storageC],
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
remoteHeadsSubscriptions.handleControlMessage({
|
|
330
|
+
type: "remote-subscription-change",
|
|
331
|
+
senderId: peerC,
|
|
332
|
+
targetId: peerA,
|
|
333
|
+
add: [storageA, storageD],
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
remoteHeadsSubscriptions.removePeer(peerB)
|
|
337
|
+
|
|
338
|
+
const messages = await messagesPromise
|
|
339
|
+
assert.deepStrictEqual(messages.length, 3)
|
|
340
|
+
|
|
341
|
+
assert.deepStrictEqual(messages[0].add, [storageA, storageC])
|
|
342
|
+
assert.deepStrictEqual(messages[0].remove, [])
|
|
343
|
+
assert.deepStrictEqual(messages[0].peers, [])
|
|
344
|
+
|
|
345
|
+
assert.deepStrictEqual(messages[1].add, [storageD])
|
|
346
|
+
assert.deepStrictEqual(messages[1].remove, [])
|
|
347
|
+
assert.deepStrictEqual(messages[1].peers, [])
|
|
348
|
+
|
|
349
|
+
assert.deepStrictEqual(messages[2].add, undefined)
|
|
350
|
+
assert.deepStrictEqual(messages[2].remove, [storageC])
|
|
351
|
+
assert.deepStrictEqual(messages[2].peers, [])
|
|
352
|
+
})
|
|
353
|
+
})
|