cojson 0.3.7 → 0.4.1

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 (79) hide show
  1. package/dist/coValue.d.ts +7 -12
  2. package/dist/coValue.js.map +1 -1
  3. package/dist/coValueCore.d.ts +9 -4
  4. package/dist/coValueCore.js +69 -33
  5. package/dist/coValueCore.js.map +1 -1
  6. package/dist/coValues/account.d.ts +62 -0
  7. package/dist/{account.js → coValues/account.js} +19 -11
  8. package/dist/coValues/account.js.map +1 -0
  9. package/dist/coValues/coList.d.ts +19 -19
  10. package/dist/coValues/coList.js.map +1 -1
  11. package/dist/coValues/coMap.d.ts +31 -23
  12. package/dist/coValues/coMap.js +4 -6
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.d.ts +20 -20
  15. package/dist/coValues/coStream.js +2 -1
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/{group.d.ts → coValues/group.d.ts} +27 -38
  18. package/dist/{group.js → coValues/group.js} +69 -73
  19. package/dist/coValues/group.js.map +1 -0
  20. package/dist/ids.d.ts +1 -1
  21. package/dist/index.d.ts +15 -11
  22. package/dist/index.js +10 -5
  23. package/dist/index.js.map +1 -1
  24. package/dist/localNode.d.ts +20 -7
  25. package/dist/localNode.js +74 -39
  26. package/dist/localNode.js.map +1 -1
  27. package/dist/media.d.ts +1 -1
  28. package/dist/permissions.d.ts +1 -1
  29. package/dist/permissions.js +43 -22
  30. package/dist/permissions.js.map +1 -1
  31. package/dist/queriedCoValues/queriedAccount.d.ts +13 -0
  32. package/dist/queriedCoValues/queriedAccount.js +24 -0
  33. package/dist/queriedCoValues/queriedAccount.js.map +1 -0
  34. package/dist/queriedCoValues/queriedCoList.d.ts +10 -10
  35. package/dist/queriedCoValues/queriedCoList.js +11 -15
  36. package/dist/queriedCoValues/queriedCoList.js.map +1 -1
  37. package/dist/queriedCoValues/queriedCoMap.d.ts +14 -21
  38. package/dist/queriedCoValues/queriedCoMap.js +27 -28
  39. package/dist/queriedCoValues/queriedCoMap.js.map +1 -1
  40. package/dist/queriedCoValues/queriedCoStream.d.ts +13 -17
  41. package/dist/queriedCoValues/queriedCoStream.js +57 -32
  42. package/dist/queriedCoValues/queriedCoStream.js.map +1 -1
  43. package/dist/queriedCoValues/queriedGroup.d.ts +29 -0
  44. package/dist/queriedCoValues/queriedGroup.js +54 -0
  45. package/dist/queriedCoValues/queriedGroup.js.map +1 -0
  46. package/dist/queries.d.ts +40 -9
  47. package/dist/queries.js +104 -39
  48. package/dist/queries.js.map +1 -1
  49. package/dist/tests/testUtils.d.ts +15 -7
  50. package/dist/tests/testUtils.js +16 -17
  51. package/dist/tests/testUtils.js.map +1 -1
  52. package/package.json +2 -2
  53. package/src/coValue.ts +12 -31
  54. package/src/coValueCore.ts +100 -40
  55. package/src/{account.ts → coValues/account.ts} +46 -27
  56. package/src/coValues/coList.ts +24 -28
  57. package/src/coValues/coMap.ts +42 -68
  58. package/src/coValues/coStream.ts +22 -28
  59. package/src/{group.ts → coValues/group.ts} +121 -141
  60. package/src/ids.ts +1 -1
  61. package/src/index.ts +25 -10
  62. package/src/localNode.ts +180 -77
  63. package/src/media.ts +1 -1
  64. package/src/permissions.ts +67 -36
  65. package/src/queriedCoValues/queriedAccount.ts +40 -0
  66. package/src/queriedCoValues/queriedCoList.ts +22 -30
  67. package/src/queriedCoValues/queriedCoMap.ts +60 -72
  68. package/src/queriedCoValues/queriedCoStream.ts +105 -79
  69. package/src/queriedCoValues/queriedGroup.ts +90 -0
  70. package/src/queries.ts +181 -60
  71. package/src/tests/account.test.ts +14 -9
  72. package/src/tests/coValueCore.test.ts +2 -2
  73. package/src/tests/permissions.test.ts +351 -242
  74. package/src/tests/queries.test.ts +162 -82
  75. package/src/tests/sync.test.ts +11 -11
  76. package/src/tests/testUtils.ts +16 -18
  77. package/dist/account.d.ts +0 -58
  78. package/dist/account.js.map +0 -1
  79. package/dist/group.js.map +0 -1
