@automerge/automerge-repo 1.0.0-alpha.2 → 1.0.0-alpha.3

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 (60) hide show
  1. package/dist/DocCollection.d.ts +2 -1
  2. package/dist/DocCollection.d.ts.map +1 -1
  3. package/dist/DocCollection.js +17 -8
  4. package/dist/DocHandle.d.ts +27 -4
  5. package/dist/DocHandle.d.ts.map +1 -1
  6. package/dist/DocHandle.js +44 -6
  7. package/dist/DocUrl.d.ts +3 -3
  8. package/dist/DocUrl.js +9 -9
  9. package/dist/EphemeralData.d.ts +8 -16
  10. package/dist/EphemeralData.d.ts.map +1 -1
  11. package/dist/EphemeralData.js +1 -28
  12. package/dist/Repo.d.ts +0 -2
  13. package/dist/Repo.d.ts.map +1 -1
  14. package/dist/Repo.js +13 -33
  15. package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
  16. package/dist/helpers/tests/network-adapter-tests.js +15 -13
  17. package/dist/index.d.ts +2 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/network/NetworkAdapter.d.ts +4 -13
  20. package/dist/network/NetworkAdapter.d.ts.map +1 -1
  21. package/dist/network/NetworkSubsystem.d.ts +5 -4
  22. package/dist/network/NetworkSubsystem.d.ts.map +1 -1
  23. package/dist/network/NetworkSubsystem.js +39 -25
  24. package/dist/network/messages.d.ts +57 -0
  25. package/dist/network/messages.d.ts.map +1 -0
  26. package/dist/network/messages.js +21 -0
  27. package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -2
  28. package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
  29. package/dist/synchronizer/CollectionSynchronizer.js +19 -13
  30. package/dist/synchronizer/DocSynchronizer.d.ts +9 -3
  31. package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
  32. package/dist/synchronizer/DocSynchronizer.js +145 -29
  33. package/dist/synchronizer/Synchronizer.d.ts +3 -4
  34. package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
  35. package/dist/types.d.ts +1 -3
  36. package/dist/types.d.ts.map +1 -1
  37. package/fuzz/fuzz.ts +4 -4
  38. package/package.json +2 -2
  39. package/src/DocCollection.ts +19 -9
  40. package/src/DocHandle.ts +87 -10
  41. package/src/DocUrl.ts +9 -9
  42. package/src/EphemeralData.ts +6 -36
  43. package/src/Repo.ts +15 -49
  44. package/src/helpers/tests/network-adapter-tests.ts +18 -14
  45. package/src/index.ts +12 -2
  46. package/src/network/NetworkAdapter.ts +4 -20
  47. package/src/network/NetworkSubsystem.ts +61 -38
  48. package/src/network/messages.ts +123 -0
  49. package/src/synchronizer/CollectionSynchronizer.ts +38 -19
  50. package/src/synchronizer/DocSynchronizer.ts +196 -38
  51. package/src/synchronizer/Synchronizer.ts +3 -8
  52. package/src/types.ts +4 -1
  53. package/test/CollectionSynchronizer.test.ts +6 -7
  54. package/test/DocHandle.test.ts +28 -13
  55. package/test/DocSynchronizer.test.ts +85 -9
  56. package/test/Repo.test.ts +221 -59
  57. package/test/StorageSubsystem.test.ts +2 -2
  58. package/test/helpers/DummyNetworkAdapter.ts +1 -1
  59. package/tsconfig.json +2 -1
  60. package/test/EphemeralData.test.ts +0 -44
