@automerge/automerge-repo 1.0.18 → 1.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/DocHandle.d.ts +7 -8
  2. package/dist/DocHandle.d.ts.map +1 -1
  3. package/dist/DocHandle.js +15 -20
  4. package/dist/RemoteHeadsSubscriptions.d.ts +41 -0
  5. package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -0
  6. package/dist/RemoteHeadsSubscriptions.js +224 -0
  7. package/dist/Repo.d.ts +15 -1
  8. package/dist/Repo.d.ts.map +1 -1
  9. package/dist/Repo.js +118 -8
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/network/NetworkAdapter.d.ts +8 -1
  13. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  14. package/dist/network/NetworkAdapter.js +2 -0
  15. package/dist/network/NetworkSubsystem.d.ts +7 -1
  16. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  17. package/dist/network/NetworkSubsystem.js +11 -4
  18. package/dist/network/messages.d.ts +24 -1
  19. package/dist/network/messages.d.ts.map +1 -1
  20. package/dist/network/messages.js +5 -1
  21. package/dist/storage/StorageSubsystem.d.ts +5 -3
  22. package/dist/storage/StorageSubsystem.d.ts.map +1 -1
  23. package/dist/storage/StorageSubsystem.js +17 -4
  24. package/dist/storage/types.d.ts +4 -0
  25. package/dist/storage/types.d.ts.map +1 -1
  26. package/dist/synchronizer/CollectionSynchronizer.d.ts +2 -2
  27. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  28. package/dist/synchronizer/CollectionSynchronizer.js +7 -3
  29. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  30. package/dist/synchronizer/DocSynchronizer.js +0 -9
  31. package/package.json +3 -3
  32. package/src/DocHandle.ts +17 -22
  33. package/src/RemoteHeadsSubscriptions.ts +306 -0
  34. package/src/Repo.ts +173 -11
  35. package/src/index.ts +1 -0
  36. package/src/network/NetworkAdapter.ts +12 -1
  37. package/src/network/NetworkSubsystem.ts +24 -11
  38. package/src/network/messages.ts +30 -1
  39. package/src/storage/StorageSubsystem.ts +24 -6
  40. package/src/storage/types.ts +3 -0
  41. package/src/synchronizer/CollectionSynchronizer.ts +10 -5
  42. package/src/synchronizer/DocSynchronizer.ts +0 -12
  43. package/test/DocHandle.test.ts +1 -18
  44. package/test/RemoteHeadsSubscriptions.test.ts +343 -0
  45. package/test/Repo.test.ts +51 -15
  46. package/test/StorageSubsystem.test.ts +28 -6
  47. package/test/remoteHeads.test.ts +135 -0
