@iqauth/sdk 2.0.5 → 2.2.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.
Files changed (38) hide show
  1. package/README.md +19 -3
  2. package/dist/browser.d.mts +2 -2
  3. package/dist/browser.d.ts +2 -2
  4. package/dist/browser.js +57 -6
  5. package/dist/browser.mjs +2 -2
  6. package/dist/{chunk-ZESHDJDU.mjs → chunk-D72UL5HL.mjs} +3 -6
  7. package/dist/{chunk-JQRTY5MY.mjs → chunk-M4J6BPK7.mjs} +3 -8
  8. package/dist/chunk-QEJB7WEQ.mjs +119 -0
  9. package/dist/{chunk-S3M2IXCE.mjs → chunk-QZB745C2.mjs} +3 -8
  10. package/dist/cli/index.js +21 -0
  11. package/dist/cli/index.mjs +1 -1
  12. package/dist/{doctor-OHJRZBBT.mjs → doctor-XCI77BQS.mjs} +2 -1
  13. package/dist/express.js +54 -25
  14. package/dist/express.mjs +5 -8
  15. package/dist/fastify.js +53 -19
  16. package/dist/fastify.mjs +4 -5
  17. package/dist/hono.js +53 -19
  18. package/dist/hono.mjs +4 -5
  19. package/dist/index.d.mts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +59 -4
  22. package/dist/index.mjs +4 -2
  23. package/dist/next.js +66 -34
  24. package/dist/next.mjs +6 -9
  25. package/dist/{publishableKey-B5DIK81A.d.mts → publishableKey-BaR0HoAH.d.mts} +10 -1
  26. package/dist/{publishableKey-B5DIK81A.d.ts → publishableKey-BaR0HoAH.d.ts} +10 -1
  27. package/dist/react.d.mts +91 -4
  28. package/dist/react.d.ts +91 -4
  29. package/dist/react.js +466 -162
  30. package/dist/react.mjs +411 -147
  31. package/dist/server/handlers.js +63 -17
  32. package/dist/server/handlers.mjs +3 -2
  33. package/dist/server.js +53 -21
  34. package/dist/server.mjs +3 -3
  35. package/dist/{signIn-VRNzlNyG.d.ts → signIn-BVDTIA_t.d.ts} +1 -1
  36. package/dist/{signIn-CEMdUAwd.d.mts → signIn-D_kP3v-c.d.mts} +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-5WFR6Y33.mjs +0 -59
package/dist/react.js CHANGED
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
23
  AuthCallback: () => AuthCallback,
24
+ IQAuthLoaded: () => IQAuthLoaded,
25
+ IQAuthLoading: () => IQAuthLoading,
24
26
  IQAuthProvider: () => IQAuthProvider,
25
27
  OrganizationSwitcher: () => OrganizationSwitcher,
26
28
  RedirectToSignIn: () => RedirectToSignIn,
@@ -31,10 +33,13 @@ __export(react_exports, {
31
33
  UserButton: () => UserButton,
32
34
  UserProfile: () => UserProfile,
33
35
  __version__: () => __version__,
36
+ isSilentSsoEligible: () => isSilentSsoEligible,
37
+ sanitizeBrandCss: () => sanitizeBrandCss,
34
38
  useAuth: () => useAuth,
35
39
  useAuthFetch: () => useAuthFetch,
36
40
  useIQAuthSignInContext: () => useIQAuthSignInContext,
37
41
  useOrganization: () => useOrganization,
42
+ useResolvedSdkBranding: () => useResolvedSdkBranding,
38
43
  useSession: () => useSession,
39
44
  useUser: () => useUser
40
45
  });
@@ -67,20 +72,60 @@ function b64urlDecode(input) {
67
72
  const { Buffer: Buffer2 } = require("buffer");
68
73
  return Buffer2.from(normalized, "base64").toString("utf8");
69
74
  }
70
- function parsePublishableKey(raw) {
71
- if (typeof raw !== "string") return null;
72
- const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
73
- if (!m) return null;
75
+ function isValidIssuerUrl(iss) {
76
+ if (typeof iss !== "string" || iss.length === 0) return false;
77
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
74
78
  try {
75
- const json = JSON.parse(b64urlDecode(m[2]));
76
- if (!json || typeof json !== "object") return null;
77
- if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
78
- return null;
79
- }
80
- return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
79
+ const u = new URL(iss);
80
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
81
+ if (!u.hostname) return false;
82
+ return true;
81
83
  } catch {
82
- return null;
84
+ return false;
85
+ }
86
+ }
87
+ function assertPublishableKey(raw, opts) {
88
+ const ctx = opts?.context ? `${opts.context}: ` : "";
89
+ if (typeof raw !== "string" || raw.length === 0) {
90
+ throw new IQAuthError(
91
+ "CONFIG_INVALID",
92
+ `${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.`
93
+ );
94
+ }
95
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
96
+ if (!shapeMatch) {
97
+ throw new IQAuthError(
98
+ "CONFIG_INVALID",
99
+ `${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.`
100
+ );
101
+ }
102
+ let decoded;
103
+ try {
104
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
105
+ } catch {
106
+ throw new IQAuthError(
107
+ "CONFIG_INVALID",
108
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
109
+ );
110
+ }
111
+ if (!isPublishableKeyPayload(decoded)) {
112
+ throw new IQAuthError(
113
+ "CONFIG_INVALID",
114
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
115
+ );
116
+ }
117
+ if (!isValidIssuerUrl(decoded.iss)) {
118
+ throw new IQAuthError(
119
+ "CONFIG_INVALID",
120
+ `${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, or set IQAUTH_ISSUER to the correct issuer URL as a temporary workaround.`
121
+ );
83
122
  }
123
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
124
+ }
125
+ function isPublishableKeyPayload(value) {
126
+ if (!value || typeof value !== "object") return false;
127
+ const v = value;
128
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
84
129
  }
