cojson 0.8.12 → 0.8.17

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 (164) hide show
  1. package/CHANGELOG.md +95 -83
  2. package/dist/native/PeerKnownStates.js +6 -1
  3. package/dist/native/PeerKnownStates.js.map +1 -1
  4. package/dist/native/PeerState.js +4 -3
  5. package/dist/native/PeerState.js.map +1 -1
  6. package/dist/native/PriorityBasedMessageQueue.js +1 -10
  7. package/dist/native/PriorityBasedMessageQueue.js.map +1 -1
  8. package/dist/native/SyncStateSubscriptionManager.js +70 -0
  9. package/dist/native/SyncStateSubscriptionManager.js.map +1 -0
  10. package/dist/native/base64url.js.map +1 -1
  11. package/dist/native/base64url.test.js +1 -1
  12. package/dist/native/base64url.test.js.map +1 -1
  13. package/dist/native/coValue.js.map +1 -1
  14. package/dist/native/coValueCore.js +141 -149
  15. package/dist/native/coValueCore.js.map +1 -1
  16. package/dist/native/coValueState.js.map +1 -1
  17. package/dist/native/coValues/account.js +6 -6
  18. package/dist/native/coValues/account.js.map +1 -1
  19. package/dist/native/coValues/coList.js +2 -3
  20. package/dist/native/coValues/coList.js.map +1 -1
  21. package/dist/native/coValues/coMap.js +1 -1
  22. package/dist/native/coValues/coMap.js.map +1 -1
  23. package/dist/native/coValues/coStream.js +3 -5
  24. package/dist/native/coValues/coStream.js.map +1 -1
  25. package/dist/native/coValues/group.js +11 -11
  26. package/dist/native/coValues/group.js.map +1 -1
  27. package/dist/native/coreToCoValue.js +2 -2
  28. package/dist/native/coreToCoValue.js.map +1 -1
  29. package/dist/native/crypto/PureJSCrypto.js +4 -4
  30. package/dist/native/crypto/PureJSCrypto.js.map +1 -1
  31. package/dist/native/crypto/crypto.js.map +1 -1
  32. package/dist/native/exports.js +12 -12
  33. package/dist/native/exports.js.map +1 -1
  34. package/dist/native/ids.js.map +1 -1
  35. package/dist/native/jsonStringify.js.map +1 -1
  36. package/dist/native/localNode.js +5 -7
  37. package/dist/native/localNode.js.map +1 -1
  38. package/dist/native/permissions.js +4 -7
  39. package/dist/native/permissions.js.map +1 -1
  40. package/dist/native/priority.js.map +1 -1
  41. package/dist/native/storage/FileSystem.js.map +1 -1
  42. package/dist/native/storage/chunksAndKnownStates.js +2 -4
  43. package/dist/native/storage/chunksAndKnownStates.js.map +1 -1
  44. package/dist/native/storage/index.js +6 -15
  45. package/dist/native/storage/index.js.map +1 -1
  46. package/dist/native/streamUtils.js.map +1 -1
  47. package/dist/native/sync.js +57 -7
  48. package/dist/native/sync.js.map +1 -1
  49. package/dist/native/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  50. package/dist/native/typeUtils/expectGroup.js.map +1 -1
  51. package/dist/native/typeUtils/isAccountID.js.map +1 -1
  52. package/dist/native/typeUtils/isCoValue.js +1 -1
  53. package/dist/native/typeUtils/isCoValue.js.map +1 -1
  54. package/dist/web/PeerKnownStates.js +6 -1
  55. package/dist/web/PeerKnownStates.js.map +1 -1
  56. package/dist/web/PeerState.js +4 -3
  57. package/dist/web/PeerState.js.map +1 -1
  58. package/dist/web/PriorityBasedMessageQueue.js +1 -10
  59. package/dist/web/PriorityBasedMessageQueue.js.map +1 -1
  60. package/dist/web/SyncStateSubscriptionManager.js +70 -0
  61. package/dist/web/SyncStateSubscriptionManager.js.map +1 -0
  62. package/dist/web/base64url.js.map +1 -1
  63. package/dist/web/base64url.test.js +1 -1
  64. package/dist/web/base64url.test.js.map +1 -1
  65. package/dist/web/coValue.js.map +1 -1
  66. package/dist/web/coValueCore.js +141 -149
  67. package/dist/web/coValueCore.js.map +1 -1
  68. package/dist/web/coValueState.js.map +1 -1
  69. package/dist/web/coValues/account.js +6 -6
  70. package/dist/web/coValues/account.js.map +1 -1
  71. package/dist/web/coValues/coList.js +2 -3
  72. package/dist/web/coValues/coList.js.map +1 -1
  73. package/dist/web/coValues/coMap.js +1 -1
  74. package/dist/web/coValues/coMap.js.map +1 -1
  75. package/dist/web/coValues/coStream.js +3 -5
  76. package/dist/web/coValues/coStream.js.map +1 -1
  77. package/dist/web/coValues/group.js +11 -11
  78. package/dist/web/coValues/group.js.map +1 -1
  79. package/dist/web/coreToCoValue.js +2 -2
  80. package/dist/web/coreToCoValue.js.map +1 -1
  81. package/dist/web/crypto/PureJSCrypto.js +4 -4
  82. package/dist/web/crypto/PureJSCrypto.js.map +1 -1
  83. package/dist/web/crypto/WasmCrypto.js +5 -5
  84. package/dist/web/crypto/WasmCrypto.js.map +1 -1
  85. package/dist/web/crypto/crypto.js.map +1 -1
  86. package/dist/web/exports.js +12 -12
  87. package/dist/web/exports.js.map +1 -1
  88. package/dist/web/ids.js.map +1 -1
  89. package/dist/web/jsonStringify.js.map +1 -1
  90. package/dist/web/localNode.js +5 -7
  91. package/dist/web/localNode.js.map +1 -1
  92. package/dist/web/permissions.js +4 -7
  93. package/dist/web/permissions.js.map +1 -1
  94. package/dist/web/priority.js.map +1 -1
  95. package/dist/web/storage/FileSystem.js.map +1 -1
  96. package/dist/web/storage/chunksAndKnownStates.js +2 -4
  97. package/dist/web/storage/chunksAndKnownStates.js.map +1 -1
  98. package/dist/web/storage/index.js +6 -15
  99. package/dist/web/storage/index.js.map +1 -1
  100. package/dist/web/streamUtils.js.map +1 -1
  101. package/dist/web/sync.js +57 -7
  102. package/dist/web/sync.js.map +1 -1
  103. package/dist/web/typeUtils/accountOrAgentIDfromSessionID.js.map +1 -1
  104. package/dist/web/typeUtils/expectGroup.js.map +1 -1
  105. package/dist/web/typeUtils/isAccountID.js.map +1 -1
  106. package/dist/web/typeUtils/isCoValue.js +1 -1
  107. package/dist/web/typeUtils/isCoValue.js.map +1 -1
  108. package/package.json +4 -14
  109. package/src/PeerKnownStates.ts +98 -90
  110. package/src/PeerState.ts +92 -73
  111. package/src/PriorityBasedMessageQueue.ts +42 -49
  112. package/src/SyncStateSubscriptionManager.ts +124 -0
  113. package/src/base64url.test.ts +24 -24
  114. package/src/base64url.ts +44 -45
  115. package/src/coValue.ts +45 -45
  116. package/src/coValueCore.ts +746 -785
  117. package/src/coValueState.ts +82 -72
  118. package/src/coValues/account.ts +143 -150
  119. package/src/coValues/coList.ts +520 -522
  120. package/src/coValues/coMap.ts +283 -285
  121. package/src/coValues/coStream.ts +320 -324
  122. package/src/coValues/group.ts +306 -305
  123. package/src/coreToCoValue.ts +28 -31
  124. package/src/crypto/PureJSCrypto.ts +188 -194
  125. package/src/crypto/WasmCrypto.ts +236 -254
  126. package/src/crypto/crypto.ts +302 -309
  127. package/src/exports.ts +116 -116
  128. package/src/ids.ts +9 -9
  129. package/src/jsonStringify.ts +46 -46
  130. package/src/jsonValue.ts +24 -10
  131. package/src/localNode.ts +635 -660
  132. package/src/media.ts +3 -3
  133. package/src/permissions.ts +272 -278
  134. package/src/priority.ts +21 -19
  135. package/src/storage/FileSystem.ts +91 -99
  136. package/src/storage/chunksAndKnownStates.ts +110 -115
  137. package/src/storage/index.ts +466 -497
  138. package/src/streamUtils.ts +60 -60
  139. package/src/sync.ts +656 -608
  140. package/src/tests/PeerKnownStates.test.ts +38 -34
  141. package/src/tests/PeerState.test.ts +101 -64
  142. package/src/tests/PriorityBasedMessageQueue.test.ts +91 -91
  143. package/src/tests/SyncStateSubscriptionManager.test.ts +232 -0
  144. package/src/tests/account.test.ts +59 -59
  145. package/src/tests/coList.test.ts +65 -65
  146. package/src/tests/coMap.test.ts +137 -137
  147. package/src/tests/coStream.test.ts +254 -257
  148. package/src/tests/coValueCore.test.ts +153 -156
  149. package/src/tests/crypto.test.ts +136 -144
  150. package/src/tests/cryptoImpl.test.ts +205 -197
  151. package/src/tests/group.test.ts +24 -24
  152. package/src/tests/permissions.test.ts +1306 -1371
  153. package/src/tests/priority.test.ts +65 -82
  154. package/src/tests/sync.test.ts +1573 -1263
  155. package/src/tests/testUtils.ts +85 -53
  156. package/src/typeUtils/accountOrAgentIDfromSessionID.ts +4 -4
  157. package/src/typeUtils/expectGroup.ts +9 -9
  158. package/src/typeUtils/isAccountID.ts +1 -1
  159. package/src/typeUtils/isCoValue.ts +9 -9
  160. package/tsconfig.json +4 -6
  161. package/tsconfig.native.json +9 -11
  162. package/tsconfig.web.json +4 -10
  163. package/.eslintrc.cjs +0 -25
  164. package/.prettierrc.js +0 -9
