cojson 0.0.21 → 0.0.23
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/dist/account.d.ts +1 -1
- package/dist/account.js +1 -1
- package/dist/account.js.map +1 -1
- package/dist/coValue.d.ts +10 -4
- package/dist/coValue.js +32 -15
- package/dist/coValue.js.map +1 -1
- package/dist/contentTypes/coMap.d.ts +2 -2
- package/dist/contentTypes/coMap.js +6 -6
- package/dist/contentTypes/coMap.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.js +68 -11
- package/dist/node.js.map +1 -1
- package/dist/permissions.d.ts +4 -32
- package/dist/permissions.js +42 -106
- package/dist/permissions.js.map +1 -1
- package/dist/team.d.ts +37 -0
- package/dist/team.js +116 -0
- package/dist/team.js.map +1 -0
- package/dist/testUtils.d.ts +2 -2
- package/dist/testUtils.js +1 -1
- package/dist/testUtils.js.map +1 -1
- package/package.json +2 -2
- package/src/account.ts +1 -1
- package/src/coValue.test.ts +58 -0
- package/src/coValue.ts +41 -24
- package/src/contentTypes/coMap.ts +8 -8
- package/src/crypto.test.ts +10 -9
- package/src/index.ts +5 -0
- package/src/node.ts +151 -26
- package/src/permissions.test.ts +503 -3
- package/src/permissions.ts +70 -206
- package/src/sync.test.ts +8 -8
- package/src/team.ts +233 -0
- package/src/testUtils.ts +1 -1
- package/tsconfig.json +1 -0
- package/dist/account.test.d.ts +0 -1
- package/dist/account.test.js +0 -40
- package/dist/account.test.js.map +0 -1
- package/dist/coValue.test.d.ts +0 -1
- package/dist/coValue.test.js +0 -78
- package/dist/coValue.test.js.map +0 -1
- package/dist/contentType.test.d.ts +0 -1
- package/dist/contentType.test.js +0 -145
- package/dist/contentType.test.js.map +0 -1
- package/dist/crypto.test.d.ts +0 -1
- package/dist/crypto.test.js +0 -111
- package/dist/crypto.test.js.map +0 -1
- package/dist/permissions.test.d.ts +0 -1
- package/dist/permissions.test.js +0 -711
- package/dist/permissions.test.js.map +0 -1
- package/dist/sync.test.d.ts +0 -1
- package/dist/sync.test.js +0 -827
- package/dist/sync.test.js.map +0 -1
package/src/coValue.ts
CHANGED
|
@@ -24,11 +24,10 @@ import { JsonObject, JsonValue } from "./jsonValue.js";
|
|
|
24
24
|
import { base58 } from "@scure/base";
|
|
25
25
|
import {
|
|
26
26
|
PermissionsDef as RulesetDef,
|
|
27
|
-
Team,
|
|
28
27
|
determineValidTransactions,
|
|
29
|
-
expectTeamContent,
|
|
30
28
|
isKeyForKeyField,
|
|
31
29
|
} from "./permissions.js";
|
|
30
|
+
import { Team, expectTeamContent } from "./team.js";
|
|
32
31
|
import { LocalNode } from "./node.js";
|
|
33
32
|
import { CoValueKnownState, NewContentMessage } from "./sync.js";
|
|
34
33
|
import { RawCoID, SessionID, TransactionID } from "./ids.js";
|
|
@@ -97,15 +96,31 @@ export class CoValue {
|
|
|
97
96
|
id: RawCoID;
|
|
98
97
|
node: LocalNode;
|
|
99
98
|
header: CoValueHeader;
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
_sessions: { [key: SessionID]: SessionLog };
|
|
100
|
+
_cachedContent?: ContentType;
|
|
102
101
|
listeners: Set<(content?: ContentType) => void> = new Set();
|
|
103
102
|
|
|
104
|
-
constructor(header: CoValueHeader, node: LocalNode) {
|
|
103
|
+
constructor(header: CoValueHeader, node: LocalNode, internalInitSessions: { [key: SessionID]: SessionLog } = {}) {
|
|
105
104
|
this.id = idforHeader(header);
|
|
106
105
|
this.header = header;
|
|
107
|
-
this.
|
|
106
|
+
this._sessions = internalInitSessions;
|
|
108
107
|
this.node = node;
|
|
108
|
+
|
|
109
|
+
if (header.ruleset.type == "ownedByTeam") {
|
|
110
|
+
this.node
|
|
111
|
+
.expectCoValueLoaded(header.ruleset.team)
|
|
112
|
+
.subscribe((_teamUpdate) => {
|
|
113
|
+
this._cachedContent = undefined;
|
|
114
|
+
const newContent = this.getCurrentContent();
|
|
115
|
+
for (const listener of this.listeners) {
|
|
116
|
+
listener(newContent);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get sessions(): Readonly<{ [key: SessionID]: SessionLog }> {
|
|
123
|
+
return this._sessions;
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
testWithDifferentAccount(
|
|
@@ -172,7 +187,10 @@ export class CoValue {
|
|
|
172
187
|
);
|
|
173
188
|
|
|
174
189
|
if (givenExpectedNewHash && givenExpectedNewHash !== expectedNewHash) {
|
|
175
|
-
console.warn("Invalid hash", {
|
|
190
|
+
console.warn("Invalid hash", {
|
|
191
|
+
expectedNewHash,
|
|
192
|
+
givenExpectedNewHash,
|
|
193
|
+
});
|
|
176
194
|
return false;
|
|
177
195
|
}
|
|
178
196
|
|
|
@@ -190,14 +208,14 @@ export class CoValue {
|
|
|
190
208
|
|
|
191
209
|
transactions.push(...newTransactions);
|
|
192
210
|
|
|
193
|
-
this.
|
|
211
|
+
this._sessions[sessionID] = {
|
|
194
212
|
transactions,
|
|
195
213
|
lastHash: expectedNewHash,
|
|
196
214
|
streamingHash: newStreamingHash,
|
|
197
215
|
lastSignature: newSignature,
|
|
198
216
|
};
|
|
199
217
|
|
|
200
|
-
this.
|
|
218
|
+
this._cachedContent = undefined;
|
|
201
219
|
|
|
202
220
|
const content = this.getCurrentContent();
|
|
203
221
|
|
|
@@ -296,23 +314,23 @@ export class CoValue {
|
|
|
296
314
|
}
|
|
297
315
|
|
|
298
316
|
getCurrentContent(): ContentType {
|
|
299
|
-
if (this.
|
|
300
|
-
return this.
|
|
317
|
+
if (this._cachedContent) {
|
|
318
|
+
return this._cachedContent;
|
|
301
319
|
}
|
|
302
320
|
|
|
303
321
|
if (this.header.type === "comap") {
|
|
304
|
-
this.
|
|
322
|
+
this._cachedContent = new CoMap(this);
|
|
305
323
|
} else if (this.header.type === "colist") {
|
|
306
|
-
this.
|
|
324
|
+
this._cachedContent = new CoList(this);
|
|
307
325
|
} else if (this.header.type === "costream") {
|
|
308
|
-
this.
|
|
326
|
+
this._cachedContent = new CoStream(this);
|
|
309
327
|
} else if (this.header.type === "static") {
|
|
310
|
-
this.
|
|
328
|
+
this._cachedContent = new Static(this);
|
|
311
329
|
} else {
|
|
312
330
|
throw new Error(`Unknown coValue type ${this.header.type}`);
|
|
313
331
|
}
|
|
314
332
|
|
|
315
|
-
return this.
|
|
333
|
+
return this._cachedContent;
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
getValidSortedTransactions(): DecryptedTransaction[] {
|
|
@@ -399,7 +417,9 @@ export class CoValue {
|
|
|
399
417
|
|
|
400
418
|
// Try to find key revelation for us
|
|
401
419
|
|
|
402
|
-
const readKeyEntry = content.getLastEntry(
|
|
420
|
+
const readKeyEntry = content.getLastEntry(
|
|
421
|
+
`${keyID}_for_${this.node.account.id}`
|
|
422
|
+
);
|
|
403
423
|
|
|
404
424
|
if (readKeyEntry) {
|
|
405
425
|
const revealer = accountOrAgentIDfromSessionID(
|
|
@@ -428,7 +448,8 @@ export class CoValue {
|
|
|
428
448
|
for (const field of content.keys()) {
|
|
429
449
|
if (isKeyForKeyField(field) && field.startsWith(keyID)) {
|
|
430
450
|
const encryptingKeyID = field.split("_for_")[1] as KeyID;
|
|
431
|
-
const encryptingKeySecret =
|
|
451
|
+
const encryptingKeySecret =
|
|
452
|
+
this.getReadKey(encryptingKeyID);
|
|
432
453
|
|
|
433
454
|
if (!encryptingKeySecret) {
|
|
434
455
|
continue;
|
|
@@ -453,7 +474,6 @@ export class CoValue {
|
|
|
453
474
|
);
|
|
454
475
|
}
|
|
455
476
|
}
|
|
456
|
-
|
|
457
477
|
}
|
|
458
478
|
|
|
459
479
|
return undefined;
|
|
@@ -525,10 +545,7 @@ export class CoValue {
|
|
|
525
545
|
),
|
|
526
546
|
};
|
|
527
547
|
|
|
528
|
-
if (
|
|
529
|
-
!newContent.header &&
|
|
530
|
-
Object.keys(newContent.new).length === 0
|
|
531
|
-
) {
|
|
548
|
+
if (!newContent.header && Object.keys(newContent.new).length === 0) {
|
|
532
549
|
return undefined;
|
|
533
550
|
}
|
|
534
551
|
|
|
@@ -544,4 +561,4 @@ export class CoValue {
|
|
|
544
561
|
? [this.header.ruleset.team]
|
|
545
562
|
: [];
|
|
546
563
|
}
|
|
547
|
-
}
|
|
564
|
+
}
|
|
@@ -12,12 +12,12 @@ type MapOp<K extends string, V extends JsonValue> = {
|
|
|
12
12
|
// TODO: add after TransactionID[] for conflicts/ordering
|
|
13
13
|
|
|
14
14
|
export type MapOpPayload<K extends string, V extends JsonValue> = {
|
|
15
|
-
op: "
|
|
15
|
+
op: "set";
|
|
16
16
|
key: K;
|
|
17
17
|
value: V;
|
|
18
18
|
} |
|
|
19
19
|
{
|
|
20
|
-
op: "
|
|
20
|
+
op: "del";
|
|
21
21
|
key: K;
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -81,7 +81,7 @@ export class CoMap<
|
|
|
81
81
|
|
|
82
82
|
const lastEntry = ops[ops.length - 1]!;
|
|
83
83
|
|
|
84
|
-
if (lastEntry.op === "
|
|
84
|
+
if (lastEntry.op === "del") {
|
|
85
85
|
return undefined;
|
|
86
86
|
} else {
|
|
87
87
|
return lastEntry.value;
|
|
@@ -100,7 +100,7 @@ export class CoMap<
|
|
|
100
100
|
return undefined;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
if (lastOpBeforeOrAtTime.op === "
|
|
103
|
+
if (lastOpBeforeOrAtTime.op === "del") {
|
|
104
104
|
return undefined;
|
|
105
105
|
} else {
|
|
106
106
|
return lastOpBeforeOrAtTime.value;
|
|
@@ -139,7 +139,7 @@ export class CoMap<
|
|
|
139
139
|
|
|
140
140
|
const lastEntry = ops[ops.length - 1]!;
|
|
141
141
|
|
|
142
|
-
if (lastEntry.op === "
|
|
142
|
+
if (lastEntry.op === "del") {
|
|
143
143
|
return undefined;
|
|
144
144
|
} else {
|
|
145
145
|
return { at: lastEntry.madeAt, txID: lastEntry.txID, value: lastEntry.value };
|
|
@@ -155,7 +155,7 @@ export class CoMap<
|
|
|
155
155
|
const history: { at: number; txID: TransactionID; value: M[K] | undefined; }[] = [];
|
|
156
156
|
|
|
157
157
|
for (const op of ops) {
|
|
158
|
-
if (op.op === "
|
|
158
|
+
if (op.op === "del") {
|
|
159
159
|
history.push({ at: op.madeAt, txID: op.txID, value: undefined });
|
|
160
160
|
} else {
|
|
161
161
|
history.push({ at: op.madeAt, txID: op.txID, value: op.value });
|
|
@@ -199,7 +199,7 @@ export class WriteableCoMap<
|
|
|
199
199
|
set<K extends MapK<M>>(key: K, value: M[K], privacy: "private" | "trusting" = "private"): void {
|
|
200
200
|
this.coValue.makeTransaction([
|
|
201
201
|
{
|
|
202
|
-
op: "
|
|
202
|
+
op: "set",
|
|
203
203
|
key,
|
|
204
204
|
value,
|
|
205
205
|
},
|
|
@@ -211,7 +211,7 @@ export class WriteableCoMap<
|
|
|
211
211
|
delete(key: MapK<M>, privacy: "private" | "trusting" = "private"): void {
|
|
212
212
|
this.coValue.makeTransaction([
|
|
213
213
|
{
|
|
214
|
-
op: "
|
|
214
|
+
op: "del",
|
|
215
215
|
key,
|
|
216
216
|
},
|
|
217
217
|
], privacy);
|
package/src/crypto.test.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { x25519 } from "@noble/curves/ed25519";
|
|
|
20
20
|
import { xsalsa20_poly1305 } from "@noble/ciphers/salsa";
|
|
21
21
|
import { blake3 } from "@noble/hashes/blake3";
|
|
22
22
|
import stableStringify from "fast-json-stable-stringify";
|
|
23
|
+
import { SessionID } from './ids.js';
|
|
23
24
|
|
|
24
25
|
test("Signatures round-trip and use stable stringify", () => {
|
|
25
26
|
const data = { b: "world", a: "hello" };
|
|
@@ -49,7 +50,7 @@ test("encrypting round-trips, but invalid receiver can't unseal", () => {
|
|
|
49
50
|
|
|
50
51
|
const nOnceMaterial = {
|
|
51
52
|
in: "co_zTEST",
|
|
52
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
53
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
53
54
|
} as const;
|
|
54
55
|
|
|
55
56
|
const sealed = seal(
|
|
@@ -101,22 +102,22 @@ test("Encryption for transactions round-trips", () => {
|
|
|
101
102
|
|
|
102
103
|
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
103
104
|
in: "co_zTEST",
|
|
104
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
105
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
105
106
|
});
|
|
106
107
|
|
|
107
108
|
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
108
109
|
in: "co_zTEST",
|
|
109
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
110
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
const decrypted1 = decryptForTransaction(encrypted1, secret, {
|
|
113
114
|
in: "co_zTEST",
|
|
114
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
115
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
115
116
|
});
|
|
116
117
|
|
|
117
118
|
const decrypted2 = decryptForTransaction(encrypted2, secret, {
|
|
118
119
|
in: "co_zTEST",
|
|
119
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
120
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
120
121
|
});
|
|
121
122
|
|
|
122
123
|
expect([decrypted1, decrypted2]).toEqual([{ a: "hello" }, { b: "world" }]);
|
|
@@ -128,22 +129,22 @@ test("Encryption for transactions doesn't decrypt with a wrong key", () => {
|
|
|
128
129
|
|
|
129
130
|
const encrypted1 = encryptForTransaction({ a: "hello" }, secret, {
|
|
130
131
|
in: "co_zTEST",
|
|
131
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
132
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
132
133
|
});
|
|
133
134
|
|
|
134
135
|
const encrypted2 = encryptForTransaction({ b: "world" }, secret, {
|
|
135
136
|
in: "co_zTEST",
|
|
136
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
137
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
137
138
|
});
|
|
138
139
|
|
|
139
140
|
const decrypted1 = decryptForTransaction(encrypted1, secret2, {
|
|
140
141
|
in: "co_zTEST",
|
|
141
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 0 },
|
|
142
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 0 },
|
|
142
143
|
});
|
|
143
144
|
|
|
144
145
|
const decrypted2 = decryptForTransaction(encrypted2, secret2, {
|
|
145
146
|
in: "co_zTEST",
|
|
146
|
-
tx: { sessionID: "co_zTEST_session_zTEST", txIndex: 1 },
|
|
147
|
+
tx: { sessionID: "co_zTEST_session_zTEST" as SessionID, txIndex: 1 },
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
expect([decrypted1, decrypted2]).toEqual([undefined, undefined]);
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { connectedPeers } from "./streamUtils.js";
|
|
15
15
|
import { AnonymousControlledAccount, ControlledAccount } from "./account.js";
|
|
16
16
|
import { rawCoIDtoBytes, rawCoIDfromBytes } from "./ids.js";
|
|
17
|
+
import { Team, expectTeamContent } from "./team.js"
|
|
17
18
|
|
|
18
19
|
import type { SessionID, AgentID } from "./ids.js";
|
|
19
20
|
import type { CoID, ContentType } from "./contentType.js";
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
ProfileContent,
|
|
27
28
|
Profile,
|
|
28
29
|
} from "./account.js";
|
|
30
|
+
import type { InviteSecret } from "./team.js";
|
|
29
31
|
|
|
30
32
|
type Value = JsonValue | ContentType;
|
|
31
33
|
|
|
@@ -42,6 +44,7 @@ export const cojsonInternals = {
|
|
|
42
44
|
agentSecretFromSecretSeed,
|
|
43
45
|
secretSeedLength,
|
|
44
46
|
shortHashLength,
|
|
47
|
+
expectTeamContent
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
export {
|
|
@@ -50,6 +53,7 @@ export {
|
|
|
50
53
|
CoMap,
|
|
51
54
|
AnonymousControlledAccount,
|
|
52
55
|
ControlledAccount,
|
|
56
|
+
Team
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
export type {
|
|
@@ -66,6 +70,7 @@ export type {
|
|
|
66
70
|
AccountContent,
|
|
67
71
|
Profile,
|
|
68
72
|
ProfileContent,
|
|
73
|
+
InviteSecret
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
package/src/node.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AgentSecret,
|
|
3
|
+
agentSecretFromSecretSeed,
|
|
3
4
|
createdNowUnique,
|
|
4
5
|
getAgentID,
|
|
5
6
|
getAgentSealerID,
|
|
@@ -9,7 +10,13 @@ import {
|
|
|
9
10
|
seal,
|
|
10
11
|
} from "./crypto.js";
|
|
11
12
|
import { CoValue, CoValueHeader, newRandomSessionID } from "./coValue.js";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
InviteSecret,
|
|
15
|
+
Team,
|
|
16
|
+
TeamContent,
|
|
17
|
+
expectTeamContent,
|
|
18
|
+
secretSeedFromInviteSecret,
|
|
19
|
+
} from "./team.js";
|
|
13
20
|
import { Peer, SyncManager } from "./sync.js";
|
|
14
21
|
import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
|
|
15
22
|
import { CoID, ContentType } from "./contentType.js";
|
|
@@ -43,7 +50,10 @@ export class LocalNode {
|
|
|
43
50
|
this.ownSessionID = ownSessionID;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
static withNewlyCreatedAccount(
|
|
53
|
+
static withNewlyCreatedAccount(
|
|
54
|
+
name: string,
|
|
55
|
+
initialAgentSecret = newRandomAgentSecret()
|
|
56
|
+
): {
|
|
47
57
|
node: LocalNode;
|
|
48
58
|
accountID: AccountID;
|
|
49
59
|
accountSecret: AgentSecret;
|
|
@@ -70,8 +80,16 @@ export class LocalNode {
|
|
|
70
80
|
};
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
static async withLoadedAccount(
|
|
74
|
-
|
|
83
|
+
static async withLoadedAccount(
|
|
84
|
+
accountID: AccountID,
|
|
85
|
+
accountSecret: AgentSecret,
|
|
86
|
+
sessionID: SessionID,
|
|
87
|
+
peersToLoadFrom: Peer[]
|
|
88
|
+
): Promise<LocalNode> {
|
|
89
|
+
const loadingNode = new LocalNode(
|
|
90
|
+
new AnonymousControlledAccount(accountSecret),
|
|
91
|
+
newRandomSessionID(accountID)
|
|
92
|
+
);
|
|
75
93
|
|
|
76
94
|
const accountPromise = loadingNode.load(accountID);
|
|
77
95
|
|
|
@@ -82,7 +100,10 @@ export class LocalNode {
|
|
|
82
100
|
const account = await accountPromise;
|
|
83
101
|
|
|
84
102
|
// since this is all synchronous, we can just swap out nodes for the SyncManager
|
|
85
|
-
const node = loadingNode.testWithDifferentAccount(
|
|
103
|
+
const node = loadingNode.testWithDifferentAccount(
|
|
104
|
+
new ControlledAccount(accountSecret, account, loadingNode),
|
|
105
|
+
sessionID
|
|
106
|
+
);
|
|
86
107
|
node.sync = loadingNode.sync;
|
|
87
108
|
node.sync.local = node;
|
|
88
109
|
|
|
@@ -124,7 +145,81 @@ export class LocalNode {
|
|
|
124
145
|
if (!profileID) {
|
|
125
146
|
throw new Error(`Account ${id} has no profile`);
|
|
126
147
|
}
|
|
127
|
-
return (
|
|
148
|
+
return (
|
|
149
|
+
await this.loadCoValue(profileID)
|
|
150
|
+
).getCurrentContent() as Profile;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async acceptInvite<T extends ContentType>(
|
|
154
|
+
teamOrOwnedValueID: CoID<T>,
|
|
155
|
+
inviteSecret: InviteSecret
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const teamOrOwnedValue = await this.load(teamOrOwnedValueID);
|
|
158
|
+
|
|
159
|
+
if (teamOrOwnedValue.coValue.header.ruleset.type === "ownedByTeam") {
|
|
160
|
+
return this.acceptInvite(
|
|
161
|
+
teamOrOwnedValue.coValue.header.ruleset.team as CoID<
|
|
162
|
+
CoMap<TeamContent>
|
|
163
|
+
>,
|
|
164
|
+
inviteSecret
|
|
165
|
+
);
|
|
166
|
+
} else if (teamOrOwnedValue.coValue.header.ruleset.type !== "team") {
|
|
167
|
+
throw new Error("Can only accept invites to teams");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const team = new Team(expectTeamContent(teamOrOwnedValue), this);
|
|
171
|
+
|
|
172
|
+
const inviteAgentSecret = agentSecretFromSecretSeed(
|
|
173
|
+
secretSeedFromInviteSecret(inviteSecret)
|
|
174
|
+
);
|
|
175
|
+
const inviteAgentID = getAgentID(inviteAgentSecret);
|
|
176
|
+
|
|
177
|
+
const invitationRole = await new Promise((resolve, reject) => {
|
|
178
|
+
team.teamMap.subscribe((teamMap) => {
|
|
179
|
+
const role = teamMap.get(inviteAgentID);
|
|
180
|
+
if (role) {
|
|
181
|
+
resolve(role);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
setTimeout(
|
|
185
|
+
() =>
|
|
186
|
+
reject(
|
|
187
|
+
new Error("Couldn't find invitation before timeout")
|
|
188
|
+
),
|
|
189
|
+
1000
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (!invitationRole) {
|
|
194
|
+
throw new Error("No invitation found");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const existingRole = team.teamMap.get(this.account.id);
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
existingRole === "admin" ||
|
|
201
|
+
(existingRole === "writer" && invitationRole === "reader")
|
|
202
|
+
) {
|
|
203
|
+
console.debug("Not accepting invite that would downgrade role");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const teamAsInvite = team.testWithDifferentAccount(
|
|
208
|
+
new AnonymousControlledAccount(inviteAgentSecret),
|
|
209
|
+
newRandomSessionID(inviteAgentID)
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
teamAsInvite.addMember(
|
|
213
|
+
this.account.id,
|
|
214
|
+
invitationRole === "adminInvite"
|
|
215
|
+
? "admin"
|
|
216
|
+
: invitationRole === "writerInvite"
|
|
217
|
+
? "writer"
|
|
218
|
+
: "reader"
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
team.teamMap.coValue._sessions = teamAsInvite.teamMap.coValue.sessions;
|
|
222
|
+
team.teamMap.coValue._cachedContent = undefined;
|
|
128
223
|
}
|
|
129
224
|
|
|
130
225
|
expectCoValueLoaded(id: RawCoID, expectation?: string): CoValue {
|
|
@@ -146,7 +241,9 @@ export class LocalNode {
|
|
|
146
241
|
|
|
147
242
|
expectProfileLoaded(id: AccountID, expectation?: string): Profile {
|
|
148
243
|
const account = this.expectCoValueLoaded(id, expectation);
|
|
149
|
-
const profileID = expectTeamContent(account.getCurrentContent()).get(
|
|
244
|
+
const profileID = expectTeamContent(account.getCurrentContent()).get(
|
|
245
|
+
"profile"
|
|
246
|
+
);
|
|
150
247
|
if (!profileID) {
|
|
151
248
|
throw new Error(
|
|
152
249
|
`${
|
|
@@ -154,10 +251,16 @@ export class LocalNode {
|
|
|
154
251
|
}Account ${id} has no profile`
|
|
155
252
|
);
|
|
156
253
|
}
|
|
157
|
-
return this.expectCoValueLoaded(
|
|
254
|
+
return this.expectCoValueLoaded(
|
|
255
|
+
profileID,
|
|
256
|
+
expectation
|
|
257
|
+
).getCurrentContent() as Profile;
|
|
158
258
|
}
|
|
159
259
|
|
|
160
|
-
createAccount(
|
|
260
|
+
createAccount(
|
|
261
|
+
name: string,
|
|
262
|
+
agentSecret = newRandomAgentSecret()
|
|
263
|
+
): ControlledAccount {
|
|
161
264
|
const account = this.createCoValue(
|
|
162
265
|
accountHeaderForInitialAgentSecret(agentSecret)
|
|
163
266
|
).testWithDifferentAccount(
|
|
@@ -165,7 +268,10 @@ export class LocalNode {
|
|
|
165
268
|
newRandomSessionID(getAgentID(agentSecret))
|
|
166
269
|
);
|
|
167
270
|
|
|
168
|
-
const accountAsTeam = new Team(
|
|
271
|
+
const accountAsTeam = new Team(
|
|
272
|
+
expectTeamContent(account.getCurrentContent()),
|
|
273
|
+
account.node
|
|
274
|
+
);
|
|
169
275
|
|
|
170
276
|
accountAsTeam.teamMap.edit((editable) => {
|
|
171
277
|
editable.set(getAgentID(agentSecret), "admin", "trusting");
|
|
@@ -195,7 +301,9 @@ export class LocalNode {
|
|
|
195
301
|
account.node
|
|
196
302
|
);
|
|
197
303
|
|
|
198
|
-
const profile = accountAsTeam.createMap<ProfileContent, ProfileMeta>({
|
|
304
|
+
const profile = accountAsTeam.createMap<ProfileContent, ProfileMeta>({
|
|
305
|
+
type: "profile",
|
|
306
|
+
});
|
|
199
307
|
|
|
200
308
|
profile.edit((editable) => {
|
|
201
309
|
editable.set("name", name, "trusting");
|
|
@@ -205,6 +313,11 @@ export class LocalNode {
|
|
|
205
313
|
editable.set("profile", profile.id, "trusting");
|
|
206
314
|
});
|
|
207
315
|
|
|
316
|
+
const accountOnThisNode = this.expectCoValueLoaded(account.id);
|
|
317
|
+
|
|
318
|
+
accountOnThisNode._sessions = {...accountAsTeam.teamMap.coValue.sessions};
|
|
319
|
+
accountOnThisNode._cachedContent = undefined;
|
|
320
|
+
|
|
208
321
|
return controlledAccount;
|
|
209
322
|
}
|
|
210
323
|
|
|
@@ -276,24 +389,36 @@ export class LocalNode {
|
|
|
276
389
|
): LocalNode {
|
|
277
390
|
const newNode = new LocalNode(account, ownSessionID);
|
|
278
391
|
|
|
279
|
-
|
|
280
|
-
Object.entries(this.coValues)
|
|
281
|
-
.map(([id, entry]) => {
|
|
282
|
-
if (entry.state === "loading") {
|
|
283
|
-
return undefined;
|
|
284
|
-
}
|
|
392
|
+
const coValuesToCopy = Object.entries(this.coValues);
|
|
285
393
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
);
|
|
394
|
+
while (coValuesToCopy.length > 0) {
|
|
395
|
+
const [coValueID, entry] =
|
|
396
|
+
coValuesToCopy[coValuesToCopy.length - 1]!;
|
|
290
397
|
|
|
291
|
-
|
|
398
|
+
if (entry.state === "loading") {
|
|
399
|
+
coValuesToCopy.pop();
|
|
400
|
+
continue;
|
|
401
|
+
} else {
|
|
402
|
+
const allDepsCopied = entry.coValue
|
|
403
|
+
.getDependedOnCoValues()
|
|
404
|
+
.every((dep) => newNode.coValues[dep]?.state === "loaded");
|
|
292
405
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
406
|
+
if (!allDepsCopied) {
|
|
407
|
+
// move to end of queue
|
|
408
|
+
coValuesToCopy.unshift(coValuesToCopy.pop()!);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const newCoValue = new CoValue(entry.coValue.header, newNode, {...entry.coValue.sessions});
|
|
413
|
+
|
|
414
|
+
newNode.coValues[coValueID as RawCoID] = {
|
|
415
|
+
state: "loaded",
|
|
416
|
+
coValue: newCoValue,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
coValuesToCopy.pop();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
297
422
|
|
|
298
423
|
return newNode;
|
|
299
424
|
}
|