85
130
 
86
131
  // src/browser/storage.ts
@@ -203,12 +248,7 @@ var SessionManager = class {
203
248
  this.remoteRefreshWaiters = [];
204
249
  /** Active claims by other tabs (keyed by source tabId). */
205
250
  this.foreignClaim = null;
206
- const parsed = parsePublishableKey(options.publishableKey);
207
- if (!parsed) {
208
- throw new Error(
209
- `Invalid IQAuth publishable key. Expected pk_test_\u2026 or pk_live_\u2026 (got ${options.publishableKey?.slice(0, 12) ?? "<empty>"}\u2026).`
210
- );
211
- }
251
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
212
252
  this.key = parsed;
213
253
  const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
214
254
  this.issuer = inferred.replace(/\/+$/, "");
@@ -843,6 +883,16 @@ function SignedOut({ children }) {
843
883
  if (!isLoaded || isSignedIn) return null;
844
884
  return (0, import_react.createElement)(import_react.Fragment, null, children);
845
885
  }
886
+ function IQAuthLoading({ children }) {
887
+ const { isLoaded } = useAuth();
888
+ if (isLoaded) return null;
889
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
890
+ }
891
+ function IQAuthLoaded({ children }) {
892
+ const { isLoaded } = useAuth();
893
+ if (!isLoaded) return null;
894
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
895
+ }
846
896
  function RedirectToSignIn(props = {}) {
847
897
  const { manager, snapshot } = useCtx();
848
898
  const [error, setError] = (0, import_react.useState)(null);
@@ -911,6 +961,12 @@ function brandStyle(branding) {
911
961
  if (branding.backgroundColor) s["--brand-bg"] = branding.backgroundColor;
912
962
  if (branding.surfaceColor) s["--brand-surface"] = branding.surfaceColor;
913
963
  if (branding.textColor) s["--brand-text"] = branding.textColor;
964
+ if (branding.borderRadius != null && branding.borderRadius !== "") {
965
+ const n = typeof branding.borderRadius === "number" ? `${branding.borderRadius}px` : String(branding.borderRadius);
966
+ s["--brand-radius"] = n;
967
+ }
968
+ if (branding.fontFamilyBody) s["--brand-font-body"] = branding.fontFamilyBody;
969
+ if (branding.fontFamilyHeading) s["--brand-font-heading"] = branding.fontFamilyHeading;
914
970
  return s;
915
971
  }
916
972
  async function jsonFetch(url, init) {
@@ -935,7 +991,7 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
935
991
  let cancelled = false;
936
992
  setLoading(true);
937
993
  const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
938
- fetch(url).then((r) => r.json()).then((payload) => {
994
+ fetch(url, { credentials: "include" }).then((r) => r.json()).then((payload) => {
939
995
  if (cancelled) return;
940
996
  if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
941
997
  setCtx(payload.data);
@@ -953,6 +1009,7 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
953
1009
  var SHELL_CSS = `
954
1010
  .iqauth-sdk-shell {
955
1011
  min-height: 100vh;
1012
+ width: 100%;
956
1013
  display: grid;
957
1014
  grid-template-columns: 1fr;
958
1015
  background: var(--brand-bg, #f7f7f6);
@@ -961,20 +1018,31 @@ var SHELL_CSS = `
961
1018
  .iqauth-sdk-hero { display: none; }
962
1019
  .iqauth-sdk-pane {
963
1020
  display: flex; flex-direction: column; align-items: center; justify-content: center;
964
- padding: 40px 16px; min-height: 100vh;
1021
+ padding: 48px 24px; min-height: 100vh;
965
1022
  }
966
1023
  .iqauth-sdk-card {
967
- width: 100%; max-width: 420px;
1024
+ width: 100%; max-width: 460px;
968
1025
  background: var(--brand-surface, #ffffff);
969
1026
  border: 1px solid rgba(15,23,42,0.08);
970
- border-radius: 14px;
971
- box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(15,23,42,0.04);
1027
+ border-radius: var(--brand-radius, 16px);
1028
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 12px 32px rgba(15,23,42,0.06);
972
1029
  overflow: hidden;
973
1030
  }
974
- .iqauth-sdk-card-header { padding: 28px 28px 0; }
975
- .iqauth-sdk-card-header h1 { font-size: 22px; font-weight: 600; margin: 0; line-height: 1.2; }
1031
+ .iqauth-sdk-shell, .iqauth-sdk-shell input, .iqauth-sdk-shell button { font-family: var(--brand-font-body, inherit); }
1032
+ .iqauth-sdk-shell h1, .iqauth-sdk-shell h2, .iqauth-sdk-shell h3 { font-family: var(--brand-font-heading, var(--brand-font-body, inherit)); }
1033
+ .iqauth-sdk-shell[data-layout="centered_card"] { grid-template-columns: 1fr; }
1034
+ .iqauth-sdk-shell[data-layout="centered_card"] .iqauth-sdk-hero { display: none; }
1035
+ .iqauth-sdk-shell[data-layout="full_bleed"] { background-size: cover; background-position: center; background-repeat: no-repeat; }
1036
+ .iqauth-sdk-shell[data-layout="full_bleed"] .iqauth-sdk-hero { display: none; }
1037
+ .iqauth-sdk-shell[data-layout="full_bleed"] .iqauth-sdk-pane { background: rgba(15,23,42,0.30); }
1038
+ .iqauth-sdk-shell[data-social-style="outline"] .iqauth-sdk-google-btn { background: transparent; border-color: var(--brand-primary, rgba(15,23,42,0.18)); color: var(--brand-text, inherit); }
1039
+ .iqauth-sdk-shell[data-social-style="ghost"] .iqauth-sdk-google-btn { background: transparent; border-color: transparent; color: var(--brand-text, inherit); }
1040
+ .iqauth-sdk-shell[data-social-style="solid"] .iqauth-sdk-google-btn { background: var(--brand-primary, #3b82f6); border-color: transparent; color: #fff; }
1041
+ .iqauth-sdk-shell[data-social-style="solid"] .iqauth-sdk-google-btn:hover { background: var(--brand-primary, #3b82f6); opacity: 0.92; }
1042
+ .iqauth-sdk-card-header { padding: 32px 36px 0; }
1043
+ .iqauth-sdk-card-header h1 { font-size: 24px; font-weight: 600; margin: 0; line-height: 1.2; letter-spacing: -0.01em; }
976
1044
  .iqauth-sdk-card-header p { margin: 8px 0 0; font-size: 14px; color: rgba(15,23,42,0.65); line-height: 1.5; }
977
- .iqauth-sdk-card-body { padding: 28px 28px 24px; display: flex; flex-direction: column; gap: 18px; }
1045
+ .iqauth-sdk-card-body { padding: 32px 36px 28px; display: flex; flex-direction: column; gap: 18px; }
978
1046
  .iqauth-sdk-mobile-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }
979
1047
  .iqauth-sdk-mobile-brand img { height: 36px; width: auto; }
980
1048
  .iqauth-sdk-mobile-brand span { font-size: 16px; font-weight: 600; }
@@ -995,11 +1063,11 @@ var SHELL_CSS = `
995
1063
  .iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
996
1064
  .iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
997
1065
 
998
- @media (min-width: 900px) {
999
- .iqauth-sdk-shell { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
1066
+ @media (min-width: 768px) {
1067
+ .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
1000
1068
  .iqauth-sdk-hero {
1001
1069
  display: flex; flex-direction: column; justify-content: space-between;
1002
- padding: 48px 56px; color: #ffffff;
1070
+ padding: clamp(32px, 4vw, 56px); color: #ffffff;
1003
1071
  background: linear-gradient(135deg, var(--brand-primary, #3b82f6) 0%, var(--brand-accent, #6366f1) 100%);
1004
1072
  background-size: cover; background-position: center;
1005
1073
  position: relative; overflow: hidden; min-height: 100vh;
@@ -1008,15 +1076,113 @@ var SHELL_CSS = `
1008
1076
  background-image: var(--iqauth-sdk-hero-image), linear-gradient(135deg, var(--brand-primary, #3b82f6), var(--brand-accent, #6366f1));
1009
1077
  background-blend-mode: multiply;
1010
1078
  }
1011
- .iqauth-sdk-hero-brand img { height: 44px; width: auto; filter: brightness(0) invert(1); opacity: 0.96; }
1012
- .iqauth-sdk-hero-brand .iqauth-sdk-hero-name { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
1013
- .iqauth-sdk-hero-content h2 { font-size: 34px; font-weight: 600; line-height: 1.15; margin: 0 0 14px; letter-spacing: -0.015em; }
1014
- .iqauth-sdk-hero-content p { font-size: 15px; line-height: 1.6; opacity: 0.92; margin: 0; max-width: 460px; white-space: pre-wrap; }
1079
+ .iqauth-sdk-hero-brand img { height: 40px; width: auto; filter: brightness(0) invert(1); opacity: 0.96; }
1080
+ .iqauth-sdk-hero-brand .iqauth-sdk-hero-name { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
1081
+ .iqauth-sdk-hero-content { max-width: 520px; }
1082
+ .iqauth-sdk-hero-content h2 { font-size: clamp(24px, 2.4vw, 34px); font-weight: 600; line-height: 1.2; margin: 0 0 14px; letter-spacing: -0.015em; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; }
1083
+ .iqauth-sdk-hero-content p { font-size: 15px; line-height: 1.6; opacity: 0.92; margin: 0; white-space: pre-wrap; }
1015
1084
  .iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
1016
1085
  .iqauth-sdk-mobile-brand { display: none; }
1017
1086
  }
1087
+ @media (min-width: 1280px) {
1088
+ .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
1089
+ }
1018
1090
  `;
1019
1091
  var sdkShellStylesInjected = false;
1092
+ var SDK_CSS_MAX_LEN = 50 * 1024;
1093
+ var SDK_CSS_FORBIDDEN = [
1094
+ /<\/?\s*style[^>]*>/gi,
1095
+ /<\/?\s*script[^>]*>/gi,
1096
+ /<!--[\s\S]*?-->/g,
1097
+ /@import\s+[^;]*;?/gi,
1098
+ /expression\s*\(/gi,
1099
+ /behavior\s*:/gi,
1100
+ /-moz-binding\s*:/gi,
1101
+ /javascript\s*:/gi,
1102
+ /vbscript\s*:/gi
1103
+ ];
1104
+ var SDK_URL_DATA_RE = /url\s*\(\s*(['"]?)\s*data\s*:[^)]*\)/gi;
1105
+ function sanitizeBrandCss(input) {
1106
+ if (!input) return "";
1107
+ let out = String(input);
1108
+ if (out.length > SDK_CSS_MAX_LEN) out = out.slice(0, SDK_CSS_MAX_LEN);
1109
+ out = out.replace(/</g, "");
1110
+ for (const re of SDK_CSS_FORBIDDEN) out = out.replace(re, "");
1111
+ out = out.replace(SDK_URL_DATA_RE, "url()");
1112
+ return out;
1113
+ }
1114
+ function SdkBrandLogo({ branding, alt, fallback }) {
1115
+ const light = branding?.logoLightUrl || branding?.logoUrl || null;
1116
+ const dark = branding?.logoDarkUrl || null;
1117
+ if (!light && !dark) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: fallback });
1118
+ const fallbackSrc = light || dark;
1119
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("picture", { "data-iqauth-sdk-logo": "", children: [
1120
+ dark ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { srcSet: dark, media: "(prefers-color-scheme: dark)" }) : null,
1121
+ light ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("source", { srcSet: light, media: "(prefers-color-scheme: light)" }) : null,
1122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: fallbackSrc, alt })
1123
+ ] });
1124
+ }
1125
+ var sdkBrandingCache = /* @__PURE__ */ new Map();
1126
+ var SDK_BRANDING_TTL_MS = 6e4;
1127
+ function flattenBrandingPayload(data) {
1128
+ const e = data && data.effective || {};
1129
+ const meta = data && data.meta || {};
1130
+ return {
1131
+ brandName: e.brandName ?? data?.brandName ?? null,
1132
+ logoUrl: e.logoLightUrl ?? data?.logoUrl ?? null,
1133
+ logoLightUrl: e.logoLightUrl ?? data?.logoLightUrl ?? null,
1134
+ logoDarkUrl: e.logoDarkUrl ?? data?.logoDarkUrl ?? null,
1135
+ faviconUrl: e.faviconUrl ?? data?.faviconUrl ?? null,
1136
+ primaryColor: e.primaryColor ?? data?.primaryColor ?? null,
1137
+ accentColor: e.accentColor ?? data?.accentColor ?? null,
1138
+ backgroundColor: e.backgroundColor ?? data?.backgroundColor ?? null,
1139
+ surfaceColor: e.surfaceColor ?? data?.surfaceColor ?? null,
1140
+ textColor: e.textColor ?? data?.textColor ?? null,
1141
+ fontFamilyBody: e.fontFamilyBody ?? data?.fontFamilyBody ?? null,
1142
+ fontFamilyHeading: e.fontFamilyHeading ?? data?.fontFamilyHeading ?? null,
1143
+ customFontUrl: e.customFontUrl ?? data?.customFontUrl ?? null,
1144
+ borderRadius: e.borderRadius ?? data?.borderRadius ?? null,
1145
+ backgroundImageUrl: e.backgroundImageUrl ?? data?.backgroundImageUrl ?? null,
1146
+ customCss: e.customCss ?? data?.customCss ?? null,
1147
+ loginLayout: e.loginLayout ?? data?.loginLayout ?? null,
1148
+ socialButtonStyle: e.socialButtonStyle ?? data?.socialButtonStyle ?? null,
1149
+ footerText: e.footerText ?? data?.footerText ?? null,
1150
+ brandingRev: meta.brandingRev ?? data?.brandingRev ?? null
1151
+ };
1152
+ }
1153
+ function useResolvedSdkBranding(iqAuthBaseUrl, appId) {
1154
+ const ctx = (0, import_react.useContext)(IQAuthContext);
1155
+ const resolvedAppId = appId ?? ctx?.manager?.appKey ?? null;
1156
+ const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/branding${resolvedAppId ? `?appId=${encodeURIComponent(resolvedAppId)}` : ""}`;
1157
+ const cached = sdkBrandingCache.get(url);
1158
+ const fresh = cached && Date.now() - cached.ts < SDK_BRANDING_TTL_MS ? cached.data : null;
1159
+ const [b, setB] = (0, import_react.useState)(fresh);
1160
+ (0, import_react.useEffect)(() => {
1161
+ let cancelled = false;
1162
+ const entry = sdkBrandingCache.get(url);
1163
+ const headers = {};
1164
+ if (entry?.rev) headers["If-None-Match"] = `W/"brand-${entry.rev}"`;
1165
+ if (entry) setB(entry.data);
1166
+ fetch(url, { credentials: "include", headers }).then(async (r) => {
1167
+ if (cancelled) return;
1168
+ if (r.status === 304 && entry) {
1169
+ sdkBrandingCache.set(url, { ...entry, ts: Date.now() });
1170
+ return;
1171
+ }
1172
+ if (!r.ok) return;
1173
+ const p = await r.json().catch(() => null);
1174
+ if (!p?.data) return;
1175
+ const flat = flattenBrandingPayload(p.data);
1176
+ sdkBrandingCache.set(url, { ts: Date.now(), rev: flat.brandingRev || "", data: flat });
1177
+ setB(flat);
1178
+ }).catch(() => {
1179
+ });
1180
+ return () => {
1181
+ cancelled = true;
1182
+ };
1183
+ }, [url]);
1184
+ return b;
1185
+ }
1020
1186
  function ensureSdkShellStyles() {
1021
1187
  if (typeof document === "undefined" || sdkShellStylesInjected) return;
1022
1188
  const tag = document.createElement("style");
@@ -1062,35 +1228,61 @@ function Shell({
1062
1228
  const brandVars = brandStyle(branding);
1063
1229
  const brandName = branding?.brandName || "IQAuth";
1064
1230
  const heroImage = branding?.heroImageUrl || null;
1231
+ const layout = (branding?.loginLayout || "split_screen").toString();
1232
+ const bgImage = branding?.backgroundImageUrl || null;
1233
+ const fontUrl = branding?.customFontUrl || null;
1234
+ const socialStyle = (branding?.socialButtonStyle || "").toString();
1065
1235
  const heroStyle = heroImage ? { ["--iqauth-sdk-hero-image"]: `url("${heroImage.replace(/"/g, '\\"')}")` } : {};
1236
+ const shellStyle = {
1237
+ ...brandVars,
1238
+ ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {}
1239
+ };
1066
1240
  const supportLink = branding?.supportUrl ? branding.supportUrl : branding?.supportEmail ? `mailto:${branding.supportEmail}` : null;
1067
1241
  const hasFooterLinks = !!(branding?.termsUrl || branding?.privacyUrl || supportLink);
1068
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`, style: brandVars, "data-iqauth-shell": "", children: [
1069
- branding?.customCss ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { "data-iqauth-sdk-custom": true, children: branding.customCss }) : null,
1070
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "iqauth-sdk-hero", "data-bg-image": heroImage ? "true" : "false", style: heroStyle, "aria-hidden": "true", children: [
1071
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-hero-brand", style: { display: "flex", alignItems: "center", gap: 12 }, children: branding?.logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: branding.logoUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "iqauth-sdk-hero-name", children: brandName }) }),
1072
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-hero-content", children: [
1073
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: branding?.tagline || `Welcome to ${brandName}` }),
1074
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: branding?.loginSideCopy || branding?.loginSubheadline || `Sign in to continue to your ${brandName} workspace.` })
1075
- ] }),
1076
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-hero-foot", children: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}` })
1077
- ] }),
1078
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { style: { width: "100%", maxWidth: 420 }, children: [
1079
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-mobile-brand", children: branding?.logoUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: branding.logoUrl, alt: `${brandName} logo` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: brandName }) }),
1080
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "iqauth-sdk-card", children: [
1081
- title || subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-card-header", children: [
1082
- title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: title }) : null,
1083
- subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: subtitle }) : null
1084
- ] }) : null,
1085
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-card-body", children })
1086
- ] }),
1087
- hasFooterLinks ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("footer", { className: "iqauth-sdk-footer", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-footer-links", children: [
1088
- branding?.termsUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: branding.termsUrl, target: "_blank", rel: "noreferrer noopener", children: "Terms" }) : null,
1089
- branding?.privacyUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: branding.privacyUrl, target: "_blank", rel: "noreferrer noopener", children: "Privacy" }) : null,
1090
- supportLink ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: supportLink, target: "_blank", rel: "noreferrer noopener", children: "Support" }) : null
1091
- ] }) }) : null
1092
- ] }) })
1093
- ] });
1242
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1243
+ "div",
1244
+ {
1245
+ className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`,
1246
+ "data-layout": layout,
1247
+ "data-social-style": socialStyle || void 0,
1248
+ style: shellStyle,
1249
+ "data-iqauth-shell": "",
1250
+ "data-branding-rev": branding?.brandingRev || "",
1251
+ children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { "data-brand": true, children: [
1253
+ fontUrl ? `@font-face{font-family:"${(branding?.fontFamilyBody || "Brand").replace(/"/g, "")}";src:url("${fontUrl.replace(/"/g, '\\"')}");font-display:swap;}` : "",
1254
+ sanitizeBrandCss(branding?.customCss)
1255
+ ].filter(Boolean).join("\n") }),
1256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "iqauth-sdk-hero", "data-bg-image": heroImage ? "true" : "false", style: heroStyle, "aria-hidden": "true", children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-hero-brand", style: { display: "flex", alignItems: "center", gap: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SdkBrandLogo, { branding, alt: "", fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "iqauth-sdk-hero-name", children: brandName }) }) }),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-hero-content", children: [
1259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: branding?.tagline || `Welcome to ${brandName}` }),
1260
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: branding?.loginSideCopy || branding?.loginSubheadline || `Sign in to continue to your ${brandName} workspace.` })
1261
+ ] }),
1262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-hero-foot", children: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}` })
1263
+ ] }),
1264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { style: { width: "100%", maxWidth: 420 }, children: [
1265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-mobile-brand", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SdkBrandLogo, { branding, alt: `${brandName} logo`, fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: brandName }) }) }),
1266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "iqauth-sdk-card", children: [
1267
+ title || subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-card-header", children: [
1268
+ title ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: title }) : null,
1269
+ subtitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: subtitle }) : null
1270
+ ] }) : null,
1271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "iqauth-sdk-card-body", children })
1272
+ ] }),
1273
+ hasFooterLinks || branding?.footerText ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("footer", { className: "iqauth-sdk-footer", children: [
1274
+ branding?.footerText ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "data-testid": "text-brand-footer-sdk", style: { fontSize: 12, opacity: 0.75 }, children: branding.footerText }) : null,
1275
+ hasFooterLinks ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "iqauth-sdk-footer-links", children: [
1276
+ branding?.termsUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: branding.termsUrl, target: "_blank", rel: "noreferrer noopener", children: "Terms" }) : null,
1277
+ branding?.privacyUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: branding.privacyUrl, target: "_blank", rel: "noreferrer noopener", children: "Privacy" }) : null,
1278
+ supportLink ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: supportLink, target: "_blank", rel: "noreferrer noopener", children: "Support" }) : null
1279
+ ] }) : null
1280
+ ] }) : null
1281
+ ] }) })
1282
+ ]
1283
+ },
1284
+ branding?.brandingRev || void 0
1285
+ );
1094
1286
  }