package/src/sync.ts CHANGED
@@ -1,60 +1,61 @@
1
- import { Signature } from "./crypto/crypto.js";
1
+ import { PeerState } from "./PeerState.js";
2
+ import { SyncStateSubscriptionManager } from "./SyncStateSubscriptionManager.js";
2
3
  import { CoValueHeader, Transaction } from "./coValueCore.js";
3
4
  import { CoValueCore } from "./coValueCore.js";
4
- import { LocalNode } from "./localNode.js";
5
5
  import { CoValueState } from "./coValueState.js";
6
+ import { Signature } from "./crypto/crypto.js";
6
7
  import { RawCoID, SessionID } from "./ids.js";
7
- import { PeerState } from "./PeerState.js";
8
+ import { LocalNode } from "./localNode.js";
8
9
  import { CoValuePriority } from "./priority.js";
9
10
 
10
11
  export type CoValueKnownState = {
11
- id: RawCoID;
12
- header: boolean;
13
- sessions: { [sessionID: SessionID]: number };
12
+ id: RawCoID;
13
+ header: boolean;
14
+ sessions: { [sessionID: SessionID]: number };
14
15
  };
15
16
 
16
17
  export function emptyKnownState(id: RawCoID): CoValueKnownState {
17
- return {
18
- id,
19
- header: false,
20
- sessions: {},
21
- };
18
+ return {
19
+ id,
20
+ header: false,
21
+ sessions: {},
22
+ };
22
23
  }
23
24
 
24
25
  export type SyncMessage =
25
- | LoadMessage
26
- | KnownStateMessage
27
- | NewContentMessage
28
- | DoneMessage;
26
+ | LoadMessage
27
+ | KnownStateMessage
28
+ | NewContentMessage
29
+ | DoneMessage;
29
30
 
30
31
  export type LoadMessage = {
31
- action: "load";
32
+ action: "load";
32
33
  } & CoValueKnownState;
33
34
 
34
35
  export type KnownStateMessage = {
35
- action: "known";
36
- asDependencyOf?: RawCoID;
37
- isCorrection?: boolean;
36
+ action: "known";
37
+ asDependencyOf?: RawCoID;
38
+ isCorrection?: boolean;
38
39
  } & CoValueKnownState;
39
40
 
40
41
  export type NewContentMessage = {
41
- action: "content";
42
- id: RawCoID;
43
- header?: CoValueHeader;
44
- priority: CoValuePriority;
45
- new: {
46
- [sessionID: SessionID]: SessionNewContent;
47
- };
42
+ action: "content";
43
+ id: RawCoID;
44
+ header?: CoValueHeader;
45
+ priority: CoValuePriority;
46
+ new: {
47
+ [sessionID: SessionID]: SessionNewContent;
48
+ };
48
49
  };
49
50
 
50
51
  export type SessionNewContent = {
51
- after: number;
52
- newTransactions: Transaction[];
53
- lastSignature: Signature;
52
+ after: number;
53
+ newTransactions: Transaction[];
54
+ lastSignature: Signature;
54
55
  };
55
56
  export type DoneMessage = {
56
- action: "done";
57
- id: RawCoID;
57
+ action: "done";
58
+ id: RawCoID;
58
59
  };
59
60
 
60
61
  export type PeerID = string;
@@ -64,661 +65,708 @@ export type DisconnectedError = "Disconnected";
64
65
  export type PingTimeoutError = "PingTimeout";
65
66
 
66
67
  export type IncomingSyncStream = AsyncIterable<
67
- SyncMessage | DisconnectedError | PingTimeoutError
68
+ SyncMessage | DisconnectedError | PingTimeoutError
68
69
  >;
69
70
  export type OutgoingSyncQueue = {
70
- push: (msg: SyncMessage) => Promise<unknown>;
71
- close: () => void;
71
+ push: (msg: SyncMessage) => Promise<unknown>;
72
+ close: () => void;
72
73
  };
73
74
 
74
75
  export interface Peer {
75
- id: PeerID;
76
- incoming: IncomingSyncStream;
77
- outgoing: OutgoingSyncQueue;
78
- role: "peer" | "server" | "client" | "storage";
79
- priority?: number;
80
- crashOnClose: boolean;
76
+ id: PeerID;
77
+ incoming: IncomingSyncStream;
78
+ outgoing: OutgoingSyncQueue;
79
+ role: "peer" | "server" | "client" | "storage";
80
+ priority?: number;
81
+ crashOnClose: boolean;
81
82
  }
