jazz-tools 0.19.14 → 0.19.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.
- package/.turbo/turbo-build.log +56 -56
- package/CHANGELOG.md +25 -0
- package/dist/{chunk-GAPMDNJY.js → chunk-R3KIZG4P.js} +47 -27
- package/dist/chunk-R3KIZG4P.js.map +1 -0
- package/dist/index.js +22 -14
- package/dist/index.js.map +1 -1
- package/dist/react-native/index.js +18 -0
- 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 -0
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/auth/clerk/index.d.ts +1 -0
- package/dist/tools/auth/clerk/index.d.ts.map +1 -1
- package/dist/tools/auth/clerk/tests/isClerkAuthStateEqual.test.d.ts +2 -0
- package/dist/tools/auth/clerk/tests/isClerkAuthStateEqual.test.d.ts.map +1 -0
- package/dist/tools/auth/clerk/types.d.ts +2 -2
- package/dist/tools/auth/clerk/types.d.ts.map +1 -1
- package/dist/tools/coValues/CoValueBase.d.ts +13 -0
- package/dist/tools/coValues/CoValueBase.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +33 -35
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/react-native-core/ReactNativeSessionProvider.ts +29 -3
- package/src/react-native-core/tests/ReactNativeSessionProvider.test.ts +175 -3
- package/src/tools/auth/clerk/index.ts +21 -11
- package/src/tools/auth/clerk/tests/JazzClerkAuth.test.ts +77 -0
- package/src/tools/auth/clerk/tests/isClerkAuthStateEqual.test.ts +124 -0
- package/src/tools/auth/clerk/types.ts +23 -6
- package/src/tools/coValues/CoValueBase.ts +24 -0
- package/src/tools/coValues/coMap.ts +1 -1
- package/src/tools/implementation/createContext.ts +2 -2
- package/src/tools/subscribe/SubscriptionScope.ts +43 -34
- package/src/tools/tests/account.test.ts +19 -0
- package/src/tools/tests/coMap.record.test.ts +43 -0
- package/src/tools/tests/coMap.test.ts +67 -1
- package/dist/chunk-GAPMDNJY.js.map +0 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { LocalNode
|
|
2
|
-
import {
|
|
3
|
-
import { CoValueCoreSubscription } from "./CoValueCoreSubscription.js";
|
|
1
|
+
import { LocalNode } from "cojson";
|
|
2
|
+
import { type CoValue, type ID, MaybeLoaded, NotLoaded, type RefEncoded, type RefsToResolve } from "../internal.js";
|
|
4
3
|
import { JazzError } from "./JazzError.js";
|
|
5
4
|
import type { BranchDefinition, SubscriptionValue, SubscriptionValueLoading } from "./types.js";
|
|
6
|
-
import { CoValueLoadingState, NotLoadedCoValueState } from "./types.js";
|
|
7
5
|
import { PromiseWithStatus } from "./utils.js";
|
|
8
6
|
export declare class SubscriptionScope<D extends CoValue> {
|
|
9
7
|
node: LocalNode;
|
|
@@ -21,24 +19,24 @@ export declare class SubscriptionScope<D extends CoValue> {
|
|
|
21
19
|
/**
|
|
22
20
|
* Autoloaded child ids that are unloaded
|
|
23
21
|
*/
|
|
24
|
-
pendingAutoloadedChildren
|
|
22
|
+
private pendingAutoloadedChildren;
|
|
25
23
|
value: SubscriptionValue<D, any> | SubscriptionValueLoading;
|
|
26
|
-
childErrors
|
|
27
|
-
validationErrors
|
|
24
|
+
private childErrors;
|
|
25
|
+
private validationErrors;
|
|
28
26
|
errorFromChildren: JazzError | undefined;
|
|
29
|
-
subscription
|
|
30
|
-
dirty
|
|
31
|
-
resolve
|
|
32
|
-
idsSubscribed
|
|
33
|
-
autoloaded
|
|
34
|
-
autoloadedKeys
|
|
35
|
-
skipInvalidKeys
|
|
36
|
-
totalValidTransactions
|
|
37
|
-
version
|
|
38
|
-
migrated
|
|
39
|
-
migrating
|
|
27
|
+
private subscription;
|
|
28
|
+
private dirty;
|
|
29
|
+
private resolve;
|
|
30
|
+
private idsSubscribed;
|
|
31
|
+
private autoloaded;
|
|
32
|
+
private autoloadedKeys;
|
|
33
|
+
private skipInvalidKeys;
|
|
34
|
+
private totalValidTransactions;
|
|
35
|
+
private version;
|
|
36
|
+
private migrated;
|
|
37
|
+
private migrating;
|
|
40
38
|
closed: boolean;
|
|
41
|
-
silenceUpdates
|
|
39
|
+
private silenceUpdates;
|
|
42
40
|
/**
|
|
43
41
|
* Stack trace captured at subscription creation time.
|
|
44
42
|
* This helps identify which component/hook created the subscription
|
|
@@ -47,22 +45,22 @@ export declare class SubscriptionScope<D extends CoValue> {
|
|
|
47
45
|
callerStack: Error | undefined;
|
|
48
46
|
constructor(node: LocalNode, resolve: RefsToResolve<D>, id: ID<D>, schema: RefEncoded<D>, skipRetry?: boolean, bestEffortResolution?: boolean, unstable_branch?: BranchDefinition | undefined, callerStack?: Error | undefined);
|
|
49
47
|
updateValue(value: SubscriptionValue<D, any>): void;
|
|
50
|
-
handleUpdate
|
|
51
|
-
computeChildErrors
|
|
52
|
-
handleChildUpdate
|
|
53
|
-
shouldSendUpdates
|
|
48
|
+
private handleUpdate;
|
|
49
|
+
private computeChildErrors;
|
|
50
|
+
handleChildUpdate(id: string, value: SubscriptionValue<any, any> | SubscriptionValueLoading, key?: string): void;
|
|
51
|
+
private shouldSendUpdates;
|
|
54
52
|
unloadedValue: NotLoaded<D> | undefined;
|
|
55
|
-
lastPromise
|
|
56
|
-
getPromise
|
|
53
|
+
private lastPromise;
|
|
54
|
+
private getPromise;
|
|
57
55
|
getCachedPromise(): PromiseWithStatus<D>;
|
|
58
56
|
private getUnloadedValue;
|
|
59
|
-
lastErrorLogged
|
|
57
|
+
private lastErrorLogged;
|
|
60
58
|
getCurrentValue(): MaybeLoaded<D>;
|
|
61
|
-
getCurrentRawValue
|
|
62
|
-
getCreationStackLines
|
|
63
|
-
getError
|
|
64
|
-
logError
|
|
65
|
-
triggerUpdate
|
|
59
|
+
private getCurrentRawValue;
|
|
60
|
+
private getCreationStackLines;
|
|
61
|
+
private getError;
|
|
62
|
+
private logError;
|
|
63
|
+
private triggerUpdate;
|
|
66
64
|
subscribers: Set<(value: SubscriptionValue<D, any>) => void>;
|
|
67
65
|
subscriberChangeCallbacks: Set<(count: number) => void>;
|
|
68
66
|
/**
|
|
@@ -83,10 +81,10 @@ export declare class SubscriptionScope<D extends CoValue> {
|
|
|
83
81
|
*/
|
|
84
82
|
pullValue(listener: (value: SubscriptionValue<D, any>) => void): void;
|
|
85
83
|
subscribeToId(id: string, descriptor: RefEncoded<any>): void;
|
|
86
|
-
loadChildren
|
|
87
|
-
loadCoMapKey
|
|
88
|
-
loadCoListKey
|
|
89
|
-
loadChildNode
|
|
84
|
+
private loadChildren;
|
|
85
|
+
private loadCoMapKey;
|
|
86
|
+
private loadCoListKey;
|
|
87
|
+
private loadChildNode;
|
|
90
88
|
destroy(): void;
|
|
91
89
|
}
|
|
92
90
|
//# sourceMappingURL=SubscriptionScope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriptionScope.d.ts","sourceRoot":"","sources":["../../../src/tools/subscribe/SubscriptionScope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"SubscriptionScope.d.ts","sourceRoot":"","sources":["../../../src/tools/subscribe/SubscriptionScope.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
},
|
|
206
206
|
"type": "module",
|
|
207
207
|
"license": "MIT",
|
|
208
|
-
"version": "0.19.
|
|
208
|
+
"version": "0.19.16",
|
|
209
209
|
"dependencies": {
|
|
210
210
|
"@manuscripts/prosemirror-recreate-steps": "^0.1.4",
|
|
211
211
|
"@scure/base": "1.2.1",
|
|
@@ -222,9 +222,9 @@
|
|
|
222
222
|
"prosemirror-transform": "^1.9.0",
|
|
223
223
|
"use-sync-external-store": "^1.5.0",
|
|
224
224
|
"zod": "4.1.11",
|
|
225
|
-
"cojson": "0.19.
|
|
226
|
-
"cojson-storage-indexeddb": "0.19.
|
|
227
|
-
"cojson-transport-ws": "0.19.
|
|
225
|
+
"cojson": "0.19.16",
|
|
226
|
+
"cojson-storage-indexeddb": "0.19.16",
|
|
227
|
+
"cojson-transport-ws": "0.19.16"
|
|
228
228
|
},
|
|
229
229
|
"devDependencies": {
|
|
230
230
|
"@scure/bip39": "^1.3.0",
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
} from "jazz-tools";
|
|
7
7
|
import { AgentID, RawAccountID } from "cojson";
|
|
8
8
|
|
|
9
|
+
const lockedSessions = new Set<SessionID>();
|
|
10
|
+
|
|
9
11
|
export class ReactNativeSessionProvider implements SessionProvider {
|
|
10
12
|
async acquireSession(
|
|
11
13
|
accountID: string,
|
|
@@ -14,11 +16,29 @@ export class ReactNativeSessionProvider implements SessionProvider {
|
|
|
14
16
|
const kvStore = KvStoreContext.getInstance().getStorage();
|
|
15
17
|
const existingSession = await kvStore.get(accountID as string);
|
|
16
18
|
|
|
19
|
+
// Check if the session is already in use, should happen only if the dev
|
|
20
|
+
// mounts multiple providers at the same time
|
|
21
|
+
if (lockedSessions.has(existingSession as SessionID)) {
|
|
22
|
+
const newSessionID = crypto.newRandomSessionID(
|
|
23
|
+
accountID as RawAccountID | AgentID,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
console.error("Existing session in use, creating new one", newSessionID);
|
|
27
|
+
|
|
28
|
+
return Promise.resolve({
|
|
29
|
+
sessionID: newSessionID,
|
|
30
|
+
sessionDone: () => {},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
if (existingSession) {
|
|
18
35
|
console.log("Using existing session", existingSession);
|
|
36
|
+
lockedSessions.add(existingSession as SessionID);
|
|
19
37
|
return Promise.resolve({
|
|
20
38
|
sessionID: existingSession as SessionID,
|
|
21
|
-
sessionDone: () => {
|
|
39
|
+
sessionDone: () => {
|
|
40
|
+
lockedSessions.delete(existingSession as SessionID);
|
|
41
|
+
},
|
|
22
42
|
});
|
|
23
43
|
}
|
|
24
44
|
|
|
@@ -30,12 +50,15 @@ export class ReactNativeSessionProvider implements SessionProvider {
|
|
|
30
50
|
accountID as RawAccountID | AgentID,
|
|
31
51
|
);
|
|
32
52
|
await kvStore.set(accountID, newSessionID);
|
|
53
|
+
lockedSessions.add(newSessionID);
|
|
33
54
|
|
|
34
55
|
console.error("Created new session", newSessionID);
|
|
35
56
|
|
|
36
57
|
return Promise.resolve({
|
|
37
58
|
sessionID: newSessionID,
|
|
38
|
-
sessionDone: () => {
|
|
59
|
+
sessionDone: () => {
|
|
60
|
+
lockedSessions.delete(newSessionID);
|
|
61
|
+
},
|
|
39
62
|
});
|
|
40
63
|
}
|
|
41
64
|
|
|
@@ -45,8 +68,11 @@ export class ReactNativeSessionProvider implements SessionProvider {
|
|
|
45
68
|
): Promise<{ sessionDone: () => void }> {
|
|
46
69
|
const kvStore = KvStoreContext.getInstance().getStorage();
|
|
47
70
|
await kvStore.set(accountID, sessionID);
|
|
71
|
+
lockedSessions.add(sessionID);
|
|
48
72
|
return Promise.resolve({
|
|
49
|
-
sessionDone: () => {
|
|
73
|
+
sessionDone: () => {
|
|
74
|
+
lockedSessions.delete(sessionID);
|
|
75
|
+
},
|
|
50
76
|
});
|
|
51
77
|
}
|
|
52
78
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
2
|
-
import { SessionID } from "cojson";
|
|
2
|
+
import { RawAccountID, SessionID } from "cojson";
|
|
3
3
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
4
4
|
import { InMemoryKVStore } from "jazz-tools";
|
|
5
5
|
import { KvStoreContext, type KvStore } from "jazz-tools";
|
|
@@ -51,6 +51,9 @@ describe("ReactNativeSessionProvider", () => {
|
|
|
51
51
|
const storedSession = await kvStore.get(accountID);
|
|
52
52
|
expect(storedSession).toBeDefined();
|
|
53
53
|
expect(storedSession).toBe(result.sessionID);
|
|
54
|
+
|
|
55
|
+
// Clean up
|
|
56
|
+
result.sessionDone();
|
|
54
57
|
});
|
|
55
58
|
|
|
56
59
|
test("returns existing session when one exists", async () => {
|
|
@@ -77,6 +80,89 @@ describe("ReactNativeSessionProvider", () => {
|
|
|
77
80
|
const sessionAfter = await kvStore.get(accountID);
|
|
78
81
|
expect(sessionAfter).toBe(existingSessionID);
|
|
79
82
|
expect(sessionAfter).toBe(result.sessionID);
|
|
83
|
+
|
|
84
|
+
// Clean up
|
|
85
|
+
result.sessionDone();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("creates new session when existing session is locked", async () => {
|
|
89
|
+
const accountID = account.$jazz.id;
|
|
90
|
+
const existingSessionID = Crypto.newRandomSessionID(
|
|
91
|
+
accountID as RawAccountID,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Pre-populate KvStore with a session ID
|
|
95
|
+
await kvStore.set(accountID, existingSessionID);
|
|
96
|
+
|
|
97
|
+
// Acquire the session (this locks it)
|
|
98
|
+
const firstResult = await sessionProvider.acquireSession(
|
|
99
|
+
accountID,
|
|
100
|
+
Crypto as CryptoProvider,
|
|
101
|
+
);
|
|
102
|
+
expect(firstResult.sessionID).toBe(existingSessionID);
|
|
103
|
+
|
|
104
|
+
// Try to acquire session again while the first is still locked
|
|
105
|
+
const secondResult = await sessionProvider.acquireSession(
|
|
106
|
+
accountID,
|
|
107
|
+
Crypto as CryptoProvider,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Should get a different (new) session since the existing one is locked
|
|
111
|
+
expect(secondResult.sessionID).not.toBe(existingSessionID);
|
|
112
|
+
expect(secondResult.sessionID).toBeDefined();
|
|
113
|
+
|
|
114
|
+
// Clean up
|
|
115
|
+
firstResult.sessionDone();
|
|
116
|
+
secondResult.sessionDone();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("reuses session after sessionDone is called", async () => {
|
|
120
|
+
const accountID = account.$jazz.id;
|
|
121
|
+
|
|
122
|
+
// Acquire initial session
|
|
123
|
+
const firstResult = await sessionProvider.acquireSession(
|
|
124
|
+
accountID,
|
|
125
|
+
Crypto as CryptoProvider,
|
|
126
|
+
);
|
|
127
|
+
const firstSessionID = firstResult.sessionID;
|
|
128
|
+
|
|
129
|
+
// Release the session
|
|
130
|
+
firstResult.sessionDone();
|
|
131
|
+
|
|
132
|
+
// Acquire session again - should reuse the same session
|
|
133
|
+
const secondResult = await sessionProvider.acquireSession(
|
|
134
|
+
accountID,
|
|
135
|
+
Crypto as CryptoProvider,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(secondResult.sessionID).toBe(firstSessionID);
|
|
139
|
+
|
|
140
|
+
// Clean up
|
|
141
|
+
secondResult.sessionDone();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("sessionDone can be called multiple times safely", async () => {
|
|
145
|
+
const accountID = account.$jazz.id;
|
|
146
|
+
|
|
147
|
+
const result = await sessionProvider.acquireSession(
|
|
148
|
+
accountID,
|
|
149
|
+
Crypto as CryptoProvider,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Call sessionDone multiple times - should not throw
|
|
153
|
+
result.sessionDone();
|
|
154
|
+
result.sessionDone();
|
|
155
|
+
result.sessionDone();
|
|
156
|
+
|
|
157
|
+
// Should still be able to acquire the session
|
|
158
|
+
const secondResult = await sessionProvider.acquireSession(
|
|
159
|
+
accountID,
|
|
160
|
+
Crypto as CryptoProvider,
|
|
161
|
+
);
|
|
162
|
+
expect(secondResult.sessionID).toBe(result.sessionID);
|
|
163
|
+
|
|
164
|
+
// Clean up
|
|
165
|
+
secondResult.sessionDone();
|
|
80
166
|
});
|
|
81
167
|
});
|
|
82
168
|
|
|
@@ -90,7 +176,10 @@ describe("ReactNativeSessionProvider", () => {
|
|
|
90
176
|
expect(sessionBefore).toBeNull();
|
|
91
177
|
|
|
92
178
|
// Persist session
|
|
93
|
-
await sessionProvider.persistSession(
|
|
179
|
+
const { sessionDone } = await sessionProvider.persistSession(
|
|
180
|
+
accountID,
|
|
181
|
+
sessionID,
|
|
182
|
+
);
|
|
94
183
|
|
|
95
184
|
// Verify the session ID is stored in KvStore
|
|
96
185
|
const storedSession = await kvStore.get(accountID);
|
|
@@ -98,6 +187,9 @@ describe("ReactNativeSessionProvider", () => {
|
|
|
98
187
|
|
|
99
188
|
// Verify the stored value matches the provided session ID
|
|
100
189
|
expect(storedSession).toBe(sessionID);
|
|
190
|
+
|
|
191
|
+
// Clean up
|
|
192
|
+
sessionDone();
|
|
101
193
|
});
|
|
102
194
|
|
|
103
195
|
test("overwrites existing session", async () => {
|
|
@@ -113,12 +205,92 @@ describe("ReactNativeSessionProvider", () => {
|
|
|
113
205
|
expect(sessionBefore).toBe(initialSessionID);
|
|
114
206
|
|
|
115
207
|
// Persist a different session ID
|
|
116
|
-
await sessionProvider.persistSession(
|
|
208
|
+
const { sessionDone } = await sessionProvider.persistSession(
|
|
209
|
+
accountID,
|
|
210
|
+
newSessionID,
|
|
211
|
+
);
|
|
117
212
|
|
|
118
213
|
// Verify the new session ID replaces the old one
|
|
119
214
|
const sessionAfter = await kvStore.get(accountID);
|
|
120
215
|
expect(sessionAfter).toBe(newSessionID);
|
|
121
216
|
expect(sessionAfter).not.toBe(initialSessionID);
|
|
217
|
+
|
|
218
|
+
// Clean up
|
|
219
|
+
sessionDone();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("locks session when persisting", async () => {
|
|
223
|
+
const accountID = account.$jazz.id;
|
|
224
|
+
const sessionID = Crypto.newRandomSessionID(accountID as RawAccountID);
|
|
225
|
+
|
|
226
|
+
// Persist session - this should lock the session
|
|
227
|
+
const { sessionDone } = await sessionProvider.persistSession(
|
|
228
|
+
accountID,
|
|
229
|
+
sessionID,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Try to acquire session while it's locked by persistSession
|
|
233
|
+
const result = await sessionProvider.acquireSession(
|
|
234
|
+
accountID,
|
|
235
|
+
Crypto as CryptoProvider,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Should get a different session since the persisted one is locked
|
|
239
|
+
expect(result.sessionID).not.toBe(sessionID);
|
|
240
|
+
|
|
241
|
+
// Clean up
|
|
242
|
+
sessionDone();
|
|
243
|
+
result.sessionDone();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("allows session reuse after sessionDone is called", async () => {
|
|
247
|
+
const accountID = account.$jazz.id;
|
|
248
|
+
const sessionID = Crypto.newRandomSessionID(accountID as RawAccountID);
|
|
249
|
+
|
|
250
|
+
// Persist session
|
|
251
|
+
const { sessionDone } = await sessionProvider.persistSession(
|
|
252
|
+
accountID,
|
|
253
|
+
sessionID,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Release the session
|
|
257
|
+
sessionDone();
|
|
258
|
+
|
|
259
|
+
// Acquire session - should reuse the persisted session
|
|
260
|
+
const result = await sessionProvider.acquireSession(
|
|
261
|
+
accountID,
|
|
262
|
+
Crypto as CryptoProvider,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(result.sessionID).toBe(sessionID);
|
|
266
|
+
|
|
267
|
+
// Clean up
|
|
268
|
+
result.sessionDone();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("sessionDone can be called multiple times safely", async () => {
|
|
272
|
+
const accountID = account.$jazz.id;
|
|
273
|
+
const sessionID = Crypto.newRandomSessionID(accountID as RawAccountID);
|
|
274
|
+
|
|
275
|
+
const { sessionDone } = await sessionProvider.persistSession(
|
|
276
|
+
accountID,
|
|
277
|
+
sessionID,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
// Call sessionDone multiple times - should not throw
|
|
281
|
+
sessionDone();
|
|
282
|
+
sessionDone();
|
|
283
|
+
sessionDone();
|
|
284
|
+
|
|
285
|
+
// Should still be able to acquire the session
|
|
286
|
+
const result = await sessionProvider.acquireSession(
|
|
287
|
+
accountID,
|
|
288
|
+
Crypto as CryptoProvider,
|
|
289
|
+
);
|
|
290
|
+
expect(result.sessionID).toBe(sessionID);
|
|
291
|
+
|
|
292
|
+
// Clean up
|
|
293
|
+
result.sessionDone();
|
|
122
294
|
});
|
|
123
295
|
});
|
|
124
296
|
});
|
|
@@ -53,18 +53,21 @@ export class JazzClerkAuth {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
private isFirstCall = true;
|
|
56
|
+
private previousUser: Pick<
|
|
57
|
+
NonNullable<MinimalClerkClient["user"]>,
|
|
58
|
+
"unsafeMetadata"
|
|
59
|
+
> | null = null;
|
|
56
60
|
|
|
57
61
|
registerListener(clerkClient: MinimalClerkClient) {
|
|
58
|
-
|
|
59
|
-
clerkClient.user ?? null;
|
|
62
|
+
this.previousUser = clerkClient.user ?? null;
|
|
60
63
|
|
|
61
64
|
// Need to use addListener because the clerk user object is not updated when the user logs in
|
|
62
65
|
return clerkClient.addListener((event) => {
|
|
63
66
|
const user = (event as Pick<MinimalClerkClient, "user">).user ?? null;
|
|
64
67
|
|
|
65
|
-
if (!isClerkAuthStateEqual(previousUser, user) || this.isFirstCall) {
|
|
68
|
+
if (!isClerkAuthStateEqual(this.previousUser, user) || this.isFirstCall) {
|
|
69
|
+
this.previousUser = user;
|
|
66
70
|
this.onClerkUserChange({ user });
|
|
67
|
-
previousUser = user;
|
|
68
71
|
this.isFirstCall = false;
|
|
69
72
|
}
|
|
70
73
|
});
|
|
@@ -137,13 +140,20 @@ export class JazzClerkAuth {
|
|
|
137
140
|
? Array.from(credentials.secretSeed)
|
|
138
141
|
: undefined;
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
const clerkCredentials = {
|
|
144
|
+
jazzAccountID: credentials.accountID,
|
|
145
|
+
jazzAccountSecret: credentials.accountSecret,
|
|
146
|
+
jazzAccountSeed,
|
|
147
|
+
};
|
|
148
|
+
// user.update will cause the Clerk user change listener to fire; updating this.previousUser beforehand
|
|
149
|
+
// ensures the listener sees the new credentials and does not trigger an unnecessary logIn operation
|
|
150
|
+
this.previousUser = { unsafeMetadata: clerkCredentials };
|
|
151
|
+
|
|
152
|
+
if (clerkClient.user) {
|
|
153
|
+
await clerkClient.user.update({
|
|
154
|
+
unsafeMetadata: clerkCredentials,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
147
157
|
|
|
148
158
|
const currentAccount = await Account.getMe().$jazz.ensureLoaded({
|
|
149
159
|
resolve: {
|
|
@@ -282,6 +282,83 @@ describe("JazzClerkAuth", () => {
|
|
|
282
282
|
|
|
283
283
|
expect(onClerkUserChangeSpy).toHaveBeenCalledTimes(1);
|
|
284
284
|
});
|
|
285
|
+
|
|
286
|
+
it("should complete signup flow when new Clerk user is detected", async () => {
|
|
287
|
+
// 1. Setup local credentials (simulating anonymous user)
|
|
288
|
+
await authSecretStorage.set({
|
|
289
|
+
accountID: "test-account-id" as ID<Account>,
|
|
290
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
|
291
|
+
accountSecret: "test-secret" as AgentSecret,
|
|
292
|
+
provider: "anonymous",
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const { client, triggerUserChange } = setupMockClerk(null);
|
|
296
|
+
|
|
297
|
+
const auth = new JazzClerkAuth(
|
|
298
|
+
mockAuthenticate,
|
|
299
|
+
mockLogOut,
|
|
300
|
+
authSecretStorage,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// 2. Register listener with null user (no one logged in yet)
|
|
304
|
+
auth.registerListener(client);
|
|
305
|
+
|
|
306
|
+
// Initial trigger with no user
|
|
307
|
+
triggerUserChange(null);
|
|
308
|
+
|
|
309
|
+
// 3. Trigger event with new Clerk user (no Jazz credentials yet)
|
|
310
|
+
const mockUserUpdate = vi.fn((data) => {
|
|
311
|
+
triggerUserChange(data);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const signInSpy = vi.spyOn(auth, "signIn");
|
|
315
|
+
const logInSpy = vi.spyOn(auth, "logIn");
|
|
316
|
+
|
|
317
|
+
const newClerkUser = {
|
|
318
|
+
fullName: "Test User",
|
|
319
|
+
firstName: "Test",
|
|
320
|
+
lastName: "User",
|
|
321
|
+
username: "testuser",
|
|
322
|
+
id: "clerk-user-123",
|
|
323
|
+
primaryEmailAddress: { emailAddress: "test@example.com" },
|
|
324
|
+
unsafeMetadata: {}, // No Jazz credentials yet
|
|
325
|
+
update: mockUserUpdate,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
triggerUserChange(newClerkUser);
|
|
329
|
+
|
|
330
|
+
// Wait for async operations to complete
|
|
331
|
+
await vi.waitFor(() => {
|
|
332
|
+
expect(mockUserUpdate).toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// 4. Verify credentials synced to Clerk
|
|
336
|
+
expect(mockUserUpdate).toHaveBeenCalledWith({
|
|
337
|
+
unsafeMetadata: {
|
|
338
|
+
jazzAccountID: "test-account-id",
|
|
339
|
+
jazzAccountSecret: "test-secret",
|
|
340
|
+
jazzAccountSeed: [1, 2, 3],
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Verify profile name was updated from Clerk username
|
|
345
|
+
const me = await Account.getMe().$jazz.ensureLoaded({
|
|
346
|
+
resolve: { profile: true },
|
|
347
|
+
});
|
|
348
|
+
expect(me.profile.name).toBe("Test User");
|
|
349
|
+
|
|
350
|
+
// Verify authSecretStorage is updated with provider "clerk"
|
|
351
|
+
const storedCredentials = await authSecretStorage.get();
|
|
352
|
+
expect(storedCredentials).toEqual({
|
|
353
|
+
accountID: "test-account-id",
|
|
354
|
+
accountSecret: "test-secret",
|
|
355
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
|
356
|
+
provider: "clerk",
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(signInSpy).toHaveBeenCalled();
|
|
360
|
+
expect(logInSpy).not.toHaveBeenCalled();
|
|
361
|
+
});
|
|
285
362
|
});
|
|
286
363
|
|
|
287
364
|
describe("initializeAuth", () => {
|