1095
1287
  function Field({ label, children }) {
1096
1288
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 13 }, children: [
@@ -1160,7 +1352,15 @@ function ErrorBanner({ message }) {
1160
1352
  color: "#b91c1c"
1161
1353
  }, children: message });
1162
1354
  }
1163
- function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
1355
+ function isSilentSsoEligible(ctx, effectivePrompt) {
1356
+ if (!ctx) return false;
1357
+ if (effectivePrompt === "login") return false;
1358
+ if (!ctx.session) return false;
1359
+ if (!ctx.app.defaultClientId) return false;
1360
+ if (!ctx.returnAllowed) return false;
1361
+ return true;
1362
+ }
1363
+ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt }) {
1164
1364
  const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
1165
1365
  const [email, setEmail] = (0, import_react.useState)("");
1166
1366
  const [password, setPassword] = (0, import_react.useState)("");
@@ -1169,6 +1369,18 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
1169
1369
  const [mfa, setMfa] = (0, import_react.useState)(null);
1170
1370
  const [tenantSel, setTenantSel] = (0, import_react.useState)(null);
1171
1371
  const [oauthExchanging, setOauthExchanging] = (0, import_react.useState)(false);
1372
+ const [silent, setSilent] = (0, import_react.useState)("idle");
1373
+ const [forcePrompt, setForcePrompt] = (0, import_react.useState)(false);
1374
+ const effectivePrompt = (0, import_react.useMemo)(() => {
1375
+ if (prompt === "login" || forcePrompt) return "login";
1376
+ if (typeof window !== "undefined") {
1377
+ try {
1378
+ if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
1379
+ } catch {
1380
+ }
1381
+ }
1382
+ return void 0;
1383
+ }, [prompt, forcePrompt]);
1172
1384
  const oidcPayload = () => ({
1173
1385
  client_id: ctx?.app.defaultClientId,
1174
1386
  redirect_uri: returnTo,
@@ -1258,6 +1470,58 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
1258
1470
  const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/google?redirect_uri=${encodeURIComponent(bridgeUrl)}&client_id=${encodeURIComponent(ctx.app.defaultClientId)}`;
1259
1471
  window.location.href = url;
1260
1472
  };
1473
+ (0, import_react.useEffect)(() => {
1474
+ if (loading || error || !ctx) return;
1475
+ if (effectivePrompt === "login") {
1476
+ setSilent("skipped");
1477
+ return;
1478
+ }
1479
+ if (silent !== "idle") return;
1480
+ if (!ctx.session || !ctx.app.defaultClientId || !ctx.returnAllowed) {
1481
+ setSilent("skipped");
1482
+ return;
1483
+ }
1484
+ setSilent("trying");
1485
+ (async () => {
1486
+ try {
1487
+ const r = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/oidc/sso-resume`, {
1488
+ method: "POST",
1489
+ headers: { "Content-Type": "application/json" },
1490
+ credentials: "include",
1491
+ body: JSON.stringify(oidcPayload())
1492
+ });
1493
+ const payload = await r.json().catch(() => ({}));
1494
+ if (payload?.type === "redirect" && payload.redirectUrl) {
1495
+ (onRedirect || ((u) => {
1496
+ window.location.replace(u);
1497
+ }))(payload.redirectUrl);
1498
+ return;
1499
+ }
1500
+ if (payload?.type === "tenant_selection") {
1501
+ setTenantSel({ token: payload.tenantSelectionToken, tenants: payload.tenants || [] });
1502
+ setSilent("failed");
1503
+ return;
1504
+ }
1505
+ setSilent("failed");
1506
+ } catch {
1507
+ setSilent("failed");
1508
+ }
1509
+ })();
1510
+ }, [loading, error, ctx, effectivePrompt]);
1511
+ const switchAccount = (e) => {
1512
+ if (e) e.preventDefault();
1513
+ setForcePrompt(true);
1514
+ setSilent("skipped");
1515
+ setTenantSel(null);
1516
+ if (typeof window !== "undefined") {
1517
+ try {
1518
+ const u = new URL(window.location.href);
1519
+ u.searchParams.set("prompt", "login");
1520
+ window.history.replaceState({}, "", u.toString());
1521
+ } catch {
1522
+ }
1523
+ }
1524
+ };
1261
1525
  (0, import_react.useEffect)(() => {
1262
1526
  if (!ctx?.app.defaultClientId) return;
1263
1527
  const params = new URLSearchParams(window.location.search);
@@ -1295,6 +1559,13 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
1295
1559
  if (loading || oauthExchanging) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: ctx?.branding || null, className, title: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026" }) });
1296
1560
  if (error || !ctx) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: null, className, title: "Application unavailable", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: error || "Failed to load app context" }) });
