@automerge/automerge-repo 1.1.0-alpha.3 → 1.1.0-alpha.5
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/RemoteHeadsSubscriptions.d.ts +1 -0
- package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
- package/dist/RemoteHeadsSubscriptions.js +74 -14
- package/dist/Repo.d.ts +1 -0
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +6 -0
- package/dist/network/messages.d.ts +5 -0
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +1 -0
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +7 -3
- package/dist/synchronizer/Synchronizer.d.ts +2 -1
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/RemoteHeadsSubscriptions.ts +83 -14
- package/src/Repo.ts +8 -0
- package/src/network/messages.ts +24 -12
- package/src/synchronizer/CollectionSynchronizer.ts +1 -0
- package/src/synchronizer/DocSynchronizer.ts +8 -4
- package/src/synchronizer/Synchronizer.ts +2 -0
- package/test/RemoteHeadsSubscriptions.test.ts +30 -23
- package/test/helpers/waitForMessages.ts +22 -0
- package/test/remoteHeads.test.ts +192 -72
|
@@ -36,6 +36,7 @@ export declare class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSu
|
|
|
36
36
|
handleImmediateRemoteHeadsChanged(documentId: DocumentId, storageId: StorageId, heads: A.Heads): void;
|
|
37
37
|
addGenerousPeer(peerId: PeerId): void;
|
|
38
38
|
removePeer(peerId: PeerId): void;
|
|
39
|
+
subscribePeerToDoc(peerId: PeerId, documentId: DocumentId): void;
|
|
39
40
|
}
|
|
40
41
|
export {};
|
|
41
42
|
//# sourceMappingURL=RemoteHeadsSubscriptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;
|
|
1
|
+
{"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,CAAC,CAAC,KAAK,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,CAAC,CAAC,KAAK,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;IAcvF,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE;IAkBvC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE;IAsB3C,oBAAoB,CAAC,OAAO,EAAE,gCAAgC;IA0E9D,sEAAsE;IACtE,iBAAiB,CAAC,GAAG,EAAE,kBAAkB;IAgDzC,kEAAkE;IAClE,iCAAiC,CAC/B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,CAAC,CAAC,KAAK;IAgChB,eAAe,CAAC,MAAM,EAAE,MAAM;IAwB9B,UAAU,CAAC,MAAM,EAAE,MAAM;IA2BzB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU;CAoE1D"}
|
|
@@ -9,6 +9,8 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
9
9
|
#theirSubscriptions = new Map();
|
|
10
10
|
// Peers we will always share remote heads with even if they are not subscribed
|
|
11
11
|
#generousPeers = new Set();
|
|
12
|
+
// Documents each peer has open, we need this information so we only send remote heads of documents that the peer knows
|
|
13
|
+
#subscribedDocsByPeer = new Map();
|
|
12
14
|
#log = debug("automerge-repo:remote-heads-subscriptions");
|
|
13
15
|
subscribeToRemotes(remotes) {
|
|
14
16
|
this.#log("subscribeToRemotes", remotes);
|
|
@@ -47,10 +49,14 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
47
49
|
handleControlMessage(control) {
|
|
48
50
|
const remotesToAdd = [];
|
|
49
51
|
const remotesToRemove = [];
|
|
52
|
+
const addedRemotesWeKnow = [];
|
|
50
53
|
this.#log("handleControlMessage", control);
|
|
51
54
|
if (control.add) {
|
|
52
55
|
for (const remote of control.add) {
|
|
53
56
|
let theirSubs = this.#theirSubscriptions.get(remote);
|
|
57
|
+
if (this.#ourSubscriptions.has(remote) || theirSubs) {
|
|
58
|
+
addedRemotesWeKnow.push(remote);
|
|
59
|
+
}
|
|
54
60
|
if (!theirSubs) {
|
|
55
61
|
theirSubs = new Set();
|
|
56
62
|
this.#theirSubscriptions.set(remote, theirSubs);
|
|
@@ -80,6 +86,28 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
80
86
|
remove: remotesToRemove,
|
|
81
87
|
});
|
|
82
88
|
}
|
|
89
|
+
// send all our stored heads of documents the peer knows for the remotes they've added
|
|
90
|
+
for (const remote of addedRemotesWeKnow) {
|
|
91
|
+
const subscribedDocs = this.#subscribedDocsByPeer.get(control.senderId);
|
|
92
|
+
if (subscribedDocs) {
|
|
93
|
+
for (const documentId of subscribedDocs) {
|
|
94
|
+
const knownHeads = this.#knownHeads.get(documentId);
|
|
95
|
+
if (!knownHeads) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const lastHeads = knownHeads.get(remote);
|
|
99
|
+
if (lastHeads) {
|
|
100
|
+
this.emit("notify-remote-heads", {
|
|
101
|
+
targetId: control.senderId,
|
|
102
|
+
documentId,
|
|
103
|
+
heads: lastHeads.heads,
|
|
104
|
+
timestamp: lastHeads.timestamp,
|
|
105
|
+
storageId: remote,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
83
111
|
}
|
|
84
112
|
/** A peer we are not directly connected to has changed their heads */
|
|
85
113
|
handleRemoteHeads(msg) {
|
|
@@ -112,13 +140,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
112
140
|
const theirSubs = this.#theirSubscriptions.get(event.storageId);
|
|
113
141
|
if (theirSubs) {
|
|
114
142
|
for (const peerId of theirSubs) {
|
|
115
|
-
this
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
if (this.#isPeerSubscribedToDoc(peerId, event.documentId)) {
|
|
144
|
+
this.emit("notify-remote-heads", {
|
|
145
|
+
targetId: peerId,
|
|
146
|
+
documentId: event.documentId,
|
|
147
|
+
heads: event.remoteHeads,
|
|
148
|
+
timestamp: event.timestamp,
|
|
149
|
+
storageId: event.storageId,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
122
152
|
}
|
|
123
153
|
}
|
|
124
154
|
}
|
|
@@ -140,13 +170,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
140
170
|
const theirSubs = this.#theirSubscriptions.get(storageId);
|
|
141
171
|
if (theirSubs) {
|
|
142
172
|
for (const peerId of theirSubs) {
|
|
143
|
-
this
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
173
|
+
if (this.#isPeerSubscribedToDoc(peerId, documentId)) {
|
|
174
|
+
this.emit("notify-remote-heads", {
|
|
175
|
+
targetId: peerId,
|
|
176
|
+
documentId: documentId,
|
|
177
|
+
heads: heads,
|
|
178
|
+
timestamp: timestamp,
|
|
179
|
+
storageId: storageId,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
150
182
|
}
|
|
151
183
|
}
|
|
152
184
|
}
|
|
@@ -175,6 +207,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
175
207
|
this.#log("removePeer", peerId);
|
|
176
208
|
const remotesToRemove = [];
|
|
177
209
|
this.#generousPeers.delete(peerId);
|
|
210
|
+
this.#subscribedDocsByPeer.delete(peerId);
|
|
178
211
|
for (const [storageId, peerIds] of this.#theirSubscriptions) {
|
|
179
212
|
if (peerIds.has(peerId)) {
|
|
180
213
|
peerIds.delete(peerId);
|
|
@@ -191,6 +224,33 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
|
|
|
191
224
|
});
|
|
192
225
|
}
|
|
193
226
|
}
|
|
227
|
+
subscribePeerToDoc(peerId, documentId) {
|
|
228
|
+
let subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
|
|
229
|
+
if (!subscribedDocs) {
|
|
230
|
+
subscribedDocs = new Set();
|
|
231
|
+
this.#subscribedDocsByPeer.set(peerId, subscribedDocs);
|
|
232
|
+
}
|
|
233
|
+
subscribedDocs.add(documentId);
|
|
234
|
+
const remoteHeads = this.#knownHeads.get(documentId);
|
|
235
|
+
if (remoteHeads) {
|
|
236
|
+
for (const [storageId, lastHeads] of remoteHeads) {
|
|
237
|
+
const subscribedPeers = this.#theirSubscriptions.get(storageId);
|
|
238
|
+
if (subscribedPeers && subscribedPeers.has(peerId)) {
|
|
239
|
+
this.emit("notify-remote-heads", {
|
|
240
|
+
targetId: peerId,
|
|
241
|
+
documentId,
|
|
242
|
+
heads: lastHeads.heads,
|
|
243
|
+
timestamp: lastHeads.timestamp,
|
|
244
|
+
storageId,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
#isPeerSubscribedToDoc(peerId, documentId) {
|
|
251
|
+
let subscribedDocs = this.#subscribedDocsByPeer.get(peerId);
|
|
252
|
+
return subscribedDocs && subscribedDocs.has(documentId);
|
|
253
|
+
}
|
|
194
254
|
/** Returns the (document, storageId) pairs which have changed after processing msg */
|
|
195
255
|
#changedHeads(msg) {
|
|
196
256
|
const changedHeads = [];
|
package/dist/Repo.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export declare class Repo extends EventEmitter<RepoEvents> {
|
|
|
34
34
|
get handles(): Record<DocumentId, DocHandle<any>>;
|
|
35
35
|
/** Returns a list of all connected peer ids */
|
|
36
36
|
get peers(): PeerId[];
|
|
37
|
+
getStorageIdOfPeer(peerId: PeerId): StorageId | undefined;
|
|
37
38
|
/**
|
|
38
39
|
* Creates a new document and returns a handle to it. The initial value of the document is
|
|
39
40
|
* an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,OAAO,EAAE,SAAS,EAAiC,MAAM,gBAAgB,CAAA;AAEzE,OAAO,EAAE,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAI9C,8FAA8F;AAC9F;;;;;;GAMG;AACH,qBAAa,IAAK,SAAQ,YAAY,CAAC,UAAU,CAAC;;IAGhD,cAAc;IACd,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc;IACd,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,mDAAmD;IACnD,cAAc;IACd,gBAAgB,SAAM;IAMtB,sDAAsD;IACtD,cAAc;IACd,WAAW,EAAE,WAAW,CAAmB;IAE3C,8GAA8G;IAC9G,cAAc;IACd,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAK;gBAI3C,EACV,OAAO,EACP,OAAO,EACP,MAAM,EACN,WAAW,EACX,WAAmC,GACpC,EAAE,UAAU;
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
|
package/dist/Repo.js
CHANGED
|
@@ -101,6 +101,9 @@ export class Repo extends EventEmitter {
|
|
|
101
101
|
this.#log(`sending ${message.type} message to ${message.targetId}`);
|
|
102
102
|
networkSubsystem.send(message);
|
|
103
103
|
});
|
|
104
|
+
this.#synchronizer.on("open-doc", ({ peerId, documentId }) => {
|
|
105
|
+
this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
|
|
106
|
+
});
|
|
104
107
|
// STORAGE
|
|
105
108
|
// The storage subsystem has access to some form of persistence, and deals with save and loading documents.
|
|
106
109
|
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
|
|
@@ -244,6 +247,9 @@ export class Repo extends EventEmitter {
|
|
|
244
247
|
get peers() {
|
|
245
248
|
return this.#synchronizer.peers;
|
|
246
249
|
}
|
|
250
|
+
getStorageIdOfPeer(peerId) {
|
|
251
|
+
return this.peerMetadataByPeerId[peerId]?.storageId;
|
|
252
|
+
}
|
|
247
253
|
/**
|
|
248
254
|
* Creates a new document and returns a handle to it. The initial value of the document is
|
|
249
255
|
* an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
|
|
@@ -111,6 +111,11 @@ export interface SyncStateMessage {
|
|
|
111
111
|
documentId: DocumentId;
|
|
112
112
|
syncState: SyncState;
|
|
113
113
|
}
|
|
114
|
+
/** Notify the repo that a peer started syncing with a doc */
|
|
115
|
+
export interface OpenDocMessage {
|
|
116
|
+
peerId: PeerId;
|
|
117
|
+
documentId: DocumentId;
|
|
118
|
+
}
|
|
114
119
|
export declare const isValidRepoMessage: (message: Message) => message is RepoMessage;
|
|
115
120
|
export declare const isDocumentUnavailableMessage: (msg: Message) => msg is DocumentUnavailableMessage;
|
|
116
121
|
export declare const isRequestMessage: (msg: Message) => msg is RequestMessage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IAEZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,iCAAiC;IACjC,IAAI,EAAE,UAAU,CAAA;IAEhB,0DAA0D;IAC1D,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;;;;;KASK;AACL,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,WAAW,CAAA;IAEjB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IAEb,8GAA8G;IAC9G,SAAS,EAAE,SAAS,CAAA;IAEpB,+CAA+C;IAC/C,UAAU,EAAE,UAAU,CAAA;IAEtB,qCAAqC;IACrC,IAAI,EAAE,UAAU,CAAA;CACjB,CAAA;AAED,uHAAuH;AACvH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE,iBAAiB,CAAA;IAEvB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yDAAyD;IACzD,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED;;;;;KAKK;AACL,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAA;IAEf,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yCAAyC;IACzC,IAAI,EAAE,UAAU,CAAA;IAEhB,4CAA4C;IAC5C,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA;AAED,sCAAsC;AACtC,MAAM,MAAM,WAAW,CAAC,QAAQ,GAAG,GAAG,IAAI;IACxC,IAAI,EAAE,MAAM,CAAA;IAEZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;IAEhB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAA;IAEhB,yEAAyE;IACzE,OAAO,EAAE,QAAQ,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,gCAAgC,GAAG;IAC7C,IAAI,EAAE,4BAA4B,
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAe,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;
|
|
1
|
+
{"version":3,"file":"CollectionSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/CollectionSynchronizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACjC,OAAO,EAAE,UAAU,EAAe,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIhD,4FAA4F;AAC5F,qBAAa,sBAAuB,SAAQ,YAAY;;IAU1C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAqD9B;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,UAAU;IAyBxC;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,UAAU;IAYlC,cAAc,CAAC,UAAU,EAAE,UAAU;IAIrC,2DAA2D;IAC3D,OAAO,CAAC,MAAM,EAAE,MAAM;IAgBtB,uDAAuD;IACvD,UAAU,CAAC,MAAM,EAAE,MAAM;IASzB,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;CACF"}
|
|
@@ -40,6 +40,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
40
40
|
},
|
|
41
41
|
});
|
|
42
42
|
docSynchronizer.on("message", event => this.emit("message", event));
|
|
43
|
+
docSynchronizer.on("open-doc", event => this.emit("open-doc", event));
|
|
43
44
|
docSynchronizer.on("sync-state", event => this.emit("sync-state", event));
|
|
44
45
|
return docSynchronizer;
|
|
45
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAG9C,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,GAAG,SAAS,CAAA;CAC9D;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAsBV,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyB9D,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IA8HD,OAAO,CAAC,MAAM,EAAE,MAAM;IAItB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA+C3B,OAAO,CAAC,MAAM,EAAE,MAAM;IAKtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;CA8EzD"}
|
|
@@ -66,9 +66,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
66
66
|
this.emit("message", message);
|
|
67
67
|
}
|
|
68
68
|
#withSyncState(peerId, callback) {
|
|
69
|
-
|
|
70
|
-
this.#peers.push(peerId);
|
|
71
|
-
}
|
|
69
|
+
this.#addPeer(peerId);
|
|
72
70
|
if (!(peerId in this.#peerDocumentStatuses)) {
|
|
73
71
|
this.#peerDocumentStatuses[peerId] = "unknown";
|
|
74
72
|
}
|
|
@@ -86,6 +84,12 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
86
84
|
}
|
|
87
85
|
pendingCallbacks.push(callback);
|
|
88
86
|
}
|
|
87
|
+
#addPeer(peerId) {
|
|
88
|
+
if (!this.#peers.includes(peerId)) {
|
|
89
|
+
this.#peers.push(peerId);
|
|
90
|
+
this.emit("open-doc", { documentId: this.documentId, peerId });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
89
93
|
#initSyncState(peerId, syncState) {
|
|
90
94
|
const pendingCallbacks = this.#pendingSyncStateCallbacks[peerId];
|
|
91
95
|
if (pendingCallbacks) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
|
-
import { MessageContents, RepoMessage, SyncStateMessage } from "../network/messages.js";
|
|
2
|
+
import { MessageContents, OpenDocMessage, RepoMessage, SyncStateMessage } from "../network/messages.js";
|
|
3
3
|
export declare abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
|
|
4
4
|
abstract receiveMessage(message: RepoMessage): void;
|
|
5
5
|
}
|
|
6
6
|
export interface SynchronizerEvents {
|
|
7
7
|
message: (arg: MessageContents) => void;
|
|
8
8
|
"sync-state": (arg: SyncStateMessage) => void;
|
|
9
|
+
"open-doc": (arg: OpenDocMessage) => void;
|
|
9
10
|
}
|
|
10
11
|
//# sourceMappingURL=Synchronizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EACjB,MAAM,wBAAwB,CAAA;AAE/B,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACvC,YAAY,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACX,gBAAgB,EACjB,MAAM,wBAAwB,CAAA;AAE/B,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACvC,YAAY,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAA;IAC7C,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CAC1C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "1.1.0-alpha.
|
|
3
|
+
"version": "1.1.0-alpha.5",
|
|
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>",
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
"crypto": false
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"http-server": "^14.1.0"
|
|
22
|
+
"http-server": "^14.1.0",
|
|
23
|
+
"vite": "^4.4.11"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@automerge/automerge": "^2.1.8
|
|
26
|
+
"@automerge/automerge": "^2.1.8",
|
|
26
27
|
"bs58check": "^3.0.1",
|
|
27
28
|
"cbor-x": "^1.3.0",
|
|
28
29
|
"debug": "^4.3.4",
|
|
@@ -54,5 +55,5 @@
|
|
|
54
55
|
"publishConfig": {
|
|
55
56
|
"access": "public"
|
|
56
57
|
},
|
|
57
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "78e8cd7e0a6dae17c1bb293afcceaa40fcf02639"
|
|
58
59
|
}
|
|
@@ -44,6 +44,9 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
44
44
|
#theirSubscriptions: Map<StorageId, Set<PeerId>> = new Map()
|
|
45
45
|
// Peers we will always share remote heads with even if they are not subscribed
|
|
46
46
|
#generousPeers: Set<PeerId> = new Set()
|
|
47
|
+
// Documents each peer has open, we need this information so we only send remote heads of documents that the peer knows
|
|
48
|
+
#subscribedDocsByPeer: Map<PeerId, Set<DocumentId>> = new Map()
|
|
49
|
+
|
|
47
50
|
#log = debug("automerge-repo:remote-heads-subscriptions")
|
|
48
51
|
|
|
49
52
|
subscribeToRemotes(remotes: StorageId[]) {
|
|
@@ -89,11 +92,17 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
89
92
|
handleControlMessage(control: RemoteSubscriptionControlMessage) {
|
|
90
93
|
const remotesToAdd: StorageId[] = []
|
|
91
94
|
const remotesToRemove: StorageId[] = []
|
|
95
|
+
const addedRemotesWeKnow: StorageId[] = []
|
|
92
96
|
|
|
93
97
|
this.#log("handleControlMessage", control)
|
|
94
98
|
if (control.add) {
|
|
95
99
|
for (const remote of control.add) {
|
|
96
100
|
let theirSubs = this.#theirSubscriptions.get(remote)
|
|
101
|
+
|
|
102
|
+
if (this.#ourSubscriptions.has(remote) || theirSubs) {
|
|
103
|
+
addedRemotesWeKnow.push(remote)
|
|
104
|
+
}
|
|
105
|
+
|
|
97
106
|
if (!theirSubs) {
|
|
98
107
|
theirSubs = new Set()
|
|
99
108
|
this.#theirSubscriptions.set(remote, theirSubs)
|
|
@@ -128,6 +137,30 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
128
137
|
remove: remotesToRemove,
|
|
129
138
|
})
|
|
130
139
|
}
|
|
140
|
+
|
|
141
|
+
// send all our stored heads of documents the peer knows for the remotes they've added
|
|
142
|
+
for (const remote of addedRemotesWeKnow) {
|
|
143
|
+
const subscribedDocs = this.#subscribedDocsByPeer.get(control.senderId)
|
|
144
|
+
if (subscribedDocs) {
|
|
145
|
+
for (const documentId of subscribedDocs) {
|
|
146
|
+
const knownHeads = this.#knownHeads.get(documentId)
|
|
147
|
+
if (!knownHeads) {
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const lastHeads = knownHeads.get(remote)
|
|
152
|
+
if (lastHeads) {
|
|
153
|
+
this.emit("notify-remote-heads", {
|
|
154
|
+
targetId: control.senderId,
|
|
155
|
+
documentId,
|
|
156
|
+
heads: lastHeads.heads,
|
|
157
|
+
timestamp: lastHeads.timestamp,
|
|
158
|
+
storageId: remote,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
131
164
|
}
|
|
132
165
|
|
|
133
166
|
/** A peer we are not directly connected to has changed their heads */
|
|
@@ -165,13 +198,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
165
198
|
const theirSubs = this.#theirSubscriptions.get(event.storageId)
|
|
166
199
|
if (theirSubs) {
|
|
167
200
|
for (const peerId of theirSubs) {
|
|
168
|
-
this
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
201
|
+
if (this.#isPeerSubscribedToDoc(peerId, event.documentId)) {
|
|
202
|
+
this.emit("notify-remote-heads", {
|
|
203
|
+
targetId: peerId,
|
|
204
|
+
documentId: event.documentId,
|
|
205
|
+
heads: event.remoteHeads,
|
|
206
|
+
timestamp: event.timestamp,
|
|
207
|
+
storageId: event.storageId,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
175
210
|
}
|
|
176
211
|
}
|
|
177
212
|
}
|
|
@@ -200,13 +235,15 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
200
235
|
const theirSubs = this.#theirSubscriptions.get(storageId)
|
|
201
236
|
if (theirSubs) {
|
|
202
237
|
for (const peerId of theirSubs) {
|
|
203
|
-
this
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
if (this.#isPeerSubscribedToDoc(peerId, documentId)) {
|
|
239
|
+
this.emit("notify-remote-heads", {
|
|
240
|
+
targetId: peerId,
|
|
241
|
+
documentId: documentId,
|
|
242
|
+
heads: heads,
|
|
243
|
+
timestamp: timestamp,
|
|
244
|
+
storageId: storageId,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
210
247
|
}
|
|
211
248
|
}
|
|
212
249
|
}
|
|
@@ -241,6 +278,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
241
278
|
const remotesToRemove = []
|
|
242
279
|
|
|
243
280
|
this.#generousPeers.delete(peerId)
|
|
281
|
+
this.#subscribedDocsByPeer.delete(peerId)
|
|
244
282
|
|
|
245
283
|
for (const [storageId, peerIds] of this.#theirSubscriptions) {
|
|
246
284
|
if (peerIds.has(peerId)) {
|
|
@@ -261,6 +299,37 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
|
|
|
261
299
|
}
|
|
262
300
|
}
|
|
263
301
|
|
|
302
|
+
subscribePeerToDoc(peerId: PeerId, documentId: DocumentId) {
|
|
303
|
+
let subscribedDocs = this.#subscribedDocsByPeer.get(peerId)
|
|
304
|
+
if (!subscribedDocs) {
|
|
305
|
+
subscribedDocs = new Set()
|
|
306
|
+
this.#subscribedDocsByPeer.set(peerId, subscribedDocs)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
subscribedDocs.add(documentId)
|
|
310
|
+
|
|
311
|
+
const remoteHeads = this.#knownHeads.get(documentId)
|
|
312
|
+
if (remoteHeads) {
|
|
313
|
+
for (const [storageId, lastHeads] of remoteHeads) {
|
|
314
|
+
const subscribedPeers = this.#theirSubscriptions.get(storageId)
|
|
315
|
+
if (subscribedPeers && subscribedPeers.has(peerId)) {
|
|
316
|
+
this.emit("notify-remote-heads", {
|
|
317
|
+
targetId: peerId,
|
|
318
|
+
documentId,
|
|
319
|
+
heads: lastHeads.heads,
|
|
320
|
+
timestamp: lastHeads.timestamp,
|
|
321
|
+
storageId,
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#isPeerSubscribedToDoc(peerId: PeerId, documentId: DocumentId) {
|
|
329
|
+
let subscribedDocs = this.#subscribedDocsByPeer.get(peerId)
|
|
330
|
+
return subscribedDocs && subscribedDocs.has(documentId)
|
|
331
|
+
}
|
|
332
|
+
|
|
264
333
|
/** Returns the (document, storageId) pairs which have changed after processing msg */
|
|
265
334
|
#changedHeads(msg: RemoteHeadsChanged): {
|
|
266
335
|
documentId: DocumentId
|
package/src/Repo.ts
CHANGED
|
@@ -140,6 +140,10 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
140
140
|
networkSubsystem.send(message)
|
|
141
141
|
})
|
|
142
142
|
|
|
143
|
+
this.#synchronizer.on("open-doc", ({ peerId, documentId }) => {
|
|
144
|
+
this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId)
|
|
145
|
+
})
|
|
146
|
+
|
|
143
147
|
// STORAGE
|
|
144
148
|
// The storage subsystem has access to some form of persistence, and deals with save and loading documents.
|
|
145
149
|
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined
|
|
@@ -332,6 +336,10 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
332
336
|
return this.#synchronizer.peers
|
|
333
337
|
}
|
|
334
338
|
|
|
339
|
+
getStorageIdOfPeer(peerId: PeerId): StorageId | undefined {
|
|
340
|
+
return this.peerMetadataByPeerId[peerId]?.storageId
|
|
341
|
+
}
|
|
342
|
+
|
|
335
343
|
/**
|
|
336
344
|
* Creates a new document and returns a handle to it. The initial value of the document is
|
|
337
345
|
* an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
|
package/src/network/messages.ts
CHANGED
|
@@ -104,19 +104,19 @@ export type AuthMessage<TPayload = any> = {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export type RemoteSubscriptionControlMessage = {
|
|
107
|
-
type: "remote-subscription-change"
|
|
108
|
-
senderId: PeerId
|
|
109
|
-
targetId: PeerId
|
|
110
|
-
add?: StorageId[]
|
|
111
|
-
remove?: StorageId[]
|
|
107
|
+
type: "remote-subscription-change"
|
|
108
|
+
senderId: PeerId
|
|
109
|
+
targetId: PeerId
|
|
110
|
+
add?: StorageId[]
|
|
111
|
+
remove?: StorageId[]
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export type RemoteHeadsChanged = {
|
|
115
|
-
type: "remote-heads-changed"
|
|
116
|
-
senderId: PeerId
|
|
117
|
-
targetId: PeerId
|
|
118
|
-
documentId: DocumentId
|
|
119
|
-
newHeads: {[key: StorageId]: {heads: string[]
|
|
115
|
+
type: "remote-heads-changed"
|
|
116
|
+
senderId: PeerId
|
|
117
|
+
targetId: PeerId
|
|
118
|
+
documentId: DocumentId
|
|
119
|
+
newHeads: { [key: StorageId]: { heads: string[]; timestamp: number } }
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/** These are message types that a {@link NetworkAdapter} surfaces to a {@link Repo}. */
|
|
@@ -128,7 +128,11 @@ export type RepoMessage =
|
|
|
128
128
|
| RemoteSubscriptionControlMessage
|
|
129
129
|
| RemoteHeadsChanged
|
|
130
130
|
|
|
131
|
-
export type DocMessage =
|
|
131
|
+
export type DocMessage =
|
|
132
|
+
| SyncMessage
|
|
133
|
+
| EphemeralMessage
|
|
134
|
+
| RequestMessage
|
|
135
|
+
| DocumentUnavailableMessage
|
|
132
136
|
|
|
133
137
|
/** These are all the message types that a {@link NetworkAdapter} might see. */
|
|
134
138
|
export type Message = RepoMessage | AuthMessage
|
|
@@ -148,6 +152,12 @@ export interface SyncStateMessage {
|
|
|
148
152
|
syncState: SyncState
|
|
149
153
|
}
|
|
150
154
|
|
|
155
|
+
/** Notify the repo that a peer started syncing with a doc */
|
|
156
|
+
export interface OpenDocMessage {
|
|
157
|
+
peerId: PeerId
|
|
158
|
+
documentId: DocumentId
|
|
159
|
+
}
|
|
160
|
+
|
|
151
161
|
// TYPE GUARDS
|
|
152
162
|
|
|
153
163
|
export const isValidRepoMessage = (message: Message): message is RepoMessage =>
|
|
@@ -174,7 +184,9 @@ export const isSyncMessage = (msg: Message): msg is SyncMessage =>
|
|
|
174
184
|
export const isEphemeralMessage = (msg: Message): msg is EphemeralMessage =>
|
|
175
185
|
msg.type === "ephemeral"
|
|
176
186
|
|
|
177
|
-
export const isRemoteSubscriptionControlMessage = (
|
|
187
|
+
export const isRemoteSubscriptionControlMessage = (
|
|
188
|
+
msg: Message
|
|
189
|
+
): msg is RemoteSubscriptionControlMessage =>
|
|
178
190
|
msg.type === "remote-subscription-change"
|
|
179
191
|
|
|
180
192
|
export const isRemoteHeadsChanged = (msg: Message): msg is RemoteHeadsChanged =>
|
|
@@ -55,6 +55,7 @@ export class CollectionSynchronizer extends Synchronizer {
|
|
|
55
55
|
},
|
|
56
56
|
})
|
|
57
57
|
docSynchronizer.on("message", event => this.emit("message", event))
|
|
58
|
+
docSynchronizer.on("open-doc", event => this.emit("open-doc", event))
|
|
58
59
|
docSynchronizer.on("sync-state", event => this.emit("sync-state", event))
|
|
59
60
|
return docSynchronizer
|
|
60
61
|
}
|
|
@@ -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"
|
|
@@ -149,6 +146,13 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
149
146
|
pendingCallbacks.push(callback)
|
|
150
147
|
}
|
|
151
148
|
|
|
149
|
+
#addPeer(peerId: PeerId) {
|
|
150
|
+
if (!this.#peers.includes(peerId)) {
|
|
151
|
+
this.#peers.push(peerId)
|
|
152
|
+
this.emit("open-doc", { documentId: this.documentId, peerId })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
152
156
|
#initSyncState(peerId: PeerId, syncState: A.SyncState) {
|
|
153
157
|
const pendingCallbacks = this.#pendingSyncStateCallbacks[peerId]
|
|
154
158
|
if (pendingCallbacks) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3"
|
|
2
2
|
import {
|
|
3
3
|
MessageContents,
|
|
4
|
+
OpenDocMessage,
|
|
4
5
|
RepoMessage,
|
|
5
6
|
SyncStateMessage,
|
|
6
7
|
} from "../network/messages.js"
|
|
@@ -12,4 +13,5 @@ export abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
|
|
|
12
13
|
export interface SynchronizerEvents {
|
|
13
14
|
message: (arg: MessageContents) => void
|
|
14
15
|
"sync-state": (arg: SyncStateMessage) => void
|
|
16
|
+
"open-doc": (arg: OpenDocMessage) => void
|
|
15
17
|
}
|
|
@@ -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,6 +261,32 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
260
261
|
assert.strictEqual(messages.length, 0)
|
|
261
262
|
})
|
|
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
|
+
|
|
263
290
|
it("should ignore sync states with an older timestamp", async () => {
|
|
264
291
|
const remoteHeadsSubscription = new RemoteHeadsSubscriptions()
|
|
265
292
|
|
|
@@ -321,23 +348,3 @@ describe("RepoHeadsSubscriptions", () => {
|
|
|
321
348
|
assert.deepStrictEqual(messages[2].peers, [])
|
|
322
349
|
})
|
|
323
350
|
})
|
|
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
|
-
}
|
|
@@ -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/automerge-repo-network-messagechannel"
|
|
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
|
|
@@ -40,86 +39,207 @@ describe("DocHandle.remoteHeads", () => {
|
|
|
40
39
|
assert.deepStrictEqual(handle.getRemoteHeads(bobStorageId), [])
|
|
41
40
|
})
|
|
42
41
|
|
|
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
|
-
|
|
42
|
+
describe("multi hop sync", () => {
|
|
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,
|
|
47
|
+
network: [],
|
|
48
|
+
sharePolicy: async () => true,
|
|
49
|
+
})
|
|
50
|
+
const leftTab2 = new Repo({
|
|
51
|
+
peerId: "left-tab-2" as PeerId,
|
|
52
|
+
network: [],
|
|
53
|
+
sharePolicy: async () => true,
|
|
54
|
+
})
|
|
55
|
+
const leftServiceWorker = new Repo({
|
|
56
|
+
peerId: "left-service-worker" as PeerId,
|
|
57
|
+
network: [],
|
|
58
|
+
sharePolicy: async peer => peer === "sync-server",
|
|
59
|
+
storage: new DummyStorageAdapter(),
|
|
60
|
+
isEphemeral: false,
|
|
61
|
+
})
|
|
62
|
+
const syncServer = new Repo({
|
|
63
|
+
peerId: "sync-server" as PeerId,
|
|
64
|
+
network: [],
|
|
65
|
+
isEphemeral: false,
|
|
66
|
+
sharePolicy: async () => false,
|
|
67
|
+
storage: new DummyStorageAdapter(),
|
|
68
|
+
})
|
|
69
|
+
const rightServiceWorker = new Repo({
|
|
70
|
+
peerId: "right-service-worker" as PeerId,
|
|
71
|
+
network: [],
|
|
72
|
+
sharePolicy: async peer => peer === "sync-server",
|
|
73
|
+
isEphemeral: false,
|
|
74
|
+
storage: new DummyStorageAdapter(),
|
|
75
|
+
})
|
|
76
|
+
const rightTab = new Repo({
|
|
77
|
+
peerId: "right-tab" as PeerId,
|
|
78
|
+
network: [],
|
|
79
|
+
sharePolicy: async () => true,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// 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)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
leftTab1,
|
|
93
|
+
leftTab2,
|
|
94
|
+
leftServiceWorker,
|
|
95
|
+
syncServer,
|
|
96
|
+
rightServiceWorker,
|
|
97
|
+
rightTab,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
it("should report remoteHeads for peers", async () => {
|
|
102
|
+
const { rightTab, rightServiceWorker, leftServiceWorker, leftTab1 } =
|
|
103
|
+
await setup()
|
|
104
|
+
|
|
105
|
+
// subscribe to the left service worker storage ID on the right tab
|
|
106
|
+
rightTab.subscribeToRemotes([await leftServiceWorker.storageId()!])
|
|
107
|
+
|
|
108
|
+
await setTimeout(100)
|
|
109
|
+
|
|
110
|
+
// create a doc in the left tab
|
|
111
|
+
const leftTabDoc = leftTab1.create<TestDoc>()
|
|
112
|
+
leftTabDoc.change(d => (d.foo = "bar"))
|
|
113
|
+
|
|
114
|
+
// wait for the document to arrive on the right tab
|
|
115
|
+
const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
|
|
116
|
+
await rightTabDoc.whenReady()
|
|
117
|
+
|
|
118
|
+
// wait for the document to arrive in the left service worker
|
|
119
|
+
const leftServiceWorkerDoc = leftServiceWorker.find(leftTabDoc.documentId)
|
|
120
|
+
await leftServiceWorkerDoc.whenReady()
|
|
121
|
+
|
|
122
|
+
const leftServiceWorkerStorageId = await leftServiceWorker.storageId()
|
|
123
|
+
let leftSeenByRightPromise = new Promise<DocHandleRemoteHeadsPayload>(
|
|
124
|
+
resolve => {
|
|
125
|
+
rightTabDoc.on("remote-heads", message => {
|
|
126
|
+
if (message.storageId === leftServiceWorkerStorageId) {
|
|
127
|
+
resolve(message)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// make a change on the right
|
|
134
|
+
rightTabDoc.change(d => (d.foo = "baz"))
|
|
135
|
+
|
|
136
|
+
// wait for the change to be acknolwedged by the left
|
|
137
|
+
const leftSeenByRight = await leftSeenByRightPromise
|
|
138
|
+
|
|
139
|
+
assert.deepStrictEqual(
|
|
140
|
+
leftSeenByRight.heads,
|
|
141
|
+
A.getHeads(leftServiceWorkerDoc.docSync())
|
|
142
|
+
)
|
|
70
143
|
})
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
144
|
+
|
|
145
|
+
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()!])
|
|
150
|
+
|
|
151
|
+
await setTimeout(100)
|
|
152
|
+
|
|
153
|
+
// create 2 docs in right tab
|
|
154
|
+
const rightTabDocA = rightTab.create<TestDoc>()
|
|
155
|
+
rightTabDocA.change(d => (d.foo = "A"))
|
|
156
|
+
|
|
157
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
158
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
159
|
+
|
|
160
|
+
// open doc b in left tab 1
|
|
161
|
+
const leftTabDocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
162
|
+
|
|
163
|
+
const remoteHeadsChangedMessages = (
|
|
164
|
+
await waitForMessages(leftTab1.networkSubsystem, "message")
|
|
165
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
166
|
+
|
|
167
|
+
// 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
|
|
172
|
+
)
|
|
75
173
|
})
|
|
76
174
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
connectRepos(leftServiceWorker, syncServer)
|
|
80
|
-
connectRepos(syncServer, rightServiceWorker)
|
|
81
|
-
connectRepos(rightServiceWorker, rightTab)
|
|
175
|
+
it("should report remote heads for doc on subscribe if peer already knows them", async () => {
|
|
176
|
+
const { leftTab1, leftTab2, rightTab, rightServiceWorker } = await setup()
|
|
82
177
|
|
|
83
|
-
|
|
178
|
+
// create 2 docs in right tab
|
|
179
|
+
const rightTabDocA = rightTab.create<TestDoc>()
|
|
180
|
+
rightTabDocA.change(d => (d.foo = "A"))
|
|
84
181
|
|
|
85
|
-
|
|
86
|
-
|
|
182
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
183
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
87
184
|
|
|
88
|
-
|
|
185
|
+
// open docs in left tab 1
|
|
186
|
+
const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
187
|
+
const leftTab1DocB = leftTab1.find<TestDoc>(rightTabDocB.url)
|
|
89
188
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
leftTabDoc.change(d => (d.foo = "bar"))
|
|
189
|
+
// subscribe leftTab 1 to storageId of rightServiceWorker
|
|
190
|
+
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
93
191
|
|
|
94
|
-
|
|
95
|
-
const rightTabDoc = rightTab.find<TestDoc>(leftTabDoc.url)
|
|
96
|
-
await rightTabDoc.whenReady()
|
|
192
|
+
await setTimeout(200)
|
|
97
193
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await leftServiceWorkerDoc.whenReady()
|
|
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
|
|
101
196
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
)
|
|
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()!])
|
|
200
|
+
|
|
201
|
+
const remoteHeadsChangedMessages = (
|
|
202
|
+
await waitForMessages(leftTab2.networkSubsystem, "message")
|
|
203
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
112
204
|
|
|
113
|
-
|
|
114
|
-
|
|
205
|
+
// 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
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
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"))
|
|
219
|
+
|
|
220
|
+
const rightTabDocB = rightTab.create<TestDoc>()
|
|
221
|
+
rightTabDocB.change(d => (d.foo = "B"))
|
|
222
|
+
|
|
223
|
+
await setTimeout(200)
|
|
115
224
|
|
|
116
|
-
|
|
117
|
-
|
|
225
|
+
// subscribe leftTab 1 to storageId of rightServiceWorker
|
|
226
|
+
leftTab1.subscribeToRemotes([await rightServiceWorker.storageId()!])
|
|
118
227
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
228
|
+
// in leftTab 1 open doc A
|
|
229
|
+
const leftTab1DocA = leftTab1.find<TestDoc>(rightTabDocA.url)
|
|
230
|
+
|
|
231
|
+
const remoteHeadsChangedMessages = (
|
|
232
|
+
await waitForMessages(leftTab1.networkSubsystem, "message")
|
|
233
|
+
).filter(({ type }) => type === "remote-heads-changed")
|
|
234
|
+
|
|
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
|
|
241
|
+
)
|
|
242
|
+
})
|
|
123
243
|
})
|
|
124
244
|
})
|
|
125
245
|
|