package/src/localNode.ts CHANGED
@@ -17,16 +17,16 @@ import {
17
17
  import {
18
18
  InviteSecret,
19
19
  Group,
20
- GroupContent,
21
- expectGroupContent,
20
+ GroupShape,
21
+ expectGroup,
22
22
  secretSeedFromInviteSecret,
23
- } from "./group.js";
23
+ } from "./coValues/group.js";
24
24
  import { Peer, SyncManager } from "./sync.js";
25
25
  import { AgentID, RawCoID, SessionID, isAgentID } from "./ids.js";
26
26
  import { CoID } from "./coValue.js";
27
27
  import { Queried, query } from "./queries.js";
28
28
  import {
29
- AccountGroup,
29
+ Account,
30
30
  AccountMeta,
31
31
  accountHeaderForInitialAgentSecret,
32
32
  GeneralizedControlledAccount,
@@ -34,10 +34,12 @@ import {
34
34
  AnonymousControlledAccount,
35
35
  AccountID,
36
36
  Profile,
37
- AccountContent,
38
- } from "./account.js";
37
+ isAccountID,
38
+ AccountMigration,
39
+ } from "./coValues/account.js";
39
40
  import { CoMap } from "./coValues/coMap.js";
40
41
  import { CoValue } from "./index.js";
42
+ import { QueriedAccount } from "./queriedCoValues/queriedAccount.js";
41
43
 
42
44
  /** A `LocalNode` represents a local view of a set of loaded `CoValue`s, from the perspective of a particular account (or primitive cryptographic agent).
43
45
 
@@ -70,10 +72,19 @@ export class LocalNode {
70
72
  }
71
73
 
72
74
  /** @category 2. Node Creation */
