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.
Files changed (56) hide show
  1. package/dist/account.d.ts +1 -1
  2. package/dist/account.js +1 -1
  3. package/dist/account.js.map +1 -1
  4. package/dist/coValue.d.ts +10 -4
  5. package/dist/coValue.js +32 -15
  6. package/dist/coValue.js.map +1 -1
  7. package/dist/contentTypes/coMap.d.ts +2 -2
  8. package/dist/contentTypes/coMap.js +6 -6
  9. package/dist/contentTypes/coMap.js.map +1 -1
  10. package/dist/index.d.ts +5 -2
  11. package/dist/index.js +3 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/node.d.ts +2 -1
  14. package/dist/node.js +68 -11
  15. package/dist/node.js.map +1 -1
  16. package/dist/permissions.d.ts +4 -32
  17. package/dist/permissions.js +42 -106
  18. package/dist/permissions.js.map +1 -1
  19. package/dist/team.d.ts +37 -0
  20. package/dist/team.js +116 -0
  21. package/dist/team.js.map +1 -0
  22. package/dist/testUtils.d.ts +2 -2
  23. package/dist/testUtils.js +1 -1
  24. package/dist/testUtils.js.map +1 -1
  25. package/package.json +2 -2
  26. package/src/account.ts +1 -1
  27. package/src/coValue.test.ts +58 -0
  28. package/src/coValue.ts +41 -24
  29. package/src/contentTypes/coMap.ts +8 -8
  30. package/src/crypto.test.ts +10 -9
  31. package/src/index.ts +5 -0
  32. package/src/node.ts +151 -26
  33. package/src/permissions.test.ts +503 -3
  34. package/src/permissions.ts +70 -206
  35. package/src/sync.test.ts +8 -8
  36. package/src/team.ts +233 -0
  37. package/src/testUtils.ts +1 -1
  38. package/tsconfig.json +1 -0
  39. package/dist/account.test.d.ts +0 -1
  40. package/dist/account.test.js +0 -40
  41. package/dist/account.test.js.map +0 -1
  42. package/dist/coValue.test.d.ts +0 -1
  43. package/dist/coValue.test.js +0 -78
  44. package/dist/coValue.test.js.map +0 -1
  45. package/dist/contentType.test.d.ts +0 -1
  46. package/dist/contentType.test.js +0 -145
  47. package/dist/contentType.test.js.map +0 -1
  48. package/dist/crypto.test.d.ts +0 -1
  49. package/dist/crypto.test.js +0 -111
  50. package/dist/crypto.test.js.map +0 -1
  51. package/dist/permissions.test.d.ts +0 -1
  52. package/dist/permissions.test.js +0 -711
  53. package/dist/permissions.test.js.map +0 -1
  54. package/dist/sync.test.d.ts +0 -1
  55. package/dist/sync.test.js +0 -827
  56. package/dist/sync.test.js.map +0 -1
@@ -1,16 +1,8 @@
1
- import { CoID, ContentType } from "./contentType.js";
2
- import { CoMap, MapOpPayload } from "./contentTypes/coMap.js";
3
- import { JsonObject, JsonValue } from "./jsonValue.js";
1
+ import { CoID } from "./contentType.js";
2
+ import { MapOpPayload } from "./contentTypes/coMap.js";
3
+ import { JsonValue } from "./jsonValue.js";
4
4
  import {
5
- Encrypted,
6
5
  KeyID,
7
- KeySecret,
8
- createdNowUnique,
9
- newRandomKeySecret,
10
- seal,
11
- encryptKeySecret,
12
- getAgentSealerID,
13
- Sealed,
14
6
  } from "./crypto.js";