1297
1561
  if (!ctx.returnAllowed) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: ctx.branding, className, title: "Invalid redirect", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
1562
+ const silentEligible = isSilentSsoEligible(ctx, effectivePrompt);
1563
+ if (silentEligible && silent !== "failed") {
1564
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: ctx.branding, className, title: "Signing you in\u2026", subtitle: ctx.session ? `Welcome back, ${ctx.session.name || ctx.session.email}.` : void 0, children: [
1565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: "Resuming your session." }),
1566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: "Not you? Use a different account" })
1567
+ ] });
1568
+ }
1298
1569
  const cardTitle = ctx.branding?.loginHeadline || `Sign in to ${ctx.app.name}`;
1299
1570
  const cardSubtitle = ctx.branding?.loginSubheadline || void 0;
1300
1571
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
@@ -1344,7 +1615,11 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
1344
1615
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { label: "Password", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
1345
1616
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? "Signing in\u2026" : "Sign in" })
1346
1617
  ] })
1347
- ] })
1618
+ ] }),
1619
+ (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: [
1620
+ silent === "failed" && ctx.session ? "Couldn't resume your session. " : null,
1621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: "Use a different account" })
1622
+ ] }) : null
1348
1623
  ] });
1349
1624
  }
1350
1625
  function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
@@ -1395,6 +1670,9 @@ function initialsOf(name, email) {
1395
1670
  function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1396
1671
  const [user, setUser] = (0, import_react.useState)(null);
1397
1672
  const [open, setOpen] = (0, import_react.useState)(false);
1673
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1674
+ const accent = branding?.accentColor || "#6366f1";
1675
+ const primary = branding?.primaryColor || "#0f172a";
1398
1676
  (0, import_react.useEffect)(() => {
1399
1677
  let cancelled = false;
1400
1678
  fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
@@ -1415,59 +1693,69 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
1415
1693
  };
1416
1694
  if (!user) return null;
1417
1695
  const target = accountUrl || `${iqAuthBaseUrl.replace(/\/$/, "")}/account`;
1418
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: { position: "relative", display: "inline-block" }, children: [
1419
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1420
- "button",
1421
- {
1422
- type: "button",
1423
- "aria-haspopup": "menu",
1424
- "aria-expanded": open,
1425
- onClick: () => setOpen((o) => !o),
1426
- style: {
1427
- width: 32,
1428
- height: 32,
1429
- borderRadius: "50%",
1430
- background: "var(--brand-accent, #6366f1)",
1431
- color: "#fff",
1432
- border: "none",
1433
- cursor: "pointer",
1434
- fontSize: 12,
1435
- fontWeight: 600
1436
- },
1437
- children: user.picture ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: user.picture, alt: user.name, style: { width: "100%", height: "100%", borderRadius: "50%" } }) : initialsOf(user.name, user.email)
1438
- }
1439
- ),
1440
- open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "menu", style: {
1441
- position: "absolute",
1442
- right: 0,
1443
- top: 40,
1444
- minWidth: 200,
1445
- background: "#fff",
1446
- border: "1px solid rgba(15,23,42,0.12)",
1447
- borderRadius: 8,
1448
- boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1449
- padding: 8,
1450
- zIndex: 100
1451
- }, children: [
1452
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 10px", fontSize: 12, opacity: 0.7, borderBottom: "1px solid rgba(15,23,42,0.06)" }, children: [
1453
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
1454
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: user.email })
1455
- ] }),
1456
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: "#0f172a", textDecoration: "none" }, children: "Account" }),
1457
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1458
- "button",
1459
- {
1460
- role: "menuitem",
1461
- type: "button",
1462
- onClick: signOut2,
1463
- style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
1464
- children: "Sign out"
1465
- }
1466
- )
1467
- ] }) : null
1468
- ] });
1696
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1697
+ "div",
1698
+ {
1699
+ className,
1700
+ style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
1701
+ "data-iqauth-sdk-userbutton": "",
1702
+ "data-branding-rev": branding?.brandingRev || "",
1703
+ children: [
1704
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1705
+ "button",
1706
+ {
1707
+ type: "button",
1708
+ "aria-haspopup": "menu",
1709
+ "aria-expanded": open,
1710
+ onClick: () => setOpen((o) => !o),
1711
+ style: {
1712
+ width: 32,
1713
+ height: 32,
1714
+ borderRadius: "50%",
1715
+ background: accent,
1716
+ color: "#fff",
1717
+ border: "none",
1718
+ cursor: "pointer",
1719
+ fontSize: 12,
1720
+ fontWeight: 600
1721
+ },
1722
+ children: user.picture ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: user.picture, alt: user.name, style: { width: "100%", height: "100%", borderRadius: "50%" } }) : initialsOf(user.name, user.email)
1723
+ }
1724
+ ),
1725
+ open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "menu", style: {
1726
+ position: "absolute",
1727
+ right: 0,
1728
+ top: 40,
1729
+ minWidth: 200,
1730
+ background: "#fff",
1731
+ border: "1px solid rgba(15,23,42,0.12)",
1732
+ borderRadius: 8,
1733
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1734
+ padding: 8,
1735
+ zIndex: 100
1736
+ }, children: [
1737
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: "8px 10px", fontSize: 12, opacity: 0.7, borderBottom: "1px solid rgba(15,23,42,0.06)" }, children: [
1738
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
1739
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: user.email })
1740
+ ] }),
1741
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: "Account" }),
1742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1743
+ "button",
1744
+ {
1745
+ role: "menuitem",
1746
+ type: "button",
1747
+ onClick: signOut2,
1748
+ style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
1749
+ children: "Sign out"
1750
+ }
1751
+ )
1752
+ ] }) : null
1753
+ ]
1754
+ }
1755
+ );
1469
1756
  }
