jazz-tools 0.9.16 → 0.9.18

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.
@@ -17,16 +17,19 @@ import {
17
17
  Profile,
18
18
  SchemaUnion,
19
19
  co,
20
+ consumeInviteLink,
20
21
  createAnonymousJazzContext,
21
22
  createCoValueObservable,
23
+ createInviteLink,
22
24
  createJazzContext,
23
25
  ephemeralCredentialsAuth,
24
26
  fixedCredentialsAuth,
25
27
  isControlledAccount,
26
28
  loadCoValue,
29
+ parseInviteLink,
27
30
  randomSessionProvider,
28
31
  subscribeToCoValue
29
- } from "./chunk-7OI3SFBS.js";
32
+ } from "./chunk-LRDIS2Q2.js";
30
33
 
31
34
  // src/index.native.ts
32
35
  import {
@@ -56,13 +59,16 @@ export {
56
59
  SchemaUnion,
57
60
  co,
58
61
  cojsonInternals,
62
+ consumeInviteLink,
59
63
  createAnonymousJazzContext,
60
64
  createCoValueObservable,
65
+ createInviteLink,
61
66
  createJazzContext,
62
67
  ephemeralCredentialsAuth,
63
68
  fixedCredentialsAuth,
64
69
  isControlledAccount,
65
70
  loadCoValue,
71
+ parseInviteLink,
66
72
  randomSessionProvider,
67
73
  subscribeToCoValue
68
74
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.native.ts"],"sourcesContent":["export * from \"./exports.js\";\n\nexport {\n MAX_RECOMMENDED_TX_SIZE,\n cojsonInternals,\n} from \"cojson/native\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../src/index.native.ts"],"sourcesContent":["export * from \"./exports.js\";\n\nexport {\n MAX_RECOMMENDED_TX_SIZE,\n cojsonInternals,\n} from \"cojson/native\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA;AAAA,EACE;AAAA,EACA;AAAA,OACK;","names":[]}
package/dist/index.web.js CHANGED
@@ -17,16 +17,19 @@ import {
17
17
  Profile,
18
18
  SchemaUnion,
19
19
  co,
20
+ consumeInviteLink,
20
21
  createAnonymousJazzContext,
21
22
  createCoValueObservable,
23
+ createInviteLink,
22
24
  createJazzContext,
23
25
  ephemeralCredentialsAuth,
24
26
  fixedCredentialsAuth,
25
27
  isControlledAccount,
26
28
  loadCoValue,
29
+ parseInviteLink,
27
30
  randomSessionProvider,
28
31
  subscribeToCoValue
29
- } from "./chunk-7OI3SFBS.js";
32
+ } from "./chunk-LRDIS2Q2.js";
30
33
 
31
34
  // src/index.web.ts
32
35
  import { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from "cojson";
@@ -54,13 +57,16 @@ export {
54
57
  WasmCrypto,
55
58
  co,
56
59
  cojsonInternals,
60
+ consumeInviteLink,
57
61
  createAnonymousJazzContext,
58
62
  createCoValueObservable,
63
+ createInviteLink,
59
64
  createJazzContext,
60
65
  ephemeralCredentialsAuth,
61
66
  fixedCredentialsAuth,
62
67
  isControlledAccount,
63
68
  loadCoValue,
69
+ parseInviteLink,
64
70
  randomSessionProvider,
65
71
  subscribeToCoValue
66
72
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.web.ts"],"sourcesContent":["export * from \"./exports.js\";\n\nexport { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from \"cojson\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,SAAS,iBAAiB,yBAAyB,kBAAkB;","names":[]}
1
+ {"version":3,"sources":["../src/index.web.ts"],"sourcesContent":["export * from \"./exports.js\";\n\nexport { cojsonInternals, MAX_RECOMMENDED_TX_SIZE, WasmCrypto } from \"cojson\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,SAAS,iBAAiB,yBAAyB,kBAAkB;","names":[]}
package/dist/testing.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  Account,
3
3
  activeAccountContext,
4
4
  createAnonymousJazzContext
5
- } from "./chunk-7OI3SFBS.js";
5
+ } from "./chunk-LRDIS2Q2.js";
6
6
 
