cojson 0.0.11 → 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 +67 -63
- package/dist/sync.js.map +1 -1
- package/dist/sync.test.js +181 -298
- 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 +258 -432
- package/src/sync.ts +95 -98
- 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,10 +152,10 @@ export class SyncManager {
|
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
async subscribeToIncludingDependencies(
|
|
158
|
-
|
|
155
|
+
id: RawCoID,
|
|
159
156
|
peer: PeerState
|
|
160
157
|
) {
|
|
161
|
-
const entry = this.local.coValues[
|
|
158
|
+
const entry = this.local.coValues[id];
|
|
162
159
|
|
|
163
160
|
if (!entry) {
|
|
164
161
|
throw new Error(
|
|
@@ -168,8 +165,8 @@ export class SyncManager {
|
|
|
168
165
|
|
|
169
166
|
if (entry.state === "loading") {
|
|
170
167
|
await this.trySendToPeer(peer, {
|
|
171
|
-
action: "
|
|
172
|
-
|
|
168
|
+
action: "load",
|
|
169
|
+
id,
|
|
173
170
|
header: false,
|
|
174
171
|
sessions: {},
|
|
175
172
|
});
|
|
@@ -178,64 +175,64 @@ export class SyncManager {
|
|
|
178
175
|
|
|
179
176
|
const coValue = entry.coValue;
|
|
180
177
|
|
|
181
|
-
for (const
|
|
182
|
-
await this.subscribeToIncludingDependencies(
|
|
178
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
179
|
+
await this.subscribeToIncludingDependencies(id, peer);
|
|
183
180
|
}
|
|
184
181
|
|
|
185
|
-
if (!peer.toldKnownState.has(
|
|
186
|
-
peer.toldKnownState.add(
|
|
182
|
+
if (!peer.toldKnownState.has(id)) {
|
|
183
|
+
peer.toldKnownState.add(id);
|
|
187
184
|
await this.trySendToPeer(peer, {
|
|
188
|
-
action: "
|
|
185
|
+
action: "load",
|
|
189
186
|
...coValue.knownState(),
|
|
190
187
|
});
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
async tellUntoldKnownStateIncludingDependencies(
|
|
195
|
-
|
|
192
|
+
id: RawCoID,
|
|
196
193
|
peer: PeerState,
|
|
197
|
-
asDependencyOf?:
|
|
194
|
+
asDependencyOf?: RawCoID
|
|
198
195
|
) {
|
|
199
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
196
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
200
197
|
|
|
201
|
-
for (const
|
|
198
|
+
for (const dependentCoID of coValue.getDependedOnCoValues()) {
|
|
202
199
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
203
|
-
|
|
200
|
+
dependentCoID,
|
|
204
201
|
peer,
|
|
205
|
-
asDependencyOf ||
|
|
202
|
+
asDependencyOf || id
|
|
206
203
|
);
|
|
207
204
|
}
|
|
208
205
|
|
|
209
|
-
if (!peer.toldKnownState.has(
|
|
206
|
+
if (!peer.toldKnownState.has(id)) {
|
|
210
207
|
await this.trySendToPeer(peer, {
|
|
211
|
-
action: "
|
|
208
|
+
action: "known",
|
|
212
209
|
asDependencyOf,
|
|
213
210
|
...coValue.knownState(),
|
|
214
211
|
});
|
|
215
212
|
|
|
216
|
-
peer.toldKnownState.add(
|
|
213
|
+
peer.toldKnownState.add(id);
|
|
217
214
|
}
|
|
218
215
|
}
|
|
219
216
|
|
|
220
217
|
async sendNewContentIncludingDependencies(
|
|
221
|
-
|
|
218
|
+
id: RawCoID,
|
|
222
219
|
peer: PeerState
|
|
223
220
|
) {
|
|
224
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
221
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
225
222
|
|
|
226
|
-
for (const
|
|
227
|
-
await this.sendNewContentIncludingDependencies(
|
|
223
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
224
|
+
await this.sendNewContentIncludingDependencies(id, peer);
|
|
228
225
|
}
|
|
229
226
|
|
|
230
227
|
const newContent = coValue.newContentSince(
|
|
231
|
-
peer.optimisticKnownStates[
|
|
228
|
+
peer.optimisticKnownStates[id]
|
|
232
229
|
);
|
|
233
230
|
|
|
234
231
|
if (newContent) {
|
|
235
232
|
await this.trySendToPeer(peer, newContent);
|
|
236
|
-
peer.optimisticKnownStates[
|
|
237
|
-
peer.optimisticKnownStates[
|
|
238
|
-
emptyKnownState(
|
|
233
|
+
peer.optimisticKnownStates[id] = combinedKnownStates(
|
|
234
|
+
peer.optimisticKnownStates[id] ||
|
|
235
|
+
emptyKnownState(id),
|
|
239
236
|
coValue.knownState()
|
|
240
237
|
);
|
|
241
238
|
}
|
|
@@ -256,11 +253,11 @@ export class SyncManager {
|
|
|
256
253
|
const initialSync = async () => {
|
|
257
254
|
for (const id of Object.keys(
|
|
258
255
|
this.local.coValues
|
|
259
|
-
) as
|
|
256
|
+
) as RawCoID[]) {
|
|
260
257
|
await this.subscribeToIncludingDependencies(id, peerState);
|
|
261
258
|
|
|
262
259
|
peerState.optimisticKnownStates[id] = {
|
|
263
|
-
|
|
260
|
+
id: id,
|
|
264
261
|
header: false,
|
|
265
262
|
sessions: {},
|
|
266
263
|
};
|
|
@@ -295,20 +292,20 @@ export class SyncManager {
|
|
|
295
292
|
});
|
|
296
293
|
}
|
|
297
294
|
|
|
298
|
-
async
|
|
299
|
-
const entry = this.local.coValues[msg.
|
|
295
|
+
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
296
|
+
const entry = this.local.coValues[msg.id];
|
|
300
297
|
|
|
301
298
|
if (!entry || entry.state === "loading") {
|
|
302
299
|
if (!entry) {
|
|
303
|
-
this.local.coValues[msg.
|
|
300
|
+
this.local.coValues[msg.id] = newLoadingState();
|
|
304
301
|
}
|
|
305
302
|
|
|
306
|
-
peer.optimisticKnownStates[msg.
|
|
307
|
-
peer.toldKnownState.add(msg.
|
|
303
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
304
|
+
peer.toldKnownState.add(msg.id);
|
|
308
305
|
|
|
309
306
|
await this.trySendToPeer(peer, {
|
|
310
|
-
action: "
|
|
311
|
-
|
|
307
|
+
action: "known",
|
|
308
|
+
id: msg.id,
|
|
312
309
|
header: false,
|
|
313
310
|
sessions: {},
|
|
314
311
|
});
|
|
@@ -316,22 +313,22 @@ export class SyncManager {
|
|
|
316
313
|
return;
|
|
317
314
|
}
|
|
318
315
|
|
|
319
|
-
peer.optimisticKnownStates[msg.
|
|
316
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
320
317
|
|
|
321
318
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
322
|
-
msg.
|
|
319
|
+
msg.id,
|
|
323
320
|
peer
|
|
324
321
|
);
|
|
325
322
|
|
|
326
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
323
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
327
324
|
}
|
|
328
325
|
|
|
329
|
-
async
|
|
330
|
-
let entry = this.local.coValues[msg.
|
|
326
|
+
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
327
|
+
let entry = this.local.coValues[msg.id];
|
|
331
328
|
|
|
332
|
-
peer.optimisticKnownStates[msg.
|
|
333
|
-
peer.optimisticKnownStates[msg.
|
|
334
|
-
emptyKnownState(msg.
|
|
329
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
330
|
+
peer.optimisticKnownStates[msg.id] ||
|
|
331
|
+
emptyKnownState(msg.id),
|
|
335
332
|
knownStateIn(msg)
|
|
336
333
|
);
|
|
337
334
|
|
|
@@ -340,7 +337,7 @@ export class SyncManager {
|
|
|
340
337
|
if (this.local.coValues[msg.asDependencyOf]) {
|
|
341
338
|
entry = newLoadingState();
|
|
342
339
|
|
|
343
|
-
this.local.coValues[msg.
|
|
340
|
+
this.local.coValues[msg.id] = entry;
|
|
344
341
|
} else {
|
|
345
342
|
throw new Error(
|
|
346
343
|
"Expected coValue dependency entry to be created, missing subscribe?"
|
|
@@ -358,14 +355,14 @@ export class SyncManager {
|
|
|
358
355
|
}
|
|
359
356
|
|
|
360
357
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
361
|
-
msg.
|
|
358
|
+
msg.id,
|
|
362
359
|
peer
|
|
363
360
|
);
|
|
364
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
361
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
365
362
|
}
|
|
366
363
|
|
|
367
364
|
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
368
|
-
let entry = this.local.coValues[msg.
|
|
365
|
+
let entry = this.local.coValues[msg.id];
|
|
369
366
|
|
|
370
367
|
if (!entry) {
|
|
371
368
|
throw new Error(
|
|
@@ -376,7 +373,7 @@ export class SyncManager {
|
|
|
376
373
|
let resolveAfterDone: ((coValue: CoValue) => void) | undefined;
|
|
377
374
|
|
|
378
375
|
const peerOptimisticKnownState =
|
|
379
|
-
peer.optimisticKnownStates[msg.
|
|
376
|
+
peer.optimisticKnownStates[msg.id];
|
|
380
377
|
|
|
381
378
|
if (!peerOptimisticKnownState) {
|
|
382
379
|
throw new Error(
|
|
@@ -400,7 +397,7 @@ export class SyncManager {
|
|
|
400
397
|
coValue: coValue,
|
|
401
398
|
};
|
|
402
399
|
|
|
403
|
-
this.local.coValues[msg.
|
|
400
|
+
this.local.coValues[msg.id] = entry;
|
|
404
401
|
}
|
|
405
402
|
|
|
406
403
|
const coValue = entry.coValue;
|
|
@@ -408,7 +405,7 @@ export class SyncManager {
|
|
|
408
405
|
let invalidStateAssumed = false;
|
|
409
406
|
|
|
410
407
|
for (const [sessionID, newContentForSession] of Object.entries(
|
|
411
|
-
msg.
|
|
408
|
+
msg.new
|
|
412
409
|
) as [SessionID, SessionNewContent][]) {
|
|
413
410
|
const ourKnownTxIdx =
|
|
414
411
|
coValue.sessions[sessionID]?.transactions.length;
|
|
@@ -451,19 +448,20 @@ export class SyncManager {
|
|
|
451
448
|
|
|
452
449
|
if (invalidStateAssumed) {
|
|
453
450
|
await this.trySendToPeer(peer, {
|
|
454
|
-
action: "
|
|
451
|
+
action: "known",
|
|
452
|
+
isCorrection: true,
|
|
455
453
|
...coValue.knownState(),
|
|
456
454
|
});
|
|
457
455
|
}
|
|
458
456
|
}
|
|
459
457
|
|
|
460
|
-
async
|
|
461
|
-
msg:
|
|
458
|
+
async handleCorrection(
|
|
459
|
+
msg: KnownStateMessage,
|
|
462
460
|
peer: PeerState
|
|
463
461
|
) {
|
|
464
|
-
const coValue = this.local.expectCoValueLoaded(msg.
|
|
462
|
+
const coValue = this.local.expectCoValueLoaded(msg.id);
|
|
465
463
|
|
|
466
|
-
peer.optimisticKnownStates[msg.
|
|
464
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
467
465
|
msg,
|
|
468
466
|
coValue.knownState()
|
|
469
467
|
);
|
|
@@ -475,7 +473,7 @@ export class SyncManager {
|
|
|
475
473
|
}
|
|
476
474
|
}
|
|
477
475
|
|
|
478
|
-
handleUnsubscribe(_msg:
|
|
476
|
+
handleUnsubscribe(_msg: DoneMessage) {
|
|
479
477
|
throw new Error("Method not implemented.");
|
|
480
478
|
}
|
|
481
479
|
|
|
@@ -505,12 +503,11 @@ export class SyncManager {
|
|
|
505
503
|
|
|
506
504
|
function knownStateIn(
|
|
507
505
|
msg:
|
|
508
|
-
|
|
|
509
|
-
|
|
|
510
|
-
| WrongAssumedKnownStateMessage
|
|
506
|
+
| LoadMessage
|
|
507
|
+
| KnownStateMessage
|
|
511
508
|
) {
|
|
512
509
|
return {
|
|
513
|
-
|
|
510
|
+
id: msg.id,
|
|
514
511
|
header: msg.header,
|
|
515
512
|
sessions: msg.sessions,
|
|
516
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
|
+
}
|