1470
1757
  function UserProfile({ iqAuthBaseUrl, className }) {
1758
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1471
1759
  const [user, setUser] = (0, import_react.useState)(null);
1472
1760
  const [oldPassword, setOldPassword] = (0, import_react.useState)("");
1473
1761
  const [newPassword, setNewPassword] = (0, import_react.useState)("");
@@ -1501,8 +1789,8 @@ function UserProfile({ iqAuthBaseUrl, className }) {
1501
1789
  await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
1502
1790
  setSessions((prev) => prev.filter((s) => s.id !== sessionId));
1503
1791
  };
1504
- if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding: null, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading account\u2026" }) });
1505
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding: null, className, children: [
1792
+ if (!user) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Shell, { branding, className, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "Loading account\u2026" }) });
1793
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Shell, { branding, className, children: [
1506
1794
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Your account" }),
1507
1795
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
1508
1796
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: "Profile" }),
@@ -1537,6 +1825,8 @@ function UserProfile({ iqAuthBaseUrl, className }) {
1537
1825
  ] });
1538
1826
  }
1539
1827
  function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1828
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1829
+ const accent = branding?.accentColor || "#6366f1";
1540
1830
  const [memberships, setMemberships] = (0, import_react.useState)([]);
1541
1831
  const [activeTenantId, setActiveTenantId] = (0, import_react.useState)(null);