7
7
  // src/testing.ts
8
8
  import { LocalNode } from "cojson";
package/package.json CHANGED
@@ -23,9 +23,9 @@
23
23
  },
24
24
  "type": "module",
25
25
  "license": "MIT",
26
- "version": "0.9.16",
26
+ "version": "0.9.18",
27
27
  "dependencies": {
28
- "cojson": "0.9.13"
28
+ "cojson": "0.9.18"
29
29
  },
30
30
  "devDependencies": {
31
31
  "tsup": "8.3.5",
@@ -1,4 +1,10 @@
1
- import type { Everyone, RawAccountID, RawGroup, Role } from "cojson";
1
+ import type {
2
+ AccountRole,
3
+ Everyone,
4
+ RawAccountID,
5
+ RawGroup,
6
+ Role,
7
+ } from "cojson";
2
8
  import type {
3
9
  CoValue,
4
10
  CoValueClass,
@@ -133,7 +139,9 @@ export class Group extends CoValueBase implements CoValue {
133
139
  return this._raw.myRole();
134
140
  }
135
141
 
136
- addMember(member: Everyone | Account, role: Role) {
142
+ addMember(member: Everyone, role: "writer" | "reader"): Group;
143
+ addMember(member: Account, role: AccountRole): Group;
144
+ addMember(member: Everyone | Account, role: AccountRole) {
137
145
  this._raw.addMember(member === "everyone" ? member : member._raw, role);
138
146
  return this;
139
147
  }
package/src/exports.ts CHANGED
@@ -49,6 +49,12 @@ export {
49
49
  subscribeToCoValue,
50
50
  } from "./internal.js";
51
51
 
52
+ export {
53
+ createInviteLink,
54
+ parseInviteLink,
55
+ consumeInviteLink,
56
+ } from "./implementation/invites.js";
57
+
52
58
  export {
53
59
  AnonymousJazzAgent,
54
60
  createAnonymousJazzContext,
@@ -0,0 +1,99 @@
1
+ import { type InviteSecret, cojsonInternals } from "cojson";
2
+ import { Account } from "../coValues/account.js";
3
+ import type { CoValue, CoValueClass, ID } from "../internal.js";
4
+
5
+ /** @category Invite Links */
6
+ export function createInviteLink<C extends CoValue>(
7
+ value: C,
8
+ role: "reader" | "writer" | "admin" | "writeOnly",
9
+ baseURL: string,
10
+ valueHint?: string,
11
+ ): string {
12
+ const coValueCore = value._raw.core;
13
+ let currentCoValue = coValueCore;
14
+
15
+ while (currentCoValue.header.ruleset.type === "ownedByGroup") {
16
+ currentCoValue = currentCoValue.getGroup().core;
17
+ }
18
+
19
+ const { ruleset, meta } = currentCoValue.header;
20
+
21
+ if (ruleset.type !== "group" || meta?.type === "account") {
22
+ throw new Error("Can't create invite link for object without group");
23
+ }
24
+
25
+ const group = cojsonInternals.expectGroup(currentCoValue.getCurrentContent());
26
+ const inviteSecret = group.createInvite(role);
27
+
28
+ return `${baseURL}#/invite/${valueHint ? valueHint + "/" : ""}${
29
+ value.id
30
+ }/${inviteSecret}`;
31
+ }
32
+
33
+ /** @category Invite Links */
34
+ export function parseInviteLink<C extends CoValue>(
35
+ inviteURL: string,
36
+ ):
37
+ | {
38
+ valueID: ID<C>;
39
+ valueHint?: string;
40
+ inviteSecret: InviteSecret;
41
+ }
42
+ | undefined {
43
+ const url = new URL(inviteURL);
44
+ const parts = url.hash.split("/");
45
+
46
+ let valueHint: string | undefined;
47
+ let valueID: ID<C> | undefined;
48
+ let inviteSecret: InviteSecret | undefined;
49
+
50
+ if (parts[0] === "#" && parts[1] === "invite") {
51
+ if (parts.length === 5) {
52
+ valueHint = parts[2];
53
+ valueID = parts[3] as ID<C>;
54
+ inviteSecret = parts[4] as InviteSecret;
55
+ } else if (parts.length === 4) {
56
+ valueID = parts[2] as ID<C>;
57
+ inviteSecret = parts[3] as InviteSecret;
58
+ }
59
+
60
+ if (!valueID || !inviteSecret) {
61
+ return undefined;
62
+ }
63
+ return { valueID, inviteSecret, valueHint };
64
+ }
65
+ }
66
+
67
+ /** @category Invite Links */
68
+ export function consumeInviteLink<V extends CoValue>({
69
+ inviteURL,
70
+ as = Account.getMe(),
71
+ forValueHint,
72
+ invitedObjectSchema,
73
+ }: {
74
+ inviteURL: string;
75
+ as?: Account;
76
+ forValueHint?: string;
77
+ invitedObjectSchema: CoValueClass<V>;
78
+ }): Promise<
79
+ | {
80
+ valueID: ID<V>;
81
+ valueHint?: string;
82
+ inviteSecret: InviteSecret;
83
+ }
84
+ | undefined
85
+ > {
86
+ return new Promise((resolve, reject) => {
87
+ const result = parseInviteLink<V>(inviteURL);
88
+
89
+ if (result && result.valueHint === forValueHint) {
90
+ as.acceptInvite(result.valueID, result.inviteSecret, invitedObjectSchema)
91
+ .then(() => {
92
+ resolve(result);
93
+ })
94
+ .catch(reject);
95
+ } else {
96
+ resolve(undefined);
97
+ }
98
+ });
99
+ }
@@ -1,10 +1,17 @@
1
1
  import { WasmCrypto } from "cojson";
2
- import { describe, expect, test } from "vitest";
2
+ import { beforeEach, describe, expect, test } from "vitest";
3
3
  import { Account, CoMap, Group, Profile, co } from "../exports.js";
4
+ import { createJazzTestAccount } from "../testing.js";
4
5
  import { setupTwoNodes } from "./utils.js";
5
6
 
6
7
  const Crypto = await WasmCrypto.create();
7
8
 
9
+ beforeEach(async () => {
10
+ await createJazzTestAccount({
11
+ isCurrentActiveAccount: true,
12
+ });
13
+ });
14
+
8
15
  describe("Custom accounts and groups", async () => {
9
16
  class CustomProfile extends Profile {
10
17
  name = co.string;
@@ -180,4 +187,61 @@ describe("Group inheritance", () => {
180
187
 
181
188
  expect(loadedGroup).not.toBe("unavailable");
182
189
  });
190
+
191
+ test("everyone is valid only for reader and writer roles", () => {
192
+ const group = Group.create();
193
+ group.addMember("everyone", "reader");
194
+
195
+ expect(group.members).toContainEqual({
196
+ id: "everyone",
197
+ role: "reader",
198
+ account: undefined,
199
+ ref: undefined,
200
+ });
201
+
202
+ group.addMember("everyone", "writer");
203
+
204
+ expect(group.members).toContainEqual({
205
+ id: "everyone",
206
+ role: "writer",
207
+ account: undefined,
208
+ ref: undefined,
209
+ });
210
+
211
+ // @ts-expect-error - admin is not a valid role for everyone
212
+ expect(() => group.addMember("everyone", "admin")).toThrow();
213
+
214
+ expect(group.members).toContainEqual({
215
+ id: "everyone",
216
+ role: "writer",
217
+ account: undefined,
218
+ ref: undefined,
219
+ });
220
+
221
+ // @ts-expect-error - writeOnly is not a valid role for everyone
222
+ expect(() => group.addMember("everyone", "writeOnly")).toThrow();
223
+
224
+ expect(group.members).toContainEqual({
225
+ id: "everyone",
226
+ role: "writer",
227
+ account: undefined,
228
+ ref: undefined,
229
+ });
230
+ });
231
+
232
+ test("typescript should show an error when adding a member with a non-account role", async () => {
233
+ const account = await createJazzTestAccount({});
234
+
235
+ const group = Group.create();
236
+
237
+ // @ts-expect-error - Even though readerInvite is a valid role for an account, we don't allow it to not create confusion when using the intellisense
238
+ group.addMember(account, "readerInvite");
239
+
240
+ expect(group.members).toContainEqual(
241
+ expect.objectContaining({
242
+ id: account.id,
243
+ role: "readerInvite",
244
+ }),
245
+ );
246
+ });
183
247
  });
@@ -0,0 +1,85 @@
1
+ import { beforeEach, describe, expect, test } from "vitest";
2
+ import { Account, Group } from "../exports";
3
+ import {
4
+ consumeInviteLink,
5
+ createInviteLink,
6
+ parseInviteLink,
7
+ } from "../implementation/invites";
8
+ import { createJazzTestAccount, setupJazzTestSync } from "../testing";
9
+
10
+ describe("Invite Links", () => {
11
+ let account: Account;
12
+ let group: Group;
13
+ const baseURL = "https://example.com";
14
+
15
+ beforeEach(async () => {
16
+ await setupJazzTestSync(); // Required to sync the invite between accounts
17
+ account = await createJazzTestAccount({
18
+ isCurrentActiveAccount: true,
19
+ });
20
+ group = Group.create({ owner: account });
21
+ });
22
+
23
+ test("createInviteLink generates correct format", () => {
24
+ const inviteLink = createInviteLink(group, "writer", baseURL);
25
+
26
+ expect(inviteLink).toMatch(
27
+ new RegExp(`^${baseURL}#/invite/${group.id}/[A-Za-z0-9_-]+$`),
28
+ );
29
+ });
30
+
31
+ test("createInviteLink with valueHint", () => {
32
+ const inviteLink = createInviteLink(group, "writer", baseURL, "myGroup");
33
+
34
+ expect(inviteLink).toMatch(
35
+ new RegExp(`^${baseURL}#/invite/myGroup/${group.id}/[A-Za-z0-9_-]+$`),
36
+ );
37
+ });
38
+
39
+ test("parseInviteLink correctly parses valid link", () => {
40
+ const inviteLink = createInviteLink(group, "writer", baseURL, "myGroup");
41
+ const result = parseInviteLink(inviteLink);
42
+
43
+ expect(result).toBeDefined();
44
+ expect(result?.valueID).toBe(group.id);
45
+ expect(result?.valueHint).toBe("myGroup");
46
+ expect(result?.inviteSecret).toBeDefined();
47
+ });
48
+
49
+ test("parseInviteLink returns undefined for invalid link", () => {
50
+ const invalidLink = "https://example.com/not-an-invite";
51
+ const result = parseInviteLink(invalidLink);
52
+
53
+ expect(result).toBeUndefined();
54
+ });
55
+
56
+ test("consumeInviteLink accepts valid invite", async () => {
57
+ const inviteLink = createInviteLink(group, "writer", baseURL, "myGroup");
58
+ const newAccount = await createJazzTestAccount();
59
+
60
+ const result = await consumeInviteLink({
61
+ inviteURL: inviteLink,
62
+ as: newAccount,
63
+ forValueHint: "myGroup",
64
+ invitedObjectSchema: Group,
65
+ });
66
+
67
+ expect(result).toBeDefined();
68
+ expect(result?.valueID).toBe(group.id);
69
+ expect(result?.valueHint).toBe("myGroup");
70
+ });
71
+
72
+ test("consumeInviteLink returns undefined for mismatched valueHint", async () => {
73
+ const inviteLink = createInviteLink(group, "writer", baseURL, "myGroup");
74
+ const newAccount = await createJazzTestAccount();
75
+
76
+ const result = await consumeInviteLink({
77
+ inviteURL: inviteLink,
78
+ as: newAccount,
79
+ forValueHint: "wrongHint",
80
+ invitedObjectSchema: Group,
81
+ });
82
+
83
+ expect(result).toBeUndefined();
84
+ });
85
+ });