@@ -1,46 +1,16 @@
1
- import { decode, encode } from "cbor-x"
2
- import EventEmitter from "eventemitter3"
3
- import { ChannelId, PeerId } from "./index.js"
4
- import { MessagePayload } from "./network/NetworkAdapter.js"
5
-
6
- /**
7
- * EphemeralData provides a mechanism to broadcast short-lived data — cursor positions, presence,
8
- * heartbeats, etc. — that is useful in the moment but not worth persisting.
9
- */
10
- export class EphemeralData extends EventEmitter<EphemeralDataMessageEvents> {
11
- /** Broadcast an ephemeral message */
12
- broadcast(channelId: ChannelId, message: unknown) {
13
- const messageBytes = encode(message)
14
-
15
- this.emit("message", {
16
- targetId: "*" as PeerId, // TODO: we don't really need a targetId for broadcast
17
- channelId: ("m/" + channelId) as ChannelId,
18
- message: messageBytes,
19
- broadcast: true,
20
- })
21
- }
22
-
23
- /** Receive an ephemeral message */
24
- receive(senderId: PeerId, grossChannelId: ChannelId, message: Uint8Array) {
25
- const data = decode(message)
26
- const channelId = grossChannelId.slice(2) as ChannelId
27
- this.emit("data", {
28
- peerId: senderId,
29
- channelId,
30
- data,
31
- })
32
- }
33
- }
1
+ import { DocumentId, PeerId } from "./index.js"
2
+ import { EphemeralMessageContents } from "./network/messages.js"
34
3
 
35
4
  // types
5
+ export type SessionId = string & { __SessionId: false }
36
6
 
37
7
  export interface EphemeralDataPayload {
38
- channelId: ChannelId
8
+ documentId: DocumentId
39
9
  peerId: PeerId
40
- data: { peerId: PeerId; channelId: ChannelId; data: unknown }
10
+ data: { peerId: PeerId; documentId: DocumentId; data: unknown }
41
11
  }
42
12
 
43
13
  export type EphemeralDataMessageEvents = {
44
- message: (event: MessagePayload) => void
14
+ message: (event: EphemeralMessageContents) => void
45
15
  data: (event: EphemeralDataPayload) => void
46
16
  }
package/src/Repo.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import debug from "debug"
1
2
  import { DocCollection } from "./DocCollection.js"
2
- import { EphemeralData } from "./EphemeralData.js"
3
3
  import { NetworkAdapter } from "./network/NetworkAdapter.js"
4
4
  import { NetworkSubsystem } from "./network/NetworkSubsystem.js"
5
5
  import { StorageAdapter } from "./storage/StorageAdapter.js"
@@ -7,15 +7,12 @@ import { StorageSubsystem } from "./storage/StorageSubsystem.js"
7
7
  import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js"
8
8
  import { DocumentId, PeerId } from "./types.js"
9
9
 
10
- import debug from "debug"
11
-
12
10
  /** A Repo is a DocCollection with networking, syncing, and storage capabilities. */
13
11
  export class Repo extends DocCollection {
14
12
  #log: debug.Debugger
15
13
 
16
14
  networkSubsystem: NetworkSubsystem
17
15
  storageSubsystem?: StorageSubsystem
18
- ephemeralData: EphemeralData
19
16
 
20
17
  constructor({ storage, network, peerId, sharePolicy }: RepoConfig) {
21
18
  super()
@@ -40,44 +37,45 @@ export class Repo extends DocCollection {
40
37
  }
41
38
  }
42
39
 
40
+ handle.on("unavailable", () => {
41
+ this.#log("document unavailable", { documentId: handle.documentId })
42
+ this.emit("unavailable-document", {
43
+ documentId: handle.documentId,
44
+ })
45
+ })
46
+
43
47
  handle.request()
44
48
 
45
49
  // Register the document with the synchronizer. This advertises our interest in the document.
46
50
  synchronizer.addDocument(handle.documentId)
47
51
  })
48
52
 
