@douvery/auth 0.3.2 → 0.3.3
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/dist/index.d.ts +61 -1
- package/dist/index.js +132 -2
- package/dist/index.js.map +1 -1
- package/dist/qwik/index.d.ts +59 -25
- package/dist/qwik/index.js +154 -59
- package/dist/qwik/index.js.map +1 -1
- package/package.json +1 -1
- package/src/qwik/index.tsx +243 -75
package/src/qwik/index.tsx
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @douvery/auth/qwik - Qwik adapter
|
|
3
|
+
*
|
|
4
|
+
* Uses QRL for config to avoid Qwik serialization issues with
|
|
5
|
+
* function-based storage adapters (customStorage).
|
|
6
|
+
* The client is created inside useVisibleTask$ and wrapped with
|
|
7
|
+
* noSerialize() since DouveryAuthClient has non-serializable methods.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import {
|
|
@@ -10,8 +15,12 @@ import {
|
|
|
10
15
|
useTask$,
|
|
11
16
|
useVisibleTask$,
|
|
12
17
|
component$,
|
|
18
|
+
$,
|
|
13
19
|
Slot,
|
|
20
|
+
noSerialize,
|
|
14
21
|
type Signal,
|
|
22
|
+
type NoSerialize,
|
|
23
|
+
type QRL,
|
|
15
24
|
} from "@builder.io/qwik";
|
|
16
25
|
import {
|
|
17
26
|
DouveryAuthClient,
|
|
@@ -33,47 +42,96 @@ import {
|
|
|
33
42
|
type AuthUrl,
|
|
34
43
|
} from "@douvery/auth";
|
|
35
44
|
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Context
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
36
49
|
interface DouveryAuthContextValue {
|
|
37
50
|
state: Signal<AuthState>;
|
|
38
51
|
isInitialized: Signal<boolean>;
|
|
39
52
|
isLoading: Signal<boolean>;
|
|
40
53
|
error: Signal<Error | null>;
|
|
41
|
-
|
|
54
|
+
clientRef: Signal<NoSerialize<DouveryAuthClient> | undefined>;
|
|
55
|
+
/** Application-specific user data from SSR (e.g. routeLoader$). */
|
|
56
|
+
appUser: Signal<unknown>;
|
|
57
|
+
appUserAuthenticated: Signal<boolean>;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
export const DouveryAuthContext =
|
|
45
61
|
createContextId<DouveryAuthContextValue>("douvery-auth");
|
|
46
62
|
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Provider
|
|
65
|
+
// ============================================================================
|
|
66
|
+
|
|
47
67
|
export interface DouveryAuthProviderProps {
|
|
48
|
-
|
|
68
|
+
/**
|
|
69
|
+
* QRL that returns the auth configuration.
|
|
70
|
+
* Use $(() => getDouveryAuthConfig()) to wrap your config factory.
|
|
71
|
+
* This avoids Qwik serialization issues with customStorage functions.
|
|
72
|
+
*/
|
|
73
|
+
config$: QRL<() => DouveryAuthConfig>;
|
|
74
|
+
/**
|
|
75
|
+
* Optional application-specific user data loaded from SSR (routeLoader$).
|
|
76
|
+
* This is separate from OAuth user – it holds the full app user object
|
|
77
|
+
* (e.g. UserACC with address, active, sessionId, etc.).
|
|
78
|
+
* Pass the routeLoader$ signal directly.
|
|
79
|
+
*/
|
|
80
|
+
appUser?: Signal<unknown>;
|
|
49
81
|
}
|
|
50
82
|
|
|
83
|
+
const DEFAULT_STATE: AuthState = {
|
|
84
|
+
status: "unauthenticated",
|
|
85
|
+
user: null,
|
|
86
|
+
tokens: null,
|
|
87
|
+
error: null,
|
|
88
|
+
};
|
|
89
|
+
|
|
51
90
|
export const DouveryAuthProvider = component$<DouveryAuthProviderProps>(
|
|
52
|
-
({ config }) => {
|
|
53
|
-
|
|
54
|
-
const state = useSignal<AuthState>(
|
|
91
|
+
({ config$, appUser: externalAppUser }) => {
|
|
92
|
+
// All signals are serializable - no functions stored directly
|
|
93
|
+
const state = useSignal<AuthState>(DEFAULT_STATE);
|
|
55
94
|
const isInitialized = useSignal(false);
|
|
56
95
|
const isLoading = useSignal(false);
|
|
57
96
|
const error = useSignal<Error | null>(null);
|
|
97
|
+
const clientRef = useSignal<NoSerialize<DouveryAuthClient>>();
|
|
98
|
+
|
|
99
|
+
// App user data: use external signal if provided, otherwise create internal one
|
|
100
|
+
const internalAppUser = useSignal<unknown>(externalAppUser?.value ?? null);
|
|
101
|
+
const appUser = externalAppUser ?? internalAppUser;
|
|
102
|
+
const appUserAuthenticated = useSignal<boolean>(!!appUser.value);
|
|
103
|
+
|
|
104
|
+
// Keep appUserAuthenticated in sync
|
|
105
|
+
useTask$(({ track }) => {
|
|
106
|
+
const u = track(() => appUser.value);
|
|
107
|
+
appUserAuthenticated.value = !!u;
|
|
108
|
+
});
|
|
58
109
|
|
|
59
110
|
useContextProvider(DouveryAuthContext, {
|
|
60
111
|
state,
|
|
61
112
|
isInitialized,
|
|
62
113
|
isLoading,
|
|
63
114
|
error,
|
|
64
|
-
|
|
115
|
+
clientRef,
|
|
116
|
+
appUser,
|
|
117
|
+
appUserAuthenticated,
|
|
65
118
|
});
|
|
66
119
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
// Client creation deferred to browser-only task.
|
|
121
|
+
// The QRL is invoked here, returning the full config (with customStorage).
|
|
122
|
+
// noSerialize() wraps the client so Qwik doesn't try to serialize it.
|
|
123
|
+
useVisibleTask$(async () => {
|
|
124
|
+
const config = await config$();
|
|
125
|
+
const client = createDouveryAuth(config);
|
|
126
|
+
clientRef.value = noSerialize(client);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await client.initialize();
|
|
130
|
+
isInitialized.value = true;
|
|
131
|
+
state.value = client.getState();
|
|
132
|
+
} catch (err) {
|
|
133
|
+
error.value = err instanceof Error ? err : new Error(String(err));
|
|
134
|
+
}
|
|
77
135
|
|
|
78
136
|
const unsubscribe = client.subscribe((event) => {
|
|
79
137
|
state.value = client.getState();
|
|
@@ -93,10 +151,29 @@ export const DouveryAuthProvider = component$<DouveryAuthProviderProps>(
|
|
|
93
151
|
},
|
|
94
152
|
);
|
|
95
153
|
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Hooks
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
96
158
|
export function useDouveryAuth() {
|
|
97
159
|
return useContext(DouveryAuthContext);
|
|
98
160
|
}
|
|
99
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Internal helper: safely access the client from context.
|
|
164
|
+
* Throws if the client hasn't been initialized yet (before useVisibleTask$ runs).
|
|
165
|
+
*/
|
|
166
|
+
function getClient(ctx: DouveryAuthContextValue): DouveryAuthClient {
|
|
167
|
+
const client = ctx.clientRef.value;
|
|
168
|
+
if (!client) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"DouveryAuth client not initialized. " +
|
|
171
|
+
"Ensure DouveryAuthProvider is mounted and the page has hydrated.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return client;
|
|
175
|
+
}
|
|
176
|
+
|
|
100
177
|
export function useUser(): Signal<User | null> {
|
|
101
178
|
const { state } = useDouveryAuth();
|
|
102
179
|
const user = useSignal<User | null>(state.value.user);
|
|
@@ -118,9 +195,11 @@ export function useIsAuthenticated(): Signal<boolean> {
|
|
|
118
195
|
}
|
|
119
196
|
|
|
120
197
|
export function useAuthActions() {
|
|
121
|
-
const
|
|
198
|
+
const ctx = useDouveryAuth();
|
|
199
|
+
const { isLoading, error } = ctx;
|
|
122
200
|
|
|
123
|
-
const login = async (options?: LoginOptions) => {
|
|
201
|
+
const login = $(async (options?: LoginOptions) => {
|
|
202
|
+
const client = getClient(ctx);
|
|
124
203
|
isLoading.value = true;
|
|
125
204
|
error.value = null;
|
|
126
205
|
try {
|
|
@@ -131,9 +210,10 @@ export function useAuthActions() {
|
|
|
131
210
|
} finally {
|
|
132
211
|
isLoading.value = false;
|
|
133
212
|
}
|
|
134
|
-
};
|
|
213
|
+
});
|
|
135
214
|
|
|
136
|
-
const logout = async (options?: LogoutOptions) => {
|
|
215
|
+
const logout = $(async (options?: LogoutOptions) => {
|
|
216
|
+
const client = getClient(ctx);
|
|
137
217
|
isLoading.value = true;
|
|
138
218
|
error.value = null;
|
|
139
219
|
try {
|
|
@@ -144,41 +224,42 @@ export function useAuthActions() {
|
|
|
144
224
|
} finally {
|
|
145
225
|
isLoading.value = false;
|
|
146
226
|
}
|
|
147
|
-
};
|
|
227
|
+
});
|
|
148
228
|
|
|
149
|
-
const selectAccount = (options?: SelectAccountOptions) => {
|
|
150
|
-
|
|
151
|
-
};
|
|
229
|
+
const selectAccount = $((options?: SelectAccountOptions) => {
|
|
230
|
+
getClient(ctx).selectAccount(options);
|
|
231
|
+
});
|
|
152
232
|
|
|
153
|
-
const addAccount = (options?: AddAccountOptions) => {
|
|
154
|
-
|
|
155
|
-
};
|
|
233
|
+
const addAccount = $((options?: AddAccountOptions) => {
|
|
234
|
+
getClient(ctx).addAccount(options);
|
|
235
|
+
});
|
|
156
236
|
|
|
157
|
-
const register = (options?: RegisterOptions) => {
|
|
158
|
-
|
|
159
|
-
};
|
|
237
|
+
const register = $((options?: RegisterOptions) => {
|
|
238
|
+
getClient(ctx).register(options);
|
|
239
|
+
});
|
|
160
240
|
|
|
161
|
-
const recoverAccount = (options?: RecoverAccountOptions) => {
|
|
162
|
-
|
|
163
|
-
};
|
|
241
|
+
const recoverAccount = $((options?: RecoverAccountOptions) => {
|
|
242
|
+
getClient(ctx).recoverAccount(options);
|
|
243
|
+
});
|
|
164
244
|
|
|
165
|
-
const verifyAccount = (options?: VerifyAccountOptions) => {
|
|
166
|
-
|
|
167
|
-
};
|
|
245
|
+
const verifyAccount = $((options?: VerifyAccountOptions) => {
|
|
246
|
+
getClient(ctx).verifyAccount(options);
|
|
247
|
+
});
|
|
168
248
|
|
|
169
|
-
const upgradeAccount = (options?: UpgradeAccountOptions) => {
|
|
170
|
-
|
|
171
|
-
};
|
|
249
|
+
const upgradeAccount = $((options?: UpgradeAccountOptions) => {
|
|
250
|
+
getClient(ctx).upgradeAccount(options);
|
|
251
|
+
});
|
|
172
252
|
|
|
173
|
-
const setupPasskey = (options?: SetupPasskeyOptions) => {
|
|
174
|
-
|
|
175
|
-
};
|
|
253
|
+
const setupPasskey = $((options?: SetupPasskeyOptions) => {
|
|
254
|
+
getClient(ctx).setupPasskey(options);
|
|
255
|
+
});
|
|
176
256
|
|
|
177
|
-
const setupAddress = (options?: SetupAddressOptions) => {
|
|
178
|
-
|
|
179
|
-
};
|
|
257
|
+
const setupAddress = $((options?: SetupAddressOptions) => {
|
|
258
|
+
getClient(ctx).setupAddress(options);
|
|
259
|
+
});
|
|
180
260
|
|
|
181
|
-
const revokeToken = async (options?: RevokeTokenOptions) => {
|
|
261
|
+
const revokeToken = $(async (options?: RevokeTokenOptions) => {
|
|
262
|
+
const client = getClient(ctx);
|
|
182
263
|
isLoading.value = true;
|
|
183
264
|
error.value = null;
|
|
184
265
|
try {
|
|
@@ -189,7 +270,7 @@ export function useAuthActions() {
|
|
|
189
270
|
} finally {
|
|
190
271
|
isLoading.value = false;
|
|
191
272
|
}
|
|
192
|
-
};
|
|
273
|
+
});
|
|
193
274
|
|
|
194
275
|
return {
|
|
195
276
|
login,
|
|
@@ -209,48 +290,135 @@ export function useAuthActions() {
|
|
|
209
290
|
|
|
210
291
|
/** Get URL builders for auth pages (non-redirecting, useful for <a> tags) */
|
|
211
292
|
export function useAuthUrls() {
|
|
212
|
-
const
|
|
293
|
+
const ctx = useDouveryAuth();
|
|
213
294
|
return {
|
|
214
|
-
loginUrl: (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
295
|
+
loginUrl: $(
|
|
296
|
+
(options?: LoginOptions): AuthUrl =>
|
|
297
|
+
getClient(ctx).buildLoginUrl(options),
|
|
298
|
+
),
|
|
299
|
+
logoutUrl: $(
|
|
300
|
+
(options?: LogoutOptions): AuthUrl =>
|
|
301
|
+
getClient(ctx).buildLogoutUrl(options),
|
|
302
|
+
),
|
|
303
|
+
selectAccountUrl: $(
|
|
304
|
+
(options?: SelectAccountOptions): AuthUrl =>
|
|
305
|
+
getClient(ctx).buildSelectAccountUrl(options),
|
|
306
|
+
),
|
|
307
|
+
addAccountUrl: $(
|
|
308
|
+
(options?: AddAccountOptions): AuthUrl =>
|
|
309
|
+
getClient(ctx).buildAddAccountUrl(options),
|
|
310
|
+
),
|
|
311
|
+
registerUrl: $(
|
|
312
|
+
(options?: RegisterOptions): AuthUrl =>
|
|
313
|
+
getClient(ctx).buildRegisterUrl(options),
|
|
314
|
+
),
|
|
315
|
+
recoverAccountUrl: $(
|
|
316
|
+
(options?: RecoverAccountOptions): AuthUrl =>
|
|
317
|
+
getClient(ctx).buildRecoverAccountUrl(options),
|
|
318
|
+
),
|
|
319
|
+
verifyAccountUrl: $(
|
|
320
|
+
(options?: VerifyAccountOptions): AuthUrl =>
|
|
321
|
+
getClient(ctx).buildVerifyAccountUrl(options),
|
|
322
|
+
),
|
|
323
|
+
upgradeAccountUrl: $(
|
|
324
|
+
(options?: UpgradeAccountOptions): AuthUrl =>
|
|
325
|
+
getClient(ctx).buildUpgradeAccountUrl(options),
|
|
326
|
+
),
|
|
327
|
+
setupPasskeyUrl: $(
|
|
328
|
+
(options?: SetupPasskeyOptions): AuthUrl =>
|
|
329
|
+
getClient(ctx).buildSetupPasskeyUrl(options),
|
|
330
|
+
),
|
|
331
|
+
setupAddressUrl: $(
|
|
332
|
+
(options?: SetupAddressOptions): AuthUrl =>
|
|
333
|
+
getClient(ctx).buildSetupAddressUrl(options),
|
|
334
|
+
),
|
|
234
335
|
};
|
|
235
336
|
}
|
|
236
337
|
|
|
237
338
|
/** Get session status helpers */
|
|
238
339
|
export function useSessionStatus() {
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
340
|
+
const ctx = useDouveryAuth();
|
|
341
|
+
const { state } = ctx;
|
|
342
|
+
const c = ctx.clientRef.value;
|
|
343
|
+
const isExpired = useSignal(c ? c.isSessionExpired() : false);
|
|
344
|
+
const needsVerification = useSignal(c ? c.needsEmailVerification() : false);
|
|
345
|
+
const isGuest = useSignal(c ? c.isGuestAccount() : false);
|
|
243
346
|
|
|
244
347
|
useTask$(({ track }) => {
|
|
245
348
|
track(() => state.value);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
349
|
+
const client = ctx.clientRef.value;
|
|
350
|
+
if (client) {
|
|
351
|
+
isExpired.value = client.isSessionExpired();
|
|
352
|
+
needsVerification.value = client.needsEmailVerification();
|
|
353
|
+
isGuest.value = client.isGuestAccount();
|
|
354
|
+
}
|
|
249
355
|
});
|
|
250
356
|
|
|
251
357
|
return { isExpired, needsVerification, isGuest };
|
|
252
358
|
}
|
|
253
359
|
|
|
360
|
+
// ============================================================================
|
|
361
|
+
// App User hooks
|
|
362
|
+
// ============================================================================
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Returns the application-specific user data provided via `appUser` prop.
|
|
366
|
+
* Cast to your app's user type: `const user = useAppUser<UserACC>()`.
|
|
367
|
+
* Returns `{ user: Signal<T | null>, isAuthenticated: Signal<boolean> }`.
|
|
368
|
+
*/
|
|
369
|
+
export function useAppUser<T = unknown>() {
|
|
370
|
+
const { appUser, appUserAuthenticated } = useDouveryAuth();
|
|
371
|
+
return {
|
|
372
|
+
user: appUser as Signal<T | null>,
|
|
373
|
+
isAuthenticated: appUserAuthenticated,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Full app user context with refresh capabilities.
|
|
379
|
+
* Use when you need to re-fetch user data from the server.
|
|
380
|
+
*/
|
|
381
|
+
export function useAppUserActions<T = unknown>() {
|
|
382
|
+
const { appUser, appUserAuthenticated } = useDouveryAuth();
|
|
383
|
+
|
|
384
|
+
const updateUser = $((userData: T | null) => {
|
|
385
|
+
(appUser as Signal<T | null>).value = userData;
|
|
386
|
+
appUserAuthenticated.value = !!userData;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const refreshUser = $(async () => {
|
|
390
|
+
try {
|
|
391
|
+
const response = await fetch("/api/auth/me", {
|
|
392
|
+
method: "GET",
|
|
393
|
+
credentials: "include",
|
|
394
|
+
headers: { "Cache-Control": "no-cache", Pragma: "no-cache" },
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (response.ok) {
|
|
398
|
+
const data = await response.json();
|
|
399
|
+
(appUser as Signal<T | null>).value = data.user;
|
|
400
|
+
appUserAuthenticated.value = true;
|
|
401
|
+
} else if (response.status === 401 || response.status === 403) {
|
|
402
|
+
(appUser as Signal<T | null>).value = null;
|
|
403
|
+
appUserAuthenticated.value = false;
|
|
404
|
+
}
|
|
405
|
+
} catch {
|
|
406
|
+
// Network error: keep current state
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
user: appUser as Signal<T | null>,
|
|
412
|
+
isAuthenticated: appUserAuthenticated,
|
|
413
|
+
updateUser,
|
|
414
|
+
refreshUser,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ============================================================================
|
|
419
|
+
// Re-exports
|
|
420
|
+
// ============================================================================
|
|
421
|
+
|
|
254
422
|
export { DouveryAuthClient, createDouveryAuth } from "@douvery/auth";
|
|
255
423
|
export type {
|
|
256
424
|
DouveryAuthConfig,
|