73
- static withNewlyCreatedAccount(
74
- name: string,
75
- initialAgentSecret = newRandomAgentSecret()
76
- ): {
75
+ static withNewlyCreatedAccount<
76
+ P extends Profile = Profile,
77
+ R extends CoMap = CoMap,
78
+ Meta extends AccountMeta = AccountMeta
79
+ >({
80
+ name,
81
+ migration,
82
+ initialAgentSecret = newRandomAgentSecret(),
83
+ }: {
84
+ name: string;
85
+ migration?: AccountMigration<P, R, Meta>;
86
+ initialAgentSecret?: AgentSecret;
87
+ }): {
77
88
  node: LocalNode;
78
89
  accountID: AccountID;
79
90
  accountSecret: AgentSecret;
@@ -87,26 +98,52 @@ export class LocalNode {
87
98
 
88
99
  const account = setupNode.createAccount(name, initialAgentSecret);
89
100
 
90
- const nodeWithAccount = account.node.testWithDifferentAccount(
101
+ const nodeWithAccount = account.core.node.testWithDifferentAccount(
91
102
  account,
92
103
  newRandomSessionID(account.id)
93
104
  );
94
105
 
106
+ const accountOnNodeWithAccount = nodeWithAccount.account as ControlledAccount<P, R, Meta>;
107
+
108
+ const profile = nodeWithAccount.expectProfileLoaded(
109
+ accountOnNodeWithAccount.id,
110
+ "After creating account"
111
+ );
112
+
113
+ if (migration) {
114
+ migration(accountOnNodeWithAccount, profile as P);
115
+ nodeWithAccount.account = new ControlledAccount(
116
+ accountOnNodeWithAccount.core,
117
+ accountOnNodeWithAccount.agentSecret
118
+ );
119
+ }
120
+
95
121
  return {
96
122
  node: nodeWithAccount,
97
- accountID: account.id,
98
- accountSecret: account.agentSecret,
123
+ accountID: accountOnNodeWithAccount.id,
124
+ accountSecret: accountOnNodeWithAccount.agentSecret,
99
125
  sessionID: nodeWithAccount.currentSessionID,
100
126
  };
101
127
  }
102
128
 
103
129
  /** @category 2. Node Creation */
104
- static async withLoadedAccount(
105
- accountID: AccountID,
106
- accountSecret: AgentSecret,
107
- sessionID: SessionID,
108
- peersToLoadFrom: Peer[]
109
- ): Promise<LocalNode> {
130
+ static async withLoadedAccount<
131
+ P extends Profile = Profile,
132
+ R extends CoMap = CoMap,
133
+ Meta extends AccountMeta = AccountMeta
134
+ >({
135
+ accountID,
136
+ accountSecret,
137
+ sessionID,
138
+ peersToLoadFrom,
139
+ migration,
140
+ }: {
141
+ accountID: AccountID;
142
+ accountSecret: AgentSecret;
143
+ sessionID: SessionID;
144
+ peersToLoadFrom: Peer[];
145
+ migration?: AccountMigration<P, R, Meta>;
146
+ }): Promise<LocalNode> {
110
147
  const loadingNode = new LocalNode(
111
148
  new AnonymousControlledAccount(accountSecret),
112
149
  newRandomSessionID(accountID)
@@ -119,15 +156,38 @@ export class LocalNode {
119
156
  }
120
157
 
121
158
  const account = await accountPromise;
159
+ const controlledAccount = new ControlledAccount(
160
+ account.core,
161
+ accountSecret
162
+ );
122
163
 
123
164
  // since this is all synchronous, we can just swap out nodes for the SyncManager
124
165
  const node = loadingNode.testWithDifferentAccount(
125
- new ControlledAccount(accountSecret, account, loadingNode),
166
+ controlledAccount,
126
167
  sessionID
127
168
  );
128
169
  node.syncManager = loadingNode.syncManager;
129
170
  node.syncManager.local = node;
130
171
 
172
+ controlledAccount.core.node = node;
173
+
174
+ const profileID = account.get("profile");
175
+ if (!profileID) {
176
+ throw new Error("Account has no profile");
177
+ }
178
+ const profile = await node.load(profileID);
179
+
180
+ if (migration) {
181
+ migration(
182
+ controlledAccount as ControlledAccount<P, R, Meta>,
183
+ profile as P
184
+ );
185
+ node.account = new ControlledAccount(
186
+ controlledAccount.core,
187
+ controlledAccount.agentSecret
188
+ );
189
+ }
190
+
131
191
  return node;
132
192
  }
133
193
 
@@ -197,14 +257,47 @@ export class LocalNode {
197
257
  }
198
258
 
199
259
  /** @category 1. High-level */
260
+
200
261
  query<T extends CoValue>(
201
262
  id: CoID<T>,
202
263
  callback: (update: Queried<T> | undefined) => void
264
+ ): () => void;
265
+ query<
266
+ P extends Profile = Profile,
267
+ R extends CoMap = CoMap,
268
+ Meta extends AccountMeta = AccountMeta
269
+ >(
270
+ id: "me",
271
+ callback: (
272
+ update: QueriedAccount<Account<P, R, Meta>> | undefined
273
+ ) => void
274
+ ): () => void;
275
+ query(
276
+ id: CoID<CoValue> | "me",
277
+ callback: (
278
+ update: Queried<CoValue> | QueriedAccount | undefined
279
+ ) => void
280
+ ): () => void;
281
+ query(
282
+ id: CoID<CoValue> | "me",
283
+ callback: (
284
+ // TODO: sort this out
285
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
286
+ update: any
287
+ ) => void
203
288
  ): () => void {
204
- return query(id, this, callback);
289
+ if (id === "me") {
290
+ const meId = this.account.id;
291
+ if (!isAccountID(meId)) {
292
+ throw new Error("Can only query 'me' for accounts");
293
+ }
294
+ return query(meId, this, callback);
295
+ } else {
296
+ return query(id, this, callback);
297
+ }
205
298
  }
206
299
 
207
- /** @category 1. High-level */
300
+ /** @deprecated Use Account.acceptInvite instead */
208
301
  async acceptInvite<T extends CoValue>(
209
302
  groupOrOwnedValueID: CoID<T>,
210
303
  inviteSecret: InviteSecret
@@ -213,16 +306,14 @@ export class LocalNode {
213
306
 
214
307
  if (groupOrOwnedValue.core.header.ruleset.type === "ownedByGroup") {
215
308
  return this.acceptInvite(
216
- groupOrOwnedValue.core.header.ruleset.group as CoID<
217
- CoMap<GroupContent>
218
- >,
309
+ groupOrOwnedValue.core.header.ruleset.group as CoID<Group>,
219
310
  inviteSecret
220
311
  );
221
312
  } else if (groupOrOwnedValue.core.header.ruleset.type !== "group") {
222
313
  throw new Error("Can only accept invites to groups");
223
314
  }
224
315
 
225
- const group = new Group(expectGroupContent(groupOrOwnedValue), this);
316
+ const group = expectGroup(groupOrOwnedValue);
226
317
 
227
318
  const inviteAgentSecret = agentSecretFromSecretSeed(
228
319
  secretSeedFromInviteSecret(inviteSecret)
@@ -230,8 +321,8 @@ export class LocalNode {
230
321
  const inviteAgentID = getAgentID(inviteAgentSecret);
231
322
 
232
323
  const inviteRole = await new Promise((resolve, reject) => {
233
- group.underlyingMap.subscribe((groupMap) => {
234
- const role = groupMap.get(inviteAgentID);
324
+ group.subscribe((groupUpdate) => {
325
+ const role = groupUpdate.get(inviteAgentID);
235
326
  if (role) {
236
327
  resolve(role);
237
328
  }
@@ -246,7 +337,7 @@ export class LocalNode {
246
337
  throw new Error("No invite found");
247
338
  }
248
339
 
249
- const existingRole = group.underlyingMap.get(this.account.id);
340
+ const existingRole = group.get(this.account.id);
250
341
 
251
342
  if (
252
343
  existingRole === "admin" ||
@@ -260,9 +351,13 @@ export class LocalNode {
260
351
  return;
261
352
  }
262
353
 
263
- const groupAsInvite = group.testWithDifferentAccount(
264
- new AnonymousControlledAccount(inviteAgentSecret),
265
- newRandomSessionID(inviteAgentID)
354
+ const groupAsInvite = expectGroup(
355
+ group.core
356
+ .testWithDifferentAccount(
357
+ new AnonymousControlledAccount(inviteAgentSecret),
358
+ newRandomSessionID(inviteAgentID)
359
+ )
360
+ .getCurrentContent()
266
361
  );
267
362
 
268
363
  groupAsInvite.addMemberInternal(
@@ -274,12 +369,11 @@ export class LocalNode {
274
369
  : "reader"
275
370
  );
276
371
 
277
- group.underlyingMap.core._sessions =
278
- groupAsInvite.underlyingMap.core.sessions;
279
- group.underlyingMap.core._cachedContent = undefined;
372
+ group.core._sessions = groupAsInvite.core.sessions;
373
+ group.core._cachedContent = undefined;
280
374
 
281
- for (const groupListener of group.underlyingMap.core.listeners) {
282
- groupListener(group.underlyingMap.core.getCurrentContent());
375
+ for (const groupListener of group.core.listeners) {
376
+ groupListener(group.core.getCurrentContent());
283
377
  }
284
378
  }
285
379
 
@@ -304,7 +398,7 @@ export class LocalNode {
304
398
  /** @internal */
305
399
  expectProfileLoaded(id: AccountID, expectation?: string): Profile {
306
400
  const account = this.expectCoValueLoaded(id, expectation);
307
- const profileID = expectGroupContent(account.getCurrentContent()).get(
401
+ const profileID = expectGroup(account.getCurrentContent()).get(
308
402
  "profile"
309
403
  );
310
404
  if (!profileID) {
@@ -326,47 +420,51 @@ export class LocalNode {
326
420
  agentSecret = newRandomAgentSecret()
327
421
  ): ControlledAccount {
328
422
  const accountAgentID = getAgentID(agentSecret);
329
- const account = this.createCoValue(
330
- accountHeaderForInitialAgentSecret(agentSecret)
331
- ).testWithDifferentAccount(
332
- new AnonymousControlledAccount(agentSecret),
333
- newRandomSessionID(accountAgentID)
334
- );
335
-
336
- const accountAsGroup = new Group(
337
- expectGroupContent(account.getCurrentContent()),
338
- account.node
423
+ let account = expectGroup(
424
+ this.createCoValue(accountHeaderForInitialAgentSecret(agentSecret))
425
+ .testWithDifferentAccount(
426
+ new AnonymousControlledAccount(agentSecret),
427
+ newRandomSessionID(accountAgentID)
428
+ )
429
+ .getCurrentContent()
339
430
  );
340
431
 
341
- accountAsGroup.underlyingMap.mutate((editable) => {
432
+ account = account.mutate((editable) => {
342
433
  editable.set(accountAgentID, "admin", "trusting");
343
434
 
344
435
  const readKey = newRandomKeySecret();
345
436
 
437
+ const sealed = seal({
438
+ message: readKey.secret,
439
+ from: getAgentSealerSecret(agentSecret),
440
+ to: getAgentSealerID(accountAgentID),
441
+ nOnceMaterial: {
442
+ in: account.id,
443
+ tx: account.core.nextTransactionID(),
444
+ },
445
+ });
446
+
447
+ console.log(
448
+ "Creating read key",
449
+ getAgentSealerSecret(agentSecret),
450
+ getAgentSealerID(accountAgentID),
451
+ account.id,
452
+ account.core.nextTransactionID(),
453
+ "in session",
454
+ account.core.node.currentSessionID,
455
+ "=",
456
+ sealed
457
+ );
346
458
  editable.set(
347
459
  `${readKey.id}_for_${accountAgentID}`,
348
- seal({
349
- message: readKey.secret,
350
- from: getAgentSealerSecret(agentSecret),
351
- to: getAgentSealerID(accountAgentID),
352
- nOnceMaterial: {
353
- in: account.id,
354
- tx: account.nextTransactionID(),
355
- },
356
- }),
460
+ sealed,
357
461
  "trusting"
358
462
  );
359
463
 
360
464
  editable.set("readKey", readKey.id, "trusting");
361
465
  });
362
466
 
363
- const controlledAccount = new ControlledAccount(
364
- agentSecret,
365
- account.getCurrentContent() as CoMap<AccountContent, AccountMeta>,
366
- account.node
367
- );
368
-
369
- const profile = accountAsGroup.createMap<Profile>(
467
+ const profile = account.createMap<Profile>(
370
468
  { name },
371
469
  {
372
470
  type: "profile",
@@ -374,12 +472,12 @@ export class LocalNode {
374
472
  "trusting"
375
473
  );
376
474
 
377
- accountAsGroup.underlyingMap.set("profile", profile.id, "trusting");
475
+ account = account.set("profile", profile.id, "trusting");
378
476
 
379
477
  const accountOnThisNode = this.expectCoValueLoaded(account.id);
380
478
 
381
479
  accountOnThisNode._sessions = {
382
- ...accountAsGroup.underlyingMap.core.sessions,
480
+ ...account.core.sessions,
383
481
  };
384
482
  accountOnThisNode._cachedContent = undefined;
385
483
 
@@ -390,7 +488,7 @@ export class LocalNode {
390
488
  };
391
489
  profileOnThisNode._cachedContent = undefined;
392
490
 
393
- return controlledAccount;
491
+ return new ControlledAccount(accountOnThisNode, agentSecret);
394
492
  }
395
493
 
396
494
  /** @internal */
@@ -418,15 +516,11 @@ export class LocalNode {
418
516
  );
419
517
  }
420
518
 
421
- return new AccountGroup(
422
- coValue.getCurrentContent() as CoMap<GroupContent, AccountMeta>,
423
- this
424
- ).getCurrentAgentID();
519
+ return new Account(coValue).getCurrentAgentID();
425
520
  }
426
521
 
427
522
  /**
428
- * Creates a new group (with the current account as the group's first admin).
429
- * @category 1. High-level
523
+ * @deprecated use Account.createGroup() instead
430
524
  */
431
525
  createGroup(): Group {
432
526
  const groupCoValue = this.createCoValue({
@@ -436,9 +530,9 @@ export class LocalNode {
436
530
  ...createdNowUnique(),
437
531
  });
438
532
 
439
- let groupContent = expectGroupContent(groupCoValue.getCurrentContent());
533
+ let group = expectGroup(groupCoValue.getCurrentContent());
440
534
 
441
- groupContent = groupContent.mutate((editable) => {
535
+ group = group.mutate((editable) => {
442
536
  editable.set(this.account.id, "admin", "trusting");
443
537
 
444
538
  const readKey = newRandomKeySecret();
@@ -460,7 +554,7 @@ export class LocalNode {
460
554
  editable.set("readKey", readKey.id, "trusting");
461
555
  });
462
556
 
463
- return new Group(groupContent, this);
557
+ return group;
464
558
  }
465
559
 
466
560
  /** @internal */
@@ -505,6 +599,15 @@ export class LocalNode {
505
599
  }
506
600
  }
507
601
 
602
+ if (account instanceof ControlledAccount) {
603
+ // To make sure that when we edit the account, we're modifying the correct sessions
604
+ const accountInNode = new ControlledAccount(newNode.expectCoValueLoaded(account.id), account.agentSecret);
605
+ if (accountInNode.core.node !== newNode) {
606
+ throw new Error("Account's node is not the new node");
607
+ }
608
+ newNode.account = accountInNode;
609
+ }
610
+
508
611
  return newNode;
509
612
  }
510
613
  }
package/src/media.ts CHANGED
@@ -4,5 +4,5 @@ import { BinaryCoStream } from './coValues/coStream.js'
4
4
  export type ImageDefinition = CoMap<{
5
5
  originalSize: [number, number];
6
6
  placeholderDataURL?: string;
7
- [res: `${number}x${number}`]: BinaryCoStream;
7
+ [res: `${number}x${number}`]: BinaryCoStream["id"];
8
8
  }>;
@@ -5,13 +5,12 @@ import { KeyID } from "./crypto.js";
5
5
  import {
6
6
  CoValueCore,
7
7
  Transaction,
8
- TrustingTransaction,
9
8
  accountOrAgentIDfromSessionID,
10
9
  } from "./coValueCore.js";
11
10
  import { AgentID, RawCoID, SessionID, TransactionID } from "./ids.js";
12
- import { AccountID, Profile } from "./account.js";
11
+ import { Account, AccountID, Profile } from "./coValues/account.js";
13
12
  import { parseJSON } from "./jsonStringify.js";
14
- import { expectGroupContent } from "./group.js";
13
+ import { EVERYONE, Everyone, expectGroup } from "./coValues/group.js";
15
14
 
16
15
  export type PermissionsDef =
17
16
  | { type: "group"; initialAdmin: AccountID | AgentID }
@@ -31,26 +30,21 @@ export function determineValidTransactions(
31
30
  coValue: CoValueCore
32
31
  ): { txID: TransactionID; tx: Transaction }[] {
33
32
  if (coValue.header.ruleset.type === "group") {
34
- const allTrustingTransactionsSorted = Object.entries(
35
- coValue.sessions
36
- ).flatMap(([sessionID, sessionLog]) => {
37
- return sessionLog.transactions
38
- .map((tx, txIndex) => ({ sessionID, txIndex, tx }))
39
- .filter(({ tx }) => {
40
- if (tx.privacy === "trusting") {
41
- return true;
42
- } else {
43
- console.warn("Unexpected private transaction in Group");
44
- return false;
45
- }
46
- }) as {
47
- sessionID: SessionID;
48
- txIndex: number;
49
- tx: TrustingTransaction;
50
- }[];
51
- });
33
+ const allTransactionsSorted = Object.entries(coValue.sessions).flatMap(
34
+ ([sessionID, sessionLog]) => {
35
+ return sessionLog.transactions.map((tx, txIndex) => ({
36
+ sessionID,
37
+ txIndex,
38
+ tx,
39
+ })) as {
40
+ sessionID: SessionID;
41
+ txIndex: number;
42
+ tx: Transaction;
43
+ }[];
44
+ }
45
+ );
52
46
 
53
- allTrustingTransactionsSorted.sort((a, b) => {
47
+ allTransactionsSorted.sort((a, b) => {
54
48
  return a.tx.madeAt - b.tx.madeAt;
55
49
  });
56
50
 
@@ -60,19 +54,33 @@ export function determineValidTransactions(
60
54
  throw new Error("Group must have initialAdmin");
61
55
  }
62
56
 
63
- const memberState: { [agent: AccountID | AgentID]: Role } = {};
57
+ const memberState: {
58
+ [agent: AccountID | AgentID]: Role;
59
+ [EVERYONE]?: Role;
60
+ } = {};
64
61
 
65
62
  const validTransactions: { txID: TransactionID; tx: Transaction }[] =
66
63
  [];
67
64
 
68
- for (const {
69
- sessionID,
70
- txIndex,
71
- tx,
72
- } of allTrustingTransactionsSorted) {
65
+ for (const { sessionID, txIndex, tx } of allTransactionsSorted) {
73
66
  // console.log("before", { memberState, validTransactions });
74
67
  const transactor = accountOrAgentIDfromSessionID(sessionID);
75
68
 
69
+ if (tx.privacy === "private") {
70
+ if (memberState[transactor] === "admin") {
71
+ validTransactions.push({
72
+ txID: { sessionID, txIndex },
73
+ tx,
74
+ });
75
+ continue;
76
+ } else {
77
+ console.warn(
78
+ "Only admins can make private transactions in groups"
79
+ );
80
+ continue;
81
+ }
82
+ }
83
+
76
84
  let changes;
77
85
 
78
86
  try {
@@ -93,7 +101,7 @@ export function determineValidTransactions(
93
101
  }
94
102
 
95
103
  const change = changes[0] as
96
- | MapOpPayload<AccountID | AgentID, Role>
104
+ | MapOpPayload<AccountID | AgentID | Everyone, Role>
97
105
  | MapOpPayload<"readKey", JsonValue>
98
106
  | MapOpPayload<"profile", CoID<Profile>>;
99
107
  if (changes.length !== 1) {
@@ -158,6 +166,20 @@ export function determineValidTransactions(
158
166
  continue;
159
167
  }
160
168
 
169
+ if (
170
+ affectedMember === EVERYONE &&
171
+ !(
172
+ change.value === "reader" ||
173
+ change.value === "writer" ||
174
+ change.value === "revoked"
175
+ )
176
+ ) {
177
+ console.warn(
178
+ "Everyone can only be set to reader, writer or revoked"
179
+ );
180
+ continue;
181
+ }
182
+
161
183
  const isFirstSelfAppointment =
162
184
  !memberState[transactor] &&
163
185
  transactor === initialAdmin &&
@@ -206,7 +228,7 @@ export function determineValidTransactions(
206
228
 
207
229
  return validTransactions;
208
230
  } else if (coValue.header.ruleset.type === "ownedByGroup") {
209
- const groupContent = expectGroupContent(
231
+ const groupContent = expectGroup(
210
232
  coValue.node
211
233
  .expectCoValueLoaded(
212
234
  coValue.header.ruleset.group,
@@ -224,11 +246,18 @@ export function determineValidTransactions(
224
246
  const transactor = accountOrAgentIDfromSessionID(
225
247
  sessionID as SessionID
226
248
  );
249
+
227
250
  return sessionLog.transactions
228
251
  .filter((tx) => {
229
- const transactorRoleAtTxTime = groupContent
230
- .atTime(tx.madeAt)
231
- .get(transactor);
252
+ const groupAtTime = groupContent.atTime(tx.madeAt);
253
+ const effectiveTransactor =
254
+ transactor === groupContent.id &&
255
+ groupAtTime instanceof Account
256
+ ? groupAtTime.getCurrentAgentID()
257
+ : transactor;
258
+ const transactorRoleAtTxTime =
259
+ groupAtTime.get(effectiveTransactor) ||
260
+ groupAtTime.get(EVERYONE);
232
261
 
233
262
  return (
234
263
  transactorRoleAtTxTime === "admin" ||
@@ -252,7 +281,8 @@ export function determineValidTransactions(
252
281
  );
253
282
  } else {
254
283
  throw new Error(
255
- "Unknown ruleset type " + (coValue.header.ruleset as {type: string}).type
284
+ "Unknown ruleset type " +
285
+ (coValue.header.ruleset as { type: string }).type
256
286
  );
257
287
  }
258
288
  }
@@ -267,7 +297,8 @@ export function isKeyForAccountField(
267
297
  field: string
268
298
  ): field is `${KeyID}_for_${AccountID | AgentID}` {
269
299
  return (
270
- field.startsWith("key_") &&
271
- (field.includes("_for_sealer") || field.includes("_for_co"))
300
+ (field.startsWith("key_") &&
301
+ (field.includes("_for_sealer") || field.includes("_for_co"))) ||
302
+ field.includes("_for_everyone")
272
303
  );
273
304
  }
@@ -0,0 +1,40 @@
1
+ import { Account } from "../coValues/account.js";
2
+ import { CoID, CoValue, ControlledAccount, InviteSecret } from "../index.js";
3
+ import { QueryContext } from "../queries.js";
4
+ import { QueriedGroup } from "./queriedGroup.js";
5
+
6
+ export class QueriedAccount<A extends Account = Account> extends QueriedGroup<A> {
7
+ id!: CoID<A>;
8
+ isMe!: boolean;
9
+
10
+ constructor(account: A, queryContext: QueryContext) {
11
+ super(account, queryContext);
12
+ Object.defineProperties(this, {
13
+ id: { value: account.id, enumerable: false },
14
+ isMe: {
15
+ value: account.core.node.account.id === account.id,
16
+ enumerable: false,
17
+ },
18
+ });
19
+ }
20
+
21
+ createGroup() {
22
+ if (!this.isMe)
23
+ throw new Error("Only the current user can create a group");
24
+ return (
25
+ this.group.core.node.account as ControlledAccount
26
+ ).createGroup();
27
+ }
28
+
29
+ acceptInvite<T extends CoValue>(
30
+ groupOrOwnedValueID: CoID<T>,
31
+ inviteSecret: InviteSecret
32
+ ) {
33
+ if (!this.isMe)
34
+ throw new Error("Only the current user can accept an invite");
35
+ return (this.group.core.node.account as ControlledAccount).acceptInvite(
36
+ groupOrOwnedValueID,
37
+ inviteSecret
38
+ );
39
+ }
40
+ }