82
83
 
83
84
  export function combinedKnownStates(
84
- stateA: CoValueKnownState,
85
- stateB: CoValueKnownState,
85
+ stateA: CoValueKnownState,
86
+ stateB: CoValueKnownState,
86
87
  ): CoValueKnownState {
87
- const sessionStates: CoValueKnownState["sessions"] = {};
88
+ const sessionStates: CoValueKnownState["sessions"] = {};
88
89
 
89
- const allSessions = new Set([
90
- ...Object.keys(stateA.sessions),
91
- ...Object.keys(stateB.sessions),
92
- ] as SessionID[]);
90
+ const allSessions = new Set([
91
+ ...Object.keys(stateA.sessions),
92
+ ...Object.keys(stateB.sessions),
93
+ ] as SessionID[]);
93
94
 
94
- for (const sessionID of allSessions) {
95
- const stateAValue = stateA.sessions[sessionID];
96
- const stateBValue = stateB.sessions[sessionID];
95
+ for (const sessionID of allSessions) {
96
+ const stateAValue = stateA.sessions[sessionID];
97
+ const stateBValue = stateB.sessions[sessionID];
97
98
 
98
- sessionStates[sessionID] = Math.max(stateAValue || 0, stateBValue || 0);
99
- }
99
+ sessionStates[sessionID] = Math.max(stateAValue || 0, stateBValue || 0);
100
+ }
100
101
 
101
- return {
102
- id: stateA.id,
103
- header: stateA.header || stateB.header,
104
- sessions: sessionStates,
105
- };
102
+ return {
103
+ id: stateA.id,
104
+ header: stateA.header || stateB.header,
105
+ sessions: sessionStates,
106
+ };
106
107
  }
107
108
 