1542
1832
  const [open, setOpen] = (0, import_react.useState)(false);
@@ -1562,60 +1852,71 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1562
1852
  }
1563
1853
  };
1564
1854
  const active = memberships.find((m) => m.tenantId === activeTenantId);
1565
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className, style: { position: "relative", display: "inline-block" }, children: [
1566
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1567
- "button",
1568
- {
1569
- type: "button",
1570
- "aria-haspopup": "menu",
1571
- "aria-expanded": open,
1572
- onClick: () => setOpen((o) => !o),
1573
- style: { background: "transparent", border: "1px solid rgba(15,23,42,0.15)", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
1574
- children: active?.tenantName || active?.tenantSlug || "Select organization"
1575
- }
1576
- ),
1577
- open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", style: {
1578
- position: "absolute",
1579
- left: 0,
1580
- top: 36,
1581
- minWidth: 220,
1582
- background: "#fff",
1583
- border: "1px solid rgba(15,23,42,0.12)",
1584
- borderRadius: 8,
1585
- boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1586
- padding: 8,
1587
- zIndex: 100
1588
- }, children: memberships.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1589
- "button",
1590
- {
1591
- role: "menuitem",
1592
- type: "button",
1593
- onClick: () => switchTo(m.tenantId),
1594
- style: {
1595
- display: "block",
1596
- width: "100%",
1597
- textAlign: "left",
1598
- padding: "8px 10px",
1599
- background: m.tenantId === activeTenantId ? "rgba(99,102,241,0.08)" : "transparent",
1600
- border: "none",
1601
- borderRadius: 4,
1602
- cursor: "pointer",
1603
- fontSize: 13,
1604
- color: "#0f172a"
1605
- },
1606
- children: [
1607
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
1608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
1609
- ]
1610
- },
1611
- m.tenantId
1612
- )) }) : null
1613
- ] });
1855
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1856
+ "div",
1857
+ {
1858
+ className,
1859
+ style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
1860
+ "data-iqauth-sdk-orgswitcher": "",
1861
+ "data-branding-rev": branding?.brandingRev || "",
1862
+ children: [
1863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1864
+ "button",
1865
+ {
1866
+ type: "button",
1867
+ "aria-haspopup": "menu",
1868
+ "aria-expanded": open,
1869
+ onClick: () => setOpen((o) => !o),
1870
+ style: { background: "transparent", border: `1px solid ${accent}55`, color: branding?.primaryColor || "#0f172a", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
1871
+ children: active?.tenantName || active?.tenantSlug || "Select organization"
1872
+ }
1873
+ ),
1874
+ open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", style: {
1875
+ position: "absolute",
1876
+ left: 0,
1877
+ top: 36,
1878
+ minWidth: 220,
1879
+ background: "#fff",
1880
+ border: "1px solid rgba(15,23,42,0.12)",
1881
+ borderRadius: 8,
1882
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1883
+ padding: 8,
1884
+ zIndex: 100
1885
+ }, children: memberships.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1886
+ "button",
1887
+ {
1888
+ role: "menuitem",
1889
+ type: "button",
1890
+ onClick: () => switchTo(m.tenantId),
1891
+ style: {
1892
+ display: "block",
1893
+ width: "100%",
1894
+ textAlign: "left",
1895
+ padding: "8px 10px",
1896
+ background: m.tenantId === activeTenantId ? `${accent}14` : "transparent",
1897
+ border: "none",
1898
+ borderRadius: 4,
1899
+ cursor: "pointer",
1900
+ fontSize: 13,
1901
+ color: "#0f172a"
1902
+ },
1903
+ children: [
1904
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
1906
+ ]
1907
+ },
1908
+ m.tenantId
1909
+ )) }) : null
1910
+ ]
1911
+ }
1912
+ );
1614
1913
  }
1615
1914
  var __version__ = "phase-bc-1.0.0";
1616
1915
  // Annotate the CommonJS export names for ESM import in node:
1617
1916
  0 && (module.exports = {
1618
1917
  AuthCallback,
1918
+ IQAuthLoaded,
1919
+ IQAuthLoading,
1619
1920
  IQAuthProvider,
1620
1921
  OrganizationSwitcher,
1621
1922
  RedirectToSignIn,
@@ -1626,10 +1927,13 @@ var __version__ = "phase-bc-1.0.0";
1626
1927
  UserButton,
1627
1928
  UserProfile,
1628
1929
  __version__,
1930
+ isSilentSsoEligible,
1931
+ sanitizeBrandCss,
1629
1932
  useAuth,
1630
1933
  useAuthFetch,
1631
1934
  useIQAuthSignInContext,
1632
1935
  useOrganization,
1936
+ useResolvedSdkBranding,
1633
1937
  useSession,
1634
1938
  useUser
1635
1939
  });