jazz-tools 0.19.16 → 0.19.17
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/.svelte-kit/__package__/tests/media/image.svelte.test.js +4 -1
- package/.turbo/turbo-build.log +55 -55
- package/CHANGELOG.md +9 -0
- package/dist/{chunk-R3KIZG4P.js → chunk-OH2GW5WP.js} +24 -8
- package/dist/{chunk-R3KIZG4P.js.map → chunk-OH2GW5WP.js.map} +1 -1
- package/dist/index.js +72 -48
- package/dist/index.js.map +1 -1
- package/dist/react-native/index.js +18 -17
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/ReactNativeSessionProvider.d.ts.map +1 -1
- package/dist/react-native-core/index.js +18 -17
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/svelte/tests/media/image.svelte.test.js +4 -1
- package/dist/testing.js +1 -1
- package/dist/tools/auth/clerk/getClerkUsername.d.ts +2 -2
- package/dist/tools/auth/clerk/getClerkUsername.d.ts.map +1 -1
- package/dist/tools/auth/clerk/index.d.ts +4 -4
- package/dist/tools/auth/clerk/index.d.ts.map +1 -1
- package/dist/tools/auth/clerk/tests/isClerkCredentials.test.d.ts +2 -0
- package/dist/tools/auth/clerk/tests/isClerkCredentials.test.d.ts.map +1 -0
- package/dist/tools/auth/clerk/types.d.ts +62 -19
- package/dist/tools/auth/clerk/types.d.ts.map +1 -1
- package/dist/tools/implementation/ContextManager.d.ts +10 -0
- package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
- package/package.json +5 -6
- package/src/react/tests/media/image.test.tsx +5 -1
- package/src/react-native-core/ReactNativeSessionProvider.ts +24 -24
- package/src/svelte/tests/media/image.svelte.test.ts +5 -1
- package/src/tools/auth/clerk/getClerkUsername.ts +13 -20
- package/src/tools/auth/clerk/index.ts +25 -28
- package/src/tools/auth/clerk/tests/JazzClerkAuth.test.ts +29 -34
- package/src/tools/auth/clerk/tests/getClerkUsername.test.ts +25 -45
- package/src/tools/auth/clerk/tests/isClerkAuthStateEqual.test.ts +7 -3
- package/src/tools/auth/clerk/tests/{types.test.ts → isClerkCredentials.test.ts} +4 -2
- package/src/tools/auth/clerk/types.ts +55 -34
- package/src/tools/implementation/ContextManager.ts +28 -7
- package/src/tools/tests/ContextManager.test.ts +16 -0
- package/dist/tools/auth/clerk/tests/types.test.d.ts +0 -2
- package/dist/tools/auth/clerk/tests/types.test.d.ts.map +0 -1
|
@@ -1,81 +1,61 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { getClerkUsername } from "../getClerkUsername.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ClerkUser } from "../types.js";
|
|
4
4
|
|
|
5
5
|
describe("getClerkUsername", () => {
|
|
6
|
-
it("should return null if no user", () => {
|
|
7
|
-
const mockClerk = {
|
|
8
|
-
user: null,
|
|
9
|
-
} as MinimalClerkClient;
|
|
10
|
-
|
|
11
|
-
expect(getClerkUsername(mockClerk)).toBe(null);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
6
|
it("should return fullName if available", () => {
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
expect(
|
|
8
|
+
getClerkUsername({
|
|
17
9
|
fullName: "John Doe",
|
|
18
10
|
firstName: "John",
|
|
19
11
|
lastName: "Doe",
|
|
20
12
|
username: "johndoe",
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expect(getClerkUsername(mockClerk)).toBe("John Doe");
|
|
13
|
+
} as ClerkUser),
|
|
14
|
+
).toBe("John Doe");
|
|
25
15
|
});
|
|
26
16
|
|
|
27
17
|
it("should return firstName + lastName if available and no fullName", () => {
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
expect(
|
|
19
|
+
getClerkUsername({
|
|
30
20
|
firstName: "John",
|
|
31
21
|
lastName: "Doe",
|
|
32
22
|
username: "johndoe",
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
expect(getClerkUsername(mockClerk)).toBe("John Doe");
|
|
23
|
+
} as ClerkUser),
|
|
24
|
+
).toBe("John Doe");
|
|
37
25
|
});
|
|
38
26
|
|
|
39
27
|
it("should return firstName if available and no lastName or fullName", () => {
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
expect(
|
|
29
|
+
getClerkUsername({
|
|
42
30
|
firstName: "John",
|
|
43
31
|
username: "johndoe",
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
expect(getClerkUsername(mockClerk)).toBe("John");
|
|
32
|
+
} as ClerkUser),
|
|
33
|
+
).toBe("John");
|
|
48
34
|
});
|
|
49
35
|
|
|
50
36
|
it("should return username if available and no names", () => {
|
|
51
|
-
|
|
52
|
-
|
|
37
|
+
expect(
|
|
38
|
+
getClerkUsername({
|
|
53
39
|
username: "johndoe",
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
expect(getClerkUsername(mockClerk)).toBe("johndoe");
|
|
40
|
+
} as ClerkUser),
|
|
41
|
+
).toBe("johndoe");
|
|
58
42
|
});
|
|
59
43
|
|
|
60
44
|
it("should return email username if available and no other identifiers", () => {
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
expect(
|
|
46
|
+
getClerkUsername({
|
|
63
47
|
primaryEmailAddress: {
|
|
64
48
|
emailAddress: "john.doe@example.com",
|
|
65
49
|
},
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
expect(getClerkUsername(mockClerk)).toBe("john.doe");
|
|
50
|
+
} as ClerkUser),
|
|
51
|
+
).toBe("john.doe");
|
|
70
52
|
});
|
|
71
53
|
|
|
72
54
|
it("should return user id as last resort", () => {
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
expect(
|
|
56
|
+
getClerkUsername({
|
|
75
57
|
id: "user_123",
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
expect(getClerkUsername(mockClerk)).toBe("user_123");
|
|
58
|
+
} as ClerkUser),
|
|
59
|
+
).toBe("user_123");
|
|
80
60
|
});
|
|
81
61
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isClerkAuthStateEqual } from "../types";
|
|
2
|
+
import { ClerkUser, isClerkAuthStateEqual } from "../types";
|
|
3
3
|
|
|
4
4
|
describe("isClerkAuthStateEqual", () => {
|
|
5
5
|
const validCredentials = {
|
|
@@ -91,7 +91,9 @@ describe("isClerkAuthStateEqual", () => {
|
|
|
91
91
|
description: "both have incomplete credentials",
|
|
92
92
|
},
|
|
93
93
|
])("returns true when $description", ({ previous, next }) => {
|
|
94
|
-
expect(
|
|
94
|
+
expect(
|
|
95
|
+
isClerkAuthStateEqual(previous as ClerkUser, next as ClerkUser),
|
|
96
|
+
).toBe(true);
|
|
95
97
|
});
|
|
96
98
|
});
|
|
97
99
|
|
|
@@ -118,7 +120,9 @@ describe("isClerkAuthStateEqual", () => {
|
|
|
118
120
|
description: "previous has incomplete, next has credentials",
|
|
119
121
|
},
|
|
120
122
|
])("returns false when $description", ({ previous, next }) => {
|
|
121
|
-
expect(
|
|
123
|
+
expect(
|
|
124
|
+
isClerkAuthStateEqual(previous as ClerkUser, next as ClerkUser),
|
|
125
|
+
).toBe(false);
|
|
122
126
|
});
|
|
123
127
|
});
|
|
124
128
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isClerkCredentials } from "../types";
|
|
2
|
+
import { ClerkUser, isClerkCredentials } from "../types";
|
|
3
3
|
|
|
4
4
|
describe("isClerkCredentials", () => {
|
|
5
5
|
it.each([
|
|
@@ -44,6 +44,8 @@ describe("isClerkCredentials", () => {
|
|
|
44
44
|
description: "missing jazzAccountSecret",
|
|
45
45
|
},
|
|
46
46
|
])("fails for invalid credentials: $description", ({ metadata }) => {
|
|
47
|
-
expect(isClerkCredentials(metadata)).toBe(
|
|
47
|
+
expect(isClerkCredentials(metadata as ClerkUser["unsafeMetadata"])).toBe(
|
|
48
|
+
false,
|
|
49
|
+
);
|
|
48
50
|
});
|
|
49
51
|
});
|
|
@@ -1,32 +1,57 @@
|
|
|
1
|
-
import { AgentSecret } from "cojson";
|
|
2
|
-
import {
|
|
1
|
+
import { type AgentSecret } from "cojson";
|
|
2
|
+
import { z } from "zod/v4";
|
|
3
|
+
|
|
4
|
+
const ClerkJazzCredentialsSchema = z.object({
|
|
5
|
+
jazzAccountID: z.string(),
|
|
6
|
+
jazzAccountSecret: z.string(),
|
|
7
|
+
jazzAccountSeed: z.array(z.number()).optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const ClerkUserSchema = z.object({
|
|
11
|
+
fullName: z.string().nullish(),
|
|
12
|
+
username: z.string().nullish(),
|
|
13
|
+
firstName: z.string().nullish(),
|
|
14
|
+
lastName: z.string().nullish(),
|
|
15
|
+
id: z.string().optional(),
|
|
16
|
+
primaryEmailAddress: z
|
|
17
|
+
.object({
|
|
18
|
+
emailAddress: z.string().nullable(),
|
|
19
|
+
})
|
|
20
|
+
.nullish(),
|
|
21
|
+
unsafeMetadata: z.union([z.object({}), ClerkJazzCredentialsSchema]),
|
|
22
|
+
update: z.function({
|
|
23
|
+
input: [
|
|
24
|
+
z.object({
|
|
25
|
+
unsafeMetadata: ClerkJazzCredentialsSchema,
|
|
26
|
+
}),
|
|
27
|
+
],
|
|
28
|
+
output: z.promise(z.unknown()),
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const ClerkEventSchema = z.object({
|
|
33
|
+
user: ClerkUserSchema.nullish(),
|
|
34
|
+
});
|
|
35
|
+
export type ClerkEventSchema = z.infer<typeof ClerkEventSchema>;
|
|
36
|
+
|
|
37
|
+
export type ClerkUser = z.infer<typeof ClerkUserSchema>;
|
|
38
|
+
|
|
39
|
+
// Need to provide a permissive type externally to accept
|
|
40
|
+
type PermissiveClerkUser = Omit<ClerkUser, "unsafeMetadata" | "update"> & {
|
|
41
|
+
unsafeMetadata: Record<string, unknown>;
|
|
42
|
+
update: (args: {
|
|
43
|
+
unsafeMetadata: Record<string, unknown>;
|
|
44
|
+
}) => Promise<unknown>;
|
|
45
|
+
};
|
|
3
46
|
|
|
4
47
|
export type MinimalClerkClient = {
|
|
5
|
-
user:
|
|
6
|
-
| {
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
-
unsafeMetadata: Record<string, any>;
|
|
9
|
-
fullName: string | null;
|
|
10
|
-
username: string | null;
|
|
11
|
-
firstName: string | null;
|
|
12
|
-
lastName: string | null;
|
|
13
|
-
id: string;
|
|
14
|
-
primaryEmailAddress: {
|
|
15
|
-
emailAddress: string | null;
|
|
16
|
-
} | null;
|
|
17
|
-
update: (args: {
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19
|
-
unsafeMetadata: Record<string, any>;
|
|
20
|
-
}) => Promise<unknown>;
|
|
21
|
-
}
|
|
22
|
-
| null
|
|
23
|
-
| undefined;
|
|
48
|
+
user: PermissiveClerkUser | null | undefined;
|
|
24
49
|
signOut: () => Promise<void>;
|
|
25
50
|
addListener: (listener: (data: unknown) => void) => void;
|
|
26
51
|
};
|
|
27
52
|
|
|
28
53
|
export type ClerkCredentials = {
|
|
29
|
-
jazzAccountID:
|
|
54
|
+
jazzAccountID: string;
|
|
30
55
|
jazzAccountSecret: AgentSecret;
|
|
31
56
|
jazzAccountSeed?: number[];
|
|
32
57
|
};
|
|
@@ -36,23 +61,19 @@ export type ClerkCredentials = {
|
|
|
36
61
|
* **Note**: It does not validate the credentials, only checks if the necessary fields are present in the metadata object.
|
|
37
62
|
*/
|
|
38
63
|
export function isClerkCredentials(
|
|
39
|
-
data:
|
|
40
|
-
| NonNullable<MinimalClerkClient["user"]>["unsafeMetadata"]
|
|
41
|
-
| null
|
|
42
|
-
| undefined,
|
|
64
|
+
data: Record<string, unknown> | undefined,
|
|
43
65
|
): data is ClerkCredentials {
|
|
44
66
|
return !!data && "jazzAccountID" in data && "jazzAccountSecret" in data;
|
|
45
67
|
}
|
|
46
68
|
|
|
69
|
+
type ClerkUserWithUnsafeMetadata =
|
|
70
|
+
| Pick<ClerkUser, "unsafeMetadata">
|
|
71
|
+
| null
|
|
72
|
+
| undefined;
|
|
73
|
+
|
|
47
74
|
export function isClerkAuthStateEqual(
|
|
48
|
-
previousUser:
|
|
49
|
-
|
|
50
|
-
| null
|
|
51
|
-
| undefined,
|
|
52
|
-
newUser:
|
|
53
|
-
| Pick<NonNullable<MinimalClerkClient["user"]>, "unsafeMetadata">
|
|
54
|
-
| null
|
|
55
|
-
| undefined,
|
|
75
|
+
previousUser: ClerkUserWithUnsafeMetadata,
|
|
76
|
+
newUser: ClerkUserWithUnsafeMetadata,
|
|
56
77
|
) {
|
|
57
78
|
if (Boolean(previousUser) !== Boolean(newUser)) {
|
|
58
79
|
return false;
|
|
@@ -188,24 +188,45 @@ export class JazzContextManager<
|
|
|
188
188
|
return this.subscriptionCache;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Flag to indicate if a logout operation is currently in progress.
|
|
193
|
+
* Used to prevent concurrent logout attempts or double-logout issues.
|
|
194
|
+
* Set to true when logout starts, reset to false once all logout logic runs.
|
|
195
|
+
*/
|
|
196
|
+
loggingOut = false;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Handles the logout process.
|
|
200
|
+
* Uses the loggingOut flag to ensure only one logout can happen at a time.
|
|
201
|
+
*/
|
|
191
202
|
logOut = async () => {
|
|
192
|
-
if (!this.context || !this.props) {
|
|
203
|
+
if (!this.context || !this.props || this.loggingOut) {
|
|
193
204
|
return;
|
|
194
205
|
}
|
|
195
206
|
|
|
207
|
+
// Mark as logging out to prevent reentry
|
|
208
|
+
this.loggingOut = true;
|
|
209
|
+
|
|
196
210
|
this.authenticatingAccountID = null;
|
|
197
211
|
|
|
198
212
|
// Clear cache on logout to prevent subscription leaks across authentication boundaries
|
|
199
213
|
this.subscriptionCache.clear();
|
|
200
214
|
|
|
201
|
-
|
|
215
|
+
try {
|
|
216
|
+
await this.props.onLogOut?.();
|
|
202
217
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
218
|
+
if (this.props.logOutReplacement) {
|
|
219
|
+
await this.props.logOutReplacement();
|
|
220
|
+
} else {
|
|
221
|
+
await this.context.logOut();
|
|
222
|
+
await this.createContext(this.props);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error("Error during logout", error);
|
|
208
226
|
}
|
|
227
|
+
|
|
228
|
+
// Reset flag after standard logout finishes
|
|
229
|
+
this.loggingOut = false;
|
|
209
230
|
};
|
|
210
231
|
|
|
211
232
|
done = () => {
|
|
@@ -604,6 +604,22 @@ describe("ContextManager", () => {
|
|
|
604
604
|
expect(onAnonymousAccountDiscarded).toHaveBeenCalledTimes(1);
|
|
605
605
|
});
|
|
606
606
|
|
|
607
|
+
test("prevents concurrent logout attempts", async () => {
|
|
608
|
+
const onLogOut = vi.fn();
|
|
609
|
+
await manager.createContext({ onLogOut });
|
|
610
|
+
|
|
611
|
+
// Start multiple concurrent logout attempts
|
|
612
|
+
const promises = [];
|
|
613
|
+
for (let i = 0; i < 5; i++) {
|
|
614
|
+
promises.push(manager.logOut());
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
await Promise.all(promises);
|
|
618
|
+
|
|
619
|
+
// onLogOut should only be called once despite multiple logOut calls
|
|
620
|
+
expect(onLogOut).toHaveBeenCalledTimes(1);
|
|
621
|
+
});
|
|
622
|
+
|
|
607
623
|
test("allows authentication after logout", async () => {
|
|
608
624
|
const account = await createJazzTestAccount();
|
|
609
625
|
const onAnonymousAccountDiscarded = vi.fn();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.test.d.ts","sourceRoot":"","sources":["../../../../../src/tools/auth/clerk/tests/types.test.ts"],"names":[],"mappings":""}
|