108
109
  export class SyncManager {
109
- peers: { [key: PeerID]: PeerState } = {};
110
- local: LocalNode;
111
- requestedSyncs: {
112
- [id: RawCoID]:
113
- | { done: Promise<void>; nRequestsThisTick: number }
114
- | undefined;
115
- } = {};
116
-
117
- constructor(local: LocalNode) {
118
- this.local = local;
119
- }
120
-
121
- peersInPriorityOrder(): PeerState[] {
122
- return Object.values(this.peers).sort((a, b) => {
123
- const aPriority = a.priority || 0;
124
- const bPriority = b.priority || 0;
110
+ peers: { [key: PeerID]: PeerState } = {};
111
+ local: LocalNode;
112
+ requestedSyncs: {
113
+ [id: RawCoID]:
114
+ | { done: Promise<void>; nRequestsThisTick: number }
115
+ | undefined;
116
+ } = {};
117
+
118
+ constructor(local: LocalNode) {
119
+ this.local = local;
120
+ this.syncStateSubscriptionManager = new SyncStateSubscriptionManager(this);
121
+ }
122
+
123
+ syncStateSubscriptionManager: SyncStateSubscriptionManager;
124
+
125
+ peersInPriorityOrder(): PeerState[] {
126
+ return Object.values(this.peers).sort((a, b) => {
127
+ const aPriority = a.priority || 0;
128
+ const bPriority = b.priority || 0;
129
+
130
+ return bPriority - aPriority;
131
+ });
132
+ }
133
+
134
+ getPeers(): PeerState[] {
135
+ return Object.values(this.peers);
136
+ }
137
+
138
+ async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
139
+ const eligiblePeers = this.peersInPriorityOrder().filter(
140
+ (peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
141
+ );
142
+
143
+ const coValueEntry = this.local.coValues[id];
144
+
145
+ for (const peer of eligiblePeers) {
146
+ await peer.pushOutgoingMessage({
147
+ action: "load",
148
+ id: id,
149
+ header: false,
150
+ sessions: {},
151
+ });
125
152
 
126
- return bPriority - aPriority;
127
- });
153
+ if (coValueEntry?.state.type === "unknown") {
154
+ await coValueEntry.state.waitForPeer(peer.id);
155
+ }
128
156
  }
129
-
130
- async loadFromPeers(id: RawCoID, forPeer?: PeerID) {
131
- const eligiblePeers = this.peersInPriorityOrder().filter(
132
- (peer) => peer.id !== forPeer && peer.isServerOrStoragePeer(),
157
+ }
158
+
159
+ async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
160
+ // TODO: validate
161
+ switch (msg.action) {
162
+ case "load":
163
+ return await this.handleLoad(msg, peer);
164
+ case "known":
165
+ if (msg.isCorrection) {
166
+ return await this.handleCorrection(msg, peer);
167
+ } else {
168
+ return await this.handleKnownState(msg, peer);
169
+ }
170
+ case "content":
171
+ // await new Promise<void>((resolve) => setTimeout(resolve, 0));
172
+ return await this.handleNewContent(msg, peer);
173
+ case "done":
174
+ return await this.handleUnsubscribe(msg);
175
+ default:
176
+ throw new Error(
177
+ `Unknown message type ${(msg as { action: "string" }).action}`,
133
178
  );
179
+ }
180
+ }
134
181
 
135
- const coValueEntry = this.local.coValues[id];
136
-
137
- for (const peer of eligiblePeers) {
138
- await peer.pushOutgoingMessage({
139
- action: "load",
140
- id: id,
141
- header: false,
142
- sessions: {},
143
- });
182
+ async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
183
+ const entry = this.local.coValues[id];
144
184
 
145
- if (coValueEntry?.state.type === "unknown") {
146
- await coValueEntry.state.waitForPeer(peer.id);
147
- }
148
- }
185
+ if (!entry) {
186
+ throw new Error("Expected coValue entry on subscribe");
149
187
  }
150
188
 
151
- async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
152
- // TODO: validate
153
- switch (msg.action) {
154
- case "load":
155
- return await this.handleLoad(msg, peer);
156
- case "known":
157
- if (msg.isCorrection) {
158
- return await this.handleCorrection(msg, peer);
159
- } else {
160
- return await this.handleKnownState(msg, peer);
161
- }
162
- case "content":
163
- // await new Promise<void>((resolve) => setTimeout(resolve, 0));
164
- return await this.handleNewContent(msg, peer);
165
- case "done":
166
- return await this.handleUnsubscribe(msg);
167
- default:
168
- throw new Error(
169
- `Unknown message type ${
170
- (msg as { action: "string" }).action
171
- }`,
172
- );
173
- }
189
+ if (entry.state.type === "unknown") {
190
+ this.trySendToPeer(peer, {
191
+ action: "load",
192
+ id,
193
+ header: false,
194
+ sessions: {},
195
+ }).catch((e: unknown) => {
196
+ console.error("Error sending load", e);
197
+ });
198
+ return;
174
199
  }
175
200
 
176
- async subscribeToIncludingDependencies(id: RawCoID, peer: PeerState) {
177
- const entry = this.local.coValues[id];
201
+ const coValue = entry.state.coValue;
178
202
 
179
- if (!entry) {
180
- throw new Error("Expected coValue entry on subscribe");
181
- }
203
+ for (const id of coValue.getDependedOnCoValues()) {
204
+ await this.subscribeToIncludingDependencies(id, peer);
205
+ }
182
206
 
183
- if (entry.state.type === "unknown") {
184
- this.trySendToPeer(peer, {
185
- action: "load",
186
- id,
187
- header: false,
188
- sessions: {},
189
- }).catch((e: unknown) => {
190
- console.error("Error sending load", e);
207
+ if (!peer.toldKnownState.has(id)) {
208
+ peer.toldKnownState.add(id);
209
+ this.trySendToPeer(peer, {
210
+ action: "load",
211
+ ...coValue.knownState(),
212
+ }).catch((e: unknown) => {
213
+ console.error("Error sending load", e);
214
+ });
215
+ }
216
+ }
217
+
218
+ async tellUntoldKnownStateIncludingDependencies(
219
+ id: RawCoID,
220
+ peer: PeerState,
221
+ asDependencyOf?: RawCoID,
222
+ ) {
223
+ const coValue = this.local.expectCoValueLoaded(id);
224
+
225
+ await Promise.all(
226
+ coValue
227
+ .getDependedOnCoValues()
228
+ .map((dependentCoID) =>
229
+ this.tellUntoldKnownStateIncludingDependencies(
230
+ dependentCoID,
231
+ peer,
232
+ asDependencyOf || id,
233
+ ),
234
+ ),
235
+ );
236
+
237
+ if (!peer.toldKnownState.has(id)) {
238
+ this.trySendToPeer(peer, {
239
+ action: "known",
240
+ asDependencyOf,
241
+ ...coValue.knownState(),
242
+ }).catch((e: unknown) => {
243
+ console.error("Error sending known state", e);
244
+ });
245
+
246
+ peer.toldKnownState.add(id);
247
+ }
248
+ }
249
+
250
+ async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
251
+ const coValue = this.local.expectCoValueLoaded(id);
252
+
253
+ await Promise.all(
254
+ coValue
255
+ .getDependedOnCoValues()
256
+ .map((id) => this.sendNewContentIncludingDependencies(id, peer)),
257
+ );
258
+
259
+ const newContentPieces = coValue.newContentSince(
260
+ peer.optimisticKnownStates.get(id),
261
+ );
262
+
263
+ if (newContentPieces) {
264
+ const optimisticKnownStateBefore =
265
+ peer.optimisticKnownStates.get(id) || emptyKnownState(id);
266
+
267
+ const sendPieces = async () => {
268
+ let lastYield = performance.now();
269
+ for (const [_i, piece] of newContentPieces.entries()) {
270
+ // console.log(
271
+ // `${id} -> ${peer.id}: Sending content piece ${i + 1}/${
272
+ // newContentPieces.length
273
+ // } header: ${!!piece.header}`,
274
+ // // Object.values(piece.new).map((s) => s.newTransactions)
275
+ // );
276
+
277
+ this.trySendToPeer(peer, piece).catch((e: unknown) => {
278
+ console.error("Error sending content piece", e);
279
+ });
280
+
281
+ if (performance.now() - lastYield > 10) {
282
+ await new Promise<void>((resolve) => {
283
+ setTimeout(resolve, 0);
191
284
  });
192
- return;
285
+ lastYield = performance.now();
286
+ }
193
287
  }
288
+ };
194
289
 
195
- const coValue = entry.state.coValue;
196
-
197
- for (const id of coValue.getDependedOnCoValues()) {
198
- await this.subscribeToIncludingDependencies(id, peer);
199
- }
290
+ sendPieces().catch((e) => {
291
+ console.error("Error sending new content piece, retrying", e);
292
+ peer.optimisticKnownStates.dispatch({
293
+ type: "SET",
294
+ id,
295
+ value: optimisticKnownStateBefore ?? emptyKnownState(id),
296
+ });
297
+ return this.sendNewContentIncludingDependencies(id, peer);
298
+ });
200
299
 
201
- if (!peer.toldKnownState.has(id)) {
202
- peer.toldKnownState.add(id);
203
- this.trySendToPeer(peer, {
204
- action: "load",
205
- ...coValue.knownState(),
206
- }).catch((e: unknown) => {
207
- console.error("Error sending load", e);
208
- });
209
- }
300
+ peer.optimisticKnownStates.dispatch({
301
+ type: "COMBINE_WITH",
302
+ id,
303
+ value: coValue.knownState(),
304
+ });
210
305
  }
306
+ }
211
307
 
212
- async tellUntoldKnownStateIncludingDependencies(
213
- id: RawCoID,
214
- peer: PeerState,
215
- asDependencyOf?: RawCoID,
216
- ) {
217
- const coValue = this.local.expectCoValueLoaded(id);
218
-
219
- await Promise.all(
220
- coValue
221
- .getDependedOnCoValues()
222
- .map((dependentCoID) =>
223
- this.tellUntoldKnownStateIncludingDependencies(
224
- dependentCoID,
225
- peer,
226
- asDependencyOf || id,
227
- ),
228
- ),
229
- );
230
-
231
- if (!peer.toldKnownState.has(id)) {
232
- this.trySendToPeer(peer, {
233
- action: "known",
234
- asDependencyOf,
235
- ...coValue.knownState(),
236
- }).catch((e: unknown) => {
237
- console.error("Error sending known state", e);
238
- });
308
+ addPeer(peer: Peer) {
309
+ const prevPeer = this.peers[peer.id];
310
+ const peerState = new PeerState(peer, prevPeer?.knownStates);
311
+ this.peers[peer.id] = peerState;
239
312
 
240
- peer.toldKnownState.add(id);
241
- }
313
+ if (prevPeer && !prevPeer.closed) {
314
+ prevPeer.gracefulShutdown();
242
315
  }
243
316
 
244
- async sendNewContentIncludingDependencies(id: RawCoID, peer: PeerState) {
245
- const coValue = this.local.expectCoValueLoaded(id);
246
-
247
- await Promise.all(
248
- coValue
249
- .getDependedOnCoValues()
250
- .map((id) =>
251
- this.sendNewContentIncludingDependencies(id, peer),
252
- ),
253
- );
254
-
255
- const newContentPieces = coValue.newContentSince(
256
- peer.optimisticKnownStates.get(id),
257
- );
258
-
259
- if (newContentPieces) {
260
- const optimisticKnownStateBefore =
261
- peer.optimisticKnownStates.get(id) || emptyKnownState(id);
262
-
263
- const sendPieces = async () => {
264
- let lastYield = performance.now();
265
- for (const [_i, piece] of newContentPieces.entries()) {
266
- // console.log(
267
- // `${id} -> ${peer.id}: Sending content piece ${i + 1}/${
268
- // newContentPieces.length
269
- // } header: ${!!piece.header}`,
270
- // // Object.values(piece.new).map((s) => s.newTransactions)
271
- // );
272
-
273
- this.trySendToPeer(peer, piece).catch((e: unknown) => {
274
- console.error("Error sending content piece", e);
275
- });
276
-
277
- if (performance.now() - lastYield > 10) {
278
- await new Promise<void>((resolve) => {
279
- setTimeout(resolve, 0);
280
- });
281
- lastYield = performance.now();
282
- }
283
- }
284
- };
285
-
286
- sendPieces().catch((e) => {
287
- console.error("Error sending new content piece, retrying", e);
288
- peer.optimisticKnownStates.dispatch({
289
- type: "SET",
290
- id,
291
- value: optimisticKnownStateBefore ?? emptyKnownState(id),
292
- });
293
- return this.sendNewContentIncludingDependencies(id, peer);
294
- });
295
-
296
- peer.optimisticKnownStates.dispatch({
297
- type: "COMBINE_WITH",
298
- id,
299
- value: coValue.knownState(),
300
- });
317
+ const unsubscribeFromKnownStatesUpdates = peerState.knownStates.subscribe(
318
+ (id) => {
319
+ this.syncStateSubscriptionManager.triggerUpdate(peer.id, id);
320
+ },
321
+ );
322
+
323
+ if (peerState.isServerOrStoragePeer()) {
324
+ const initialSync = async () => {
325
+ for (const id of Object.keys(this.local.coValues) as RawCoID[]) {
326
+ // console.log("subscribing to after peer added", id, peer.id)
327
+ await this.subscribeToIncludingDependencies(id, peerState);
328
+
329
+ peerState.optimisticKnownStates.dispatch({
330
+ type: "SET_AS_EMPTY",
331
+ id,
332
+ });
301
333
  }
334
+ };
335
+ void initialSync();
302
336
  }
303
337
 
304
- addPeer(peer: Peer) {
305
- const peerState = new PeerState(peer);
306
- this.peers[peer.id] = peerState;
307
-
308
- if (peerState.isServerOrStoragePeer()) {
309
- const initialSync = async () => {
310
- for (const id of Object.keys(
311
- this.local.coValues,
312
- ) as RawCoID[]) {
313
- // console.log("subscribing to after peer added", id, peer.id)
314
- await this.subscribeToIncludingDependencies(id, peerState);
315
-
316
- peerState.optimisticKnownStates.dispatch({
317
- type: "SET_AS_EMPTY",
318
- id,
319
- });
320
- }
321
- };
322
- void initialSync();
338
+ const processMessages = async () => {
339
+ for await (const msg of peerState.incoming) {
340
+ if (msg === "Disconnected") {
341
+ return;
323
342
  }
324
-
325
- const processMessages = async () => {
326
- for await (const msg of peerState.incoming) {
327
- if (msg === "Disconnected") {
328
- return;
329
- }
330
- if (msg === "PingTimeout") {
331
- console.error("Ping timeout from peer", peer.id);
332
- return;
333
- }
334
- try {
335
- await this.handleSyncMessage(msg, peerState);
336
- } catch (e) {
337
- throw new Error(
338
- `Error reading from peer ${
339
- peer.id
340
- }, handling msg\n\n${JSON.stringify(msg, (k, v) =>
341
- k === "changes" || k === "encryptedChanges"
342
- ? v.slice(0, 20) + "..."
343
- : v,
344
- )}`,
345
- { cause: e },
346
- );
347
- }
348
- }
349
- };
350
-
351
- processMessages()
352
- .then(() => {
353
- if (peer.crashOnClose) {
354
- console.error("Unexepcted close from peer", peer.id);
355
- this.local.crashed = new Error(
356
- "Unexpected close from peer",
357
- );
358
- throw new Error("Unexpected close from peer");
359
- }
360
- })
361
- .catch((e) => {
362
- console.error(
363
- "Error processing messages from peer",
364
- peer.id,
365
- e,
366
- );
367
- if (peer.crashOnClose) {
368
- this.local.crashed = e;
369
- throw new Error(e);
370
- }
371
- })
372
- .finally(() => {
373
- peer.outgoing.close();
374
- delete this.peers[peer.id];
375
- });
376
- }
377
-
378
- trySendToPeer(peer: PeerState, msg: SyncMessage) {
379
- return peer.pushOutgoingMessage(msg);
380
- }
381
-
382
- async handleLoad(msg: LoadMessage, peer: PeerState) {
383
- peer.optimisticKnownStates.dispatch({
384
- type: "SET",
385
- id: msg.id,
386
- value: knownStateIn(msg),
387
- });
388
- let entry = this.local.coValues[msg.id];
389
-
390
- if (!entry) {
391
- // console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
392
-
393
- // special case: we should be able to solve this much more neatly
394
- // with an explicit state machine in the future
395
- const eligiblePeers = this.peersInPriorityOrder().filter(
396
- (other) => other.id !== peer.id && other.isServerOrStoragePeer(),
397
- );
398
- if (eligiblePeers.length === 0) {
399
- if (msg.header || Object.keys(msg.sessions).length > 0) {
400
- this.local.coValues[msg.id] = CoValueState.Unknown(
401
- new Set([peer.id]),
402
- );
403
- this.trySendToPeer(peer, {
404
- action: "known",
405
- id: msg.id,
406
- header: false,
407
- sessions: {},
408
- }).catch((e) => {
409
- console.error("Error sending known state", e);
410
- });
411
- }
412
- return;
413
- } else {
414
- this.local
415
- .loadCoValueCore(msg.id, {
416
- dontLoadFrom: peer.id,
417
- dontWaitFor: peer.id,
418
- })
419
- .catch((e) => {
420
- console.error("Error loading coValue in handleLoad", e);
421
- });
422
- }
423
-
424
- entry = this.local.coValues[msg.id]!;
343
+ if (msg === "PingTimeout") {
344
+ console.error("Ping timeout from peer", peer.id);
345
+ return;
425
346
  }
426
-
427
- if (entry.state.type === "unknown") {
428
- // console.debug(
429
- // "Waiting for loaded",
430
- // msg.id,
431
- // "after message from",
432
- // peer.id,
433
- // );
434
- const loaded = await entry.state.ready;
435
-
436
- if (loaded === "unavailable") {
437
- peer.optimisticKnownStates.dispatch({
438
- type: "SET",
439
- id: msg.id,
440
- value: knownStateIn(msg),
441
- });
442
- peer.toldKnownState.add(msg.id);
443
-
444
- this.trySendToPeer(peer, {
445
- action: "known",
446
- id: msg.id,
447
- header: false,
448
- sessions: {},
449
- }).catch((e) => {
450
- console.error("Error sending known state back", e);
451
- });
452
-
453
- return;
454
- }
347
+ try {
348
+ await this.handleSyncMessage(msg, peerState);
349
+ } catch (e) {
350
+ throw new Error(
351
+ `Error reading from peer ${
352
+ peer.id
353
+ }, handling msg\n\n${JSON.stringify(msg, (k, v) =>
354
+ k === "changes" || k === "encryptedChanges"
355
+ ? v.slice(0, 20) + "..."
356
+ : v,
357
+ )}`,
358
+ { cause: e },
359
+ );
455
360
  }
361
+ }
362
+ };
456
363
 
457
- await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
458
- await this.sendNewContentIncludingDependencies(msg.id, peer);
364
+ processMessages()
365
+ .then(() => {
366
+ if (peer.crashOnClose) {
367
+ console.error("Unexepcted close from peer", peer.id);
368
+ this.local.crashed = new Error("Unexpected close from peer");
369
+ throw new Error("Unexpected close from peer");
370
+ }
371
+ })
372
+ .catch((e) => {
373
+ console.error("Error processing messages from peer", peer.id, e);
374
+ if (peer.crashOnClose) {
375
+ this.local.crashed = e;
376
+ throw new Error(e);
377
+ }
378
+ })
379
+ .finally(() => {
380
+ const state = this.peers[peer.id];
381
+ state?.gracefulShutdown();
382
+ unsubscribeFromKnownStatesUpdates();
383
+ });
384
+ }
385
+
386
+ trySendToPeer(peer: PeerState, msg: SyncMessage) {
387
+ return peer.pushOutgoingMessage(msg);
388
+ }
389
+
390
+ async handleLoad(msg: LoadMessage, peer: PeerState) {
391
+ peer.optimisticKnownStates.dispatch({
392
+ type: "SET",
393
+ id: msg.id,
394
+ value: knownStateIn(msg),
395
+ });
396
+ let entry = this.local.coValues[msg.id];
397
+
398
+ if (!entry) {
399
+ // console.log(`Loading ${msg.id} from all peers except ${peer.id}`);
400
+
401
+ // special case: we should be able to solve this much more neatly
402
+ // with an explicit state machine in the future
403
+ const eligiblePeers = this.peersInPriorityOrder().filter(
404
+ (other) => other.id !== peer.id && other.isServerOrStoragePeer(),
405
+ );
406
+ if (eligiblePeers.length === 0) {
407
+ if (msg.header || Object.keys(msg.sessions).length > 0) {
408
+ this.local.coValues[msg.id] = CoValueState.Unknown(
409
+ new Set([peer.id]),
410
+ );
411
+ this.trySendToPeer(peer, {
412
+ action: "known",
413
+ id: msg.id,
414
+ header: false,
415
+ sessions: {},
416
+ }).catch((e) => {
417
+ console.error("Error sending known state", e);
418
+ });
419
+ }
420
+ return;
421
+ } else {
422
+ this.local
423
+ .loadCoValueCore(msg.id, {
424
+ dontLoadFrom: peer.id,
425
+ dontWaitFor: peer.id,
426
+ })
427
+ .catch((e) => {
428
+ console.error("Error loading coValue in handleLoad", e);
429
+ });
430
+ }
431
+
432
+ entry = this.local.coValues[msg.id]!;
459
433
  }
460
434
 
461
- async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
462
- let entry = this.local.coValues[msg.id];
435
+ if (entry.state.type === "unknown") {
436
+ // console.debug(
437
+ // "Waiting for loaded",
438
+ // msg.id,
439
+ // "after message from",
440
+ // peer.id,
441
+ // );
442
+ const loaded = await entry.state.ready;
463
443
 
444
+ if (loaded === "unavailable") {
464
445
  peer.optimisticKnownStates.dispatch({
465
- type: "COMBINE_WITH",
466
- id: msg.id,
467
- value: knownStateIn(msg),
446
+ type: "SET",
447
+ id: msg.id,
448
+ value: knownStateIn(msg),
449
+ });
450
+ peer.toldKnownState.add(msg.id);
451
+
452
+ this.trySendToPeer(peer, {
453
+ action: "known",
454
+ id: msg.id,
455
+ header: false,
456
+ sessions: {},
457
+ }).catch((e) => {
458
+ console.error("Error sending known state back", e);
468
459
  });
469
460
 
470
- if (!entry) {
471
- if (msg.asDependencyOf) {
472
- if (this.local.coValues[msg.asDependencyOf]) {
473
- this.local
474
- .loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
475
- .catch((e) => {
476
- console.error(
477
- `Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
478
- e,
479
- );
480
- });
481
- entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
482
- } else {
483
- throw new Error(
484
- "Expected coValue dependency entry to be created, missing subscribe?",
485
- );
486
- }
487
- } else {
488
- throw new Error(
489
- `Expected coValue entry for ${msg.id} to be created on known state, missing subscribe?`,
490
- );
491
- }
492
- }
493
-
494
- if (entry.state.type === "unknown") {
495
- const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
496
-
497
- if (!availableOnPeer) {
498
- entry.dispatch({
499
- type: "not-found",
500
- peerId: peer.id,
501
- });
502
- }
503
-
504
- return;
505
- }
506
-
507
- await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
508
- await this.sendNewContentIncludingDependencies(msg.id, peer);
461
+ return;
462
+ }
509
463
  }
510
464
 
511
- async handleNewContent(msg: NewContentMessage, peer: PeerState) {
512
- const entry = this.local.coValues[msg.id];
513
-
514
- if (!entry) {
515
- console.error(
516
- `Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
517
- );
518
- return;
519
- }
520
-
521
- let coValue: CoValueCore;
522
-
523
- if (entry.state.type === "unknown") {
524
- if (!msg.header) {
525
- console.error("Expected header to be sent in first message");
526
- return;
527
- }
528
-
529
- peer.optimisticKnownStates.dispatch({
530
- type: "UPDATE_HEADER",
531
- id: msg.id,
532
- header: true,
533
- });
534
-
535
- coValue = new CoValueCore(msg.header, this.local);
536
-
537
- entry.dispatch({
538
- type: "found",
539
- coValue,
540
- peerId: peer.id,
465
+ await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
466
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
467
+ }
468
+
469
+ async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
470
+ let entry = this.local.coValues[msg.id];
471
+
472
+ peer.optimisticKnownStates.dispatch({
473
+ type: "COMBINE_WITH",
474
+ id: msg.id,
475
+ value: knownStateIn(msg),
476
+ });
477
+
478
+ peer.knownStates.dispatch({
479
+ type: "COMBINE_WITH",
480
+ id: msg.id,
481
+ value: knownStateIn(msg),
482
+ });
483
+
484
+ if (!entry) {
485
+ if (msg.asDependencyOf) {
486
+ if (this.local.coValues[msg.asDependencyOf]) {
487
+ this.local
488
+ .loadCoValueCore(msg.id, { dontLoadFrom: peer.id })
489
+ .catch((e) => {
490
+ console.error(
491
+ `Error loading coValue ${msg.id} to create loading state, as dependency of ${msg.asDependencyOf}`,
492
+ e,
493
+ );
541
494
  });
495
+ entry = this.local.coValues[msg.id]!; // must exist after loadCoValueCore
542
496
  } else {
543
- coValue = entry.state.coValue;
497
+ throw new Error(
498
+ "Expected coValue dependency entry to be created, missing subscribe?",
499
+ );
544
500
  }
501
+ } else {
502
+ throw new Error(
503
+ `Expected coValue entry for ${msg.id} to be created on known state, missing subscribe?`,
504
+ );
505
+ }
506
+ }
545
507
 
546
- let invalidStateAssumed = false;
508
+ if (entry.state.type === "unknown") {
509
+ const availableOnPeer = peer.optimisticKnownStates.get(msg.id)?.header;
547
510
 
548
- for (const [sessionID, newContentForSession] of Object.entries(
549
- msg.new,
550
- ) as [SessionID, SessionNewContent][]) {
551
- const ourKnownTxIdx =
552
- coValue.sessionLogs.get(sessionID)?.transactions.length;
553
- const theirFirstNewTxIdx = newContentForSession.after;
511
+ if (!availableOnPeer) {
512
+ entry.dispatch({
513
+ type: "not-found",
514
+ peerId: peer.id,
515
+ });
516
+ }
554
517
 
555
- if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
556
- invalidStateAssumed = true;
557
- continue;
558
- }
518
+ return;
519
+ }
559
520
 
560
- const alreadyKnownOffset = ourKnownTxIdx
561
- ? ourKnownTxIdx - theirFirstNewTxIdx
562
- : 0;
521
+ await this.tellUntoldKnownStateIncludingDependencies(msg.id, peer);
522
+ await this.sendNewContentIncludingDependencies(msg.id, peer);
523
+ }
563
524
 
564
- const newTransactions =
565
- newContentForSession.newTransactions.slice(alreadyKnownOffset);
525
+ async handleNewContent(msg: NewContentMessage, peer: PeerState) {
526
+ const entry = this.local.coValues[msg.id];
566
527
 
567
- if (newTransactions.length === 0) {
568
- continue;
569
- }
528
+ if (!entry) {
529
+ console.error(
530
+ `Expected coValue entry for ${msg.id} to be created on new content, missing subscribe?`,
531
+ );
532
+ return;
533
+ }
570
534
 
571
- const before = performance.now();
572
- // eslint-disable-next-line neverthrow/must-use-result
573
- const result = coValue.tryAddTransactions(
574
- sessionID,
575
- newTransactions,
576
- undefined,
577
- newContentForSession.lastSignature,
578
- );
579
- const after = performance.now();
580
- if (after - before > 80) {
581
- const totalTxLength = newTransactions
582
- .map((t) =>
583
- t.privacy === "private"
584
- ? t.encryptedChanges.length
585
- : t.changes.length,
586
- )
587
- .reduce((a, b) => a + b, 0);
588
- console.log(
589
- `Adding incoming transactions took ${(
590
- after - before
591
- ).toFixed(2)}ms for ${totalTxLength} bytes = bandwidth: ${(
592
- (1000 * totalTxLength) /
593
- (after - before) /
594
- (1024 * 1024)
595
- ).toFixed(2)} MB/s`,
596
- );
597
- }
535
+ let coValue: CoValueCore;
598
536
 
599
- // const theirTotalnTxs = Object.values(
600
- // peer.optimisticKnownStates[msg.id]?.sessions || {},
601
- // ).reduce((sum, nTxs) => sum + nTxs, 0);
602
- // const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
603
- // (sum, session) => sum + session.transactions.length,
604
- // 0,
605
- // );
606
-
607
- if (result.isErr()) {
608
- console.error(
609
- "Failed to add transactions from",
610
- peer.id,
611
- result.error,
612
- msg.id,
613
- newTransactions.length + " new transactions",
614
- "after: " + newContentForSession.after,
615
- "our last known tx idx initially: " + ourKnownTxIdx,
616
- "our last known tx idx now: " +
617
- coValue.sessionLogs.get(sessionID)?.transactions.length,
618
- );
619
- continue;
620
- }
537
+ if (entry.state.type === "unknown") {
538
+ if (!msg.header) {
539
+ console.error("Expected header to be sent in first message");
540
+ return;
541
+ }
621
542
 
622
- peer.optimisticKnownStates.dispatch({
623
- type: "UPDATE_SESSION_COUNTER",
624
- id: msg.id,
625
- sessionId: sessionID,
626
- value:
627
- newContentForSession.after +
628
- newContentForSession.newTransactions.length,
629
- });
630
- }
543
+ peer.optimisticKnownStates.dispatch({
544
+ type: "UPDATE_HEADER",
545
+ id: msg.id,
546
+ header: true,
547
+ });
548
+
549
+ coValue = new CoValueCore(msg.header, this.local);
550
+
551
+ entry.dispatch({
552
+ type: "found",
553
+ coValue,
554
+ peerId: peer.id,
555
+ });
556
+ } else {
557
+ coValue = entry.state.coValue;
558
+ }
631
559
 
632
- await this.syncCoValue(coValue);
560
+ let invalidStateAssumed = false;
561
+
562
+ for (const [sessionID, newContentForSession] of Object.entries(msg.new) as [
563
+ SessionID,
564
+ SessionNewContent,
565
+ ][]) {
566
+ const ourKnownTxIdx =
567
+ coValue.sessionLogs.get(sessionID)?.transactions.length;
568
+ const theirFirstNewTxIdx = newContentForSession.after;
569
+
570
+ if ((ourKnownTxIdx || 0) < theirFirstNewTxIdx) {
571
+ invalidStateAssumed = true;
572
+ continue;
573
+ }
574
+
575
+ const alreadyKnownOffset = ourKnownTxIdx
576
+ ? ourKnownTxIdx - theirFirstNewTxIdx
577
+ : 0;
578
+
579
+ const newTransactions =
580
+ newContentForSession.newTransactions.slice(alreadyKnownOffset);
581
+
582
+ if (newTransactions.length === 0) {
583
+ continue;
584
+ }
585
+
586
+ const before = performance.now();
587
+ // eslint-disable-next-line neverthrow/must-use-result
588
+ const result = coValue.tryAddTransactions(
589
+ sessionID,
590
+ newTransactions,
591
+ undefined,
592
+ newContentForSession.lastSignature,
593
+ );
594
+ const after = performance.now();
595
+ if (after - before > 80) {
596
+ const totalTxLength = newTransactions
597
+ .map((t) =>
598
+ t.privacy === "private"
599
+ ? t.encryptedChanges.length
600
+ : t.changes.length,
601
+ )
602
+ .reduce((a, b) => a + b, 0);
603
+ console.log(
604
+ `Adding incoming transactions took ${(after - before).toFixed(
605
+ 2,
606
+ )}ms for ${totalTxLength} bytes = bandwidth: ${(
607
+ (1000 * totalTxLength) / (after - before) / (1024 * 1024)
608
+ ).toFixed(2)} MB/s`,
609
+ );
610
+ }
611
+
612
+ // const theirTotalnTxs = Object.values(
613
+ // peer.optimisticKnownStates[msg.id]?.sessions || {},
614
+ // ).reduce((sum, nTxs) => sum + nTxs, 0);
615
+ // const ourTotalnTxs = [...coValue.sessionLogs.values()].reduce(
616
+ // (sum, session) => sum + session.transactions.length,
617
+ // 0,
618
+ // );
619
+
620
+ if (result.isErr()) {
621
+ console.error(
622
+ "Failed to add transactions from",
623
+ peer.id,
624
+ result.error,
625
+ msg.id,
626
+ newTransactions.length + " new transactions",
627
+ "after: " + newContentForSession.after,
628
+ "our last known tx idx initially: " + ourKnownTxIdx,
629
+ "our last known tx idx now: " +
630
+ coValue.sessionLogs.get(sessionID)?.transactions.length,
631
+ );
632
+ continue;
633
+ }
633
634
 
634
- if (invalidStateAssumed) {
635
- this.trySendToPeer(peer, {
636
- action: "known",
637
- isCorrection: true,
638
- ...coValue.knownState(),
639
- }).catch((e) => {
640
- console.error("Error sending known state correction", e);
641
- });
642
- }
635
+ peer.optimisticKnownStates.dispatch({
636
+ type: "UPDATE_SESSION_COUNTER",
637
+ id: msg.id,
638
+ sessionId: sessionID,
639
+ value:
640
+ newContentForSession.after +
641
+ newContentForSession.newTransactions.length,
642
+ });
643
643
  }
644
644
 
645
- async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
646
- peer.optimisticKnownStates.dispatch({
647
- type: "SET",
648
- id: msg.id,
649
- value: knownStateIn(msg),
645
+ await this.syncCoValue(coValue);
646
+
647
+ if (invalidStateAssumed) {
648
+ this.trySendToPeer(peer, {
649
+ action: "known",
650
+ isCorrection: true,
651
+ ...coValue.knownState(),
652
+ }).catch((e) => {
653
+ console.error("Error sending known state correction", e);
654
+ });
655
+ } else {
656
+ /**
657
+ * We are sending a known state message to the peer to acknowledge the
658
+ * receipt of the new content.
659
+ *
660
+ * This way the sender knows that the content has been received and applied
661
+ * and can update their peer's knownState accordingly.
662
+ */
663
+ this.trySendToPeer(peer, {
664
+ action: "known",
665
+ ...coValue.knownState(),
666
+ }).catch((e: unknown) => {
667
+ console.error("Error sending known state", e);
668
+ });
669
+ }
670
+ }
671
+
672
+ async handleCorrection(msg: KnownStateMessage, peer: PeerState) {
673
+ peer.optimisticKnownStates.dispatch({
674
+ type: "SET",
675
+ id: msg.id,
676
+ value: knownStateIn(msg),
677
+ });
678
+
679
+ return this.sendNewContentIncludingDependencies(msg.id, peer);
680
+ }
681
+
682
+ handleUnsubscribe(_msg: DoneMessage) {
683
+ throw new Error("Method not implemented.");
684
+ }
685
+
686
+ async syncCoValue(coValue: CoValueCore) {
687
+ if (this.requestedSyncs[coValue.id]) {
688
+ this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
689
+ return this.requestedSyncs[coValue.id]!.done;
690
+ } else {
691
+ const done = new Promise<void>((resolve) => {
692
+ queueMicrotask(async () => {
693
+ delete this.requestedSyncs[coValue.id];
694
+ // if (entry.nRequestsThisTick >= 2) {
695
+ // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
696
+ // }
697
+ await this.actuallySyncCoValue(coValue);
698
+ resolve();
650
699
  });
651
-
652
- return this.sendNewContentIncludingDependencies(msg.id, peer);
700
+ });
701
+ const entry = {
702
+ done,
703
+ nRequestsThisTick: 1,
704
+ };
705
+ this.requestedSyncs[coValue.id] = entry;
706
+ return done;
707
+ }
708
+ }
709
+
710
+ async actuallySyncCoValue(coValue: CoValueCore) {
711
+ // let blockingSince = performance.now();
712
+ for (const peer of this.peersInPriorityOrder()) {
713
+ if (peer.closed) continue;
714
+ // if (performance.now() - blockingSince > 5) {
715
+ // await new Promise<void>((resolve) => {
716
+ // setTimeout(resolve, 0);
717
+ // });
718
+ // blockingSince = performance.now();
719
+ // }
720
+ if (peer.optimisticKnownStates.has(coValue.id)) {
721
+ await this.tellUntoldKnownStateIncludingDependencies(coValue.id, peer);
722
+ await this.sendNewContentIncludingDependencies(coValue.id, peer);
723
+ } else if (peer.isServerOrStoragePeer()) {
724
+ await this.subscribeToIncludingDependencies(coValue.id, peer);
725
+ await this.sendNewContentIncludingDependencies(coValue.id, peer);
726
+ }
653
727
  }
654
728
 
655
- handleUnsubscribe(_msg: DoneMessage) {
656
- throw new Error("Method not implemented.");
729
+ for (const peer of this.getPeers()) {
730
+ this.syncStateSubscriptionManager.triggerUpdate(peer.id, coValue.id);
657
731
  }
732
+ }
658
733
 
659
- async syncCoValue(coValue: CoValueCore) {
660
- if (this.requestedSyncs[coValue.id]) {
661
- this.requestedSyncs[coValue.id]!.nRequestsThisTick++;
662
- return this.requestedSyncs[coValue.id]!.done;
663
- } else {
664
- const done = new Promise<void>((resolve) => {
665
- queueMicrotask(async () => {
666
- delete this.requestedSyncs[coValue.id];
667
- // if (entry.nRequestsThisTick >= 2) {
668
- // console.log("Syncing", coValue.id, "for", entry.nRequestsThisTick, "requests");
669
- // }
670
- await this.actuallySyncCoValue(coValue);
671
- resolve();
672
- });
673
- });
674
- const entry = {
675
- done,
676
- nRequestsThisTick: 1,
677
- };
678
- this.requestedSyncs[coValue.id] = entry;
679
- return done;
680
- }
734
+ async waitForUploadIntoPeer(peerId: PeerID, id: RawCoID) {
735
+ const isAlreadyUploaded =
736
+ this.syncStateSubscriptionManager.getIsCoValueFullyUploadedIntoPeer(
737
+ peerId,
738
+ id,
739
+ );
740
+
741
+ if (isAlreadyUploaded) {
742
+ return true;
681
743
  }
682
744
 
683
- async actuallySyncCoValue(coValue: CoValueCore) {
684
- // let blockingSince = performance.now();
685
- for (const peer of this.peersInPriorityOrder()) {
686
- // if (performance.now() - blockingSince > 5) {
687
- // await new Promise<void>((resolve) => {
688
- // setTimeout(resolve, 0);
689
- // });
690
- // blockingSince = performance.now();
691
- // }
692
- if (peer.optimisticKnownStates.has(coValue.id)) {
693
- await this.tellUntoldKnownStateIncludingDependencies(
694
- coValue.id,
695
- peer,
696
- );
697
- await this.sendNewContentIncludingDependencies(
698
- coValue.id,
699
- peer,
700
- );
701
- } else if (peer.isServerOrStoragePeer()) {
702
- await this.subscribeToIncludingDependencies(coValue.id, peer);
703
- await this.sendNewContentIncludingDependencies(
704
- coValue.id,
705
- peer,
706
- );
745
+ return new Promise((resolve) => {
746
+ const unsubscribe =
747
+ this.syncStateSubscriptionManager.subscribeToPeerUpdates(
748
+ peerId,
749
+ (knownState, uploadCompleted) => {
750
+ if (uploadCompleted && knownState.id === id) {
751
+ resolve(true);
752
+ unsubscribe?.();
707
753
  }
708
- }
709
- }
754
+ },
755
+ );
756
+ });
757
+ }
710
758
 
711
- gracefulShutdown() {
712
- for (const peer of Object.values(this.peers)) {
713
- peer.gracefulShutdown();
714
- }
759
+ gracefulShutdown() {
760
+ for (const peer of Object.values(this.peers)) {
761
+ peer.gracefulShutdown();
715
762
  }
763
+ }
716
764
  }
717
765
 
718
766
  function knownStateIn(msg: LoadMessage | KnownStateMessage) {
719
- return {
720
- id: msg.id,
721
- header: msg.header,
722
- sessions: msg.sessions,
723
- };
767
+ return {
768
+ id: msg.id,
769
+ header: msg.header,
770
+ sessions: msg.sessions,
771
+ };
724
772
  }