better-auth 1.6.16 → 1.6.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/dist/api/index.d.mts +2 -2
- package/dist/api/index.mjs +3 -4
- package/dist/api/middlewares/origin-check.mjs +5 -1
- package/dist/api/rate-limiter/index.mjs +259 -73
- package/dist/api/routes/account.mjs +22 -7
- package/dist/api/routes/callback.mjs +2 -2
- package/dist/api/routes/index.d.mts +1 -1
- package/dist/api/routes/password.mjs +3 -4
- package/dist/api/routes/session.d.mts +12 -1
- package/dist/api/routes/session.mjs +13 -1
- package/dist/api/routes/sign-in.mjs +5 -5
- package/dist/api/routes/sign-up.mjs +2 -2
- package/dist/api/routes/update-session.mjs +2 -3
- package/dist/api/routes/update-user.mjs +10 -12
- package/dist/auth/base.mjs +11 -7
- package/dist/client/equality.d.mts +19 -0
- package/dist/client/equality.mjs +42 -0
- package/dist/client/index.d.mts +5 -4
- package/dist/client/index.mjs +2 -1
- package/dist/client/path-to-object.d.mts +5 -2
- package/dist/client/plugins/index.d.mts +4 -1
- package/dist/client/plugins/index.mjs +4 -1
- package/dist/client/query.d.mts +4 -3
- package/dist/client/query.mjs +27 -17
- package/dist/client/session-atom.mjs +129 -4
- package/dist/client/session-refresh.d.mts +3 -18
- package/dist/client/session-refresh.mjs +38 -49
- package/dist/client/types.d.mts +2 -2
- package/dist/context/create-context.mjs +2 -1
- package/dist/context/store-capabilities.mjs +12 -0
- package/dist/cookies/index.mjs +25 -2
- package/dist/db/internal-adapter.mjs +51 -0
- package/dist/package.mjs +1 -1
- package/dist/plugins/access/access.mjs +49 -19
- package/dist/plugins/admin/routes.mjs +10 -3
- package/dist/plugins/captcha/constants.mjs +8 -1
- package/dist/plugins/captcha/index.mjs +8 -2
- package/dist/plugins/captcha/types.d.mts +21 -0
- package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -0
- package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +7 -2
- package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +7 -2
- package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -0
- package/dist/plugins/device-authorization/routes.mjs +16 -9
- package/dist/plugins/email-otp/routes.mjs +22 -52
- package/dist/plugins/generic-oauth/index.mjs +7 -2
- package/dist/plugins/generic-oauth/routes.mjs +16 -12
- package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
- package/dist/plugins/haveibeenpwned/index.mjs +5 -1
- package/dist/plugins/index.d.mts +5 -1
- package/dist/plugins/index.mjs +4 -1
- package/dist/plugins/jwt/index.mjs +2 -2
- package/dist/plugins/mcp/client/index.mjs +1 -0
- package/dist/plugins/mcp/index.mjs +8 -0
- package/dist/plugins/multi-session/index.mjs +7 -5
- package/dist/plugins/oauth-popup/client.d.mts +82 -0
- package/dist/plugins/oauth-popup/client.mjs +203 -0
- package/dist/plugins/oauth-popup/constants.d.mts +11 -0
- package/dist/plugins/oauth-popup/constants.mjs +11 -0
- package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
- package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
- package/dist/plugins/oauth-popup/index.d.mts +67 -0
- package/dist/plugins/oauth-popup/index.mjs +227 -0
- package/dist/plugins/oauth-popup/types.d.mts +30 -0
- package/dist/plugins/oauth-proxy/index.mjs +2 -2
- package/dist/plugins/oauth-proxy/utils.mjs +16 -2
- package/dist/plugins/oidc-provider/index.mjs +10 -0
- package/dist/plugins/one-tap/client.mjs +12 -6
- package/dist/plugins/one-tap/index.d.mts +1 -0
- package/dist/plugins/one-tap/index.mjs +9 -5
- package/dist/plugins/one-time-token/index.mjs +1 -3
- package/dist/plugins/open-api/generator.mjs +7 -4
- package/dist/plugins/organization/adapter.d.mts +29 -1
- package/dist/plugins/organization/adapter.mjs +66 -6
- package/dist/plugins/organization/routes/crud-invites.mjs +49 -34
- package/dist/plugins/organization/routes/crud-members.mjs +42 -6
- package/dist/plugins/organization/routes/crud-team.mjs +36 -3
- package/dist/plugins/phone-number/routes.mjs +41 -36
- package/dist/plugins/siwe/index.mjs +2 -3
- package/dist/plugins/two-factor/backup-codes/index.mjs +1 -1
- package/dist/plugins/two-factor/otp/index.mjs +11 -13
- package/dist/plugins/two-factor/totp/index.mjs +1 -1
- package/dist/plugins/two-factor/verify-two-factor.mjs +6 -2
- package/dist/plugins/username/index.mjs +6 -6
- package/package.json +9 -9
|
@@ -80,7 +80,7 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
|
|
|
80
80
|
}
|
|
81
81
|
const { token, nonce } = c.body.idToken;
|
|
82
82
|
if (!await provider.verifyIdToken(token, nonce)) {
|
|
83
|
-
c.context.logger.
|
|
83
|
+
c.context.logger.warn("Invalid id token", { provider: c.body.provider });
|
|
84
84
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
|
|
85
85
|
}
|
|
86
86
|
const userInfo = await provider.getUserInfo({
|
|
@@ -205,26 +205,26 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
|
|
|
205
205
|
const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
|
|
206
206
|
if (!user) {
|
|
207
207
|
await ctx.context.password.hash(password);
|
|
208
|
-
ctx.context.logger.
|
|
208
|
+
ctx.context.logger.warn("User not found");
|
|
209
209
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
|
|
210
210
|
}
|
|
211
211
|
const credentialAccount = user.accounts.find((a) => a.providerId === "credential");
|
|
212
212
|
if (!credentialAccount) {
|
|
213
213
|
await ctx.context.password.hash(password);
|
|
214
|
-
ctx.context.logger.
|
|
214
|
+
ctx.context.logger.warn("Credential account not found");
|
|
215
215
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
|
|
216
216
|
}
|
|
217
217
|
const currentPassword = credentialAccount?.password;
|
|
218
218
|
if (!currentPassword) {
|
|
219
219
|
await ctx.context.password.hash(password);
|
|
220
|
-
ctx.context.logger.
|
|
220
|
+
ctx.context.logger.warn("Password not found");
|
|
221
221
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
|
|
222
222
|
}
|
|
223
223
|
if (!await ctx.context.password.verify({
|
|
224
224
|
hash: currentPassword,
|
|
225
225
|
password
|
|
226
226
|
})) {
|
|
227
|
-
ctx.context.logger.
|
|
227
|
+
ctx.context.logger.warn("Invalid password");
|
|
228
228
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
|
|
229
229
|
}
|
|
230
230
|
if (ctx.context.options?.emailAndPassword?.requireEmailVerification && !user.user.emailVerified) {
|
|
@@ -150,12 +150,12 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
|
|
|
150
150
|
if (!password || typeof password !== "string") throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
|
|
151
151
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
|
152
152
|
if (password.length < minPasswordLength) {
|
|
153
|
-
ctx.context.logger.
|
|
153
|
+
ctx.context.logger.warn("Password is too short");
|
|
154
154
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
|
|
155
155
|
}
|
|
156
156
|
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
|
157
157
|
if (password.length > maxPasswordLength) {
|
|
158
|
-
ctx.context.logger.
|
|
158
|
+
ctx.context.logger.warn("Password is too long");
|
|
159
159
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
|
|
160
160
|
}
|
|
161
161
|
const shouldReturnGenericDuplicateResponse = ctx.context.options.emailAndPassword.requireEmailVerification || ctx.context.options.emailAndPassword.autoSignIn === false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseSessionInput, parseSessionOutput } from "../../db/schema.mjs";
|
|
2
2
|
import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
|
|
3
|
-
import { sessionMiddleware } from "./session.mjs";
|
|
3
|
+
import { isStateful, sessionMiddleware } from "./session.mjs";
|
|
4
4
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
5
5
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
6
6
|
import * as z from "zod";
|
|
@@ -38,8 +38,7 @@ const updateSession = () => createAuthEndpoint("/update-session", {
|
|
|
38
38
|
...additionalFields,
|
|
39
39
|
updatedAt: /* @__PURE__ */ new Date()
|
|
40
40
|
});
|
|
41
|
-
|
|
42
|
-
if (!updatedSession && isStateful) {
|
|
41
|
+
if (!updatedSession && isStateful(ctx)) {
|
|
43
42
|
deleteSessionCookie(ctx);
|
|
44
43
|
throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
|
|
45
44
|
}
|
|
@@ -2,7 +2,7 @@ import { originCheck } from "../middlewares/origin-check.mjs";
|
|
|
2
2
|
import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
|
|
3
3
|
import { generateRandomString } from "../../crypto/random.mjs";
|
|
4
4
|
import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
|
|
5
|
-
import { getSessionFromCtx, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
|
|
5
|
+
import { getSessionFromCtx, isStateful, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
|
|
6
6
|
import { createEmailVerificationToken } from "./email-verification.mjs";
|
|
7
7
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
8
8
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
@@ -150,12 +150,12 @@ const changePassword = createAuthEndpoint("/change-password", {
|
|
|
150
150
|
const session = ctx.context.session;
|
|
151
151
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
|
152
152
|
if (newPassword.length < minPasswordLength) {
|
|
153
|
-
ctx.context.logger.
|
|
153
|
+
ctx.context.logger.warn("Password is too short");
|
|
154
154
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
|
|
155
155
|
}
|
|
156
156
|
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
|
157
157
|
if (newPassword.length > maxPasswordLength) {
|
|
158
|
-
ctx.context.logger.
|
|
158
|
+
ctx.context.logger.warn("Password is too long");
|
|
159
159
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
|
|
160
160
|
}
|
|
161
161
|
const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
|
|
@@ -182,7 +182,7 @@ const changePassword = createAuthEndpoint("/change-password", {
|
|
|
182
182
|
user: parseUserOutput(ctx.context.options, session.user)
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
|
-
const setPassword = createAuthEndpoint({
|
|
185
|
+
const setPassword = createAuthEndpoint.serverOnly({
|
|
186
186
|
method: "POST",
|
|
187
187
|
body: z.object({ newPassword: z.string().meta({ description: "The new password to set is required" }) }),
|
|
188
188
|
use: [sensitiveSessionMiddleware]
|
|
@@ -191,12 +191,12 @@ const setPassword = createAuthEndpoint({
|
|
|
191
191
|
const session = ctx.context.session;
|
|
192
192
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
|
193
193
|
if (newPassword.length < minPasswordLength) {
|
|
194
|
-
ctx.context.logger.
|
|
194
|
+
ctx.context.logger.warn("Password is too short");
|
|
195
195
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
|
|
196
196
|
}
|
|
197
197
|
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
|
198
198
|
if (newPassword.length > maxPasswordLength) {
|
|
199
|
-
ctx.context.logger.
|
|
199
|
+
ctx.context.logger.warn("Password is too long");
|
|
200
200
|
throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
|
|
201
201
|
}
|
|
202
202
|
const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
|
|
@@ -354,17 +354,15 @@ const deleteUserCallback = createAuthEndpoint("/delete-user/callback", {
|
|
|
354
354
|
code: "NOT_FOUND"
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
|
-
const session = await getSessionFromCtx(ctx);
|
|
357
|
+
const session = await getSessionFromCtx(ctx, { disableCookieCache: isStateful(ctx) });
|
|
358
358
|
if (!session) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO);
|
|
359
|
-
const token = await ctx.context.internalAdapter.
|
|
360
|
-
if (!token || token.
|
|
361
|
-
if (token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
|
|
359
|
+
const token = await ctx.context.internalAdapter.consumeVerificationValue(`delete-account-${ctx.query.token}`);
|
|
360
|
+
if (!token || token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
|
|
362
361
|
const beforeDelete = ctx.context.options.user.deleteUser?.beforeDelete;
|
|
363
362
|
if (beforeDelete) await beforeDelete(session.user, ctx.request);
|
|
364
363
|
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
|
365
364
|
await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
|
|
366
365
|
await ctx.context.internalAdapter.deleteAccounts(session.user.id);
|
|
367
|
-
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`delete-account-${ctx.query.token}`);
|
|
368
366
|
deleteSessionCookie(ctx);
|
|
369
367
|
const afterDelete = ctx.context.options.user.deleteUser?.afterDelete;
|
|
370
368
|
if (afterDelete) await afterDelete(session.user, ctx.request);
|
|
@@ -414,7 +412,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
|
|
|
414
412
|
}
|
|
415
413
|
const newEmail = ctx.body.newEmail.toLowerCase();
|
|
416
414
|
if (newEmail === ctx.context.session.user.email) {
|
|
417
|
-
ctx.context.logger.
|
|
415
|
+
ctx.context.logger.warn("Email is the same");
|
|
418
416
|
throw APIError.fromStatus("BAD_REQUEST", { message: "Email is the same" });
|
|
419
417
|
}
|
|
420
418
|
/**
|
package/dist/auth/base.mjs
CHANGED
|
@@ -14,16 +14,20 @@ const createBetterAuth = (options, initFn) => {
|
|
|
14
14
|
let handlerCtx;
|
|
15
15
|
if (isDynamicBaseURLConfig(options.baseURL)) handlerCtx = await resolveRequestContext(ctx, request, resolveDynamicTrustedProxyHeaders(ctx.options));
|
|
16
16
|
else {
|
|
17
|
-
handlerCtx = ctx;
|
|
17
|
+
handlerCtx = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
|
|
18
|
+
let trustOptions = ctx.options;
|
|
18
19
|
if (!ctx.options.baseURL) {
|
|
19
20
|
const baseURL = getBaseURL(void 0, basePath, request, void 0, ctx.options.advanced?.trustedProxyHeaders);
|
|
20
|
-
if (baseURL)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (!baseURL) throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
|
|
22
|
+
handlerCtx.baseURL = baseURL;
|
|
23
|
+
handlerCtx.options = {
|
|
24
|
+
...ctx.options,
|
|
25
|
+
baseURL: getOrigin(baseURL) || void 0
|
|
26
|
+
};
|
|
27
|
+
trustOptions = handlerCtx.options;
|
|
24
28
|
}
|
|
25
|
-
handlerCtx.trustedOrigins = await getTrustedOrigins(
|
|
26
|
-
handlerCtx.trustedProviders = await getTrustedProviders(
|
|
29
|
+
handlerCtx.trustedOrigins = await getTrustedOrigins(trustOptions, request);
|
|
30
|
+
handlerCtx.trustedProviders = await getTrustedProviders(trustOptions, request);
|
|
27
31
|
}
|
|
28
32
|
const { handler } = router(handlerCtx, options);
|
|
29
33
|
return runWithAdapter(handlerCtx.adapter, () => handler(request));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Store, StoreValue } from "nanostores";
|
|
2
|
+
|
|
3
|
+
//#region src/client/equality.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Deep structural equality for JSON-serializable values.
|
|
6
|
+
* Handles: primitives, null, arrays, and plain objects.
|
|
7
|
+
* Short-circuits on referential equality at every recursion level.
|
|
8
|
+
*/
|
|
9
|
+
declare function isJsonEqual(a: unknown, b: unknown): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Attach an equality gate to a nanostores atom via `onSet`.
|
|
12
|
+
* When `isEqual(currentValue, newValue)` returns true, the `set()` call
|
|
13
|
+
* is aborted: no listeners fire, no framework re-renders occur.
|
|
14
|
+
*
|
|
15
|
+
* Returns the unsubscribe function from `onSet`.
|
|
16
|
+
*/
|
|
17
|
+
declare function withEquality<S extends Store>(store: S, isEqual: (a: StoreValue<S>, b: StoreValue<S>) => boolean): () => void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { isJsonEqual, withEquality };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { onSet } from "nanostores";
|
|
2
|
+
//#region src/client/equality.ts
|
|
3
|
+
function isPlainObject(value) {
|
|
4
|
+
if (typeof value !== "object" || value === null) return false;
|
|
5
|
+
const prototype = Object.getPrototypeOf(value);
|
|
6
|
+
return prototype === Object.prototype || prototype === null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Deep structural equality for JSON-serializable values.
|
|
10
|
+
* Handles: primitives, null, arrays, and plain objects.
|
|
11
|
+
* Short-circuits on referential equality at every recursion level.
|
|
12
|
+
*/
|
|
13
|
+
function isJsonEqual(a, b) {
|
|
14
|
+
if (a === b) return true;
|
|
15
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
16
|
+
if (a.length !== b.length) return false;
|
|
17
|
+
for (let i = 0; i < a.length; i++) if (!isJsonEqual(a[i], b[i])) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
21
|
+
const keysA = Object.keys(a);
|
|
22
|
+
const keysB = Object.keys(b);
|
|
23
|
+
if (keysA.length !== keysB.length) return false;
|
|
24
|
+
for (const key of keysA) if (!(key in b) || !isJsonEqual(a[key], b[key])) return false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Attach an equality gate to a nanostores atom via `onSet`.
|
|
31
|
+
* When `isEqual(currentValue, newValue)` returns true, the `set()` call
|
|
32
|
+
* is aborted: no listeners fire, no framework re-renders occur.
|
|
33
|
+
*
|
|
34
|
+
* Returns the unsubscribe function from `onSet`.
|
|
35
|
+
*/
|
|
36
|
+
function withEquality(store, isEqual) {
|
|
37
|
+
return onSet(store, ({ newValue, abort }) => {
|
|
38
|
+
if (isEqual(store.value, newValue)) abort();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { isJsonEqual, withEquality };
|
package/dist/client/index.d.mts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../types/helper.mjs";
|
|
2
|
-
import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
|
|
2
|
+
import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
|
|
3
3
|
import { BetterAuthClientOptions, BetterAuthClientPlugin, ClientAtomListener, ClientStore, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferSessionFromClient, InferUserFromClient, IsSignal, SessionQueryParams } from "./types.mjs";
|
|
4
4
|
import { BroadcastChannel, BroadcastListener, BroadcastMessage, getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
|
|
5
|
+
import { isJsonEqual, withEquality } from "./equality.mjs";
|
|
5
6
|
import { FocusListener, FocusManager, kFocusManager } from "./focus-manager.mjs";
|
|
6
7
|
import { OnlineListener, OnlineManager, kOnlineManager } from "./online-manager.mjs";
|
|
7
8
|
import { parseJSON } from "./parser.mjs";
|
|
8
|
-
import { AuthQueryAtom, useAuthQuery } from "./query.mjs";
|
|
9
|
-
import { SessionRefreshOptions,
|
|
9
|
+
import { AuthQueryAtom, AuthQueryState, useAuthQuery } from "./query.mjs";
|
|
10
|
+
import { SessionRefreshOptions, createSessionRefreshManager } from "./session-refresh.mjs";
|
|
10
11
|
import { AuthClient, createAuthClient } from "./vanilla.mjs";
|
|
11
12
|
import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "../plugins/access/types.mjs";
|
|
12
13
|
import { AuthorizeResponse, createAccessControl, role } from "../plugins/access/access.mjs";
|
|
@@ -31,4 +32,4 @@ declare function InferAuth<O extends {
|
|
|
31
32
|
options: BetterAuthOptions;
|
|
32
33
|
}>(): O["options"];
|
|
33
34
|
//#endregion
|
|
34
|
-
export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions,
|
|
35
|
+
export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthQueryState, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSessionUpdateCtx, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions, Statements, StripEmptyObjects, SubArray, Subset, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, type UnionToIntersection, createAccessControl, createAuthClient, createSessionRefreshManager, defaultRolesSchema, getGlobalBroadcastChannel, getOrgAdapter, hasPermission, invitationSchema, invitationStatus, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, memberSchema, organization, organizationRoleSchema, organizationSchema, parseJSON, parseRoles, role, roleSchema, teamMemberSchema, teamSchema, useAuthQuery, withEquality };
|
package/dist/client/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PACKAGE_VERSION } from "../version.mjs";
|
|
2
2
|
import { getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
|
|
3
|
+
import { isJsonEqual, withEquality } from "./equality.mjs";
|
|
3
4
|
import { kFocusManager } from "./focus-manager.mjs";
|
|
4
5
|
import { kOnlineManager } from "./online-manager.mjs";
|
|
5
6
|
import { parseJSON } from "./parser.mjs";
|
|
@@ -18,4 +19,4 @@ function InferAuth() {
|
|
|
18
19
|
return {};
|
|
19
20
|
}
|
|
20
21
|
//#endregion
|
|
21
|
-
export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery };
|
|
22
|
+
export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery, withEquality };
|
|
@@ -30,6 +30,9 @@ type InferUserUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions
|
|
|
30
30
|
name?: string | undefined;
|
|
31
31
|
fetchOptions?: FetchOptions | undefined;
|
|
32
32
|
} & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "user", "input">>>;
|
|
33
|
+
type InferSessionUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions extends ClientFetchOption> = {
|
|
34
|
+
fetchOptions?: FetchOptions | undefined;
|
|
35
|
+
} & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "session", "input">>>;
|
|
33
36
|
type InferCtxQuery<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = C["query"] extends Record<string, any> ? {
|
|
34
37
|
query: C["query"];
|
|
35
38
|
fetchOptions?: FetchOptions | undefined;
|
|
@@ -51,7 +54,7 @@ type InferRoute<API, COpts extends BetterAuthClientOptions> = API extends Record
|
|
|
51
54
|
scope: "http";
|
|
52
55
|
} | {
|
|
53
56
|
scope: "server";
|
|
54
|
-
} ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
|
|
57
|
+
} ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : T["path"] extends `/update-session` ? InferSessionUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
|
|
55
58
|
CUSTOM_SESSION: boolean;
|
|
56
59
|
} ? MergeCustomSessionWithInferred<NonNullable<Awaited<R>>, COpts> : T["path"] extends "/get-session" ? {
|
|
57
60
|
user: InferUserFromClient<COpts>;
|
|
@@ -69,4 +72,4 @@ type ProxyRequest = {
|
|
|
69
72
|
[key: string]: any;
|
|
70
73
|
};
|
|
71
74
|
//#endregion
|
|
72
|
-
export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
|
|
75
|
+
export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
|
|
@@ -19,6 +19,8 @@ import { JWKOptions, JWSAlgorithms, Jwk, JwtOptions } from "../../plugins/jwt/ty
|
|
|
19
19
|
import { AuthorizationQuery, Client, CodeVerificationValue, OAuthAccessToken, OIDCMetadata, OIDCOptions, TokenBody } from "../../plugins/oidc-provider/types.mjs";
|
|
20
20
|
import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
|
|
21
21
|
import { MultiSessionConfig } from "../../plugins/multi-session/index.mjs";
|
|
22
|
+
import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
|
|
23
|
+
import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
|
|
22
24
|
import { OneTimeTokenOptions } from "../../plugins/one-time-token/index.mjs";
|
|
23
25
|
import { PhoneNumberOptions, UserWithPhoneNumber } from "../../plugins/phone-number/types.mjs";
|
|
24
26
|
import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "../../plugins/two-factor/backup-codes/index.mjs";
|
|
@@ -44,6 +46,7 @@ import { jwtClient } from "../../plugins/jwt/client.mjs";
|
|
|
44
46
|
import { LastLoginMethodClientConfig, lastLoginMethodClient } from "../../plugins/last-login-method/client.mjs";
|
|
45
47
|
import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
|
|
46
48
|
import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
|
|
49
|
+
import { SignInPopupOptions, SignInPopupResult, createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
|
|
47
50
|
import { OidcClientPlugin, oidcClient } from "../../plugins/oidc-provider/client.mjs";
|
|
48
51
|
import { GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, oneTapClient } from "../../plugins/one-tap/client.mjs";
|
|
49
52
|
import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
|
|
@@ -53,4 +56,4 @@ import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
|
|
|
53
56
|
import { siweClient } from "../../plugins/siwe/client.mjs";
|
|
54
57
|
import { usernameClient } from "../../plugins/username/client.mjs";
|
|
55
58
|
import { InferServerPlugin } from "./infer-plugin.mjs";
|
|
56
|
-
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
|
|
59
|
+
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAUTH_POPUP_ERROR_CODES, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SignInPopupOptions, SignInPopupResult, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, createSignInPopup, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, getStoredPopupToken, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oauthPopupClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, popupBearerFetchPlugin, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
|
|
@@ -14,6 +14,9 @@ import { lastLoginMethodClient } from "../../plugins/last-login-method/client.mj
|
|
|
14
14
|
import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
|
|
15
15
|
import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
|
|
16
16
|
import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
|
|
17
|
+
import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
|
|
18
|
+
import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
|
|
19
|
+
import { createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
|
|
17
20
|
import { oidcClient } from "../../plugins/oidc-provider/client.mjs";
|
|
18
21
|
import { oneTapClient } from "../../plugins/one-tap/client.mjs";
|
|
19
22
|
import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
|
|
@@ -27,4 +30,4 @@ import { twoFactorClient } from "../../plugins/two-factor/client.mjs";
|
|
|
27
30
|
import { USERNAME_ERROR_CODES } from "../../plugins/username/error-codes.mjs";
|
|
28
31
|
import { usernameClient } from "../../plugins/username/client.mjs";
|
|
29
32
|
import { InferServerPlugin } from "./infer-plugin.mjs";
|
|
30
|
-
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient };
|
|
33
|
+
export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, OAUTH_POPUP_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, createSignInPopup, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, getStoredPopupToken, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oauthPopupClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, popupBearerFetchPlugin, siweClient, twoFactorClient, usernameClient };
|
package/dist/client/query.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ import { PreinitializedWritableAtom } from "nanostores";
|
|
|
4
4
|
import { BetterFetch, BetterFetchError } from "@better-fetch/fetch";
|
|
5
5
|
|
|
6
6
|
//#region src/client/query.d.ts
|
|
7
|
-
type
|
|
7
|
+
type AuthQueryState<T> = {
|
|
8
8
|
data: null | T;
|
|
9
9
|
error: null | BetterFetchError;
|
|
10
10
|
isPending: boolean;
|
|
@@ -12,11 +12,12 @@ type AuthQueryAtom<T> = PreinitializedWritableAtom<{
|
|
|
12
12
|
refetch: (queryParams?: {
|
|
13
13
|
query?: SessionQueryParams;
|
|
14
14
|
} | undefined) => Promise<void>;
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
|
+
type AuthQueryAtom<T> = PreinitializedWritableAtom<AuthQueryState<T>>;
|
|
16
17
|
declare const useAuthQuery: <T>(initializedAtom: PreinitializedWritableAtom<any> | PreinitializedWritableAtom<any>[], path: string, $fetch: BetterFetch, options?: (((value: {
|
|
17
18
|
data: null | T;
|
|
18
19
|
error: null | BetterFetchError;
|
|
19
20
|
isPending: boolean;
|
|
20
21
|
}) => ClientFetchOption) | ClientFetchOption) | undefined) => AuthQueryAtom<T>;
|
|
21
22
|
//#endregion
|
|
22
|
-
export { AuthQueryAtom, useAuthQuery };
|
|
23
|
+
export { AuthQueryAtom, AuthQueryState, useAuthQuery };
|
package/dist/client/query.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { isJsonEqual, withEquality } from "./equality.mjs";
|
|
1
2
|
import { atom, onMount } from "nanostores";
|
|
2
3
|
//#region src/client/query.ts
|
|
3
4
|
const isServer = () => typeof window === "undefined";
|
|
5
|
+
function isAuthQueryStateEqual(a, b) {
|
|
6
|
+
return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
|
|
7
|
+
}
|
|
4
8
|
const useAuthQuery = (initializedAtom, path, $fetch, options) => {
|
|
5
9
|
const value = atom({
|
|
6
10
|
data: null,
|
|
@@ -9,6 +13,7 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
|
|
|
9
13
|
isRefetching: false,
|
|
10
14
|
refetch: (queryParams) => fn(queryParams)
|
|
11
15
|
});
|
|
16
|
+
withEquality(value, isAuthQueryStateEqual);
|
|
12
17
|
const fn = async (queryParams) => {
|
|
13
18
|
return new Promise((resolve) => {
|
|
14
19
|
const opts = typeof options === "function" ? options({
|
|
@@ -23,8 +28,10 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
|
|
|
23
28
|
...queryParams?.query
|
|
24
29
|
},
|
|
25
30
|
async onSuccess(context) {
|
|
31
|
+
const current = value.get();
|
|
32
|
+
const stableData = current.data != null && context.data != null && isJsonEqual(current.data, context.data) ? current.data : context.data;
|
|
26
33
|
value.set({
|
|
27
|
-
data:
|
|
34
|
+
data: stableData,
|
|
28
35
|
error: null,
|
|
29
36
|
isPending: false,
|
|
30
37
|
isRefetching: false,
|
|
@@ -73,23 +80,26 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
|
|
|
73
80
|
};
|
|
74
81
|
initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
|
|
75
82
|
let isInitialized = false;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
const cleanups = [];
|
|
84
|
+
for (const initAtom of initializedAtom) {
|
|
85
|
+
const unbind = initAtom.subscribe(async () => {
|
|
86
|
+
if (isServer()) return;
|
|
87
|
+
if (isInitialized) await fn();
|
|
88
|
+
else onMount(value, () => {
|
|
89
|
+
const timeoutId = setTimeout(async () => {
|
|
90
|
+
if (!isInitialized) {
|
|
91
|
+
isInitialized = true;
|
|
92
|
+
await fn();
|
|
93
|
+
}
|
|
94
|
+
}, 0);
|
|
95
|
+
return () => {
|
|
96
|
+
for (const u of cleanups) u();
|
|
97
|
+
clearTimeout(timeoutId);
|
|
98
|
+
};
|
|
99
|
+
});
|
|
91
100
|
});
|
|
92
|
-
|
|
101
|
+
cleanups.push(unbind);
|
|
102
|
+
}
|
|
93
103
|
return value;
|
|
94
104
|
};
|
|
95
105
|
//#endregion
|
|
@@ -1,21 +1,146 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isJsonEqual, withEquality } from "./equality.mjs";
|
|
2
2
|
import { createSessionRefreshManager } from "./session-refresh.mjs";
|
|
3
3
|
import { atom, onMount } from "nanostores";
|
|
4
4
|
//#region src/client/session-atom.ts
|
|
5
|
+
const isServer = () => typeof window === "undefined";
|
|
6
|
+
/**
|
|
7
|
+
* Normalize $fetch response: `throw: true` returns data directly,
|
|
8
|
+
* otherwise `{ data, error }`.
|
|
9
|
+
*/
|
|
10
|
+
function normalizeSessionResponse(res) {
|
|
11
|
+
if (typeof res === "object" && res !== null && "data" in res && "error" in res) return res;
|
|
12
|
+
return {
|
|
13
|
+
data: res,
|
|
14
|
+
error: null
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function normalizeSessionData(data) {
|
|
18
|
+
if (!data) return null;
|
|
19
|
+
if (data.session === null && data.user === null) return null;
|
|
20
|
+
return data;
|
|
21
|
+
}
|
|
22
|
+
function isSessionAtomEqual(a, b) {
|
|
23
|
+
return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
|
|
24
|
+
}
|
|
5
25
|
function getSessionAtom($fetch, options) {
|
|
6
26
|
const $signal = atom(false);
|
|
7
|
-
|
|
27
|
+
let abortController;
|
|
28
|
+
const refetch = (queryParams) => fetchSession(queryParams);
|
|
29
|
+
const session = atom({
|
|
30
|
+
data: null,
|
|
31
|
+
error: null,
|
|
32
|
+
isPending: true,
|
|
33
|
+
isRefetching: false,
|
|
34
|
+
refetch
|
|
35
|
+
});
|
|
36
|
+
withEquality(session, isSessionAtomEqual);
|
|
37
|
+
const settleAbortedFetch = (controller) => {
|
|
38
|
+
if (abortController !== controller) return;
|
|
39
|
+
const current = session.get();
|
|
40
|
+
abortController = void 0;
|
|
41
|
+
if (!current.isPending && !current.isRefetching) return;
|
|
42
|
+
session.set({
|
|
43
|
+
...current,
|
|
44
|
+
isPending: false,
|
|
45
|
+
isRefetching: false,
|
|
46
|
+
refetch
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const fetchSession = async (queryParams) => {
|
|
50
|
+
abortController?.abort();
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
abortController = controller;
|
|
53
|
+
const current = session.get();
|
|
54
|
+
session.set({
|
|
55
|
+
...current,
|
|
56
|
+
isPending: current.data === null,
|
|
57
|
+
isRefetching: true,
|
|
58
|
+
error: null,
|
|
59
|
+
refetch
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
const res = await $fetch("/get-session", {
|
|
63
|
+
method: "GET",
|
|
64
|
+
query: queryParams?.query,
|
|
65
|
+
signal: controller.signal
|
|
66
|
+
});
|
|
67
|
+
if (controller.signal.aborted) {
|
|
68
|
+
settleAbortedFetch(controller);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let { data, error } = normalizeSessionResponse(res);
|
|
72
|
+
if (data?.needsRefresh) try {
|
|
73
|
+
const refreshRes = await $fetch("/get-session", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
signal: controller.signal
|
|
76
|
+
});
|
|
77
|
+
if (controller.signal.aborted) {
|
|
78
|
+
settleAbortedFetch(controller);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
({data, error} = normalizeSessionResponse(refreshRes));
|
|
82
|
+
} catch {
|
|
83
|
+
if (controller.signal.aborted) {
|
|
84
|
+
settleAbortedFetch(controller);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (error) {
|
|
89
|
+
const latest = session.get();
|
|
90
|
+
const isUnauthorized = error?.status === 401;
|
|
91
|
+
session.set({
|
|
92
|
+
data: isUnauthorized ? null : latest.data,
|
|
93
|
+
error,
|
|
94
|
+
isPending: false,
|
|
95
|
+
isRefetching: false,
|
|
96
|
+
refetch
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const sessionData = normalizeSessionData(data);
|
|
101
|
+
const current = session.get();
|
|
102
|
+
const stableData = current.data != null && sessionData != null && isJsonEqual(current.data, sessionData) ? current.data : sessionData;
|
|
103
|
+
session.set({
|
|
104
|
+
data: stableData,
|
|
105
|
+
error: null,
|
|
106
|
+
isPending: false,
|
|
107
|
+
isRefetching: false,
|
|
108
|
+
refetch
|
|
109
|
+
});
|
|
110
|
+
} catch (fetchError) {
|
|
111
|
+
if (controller.signal.aborted) {
|
|
112
|
+
settleAbortedFetch(controller);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const latest = session.get();
|
|
116
|
+
session.set({
|
|
117
|
+
data: latest.data,
|
|
118
|
+
error: fetchError,
|
|
119
|
+
isPending: false,
|
|
120
|
+
isRefetching: false,
|
|
121
|
+
refetch
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
8
125
|
let broadcastSessionUpdate = () => {};
|
|
9
126
|
onMount(session, () => {
|
|
127
|
+
let timeoutId;
|
|
128
|
+
if (!isServer()) timeoutId = setTimeout(() => {
|
|
129
|
+
fetchSession();
|
|
130
|
+
}, 0);
|
|
10
131
|
const refreshManager = createSessionRefreshManager({
|
|
11
|
-
|
|
132
|
+
fetchSession,
|
|
133
|
+
shouldPollSession: () => session.get().data != null,
|
|
12
134
|
sessionSignal: $signal,
|
|
13
|
-
$fetch,
|
|
14
135
|
options
|
|
15
136
|
});
|
|
16
137
|
refreshManager.init();
|
|
17
138
|
broadcastSessionUpdate = refreshManager.broadcastSessionUpdate;
|
|
18
139
|
return () => {
|
|
140
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
141
|
+
const controller = abortController;
|
|
142
|
+
controller?.abort();
|
|
143
|
+
if (controller) settleAbortedFetch(controller);
|
|
19
144
|
refreshManager.cleanup();
|
|
20
145
|
};
|
|
21
146
|
});
|