@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
package/dist/Repo.js
CHANGED
|
@@ -3,6 +3,8 @@ import debug from "debug";
|
|
|
3
3
|
import { EventEmitter } from "eventemitter3";
|
|
4
4
|
import { generateAutomergeUrl, interpretAsDocumentId, parseAutomergeUrl, } from "./AutomergeUrl.js";
|
|
5
5
|
import { DocHandle } from "./DocHandle.js";
|
|
6
|
+
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
|
|
7
|
+
import { headsAreSame } from "./helpers/headsAreSame.js";
|
|
6
8
|
import { throttle } from "./helpers/throttle.js";
|
|
7
9
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
8
10
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js";
|
|
@@ -29,8 +31,14 @@ export class Repo extends EventEmitter {
|
|
|
29
31
|
/** By default, we share generously with all peers. */
|
|
30
32
|
/** @hidden */
|
|
31
33
|
sharePolicy = async () => true;
|
|
32
|
-
|
|
34
|
+
/** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
|
|
35
|
+
/** @hidden */
|
|
36
|
+
peerMetadataByPeerId = {};
|
|
37
|
+
#remoteHeadsSubscriptions = new RemoteHeadsSubscriptions();
|
|
38
|
+
#remoteHeadsGossipingEnabled = false;
|
|
39
|
+
constructor({ storage, network, peerId, sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, }) {
|
|
33
40
|
super();
|
|
41
|
+
this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
|
|
34
42
|
this.#log = debug(`automerge-repo:repo`);
|
|
35
43
|
this.sharePolicy = sharePolicy ?? this.sharePolicy;
|
|
36
44
|
// DOC COLLECTION
|
|
@@ -42,7 +50,7 @@ export class Repo extends EventEmitter {
|
|
|
42
50
|
const saveFn = ({ handle, doc, }) => {
|
|
43
51
|
void storageSubsystem.saveDoc(handle.documentId, doc);
|
|
44
52
|
};
|
|
45
|
-
|
|
53
|
+
handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
|
|
46
54
|
if (isNew) {
|
|
47
55
|
// this is a new document, immediately save it
|
|
48
56
|
await storageSubsystem.saveDoc(handle.documentId, handle.docSync());
|
|
@@ -95,33 +103,137 @@ export class Repo extends EventEmitter {
|
|
|
95
103
|
this.#log(`sending ${message.type} message to ${message.targetId}`);
|
|
96
104
|
networkSubsystem.send(message);
|
|
97
105
|
});
|
|
106
|
+
if (this.#remoteHeadsGossipingEnabled) {
|
|
107
|
+
this.#synchronizer.on("open-doc", ({ peerId, documentId }) => {
|
|
108
|
+
this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
98
111
|
// STORAGE
|
|
99
112
|
// The storage subsystem has access to some form of persistence, and deals with save and loading documents.
|
|
100
113
|
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
|
|
101
114
|
this.storageSubsystem = storageSubsystem;
|
|
102
115
|
// NETWORK
|
|
103
116
|
// The network subsystem deals with sending and receiving messages to and from peers.
|
|
104
|
-
const
|
|
117
|
+
const myPeerMetadata = new Promise(
|
|
118
|
+
// eslint-disable-next-line no-async-promise-executor -- TODO: fix
|
|
119
|
+
async (resolve) => resolve({
|
|
120
|
+
storageId: await storageSubsystem?.id(),
|
|
121
|
+
isEphemeral,
|
|
122
|
+
}));
|
|
123
|
+
const networkSubsystem = new NetworkSubsystem(network, peerId, myPeerMetadata);
|
|
105
124
|
this.networkSubsystem = networkSubsystem;
|
|
106
125
|
// When we get a new peer, register it with the synchronizer
|
|
107
|
-
networkSubsystem.on("peer", async ({ peerId }) => {
|
|
126
|
+
networkSubsystem.on("peer", async ({ peerId, peerMetadata }) => {
|
|
108
127
|
this.#log("peer connected", { peerId });
|
|
128
|
+
if (peerMetadata) {
|
|
129
|
+
this.peerMetadataByPeerId[peerId] = { ...peerMetadata };
|
|
130
|
+
}
|
|
131
|
+
this.sharePolicy(peerId)
|
|
132
|
+
.then(shouldShare => {
|
|
133
|
+
if (shouldShare && this.#remoteHeadsGossipingEnabled) {
|
|
134
|
+
this.#remoteHeadsSubscriptions.addGenerousPeer(peerId);
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.catch(err => {
|
|
138
|
+
console.log("error in share policy", { err });
|
|
139
|
+
});
|
|
109
140
|
this.#synchronizer.addPeer(peerId);
|
|
110
141
|
});
|
|
111
142
|
// When a peer disconnects, remove it from the synchronizer
|
|
112
143
|
networkSubsystem.on("peer-disconnected", ({ peerId }) => {
|
|
113
144
|
this.#synchronizer.removePeer(peerId);
|
|
145
|
+
this.#remoteHeadsSubscriptions.removePeer(peerId);
|
|
114
146
|
});
|
|
115
147
|
// Handle incoming messages
|
|
116
148
|
networkSubsystem.on("message", async (msg) => {
|
|
117
|
-
|
|
149
|
+
this.#receiveMessage(msg);
|
|
118
150
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
151
|
+
this.#synchronizer.on("sync-state", message => {
|
|
152
|
+
this.#saveSyncState(message);
|
|
153
|
+
const handle = this.#handleCache[message.documentId];
|
|
154
|
+
const { storageId } = this.peerMetadataByPeerId[message.peerId] || {};
|
|
155
|
+
if (!storageId) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const heads = handle.getRemoteHeads(storageId);
|
|
159
|
+
const haveHeadsChanged = message.syncState.theirHeads &&
|
|
160
|
+
(!heads || !headsAreSame(heads, message.syncState.theirHeads));
|
|
161
|
+
if (haveHeadsChanged) {
|
|
162
|
+
handle.setRemoteHeads(storageId, message.syncState.theirHeads);
|
|
163
|
+
if (storageId && this.#remoteHeadsGossipingEnabled) {
|
|
164
|
+
this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(message.documentId, storageId, message.syncState.theirHeads);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
if (this.#remoteHeadsGossipingEnabled) {
|
|
169
|
+
this.#remoteHeadsSubscriptions.on("notify-remote-heads", message => {
|
|
170
|
+
this.networkSubsystem.send({
|
|
171
|
+
type: "remote-heads-changed",
|
|
172
|
+
targetId: message.targetId,
|
|
173
|
+
documentId: message.documentId,
|
|
174
|
+
newHeads: {
|
|
175
|
+
[message.storageId]: {
|
|
176
|
+
heads: message.heads,
|
|
177
|
+
timestamp: message.timestamp,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
this.#remoteHeadsSubscriptions.on("change-remote-subs", message => {
|
|
183
|
+
this.#log("change-remote-subs", message);
|
|
184
|
+
for (const peer of message.peers) {
|
|
185
|
+
this.networkSubsystem.send({
|
|
186
|
+
type: "remote-subscription-change",
|
|
187
|
+
targetId: peer,
|
|
188
|
+
add: message.add,
|
|
189
|
+
remove: message.remove,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
|
|
194
|
+
const handle = this.#handleCache[message.documentId];
|
|
195
|
+
handle.setRemoteHeads(message.storageId, message.remoteHeads);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
#receiveMessage(message) {
|
|
200
|
+
switch (message.type) {
|
|
201
|
+
case "remote-subscription-change":
|
|
202
|
+
if (this.#remoteHeadsGossipingEnabled) {
|
|
203
|
+
this.#remoteHeadsSubscriptions.handleControlMessage(message);
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
case "remote-heads-changed":
|
|
207
|
+
if (this.#remoteHeadsGossipingEnabled) {
|
|
208
|
+
this.#remoteHeadsSubscriptions.handleRemoteHeads(message);
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case "sync":
|
|
212
|
+
case "request":
|
|
213
|
+
case "ephemeral":
|
|
214
|
+
case "doc-unavailable":
|
|
215
|
+
this.#synchronizer.receiveMessage(message).catch(err => {
|
|
216
|
+
console.log("error receiving message", { err });
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
#throttledSaveSyncStateHandlers = {};
|
|
221
|
+
/** saves sync state throttled per storage id, if a peer doesn't have a storage id it's sync state is not persisted */
|
|
222
|
+
#saveSyncState(payload) {
|
|
223
|
+
if (!this.storageSubsystem) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const { storageId, isEphemeral } = this.peerMetadataByPeerId[payload.peerId] || {};
|
|
227
|
+
if (!storageId || isEphemeral) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let handler = this.#throttledSaveSyncStateHandlers[storageId];
|
|
231
|
+
if (!handler) {
|
|
232
|
+
handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(({ documentId, syncState }) => {
|
|
233
|
+
void this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
|
|
122
234
|
}, this.saveDebounceRate);
|
|
123
|
-
this.#synchronizer.on("sync-state", debouncedSaveSyncState);
|
|
124
235
|
}
|
|
236
|
+
handler(payload);
|
|
125
237
|
}
|
|
126
238
|
/** Returns an existing handle if we have it; creates one otherwise. */
|
|
127
239
|
#getHandle(
|
|
@@ -147,6 +259,9 @@ export class Repo extends EventEmitter {
|
|
|
147
259
|
get peers() {
|
|
148
260
|
return this.#synchronizer.peers;
|
|
149
261
|
}
|
|
262
|
+
getStorageIdOfPeer(peerId) {
|
|
263
|
+
return this.peerMetadataByPeerId[peerId]?.storageId;
|
|
264
|
+
}
|
|
150
265
|
/**
|
|
151
266
|
* Creates a new document and returns a handle to it. The initial value of the document is
|
|
152
267
|
* an empty object `{}`. Its documentId is generated by the system. we emit a `document` event
|
|
@@ -237,4 +352,48 @@ export class Repo extends EventEmitter {
|
|
|
237
352
|
delete this.#handleCache[documentId];
|
|
238
353
|
this.emit("delete-document", { documentId });
|
|
239
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Exports a document to a binary format.
|
|
357
|
+
* @param id - The url or documentId of the handle to export
|
|
358
|
+
*
|
|
359
|
+
* @returns Promise<Uint8Array | undefined> - A Promise containing the binary document,
|
|
360
|
+
* or undefined if the document is unavailable.
|
|
361
|
+
*/
|
|
362
|
+
async export(id) {
|
|
363
|
+
const documentId = interpretAsDocumentId(id);
|
|
364
|
+
const handle = this.#getHandle(documentId, false);
|
|
365
|
+
const doc = await handle.doc();
|
|
366
|
+
if (!doc)
|
|
367
|
+
return undefined;
|
|
368
|
+
return Automerge.save(doc);
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Imports document binary into the repo.
|
|
372
|
+
* @param binary - The binary to import
|
|
373
|
+
*/
|
|
374
|
+
import(binary) {
|
|
375
|
+
const doc = Automerge.load(binary);
|
|
376
|
+
const handle = this.create();
|
|
377
|
+
handle.update(() => {
|
|
378
|
+
return Automerge.clone(doc);
|
|
379
|
+
});
|
|
380
|
+
return handle;
|
|
381
|
+
}
|
|
382
|
+
subscribeToRemotes = (remotes) => {
|
|
383
|
+
if (this.#remoteHeadsGossipingEnabled) {
|
|
384
|
+
this.#log("subscribeToRemotes", { remotes });
|
|
385
|
+
this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
this.#log("WARN: subscribeToRemotes called but remote heads gossiping is not enabled");
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
storageId = async () => {
|
|
392
|
+
if (!this.storageSubsystem) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
return this.storageSubsystem.id();
|
|
397
|
+
}
|
|
398
|
+
};
|
|
240
399
|
}
|
package/dist/helpers/debounce.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK,+BAC4C,CAAA;AAE9D,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,CAAC,CAAC,CAOZ"}
|
package/dist/helpers/pause.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* c8 ignore start */
|
|
1
2
|
export const pause = (t = 0) => new Promise(resolve => setTimeout(() => resolve(), t));
|
|
2
3
|
export function rejectOnTimeout(promise, millis) {
|
|
3
4
|
return Promise.race([
|
|
@@ -7,3 +8,4 @@ export function rejectOnTimeout(promise, millis) {
|
|
|
7
8
|
}),
|
|
8
9
|
]);
|
|
9
10
|
}
|
|
11
|
+
/* c8 ignore end */
|
package/dist/helpers/throttle.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,eAAO,MAAM,WAAW,8BAEnB,MAAM,eAcV,CAAA;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* c8 ignore start */
|
|
1
2
|
/**
|
|
2
3
|
* If `promise` is resolved before `t` ms elapse, the timeout is cleared and the result of the
|
|
3
4
|
* promise is returned. If the timeout ends first, a `TimeoutError` is thrown.
|
|
@@ -20,3 +21,4 @@ export class TimeoutError extends Error {
|
|
|
20
21
|
this.name = "TimeoutError";
|
|
21
22
|
}
|
|
22
23
|
}
|
|
24
|
+
/* c8 ignore end */
|
package/dist/index.d.ts
CHANGED
|
@@ -29,14 +29,14 @@ export { DocHandle } from "./DocHandle.js";
|
|
|
29
29
|
export { isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "./AutomergeUrl.js";
|
|
30
30
|
export { Repo } from "./Repo.js";
|
|
31
31
|
export { NetworkAdapter } from "./network/NetworkAdapter.js";
|
|
32
|
-
export {
|
|
32
|
+
export { isRepoMessage } from "./network/messages.js";
|
|
33
33
|
export { StorageAdapter } from "./storage/StorageAdapter.js";
|
|
34
34
|
/** @hidden **/
|
|
35
35
|
export * as cbor from "./helpers/cbor.js";
|
|
36
36
|
export type { DocHandleChangePayload, DocHandleDeletePayload, DocHandleEncodedChangePayload, DocHandleEphemeralMessagePayload, DocHandleRemoteHeadsPayload, DocHandleEvents, DocHandleOptions, DocHandleOutboundEphemeralMessagePayload, HandleState, } from "./DocHandle.js";
|
|
37
37
|
export type { DeleteDocumentPayload, DocumentPayload, RepoConfig, RepoEvents, SharePolicy, } from "./Repo.js";
|
|
38
|
-
export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, } from "./network/NetworkAdapter.js";
|
|
38
|
+
export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, PeerMetadata, } from "./network/NetworkAdapter.js";
|
|
39
39
|
export type { DocumentUnavailableMessage, EphemeralMessage, Message, RepoMessage, RequestMessage, SyncMessage, } from "./network/messages.js";
|
|
40
|
-
export type { Chunk, ChunkInfo, ChunkType, StorageKey, } from "./storage/types.js";
|
|
40
|
+
export type { Chunk, ChunkInfo, ChunkType, StorageKey, StorageId, } from "./storage/types.js";
|
|
41
41
|
export * from "./types.js";
|
|
42
42
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAE5D,eAAe;AACf,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA;AAIzC,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,wCAAwC,EACxC,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,uBAAuB,EACvB,YAAY,GACb,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EACV,0BAA0B,EAC1B,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,YAAY,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAA;AAE3B,cAAc,YAAY,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { DocHandle } from "./DocHandle.js";
|
|
|
29
29
|
export { isValidAutomergeUrl, parseAutomergeUrl, stringifyAutomergeUrl, } from "./AutomergeUrl.js";
|
|
30
30
|
export { Repo } from "./Repo.js";
|
|
31
31
|
export { NetworkAdapter } from "./network/NetworkAdapter.js";
|
|
32
|
-
export {
|
|
32
|
+
export { isRepoMessage } from "./network/messages.js";
|
|
33
33
|
export { StorageAdapter } from "./storage/StorageAdapter.js";
|
|
34
34
|
/** @hidden **/
|
|
35
35
|
export * as cbor from "./helpers/cbor.js";
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { PeerId } from "../types.js";
|
|
3
3
|
import { Message } from "./messages.js";
|
|
4
|
+
import { StorageId } from "../storage/types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Describes a peer intent to the system
|
|
7
|
+
* storageId: the key for syncState to decide what the other peer already has
|
|
8
|
+
* isEphemeral: to decide if we bother recording this peer's sync state
|
|
9
|
+
*
|
|
10
|
+
*/
|
|
11
|
+
export interface PeerMetadata {
|
|
12
|
+
storageId?: StorageId;
|
|
13
|
+
isEphemeral?: boolean;
|
|
14
|
+
}
|
|
4
15
|
/** An interface representing some way to connect to other peers
|
|
5
16
|
*
|
|
6
17
|
* @remarks
|
|
@@ -10,11 +21,13 @@ import { Message } from "./messages.js";
|
|
|
10
21
|
*/
|
|
11
22
|
export declare abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
|
|
12
23
|
peerId?: PeerId;
|
|
24
|
+
peerMetadata?: PeerMetadata;
|
|
13
25
|
/** Called by the {@link Repo} to start the connection process
|
|
14
26
|
*
|
|
15
27
|
* @argument peerId - the peerId of this repo
|
|
28
|
+
* @argument peerMetadata - how this adapter should present itself to other peers
|
|
16
29
|
*/
|
|
17
|
-
abstract connect(peerId: PeerId): void;
|
|
30
|
+
abstract connect(peerId: PeerId, peerMetadata?: PeerMetadata): void;
|
|
18
31
|
/** Called by the {@link Repo} to send a message to a peer
|
|
19
32
|
*
|
|
20
33
|
* @argument message - the message to send
|
|
@@ -40,6 +53,7 @@ export interface OpenPayload {
|
|
|
40
53
|
}
|
|
41
54
|
export interface PeerCandidatePayload {
|
|
42
55
|
peerId: PeerId;
|
|
56
|
+
peerMetadata: PeerMetadata;
|
|
43
57
|
}
|
|
44
58
|
export interface PeerDisconnectedPayload {
|
|
45
59
|
peerId: PeerId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NetworkAdapter.d.ts","sourceRoot":"","sources":["../../src/network/NetworkAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAE/C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED;;;;;;GAMG;AACH,8BAAsB,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,YAAY,CAAA;IAE3B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI;IAEnE;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAErC,gEAAgE;IAChE,QAAQ,CAAC,UAAU,IAAI,IAAI;CAC5B;AAID,MAAM,WAAW,oBAAoB;IACnC,mDAAmD;IACnD,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IAErC,yCAAyC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAA;IAEjB,+DAA+D;IAC/D,gBAAgB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAEzD,2EAA2E;IAC3E,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAE/D,sEAAsE;IACtE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAA;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* c8 ignore start */
|
|
1
2
|
import { EventEmitter } from "eventemitter3";
|
|
2
3
|
/** An interface representing some way to connect to other peers
|
|
3
4
|
*
|
|
@@ -7,5 +8,6 @@ import { EventEmitter } from "eventemitter3";
|
|
|
7
8
|
* until the adapter emits a `ready` event before it starts trying to use it
|
|
8
9
|
*/
|
|
9
10
|
export class NetworkAdapter extends EventEmitter {
|
|
10
|
-
peerId;
|
|
11
|
+
peerId;
|
|
12
|
+
peerMetadata;
|
|
11
13
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { PeerId } from "../types.js";
|
|
3
|
-
import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js";
|
|
3
|
+
import type { NetworkAdapter, PeerDisconnectedPayload, PeerMetadata } from "./NetworkAdapter.js";
|
|
4
4
|
import { MessageContents, RepoMessage } from "./messages.js";
|
|
5
5
|
export declare class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
|
|
6
6
|
#private;
|
|
7
7
|
peerId: PeerId;
|
|
8
|
-
|
|
8
|
+
private peerMetadata;
|
|
9
|
+
constructor(adapters: NetworkAdapter[], peerId: PeerId, peerMetadata: Promise<PeerMetadata>);
|
|
9
10
|
addNetworkAdapter(networkAdapter: NetworkAdapter): void;
|
|
10
11
|
send(message: MessageContents): void;
|
|
11
12
|
isReady: () => boolean;
|
|
@@ -19,5 +20,6 @@ export interface NetworkSubsystemEvents {
|
|
|
19
20
|
}
|
|
20
21
|
export interface PeerPayload {
|
|
21
22
|
peerId: PeerId;
|
|
23
|
+
peerMetadata: PeerMetadata;
|
|
22
24
|
}
|
|
23
25
|
//# sourceMappingURL=NetworkSubsystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,
|
|
1
|
+
{"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EACvB,YAAY,EACb,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAY/D,MAAM;IACb,OAAO,CAAC,YAAY;gBAFpB,QAAQ,EAAE,cAAc,EAAE,EACnB,MAAM,QAAiB,EACtB,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;IAO7C,iBAAiB,CAAC,cAAc,EAAE,cAAc;IAyEhD,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,sBAUR;CACF;AAQD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACvC,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import debug from "debug";
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
|
-
import { isEphemeralMessage,
|
|
3
|
+
import { isEphemeralMessage, isRepoMessage, } from "./messages.js";
|
|
4
4
|
const getEphemeralMessageSource = (message) => `${message.senderId}:${message.sessionId}`;
|
|
5
5
|
export class NetworkSubsystem extends EventEmitter {
|
|
6
6
|
peerId;
|
|
7
|
+
peerMetadata;
|
|
7
8
|
#log;
|
|
8
9
|
#adaptersByPeer = {};
|
|
9
10
|
#count = 0;
|
|
@@ -11,9 +12,10 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
11
12
|
#ephemeralSessionCounts = {};
|
|
12
13
|
#readyAdapterCount = 0;
|
|
13
14
|
#adapters = [];
|
|
14
|
-
constructor(adapters, peerId = randomPeerId()) {
|
|
15
|
+
constructor(adapters, peerId = randomPeerId(), peerMetadata) {
|
|
15
16
|
super();
|
|
16
17
|
this.peerId = peerId;
|
|
18
|
+
this.peerMetadata = peerMetadata;
|
|
17
19
|
this.#log = debug(`automerge-repo:network:${this.peerId}`);
|
|
18
20
|
adapters.forEach(a => this.addNetworkAdapter(a));
|
|
19
21
|
}
|
|
@@ -26,14 +28,14 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
26
28
|
this.emit("ready");
|
|
27
29
|
}
|
|
28
30
|
});
|
|
29
|
-
networkAdapter.on("peer-candidate", ({ peerId }) => {
|
|
31
|
+
networkAdapter.on("peer-candidate", ({ peerId, peerMetadata }) => {
|
|
30
32
|
this.#log(`peer candidate: ${peerId} `);
|
|
31
33
|
// TODO: This is where authentication would happen
|
|
32
34
|
if (!this.#adaptersByPeer[peerId]) {
|
|
33
35
|
// TODO: handle losing a server here
|
|
34
36
|
this.#adaptersByPeer[peerId] = networkAdapter;
|
|
35
37
|
}
|
|
36
|
-
this.emit("peer", { peerId });
|
|
38
|
+
this.emit("peer", { peerId, peerMetadata });
|
|
37
39
|
});
|
|
38
40
|
networkAdapter.on("peer-disconnected", ({ peerId }) => {
|
|
39
41
|
this.#log(`peer disconnected: ${peerId} `);
|
|
@@ -41,7 +43,7 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
41
43
|
this.emit("peer-disconnected", { peerId });
|
|
42
44
|
});
|
|
43
45
|
networkAdapter.on("message", msg => {
|
|
44
|
-
if (!
|
|
46
|
+
if (!isRepoMessage(msg)) {
|
|
45
47
|
this.#log(`invalid message: ${JSON.stringify(msg)}`);
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
@@ -65,7 +67,11 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
69
|
});
|
|
68
|
-
|
|
70
|
+
this.peerMetadata.then(peerMetadata => {
|
|
71
|
+
networkAdapter.connect(this.peerId, peerMetadata);
|
|
72
|
+
}).catch(err => {
|
|
73
|
+
this.#log("error connecting to network", err);
|
|
74
|
+
});
|
|
69
75
|
}
|
|
70
76
|
send(message) {
|
|
71
77
|
const peer = this.#adaptersByPeer[message.targetId];
|
|
@@ -101,7 +107,7 @@ export class NetworkSubsystem extends EventEmitter {
|
|
|
101
107
|
}
|
|
102
108
|
};
|
|
103
109
|
const outbound = prepareMessage(message);
|
|
104
|
-
this.#log("sending message", outbound);
|
|
110
|
+
this.#log("sending message %o", outbound);
|
|
105
111
|
peer.send(outbound);
|
|
106
112
|
}
|
|
107
113
|
isReady = () => {
|