cojson 0.0.11 → 0.0.13
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 +17 -36
- package/dist/coValue.js +53 -117
- 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 +27 -30
- package/dist/sync.js +68 -64
- package/dist/sync.js.map +1 -1
- package/dist/sync.test.js +181 -305
- 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 +98 -185
- 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 +262 -440
- package/src/sync.ts +96 -101
- 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
|
};
|
|
@@ -52,18 +52,11 @@ export type NewContentMessage = {
|
|
|
52
52
|
export type SessionNewContent = {
|
|
53
53
|
after: number;
|
|
54
54
|
newTransactions: Transaction[];
|
|
55
|
-
// TODO: is lastHash needed here?
|
|
56
|
-
lastHash: Hash;
|
|
57
55
|
lastSignature: Signature;
|
|
58
56
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
} & CoValueKnownState;
|
|
63
|
-
|
|
64
|
-
export type UnsubscribeMessage = {
|
|
65
|
-
action: "unsubscribe";
|
|
66
|
-
coValueID: RawCoValueID;
|
|
57
|
+
export type DoneMessage = {
|
|
58
|
+
action: "done";
|
|
59
|
+
id: RawCoID;
|
|
67
60
|
};
|
|
68
61
|
|
|
69
62
|
export type PeerID = string;
|
|
@@ -77,8 +70,8 @@ export interface Peer {
|
|
|
77
70
|
|
|
78
71
|
export interface PeerState {
|
|
79
72
|
id: PeerID;
|
|
80
|
-
optimisticKnownStates: { [
|
|
81
|
-
toldKnownState: Set<
|
|
73
|
+
optimisticKnownStates: { [id: RawCoID]: CoValueKnownState };
|
|
74
|
+
toldKnownState: Set<RawCoID>;
|
|
82
75
|
incoming: ReadableStream<SyncMessage>;
|
|
83
76
|
outgoing: WritableStreamDefaultWriter<SyncMessage>;
|
|
84
77
|
role: "peer" | "server" | "client";
|
|
@@ -103,7 +96,7 @@ export function combinedKnownStates(
|
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
return {
|
|
106
|
-
|
|
99
|
+
id: stateA.id,
|
|
107
100
|
header: stateA.header || stateB.header,
|
|
108
101
|
sessions: sessionStates,
|
|
109
102
|
};
|
|
@@ -117,12 +110,12 @@ export class SyncManager {
|
|
|
117
110
|
this.local = local;
|
|
118
111
|
}
|
|
119
112
|
|
|
120
|
-
loadFromPeers(id:
|
|
113
|
+
loadFromPeers(id: RawCoID) {
|
|
121
114
|
for (const peer of Object.values(this.peers)) {
|
|
122
115
|
peer.outgoing
|
|
123
116
|
.write({
|
|
124
|
-
action: "
|
|
125
|
-
|
|
117
|
+
action: "load",
|
|
118
|
+
id: id,
|
|
126
119
|
header: false,
|
|
127
120
|
sessions: {},
|
|
128
121
|
})
|
|
@@ -135,15 +128,17 @@ export class SyncManager {
|
|
|
135
128
|
async handleSyncMessage(msg: SyncMessage, peer: PeerState) {
|
|
136
129
|
// TODO: validate
|
|
137
130
|
switch (msg.action) {
|
|
138
|
-
case "
|
|
139
|
-
return await this.
|
|
140
|
-
case "
|
|
141
|
-
|
|
142
|
-
|
|
131
|
+
case "load":
|
|
132
|
+
return await this.handleLoad(msg, peer);
|
|
133
|
+
case "known":
|
|
134
|
+
if (msg.isCorrection) {
|
|
135
|
+
return await this.handleCorrection(msg, peer);
|
|
136
|
+
} else {
|
|
137
|
+
return await this.handleKnownState(msg, peer);
|
|
138
|
+
}
|
|
139
|
+
case "content":
|
|
143
140
|
return await this.handleNewContent(msg, peer);
|
|
144
|
-
case "
|
|
145
|
-
return await this.handleWrongAssumedKnownState(msg, peer);
|
|
146
|
-
case "unsubscribe":
|
|
141
|
+
case "done":
|
|
147
142
|
return await this.handleUnsubscribe(msg);
|
|
148
143
|
default:
|
|
149
144
|
throw new Error(
|
|
@@ -155,10 +150,10 @@ export class SyncManager {
|
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
async subscribeToIncludingDependencies(
|
|
158
|
-
|
|
153
|
+
id: RawCoID,
|
|
159
154
|
peer: PeerState
|
|
160
155
|
) {
|
|
161
|
-
const entry = this.local.coValues[
|
|
156
|
+
const entry = this.local.coValues[id];
|
|
162
157
|
|
|
163
158
|
if (!entry) {
|
|
164
159
|
throw new Error(
|
|
@@ -168,8 +163,8 @@ export class SyncManager {
|
|
|
168
163
|
|
|
169
164
|
if (entry.state === "loading") {
|
|
170
165
|
await this.trySendToPeer(peer, {
|
|
171
|
-
action: "
|
|
172
|
-
|
|
166
|
+
action: "load",
|
|
167
|
+
id,
|
|
173
168
|
header: false,
|
|
174
169
|
sessions: {},
|
|
175
170
|
});
|
|
@@ -178,64 +173,64 @@ export class SyncManager {
|
|
|
178
173
|
|
|
179
174
|
const coValue = entry.coValue;
|
|
180
175
|
|
|
181
|
-
for (const
|
|
182
|
-
await this.subscribeToIncludingDependencies(
|
|
176
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
177
|
+
await this.subscribeToIncludingDependencies(id, peer);
|
|
183
178
|
}
|
|
184
179
|
|
|
185
|
-
if (!peer.toldKnownState.has(
|
|
186
|
-
peer.toldKnownState.add(
|
|
180
|
+
if (!peer.toldKnownState.has(id)) {
|
|
181
|
+
peer.toldKnownState.add(id);
|
|
187
182
|
await this.trySendToPeer(peer, {
|
|
188
|
-
action: "
|
|
183
|
+
action: "load",
|
|
189
184
|
...coValue.knownState(),
|
|
190
185
|
});
|
|
191
186
|
}
|
|
192
187
|
}
|
|
193
188
|
|
|
194
189
|
async tellUntoldKnownStateIncludingDependencies(
|
|
195
|
-
|
|
190
|
+
id: RawCoID,
|
|
196
191
|
peer: PeerState,
|
|
197
|
-
asDependencyOf?:
|
|
192
|
+
asDependencyOf?: RawCoID
|
|
198
193
|
) {
|
|
199
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
194
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
200
195
|
|
|
201
|
-
for (const
|
|
196
|
+
for (const dependentCoID of coValue.getDependedOnCoValues()) {
|
|
202
197
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
203
|
-
|
|
198
|
+
dependentCoID,
|
|
204
199
|
peer,
|
|
205
|
-
asDependencyOf ||
|
|
200
|
+
asDependencyOf || id
|
|
206
201
|
);
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
if (!peer.toldKnownState.has(
|
|
204
|
+
if (!peer.toldKnownState.has(id)) {
|
|
210
205
|
await this.trySendToPeer(peer, {
|
|
211
|
-
action: "
|
|
206
|
+
action: "known",
|
|
212
207
|
asDependencyOf,
|
|
213
208
|
...coValue.knownState(),
|
|
214
209
|
});
|
|
215
210
|
|
|
216
|
-
peer.toldKnownState.add(
|
|
211
|
+
peer.toldKnownState.add(id);
|
|
217
212
|
}
|
|
218
213
|
}
|
|
219
214
|
|
|
220
215
|
async sendNewContentIncludingDependencies(
|
|
221
|
-
|
|
216
|
+
id: RawCoID,
|
|
222
217
|
peer: PeerState
|
|
223
218
|
) {
|
|
224
|
-
const coValue = this.local.expectCoValueLoaded(
|
|
219
|
+
const coValue = this.local.expectCoValueLoaded(id);
|
|
225
220
|
|
|
226
|
-
for (const
|
|
227
|
-
await this.sendNewContentIncludingDependencies(
|
|
221
|
+
for (const id of coValue.getDependedOnCoValues()) {
|
|
222
|
+
await this.sendNewContentIncludingDependencies(id, peer);
|
|
228
223
|
}
|
|
229
224
|
|
|
230
225
|
const newContent = coValue.newContentSince(
|
|
231
|
-
peer.optimisticKnownStates[
|
|
226
|
+
peer.optimisticKnownStates[id]
|
|
232
227
|
);
|
|
233
228
|
|
|
234
229
|
if (newContent) {
|
|
235
230
|
await this.trySendToPeer(peer, newContent);
|
|
236
|
-
peer.optimisticKnownStates[
|
|
237
|
-
peer.optimisticKnownStates[
|
|
238
|
-
emptyKnownState(
|
|
231
|
+
peer.optimisticKnownStates[id] = combinedKnownStates(
|
|
232
|
+
peer.optimisticKnownStates[id] ||
|
|
233
|
+
emptyKnownState(id),
|
|
239
234
|
coValue.knownState()
|
|
240
235
|
);
|
|
241
236
|
}
|
|
@@ -256,11 +251,11 @@ export class SyncManager {
|
|
|
256
251
|
const initialSync = async () => {
|
|
257
252
|
for (const id of Object.keys(
|
|
258
253
|
this.local.coValues
|
|
259
|
-
) as
|
|
254
|
+
) as RawCoID[]) {
|
|
260
255
|
await this.subscribeToIncludingDependencies(id, peerState);
|
|
261
256
|
|
|
262
257
|
peerState.optimisticKnownStates[id] = {
|
|
263
|
-
|
|
258
|
+
id: id,
|
|
264
259
|
header: false,
|
|
265
260
|
sessions: {},
|
|
266
261
|
};
|
|
@@ -295,20 +290,20 @@ export class SyncManager {
|
|
|
295
290
|
});
|
|
296
291
|
}
|
|
297
292
|
|
|
298
|
-
async
|
|
299
|
-
const entry = this.local.coValues[msg.
|
|
293
|
+
async handleLoad(msg: LoadMessage, peer: PeerState) {
|
|
294
|
+
const entry = this.local.coValues[msg.id];
|
|
300
295
|
|
|
301
296
|
if (!entry || entry.state === "loading") {
|
|
302
297
|
if (!entry) {
|
|
303
|
-
this.local.coValues[msg.
|
|
298
|
+
this.local.coValues[msg.id] = newLoadingState();
|
|
304
299
|
}
|
|
305
300
|
|
|
306
|
-
peer.optimisticKnownStates[msg.
|
|
307
|
-
peer.toldKnownState.add(msg.
|
|
301
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
302
|
+
peer.toldKnownState.add(msg.id);
|
|
308
303
|
|
|
309
304
|
await this.trySendToPeer(peer, {
|
|
310
|
-
action: "
|
|
311
|
-
|
|
305
|
+
action: "known",
|
|
306
|
+
id: msg.id,
|
|
312
307
|
header: false,
|
|
313
308
|
sessions: {},
|
|
314
309
|
});
|
|
@@ -316,22 +311,22 @@ export class SyncManager {
|
|
|
316
311
|
return;
|
|
317
312
|
}
|
|
318
313
|
|
|
319
|
-
peer.optimisticKnownStates[msg.
|
|
314
|
+
peer.optimisticKnownStates[msg.id] = knownStateIn(msg);
|
|
320
315
|
|
|
321
316
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
322
|
-
msg.
|
|
317
|
+
msg.id,
|
|
323
318
|
peer
|
|
324
319
|
);
|
|
325
320
|
|
|
326
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
321
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
327
322
|
}
|
|
328
323
|
|
|
329
|
-
async
|
|
330
|
-
let entry = this.local.coValues[msg.
|
|
324
|
+
async handleKnownState(msg: KnownStateMessage, peer: PeerState) {
|
|
325
|
+
let entry = this.local.coValues[msg.id];
|
|
331
326
|
|
|
332
|
-
peer.optimisticKnownStates[msg.
|
|
333
|
-
peer.optimisticKnownStates[msg.
|
|
334
|
-
emptyKnownState(msg.
|
|
327
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
328
|
+
peer.optimisticKnownStates[msg.id] ||
|
|
329
|
+
emptyKnownState(msg.id),
|
|
335
330
|
knownStateIn(msg)
|
|
336
331
|
);
|
|
337
332
|
|
|
@@ -340,7 +335,7 @@ export class SyncManager {
|
|
|
340
335
|
if (this.local.coValues[msg.asDependencyOf]) {
|
|
341
336
|
entry = newLoadingState();
|
|
342
337
|
|
|
343
|
-
this.local.coValues[msg.
|
|
338
|
+
this.local.coValues[msg.id] = entry;
|
|
344
339
|
} else {
|
|
345
340
|
throw new Error(
|
|
346
341
|
"Expected coValue dependency entry to be created, missing subscribe?"
|
|
@@ -358,14 +353,14 @@ export class SyncManager {
|
|
|
358
353
|
}
|
|
359
354
|
|
|
360
355
|
await this.tellUntoldKnownStateIncludingDependencies(
|
|
361
|
-
msg.
|
|
356
|
+
msg.id,
|
|
362
357
|
peer
|
|
363
358
|
);
|
|
364
|
-
await this.sendNewContentIncludingDependencies(msg.
|
|
359
|
+
await this.sendNewContentIncludingDependencies(msg.id, peer);
|
|
365
360
|
}
|
|
366
361
|
|
|
367
362
|
async handleNewContent(msg: NewContentMessage, peer: PeerState) {
|
|
368
|
-
let entry = this.local.coValues[msg.
|
|
363
|
+
let entry = this.local.coValues[msg.id];
|
|
369
364
|
|
|
370
365
|
if (!entry) {
|
|
371
366
|
throw new Error(
|
|
@@ -376,7 +371,7 @@ export class SyncManager {
|
|
|
376
371
|
let resolveAfterDone: ((coValue: CoValue) => void) | undefined;
|
|
377
372
|
|
|
378
373
|
const peerOptimisticKnownState =
|
|
379
|
-
peer.optimisticKnownStates[msg.
|
|
374
|
+
peer.optimisticKnownStates[msg.id];
|
|
380
375
|
|
|
381
376
|
if (!peerOptimisticKnownState) {
|
|
382
377
|
throw new Error(
|
|
@@ -400,7 +395,7 @@ export class SyncManager {
|
|
|
400
395
|
coValue: coValue,
|
|
401
396
|
};
|
|
402
397
|
|
|
403
|
-
this.local.coValues[msg.
|
|
398
|
+
this.local.coValues[msg.id] = entry;
|
|
404
399
|
}
|
|
405
400
|
|
|
406
401
|
const coValue = entry.coValue;
|
|
@@ -408,7 +403,7 @@ export class SyncManager {
|
|
|
408
403
|
let invalidStateAssumed = false;
|
|
409
404
|
|
|
410
405
|
for (const [sessionID, newContentForSession] of Object.entries(
|
|
411
|
-
msg.
|
|
406
|
+
msg.new
|
|
412
407
|
) as [SessionID, SessionNewContent][]) {
|
|
413
408
|
const ourKnownTxIdx =
|
|
414
409
|
coValue.sessions[sessionID]?.transactions.length;
|
|
@@ -429,7 +424,7 @@ export class SyncManager {
|
|
|
429
424
|
const success = coValue.tryAddTransactions(
|
|
430
425
|
sessionID,
|
|
431
426
|
newTransactions,
|
|
432
|
-
|
|
427
|
+
undefined,
|
|
433
428
|
newContentForSession.lastSignature
|
|
434
429
|
);
|
|
435
430
|
|
|
@@ -451,19 +446,20 @@ export class SyncManager {
|
|
|
451
446
|
|
|
452
447
|
if (invalidStateAssumed) {
|
|
453
448
|
await this.trySendToPeer(peer, {
|
|
454
|
-
action: "
|
|
449
|
+
action: "known",
|
|
450
|
+
isCorrection: true,
|
|
455
451
|
...coValue.knownState(),
|
|
456
452
|
});
|
|
457
453
|
}
|
|
458
454
|
}
|
|
459
455
|
|
|
460
|
-
async
|
|
461
|
-
msg:
|
|
456
|
+
async handleCorrection(
|
|
457
|
+
msg: KnownStateMessage,
|
|
462
458
|
peer: PeerState
|
|
463
459
|
) {
|
|
464
|
-
const coValue = this.local.expectCoValueLoaded(msg.
|
|
460
|
+
const coValue = this.local.expectCoValueLoaded(msg.id);
|
|
465
461
|
|
|
466
|
-
peer.optimisticKnownStates[msg.
|
|
462
|
+
peer.optimisticKnownStates[msg.id] = combinedKnownStates(
|
|
467
463
|
msg,
|
|
468
464
|
coValue.knownState()
|
|
469
465
|
);
|
|
@@ -475,7 +471,7 @@ export class SyncManager {
|
|
|
475
471
|
}
|
|
476
472
|
}
|
|
477
473
|
|
|
478
|
-
handleUnsubscribe(_msg:
|
|
474
|
+
handleUnsubscribe(_msg: DoneMessage) {
|
|
479
475
|
throw new Error("Method not implemented.");
|
|
480
476
|
}
|
|
481
477
|
|
|
@@ -505,12 +501,11 @@ export class SyncManager {
|
|
|
505
501
|
|
|
506
502
|
function knownStateIn(
|
|
507
503
|
msg:
|
|
508
|
-
|
|
|
509
|
-
|
|
|
510
|
-
| WrongAssumedKnownStateMessage
|
|
504
|
+
| LoadMessage
|
|
505
|
+
| KnownStateMessage
|
|
511
506
|
) {
|
|
512
507
|
return {
|
|
513
|
-
|
|
508
|
+
id: msg.id,
|
|
514
509
|
header: msg.header,
|
|
515
510
|
sessions: msg.sessions,
|
|
516
511
|
};
|
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
|
+
}
|