cojson 0.14.0 → 0.14.16

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.
@@ -451,7 +451,9 @@ export class RawGroup<
451
451
  }
452
452
 
453
453
  getCurrentReadKeyId() {
454
- if (this.myRole() === "writeOnly") {
454
+ const myRole = this.myRole();
455
+
456
+ if (myRole === "writeOnly") {
455
457
  const accountId = this.core.node.getCurrentAgent().id;
456
458
 
457
459
  const key = this.get(`writeKeyFor_${accountId}`) as KeyID;
@@ -469,6 +471,16 @@ export class RawGroup<
469
471
  return key;
470
472
  }
471
473
 
474
+ if (!myRole) {
475
+ const accountId = this.core.node.getCurrentAgent().id;
476
+
477
+ const key = this.get(`writeKeyFor_${accountId}`) as KeyID;
478
+
479
+ if (key) {
480
+ return key;
481
+ }
482
+ }
483
+
472
484
  return this.get("readKey");
473
485
  }
474
486
 
@@ -670,22 +682,24 @@ export class RawGroup<
670
682
  );
671
683
  }
672
684
 
685
+ const value = role === "inherit" ? "extend" : role;
686
+
687
+ this.set(`parent_${parent.id}`, value, "trusting");
688
+ parent.set(`child_${this.id}`, "extend", "trusting");
689
+
673
690
  if (
674
691
  parent.myRole() !== "admin" &&
675
692
  parent.myRole() !== "writer" &&
676
693
  parent.myRole() !== "reader" &&
677
694
  parent.myRole() !== "writeOnly"
678
695
  ) {
679
- throw new Error(
680
- "To extend a group, the current account must be a member of the parent group",
696
+ // Create a writeOnly key in the parent group to be able to reveal the current child key to the parent group
697
+ parent.internalCreateWriteOnlyKeyForMember(
698
+ this.core.node.getCurrentAgent().id,
699
+ this.core.node.getCurrentAgent().currentAgentID(),
681
700
  );
682
701
  }
683
702
 
684
- const value = role === "inherit" ? "extend" : role;
685
-
686
- this.set(`parent_${parent.id}`, value, "trusting");
687
- parent.set(`child_${this.id}`, "extend", "trusting");
688
-
689
703
  const { id: parentReadKeyID, secret: parentReadKeySecret } =
690
704
  parent.core.getCurrentReadKey();
691
705
  if (!parentReadKeySecret) {
package/src/exports.ts CHANGED
@@ -72,6 +72,7 @@ import type {
72
72
  import {
73
73
  DisconnectedError,
74
74
  PingTimeoutError,
75
+ SyncManager,
75
76
  emptyKnownState,
76
77
  } from "./sync.js";
77
78
 
@@ -102,6 +103,7 @@ export const cojsonInternals = {
102
103
  getGroupDependentKeyList,
103
104
  getGroupDependentKey,
104
105
  disablePermissionErrors,
106
+ SyncManager,
105
107
  CO_VALUE_LOADING_CONFIG,
106
108
  setCoValueLoadingRetryDelay(delay: number) {
107
109
  CO_VALUE_LOADING_CONFIG.RETRY_DELAY = delay;
package/src/localNode.ts CHANGED
@@ -548,6 +548,7 @@ export class LocalNode {
548
548
  group.processNewTransactions();
549
549
 
550
550
  group.core.notifyUpdate("immediate");
551
+ this.syncManager.requestCoValueSync(group.core);
551
552
  }
552
553
 
553
554
  /** @internal */
@@ -345,18 +345,6 @@ function determineValidTransactionsForGroup(
345
345
  validTransactions.push({ txID: { sessionID, txIndex }, tx });
346
346
  continue;
347
347
  } else if (isChildExtension(change.key)) {
348
- if (
349
- memberState[transactor] !== "admin" &&
350
- memberState[transactor] !== "writer" &&
351
- memberState[transactor] !== "reader" &&
352
- memberState[transactor] !== "writeOnly"
353
- ) {
354
- logPermissionError(
355
- "Only admins, writers, readers and writeOnly can set child extensions",
356
- );
357
- continue;
358
- }
359
-
360
348
  validTransactions.push({ txID: { sessionID, txIndex }, tx });
361
349
  continue;
362
350
  } else if (isWriteKeyForMember(change.key)) {
@@ -157,6 +157,29 @@ describe("extend", () => {
157
157
 
158
158
  expect(childGroup.roleOf(node2.accountID)).toEqual(undefined);
159
159
  });
160
+
161
+ test("should be possible to extend a group without having membership in the parent group", async () => {
162
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
163
+ "server",
164
+ "server",
165
+ "server",
166
+ );
167
+
168
+ const parentGroup = node1.node.createGroup();
169
+ const childGroup = node2.node.createGroup();
170
+
171
+ const alice = await loadCoValueOrFail(node1.node, node3.accountID);
172
+ parentGroup.addMember(alice, "writer");
173
+
174
+ const parentGroupOnNode2 = await loadCoValueOrFail(
175
+ node2.node,
176
+ parentGroup.id,
177
+ );
178
+
179
+ childGroup.extend(parentGroupOnNode2);
180
+
181
+ expect(childGroup.roleOf(alice.id)).toBe("writer");
182
+ });
160
183
  });
161
184
 
162
185
  describe("unextend", () => {
@@ -191,6 +214,78 @@ describe("unextend", () => {
191
214
  expect(childGroup.roleOf(alice.id)).toBe(undefined);
192
215
  });
193
216
 
217
+ test("should work when the account has no access to the parent group but owns the writeKey", async () => {
218
+ const { node1, node2, node3 } = await createThreeConnectedNodes(
219
+ "server",
220
+ "server",
221
+ "server",
222
+ );
223
+
224
+ const parentGroup = node1.node.createGroup();
225
+ const childGroup = node2.node.createGroup();
226
+
227
+ const alice = await loadCoValueOrFail(node1.node, node3.accountID);
228
+ parentGroup.addMember(alice, "writer");
229
+
230
+ const parentGroupOnNode2 = await loadCoValueOrFail(
231
+ node2.node,
232
+ parentGroup.id,
233
+ );
234
+
235
+ childGroup.extend(parentGroupOnNode2);
236
+
237
+ expect(childGroup.roleOf(alice.id)).toBe("writer");
238
+
239
+ // `childGroup` no longer has `parentGroup`'s members
240
+ await childGroup.revokeExtend(parentGroup);
241
+ expect(childGroup.roleOf(alice.id)).toBe(undefined);
242
+
243
+ const map = childGroup.createMap();
244
+ map.set("test", "Hello!");
245
+
246
+ const mapOnAlice = await loadCoValueOrFail(node3.node, map.id);
247
+
248
+ expect(mapOnAlice.get("test")).toEqual(undefined);
249
+ });
250
+
251
+ test("should work when the account has no access to the parent group and not owns the writeKey", async () => {
252
+ const {
253
+ node1: bobNode,
254
+ node2: johnNode,
255
+ node3: aliceNode,
256
+ } = await createThreeConnectedNodes("server", "server", "server");
257
+
258
+ const parentGroup = bobNode.node.createGroup();
259
+ const childGroup = johnNode.node.createGroup();
260
+
261
+ const parentGroupOnJohn = await loadCoValueOrFail(
262
+ johnNode.node,
263
+ parentGroup.id,
264
+ );
265
+
266
+ childGroup.extend(parentGroupOnJohn);
267
+
268
+ const bob = await loadCoValueOrFail(johnNode.node, bobNode.accountID);
269
+ const alice = await loadCoValueOrFail(johnNode.node, aliceNode.accountID);
270
+ childGroup.addMember(alice, "admin");
271
+
272
+ const childGroupOnAlice = await loadCoValueOrFail(
273
+ aliceNode.node,
274
+ childGroup.id,
275
+ );
276
+
277
+ // `childGroup` no longer has `parentGroup`'s members
278
+ await childGroupOnAlice.revokeExtend(parentGroup);
279
+ expect(childGroupOnAlice.roleOf(bob.id)).toBe(undefined);
280
+
281
+ const map = childGroupOnAlice.createMap();
282
+ map.set("test", "Hello!");
283
+
284
+ const mapOnBob = await loadCoValueOrFail(bobNode.node, map.id);
285
+
286
+ expect(mapOnBob.get("test")).toEqual(undefined);
287
+ });
288
+
194
289
  test("should do nothing if applied to a group that is not extended", async () => {
195
290
  const { node1, node2, node3 } = await createThreeConnectedNodes(
196
291
  "server",
@@ -323,6 +323,10 @@ describe("Group invites", () => {
323
323
 
324
324
  expect(groupOnMemberNode.roleOf(member.accountID)).toEqual("reader");
325
325
 
326
+ await waitFor(() => {
327
+ expect(group.roleOf(member.accountID)).toEqual("reader");
328
+ });
329
+
326
330
  // Verify read access is restored
327
331
  const personOnMemberNode = await loadCoValueOrFail(member.node, person.id);
328
332
  expect(personOnMemberNode.get("name")).toEqual("John Doe");
@@ -1982,40 +1982,6 @@ test("Writers, readers and writeOnly can set child extensions", () => {
1982
1982
  expect(groupAsReader.get(`child_${childGroup.id}`)).toEqual("extend");
1983
1983
  });
1984
1984
 
1985
- test("Invitees can not set child extensions", () => {
1986
- const { group, node } = newGroupHighLevel();
1987
- const childGroup = node.createGroup();
1988
-
1989
- const adminInvite = createAccountInNode(node);
1990
- const writerInvite = createAccountInNode(node);
1991
- const readerInvite = createAccountInNode(node);
1992
-
1993
- group.addMember(adminInvite, "adminInvite");
1994
- group.addMember(writerInvite, "writerInvite");
1995
- group.addMember(readerInvite, "readerInvite");
1996
-
1997
- const groupAsAdminInvite = expectGroup(
1998
- group.core.contentInClonedNodeWithDifferentAccount(adminInvite),
1999
- );
2000
-
2001
- groupAsAdminInvite.set(`child_${childGroup.id}`, "extend", "trusting");
2002
- expect(groupAsAdminInvite.get(`child_${childGroup.id}`)).toBeUndefined();
2003
-
2004
- const groupAsWriterInvite = expectGroup(
2005
- group.core.contentInClonedNodeWithDifferentAccount(writerInvite),
2006
- );
2007
-
2008
- groupAsWriterInvite.set(`child_${childGroup.id}`, "extend", "trusting");
2009
- expect(groupAsWriterInvite.get(`child_${childGroup.id}`)).toBeUndefined();
2010
-
2011
- const groupAsReaderInvite = expectGroup(
2012
- group.core.contentInClonedNodeWithDifferentAccount(readerInvite),
2013
- );
2014
-
2015
- groupAsReaderInvite.set(`child_${childGroup.id}`, "extend", "trusting");
2016
- expect(groupAsReaderInvite.get(`child_${childGroup.id}`)).toBeUndefined();
2017
- });
2018
-
2019
1985
  test("Member roles are inherited by child groups (except invites)", () => {
2020
1986
  const { group, node, admin } = newGroupHighLevel();
2021
1987
  const parentGroup = node.createGroup();
@@ -56,6 +56,10 @@ describe("invitations sync", () => {
56
56
  "invite-consumer -> server | KNOWN Group sessions: header/5",
57
57
  "server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
58
58
  "invite-consumer -> server | KNOWN Map sessions: header/1",
59
+ "invite-consumer -> server | CONTENT Group header: false new: After: 0 New: 2",
60
+ "server -> invite-consumer | KNOWN Group sessions: header/7",
61
+ "server -> invite-provider | CONTENT Group header: false new: After: 0 New: 2",
62
+ "invite-provider -> server | KNOWN Group sessions: header/7",
59
63
  ]
60
64
  `);
61
65
  });
@@ -104,6 +108,10 @@ describe("invitations sync", () => {
104
108
  "invite-consumer -> server | KNOWN Group sessions: header/7",
105
109
  "server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
106
110
  "invite-consumer -> server | KNOWN Map sessions: header/1",
111
+ "invite-consumer -> server | CONTENT Group header: false new: After: 0 New: 2",
112
+ "server -> invite-consumer | KNOWN Group sessions: header/9",
113
+ "server -> invite-provider | CONTENT Group header: false new: After: 0 New: 2",
114
+ "invite-provider -> server | KNOWN Group sessions: header/9",
107
115
  ]
108
116
  `);
109
117
  });
@@ -148,15 +156,15 @@ describe("invitations sync", () => {
148
156
  "invite-consumer -> server | LOAD ParentGroup sessions: empty",
149
157
  "server -> invite-consumer | CONTENT ParentGroup header: true new: After: 0 New: 6",
150
158
  "invite-consumer -> server | KNOWN ParentGroup sessions: header/6",
159
+ "invite-consumer -> server | CONTENT ParentGroup header: false new: After: 0 New: 2",
160
+ "server -> invite-consumer | KNOWN ParentGroup sessions: header/8",
161
+ "server -> invite-provider | CONTENT ParentGroup header: false new: After: 0 New: 2",
151
162
  "invite-consumer -> server | LOAD Map sessions: empty",
163
+ "invite-provider -> server | KNOWN ParentGroup sessions: header/8",
152
164
  "server -> invite-consumer | CONTENT Group header: true new: After: 0 New: 5",
153
165
  "invite-consumer -> server | KNOWN Group sessions: header/5",
154
166
  "server -> invite-consumer | CONTENT Map header: true new: After: 0 New: 1",
155
- "invite-consumer -> server | CONTENT ParentGroup header: false new: After: 0 New: 2",
156
- "server -> invite-consumer | KNOWN ParentGroup sessions: header/8",
157
- "server -> invite-provider | CONTENT ParentGroup header: false new: After: 0 New: 2",
158
167
  "invite-consumer -> server | KNOWN Map sessions: header/1",
159
- "invite-provider -> server | KNOWN ParentGroup sessions: header/8",
160
168
  ]
161
169
  `);
162
170
  });