cojson 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/account.d.ts +57 -0
- package/dist/account.js +76 -0
- package/dist/account.js.map +1 -0
- package/dist/account.test.d.ts +1 -0
- package/dist/account.test.js +40 -0
- package/dist/account.test.js.map +1 -0
- package/dist/coValue.d.ts +16 -35
- package/dist/coValue.js +49 -112
- package/dist/coValue.js.map +1 -1
- package/dist/coValue.test.js +16 -16
- package/dist/coValue.test.js.map +1 -1
- package/dist/contentType.d.ts +9 -9
- package/dist/contentType.js.map +1 -1
- package/dist/contentType.test.js +13 -17
- package/dist/contentType.test.js.map +1 -1
- package/dist/contentTypes/coList.d.ts +3 -3
- package/dist/contentTypes/coList.js.map +1 -1
- package/dist/contentTypes/coMap.d.ts +31 -21
- package/dist/contentTypes/coMap.js +28 -0
- package/dist/contentTypes/coMap.js.map +1 -1
- package/dist/contentTypes/coStream.d.ts +3 -3
- package/dist/contentTypes/coStream.js.map +1 -1
- package/dist/contentTypes/static.d.ts +4 -4
- package/dist/contentTypes/static.js.map +1 -1
- package/dist/crypto.d.ts +45 -39
- package/dist/crypto.js +68 -49
- package/dist/crypto.js.map +1 -1
- package/dist/crypto.test.js +45 -49
- package/dist/crypto.test.js.map +1 -1
- package/dist/ids.d.ts +5 -3
- package/dist/ids.js +3 -1
- package/dist/ids.js.map +1 -1
- package/dist/index.d.ts +12 -14
- package/dist/index.js +6 -8
- package/dist/index.js.map +1 -1
- package/dist/jsonValue.d.ts +2 -2
- package/dist/node.d.ts +25 -15
- package/dist/node.js +88 -33
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +27 -33
- package/dist/permissions.js +55 -47
- package/dist/permissions.js.map +1 -1
- package/dist/permissions.test.js +231 -314
- package/dist/permissions.test.js.map +1 -1
- package/dist/sync.d.ts +26 -28
- package/dist/sync.js +81 -67
- package/dist/sync.js.map +1 -1
- package/dist/sync.test.js +194 -293
- package/dist/sync.test.js.map +1 -1
- package/dist/testUtils.d.ts +37 -0
- package/dist/testUtils.js +157 -0
- package/dist/testUtils.js.map +1 -0
- package/package.json +1 -1
- package/src/account.test.ts +67 -0
- package/src/account.ts +152 -0
- package/src/coValue.test.ts +17 -31
- package/src/coValue.ts +93 -179
- package/src/contentType.test.ts +18 -45
- package/src/contentType.ts +15 -13
- package/src/contentTypes/coList.ts +4 -4
- package/src/contentTypes/coMap.ts +55 -29
- package/src/contentTypes/coStream.ts +4 -4
- package/src/contentTypes/static.ts +5 -5
- package/src/crypto.test.ts +53 -59
- package/src/crypto.ts +123 -95
- package/src/ids.ts +9 -3
- package/src/index.ts +14 -25
- package/src/jsonValue.ts +2 -2
- package/src/node.ts +189 -61
- package/src/permissions.test.ts +370 -404
- package/src/permissions.ts +126 -109
- package/src/sync.test.ts +284 -426
- package/src/sync.ts +115 -105
- package/src/testUtils.ts +229 -0
package/src/sync.ts
CHANGED
|
@@ -8,43 +8,43 @@ import {
|
|
|
8
8
|
WritableStream,
|
|
9
9
|
WritableStreamDefaultWriter,
|
|
10
10
|
} from "isomorphic-streams";
|
|
11
|
-
import {
|
|
11
|
+
import { RawCoID, SessionID } from "./ids.js";
|
|
12
12
|
|
|
13
13
|
export type CoValueKnownState = {
|
|
14
|
-
|
|
14
|
+
id: RawCoID;
|
|
15
15
|
header: boolean;
|
|
16
16
|
sessions: { [sessionID: SessionID]: number };
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export function emptyKnownState(
|
|
19
|
+
export function emptyKnownState(id: RawCoID): CoValueKnownState {
|
|
20
20
|
return {
|
|
21
|
-
|
|
21
|
+
id,
|
|
22
22
|
header: false,
|
|
23
23
|
sessions: {},
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type SyncMessage =
|
|
28
|
-
|
|
|
29
|
-
|
|
|
28
|
+
| LoadMessage
|
|
29
|
+
| KnownStateMessage
|
|
30
30
|
| NewContentMessage
|
|
31
|
-
|
|
|
32
|
-
| UnsubscribeMessage;
|
|
31
|
+
| DoneMessage;
|
|
33
32
|
|
|
34
|
-
export type
|
|
35
|
-
action: "
|
|
33
|
+
export type LoadMessage = {
|
|
34
|
+
action: "load";
|
|
36
35
|
} & CoValueKnownState;
|
|
37
36
|
|
|
38
|
-
export type
|
|
39
|
-
action: "
|
|
40
|
-
asDependencyOf?:
|
|
37
|
+
export type KnownStateMessage = {
|
|
38
|
+
action: "known";
|
|
39
|
+
asDependencyOf?: RawCoID;
|
|
40
|
+
isCorrection?: boolean;
|
|
41
41
|
} & CoValueKnownState;
|
|
42
42
|
|
|
43
43
|
export type NewContentMessage = {
|
|
44
|
-
action: "
|
|
45
|
-
|
|
44
|
+
action: "content";
|
|
45
|
+
id: RawCoID;
|
|
46
46
|
header?: CoValueHeader;
|
|
47
|
-
|
|
47
|
+
new: {
|
|
48
48
|
[sessionID: SessionID]: SessionNewContent;
|
|
49
49
|
};
|
|
50
50
|
};
|
|
@@ -56,14 +56,9 @@ export type SessionNewContent = {
|
|
|
56
56
|
lastHash: Hash;
|
|
57
57
|
lastSignature: Signature;
|
|
58
58
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} & CoValueKnownState;
|
|
63
|
-
|
|
64
|
-
export type UnsubscribeMessage = {
|
|
65
|
-
action: "unsubscribe";
|
|
66
|
-
coValueID: RawCoValueID;
|
|
59
|
+
export type DoneMessage = {
|
|
60
|
+
action: "done";
|
|
61
|
+
id: RawCoID;
|
|
67
62
|
};
|
|
68
63
|
|
|
69
64
|
export type PeerID = string;
|
|
@@ -77,8 +72,8 @@ export interface Peer {
|
|
|
77
72
|
|
|
78
73
|
export interface PeerState {
|
|
79
74
|
id: PeerID;
|
|
80
|
-
optimisticKnownStates: { [
|
|
81
|
-
toldKnownState: Set<
|
|
75
|
+
optimisticKnownStates: { [id: RawCoID]: CoValueKnownState };
|
|
76
|
+
toldKnownState: Set<RawCoID>;
|
|
82
77
|
incoming: ReadableStream<SyncMessage>;
|
|
83
78
|
outgoing: WritableStreamDefaultWriter<SyncMessage>;
|
|
84
79
|
role: "peer" | "server" | "client";
|
|
@@ -103,7 +98,7 @@ export function combinedKnownStates(
|
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
return {
|
|
106
|
-
|
|
101
|
+
id: stateA.id,
|
|
107
102
|
header: stateA.header || stateB.header,
|
|
108
103
|
sessions: sessionStates,
|
|
109
104
|
};
|
|
@@ -117,12 +112,12 @@ export class SyncManager {
|
|
|
117
112
|
this.local = local;
|
|
118
113
|
}
|
|
119
114
|
|
|
120
|
-
loadFromPeers(id:
|
|
115
|
+
loadFromPeers(id: RawCoID) {
|
|
121
116
|
for (const peer of Object.values(this.peers)) {
|
|
122
117
|
peer.outgoing
|
|
123
118
|
.write({
|
|
124
|
-
action: "
|
|
125
|
-
|
|
119
|
+
action: "load",
|
|
120
|
+
id: id,
|
|
126
121
|
header: false,
|
|
127
122
|
sessions: {},
|
|
128
123
|
})
|
|
@@ -135,15 +130,17 @@ export class SyncManager {
|
|
|
135
130
|
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
136
131
|
// TODO: validate
|
|
137
132
|
switch (msg.action) {
|
|
138
|
-
case "
|
|
139
|
-
return await this.
|
|
140
|
-
case "
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
case "load":
|
|
134
|
+
return await this.handleLoad(msg, peer);
|
|
135
|
+
case "known":
|
|
136
|
+
if (msg.isCorrection) {
|
|
137
|
+
return await this.handleCorrection(msg, peer);
|
|
138
|
+
} else {
|
|
139
|
+
return await this.handleKnownState(msg, peer);
|
|
140
|
+
}
|
|
141
|
+
case "content":
|
|
143
142
|
return await this.handleNewContent(msg, peer);
|
|
144
|
-
case "
|
|
145
|
-
return await this.handleWrongAssumedKnownState(msg, peer);
|
|
146
|
-
case "unsubscribe":
|
|
143
|
+
case "done":
|
|
147
144
|
return await this.handleUnsubscribe(msg);
|
|
148
145
|
default:
|
|
149
146
|
throw new Error(
|
|
@@ -155,69 +152,87 @@ export class SyncManager {
|
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
async subscribeToIncludingDependencies(
|
|
158
|
-
|
|
155
|
+
id: RawCoID,
|
|
159
156
|
peer: PeerState
|
|
160
157
|
) {
|
|
161
|
-
const
|
|
158
|
+
const entry = this.local.coValues[id];
|
|
162
159
|
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
if (!entry) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
"Expected coValue entry on subscribe"
|
|
163
|
+
);
|
|
165
164
|
}
|
|
166
165
|
|
|
167
|
-
if (
|
|
168
|
-
peer.toldKnownState.add(coValueID);
|
|
166
|
+
if (entry.state === "loading") {
|
|
169
167
|
await this.trySendToPeer(peer, {
|
|
170
|
-
action: "
|
|
168
|
+
action: "load",
|
|
169
|
+
id,
|
|
170
|
+
header: false,
|
|
171
|
+
sessions: {},
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const coValue = entry.coValue;
|
|
177
|
+
|
|
178
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
179
|
+
await this.subscribeToIncludingDependencies(id, peer);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!peer.toldKnownState.has(id)) {
|
|
183
|
+
peer.toldKnownState.add(id);
|
|
184
|
+
await this.trySendToPeer(peer, {
|
|
185
|
+
action: "load",
|
|
171
186
|
...coValue.knownState(),
|
|
172
187
|
});
|
|
173
188
|
}
|
|
174
189
|
}
|
|
175
190
|
|
|
176
191
|
async tellUntoldKnownStateIncludingDependencies(
|
|
177
|
-
|
|
192
|
+
id: RawCoID,
|
|
178
193
|
peer: PeerState,
|
|
179
|
-
asDependencyOf?:
|
|
194
|
+
asDependencyOf?: RawCoID
|
|
180
195
|
) {
|
|
181
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
196
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
182
197
|
|
|
183
|
-
for (const
|
|
198
|
+
for (const dependentCoID of coValue.getDependedOnCoValues()) {
|
|
184
199
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
185
|
-
|
|
200
|
+
dependentCoID,
|
|
186
201
|
peer,
|
|
187
|
-
asDependencyOf ||
|
|
202
|
+
asDependencyOf || id
|
|
188
203
|
);
|
|
189
204
|
}
|
|
190
205
|
|
|
191
|
-
if (!peer.toldKnownState.has(
|
|
206
|
+
if (!peer.toldKnownState.has(id)) {
|
|
192
207
|
await this.trySendToPeer(peer, {
|
|
193
|
-
action: "
|
|
208
|
+
action: "known",
|
|
194
209
|
asDependencyOf,
|
|
195
210
|
...coValue.knownState(),
|
|
196
211
|
});
|
|
197
212
|
|
|
198
|
-
peer.toldKnownState.add(
|
|
213
|
+
peer.toldKnownState.add(id);
|
|
199
214
|
}
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
async sendNewContentIncludingDependencies(
|
|
203
|
-
|
|
218
|
+
id: RawCoID,
|
|
204
219
|
peer: PeerState
|
|
205
220
|
) {
|
|
206
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
221
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
207
222
|
|
|
208
|
-
for (const
|
|
209
|
-
await this.sendNewContentIncludingDependencies(
|
|
223
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
224
|
+
await this.sendNewContentIncludingDependencies(id, peer);
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
const newContent = coValue.newContentSince(
|
|
213
|
-
peer.optimisticKnownStates[
|
|
228
|
+
peer.optimisticKnownStates[id]
|
|
214
229
|
);
|
|
215
230
|
|
|
216
231
|
if (newContent) {
|
|
217
232
|
await this.trySendToPeer(peer, newContent);
|
|
218
|
-
peer.optimisticKnownStates[
|
|
219
|
-
peer.optimisticKnownStates[
|
|
220
|
-
emptyKnownState(
|
|
233
|
+
peer.optimisticKnownStates[id] = combinedKnownStates(
|
|
234
|
+
peer.optimisticKnownStates[id] ||
|
|
235
|
+
emptyKnownState(id),
|
|
221
236
|
coValue.knownState()
|
|
222
237
|
);
|
|
223
238
|
}
|
|
@@ -236,18 +251,13 @@ export class SyncManager {
|
|
|
236
251
|
|
|
237
252
|
if (peer.role === "server") {
|
|
238
253
|
const initialSync = async () => {
|
|
239
|
-
for (const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
await this.subscribeToIncludingDependencies(
|
|
245
|
-
entry.coValue.id,
|
|
246
|
-
peerState
|
|
247
|
-
);
|
|
254
|
+
for (const id of Object.keys(
|
|
255
|
+
this.local.coValues
|
|
256
|
+
) as RawCoID[]) {
|
|
257
|
+
await this.subscribeToIncludingDependencies(id, peerState);
|
|
248
258
|
|
|
249
|
-
peerState.optimisticKnownStates[
|
|
250
|
-
|
|
259
|
+
peerState.optimisticKnownStates[id] = {
|
|
260
|
+
id: id,
|
|
251
261
|
header: false,
|
|
252
262
|
sessions: {},
|
|
253
263
|
};
|
|
@@ -282,20 +292,20 @@ export class SyncManager {
|
|
|
282
292
|
});
|
|
283
293
|
}
|
|
284
294
|
|
|
285
|
-
async
|
|
286
|
-
const entry = this.local.coValues[msg.
|
|
295
|
+
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
296
|
+
const entry = this.local.coValues[msg.id];
|
|
287
297
|
|
|
288
298
|
if (!entry || entry.state === "loading") {
|
|
289
299
|
if (!entry) {
|
|
290
|
-
this.local.coValues[msg.
|
|
300
|
+
this.local.coValues[msg.id] = newLoadingState();
|
|
291
301
|
}
|
|
292
302
|
|
|
293
|
-
peer.optimisticKnownStates[msg.
|
|
294
|
-
peer.toldKnownState.add(msg.
|
|
303
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
304
|
+
peer.toldKnownState.add(msg.id);
|
|
295
305
|
|
|
296
306
|
await this.trySendToPeer(peer, {
|
|
297
|
-
action: "
|
|
298
|
-
|
|
307
|
+
action: "known",
|
|
308
|
+
id: msg.id,
|
|
299
309
|
header: false,
|
|
300
310
|
sessions: {},
|
|
301
311
|
});
|
|
@@ -303,22 +313,22 @@ export class SyncManager {
|
|
|
303
313
|
return;
|
|
304
314
|
}
|
|
305
315
|
|
|
306
|
-
peer.optimisticKnownStates[msg.
|
|
316
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
307
317
|
|
|
308
318
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
309
|
-
msg.
|
|
319
|
+
msg.id,
|
|
310
320
|
peer
|
|
311
321
|
);
|
|
312
322
|
|
|
313
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
323
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
314
324
|
}
|
|
315
325
|
|
|
316
|
-
async
|
|
317
|
-
let entry = this.local.coValues[msg.
|
|
326
|
+
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
327
|
+
let entry = this.local.coValues[msg.id];
|
|
318
328
|
|
|
319
|
-
peer.optimisticKnownStates[msg.
|
|
320
|
-
peer.optimisticKnownStates[msg.
|
|
321
|
-
emptyKnownState(msg.
|
|
329
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
330
|
+
peer.optimisticKnownStates[msg.id] ||
|
|
331
|
+
emptyKnownState(msg.id),
|
|
322
332
|
knownStateIn(msg)
|
|
323
333
|
);
|
|
324
334
|
|
|
@@ -327,7 +337,7 @@ export class SyncManager {
|
|
|
327
337
|
if (this.local.coValues[msg.asDependencyOf]) {
|
|
328
338
|
entry = newLoadingState();
|
|
329
339
|
|
|
330
|
-
this.local.coValues[msg.
|
|
340
|
+
this.local.coValues[msg.id] = entry;
|
|
331
341
|
} else {
|
|
332
342
|
throw new Error(
|
|
333
343
|
"Expected coValue dependency entry to be created, missing subscribe?"
|
|
@@ -345,14 +355,14 @@ export class SyncManager {
|
|
|
345
355
|
}
|
|
346
356
|
|
|
347
357
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
348
|
-
msg.
|
|
358
|
+
msg.id,
|
|
349
359
|
peer
|
|
350
360
|
);
|
|
351
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
361
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
352
362
|
}
|
|
353
363
|
|
|
354
364
|
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
355
|
-
let entry = this.local.coValues[msg.
|
|
365
|
+
let entry = this.local.coValues[msg.id];
|
|
356
366
|
|
|
357
367
|
if (!entry) {
|
|
358
368
|
throw new Error(
|
|
@@ -363,7 +373,7 @@ export class SyncManager {
|
|
|
363
373
|
let resolveAfterDone: ((coValue: CoValue) => void) | undefined;
|
|
364
374
|
|
|
365
375
|
const peerOptimisticKnownState =
|
|
366
|
-
peer.optimisticKnownStates[msg.
|
|
376
|
+
peer.optimisticKnownStates[msg.id];
|
|
367
377
|
|
|
368
378
|
if (!peerOptimisticKnownState) {
|
|
369
379
|
throw new Error(
|
|
@@ -387,7 +397,7 @@ export class SyncManager {
|
|
|
387
397
|
coValue: coValue,
|
|
388
398
|
};
|
|
389
399
|
|
|
390
|
-
this.local.coValues[msg.
|
|
400
|
+
this.local.coValues[msg.id] = entry;
|
|
391
401
|
}
|
|
392
402
|
|
|
393
403
|
const coValue = entry.coValue;
|
|
@@ -395,7 +405,7 @@ export class SyncManager {
|
|
|
395
405
|
let invalidStateAssumed = false;
|
|
396
406
|
|
|
397
407
|
for (const [sessionID, newContentForSession] of Object.entries(
|
|
398
|
-
msg.
|
|
408
|
+
msg.new
|
|
399
409
|
) as [SessionID, SessionNewContent][]) {
|
|
400
410
|
const ourKnownTxIdx =
|
|
401
411
|
coValue.sessions[sessionID]?.transactions.length;
|
|
@@ -438,19 +448,20 @@ export class SyncManager {
|
|
|
438
448
|
|
|
439
449
|
if (invalidStateAssumed) {
|
|
440
450
|
await this.trySendToPeer(peer, {
|
|
441
|
-
action: "
|
|
451
|
+
action: "known",
|
|
452
|
+
isCorrection: true,
|
|
442
453
|
...coValue.knownState(),
|
|
443
454
|
});
|
|
444
455
|
}
|
|
445
456
|
}
|
|
446
457
|
|
|
447
|
-
async
|
|
448
|
-
msg:
|
|
458
|
+
async handleCorrection(
|
|
459
|
+
msg: KnownStateMessage,
|
|
449
460
|
peer: PeerState
|
|
450
461
|
) {
|
|
451
|
-
const coValue = this.local.expectCoValueLoaded(msg.
|
|
462
|
+
const coValue = this.local.expectCoValueLoaded(msg.id);
|
|
452
463
|
|
|
453
|
-
peer.optimisticKnownStates[msg.
|
|
464
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
454
465
|
msg,
|
|
455
466
|
coValue.knownState()
|
|
456
467
|
);
|
|
@@ -462,7 +473,7 @@ export class SyncManager {
|
|
|
462
473
|
}
|
|
463
474
|
}
|
|
464
475
|
|
|
465
|
-
handleUnsubscribe(_msg:
|
|
476
|
+
handleUnsubscribe(_msg: DoneMessage) {
|
|
466
477
|
throw new Error("Method not implemented.");
|
|
467
478
|
}
|
|
468
479
|
|
|
@@ -492,12 +503,11 @@ export class SyncManager {
|
|
|
492
503
|
|
|
493
504
|
function knownStateIn(
|
|
494
505
|
msg:
|
|
495
|
-
|
|
|
496
|
-
|
|
|
497
|
-
| WrongAssumedKnownStateMessage
|
|
506
|
+
| LoadMessage
|
|
507
|
+
| KnownStateMessage
|
|
498
508
|
) {
|
|
499
509
|
return {
|
|
500
|
-
|
|
510
|
+
id: msg.id,
|
|
501
511
|
header: msg.header,
|
|
502
512
|
sessions: msg.sessions,
|
|
503
513
|
};
|
package/src/testUtils.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from "./crypto.js";
|
|
2
|
+
import { newRandomSessionID } from "./coValue.js";
|
|
3
|
+
import { LocalNode } from "./node.js";
|
|
4
|
+
import { expectTeamContent } from "./permissions.js";
|
|
5
|
+
import { AnonymousControlledAccount } from "./account.js";
|
|
6
|
+
import { SessionID } from "./ids.js";
|
|
7
|
+
import { ReadableStream, TransformStream, WritableStream } from "isomorphic-streams";
|
|
8
|
+
import { Peer, PeerID, SyncMessage } from "./sync.js";
|
|
9
|
+
|
|
10
|
+
export function randomAnonymousAccountAndSessionID(): [AnonymousControlledAccount, SessionID] {
|
|
11
|
+
const agentSecret = newRandomAgentSecret();
|
|
12
|
+
|
|
13
|
+
const sessionID = newRandomSessionID(getAgentID(agentSecret));
|
|
14
|
+
|
|
15
|
+
return [new AnonymousControlledAccount(agentSecret), sessionID];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function newTeam() {
|
|
19
|
+
const [admin, sessionID] = randomAnonymousAccountAndSessionID();
|
|
20
|
+
|
|
21
|
+
const node = new LocalNode(admin, sessionID);
|
|
22
|
+
|
|
23
|
+
const team = node.createCoValue({
|
|
24
|
+
type: "comap",
|
|
25
|
+
ruleset: { type: "team", initialAdmin: admin.id },
|
|
26
|
+
meta: null,
|
|
27
|
+
...createdNowUnique(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const teamContent = expectTeamContent(team.getCurrentContent());
|
|
31
|
+
|
|
32
|
+
teamContent.edit((editable) => {
|
|
33
|
+
editable.set(admin.id, "admin", "trusting");
|
|
34
|
+
expect(editable.get(admin.id)).toEqual("admin");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return { node, team, admin };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function teamWithTwoAdmins() {
|
|
41
|
+
const { team, admin, node } = newTeam();
|
|
42
|
+
|
|
43
|
+
const otherAdmin = node.createAccount("otherAdmin");
|
|
44
|
+
|
|
45
|
+
let content = expectTeamContent(team.getCurrentContent());
|
|
46
|
+
|
|
47
|
+
content.edit((editable) => {
|
|
48
|
+
editable.set(otherAdmin.id, "admin", "trusting");
|
|
49
|
+
expect(editable.get(otherAdmin.id)).toEqual("admin");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
content = expectTeamContent(team.getCurrentContent());
|
|
53
|
+
|
|
54
|
+
if (content.type !== "comap") {
|
|
55
|
+
throw new Error("Expected map");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
expect(content.get(otherAdmin.id)).toEqual("admin");
|
|
59
|
+
return { team, admin, otherAdmin, node };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function newTeamHighLevel() {
|
|
63
|
+
const [admin, sessionID] = randomAnonymousAccountAndSessionID();
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const node = new LocalNode(admin, sessionID);
|
|
67
|
+
|
|
68
|
+
const team = node.createTeam();
|
|
69
|
+
|
|
70
|
+
return { admin, node, team };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function teamWithTwoAdminsHighLevel() {
|
|
74
|
+
const { admin, node, team } = newTeamHighLevel();
|
|
75
|
+
|
|
76
|
+
const otherAdmin = node.createAccount("otherAdmin");
|
|
77
|
+
|
|
78
|
+
team.addMember(otherAdmin.id, "admin");
|
|
79
|
+
|
|
80
|
+
return { admin, node, team, otherAdmin };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function newStreamPair<T>(): [ReadableStream<T>, WritableStream<T>] {
|
|
84
|
+
const queue: T[] = [];
|
|
85
|
+
let resolveNextItemReady: () => void = () => {};
|
|
86
|
+
let nextItemReady: Promise<void> = new Promise((resolve) => {
|
|
87
|
+
resolveNextItemReady = resolve;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
let writerClosed = false;
|
|
91
|
+
let readerClosed = false;
|
|
92
|
+
|
|
93
|
+
const readable = new ReadableStream<T>({
|
|
94
|
+
async pull(controller) {
|
|
95
|
+
let retriesLeft = 3;
|
|
96
|
+
while (retriesLeft > 0) {
|
|
97
|
+
if (writerClosed) {
|
|
98
|
+
controller.close();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
retriesLeft--;
|
|
102
|
+
if (queue.length > 0) {
|
|
103
|
+
controller.enqueue(queue.shift()!);
|
|
104
|
+
if (queue.length === 0) {
|
|
105
|
+
nextItemReady = new Promise((resolve) => {
|
|
106
|
+
resolveNextItemReady = resolve;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
} else {
|
|
111
|
+
await nextItemReady;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
throw new Error(
|
|
115
|
+
"Should only use one retry to get next item in queue."
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
cancel(reason) {
|
|
120
|
+
console.log("Manually closing reader");
|
|
121
|
+
readerClosed = true;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const writable = new WritableStream<T>({
|
|
126
|
+
write(chunk, controller) {
|
|
127
|
+
if (readerClosed) {
|
|
128
|
+
console.log("Reader closed, not writing chunk", chunk);
|
|
129
|
+
throw new Error("Reader closed, not writing chunk");
|
|
130
|
+
}
|
|
131
|
+
queue.push(chunk);
|
|
132
|
+
if (queue.length === 1) {
|
|
133
|
+
// make sure that await write resolves before corresponding read
|
|
134
|
+
process.nextTick(() => resolveNextItemReady());
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
abort(reason) {
|
|
138
|
+
console.log("Manually closing writer");
|
|
139
|
+
writerClosed = true;
|
|
140
|
+
resolveNextItemReady();
|
|
141
|
+
return Promise.resolve();
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return [readable, writable];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function shouldNotResolve<T>(
|
|
149
|
+
promise: Promise<T>,
|
|
150
|
+
ops: { timeout: number }
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
promise
|
|
154
|
+
.then((v) =>
|
|
155
|
+
reject(
|
|
156
|
+
new Error(
|
|
157
|
+
"Should not have resolved, but resolved to " +
|
|
158
|
+
JSON.stringify(v)
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
.catch(reject);
|
|
163
|
+
setTimeout(resolve, ops.timeout);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function connectedPeers(
|
|
168
|
+
peer1id: PeerID,
|
|
169
|
+
peer2id: PeerID,
|
|
170
|
+
{
|
|
171
|
+
trace = false,
|
|
172
|
+
peer1role = "peer",
|
|
173
|
+
peer2role = "peer",
|
|
174
|
+
}: {
|
|
175
|
+
trace?: boolean;
|
|
176
|
+
peer1role?: Peer["role"];
|
|
177
|
+
peer2role?: Peer["role"];
|
|
178
|
+
} = {}
|
|
179
|
+
): [Peer, Peer] {
|
|
180
|
+
const [inRx1, inTx1] = newStreamPair<SyncMessage>();
|
|
181
|
+
const [outRx1, outTx1] = newStreamPair<SyncMessage>();
|
|
182
|
+
|
|
183
|
+
const [inRx2, inTx2] = newStreamPair<SyncMessage>();
|
|
184
|
+
const [outRx2, outTx2] = newStreamPair<SyncMessage>();
|
|
185
|
+
|
|
186
|
+
void outRx2
|
|
187
|
+
.pipeThrough(
|
|
188
|
+
new TransformStream({
|
|
189
|
+
transform(
|
|
190
|
+
chunk: SyncMessage,
|
|
191
|
+
controller: { enqueue: (msg: SyncMessage) => void }
|
|
192
|
+
) {
|
|
193
|
+
trace && console.log(`${peer2id} -> ${peer1id}`, JSON.stringify(chunk, null, 2));
|
|
194
|
+
controller.enqueue(chunk);
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
)
|
|
198
|
+
.pipeTo(inTx1);
|
|
199
|
+
|
|
200
|
+
void outRx1
|
|
201
|
+
.pipeThrough(
|
|
202
|
+
new TransformStream({
|
|
203
|
+
transform(
|
|
204
|
+
chunk: SyncMessage,
|
|
205
|
+
controller: { enqueue: (msg: SyncMessage) => void }
|
|
206
|
+
) {
|
|
207
|
+
trace && console.log(`${peer1id} -> ${peer2id}`, JSON.stringify(chunk, null, 2));
|
|
208
|
+
controller.enqueue(chunk);
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
)
|
|
212
|
+
.pipeTo(inTx2);
|
|
213
|
+
|
|
214
|
+
const peer2AsPeer: Peer = {
|
|
215
|
+
id: peer2id,
|
|
216
|
+
incoming: inRx1,
|
|
217
|
+
outgoing: outTx1,
|
|
218
|
+
role: peer2role,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const peer1AsPeer: Peer = {
|
|
222
|
+
id: peer1id,
|
|
223
|
+
incoming: inRx2,
|
|
224
|
+
outgoing: outTx2,
|
|
225
|
+
role: peer1role,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
return [peer1AsPeer, peer2AsPeer];
|
|
229
|
+
}
|