@@ -0,0 +1,306 @@
1
+ import { next as A } from "@automerge/automerge"
2
+ import { EventEmitter } from "eventemitter3"
3
+ import { DocumentId, PeerId } from "./types.js"
4
+ import {
5
+ RemoteHeadsChanged,
6
+ RemoteSubscriptionControlMessage,
7
+ } from "./network/messages.js"
8
+ import { StorageId } from "./index.js"
9
+ import debug from "debug"
10
+
11
+ // Notify a DocHandle that remote heads have changed
12
+ export type RemoteHeadsSubscriptionEventPayload = {
13
+ documentId: DocumentId
14
+ storageId: StorageId
15
+ remoteHeads: A.Heads
16
+ timestamp: number
17
+ }
18
+
19
+ // Send a message to the given peer notifying them of new heads
20
+ export type NotifyRemoteHeadsPayload = {
21
+ targetId: PeerId
22
+ documentId: DocumentId
23
+ storageId: StorageId
24
+ heads: A.Heads
25
+ timestamp: number
26
+ }
27
+
28
+ type RemoteHeadsSubscriptionEvents = {
29
+ "remote-heads-changed": (payload: RemoteHeadsSubscriptionEventPayload) => void
30
+ "change-remote-subs": (payload: {
31
+ peers: PeerId[]
32
+ add?: StorageId[]
33
+ remove?: StorageId[]
34
+ }) => void
35
+ "notify-remote-heads": (payload: NotifyRemoteHeadsPayload) => void
36
+ }
37
+
38
+ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscriptionEvents> {
39
+ // Storage IDs we have received remote heads from
40
+ #knownHeads: Map<DocumentId, Map<StorageId, LastHeads>> = new Map()
41
+ // Storage IDs we have subscribed to via Repo.subscribeToRemoteHeads
42
+ #ourSubscriptions: Set<StorageId> = new Set()
43
+ // Storage IDs other peers have subscribed to by sending us a control message
44
+ #theirSubscriptions: Map<StorageId, Set<PeerId>> = new Map()
45
+ // Peers we will always share remote heads with even if they are not subscribed
46
+ #generousPeers: Set<PeerId> = new Set()
47
+ #log = debug("automerge-repo:remote-heads-subscriptions")
48
+
49
+ subscribeToRemotes(remotes: StorageId[]) {
50
+ this.#log("subscribeToRemotes", remotes)
51
+ const remotesToAdd = []
52
+ for (const remote of remotes) {
53
+ if (!this.#ourSubscriptions.has(remote)) {
54
+ this.#ourSubscriptions.add(remote)
55
+ remotesToAdd.push(remote)
56
+ }
57
+ }
58
+
59
+ if (remotesToAdd.length > 0) {
60
+ this.emit("change-remote-subs", {
61
+ add: remotesToAdd,
62
+ peers: Array.from(this.#generousPeers),
63
+ })
64
+ }
65
+ }
66
+
67
+ unsubscribeFromRemotes(remotes: StorageId[]) {
68
+ this.#log("subscribeToRemotes", remotes)
69
+ const remotesToRemove = []
70
+
71
+ for (const remote of remotes) {
72
+ if (this.#ourSubscriptions.has(remote)) {
73
+ this.#ourSubscriptions.delete(remote)
74
+
75
+ if (!this.#theirSubscriptions.has(remote)) {
76
+ remotesToRemove.push(remote)
77
+ }
78
+ }
79
+ }
80
+
81
+ if (remotesToRemove.length > 0) {
82
+ this.emit("change-remote-subs", {
83
+ remove: remotesToRemove,
84
+ peers: Array.from(this.#generousPeers),
85
+ })
86
+ }
87
+ }
88
+
89
+ handleControlMessage(control: RemoteSubscriptionControlMessage) {
90
+ const remotesToAdd: StorageId[] = []
91
+ const remotesToRemove: StorageId[] = []
92
+
93
+ this.#log("handleControlMessage", control)
94
+ if (control.add) {
95
+ for (const remote of control.add) {
96
+ let theirSubs = this.#theirSubscriptions.get(remote)
97
+ if (!theirSubs) {
98
+ theirSubs = new Set()
99
+ this.#theirSubscriptions.set(remote, theirSubs)
100
+
101
+ if (!this.#ourSubscriptions.has(remote)) {
102
+ remotesToAdd.push(remote)
103
+ }
104
+ }
105
+
106
+ theirSubs.add(control.senderId)
107
+ }
108
+ }
109
+
110
+ if (control.remove) {
111
+ for (const remote of control.remove) {
112
+ const theirSubs = this.#theirSubscriptions.get(remote)
113
+ if (theirSubs) {
114
+ theirSubs.delete(control.senderId)
115
+
116
+ // if no one is subscribed anymore remove remote
117
+ if (theirSubs.size == 0 && !this.#ourSubscriptions.has(remote)) {
118
+ remotesToRemove.push(remote)
119
+ }
120
+ }
121
+ }
122
+ }
123
+
124
+ if (remotesToAdd.length > 0 || remotesToRemove.length > 0) {
125
+ this.emit("change-remote-subs", {
126
+ peers: Array.from(this.#generousPeers),
127
+ add: remotesToAdd,
128
+ remove: remotesToRemove,
129
+ })
130
+ }
131
+ }
132
+
133
+ /** A peer we are not directly connected to has changed their heads */
134
+ handleRemoteHeads(msg: RemoteHeadsChanged) {
135
+ this.#log("handleRemoteHeads", msg)
136
+ const changedHeads = this.#changedHeads(msg)
137
+
138
+ // Emit a remote-heads-changed event to update local dochandles
139
+ for (const event of changedHeads) {
140
+ if (this.#ourSubscriptions.has(event.storageId)) {
141
+ this.emit("remote-heads-changed", event)
142
+ }
143
+ }
144
+
145
+ // Notify generous peers of these changes regardless of if they are subscribed to us
146
+ for (const event of changedHeads) {
147
+ for (const peer of this.#generousPeers) {
148
+ // don't emit event to sender if sender is a generous peer
149
+ if (peer === msg.senderId) {
150
+ continue
151
+ }
152
+
153
+ this.emit("notify-remote-heads", {
154
+ targetId: peer,
155
+ documentId: event.documentId,
156
+ heads: event.remoteHeads,
157
+ timestamp: event.timestamp,
158
+ storageId: event.storageId,
159
+ })
160
+ }
161
+ }
162
+
163
+ // Notify subscribers of these changes
164
+ for (const event of changedHeads) {
165
+ const theirSubs = this.#theirSubscriptions.get(event.storageId)
166
+ if (theirSubs) {
167
+ for (const peerId of theirSubs) {
168
+ this.emit("notify-remote-heads", {
169
+ targetId: peerId,
170
+ documentId: event.documentId,
171
+ heads: event.remoteHeads,
172
+ timestamp: event.timestamp,
173
+ storageId: event.storageId,
174
+ })
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ /** A peer we are directly connected to has updated their heads */
181
+ handleImmediateRemoteHeadsChanged(
182
+ documentId: DocumentId,
183
+ storageId: StorageId,
184
+ heads: A.Heads
185
+ ) {
186
+ this.#log("handleLocalHeadsChanged", documentId, storageId, heads)
187
+ const remote = this.#knownHeads.get(documentId)
188
+ const timestamp = Date.now()
189
+ if (!remote) {
190
+ this.#knownHeads.set(
191
+ documentId,
192
+ new Map([[storageId, { heads, timestamp }]])
193
+ )
194
+ } else {
195
+ const docRemote = remote.get(storageId)
196
+ if (!docRemote || docRemote.timestamp < Date.now()) {
197
+ remote.set(storageId, { heads, timestamp: Date.now() })
198
+ }
199
+ }
200
+ const theirSubs = this.#theirSubscriptions.get(storageId)
201
+ if (theirSubs) {
202
+ for (const peerId of theirSubs) {
203
+ this.emit("notify-remote-heads", {
204
+ targetId: peerId,
205
+ documentId: documentId,
206
+ heads: heads,
207
+ timestamp: timestamp,
208
+ storageId: storageId,
209
+ })
210
+ }
211
+ }
212
+ }
213
+
214
+ addGenerousPeer(peerId: PeerId) {
215
+ this.#log("addGenerousPeer", peerId)
216
+ this.#generousPeers.add(peerId)
217
+
218
+ if (this.#ourSubscriptions.size > 0) {
219
+ this.emit("change-remote-subs", {
220
+ add: Array.from(this.#ourSubscriptions),
221
+ peers: [peerId],
222
+ })
223
+ }
224
+
225
+ for (const [documentId, remote] of this.#knownHeads) {
226
+ for (const [storageId, { heads, timestamp }] of remote) {
227
+ this.emit("notify-remote-heads", {
228
+ targetId: peerId,
229
+ documentId: documentId,
230
+ heads: heads,
231
+ timestamp: timestamp,
232
+ storageId: storageId,
233
+ })
234
+ }
235
+ }
236
+ }
237
+
238
+ removePeer(peerId: PeerId) {
239
+ this.#log("removePeer", peerId)
240
+
241
+ const remotesToRemove = []
242
+
243
+ this.#generousPeers.delete(peerId)
244
+
245
+ for (const [storageId, peerIds] of this.#theirSubscriptions) {
246
+ if (peerIds.has(peerId)) {
247
+ peerIds.delete(peerId)
248
+
249
+ if (peerIds.size == 0) {
250
+ remotesToRemove.push(storageId)
251
+ this.#theirSubscriptions.delete(storageId)
252
+ }
253
+ }
254
+ }
255
+
256
+ if (remotesToRemove.length > 0) {
257
+ this.emit("change-remote-subs", {
258
+ remove: remotesToRemove,
259
+ peers: Array.from(this.#generousPeers),
260
+ })
261
+ }
262
+ }
263
+
264
+ /** Returns the (document, storageId) pairs which have changed after processing msg */
265
+ #changedHeads(msg: RemoteHeadsChanged): {
266
+ documentId: DocumentId
267
+ storageId: StorageId
268
+ remoteHeads: A.Heads
269
+ timestamp: number
270
+ }[] {
271
+ const changedHeads = []
272
+ const { documentId, newHeads } = msg
273
+ for (const [storageId, { heads, timestamp }] of Object.entries(newHeads)) {
274
+ if (
275
+ !this.#ourSubscriptions.has(storageId as StorageId) &&
276
+ !this.#theirSubscriptions.has(storageId as StorageId)
277
+ ) {
278
+ continue
279
+ }
280
+ let remote = this.#knownHeads.get(documentId)
281
+ if (!remote) {
282
+ remote = new Map([[storageId as StorageId, { heads, timestamp }]])
283
+ this.#knownHeads.set(documentId, remote)
284
+ }
285
+
286
+ const docRemote = remote.get(storageId as StorageId)
287
+ if (docRemote && docRemote.timestamp > timestamp) {
288
+ continue
289
+ } else {
290
+ remote.set(storageId as StorageId, { timestamp, heads })
291
+ changedHeads.push({
292
+ documentId,
293
+ storageId: storageId as StorageId,
294
+ remoteHeads: heads,
295
+ timestamp,
296
+ })
297
+ }
298
+ }
299
+ return changedHeads
300
+ }
301
+ }
302
+
303
+ type LastHeads = {
304
+ timestamp: number
305
+ heads: A.Heads
306
+ }
package/src/Repo.ts CHANGED
@@ -14,7 +14,10 @@ import { StorageAdapter } from "./storage/StorageAdapter.js"
14
14
  import { StorageSubsystem } from "./storage/StorageSubsystem.js"
15
15
  import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
16
16
  import type { AnyDocumentId, DocumentId, PeerId } from "./types.js"
17
- import { SyncStateMessage } from "./network/messages.js"
17
+ import { RepoMessage, SyncStateMessage } from "./network/messages.js"
18
+ import { StorageId } from "./storage/types.js"
19
+ import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js"
20
+ import { headsAreSame } from "./helpers/headsAreSame.js"
18
21
 
19
22
  /** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
20
23
  /** The `Repo` is the main entry point of this library
@@ -44,7 +47,19 @@ export class Repo extends EventEmitter<RepoEvents> {
44
47
  /** @hidden */
45
48
  sharePolicy: SharePolicy = async () => true
46
49
 
47
- constructor({ storage, network, peerId, sharePolicy }: RepoConfig) {
50
+ /** maps peer id to to persistance information (storageId, isEphemeral), access by collection synchronizer */
51
+ /** @hidden */
52
+ persistanceInfoByPeerId: Record<PeerId, PersistanceInfo> = {}
53
+
54
+ #remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
55
+
56
+ constructor({
57
+ storage,
58
+ network,
59
+ peerId,
60
+ sharePolicy,
61
+ isEphemeral = storage === undefined,
62
+ }: RepoConfig) {
48
63
  super()
49
64
  this.#log = debug(`automerge-repo:repo`)
50
65
  this.sharePolicy = sharePolicy ?? this.sharePolicy
@@ -132,33 +147,158 @@ export class Repo extends EventEmitter<RepoEvents> {
132
147
 
133
148
  // NETWORK
134
149
  // The network subsystem deals with sending and receiving messages to and from peers.
135
- const networkSubsystem = new NetworkSubsystem(network, peerId)
150
+ const networkSubsystem = new NetworkSubsystem(
151
+ network,
152
+ peerId,
153
+ storageSubsystem?.id() ?? Promise.resolve(undefined),
154
+ isEphemeral
155
+ )
136
156
  this.networkSubsystem = networkSubsystem
137
157
 
138
158
  // When we get a new peer, register it with the synchronizer
139
- networkSubsystem.on("peer", async ({ peerId }) => {
159
+ networkSubsystem.on("peer", async ({ peerId, storageId, isEphemeral }) => {
140
160
  this.#log("peer connected", { peerId })
161
+
162
+ if (storageId) {
163
+ this.persistanceInfoByPeerId[peerId] = {
164
+ storageId,
165
+ isEphemeral,
166
+ }
167
+ }
168
+
169
+ this.sharePolicy(peerId)
170
+ .then(shouldShare => {
171
+ if (shouldShare) {
172
+ this.#remoteHeadsSubscriptions.addGenerousPeer(peerId)
173
+ }
174
+ })
175
+ .catch(err => {
176
+ console.log("error in share policy", { err })
177
+ })
178
+
141
179
  this.#synchronizer.addPeer(peerId)
142
180
  })
143
181
 
144
182
  // When a peer disconnects, remove it from the synchronizer
145
183
  networkSubsystem.on("peer-disconnected", ({ peerId }) => {
146
184
  this.#synchronizer.removePeer(peerId)
185
+ this.#remoteHeadsSubscriptions.removePeer(peerId)
147
186
  })
148
187
 
149
188
  // Handle incoming messages
150
189
  networkSubsystem.on("message", async msg => {
151
- await this.#synchronizer.receiveMessage(msg)
190
+ this.#receiveMessage(msg)
152
191
  })
153
192
 
154
- if (storageSubsystem) {
155
- const debouncedSaveSyncState: (syncState: SyncStateMessage) => void =
156
- throttle(({ documentId, peerId, syncState }: SyncStateMessage) => {
157
- storageSubsystem.saveSyncState(documentId, peerId, syncState)
158
- }, this.saveDebounceRate)
193
+ this.#synchronizer.on("sync-state", message => {
194
+ this.#saveSyncState(message)
195
+
196
+ const handle = this.#handleCache[message.documentId]
197
+
198
+ const info = this.persistanceInfoByPeerId[message.peerId]
199
+ if (!info) {
200
+ return
201
+ }
202
+
203
+ const { storageId } = info
204
+ const heads = handle.getRemoteHeads(storageId)
205
+ const haveHeadsChanged =
206
+ message.syncState.theirHeads &&
207
+ (!heads || !headsAreSame(heads, message.syncState.theirHeads))
208
+
209
+ if (haveHeadsChanged) {
210
+ handle.setRemoteHeads(storageId, message.syncState.theirHeads)
211
+
212
+ if (storageId) {
213
+ this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
214
+ message.documentId,
215
+ storageId,
216
+ message.syncState.theirHeads
217
+ )
218
+ }
219
+ }
220
+ })
221
+
222
+ this.#remoteHeadsSubscriptions.on("notify-remote-heads", message => {
223
+ this.networkSubsystem.send({
224
+ type: "remote-heads-changed",
225
+ targetId: message.targetId,
226
+ documentId: message.documentId,
227
+ newHeads: {
228
+ [message.storageId]: {
229
+ heads: message.heads,
230
+ timestamp: message.timestamp,
231
+ },
232
+ },
233
+ })
234
+ })
235
+
236
+ this.#remoteHeadsSubscriptions.on("change-remote-subs", message => {
237
+ this.#log("change-remote-subs", message)
238
+ for (const peer of message.peers) {
239
+ this.networkSubsystem.send({
240
+ type: "remote-subscription-change",
241
+ targetId: peer,
242
+ add: message.add,
243
+ remove: message.remove,
244
+ })
245
+ }
246
+ })
159
247
 
160
- this.#synchronizer.on("sync-state", debouncedSaveSyncState)
248
+ this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
249
+ const handle = this.#handleCache[message.documentId]
250
+ handle.setRemoteHeads(message.storageId, message.remoteHeads)
251
+ })
252
+ }
253
+
254
+ #receiveMessage(message: RepoMessage) {
255
+ switch (message.type) {
256
+ case "remote-subscription-change":
257
+ this.#remoteHeadsSubscriptions.handleControlMessage(message)
258
+ break
259
+ case "remote-heads-changed":
260
+ this.#remoteHeadsSubscriptions.handleRemoteHeads(message)
261
+ break
262
+ case "sync":
263
+ case "request":
264
+ case "ephemeral":
265
+ case "doc-unavailable":
266
+ this.#synchronizer.receiveMessage(message).catch(err => {
267
+ console.log("error receiving message", { err })
268
+ })
269
+ }
270
+ }
271
+
272
+ #throttledSaveSyncStateHandlers: Record<
273
+ StorageId,
274
+ (message: SyncStateMessage) => void
275
+ > = {}
276
+
277
+ /** saves sync state throttled per storage id, if a peer doesn't have a storage id it's sync state is not persisted */
278
+ #saveSyncState(message: SyncStateMessage) {
279
+ if (!this.storageSubsystem) {
280
+ return
281
+ }
282
+
283
+ const persistanceInfo = this.persistanceInfoByPeerId[message.peerId]
284
+
285
+ if (!persistanceInfo || persistanceInfo.isEphemeral) {
286
+ return
287
+ }
288
+
289
+ const { storageId } = persistanceInfo
290
+
291
+ let handler = this.#throttledSaveSyncStateHandlers[storageId]
292
+ if (!handler) {
293
+ handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(
294
+ ({ documentId, syncState }: SyncStateMessage) => {
295
+ this.storageSubsystem!.saveSyncState(documentId, storageId, syncState)
296
+ },
297
+ this.saveDebounceRate
298
+ )
161
299
  }