49
- this.on("delete-document", ({ encodedDocumentId }) => {
53
+ this.on("delete-document", ({ documentId }) => {
50
54
  // TODO Pass the delete on to the network
51
55
  // synchronizer.removeDocument(documentId)
52
56
 
53
57
  if (storageSubsystem) {
54
- storageSubsystem.remove(encodedDocumentId)
58
+ storageSubsystem.remove(documentId)
55
59
  }
56
60
  })
57
61
 
58
62
  // SYNCHRONIZER
59
63
  // The synchronizer uses the network subsystem to keep documents in sync with peers.
60
-
61
64
  const synchronizer = new CollectionSynchronizer(this)
62
65
 
63
66
  // When the synchronizer emits sync messages, send them to peers
64
- synchronizer.on(
65
- "message",
66
- ({ targetId, channelId, message, broadcast }) => {
67
- this.#log(`sending sync message to ${targetId}`)
68
- networkSubsystem.sendMessage(targetId, channelId, message, broadcast)
69
- }
70
- )
67
+ synchronizer.on("message", message => {
68
+ this.#log(`sending sync message to ${message.targetId}`)
69
+ networkSubsystem.send(message)
70
+ })
71
71
 
72
72
  // STORAGE
73
73
  // The storage subsystem has access to some form of persistence, and deals with save and loading documents.
74
-
75
74
  const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined
76
75
  this.storageSubsystem = storageSubsystem
77
76
 
78
77
  // NETWORK
79
78
  // The network subsystem deals with sending and receiving messages to and from peers.
80
-
81
79
  const networkSubsystem = new NetworkSubsystem(network, peerId)
82
80
  this.networkSubsystem = networkSubsystem
83
81
 
@@ -94,40 +92,8 @@ export class Repo extends DocCollection {
94
92
 
95
93
  // Handle incoming messages
96
94
  networkSubsystem.on("message", async msg => {
97
- const { senderId, channelId, message } = msg
98
-
99
- // TODO: this demands a more principled way of associating channels with recipients
100
-
101
- // Ephemeral channel ids start with "m/"
102
- if (channelId.startsWith("m/")) {
103
- // Ephemeral message
104
- this.#log(`receiving ephemeral message from ${senderId}`)
105
- ephemeralData.receive(senderId, channelId, message)
106
- } else {
107
- // Sync message
108
- this.#log(`receiving sync message from ${senderId}`)
109
- await synchronizer.receiveSyncMessage(senderId, channelId, message)
110
- }
95
+ await synchronizer.receiveMessage(msg)
111
96
  })
112
-
113
- // We establish a special channel for sync messages
114
- networkSubsystem.join()
115
-
116
- // EPHEMERAL DATA
117
- // The ephemeral data subsystem uses the network to send and receive messages that are not
118
- // persisted to storage, e.g. cursor position, presence, etc.
119
-
120
- const ephemeralData = new EphemeralData()
121
- this.ephemeralData = ephemeralData
122
-
123
- // Send ephemeral messages to peers
124
- ephemeralData.on(
125
- "message",
126
- ({ targetId, channelId, message, broadcast }) => {
127
- this.#log(`sending ephemeral message to ${targetId}`)
128
- networkSubsystem.sendMessage(targetId, channelId, message, broadcast)
129
- }
130
- )
131
97
  }
132
98
  }
133
99
 
@@ -1,7 +1,8 @@
1
- import { PeerId, Repo, type NetworkAdapter, ChannelId } from "../../index.js"
1
+ import { PeerId, Repo, type NetworkAdapter, DocumentId } from "../../index.js"
2
2
  import { eventPromise, eventPromises } from "../eventPromise.js"
3
3
  import { assert } from "chai"
4
4
  import { describe, it } from "mocha"
5
+ import { pause } from "../pause.js"
5
6
 
6
7
  /**
7
8
  * Runs a series of tests against a set of three peers, each represented by one or more instantiated
@@ -46,7 +47,7 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
46
47
 
47
48
  // Bob receives the change
48
49
  await eventPromise(bobHandle, "change")
49
- assert.equal((await bobHandle.doc()).foo, "bar")
50
+ assert.equal((await bobHandle.doc())?.foo, "bar")
50
51
 
51
52
  // Bob changes the document
52
53
  bobHandle.change(d => {
@@ -55,7 +56,7 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
55
56
 
56
57
  // Alice receives the change
57
58
  await eventPromise(aliceHandle, "change")
58
- assert.equal((await aliceHandle.doc()).foo, "baz")
59
+ assert.equal((await aliceHandle.doc())?.foo, "baz")
59
60
  }
60
61
 
61
62
  // Run the test in both directions, in case they're different types of adapters
@@ -97,8 +98,8 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
97
98
 
98
99
  // Bob and Charlie receive the change
99
100
  await eventPromises([bobHandle, charlieHandle], "change")
100
- assert.equal((await bobHandle.doc()).foo, "bar")
101
- assert.equal((await charlieHandle.doc()).foo, "bar")
101
+ assert.equal((await bobHandle.doc())?.foo, "bar")
102
+ assert.equal((await charlieHandle.doc())?.foo, "bar")
102
103
 
103
104
  // Charlie changes the document
104
105
  charlieHandle.change(d => {
@@ -107,15 +108,13 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
107
108
 
108
109
  // Alice and Bob receive the change
109
110
  await eventPromises([aliceHandle, bobHandle], "change")
110
- assert.equal((await bobHandle.doc()).foo, "baz")
111
- assert.equal((await charlieHandle.doc()).foo, "baz")
111
+ assert.equal((await bobHandle.doc())?.foo, "baz")
112
+ assert.equal((await charlieHandle.doc())?.foo, "baz")
112
113
 
113
114
  teardown()
114
115
  })
115
116
 
116
- // TODO: with BroadcastChannel, this test never ends, because it goes into an infinite loop,
117
- // because the network has cycles (see #92)
118
- it.skip("can broadcast a message", async () => {
117
+ it("can broadcast a message", async () => {
119
118
  const { adapters, teardown } = await setup()
120
119
  const [a, b, c] = adapters
121
120
 
@@ -128,13 +127,18 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
128
127
  "peer"
129
128
  )
130
129
 
131
- const channelId = "broadcast" as ChannelId
130
+ const aliceHandle = aliceRepo.create<TestDoc>()
131
+ const charlieHandle = charlieRepo.find(aliceHandle.url)
132
+
133
+ // pause to give charlie a chance to let alice know it wants the doc
134
+ await pause(100)
135
+
132
136
  const alicePresenceData = { presence: "alice" }
137
+ aliceHandle.broadcast(alicePresenceData)
133
138
 
134
- aliceRepo.ephemeralData.broadcast(channelId, alicePresenceData)
135
- const { data } = await eventPromise(charlieRepo.ephemeralData, "data")
139
+ const { message } = await eventPromise(charlieHandle, "ephemeral-message")
136
140
 
137
- assert.deepStrictEqual(data, alicePresenceData)
141
+ assert.deepStrictEqual(message, alicePresenceData)
138
142
  teardown()
139
143
  })
140
144
  })
package/src/index.ts CHANGED
@@ -3,12 +3,22 @@ export { DocHandle, HandleState } from "./DocHandle.js"
3
3
  export type { DocHandleChangePayload } from "./DocHandle.js"
4
4
  export { NetworkAdapter } from "./network/NetworkAdapter.js"
5
5
  export type {
6
- InboundMessagePayload,
7
- MessagePayload,
8
6
  OpenPayload,
9
7
  PeerCandidatePayload,
10
8
  PeerDisconnectedPayload,
11
9
  } from "./network/NetworkAdapter.js"
10
+
11
+ // This is a bit confusing right now, but:
12
+ // Message is the type for messages used outside of the network adapters
13
+ // there are some extra internal network adapter-only messages on NetworkAdapterMessage
14
+ // and Message is (as of this writing) a union type for EphmeralMessage and SyncMessage
15
+ export type {
16
+ Message,
17
+ NetworkAdapterMessage,
18
+ EphemeralMessage,
19
+ SyncMessage,
20
+ } from "./network/messages.js"
21
+
12
22
  export { NetworkSubsystem } from "./network/NetworkSubsystem.js"
13
23
  export { Repo, type SharePolicy } from "./Repo.js"
14
24
  export { StorageAdapter, type StorageKey } from "./storage/StorageAdapter.js"
@@ -1,17 +1,13 @@
1
1
  import EventEmitter from "eventemitter3"
2
- import { PeerId, ChannelId } from "../types.js"
2
+ import { PeerId } from "../types.js"
3
+ import { Message } from "./messages.js"
3
4
 
4
5
  export abstract class NetworkAdapter extends EventEmitter<NetworkAdapterEvents> {
5
6
  peerId?: PeerId // hmmm, maybe not
6
7
 
7
8
  abstract connect(url?: string): void
8
9
 
9
- abstract sendMessage(
10
- peerId: PeerId,
11
- channelId: ChannelId,
12
- message: Uint8Array,
13
- broadcast: boolean
14
- ): void
10
+ abstract send(message: Message): void
15
11
 
16
12
  abstract join(): void
17
13
 
@@ -25,7 +21,7 @@ export interface NetworkAdapterEvents {
25
21
  close: () => void
26
22
  "peer-candidate": (payload: PeerCandidatePayload) => void
27
23
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void
28
- message: (payload: InboundMessagePayload) => void
24
+ message: (payload: Message) => void
29
25
  }
30
26
 
31
27
  export interface OpenPayload {
@@ -36,18 +32,6 @@ export interface PeerCandidatePayload {
36
32
  peerId: PeerId
37
33
  }
38
34
 
39
- export interface MessagePayload {
40
- targetId: PeerId
41
- channelId: ChannelId
42
- message: Uint8Array
43
- broadcast: boolean
44
- }
45
-
46
- export interface InboundMessagePayload extends MessagePayload {
47
- type?: string
48
- senderId: PeerId
49
- }
50
-
51
35
  export interface PeerDisconnectedPayload {
52
36
  peerId: PeerId
53
37
  }
@@ -1,17 +1,31 @@
1
1
  import EventEmitter from "eventemitter3"
2
+ import { PeerId } from "../types.js"
3
+ import { NetworkAdapter, PeerDisconnectedPayload } from "./NetworkAdapter.js"
4
+
2
5
  import {
3
- InboundMessagePayload,
4
- NetworkAdapter,
5
- PeerDisconnectedPayload,
6
- } from "./NetworkAdapter.js"
7
- import { ChannelId, PeerId } from "../types.js"
6
+ EphemeralMessage,
7
+ isEphemeralMessage,
8
+ isValidMessage,
9
+ Message,
10
+ MessageContents,
11
+ } from "./messages.js"
8
12
 
9
13
  import debug from "debug"
14
+ import { SessionId } from "../EphemeralData.js"
15
+
16
+ type EphemeralMessageSource = `${PeerId}:${SessionId}`
17
+
18
+ const getEphemeralMessageSource = (message: EphemeralMessage) =>
19
+ `${message.senderId}:${message.sessionId}` as EphemeralMessageSource
10
20
 
11
21
  export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
12
22
  #log: debug.Debugger
13
23
  #adaptersByPeer: Record<PeerId, NetworkAdapter> = {}
14
24
 
25
+ #count = 0
26
+ #sessionId: SessionId = Math.random().toString(36).slice(2) as SessionId
27
+ #ephemeralSessionCounts: Record<EphemeralMessageSource, number> = {}
28
+
15
29
  constructor(
16
30
  private adapters: NetworkAdapter[],
17
31
  public peerId = randomPeerId()
@@ -44,20 +58,24 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
44
58
  })
45
59
 
46
60
  networkAdapter.on("message", msg => {
47
- const { senderId, channelId, broadcast, message } = msg
48
- this.#log(`message from ${senderId}`)
49
-
50
- // If we receive a broadcast message from a network adapter we need to re-broadcast it to all
51
- // our other peers. This is the world's worst gossip protocol.
52
-
53
- // TODO: This relies on the network forming a tree! If there are cycles, this approach will
54
- // loop messages around forever.
55
- if (broadcast) {
56
- Object.entries(this.#adaptersByPeer)
57
- .filter(([id]) => id !== senderId)
58
- .forEach(([id, peer]) => {
59
- peer.sendMessage(id as PeerId, channelId, message, broadcast)
60
- })
61
+ if (!isValidMessage(msg)) {
62
+ this.#log(`invalid message: ${JSON.stringify(msg)}`)
63
+ return
64
+ }
65
+
66
+ this.#log(`message from ${msg.senderId}`)
67
+
68
+ if (isEphemeralMessage(msg)) {
69
+ const source = getEphemeralMessageSource(msg)
70
+ if (
71
+ this.#ephemeralSessionCounts[source] === undefined ||
72
+ msg.count > this.#ephemeralSessionCounts[source]
73
+ ) {
74
+ this.#ephemeralSessionCounts[source] = msg.count
75
+ this.emit("message", msg)
76
+ }
77
+
78
+ return
61
79
  }
62
80
 
63
81
  this.emit("message", msg)
@@ -75,25 +93,30 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
75
93
  networkAdapter.join()
76
94
  }
77
95
 
78
- sendMessage(
79
- peerId: PeerId,
80
- channelId: ChannelId,
81
- message: Uint8Array,
82
- broadcast: boolean
83
- ) {
84
- if (broadcast) {
85
- Object.entries(this.#adaptersByPeer).forEach(([id, peer]) => {
86
- this.#log(`sending broadcast to ${id}`)
87
- peer.sendMessage(id as PeerId, channelId, message, true)
88
- })
96
+ send(message: MessageContents) {
97
+ const peer = this.#adaptersByPeer[message.targetId]
98
+ if (!peer) {
99
+ this.#log(`Tried to send message but peer not found: ${message.targetId}`)
100
+ return
101
+ }
102
+ this.#log(`Sending message to ${message.targetId}`)
103
+
104
+ if (isEphemeralMessage(message)) {
105
+ const outbound =
106
+ "count" in message
107
+ ? message
108
+ : {
109
+ ...message,
110
+ count: ++this.#count,
111
+ sessionId: this.#sessionId,
112
+ senderId: this.peerId,
113
+ }
114
+ this.#log("Ephemeral message", outbound)
115
+ peer.send(outbound)
89
116
  } else {
90
- const peer = this.#adaptersByPeer[peerId]
91
- if (!peer) {
92
- this.#log(`Tried to send message but peer not found: ${peerId}`)
93
- return
94
- }
95
- this.#log(`Sending message to ${peerId}`)
96
- peer.sendMessage(peerId, channelId, message, false)
117
+ const outbound = { ...message, senderId: this.peerId }
118
+ this.#log("Sync message", outbound)
119
+ peer.send(outbound)
97
120
  }
98
121
  }
99
122
 
@@ -117,7 +140,7 @@ function randomPeerId() {
117
140
  export interface NetworkSubsystemEvents {
118
141
  peer: (payload: PeerPayload) => void
119
142
  "peer-disconnected": (payload: PeerDisconnectedPayload) => void
120
- message: (payload: InboundMessagePayload) => void
143
+ message: (payload: Message) => void
121
144
  }
122
145
 
123
146
  export interface PeerPayload {
@@ -0,0 +1,123 @@
1
+ // utilities
2
+ import { SessionId } from "../EphemeralData"
3
+ import { DocumentId, PeerId } from "../types"
4
+
5
+ export function isValidMessage(
6
+ message: NetworkAdapterMessage
7
+ ): message is
8
+ | SyncMessage
9
+ | EphemeralMessage
10
+ | RequestMessage
11
+ | DocumentUnavailableMessage {
12
+ return (
13
+ typeof message === "object" &&
14
+ typeof message.type === "string" &&
15
+ typeof message.senderId === "string" &&
16
+ (isSyncMessage(message) ||
17
+ isEphemeralMessage(message) ||
18
+ isRequestMessage(message) ||
19
+ isDocumentUnavailableMessage(message))
20
+ )
21
+ }
22
+
23
+ export function isDocumentUnavailableMessage(
24
+ message: NetworkAdapterMessage
25
+ ): message is DocumentUnavailableMessage {
26
+ return message.type === "doc-unavailable"
27
+ }
28
+
29
+ export function isRequestMessage(
30
+ message: NetworkAdapterMessage
31
+ ): message is RequestMessage {
32
+ return message.type === "request"
33
+ }
34
+
35
+ export function isSyncMessage(
36
+ message: NetworkAdapterMessage
37
+ ): message is SyncMessage {
38
+ return message.type === "sync"
39
+ }
40
+
41
+ export function isEphemeralMessage(
42
+ message: NetworkAdapterMessage | MessageContents
43
+ ): message is EphemeralMessage | EphemeralMessageContents {
44
+ return message.type === "ephemeral"
45
+ }
46
+
47
+ export interface SyncMessageEnvelope {
48
+ senderId: PeerId
49
+ }
50
+
51
+ export interface SyncMessageContents {
52
+ type: "sync"
53
+ data: Uint8Array
54
+ targetId: PeerId
55
+ documentId: DocumentId
56
+ }
57
+
58
+ export type SyncMessage = SyncMessageEnvelope & SyncMessageContents
59
+
60
+ export interface EphemeralMessageEnvelope {
61
+ senderId: PeerId
62
+ count: number
63
+ sessionId: SessionId
64
+ }
65
+
66
+ export interface EphemeralMessageContents {
67
+ type: "ephemeral"
68
+ targetId: PeerId
69
+ documentId: DocumentId
70
+ data: Uint8Array
71
+ }
72
+
73
+ export type EphemeralMessage = EphemeralMessageEnvelope &
74
+ EphemeralMessageContents
75
+
76
+ export interface DocumentUnavailableMessageContents {
77
+ type: "doc-unavailable"
78
+ documentId: DocumentId
79
+ targetId: PeerId
80
+ }
81
+
82
+ export type DocumentUnavailableMessage = SyncMessageEnvelope &
83
+ DocumentUnavailableMessageContents
84
+
85
+ export interface RequestMessageContents {
86
+ type: "request"
87
+ data: Uint8Array
88
+ targetId: PeerId
89
+ documentId: DocumentId
90
+ }
91
+
92
+ export type RequestMessage = SyncMessageEnvelope & RequestMessageContents
93
+
94
+ export type MessageContents =
95
+ | SyncMessageContents
96
+ | EphemeralMessageContents
97
+ | RequestMessageContents
98
+ | DocumentUnavailableMessageContents
99
+
100
+ export type Message =
101
+ | SyncMessage
102
+ | EphemeralMessage
103
+ | RequestMessage
104
+ | DocumentUnavailableMessage
105
+
106
+ export type SynchronizerMessage =
107
+ | SyncMessage
108
+ | RequestMessage
109
+ | DocumentUnavailableMessage
110
+ | EphemeralMessage
111
+
112
+ type ArriveMessage = {
113
+ senderId: PeerId
114
+ type: "arrive"
115
+ }
116
+
117
+ type WelcomeMessage = {
118
+ senderId: PeerId
119
+ targetId: PeerId
120
+ type: "welcome"
121
+ }
122
+
123
+ export type NetworkAdapterMessage = ArriveMessage | WelcomeMessage | Message