15
7
  import {
16
8
  CoValue,
@@ -18,16 +10,25 @@ import {
18
10
  TrustingTransaction,
19
11
  accountOrAgentIDfromSessionID,
20
12
  } from "./coValue.js";
21
- import { LocalNode } from "./node.js";
22
- import { RawCoID, SessionID, TransactionID, isAgentID } from "./ids.js";
23
- import { AccountIDOrAgentID, GeneralizedControlledAccount, Profile } from "./account.js";
13
+ import { RawCoID, SessionID, TransactionID } from "./ids.js";
14
+ import {
15
+ AccountIDOrAgentID,
16
+ Profile,
17
+ } from "./account.js";
24
18
 
25
19
  export type PermissionsDef =
26
20
  | { type: "team"; initialAdmin: AccountIDOrAgentID }
27
21
  | { type: "ownedByTeam"; team: RawCoID }
28
22
  | { type: "unsafeAllowAll" };
29
23
 
30
- export type Role = "reader" | "writer" | "admin" | "revoked";
24
+ export type Role =
25
+ | "reader"
26
+ | "writer"
27
+ | "admin"
28
+ | "revoked"
29
+ | "adminInvite"
30
+ | "writerInvite"
31
+ | "readerInvite";
31
32
 
32
33
  export function determineValidTransactions(
33
34
  coValue: CoValue
@@ -84,7 +85,7 @@ export function determineValidTransactions(
84
85
  continue;
85
86
  }
86
87
 
87
- if (change.op !== "insert") {
88
+ if (change.op !== "set") {
88
89
  console.warn("Team transaction must set a role or readKey");
89
90
  continue;
90
91
  }
@@ -97,7 +98,7 @@ export function determineValidTransactions(
97
98
 
98
99
  validTransactions.push({ txID: { sessionID, txIndex }, tx });
99
100
  continue;
100
- } else if (change.key === 'profile') {
101
+ } else if (change.key === "profile") {
101
102
  if (memberState[transactor] !== "admin") {
102
103
  console.warn("Only admins can set profile");
103
104
  continue;
@@ -105,8 +106,16 @@ export function determineValidTransactions(
105
106
 
106
107
  validTransactions.push({ txID: { sessionID, txIndex }, tx });
107
108
  continue;
108
- } else if (isKeyForKeyField(change.key) || isKeyForAccountField(change.key)) {
109
- if (memberState[transactor] !== "admin") {
109
+ } else if (
110
+ isKeyForKeyField(change.key) ||
111
+ isKeyForAccountField(change.key)
112
+ ) {
113
+ if (
114
+ memberState[transactor] !== "admin" &&
115
+ memberState[transactor] !== "adminInvite" &&
116
+ memberState[transactor] !== "writerInvite" &&
117
+ memberState[transactor] !== "readerInvite"
118
+ ) {
110
119
  console.warn("Only admins can reveal keys");
111
120
  continue;
112
121
  }
@@ -124,7 +133,10 @@ export function determineValidTransactions(
124
133
  change.value !== "admin" &&
125
134
  change.value !== "writer" &&
126
135
  change.value !== "reader" &&
127
- change.value !== "revoked"
136
+ change.value !== "revoked" &&
137
+ change.value !== "adminInvite" &&
138
+ change.value !== "writerInvite" &&
139
+ change.value !== "readerInvite"
128
140
  ) {
129
141
  console.warn("Team transaction must set a valid role");
130
142
  continue;
@@ -133,26 +145,41 @@ export function determineValidTransactions(
133
145
  const isFirstSelfAppointment =
134
146
  !memberState[transactor] &&
135
147
  transactor === initialAdmin &&
136
- change.op === "insert" &&
148
+ change.op === "set" &&
137
149
  change.key === transactor &&
138
150
  change.value === "admin";
139
151
 
140
152
  if (!isFirstSelfAppointment) {
141
- if (memberState[transactor] !== "admin") {
153
+ if (memberState[transactor] === "admin") {
154
+ if (
155
+ memberState[affectedMember] === "admin" &&
156
+ affectedMember !== transactor &&
157
+ assignedRole !== "admin"
158
+ ) {
159
+ console.warn("Admins can only demote themselves.");
160
+ continue;
161
+ }
162
+ } else if (memberState[transactor] === "adminInvite") {
163
+ if (change.value !== "admin") {
164
+ console.warn("AdminInvites can only create admins.");
165
+ continue;
166
+ }
167
+ } else if (memberState[transactor] === "writerInvite") {
168
+ if (change.value !== "writer") {
169
+ console.warn("WriterInvites can only create writers.");
170
+ continue;
171
+ }
172
+ } else if (memberState[transactor] === "readerInvite") {
173
+ if (change.value !== "reader") {
174
+ console.warn("ReaderInvites can only create reader.");
175
+ continue;
176
+ }
177
+ } else {
142
178
  console.warn(
143
- "Team transaction must be made by current admin"
179
+ "Team transaction must be made by current admin or invite"
144
180
  );
145
181
  continue;
146
182
  }
147
-
148
- if (
149
- memberState[affectedMember] === "admin" &&
150
- affectedMember !== transactor &&
151
- assignedRole !== "admin"
152
- ) {
153
- console.warn("Admins can only demote themselves.");
154
- continue;
155
- }
156
183
  }
157
184
 
158
185
  memberState[affectedMember] = change.value;
@@ -213,180 +240,17 @@ export function determineValidTransactions(
213
240
  }
214
241
  }
215
242
 
216
- export type TeamContent = {
217
- profile: CoID<Profile> | null;
218
- [key: AccountIDOrAgentID]: Role;
219
- readKey: KeyID;
220
- [revelationFor: `${KeyID}_for_${AccountIDOrAgentID}`]: Sealed<KeySecret>;
221
- [oldKeyForNewKey: `${KeyID}_for_${KeyID}`]: Encrypted<
222
- KeySecret,
223
- { encryptedID: KeyID; encryptingID: KeyID }
224
- >;
225
- };
226
-
227
- export function expectTeamContent(
228
- content: ContentType
229
- ): CoMap<TeamContent, JsonObject | null> {
230
- if (content.type !== "comap") {
231
- throw new Error("Expected map");
232
- }
233
-
234
- return content as CoMap<TeamContent, JsonObject | null>;
235
- }
236
-
237
- export class Team {
238
- teamMap: CoMap<TeamContent, JsonObject | null>;
239
- node: LocalNode;
240
-
241
- constructor(teamMap: CoMap<TeamContent, JsonObject | null>, node: LocalNode) {
242
- this.teamMap = teamMap;
243
- this.node = node;
244
- }
245
-
246
- get id(): CoID<CoMap<TeamContent, JsonObject | null>> {
247
- return this.teamMap.id;
248
- }
249
-
250
- addMember(accountID: AccountIDOrAgentID, role: Role) {
251
- this.teamMap = this.teamMap.edit((map) => {
252
- const currentReadKey = this.teamMap.coValue.getCurrentReadKey();
253
-
254
- if (!currentReadKey.secret) {
255
- throw new Error("Can't add member without read key secret");
256
- }
257
-
258
- const agent = this.node.resolveAccountAgent(
259
- accountID,
260
- "Expected to know agent to add them to team"
261
- );
262
-
263
- map.set(accountID, role, "trusting");
264
-
265
- if (map.get(accountID) !== role) {
266
- throw new Error("Failed to set role");
267
- }
268
-
269
- map.set(
270
- `${currentReadKey.id}_for_${accountID}`,
271
- seal(
272
- currentReadKey.secret,
273
- this.teamMap.coValue.node.account.currentSealerSecret(),
274
- getAgentSealerID(agent),
275
- {
276
- in: this.teamMap.coValue.id,
277
- tx: this.teamMap.coValue.nextTransactionID(),
278
- }
279
- ),
280
- "trusting"
281
- );
282
- });
283
- }
284
-
285
- rotateReadKey() {
286
- const currentlyPermittedReaders = this.teamMap.keys().filter((key) => {
287
- if (key.startsWith("co_") || isAgentID(key)) {
288
- const role = this.teamMap.get(key);
289
- return (
290
- role === "admin" || role === "writer" || role === "reader"
291
- );
292
- } else {
293
- return false;
294
- }
295
- }) as AccountIDOrAgentID[];
296
-
297
- const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
298
-
299
- if (!maybeCurrentReadKey.secret) {
300
- throw new Error(
301
- "Can't rotate read key secret we don't have access to"
302
- );
303
- }
304
-
305
- const currentReadKey = {
306
- id: maybeCurrentReadKey.id,
307
- secret: maybeCurrentReadKey.secret,
308
- };
309
-
310
- const newReadKey = newRandomKeySecret();
311
-
312
- this.teamMap = this.teamMap.edit((map) => {
313
- for (const readerID of currentlyPermittedReaders) {
314
- const reader = this.node.resolveAccountAgent(
315
- readerID,
316
- "Expected to know currently permitted reader"
317
- );
318
-
319
- map.set(
320
- `${newReadKey.id}_for_${readerID}`,
321
- seal(
322
- newReadKey.secret,
323
- this.teamMap.coValue.node.account.currentSealerSecret(),
324
- getAgentSealerID(reader),
325
- {
326
- in: this.teamMap.coValue.id,
327
- tx: this.teamMap.coValue.nextTransactionID(),
328
- }
329
- ),
330
- "trusting"
331
- );
332
- }
333
-
334
- map.set(
335
- `${currentReadKey.id}_for_${newReadKey.id}`,
336
- encryptKeySecret({
337
- encrypting: newReadKey,
338
- toEncrypt: currentReadKey,
339
- }).encrypted,
340
- "trusting"
341
- );
342
-
343
- map.set("readKey", newReadKey.id, "trusting");
344
- });
345
- }
346
-
347
- removeMember(accountID: AccountIDOrAgentID) {
348
- this.teamMap = this.teamMap.edit((map) => {
349
- map.set(accountID, "revoked", "trusting");
350
- });
351
-
352
- this.rotateReadKey();
353
- }
354
-
355
- createMap<M extends { [key: string]: JsonValue }, Meta extends JsonObject | null = null>(
356
- meta?: Meta
357
- ): CoMap<M, Meta> {
358
- return this.node
359
- .createCoValue({
360
- type: "comap",
361
- ruleset: {
362
- type: "ownedByTeam",
363
- team: this.teamMap.id,
364
- },
365
- meta: meta || null,
366
- ...createdNowUnique(),
367
- })
368
- .getCurrentContent() as CoMap<M, Meta>;
369
- }
370
-
371
- testWithDifferentAccount(
372
- account: GeneralizedControlledAccount,
373
- sessionId: SessionID
374
- ): Team {
375
- return new Team(
376
- expectTeamContent(
377
- this.teamMap.coValue
378
- .testWithDifferentAccount(account, sessionId)
379
- .getCurrentContent()
380
- ),
381
- this.node
382
- );
383
- }
384
- }
385
-
386
- export function isKeyForKeyField(field: string): field is `${KeyID}_for_${KeyID}` {
243
+ export function isKeyForKeyField(
244
+ field: string
245
+ ): field is `${KeyID}_for_${KeyID}` {
387
246
  return field.startsWith("key_") && field.includes("_for_key");
388
247
  }
389
248
 
390
- export function isKeyForAccountField(field: string): field is `${KeyID}_for_${AccountIDOrAgentID}` {
391
- return field.startsWith("key_") && (field.includes("_for_sealer") || field.includes("_for_co"));
392
- }
249
+ export function isKeyForAccountField(
250
+ field: string
251
+ ): field is `${KeyID}_for_${AccountIDOrAgentID}` {
252
+ return (
253
+ field.startsWith("key_") &&
254
+ (field.includes("_for_sealer") || field.includes("_for_co"))
255
+ );
256
+ }
package/src/sync.test.ts CHANGED
@@ -3,7 +3,7 @@ import { LocalNode } from "./node.js";
3
3
  import { Peer, PeerID, SyncMessage } from "./sync.js";
4
4
  import { expectMap } from "./contentType.js";
5
5
  import { MapOpPayload } from "./contentTypes/coMap.js";
6
- import { Team } from "./permissions.js";
6
+ import { Team } from "./team.js";
7
7
  import {
8
8
  ReadableStream,
9
9
  WritableStream,
@@ -86,7 +86,7 @@ test("Node replies with initial tx and header to empty subscribe", async () => {
86
86
  .transactions[0]!.madeAt,
87
87
  changes: [
88
88
  {
89
- op: "insert",
89
+ op: "set",
90
90
  key: "hello",
91
91
  value: "world",
92
92
  } satisfies MapOpPayload<string, string>,
@@ -164,7 +164,7 @@ test("Node replies with only new tx to subscribe with some known state", async (
164
164
  .transactions[1]!.madeAt,
165
165
  changes: [
166
166
  {
167
- op: "insert",
167
+ op: "set",
168
168
  key: "goodbye",
169
169
  value: "world",
170
170
  } satisfies MapOpPayload<string, string>,
@@ -253,7 +253,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
253
253
  .transactions[0]!.madeAt,
254
254
  changes: [
255
255
  {
256
- op: "insert",
256
+ op: "set",
257
257
  key: "hello",
258
258
  value: "world",
259
259
  } satisfies MapOpPayload<string, string>,
@@ -285,7 +285,7 @@ test("After subscribing, node sends own known state and new txs to peer", async
285
285
  .transactions[1]!.madeAt,
286
286
  changes: [
287
287
  {
288
- op: "insert",
288
+ op: "set",
289
289
  key: "goodbye",
290
290
  value: "world",
291
291
  } satisfies MapOpPayload<string, string>,
@@ -364,7 +364,7 @@ test("Client replies with known new content to tellKnownState from server", asyn
364
364
  .transactions[0]!.madeAt,
365
365
  changes: [
366
366
  {
367
- op: "insert",
367
+ op: "set",
368
368
  key: "hello",
369
369
  value: "world",
370
370
  } satisfies MapOpPayload<string, string>,
@@ -467,7 +467,7 @@ test("No matter the optimistic known state, node respects invalid known state me
467
467
  .transactions[1]!.madeAt,
468
468
  changes: [
469
469
  {
470
- op: "insert",
470
+ op: "set",
471
471
  key: "goodbye",
472
472
  value: "world",
473
473
  } satisfies MapOpPayload<string, string>,
@@ -570,7 +570,7 @@ test("If we add a server peer, all updates to all coValues are sent to it, even
570
570
  .transactions[0]!.madeAt,
571
571
  changes: [
572
572
  {
573
- op: "insert",
573
+ op: "set",
574
574
  key: "hello",
575
575
  value: "world",
576
576
  } satisfies MapOpPayload<string, string>,
package/src/team.ts ADDED
@@ -0,0 +1,233 @@
1
+ import { CoID, ContentType } from "./contentType.js";
2
+ import { CoMap } from "./contentTypes/coMap.js";
3
+ import { JsonObject, JsonValue } from "./jsonValue.js";
4
+ import {
5
+ Encrypted,
6
+ KeyID,
7
+ KeySecret,
8
+ createdNowUnique,
9
+ newRandomKeySecret,
10
+ seal,
11
+ encryptKeySecret,
12
+ getAgentSealerID,
13
+ Sealed,
14
+ newRandomSecretSeed,
15
+ agentSecretFromSecretSeed,
16
+ getAgentID,
17
+ } from "./crypto.js";
18
+ import { LocalNode } from "./node.js";
19
+ import { SessionID, isAgentID } from "./ids.js";
20
+ import {
21
+ AccountIDOrAgentID,
22
+ GeneralizedControlledAccount,
23
+ Profile,
24
+ } from "./account.js";
25
+ import { Role } from "./permissions.js";
26
+ import { base58 } from "@scure/base";
27
+
28
+ export type TeamContent = {
29
+ profile: CoID<Profile> | null;
30
+ [key: AccountIDOrAgentID]: Role;
31
+ readKey: KeyID;
32
+ [revelationFor: `${KeyID}_for_${AccountIDOrAgentID}`]: Sealed<KeySecret>;
33
+ [oldKeyForNewKey: `${KeyID}_for_${KeyID}`]: Encrypted<
34
+ KeySecret,
35
+ { encryptedID: KeyID; encryptingID: KeyID }
36
+ >;
37
+ };
38
+
39
+ export function expectTeamContent(
40
+ content: ContentType
41
+ ): CoMap<TeamContent, JsonObject | null> {
42
+ if (content.type !== "comap") {
43
+ throw new Error("Expected map");
44
+ }
45
+
46
+ return content as CoMap<TeamContent, JsonObject | null>;
47
+ }
48
+
49
+ export class Team {
50
+ teamMap: CoMap<TeamContent, JsonObject | null>;
51
+ node: LocalNode;
52
+
53
+ constructor(
54
+ teamMap: CoMap<TeamContent, JsonObject | null>,
55
+ node: LocalNode
56
+ ) {
57
+ this.teamMap = teamMap;
58
+ this.node = node;
59
+ }
60
+
61
+ get id(): CoID<CoMap<TeamContent, JsonObject | null>> {
62
+ return this.teamMap.id;
63
+ }
64
+
65
+ roleOf(accountID: AccountIDOrAgentID): Role | undefined {
66
+ return this.teamMap.get(accountID);
67
+ }
68
+
69
+ myRole(): Role | undefined {
70
+ return this.roleOf(this.node.account.id);
71
+ }
72
+
73
+ addMember(accountID: AccountIDOrAgentID, role: Role) {
74
+ this.teamMap = this.teamMap.edit((map) => {
75
+ const currentReadKey = this.teamMap.coValue.getCurrentReadKey();
76
+
77
+ if (!currentReadKey.secret) {
78
+ throw new Error("Can't add member without read key secret");
79
+ }
80
+
81
+ const agent = this.node.resolveAccountAgent(
82
+ accountID,
83
+ "Expected to know agent to add them to team"
84
+ );
85
+
86
+ map.set(accountID, role, "trusting");
87
+
88
+ if (map.get(accountID) !== role) {
89
+ throw new Error("Failed to set role");
90
+ }
91
+
92
+ map.set(
93
+ `${currentReadKey.id}_for_${accountID}`,
94
+ seal(
95
+ currentReadKey.secret,
96
+ this.teamMap.coValue.node.account.currentSealerSecret(),
97
+ getAgentSealerID(agent),
98
+ {
99
+ in: this.teamMap.coValue.id,
100
+ tx: this.teamMap.coValue.nextTransactionID(),
101
+ }
102
+ ),
103
+ "trusting"
104
+ );
105
+ });
106
+ }
107
+
108
+ createInvite(role: "reader" | "writer" | "admin"): InviteSecret {
109
+ const secretSeed = newRandomSecretSeed();
110
+
111
+ const inviteSecret = agentSecretFromSecretSeed(secretSeed);
112
+ const inviteID = getAgentID(inviteSecret);
113
+
114
+ this.addMember(inviteID, `${role}Invite` as Role);
115
+
116
+ return inviteSecretFromSecretSeed(secretSeed);
117
+ }
118
+
119
+ rotateReadKey() {
120
+ const currentlyPermittedReaders = this.teamMap.keys().filter((key) => {
121
+ if (key.startsWith("co_") || isAgentID(key)) {
122
+ const role = this.teamMap.get(key);
123
+ return (
124
+ role === "admin" || role === "writer" || role === "reader"
125
+ );
126
+ } else {
127
+ return false;
128
+ }
129
+ }) as AccountIDOrAgentID[];
130
+
131
+ const maybeCurrentReadKey = this.teamMap.coValue.getCurrentReadKey();
132
+
133
+ if (!maybeCurrentReadKey.secret) {
134
+ throw new Error(
135
+ "Can't rotate read key secret we don't have access to"
136
+ );
137
+ }
138
+
139
+ const currentReadKey = {
140
+ id: maybeCurrentReadKey.id,
141
+ secret: maybeCurrentReadKey.secret,
142
+ };
143
+
144
+ const newReadKey = newRandomKeySecret();
145
+
146
+ this.teamMap = this.teamMap.edit((map) => {
147
+ for (const readerID of currentlyPermittedReaders) {
148
+ const reader = this.node.resolveAccountAgent(
149
+ readerID,
150
+ "Expected to know currently permitted reader"
151
+ );
152
+
153
+ map.set(
154
+ `${newReadKey.id}_for_${readerID}`,
155
+ seal(
156
+ newReadKey.secret,
157
+ this.teamMap.coValue.node.account.currentSealerSecret(),
158
+ getAgentSealerID(reader),
159
+ {
160
+ in: this.teamMap.coValue.id,
161
+ tx: this.teamMap.coValue.nextTransactionID(),
162
+ }
163
+ ),
164
+ "trusting"
165
+ );
166
+ }
167
+
168
+ map.set(
169
+ `${currentReadKey.id}_for_${newReadKey.id}`,
170
+ encryptKeySecret({
171
+ encrypting: newReadKey,
172
+ toEncrypt: currentReadKey,
173
+ }).encrypted,
174
+ "trusting"
175
+ );
176
+
177
+ map.set("readKey", newReadKey.id, "trusting");
178
+ });
179
+ }
180
+
181
+ removeMember(accountID: AccountIDOrAgentID) {
182
+ this.teamMap = this.teamMap.edit((map) => {
183
+ map.set(accountID, "revoked", "trusting");
184
+ });
185
+
186
+ this.rotateReadKey();
187
+ }
188
+
189
+ createMap<
190
+ M extends { [key: string]: JsonValue },
191
+ Meta extends JsonObject | null = null
192
+ >(meta?: Meta): CoMap<M, Meta> {
193
+ return this.node
194
+ .createCoValue({
195
+ type: "comap",
196
+ ruleset: {
197
+ type: "ownedByTeam",
198
+ team: this.teamMap.id,
199
+ },
200
+ meta: meta || null,
201
+ ...createdNowUnique(),
202
+ })
203
+ .getCurrentContent() as CoMap<M, Meta>;
204
+ }
205
+
206
+ testWithDifferentAccount(
207
+ account: GeneralizedControlledAccount,
208
+ sessionId: SessionID
209
+ ): Team {
210
+ return new Team(
211
+ expectTeamContent(
212
+ this.teamMap.coValue
213
+ .testWithDifferentAccount(account, sessionId)
214
+ .getCurrentContent()
215
+ ),
216
+ this.node
217
+ );
218
+ }
219
+ }
220
+
221
+ export type InviteSecret = `inviteSecret_z${string}`;
222
+
223
+ function inviteSecretFromSecretSeed(secretSeed: Uint8Array): InviteSecret {
224
+ return `inviteSecret_z${base58.encode(secretSeed)}`;
225
+ }
226
+
227
+ export function secretSeedFromInviteSecret(inviteSecret: InviteSecret) {
228
+ if (!inviteSecret.startsWith("inviteSecret_z")) {
229
+ throw new Error("Invalid invite secret");
230
+ }
231
+
232
+ return base58.decode(inviteSecret.slice("inviteSecret_z".length));
233
+ }
package/src/testUtils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AgentSecret, createdNowUnique, getAgentID, newRandomAgentSecret } from "./crypto.js";
2
2
  import { newRandomSessionID } from "./coValue.js";
3
3
  import { LocalNode } from "./node.js";
4
- import { expectTeamContent } from "./permissions.js";
4
+ import { expectTeamContent } from "./team.js";
5
5
  import { AnonymousControlledAccount } from "./account.js";
6
6
  import { SessionID } from "./ids.js";
7
7
 
package/tsconfig.json CHANGED
@@ -12,4 +12,5 @@
12
12
  "esModuleInterop": true,
13
13
  },
14
14
  "include": ["./src/**/*"],
15
+ "exclude": ["./src/**/*.test.*"],
15
16
  }
@@ -1 +0,0 @@
1
- export {};