300
+
301
+ handler(message)
162
302
  }
163
303
 
164
304
  /** Returns an existing handle if we have it; creates one otherwise. */
@@ -298,12 +438,34 @@ export class Repo extends EventEmitter<RepoEvents> {
298
438
  delete this.#handleCache[documentId]
299
439
  this.emit("delete-document", { documentId })
300
440
  }
441
+
442
+ subscribeToRemotes = (remotes: StorageId[]) => {
443
+ this.#log("subscribeToRemotes", { remotes })
444
+ this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes)
445
+ }
446
+
447
+ storageId = async (): Promise<StorageId | undefined> => {
448
+ if (!this.storageSubsystem) {
449
+ return undefined
450
+ } else {
451
+ return this.storageSubsystem.id()
452
+ }
453
+ }
454
+ }
455
+
456
+ interface PersistanceInfo {
457
+ storageId: StorageId
458
+ isEphemeral: boolean
301
459
  }
302
460
 
303
461
  export interface RepoConfig {
304
462
  /** Our unique identifier */
305
463
  peerId?: PeerId
306
464
 
465
+ /** Indicates whether other peers should persist the sync state of this peer.
466
+ * Sync state is only persisted for non-ephemeral peers */
467
+ isEphemeral?: boolean
468
+
307
469
  /** A storage adapter can be provided, or not */
308
470
  storage?: StorageAdapter
309
471
 
package/src/index.ts CHANGED
@@ -83,6 +83,7 @@ export type {
83
83
  ChunkInfo,
84
84
  ChunkType,
85
85
  StorageKey,
86
+ StorageId,
86
87
  } from "./storage/types.js"
87
88
 
88
89
  export * from "./types.js"
@@ -1,6 +1,7 @@
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"
4
5
 
5
6
  /** An interface representing some way to connect to other peers
6
7
  *
@@ -11,12 +12,20 @@ import { Message } from "./messages.js"
11
12
  */
12
13
  export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
13
14
  peerId?: PeerId // hmmm, maybe not
15
+ storageId?: StorageId
16
+ isEphemeral = true
14
17
 
15
18
  /** Called by the {@link Repo} to start the connection process
16
19
  *
17
20
  * @argument peerId - the peerId of this repo
21
+ * @argument storageId - the storage id of the peer
22
+ * @argument isEphemeral - weather or not the other end should persist our sync state
18
23
  */
19
- abstract connect(peerId: PeerId): void
24
+ abstract connect(
25
+ peerId: PeerId,
26
+ storageId: StorageId | undefined,
27
+ isEphemeral: boolean
28
+ ): void
20
29
 
21
30
  /** Called by the {@link Repo} to send a message to a peer
22
31
  *
@@ -53,6 +62,8 @@ export interface OpenPayload {
53
62
 
54
63
  export interface PeerCandidatePayload {
55
64
  peerId: PeerId
65
+ storageId?: StorageId
66
+ isEphemeral: boolean
56
67
  }
57
68
 
58
69
  export interface PeerDisconnectedPayload {
@@ -9,6 +9,7 @@ import {
9
9
  isEphemeralMessage,
10
10
  isValidRepoMessage,
11
11
  } from "./messages.js"
12
+ import { StorageId } from "../storage/types.js"
12
13
 
13
14
  type EphemeralMessageSource = `${PeerId}:${SessionId}`
14
15
 
@@ -25,7 +26,12 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
25
26
  #readyAdapterCount = 0
26
27
  #adapters: NetworkAdapter[] = []
27
28
 
28
- constructor(adapters: NetworkAdapter[], public peerId = randomPeerId()) {
29
+ constructor(
30
+ adapters: NetworkAdapter[],
31
+ public peerId = randomPeerId(),
32
+ private storageId: Promise<StorageId | undefined>, // todo: we shouldn't pass a promise here
33
+ private isEphemeral: boolean
34
+ ) {
29
35
  super()
30
36
  this.#log = debug(`automerge-repo:network:${this.peerId}`)
31
37
  adapters.forEach(a => this.addNetworkAdapter(a))
@@ -46,18 +52,21 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
46
52
  }
47
53
  })
48
54
 
49
- networkAdapter.on("peer-candidate", ({ peerId }) => {
50
- this.#log(`peer candidate: ${peerId} `)
55
+ networkAdapter.on(
56
+ "peer-candidate",
57
+ ({ peerId, storageId, isEphemeral }) => {
58
+ this.#log(`peer candidate: ${peerId} `)
51
59
 
52
- // TODO: This is where authentication would happen
60
+ // TODO: This is where authentication would happen
53
61
 
54
- if (!this.#adaptersByPeer[peerId]) {
55
- // TODO: handle losing a server here
56
- this.#adaptersByPeer[peerId] = networkAdapter
57
- }
62
+ if (!this.#adaptersByPeer[peerId]) {
63
+ // TODO: handle losing a server here
64
+ this.#adaptersByPeer[peerId] = networkAdapter
65
+ }
58
66
 
59
- this.emit("peer", { peerId })
60
- })
67
+ this.emit("peer", { peerId, storageId, isEphemeral })
68
+ }
69
+ )
61
70
 
62
71
  networkAdapter.on("peer-disconnected", ({ peerId }) => {
63
72
  this.#log(`peer disconnected: ${peerId} `)
@@ -98,7 +107,9 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
98
107
  })
99
108
  })
100
109
 
101
- networkAdapter.connect(this.peerId)
110
+ this.storageId.then(storageId => {
111
+ networkAdapter.connect(this.peerId, storageId, this.isEphemeral)
112
+ })
102
113
  }
103
114
 
104
115
  send(message: MessageContents) {
@@ -171,4 +182,6 @@ export interface NetworkSubsystemEvents {
171
182
 
172
183
  export interface PeerPayload {
173
184
  peerId: PeerId
185
+ storageId?: StorageId
186
+ isEphemeral: boolean
174
187
  }