@iqauth/sdk 2.6.4 → 2.7.0
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/README.md +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-XAWYUPMO.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +313 -33
- package/dist/react.mjs +58 -2632
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
package/dist/browser.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as SessionManager } from './signIn-
|
|
2
|
-
export {
|
|
3
|
-
export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-
|
|
4
|
-
export {
|
|
5
|
-
import './types-
|
|
1
|
+
import { S as SessionManager } from './signIn-T-CZ6t6r.mjs';
|
|
2
|
+
export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-T-CZ6t6r.mjs';
|
|
3
|
+
export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.mjs';
|
|
4
|
+
export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
|
|
5
|
+
import './types-XOV9XPVi.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Browser-safe PKCE + state/nonce generation using WebCrypto.
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { S as SessionManager } from './signIn-
|
|
2
|
-
export {
|
|
3
|
-
export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-
|
|
4
|
-
export {
|
|
5
|
-
import './types-
|
|
1
|
+
import { S as SessionManager } from './signIn-BLFnz8SV.js';
|
|
2
|
+
export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-BLFnz8SV.js';
|
|
3
|
+
export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.js';
|
|
4
|
+
export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
|
|
5
|
+
import './types-XOV9XPVi.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Browser-safe PKCE + state/nonce generation using WebCrypto.
|
package/dist/browser.js
CHANGED
|
@@ -444,13 +444,30 @@ __export(browser_exports, {
|
|
|
444
444
|
module.exports = __toCommonJS(browser_exports);
|
|
445
445
|
|
|
446
446
|
// src/errors.ts
|
|
447
|
-
var IQAuthError = class extends Error {
|
|
448
|
-
constructor(code, message, status,
|
|
447
|
+
var IQAuthError = class _IQAuthError extends Error {
|
|
448
|
+
constructor(code, message, status, cause) {
|
|
449
449
|
super(message);
|
|
450
450
|
this.name = "IQAuthError";
|
|
451
451
|
this.code = code;
|
|
452
452
|
this.status = status;
|
|
453
|
-
this.
|
|
453
|
+
this.cause = cause;
|
|
454
|
+
this.raw = cause;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Type guard: true when `value` is an `IQAuthError`. Useful for adapters
|
|
458
|
+
* that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
|
|
459
|
+
*/
|
|
460
|
+
static isIQAuthError(value) {
|
|
461
|
+
return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Type-narrowed code check. Lets callers write
|
|
465
|
+
* `if (err.is("token_expired")) …` with full IntelliSense for the typed
|
|
466
|
+
* taxonomy without losing the ability to handle server codes via
|
|
467
|
+
* `err.code === "TOKEN_REVOKED"`.
|
|
468
|
+
*/
|
|
469
|
+
is(code) {
|
|
470
|
+
return this.code === code;
|
|
454
471
|
}
|
|
455
472
|
};
|
|
456
473
|
var ErrorCodes = {
|
|
@@ -549,14 +566,14 @@ function assertPublishableKey(raw, opts) {
|
|
|
549
566
|
const ctx = opts?.context ? `${opts.context}: ` : "";
|
|
550
567
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
551
568
|
throw new IQAuthError(
|
|
552
|
-
"
|
|
569
|
+
"config_invalid",
|
|
553
570
|
`${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
|
|
554
571
|
);
|
|
555
572
|
}
|
|
556
573
|
const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
|
|
557
574
|
if (!shapeMatch) {
|
|
558
575
|
throw new IQAuthError(
|
|
559
|
-
"
|
|
576
|
+
"config_invalid",
|
|
560
577
|
`${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
|
|
561
578
|
);
|
|
562
579
|
}
|
|
@@ -565,19 +582,19 @@ function assertPublishableKey(raw, opts) {
|
|
|
565
582
|
decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
|
|
566
583
|
} catch {
|
|
567
584
|
throw new IQAuthError(
|
|
568
|
-
"
|
|
585
|
+
"config_invalid",
|
|
569
586
|
`${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
|
|
570
587
|
);
|
|
571
588
|
}
|
|
572
589
|
if (!isPublishableKeyPayload(decoded)) {
|
|
573
590
|
throw new IQAuthError(
|
|
574
|
-
"
|
|
591
|
+
"config_invalid",
|
|
575
592
|
`${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
|
|
576
593
|
);
|
|
577
594
|
}
|
|
578
595
|
if (!isValidIssuerUrl(decoded.iss)) {
|
|
579
596
|
throw new IQAuthError(
|
|
580
|
-
"
|
|
597
|
+
"config_invalid",
|
|
581
598
|
`${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
|
|
582
599
|
);
|
|
583
600
|
}
|
|
@@ -656,6 +673,7 @@ function clearPkce(state) {
|
|
|
656
673
|
}
|
|
657
674
|
|
|
658
675
|
// src/browser/sessionManager.ts
|
|
676
|
+
var PROBE_WAIT_MS = 80;
|
|
659
677
|
var DEFAULT_REFRESH_PATH = "/api/v1/auth/refresh";
|
|
660
678
|
var DEFAULT_USERINFO_PATH = "/api/v1/auth/me";
|
|
661
679
|
async function readAuthErrorCode(res) {
|
|
@@ -693,7 +711,13 @@ function claimsToSessionUser(claims) {
|
|
|
693
711
|
tenantId: claims.tenantId,
|
|
694
712
|
vendorId: claims.vendorId,
|
|
695
713
|
roles: claims.roles ?? [],
|
|
696
|
-
entitlements: claims.entitlements ?? []
|
|
714
|
+
entitlements: claims.entitlements ?? [],
|
|
715
|
+
// SDK 2.7.0 (Task #124) — pass through identity claims when issued.
|
|
716
|
+
...claims.picture !== void 0 ? { picture: claims.picture } : {},
|
|
717
|
+
...claims.email_verified !== void 0 ? { emailVerified: claims.email_verified } : {},
|
|
718
|
+
...claims.given_name !== void 0 ? { givenName: claims.given_name } : {},
|
|
719
|
+
...claims.family_name !== void 0 ? { familyName: claims.family_name } : {},
|
|
720
|
+
...claims.locale !== void 0 ? { locale: claims.locale } : {}
|
|
697
721
|
};
|
|
698
722
|
}
|
|
699
723
|
var EMPTY = {
|
|
@@ -717,11 +741,50 @@ var NO_OP_STORE = {
|
|
|
717
741
|
write: () => void 0,
|
|
718
742
|
clear: () => void 0
|
|
719
743
|
};
|
|
744
|
+
var IDEMPOTENCY_HEADER = "X-IQAuth-Idempotency";
|
|
745
|
+
function randomIdempotencyToken() {
|
|
746
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
747
|
+
const bytes = new Uint8Array(16);
|
|
748
|
+
crypto.getRandomValues(bytes);
|
|
749
|
+
let out = "";
|
|
750
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
751
|
+
return out;
|
|
752
|
+
}
|
|
753
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
754
|
+
}
|
|
720
755
|
var SessionManager = class {
|
|
721
756
|
constructor(options) {
|
|
722
757
|
this.snapshot = { ...EMPTY };
|
|
723
758
|
this.listeners = /* @__PURE__ */ new Set();
|
|
724
759
|
this.refreshPromise = null;
|
|
760
|
+
/**
|
|
761
|
+
* Cancellation handle for the in-flight refresh, if any. `signOut()` (or a
|
|
762
|
+
* `session:signout` broadcast from another tab) calls `abort()` so the
|
|
763
|
+
* refresh response is dropped before it can write a fresh access cookie
|
|
764
|
+
* on top of the just-cleared session — the second root cause of "ghost
|
|
765
|
+
* signed-in" sessions after Sign Out.
|
|
766
|
+
*/
|
|
767
|
+
this.refreshAbort = null;
|
|
768
|
+
/**
|
|
769
|
+
* Set to `true` by `signOut()` / `signOutLocal()` for the lifetime of the
|
|
770
|
+
* call. Used as a safety belt: even if a refresh response arrives while
|
|
771
|
+
* `refreshAbort` was unable to interrupt the network call (e.g. the body
|
|
772
|
+
* was already streaming back), `runRefresh` checks this flag before
|
|
773
|
+
* mutating session state and bails out.
|
|
774
|
+
*/
|
|
775
|
+
this.signoutInProgress = false;
|
|
776
|
+
/**
|
|
777
|
+
* Per-session opaque idempotency token. Sent as `X-IQAuth-Idempotency` on
|
|
778
|
+
* every /refresh and /signout request the SDK makes through a framework
|
|
779
|
+
* adapter (Express/Fastify/Hono/Next), so the adapter's `SignoutRegistry`
|
|
780
|
+
* can collapse a refresh that lands moments after a signout — even when
|
|
781
|
+
* the two requests are routed to different server instances (multi-replica
|
|
782
|
+
* deployments).
|
|
783
|
+
*
|
|
784
|
+
* Generated lazily on first use, rotated on signout so the next session
|
|
785
|
+
* starts with a fresh token. Opaque random — never the raw refresh token.
|
|
786
|
+
*/
|
|
787
|
+
this.idempotencyToken = null;
|
|
725
788
|
this.channel = null;
|
|
726
789
|
this.proactiveTimer = null;
|
|
727
790
|
this.bootstrapped = false;
|
|
@@ -729,6 +792,8 @@ var SessionManager = class {
|
|
|
729
792
|
this.remoteRefreshWaiters = [];
|
|
730
793
|
/** Active claims by other tabs (keyed by source tabId). */
|
|
731
794
|
this.foreignClaim = null;
|
|
795
|
+
/** Resolver for an in-flight cross-tab `session:probe`, set during bootstrap. */
|
|
796
|
+
this.probeResolver = null;
|
|
732
797
|
const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
|
|
733
798
|
this.key = parsed;
|
|
734
799
|
const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
|
|
@@ -741,6 +806,8 @@ var SessionManager = class {
|
|
|
741
806
|
this.refreshCookieName = options.cookieNames?.refresh ?? REFRESH_COOKIE;
|
|
742
807
|
this.tokenStore = options.tokenStore ?? (this.serverManagedSession ? NO_OP_STORE : this.useCookies ? defaultCookieStore(this.refreshCookieName) : NO_OP_STORE);
|
|
743
808
|
this.crossTabLockTimeoutMs = options.crossTabLockTimeoutMs ?? 4e3;
|
|
809
|
+
this.debug = options.debug ?? false;
|
|
810
|
+
this.onTimingEvent = options.onTimingEvent ?? null;
|
|
744
811
|
this.fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : (() => {
|
|
745
812
|
throw new Error("global fetch is not available; pass fetchImpl");
|
|
746
813
|
}));
|
|
@@ -767,10 +834,35 @@ var SessionManager = class {
|
|
|
767
834
|
get issuerUrl() {
|
|
768
835
|
return this.issuer;
|
|
769
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* SDK 2.7.0 (Task #124) — The hosted IQAuth host derived from the
|
|
839
|
+
* publishable key's `iss` claim, normalized to URL form. This is what
|
|
840
|
+
* `<SignIn/>` and `buildSignInUrl` use to talk to the hosted UI; it
|
|
841
|
+
* deliberately ignores the `issuer` constructor override so a misrouted
|
|
842
|
+
* `issuer` (e.g. pointed at the consumer app's own domain) cannot break
|
|
843
|
+
* the hosted flow. Use {@link issuerUrl} for token / discovery endpoints.
|
|
844
|
+
*/
|
|
845
|
+
get hostedIssuerUrl() {
|
|
846
|
+
const iss = this.key.iss;
|
|
847
|
+
return (iss.startsWith("http") ? iss : `https://${iss}`).replace(/\/+$/, "");
|
|
848
|
+
}
|
|
770
849
|
/** Cookie name the SDK uses for the refresh token (overridable via `cookieNames.refresh`). */
|
|
771
850
|
get refreshCookie() {
|
|
772
851
|
return this.refreshCookieName;
|
|
773
852
|
}
|
|
853
|
+
/**
|
|
854
|
+
* Returns the current per-session idempotency token, generating one
|
|
855
|
+
* lazily on first use. Sent as the `X-IQAuth-Idempotency` header on
|
|
856
|
+
* /refresh and /signout requests so the framework adapter's
|
|
857
|
+
* `SignoutRegistry` can collapse a refresh-vs-signout race even across
|
|
858
|
+
* server instances.
|
|
859
|
+
*/
|
|
860
|
+
getIdempotencyToken() {
|
|
861
|
+
if (!this.idempotencyToken) {
|
|
862
|
+
this.idempotencyToken = randomIdempotencyToken();
|
|
863
|
+
}
|
|
864
|
+
return this.idempotencyToken;
|
|
865
|
+
}
|
|
774
866
|
getSnapshot() {
|
|
775
867
|
return this.snapshot;
|
|
776
868
|
}
|
|
@@ -784,9 +876,44 @@ var SessionManager = class {
|
|
|
784
876
|
* One-time bootstrap: warm the session from the refresh cookie if present.
|
|
785
877
|
* Safe to call multiple times.
|
|
786
878
|
*/
|
|
879
|
+
/**
|
|
880
|
+
* Task #126: Public timing-event emitter. Used by the browser sign-in
|
|
881
|
+
* helpers (redirectToSignIn / handleAuthCallback) to surface signIn-phase
|
|
882
|
+
* timings through the same `debug` + `onTimingEvent` channel as
|
|
883
|
+
* bootstrap/refresh. Safe to call from anywhere — internal callers
|
|
884
|
+
* pre-compute durationMs.
|
|
885
|
+
*/
|
|
886
|
+
recordTiming(phase, durationMs, ok, code) {
|
|
887
|
+
this.emitTiming(phase, durationMs, ok, code);
|
|
888
|
+
}
|
|
889
|
+
/** Task #126: emit a session timing event to debug log + onTimingEvent hook. */
|
|
890
|
+
emitTiming(phase, durationMs, ok, code) {
|
|
891
|
+
if (this.debug) {
|
|
892
|
+
try {
|
|
893
|
+
console.debug("[iqauth_session]", { phase, durationMs, ok, code });
|
|
894
|
+
} catch {
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (this.onTimingEvent) {
|
|
898
|
+
try {
|
|
899
|
+
this.onTimingEvent({ phase, durationMs, ok, code });
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
787
904
|
async bootstrap() {
|
|
788
905
|
if (this.bootstrapped) return;
|
|
789
906
|
this.bootstrapped = true;
|
|
907
|
+
const t0 = Date.now();
|
|
908
|
+
try {
|
|
909
|
+
await this.bootstrapInner();
|
|
910
|
+
this.emitTiming("bootstrap", Date.now() - t0, this.snapshot.status === "authenticated");
|
|
911
|
+
} catch (err) {
|
|
912
|
+
this.emitTiming("bootstrap", Date.now() - t0, false, err instanceof Error ? err.message : "ERROR");
|
|
913
|
+
throw err;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
async bootstrapInner() {
|
|
790
917
|
if (this.serverManagedSession) {
|
|
791
918
|
try {
|
|
792
919
|
const res = await this.fetchImpl(`${this.issuer}${this.userinfoPath}`, {
|
|
@@ -821,6 +948,15 @@ var SessionManager = class {
|
|
|
821
948
|
return;
|
|
822
949
|
}
|
|
823
950
|
}
|
|
951
|
+
const peerSnapshot = await this.probePeers();
|
|
952
|
+
if (peerSnapshot && peerSnapshot.status === "authenticated") {
|
|
953
|
+
this.update({
|
|
954
|
+
...peerSnapshot,
|
|
955
|
+
version: Math.max(this.snapshot.version, peerSnapshot.version) + 1
|
|
956
|
+
});
|
|
957
|
+
this.scheduleProactiveRefresh();
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
824
960
|
const stored = await Promise.resolve(this.tokenStore.read());
|
|
825
961
|
if (!stored) {
|
|
826
962
|
this.setStatus("unauthenticated");
|
|
@@ -829,6 +965,22 @@ var SessionManager = class {
|
|
|
829
965
|
const ok = await this.refresh();
|
|
830
966
|
if (!ok) this.setStatus("unauthenticated");
|
|
831
967
|
}
|
|
968
|
+
probePeers() {
|
|
969
|
+
if (!this.channel) return Promise.resolve(null);
|
|
970
|
+
return new Promise((resolve) => {
|
|
971
|
+
let settled = false;
|
|
972
|
+
const finish = (snap) => {
|
|
973
|
+
if (settled) return;
|
|
974
|
+
settled = true;
|
|
975
|
+
this.probeResolver = null;
|
|
976
|
+
clearTimeout(timer);
|
|
977
|
+
resolve(snap);
|
|
978
|
+
};
|
|
979
|
+
this.probeResolver = (snap) => finish(snap);
|
|
980
|
+
const timer = setTimeout(() => finish(null), PROBE_WAIT_MS);
|
|
981
|
+
this.broadcastEnvelope({ type: "session:probe", source: this.tabId, ts: Date.now() });
|
|
982
|
+
});
|
|
983
|
+
}
|
|
832
984
|
/**
|
|
833
985
|
* Single-flight token refresh, coordinated across tabs via BroadcastChannel.
|
|
834
986
|
*
|
|
@@ -842,30 +994,48 @@ var SessionManager = class {
|
|
|
842
994
|
*/
|
|
843
995
|
refresh() {
|
|
844
996
|
if (this.refreshPromise) return this.refreshPromise;
|
|
845
|
-
|
|
997
|
+
const t0 = Date.now();
|
|
998
|
+
this.refreshPromise = this.runRefresh().then((ok) => {
|
|
999
|
+
this.emitTiming("refresh", Date.now() - t0, ok, ok ? void 0 : this.snapshot.error?.code ?? "REFRESH_FAILED");
|
|
1000
|
+
return ok;
|
|
1001
|
+
}).finally(() => {
|
|
846
1002
|
this.refreshPromise = null;
|
|
847
1003
|
});
|
|
848
1004
|
return this.refreshPromise;
|
|
849
1005
|
}
|
|
850
1006
|
async runRefresh() {
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
this.broadcastEnvelope({ type: "refresh:claim", source: myClaim.source, ts: myClaim.ts });
|
|
854
|
-
await new Promise((r) => setTimeout(r, 25));
|
|
855
|
-
const foreign = this.foreignClaim;
|
|
856
|
-
if (foreign && this.claimWins(foreign, myClaim)) {
|
|
857
|
-
return this.waitForForeignRefresh();
|
|
858
|
-
}
|
|
859
|
-
}
|
|
1007
|
+
const abort = new AbortController();
|
|
1008
|
+
this.refreshAbort = abort;
|
|
860
1009
|
try {
|
|
1010
|
+
const myClaim = { source: this.tabId, ts: Date.now() };
|
|
1011
|
+
if (this.channel) {
|
|
1012
|
+
this.broadcastEnvelope({ type: "refresh:claim", source: myClaim.source, ts: myClaim.ts });
|
|
1013
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
1014
|
+
if (abort.signal.aborted || this.signoutInProgress) {
|
|
1015
|
+
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
const foreign = this.foreignClaim;
|
|
1019
|
+
if (foreign && this.claimWins(foreign, myClaim)) {
|
|
1020
|
+
return this.waitForForeignRefresh();
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
861
1023
|
const refreshToken = await Promise.resolve(this.tokenStore.read());
|
|
862
1024
|
const res = await this.fetchImpl(`${this.issuer}${this.refreshPath}`, {
|
|
863
1025
|
method: "POST",
|
|
864
1026
|
credentials: "include",
|
|
865
|
-
headers: {
|
|
866
|
-
|
|
1027
|
+
headers: {
|
|
1028
|
+
"Content-Type": "application/json",
|
|
1029
|
+
[IDEMPOTENCY_HEADER]: this.getIdempotencyToken()
|
|
1030
|
+
},
|
|
1031
|
+
body: JSON.stringify(refreshToken ? { refreshToken } : {}),
|
|
1032
|
+
signal: abort.signal
|
|
867
1033
|
});
|
|
868
1034
|
const body = await res.json().catch(() => ({}));
|
|
1035
|
+
if (this.signoutInProgress || abort.signal.aborted) {
|
|
1036
|
+
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
869
1039
|
const data = body.data;
|
|
870
1040
|
if (!res.ok || !body.success || !data?.accessToken) {
|
|
871
1041
|
const err = body.error;
|
|
@@ -884,14 +1054,18 @@ var SessionManager = class {
|
|
|
884
1054
|
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: true });
|
|
885
1055
|
return true;
|
|
886
1056
|
} catch (err) {
|
|
887
|
-
this.
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1057
|
+
const aborted = err?.name === "AbortError" || abort.signal.aborted || this.signoutInProgress;
|
|
1058
|
+
if (!aborted) {
|
|
1059
|
+
this.setError({
|
|
1060
|
+
code: "NETWORK_ERROR",
|
|
1061
|
+
message: err instanceof Error ? err.message : "Refresh request failed"
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
891
1064
|
this.broadcastEnvelope({ type: "refresh:done", source: this.tabId, ts: Date.now(), ok: false });
|
|
892
1065
|
return false;
|
|
893
1066
|
} finally {
|
|
894
1067
|
this.foreignClaim = null;
|
|
1068
|
+
if (this.refreshAbort === abort) this.refreshAbort = null;
|
|
895
1069
|
}
|
|
896
1070
|
}
|
|
897
1071
|
claimWins(foreign, mine) {
|
|
@@ -999,6 +1173,14 @@ var SessionManager = class {
|
|
|
999
1173
|
* the server-side logout request.
|
|
1000
1174
|
*/
|
|
1001
1175
|
signOutLocal(status = "unauthenticated") {
|
|
1176
|
+
this.signoutInProgress = true;
|
|
1177
|
+
if (this.refreshAbort) {
|
|
1178
|
+
try {
|
|
1179
|
+
this.refreshAbort.abort();
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
this.refreshAbort = null;
|
|
1183
|
+
}
|
|
1002
1184
|
void Promise.resolve(this.tokenStore.clear());
|
|
1003
1185
|
if (this.proactiveTimer) {
|
|
1004
1186
|
clearTimeout(this.proactiveTimer);
|
|
@@ -1013,7 +1195,12 @@ var SessionManager = class {
|
|
|
1013
1195
|
error: null,
|
|
1014
1196
|
version: this.snapshot.version + 1
|
|
1015
1197
|
});
|
|
1198
|
+
this.broadcastEnvelope({ type: "refresh:abort", source: this.tabId, ts: Date.now() });
|
|
1016
1199
|
this.broadcast("session:signout");
|
|
1200
|
+
this.idempotencyToken = null;
|
|
1201
|
+
setTimeout(() => {
|
|
1202
|
+
this.signoutInProgress = false;
|
|
1203
|
+
}, 0);
|
|
1017
1204
|
}
|
|
1018
1205
|
/**
|
|
1019
1206
|
* Replace the refresh-token store at runtime. Used by the F22
|
|
@@ -1077,6 +1264,12 @@ var SessionManager = class {
|
|
|
1077
1264
|
}
|
|
1078
1265
|
onBroadcast(env) {
|
|
1079
1266
|
if (!env || env.source === this.tabId) return;
|
|
1267
|
+
if (env.type === "session:probe") {
|
|
1268
|
+
if (this.snapshot.status === "authenticated") {
|
|
1269
|
+
this.broadcast("session:update");
|
|
1270
|
+
}
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1080
1273
|
if (env.type === "refresh:claim") {
|
|
1081
1274
|
this.foreignClaim = { source: env.source, ts: env.ts };
|
|
1082
1275
|
return;
|
|
@@ -1089,6 +1282,24 @@ var SessionManager = class {
|
|
|
1089
1282
|
this.foreignClaim = null;
|
|
1090
1283
|
return;
|
|
1091
1284
|
}
|
|
1285
|
+
if (env.type === "refresh:abort") {
|
|
1286
|
+
this.signoutInProgress = true;
|
|
1287
|
+
if (this.refreshAbort) {
|
|
1288
|
+
try {
|
|
1289
|
+
this.refreshAbort.abort();
|
|
1290
|
+
} catch {
|
|
1291
|
+
}
|
|
1292
|
+
this.refreshAbort = null;
|
|
1293
|
+
}
|
|
1294
|
+
const waiters = this.remoteRefreshWaiters;
|
|
1295
|
+
this.remoteRefreshWaiters = [];
|
|
1296
|
+
for (const w of waiters) w(false);
|
|
1297
|
+
this.foreignClaim = null;
|
|
1298
|
+
setTimeout(() => {
|
|
1299
|
+
this.signoutInProgress = false;
|
|
1300
|
+
}, 0);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1092
1303
|
if (env.type === "session:signout") {
|
|
1093
1304
|
this.update({
|
|
1094
1305
|
status: "unauthenticated",
|
|
@@ -1102,6 +1313,12 @@ var SessionManager = class {
|
|
|
1102
1313
|
return;
|
|
1103
1314
|
}
|
|
1104
1315
|
if ((env.type === "session:update" || env.type === "session:refresh") && env.payload) {
|
|
1316
|
+
if (this.probeResolver && env.payload.status === "authenticated") {
|
|
1317
|
+
const r = this.probeResolver;
|
|
1318
|
+
this.probeResolver = null;
|
|
1319
|
+
r(env.payload);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1105
1322
|
this.update({
|
|
1106
1323
|
...env.payload,
|
|
1107
1324
|
version: Math.max(this.snapshot.version, env.payload.version) + 1
|
|
@@ -1386,7 +1603,7 @@ async function buildSignInUrl(manager, opts = {}) {
|
|
|
1386
1603
|
returnTo,
|
|
1387
1604
|
createdAt: Date.now()
|
|
1388
1605
|
});
|
|
1389
|
-
const url2 = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.
|
|
1606
|
+
const url2 = new URL(opts.signInPath ?? DEFAULT_SIGN_IN_PATH, manager.hostedIssuerUrl);
|
|
1390
1607
|
url2.searchParams.set("response_type", "code");
|
|
1391
1608
|
url2.searchParams.set("app", manager.appKey);
|
|
1392
1609
|
url2.searchParams.set("publishable_key", manager.publishableKey.raw);
|
|
@@ -1401,33 +1618,50 @@ async function buildSignInUrl(manager, opts = {}) {
|
|
|
1401
1618
|
return url2.toString();
|
|
1402
1619
|
}
|
|
1403
1620
|
async function redirectToSignIn(manager, opts = {}) {
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1621
|
+
const t0 = Date.now();
|
|
1622
|
+
let ok = false;
|
|
1623
|
+
let code;
|
|
1624
|
+
try {
|
|
1625
|
+
const url2 = await buildSignInUrl(manager, opts);
|
|
1626
|
+
if (typeof window === "undefined") {
|
|
1627
|
+
code = "NO_WINDOW";
|
|
1628
|
+
throw new Error("redirectToSignIn requires a browser environment");
|
|
1629
|
+
}
|
|
1630
|
+
ok = true;
|
|
1631
|
+
manager.recordTiming("signIn", Date.now() - t0, true);
|
|
1632
|
+
window.location.assign(url2);
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
if (!ok) manager.recordTiming("signIn", Date.now() - t0, false, code ?? (err instanceof Error ? err.message : "ERROR"));
|
|
1635
|
+
throw err;
|
|
1407
1636
|
}
|
|
1408
|
-
window.location.assign(url2);
|
|
1409
1637
|
}
|
|
1410
1638
|
async function signIn(manager, opts = {}) {
|
|
1411
1639
|
return redirectToSignIn(manager, opts);
|
|
1412
1640
|
}
|
|
1413
1641
|
async function handleAuthCallback(manager, options = {}) {
|
|
1642
|
+
const t0 = Date.now();
|
|
1643
|
+
const emit = (ok, code2) => manager.recordTiming("signIn", Date.now() - t0, ok, code2);
|
|
1414
1644
|
const url2 = new URL(options.url ?? (typeof window !== "undefined" ? window.location.href : ""));
|
|
1415
1645
|
const code = url2.searchParams.get("code");
|
|
1416
1646
|
const state = url2.searchParams.get("state");
|
|
1417
1647
|
const errorParam = url2.searchParams.get("error");
|
|
1418
1648
|
if (errorParam) {
|
|
1649
|
+
emit(false, errorParam);
|
|
1419
1650
|
return { ok: false, returnTo: "/", error: errorParam };
|
|
1420
1651
|
}
|
|
1421
1652
|
if (!code || !state) {
|
|
1653
|
+
emit(false, "missing_code_or_state");
|
|
1422
1654
|
return { ok: false, returnTo: "/", error: "missing_code_or_state" };
|
|
1423
1655
|
}
|
|
1424
1656
|
const record = loadPkce(state);
|
|
1425
1657
|
if (!record) {
|
|
1658
|
+
emit(false, "unknown_state");
|
|
1426
1659
|
return { ok: false, returnTo: "/", error: "unknown_state" };
|
|
1427
1660
|
}
|
|
1428
1661
|
clearPkce(state);
|
|
1429
1662
|
const fetchImpl = options.fetchImpl ?? (typeof fetch !== "undefined" ? fetch.bind(globalThis) : null);
|
|
1430
1663
|
if (!fetchImpl) {
|
|
1664
|
+
emit(false, "no_fetch");
|
|
1431
1665
|
return { ok: false, returnTo: record.returnTo, error: "no_fetch" };
|
|
1432
1666
|
}
|
|
1433
1667
|
const tokenUrl = `${manager.issuerUrl}${options.tokenPath ?? DEFAULT_TOKEN_PATH}`;
|
|
@@ -1446,10 +1680,12 @@ async function handleAuthCallback(manager, options = {}) {
|
|
|
1446
1680
|
const body = await res.json().catch(() => ({}));
|
|
1447
1681
|
if (!res.ok) {
|
|
1448
1682
|
const desc = body.error_description ?? body.error ?? "token_exchange_failed";
|
|
1683
|
+
emit(false, desc);
|
|
1449
1684
|
return { ok: false, returnTo: record.returnTo, error: desc };
|
|
1450
1685
|
}
|
|
1451
1686
|
const tokens = body;
|
|
1452
1687
|
if (!tokens.access_token) {
|
|
1688
|
+
emit(false, "missing_access_token");
|
|
1453
1689
|
return { ok: false, returnTo: record.returnTo, error: "missing_access_token" };
|
|
1454
1690
|
}
|
|
1455
1691
|
if (tokens.refresh_token) {
|
|
@@ -1457,21 +1693,24 @@ async function handleAuthCallback(manager, options = {}) {
|
|
|
1457
1693
|
setCookie(cookieName, tokens.refresh_token, { maxAgeSeconds: 60 * 60 * 24 * 30 });
|
|
1458
1694
|
}
|
|
1459
1695
|
manager.applyAccessToken(tokens.access_token, tokens.refresh_token);
|
|
1696
|
+
emit(true);
|
|
1460
1697
|
return { ok: true, returnTo: record.returnTo };
|
|
1461
1698
|
}
|
|
1462
1699
|
async function signOut(manager, opts = {}) {
|
|
1463
1700
|
if (!opts.localOnly) {
|
|
1464
1701
|
const issuer = manager.issuerUrl.replace(/\/$/, "");
|
|
1702
|
+
const idempotency = manager.getIdempotencyToken();
|
|
1465
1703
|
try {
|
|
1466
1704
|
const url2 = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
|
|
1467
|
-
await manager.fetch(url2, { method: "POST" }).catch(() => void 0);
|
|
1705
|
+
await manager.fetch(url2, { method: "POST", headers: { "X-IQAuth-Idempotency": idempotency } }).catch(() => void 0);
|
|
1468
1706
|
} catch {
|
|
1469
1707
|
}
|
|
1470
1708
|
if (opts.endSsoSession !== false) {
|
|
1471
1709
|
try {
|
|
1472
1710
|
await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
|
|
1473
1711
|
method: "POST",
|
|
1474
|
-
credentials: "include"
|
|
1712
|
+
credentials: "include",
|
|
1713
|
+
headers: { "X-IQAuth-Idempotency": idempotency }
|
|
1475
1714
|
}).catch(() => void 0);
|
|
1476
1715
|
} catch {
|
|
1477
1716
|
}
|
package/dist/browser.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
exitImpersonation,
|
|
5
5
|
reverify,
|
|
6
6
|
withReverification
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DFWHSDYQ.mjs";
|
|
8
8
|
import {
|
|
9
9
|
AccountRegistry,
|
|
10
10
|
MultiAccountTokenStore,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
signInWithPasskey,
|
|
21
21
|
unlinkProvider,
|
|
22
22
|
verifyMagicLink
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-RTJAIBXY.mjs";
|
|
24
24
|
import {
|
|
25
25
|
REFRESH_COOKIE,
|
|
26
26
|
buildSignInUrl,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
setCookie,
|
|
32
32
|
signIn,
|
|
33
33
|
signOut
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-GN37E64I.mjs";
|
|
35
35
|
import {
|
|
36
36
|
createPkcePair,
|
|
37
37
|
randomUrlSafe,
|
|
@@ -42,11 +42,11 @@ import {
|
|
|
42
42
|
isPublishableKey,
|
|
43
43
|
isSecretKey,
|
|
44
44
|
parsePublishableKey
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-HVHNYPDC.mjs";
|
|
46
46
|
import {
|
|
47
47
|
ErrorCodes,
|
|
48
48
|
IQAuthError
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-6PJRLRB4.mjs";
|
|
50
50
|
import "./chunk-Y6FXYEAI.mjs";
|
|
51
51
|
export {
|
|
52
52
|
AccountRegistry,
|
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
// src/errors.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
2
|
+
var IQ_AUTH_ERROR_CODES = [
|
|
3
|
+
"token_expired",
|
|
4
|
+
"token_invalid",
|
|
5
|
+
"jwks_unavailable",
|
|
6
|
+
"jwks_fetch_failed",
|
|
7
|
+
"rate_limited",
|
|
8
|
+
"network",
|
|
9
|
+
"config_invalid",
|
|
10
|
+
"app_not_found",
|
|
11
|
+
"permission_denied",
|
|
12
|
+
"unknown"
|
|
13
|
+
];
|
|
14
|
+
var IQAuthError = class _IQAuthError extends Error {
|
|
15
|
+
constructor(code, message, status, cause) {
|
|
4
16
|
super(message);
|
|
5
17
|
this.name = "IQAuthError";
|
|
6
18
|
this.code = code;
|
|
7
19
|
this.status = status;
|
|
8
|
-
this.
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
this.raw = cause;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Type guard: true when `value` is an `IQAuthError`. Useful for adapters
|
|
25
|
+
* that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
|
|
26
|
+
*/
|
|
27
|
+
static isIQAuthError(value) {
|
|
28
|
+
return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Type-narrowed code check. Lets callers write
|
|
32
|
+
* `if (err.is("token_expired")) …` with full IntelliSense for the typed
|
|
33
|
+
* taxonomy without losing the ability to handle server codes via
|
|
34
|
+
* `err.code === "TOKEN_REVOKED"`.
|
|
35
|
+
*/
|
|
36
|
+
is(code) {
|
|
37
|
+
return this.code === code;
|
|
9
38
|
}
|
|
10
39
|
};
|
|
11
40
|
var ErrorCodes = {
|
|
@@ -46,6 +75,7 @@ var ErrorCodes = {
|
|
|
46
75
|
};
|
|
47
76
|
|
|
48
77
|
export {
|
|
78
|
+
IQ_AUTH_ERROR_CODES,
|
|
49
79
|
IQAuthError,
|
|
50
80
|
ErrorCodes
|
|
51
81
|
};
|