@iqauth/sdk 2.0.5 → 2.1.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/dist/react.mjs CHANGED
@@ -212,6 +212,12 @@ function brandStyle(branding) {
212
212
  if (branding.backgroundColor) s["--brand-bg"] = branding.backgroundColor;
213
213
  if (branding.surfaceColor) s["--brand-surface"] = branding.surfaceColor;
214
214
  if (branding.textColor) s["--brand-text"] = branding.textColor;
215
+ if (branding.borderRadius != null && branding.borderRadius !== "") {
216
+ const n = typeof branding.borderRadius === "number" ? `${branding.borderRadius}px` : String(branding.borderRadius);
217
+ s["--brand-radius"] = n;
218
+ }
219
+ if (branding.fontFamilyBody) s["--brand-font-body"] = branding.fontFamilyBody;
220
+ if (branding.fontFamilyHeading) s["--brand-font-heading"] = branding.fontFamilyHeading;
215
221
  return s;
216
222
  }
217
223
  async function jsonFetch(url, init) {
@@ -236,7 +242,7 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
236
242
  let cancelled = false;
237
243
  setLoading(true);
238
244
  const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
239
- fetch(url).then((r) => r.json()).then((payload) => {
245
+ fetch(url, { credentials: "include" }).then((r) => r.json()).then((payload) => {
240
246
  if (cancelled) return;
241
247
  if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
242
248
  setCtx(payload.data);
@@ -254,6 +260,7 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
254
260
  var SHELL_CSS = `
255
261
  .iqauth-sdk-shell {
256
262
  min-height: 100vh;
263
+ width: 100%;
257
264
  display: grid;
258
265
  grid-template-columns: 1fr;
259
266
  background: var(--brand-bg, #f7f7f6);
@@ -262,20 +269,31 @@ var SHELL_CSS = `
262
269
  .iqauth-sdk-hero { display: none; }
263
270
  .iqauth-sdk-pane {
264
271
  display: flex; flex-direction: column; align-items: center; justify-content: center;
265
- padding: 40px 16px; min-height: 100vh;
272
+ padding: 48px 24px; min-height: 100vh;
266
273
  }
267
274
  .iqauth-sdk-card {
268
- width: 100%; max-width: 420px;
275
+ width: 100%; max-width: 460px;
269
276
  background: var(--brand-surface, #ffffff);
270
277
  border: 1px solid rgba(15,23,42,0.08);
271
- border-radius: 14px;
272
- box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(15,23,42,0.04);
278
+ border-radius: var(--brand-radius, 16px);
279
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 12px 32px rgba(15,23,42,0.06);
273
280
  overflow: hidden;
274
281
  }
275
- .iqauth-sdk-card-header { padding: 28px 28px 0; }
276
- .iqauth-sdk-card-header h1 { font-size: 22px; font-weight: 600; margin: 0; line-height: 1.2; }
282
+ .iqauth-sdk-shell, .iqauth-sdk-shell input, .iqauth-sdk-shell button { font-family: var(--brand-font-body, inherit); }
283
+ .iqauth-sdk-shell h1, .iqauth-sdk-shell h2, .iqauth-sdk-shell h3 { font-family: var(--brand-font-heading, var(--brand-font-body, inherit)); }
284
+ .iqauth-sdk-shell[data-layout="centered_card"] { grid-template-columns: 1fr; }
285
+ .iqauth-sdk-shell[data-layout="centered_card"] .iqauth-sdk-hero { display: none; }
286
+ .iqauth-sdk-shell[data-layout="full_bleed"] { background-size: cover; background-position: center; background-repeat: no-repeat; }
287
+ .iqauth-sdk-shell[data-layout="full_bleed"] .iqauth-sdk-hero { display: none; }
288
+ .iqauth-sdk-shell[data-layout="full_bleed"] .iqauth-sdk-pane { background: rgba(15,23,42,0.30); }
289
+ .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); }
290
+ .iqauth-sdk-shell[data-social-style="ghost"] .iqauth-sdk-google-btn { background: transparent; border-color: transparent; color: var(--brand-text, inherit); }
291
+ .iqauth-sdk-shell[data-social-style="solid"] .iqauth-sdk-google-btn { background: var(--brand-primary, #3b82f6); border-color: transparent; color: #fff; }
292
+ .iqauth-sdk-shell[data-social-style="solid"] .iqauth-sdk-google-btn:hover { background: var(--brand-primary, #3b82f6); opacity: 0.92; }
293
+ .iqauth-sdk-card-header { padding: 32px 36px 0; }
294
+ .iqauth-sdk-card-header h1 { font-size: 24px; font-weight: 600; margin: 0; line-height: 1.2; letter-spacing: -0.01em; }
277
295
  .iqauth-sdk-card-header p { margin: 8px 0 0; font-size: 14px; color: rgba(15,23,42,0.65); line-height: 1.5; }
278
- .iqauth-sdk-card-body { padding: 28px 28px 24px; display: flex; flex-direction: column; gap: 18px; }
296
+ .iqauth-sdk-card-body { padding: 32px 36px 28px; display: flex; flex-direction: column; gap: 18px; }
279
297
  .iqauth-sdk-mobile-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }
280
298
  .iqauth-sdk-mobile-brand img { height: 36px; width: auto; }
281
299
  .iqauth-sdk-mobile-brand span { font-size: 16px; font-weight: 600; }
@@ -296,11 +314,11 @@ var SHELL_CSS = `
296
314
  .iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
297
315
  .iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
298
316
 
299
- @media (min-width: 900px) {
300
- .iqauth-sdk-shell { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
317
+ @media (min-width: 768px) {
318
+ .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
301
319
  .iqauth-sdk-hero {
302
320
  display: flex; flex-direction: column; justify-content: space-between;
303
- padding: 48px 56px; color: #ffffff;
321
+ padding: clamp(32px, 4vw, 56px); color: #ffffff;
304
322
  background: linear-gradient(135deg, var(--brand-primary, #3b82f6) 0%, var(--brand-accent, #6366f1) 100%);
305
323
  background-size: cover; background-position: center;
306
324
  position: relative; overflow: hidden; min-height: 100vh;
@@ -309,15 +327,113 @@ var SHELL_CSS = `
309
327
  background-image: var(--iqauth-sdk-hero-image), linear-gradient(135deg, var(--brand-primary, #3b82f6), var(--brand-accent, #6366f1));
310
328
  background-blend-mode: multiply;
311
329
  }
312
- .iqauth-sdk-hero-brand img { height: 44px; width: auto; filter: brightness(0) invert(1); opacity: 0.96; }
313
- .iqauth-sdk-hero-brand .iqauth-sdk-hero-name { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
314
- .iqauth-sdk-hero-content h2 { font-size: 34px; font-weight: 600; line-height: 1.15; margin: 0 0 14px; letter-spacing: -0.015em; }
315
- .iqauth-sdk-hero-content p { font-size: 15px; line-height: 1.6; opacity: 0.92; margin: 0; max-width: 460px; white-space: pre-wrap; }
330
+ .iqauth-sdk-hero-brand img { height: 40px; width: auto; filter: brightness(0) invert(1); opacity: 0.96; }
331
+ .iqauth-sdk-hero-brand .iqauth-sdk-hero-name { font-size: 20px; font-weight: 600; letter-spacing: -0.01em; }
332
+ .iqauth-sdk-hero-content { max-width: 520px; }
333
+ .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; }
334
+ .iqauth-sdk-hero-content p { font-size: 15px; line-height: 1.6; opacity: 0.92; margin: 0; white-space: pre-wrap; }
316
335
  .iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
317
336
  .iqauth-sdk-mobile-brand { display: none; }
318
337
  }
338
+ @media (min-width: 1280px) {
339
+ .iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
340
+ }
319
341
  `;
320
342
  var sdkShellStylesInjected = false;
343
+ var SDK_CSS_MAX_LEN = 50 * 1024;
344
+ var SDK_CSS_FORBIDDEN = [
345
+ /<\/?\s*style[^>]*>/gi,
346
+ /<\/?\s*script[^>]*>/gi,
347
+ /<!--[\s\S]*?-->/g,
348
+ /@import\s+[^;]*;?/gi,
349
+ /expression\s*\(/gi,
350
+ /behavior\s*:/gi,
351
+ /-moz-binding\s*:/gi,
352
+ /javascript\s*:/gi,
353
+ /vbscript\s*:/gi
354
+ ];
355
+ var SDK_URL_DATA_RE = /url\s*\(\s*(['"]?)\s*data\s*:[^)]*\)/gi;
356
+ function sanitizeBrandCss(input) {
357
+ if (!input) return "";
358
+ let out = String(input);
359
+ if (out.length > SDK_CSS_MAX_LEN) out = out.slice(0, SDK_CSS_MAX_LEN);
360
+ out = out.replace(/</g, "");
361
+ for (const re of SDK_CSS_FORBIDDEN) out = out.replace(re, "");
362
+ out = out.replace(SDK_URL_DATA_RE, "url()");
363
+ return out;
364
+ }
365
+ function SdkBrandLogo({ branding, alt, fallback }) {
366
+ const light = branding?.logoLightUrl || branding?.logoUrl || null;
367
+ const dark = branding?.logoDarkUrl || null;
368
+ if (!light && !dark) return /* @__PURE__ */ jsx(Fragment2, { children: fallback });
369
+ const fallbackSrc = light || dark;
370
+ return /* @__PURE__ */ jsxs("picture", { "data-iqauth-sdk-logo": "", children: [
371
+ dark ? /* @__PURE__ */ jsx("source", { srcSet: dark, media: "(prefers-color-scheme: dark)" }) : null,
372
+ light ? /* @__PURE__ */ jsx("source", { srcSet: light, media: "(prefers-color-scheme: light)" }) : null,
373
+ /* @__PURE__ */ jsx("img", { src: fallbackSrc, alt })
374
+ ] });
375
+ }
376
+ var sdkBrandingCache = /* @__PURE__ */ new Map();
377
+ var SDK_BRANDING_TTL_MS = 6e4;
378
+ function flattenBrandingPayload(data) {
379
+ const e = data && data.effective || {};
380
+ const meta = data && data.meta || {};
381
+ return {
382
+ brandName: e.brandName ?? data?.brandName ?? null,
383
+ logoUrl: e.logoLightUrl ?? data?.logoUrl ?? null,
384
+ logoLightUrl: e.logoLightUrl ?? data?.logoLightUrl ?? null,
385
+ logoDarkUrl: e.logoDarkUrl ?? data?.logoDarkUrl ?? null,
386
+ faviconUrl: e.faviconUrl ?? data?.faviconUrl ?? null,
387
+ primaryColor: e.primaryColor ?? data?.primaryColor ?? null,
388
+ accentColor: e.accentColor ?? data?.accentColor ?? null,
389
+ backgroundColor: e.backgroundColor ?? data?.backgroundColor ?? null,
390
+ surfaceColor: e.surfaceColor ?? data?.surfaceColor ?? null,
391
+ textColor: e.textColor ?? data?.textColor ?? null,
392
+ fontFamilyBody: e.fontFamilyBody ?? data?.fontFamilyBody ?? null,
393
+ fontFamilyHeading: e.fontFamilyHeading ?? data?.fontFamilyHeading ?? null,
394
+ customFontUrl: e.customFontUrl ?? data?.customFontUrl ?? null,
395
+ borderRadius: e.borderRadius ?? data?.borderRadius ?? null,
396
+ backgroundImageUrl: e.backgroundImageUrl ?? data?.backgroundImageUrl ?? null,
397
+ customCss: e.customCss ?? data?.customCss ?? null,
398
+ loginLayout: e.loginLayout ?? data?.loginLayout ?? null,
399
+ socialButtonStyle: e.socialButtonStyle ?? data?.socialButtonStyle ?? null,
400
+ footerText: e.footerText ?? data?.footerText ?? null,
401
+ brandingRev: meta.brandingRev ?? data?.brandingRev ?? null
402
+ };
403
+ }
404
+ function useResolvedSdkBranding(iqAuthBaseUrl, appId) {
405
+ const ctx = useContext(IQAuthContext);
406
+ const resolvedAppId = appId ?? ctx?.manager?.appKey ?? null;
407
+ const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/branding${resolvedAppId ? `?appId=${encodeURIComponent(resolvedAppId)}` : ""}`;
408
+ const cached = sdkBrandingCache.get(url);
409
+ const fresh = cached && Date.now() - cached.ts < SDK_BRANDING_TTL_MS ? cached.data : null;
410
+ const [b, setB] = useState(fresh);
411
+ useEffect(() => {
412
+ let cancelled = false;
413
+ const entry = sdkBrandingCache.get(url);
414
+ const headers = {};
415
+ if (entry?.rev) headers["If-None-Match"] = `W/"brand-${entry.rev}"`;
416
+ if (entry) setB(entry.data);
417
+ fetch(url, { credentials: "include", headers }).then(async (r) => {
418
+ if (cancelled) return;
419
+ if (r.status === 304 && entry) {
420
+ sdkBrandingCache.set(url, { ...entry, ts: Date.now() });
421
+ return;
422
+ }
423
+ if (!r.ok) return;
424
+ const p = await r.json().catch(() => null);
425
+ if (!p?.data) return;
426
+ const flat = flattenBrandingPayload(p.data);
427
+ sdkBrandingCache.set(url, { ts: Date.now(), rev: flat.brandingRev || "", data: flat });
428
+ setB(flat);
429
+ }).catch(() => {
430
+ });
431
+ return () => {
432
+ cancelled = true;
433
+ };
434
+ }, [url]);
435
+ return b;
436
+ }
321
437
  function ensureSdkShellStyles() {
322
438
  if (typeof document === "undefined" || sdkShellStylesInjected) return;
323
439
  const tag = document.createElement("style");
@@ -363,35 +479,61 @@ function Shell({
363
479
  const brandVars = brandStyle(branding);
364
480
  const brandName = branding?.brandName || "IQAuth";
365
481
  const heroImage = branding?.heroImageUrl || null;
482
+ const layout = (branding?.loginLayout || "split_screen").toString();
483
+ const bgImage = branding?.backgroundImageUrl || null;
484
+ const fontUrl = branding?.customFontUrl || null;
485
+ const socialStyle = (branding?.socialButtonStyle || "").toString();
366
486
  const heroStyle = heroImage ? { ["--iqauth-sdk-hero-image"]: `url("${heroImage.replace(/"/g, '\\"')}")` } : {};
487
+ const shellStyle = {
488
+ ...brandVars,
489
+ ...layout === "full_bleed" && bgImage ? { backgroundImage: `linear-gradient(rgba(15,23,42,0.35), rgba(15,23,42,0.45)), url("${bgImage.replace(/"/g, '\\"')}")` } : {}
490
+ };
367
491
  const supportLink = branding?.supportUrl ? branding.supportUrl : branding?.supportEmail ? `mailto:${branding.supportEmail}` : null;
368
492
  const hasFooterLinks = !!(branding?.termsUrl || branding?.privacyUrl || supportLink);
369
- return /* @__PURE__ */ jsxs("div", { className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`, style: brandVars, "data-iqauth-shell": "", children: [
370
- branding?.customCss ? /* @__PURE__ */ jsx("style", { "data-iqauth-sdk-custom": true, children: branding.customCss }) : null,
371
- /* @__PURE__ */ jsxs("aside", { className: "iqauth-sdk-hero", "data-bg-image": heroImage ? "true" : "false", style: heroStyle, "aria-hidden": "true", children: [
372
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-hero-brand", style: { display: "flex", alignItems: "center", gap: 12 }, children: branding?.logoUrl ? /* @__PURE__ */ jsx("img", { src: branding.logoUrl, alt: "" }) : /* @__PURE__ */ jsx("span", { className: "iqauth-sdk-hero-name", children: brandName }) }),
373
- /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-hero-content", children: [
374
- /* @__PURE__ */ jsx("h2", { children: branding?.tagline || `Welcome to ${brandName}` }),
375
- /* @__PURE__ */ jsx("p", { children: branding?.loginSideCopy || branding?.loginSubheadline || `Sign in to continue to your ${brandName} workspace.` })
376
- ] }),
377
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-hero-foot", children: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}` })
378
- ] }),
379
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ jsxs("main", { style: { width: "100%", maxWidth: 420 }, children: [
380
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-mobile-brand", children: branding?.logoUrl ? /* @__PURE__ */ jsx("img", { src: branding.logoUrl, alt: `${brandName} logo` }) : /* @__PURE__ */ jsx("span", { children: brandName }) }),
381
- /* @__PURE__ */ jsxs("section", { className: "iqauth-sdk-card", children: [
382
- title || subtitle ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-card-header", children: [
383
- title ? /* @__PURE__ */ jsx("h1", { children: title }) : null,
384
- subtitle ? /* @__PURE__ */ jsx("p", { children: subtitle }) : null
385
- ] }) : null,
386
- /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-card-body", children })
387
- ] }),
388
- hasFooterLinks ? /* @__PURE__ */ jsx("footer", { className: "iqauth-sdk-footer", children: /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-footer-links", children: [
389
- branding?.termsUrl ? /* @__PURE__ */ jsx("a", { href: branding.termsUrl, target: "_blank", rel: "noreferrer noopener", children: "Terms" }) : null,
390
- branding?.privacyUrl ? /* @__PURE__ */ jsx("a", { href: branding.privacyUrl, target: "_blank", rel: "noreferrer noopener", children: "Privacy" }) : null,
391
- supportLink ? /* @__PURE__ */ jsx("a", { href: supportLink, target: "_blank", rel: "noreferrer noopener", children: "Support" }) : null
392
- ] }) }) : null
393
- ] }) })
394
- ] });
493
+ return /* @__PURE__ */ jsxs(
494
+ "div",
495
+ {
496
+ className: `iqauth-sdk-shell${className ? ` ${className}` : ""}`,
497
+ "data-layout": layout,
498
+ "data-social-style": socialStyle || void 0,
499
+ style: shellStyle,
500
+ "data-iqauth-shell": "",
501
+ "data-branding-rev": branding?.brandingRev || "",
502
+ children: [
503
+ /* @__PURE__ */ jsx("style", { "data-brand": true, children: [
504
+ fontUrl ? `@font-face{font-family:"${(branding?.fontFamilyBody || "Brand").replace(/"/g, "")}";src:url("${fontUrl.replace(/"/g, '\\"')}");font-display:swap;}` : "",
505
+ sanitizeBrandCss(branding?.customCss)
506
+ ].filter(Boolean).join("\n") }),
507
+ /* @__PURE__ */ jsxs("aside", { className: "iqauth-sdk-hero", "data-bg-image": heroImage ? "true" : "false", style: heroStyle, "aria-hidden": "true", children: [
508
+ /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-hero-brand", style: { display: "flex", alignItems: "center", gap: 12 }, children: /* @__PURE__ */ jsx(SdkBrandLogo, { branding, alt: "", fallback: /* @__PURE__ */ jsx("span", { className: "iqauth-sdk-hero-name", children: brandName }) }) }),
509
+ /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-hero-content", children: [
510
+ /* @__PURE__ */ jsx("h2", { children: branding?.tagline || `Welcome to ${brandName}` }),
511
+ /* @__PURE__ */ jsx("p", { children: branding?.loginSideCopy || branding?.loginSubheadline || `Sign in to continue to your ${brandName} workspace.` })
512
+ ] }),
513
+ /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-hero-foot", children: `\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} ${brandName}` })
514
+ ] }),
515
+ /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-pane", children: /* @__PURE__ */ jsxs("main", { style: { width: "100%", maxWidth: 420 }, children: [
516
+ /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-mobile-brand", children: /* @__PURE__ */ jsx(SdkBrandLogo, { branding, alt: `${brandName} logo`, fallback: /* @__PURE__ */ jsx("span", { children: brandName }) }) }),
517
+ /* @__PURE__ */ jsxs("section", { className: "iqauth-sdk-card", children: [
518
+ title || subtitle ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-card-header", children: [
519
+ title ? /* @__PURE__ */ jsx("h1", { children: title }) : null,
520
+ subtitle ? /* @__PURE__ */ jsx("p", { children: subtitle }) : null
521
+ ] }) : null,
522
+ /* @__PURE__ */ jsx("div", { className: "iqauth-sdk-card-body", children })
523
+ ] }),
524
+ hasFooterLinks || branding?.footerText ? /* @__PURE__ */ jsxs("footer", { className: "iqauth-sdk-footer", children: [
525
+ branding?.footerText ? /* @__PURE__ */ jsx("div", { "data-testid": "text-brand-footer-sdk", style: { fontSize: 12, opacity: 0.75 }, children: branding.footerText }) : null,
526
+ hasFooterLinks ? /* @__PURE__ */ jsxs("div", { className: "iqauth-sdk-footer-links", children: [
527
+ branding?.termsUrl ? /* @__PURE__ */ jsx("a", { href: branding.termsUrl, target: "_blank", rel: "noreferrer noopener", children: "Terms" }) : null,
528
+ branding?.privacyUrl ? /* @__PURE__ */ jsx("a", { href: branding.privacyUrl, target: "_blank", rel: "noreferrer noopener", children: "Privacy" }) : null,
529
+ supportLink ? /* @__PURE__ */ jsx("a", { href: supportLink, target: "_blank", rel: "noreferrer noopener", children: "Support" }) : null
530
+ ] }) : null
531
+ ] }) : null
532
+ ] }) })
533
+ ]
534
+ },
535
+ branding?.brandingRev || void 0
536
+ );
395
537
  }
396
538
  function Field({ label, children }) {
397
539
  return /* @__PURE__ */ jsxs("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 13 }, children: [
@@ -461,7 +603,15 @@ function ErrorBanner({ message }) {
461
603
  color: "#b91c1c"
462
604
  }, children: message });
463
605
  }
464
- function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
606
+ function isSilentSsoEligible(ctx, effectivePrompt) {
607
+ if (!ctx) return false;
608
+ if (effectivePrompt === "login") return false;
609
+ if (!ctx.session) return false;
610
+ if (!ctx.app.defaultClientId) return false;
611
+ if (!ctx.returnAllowed) return false;
612
+ return true;
613
+ }
614
+ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className, prompt }) {
465
615
  const { ctx, loading, error } = useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo);
466
616
  const [email, setEmail] = useState("");
467
617
  const [password, setPassword] = useState("");
@@ -470,6 +620,18 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
470
620
  const [mfa, setMfa] = useState(null);
471
621
  const [tenantSel, setTenantSel] = useState(null);
472
622
  const [oauthExchanging, setOauthExchanging] = useState(false);
623
+ const [silent, setSilent] = useState("idle");
624
+ const [forcePrompt, setForcePrompt] = useState(false);
625
+ const effectivePrompt = useMemo(() => {
626
+ if (prompt === "login" || forcePrompt) return "login";
627
+ if (typeof window !== "undefined") {
628
+ try {
629
+ if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
630
+ } catch {
631
+ }
632
+ }
633
+ return void 0;
634
+ }, [prompt, forcePrompt]);
473
635
  const oidcPayload = () => ({
474
636
  client_id: ctx?.app.defaultClientId,
475
637
  redirect_uri: returnTo,
@@ -559,6 +721,58 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
559
721
  const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/google?redirect_uri=${encodeURIComponent(bridgeUrl)}&client_id=${encodeURIComponent(ctx.app.defaultClientId)}`;
560
722
  window.location.href = url;
561
723
  };
724
+ useEffect(() => {
725
+ if (loading || error || !ctx) return;
726
+ if (effectivePrompt === "login") {
727
+ setSilent("skipped");
728
+ return;
729
+ }
730
+ if (silent !== "idle") return;
731
+ if (!ctx.session || !ctx.app.defaultClientId || !ctx.returnAllowed) {
732
+ setSilent("skipped");
733
+ return;
734
+ }
735
+ setSilent("trying");
736
+ (async () => {
737
+ try {
738
+ const r = await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/oidc/sso-resume`, {
739
+ method: "POST",
740
+ headers: { "Content-Type": "application/json" },
741
+ credentials: "include",
742
+ body: JSON.stringify(oidcPayload())
743
+ });
744
+ const payload = await r.json().catch(() => ({}));
745
+ if (payload?.type === "redirect" && payload.redirectUrl) {
746
+ (onRedirect || ((u) => {
747
+ window.location.replace(u);
748
+ }))(payload.redirectUrl);
749
+ return;
750
+ }
751
+ if (payload?.type === "tenant_selection") {
752
+ setTenantSel({ token: payload.tenantSelectionToken, tenants: payload.tenants || [] });
753
+ setSilent("failed");
754
+ return;
755
+ }
756
+ setSilent("failed");
757
+ } catch {
758
+ setSilent("failed");
759
+ }
760
+ })();
761
+ }, [loading, error, ctx, effectivePrompt]);
762
+ const switchAccount = (e) => {
763
+ if (e) e.preventDefault();
764
+ setForcePrompt(true);
765
+ setSilent("skipped");
766
+ setTenantSel(null);
767
+ if (typeof window !== "undefined") {
768
+ try {
769
+ const u = new URL(window.location.href);
770
+ u.searchParams.set("prompt", "login");
771
+ window.history.replaceState({}, "", u.toString());
772
+ } catch {
773
+ }
774
+ }
775
+ };
562
776
  useEffect(() => {
563
777
  if (!ctx?.app.defaultClientId) return;
564
778
  const params = new URLSearchParams(window.location.search);
@@ -596,6 +810,13 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
596
810
  if (loading || oauthExchanging) return /* @__PURE__ */ jsx(Shell, { branding: ctx?.branding || null, className, title: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026", children: /* @__PURE__ */ jsx("p", { children: oauthExchanging ? "Completing sign-in\u2026" : "Loading\u2026" }) });
597
811
  if (error || !ctx) return /* @__PURE__ */ jsx(Shell, { branding: null, className, title: "Application unavailable", children: /* @__PURE__ */ jsx(ErrorBanner, { message: error || "Failed to load app context" }) });
598
812
  if (!ctx.returnAllowed) return /* @__PURE__ */ jsx(Shell, { branding: ctx.branding, className, title: "Invalid redirect", children: /* @__PURE__ */ jsx(ErrorBanner, { message: `returnTo "${returnTo}" is not in this app's allowed origins.` }) });
813
+ const silentEligible = isSilentSsoEligible(ctx, effectivePrompt);
814
+ if (silentEligible && silent !== "failed") {
815
+ return /* @__PURE__ */ 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: [
816
+ /* @__PURE__ */ jsx("p", { "data-testid": "text-silent-resume", style: { fontSize: 14, opacity: 0.8 }, children: "Resuming your session." }),
817
+ /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", style: { fontSize: 13 }, children: "Not you? Use a different account" })
818
+ ] });
819
+ }
599
820
  const cardTitle = ctx.branding?.loginHeadline || `Sign in to ${ctx.app.name}`;
600
821
  const cardSubtitle = ctx.branding?.loginSubheadline || void 0;
601
822
  return /* @__PURE__ */ jsxs(Shell, { branding: ctx.branding, className, title: cardTitle, subtitle: cardSubtitle, children: [
@@ -645,7 +866,11 @@ function SignIn({ iqAuthBaseUrl, appKey, returnTo, onRedirect, className }) {
645
866
  /* @__PURE__ */ jsx(Field, { label: "Password", children: /* @__PURE__ */ jsx("input", { style: inputStyle(), type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value) }) }),
646
867
  /* @__PURE__ */ jsx(PrimaryButton, { type: "submit", disabled: submitting || !email || !password, children: submitting ? "Signing in\u2026" : "Sign in" })
647
868
  ] })
648
- ] })
869
+ ] }),
870
+ (silent === "failed" || effectivePrompt === "login" && ctx.session) && !mfa ? /* @__PURE__ */ jsxs("p", { style: { marginTop: 12, fontSize: 13, opacity: 0.75 }, children: [
871
+ silent === "failed" && ctx.session ? "Couldn't resume your session. " : null,
872
+ /* @__PURE__ */ jsx("a", { href: "#", onClick: switchAccount, "data-testid": "link-switch-account", children: "Use a different account" })
873
+ ] }) : null
649
874
  ] });
650
875
  }
651
876
  function SignUp({ iqAuthBaseUrl, appKey, returnTo, onSuccess, className }) {
@@ -696,6 +921,9 @@ function initialsOf(name, email) {
696
921
  function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
697
922
  const [user, setUser] = useState(null);
698
923
  const [open, setOpen] = useState(false);
924
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
925
+ const accent = branding?.accentColor || "#6366f1";
926
+ const primary = branding?.primaryColor || "#0f172a";
699
927
  useEffect(() => {
700
928
  let cancelled = false;
701
929
  fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/me`, { credentials: "include" }).then((r) => r.json()).then((p) => {
@@ -716,59 +944,69 @@ function UserButton({ iqAuthBaseUrl, accountUrl, onSignOut, className }) {
716
944
  };
717
945
  if (!user) return null;
718
946
  const target = accountUrl || `${iqAuthBaseUrl.replace(/\/$/, "")}/account`;
719
- return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", display: "inline-block" }, children: [
720
- /* @__PURE__ */ jsx(
721
- "button",
722
- {
723
- type: "button",
724
- "aria-haspopup": "menu",
725
- "aria-expanded": open,
726
- onClick: () => setOpen((o) => !o),
727
- style: {
728
- width: 32,
729
- height: 32,
730
- borderRadius: "50%",
731
- background: "var(--brand-accent, #6366f1)",
732
- color: "#fff",
733
- border: "none",
734
- cursor: "pointer",
735
- fontSize: 12,
736
- fontWeight: 600
737
- },
738
- children: user.picture ? /* @__PURE__ */ jsx("img", { src: user.picture, alt: user.name, style: { width: "100%", height: "100%", borderRadius: "50%" } }) : initialsOf(user.name, user.email)
739
- }
740
- ),
741
- open ? /* @__PURE__ */ jsxs("div", { role: "menu", style: {
742
- position: "absolute",
743
- right: 0,
744
- top: 40,
745
- minWidth: 200,
746
- background: "#fff",
747
- border: "1px solid rgba(15,23,42,0.12)",
748
- borderRadius: 8,
749
- boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
750
- padding: 8,
751
- zIndex: 100
752
- }, children: [
753
- /* @__PURE__ */ jsxs("div", { style: { padding: "8px 10px", fontSize: 12, opacity: 0.7, borderBottom: "1px solid rgba(15,23,42,0.06)" }, children: [
754
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
755
- /* @__PURE__ */ jsx("div", { children: user.email })
756
- ] }),
757
- /* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: "#0f172a", textDecoration: "none" }, children: "Account" }),
758
- /* @__PURE__ */ jsx(
759
- "button",
760
- {
761
- role: "menuitem",
762
- type: "button",
763
- onClick: signOut2,
764
- style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
765
- children: "Sign out"
766
- }
767
- )
768
- ] }) : null
769
- ] });
947
+ return /* @__PURE__ */ jsxs(
948
+ "div",
949
+ {
950
+ className,
951
+ style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
952
+ "data-iqauth-sdk-userbutton": "",
953
+ "data-branding-rev": branding?.brandingRev || "",
954
+ children: [
955
+ /* @__PURE__ */ jsx(
956
+ "button",
957
+ {
958
+ type: "button",
959
+ "aria-haspopup": "menu",
960
+ "aria-expanded": open,
961
+ onClick: () => setOpen((o) => !o),
962
+ style: {
963
+ width: 32,
964
+ height: 32,
965
+ borderRadius: "50%",
966
+ background: accent,
967
+ color: "#fff",
968
+ border: "none",
969
+ cursor: "pointer",
970
+ fontSize: 12,
971
+ fontWeight: 600
972
+ },
973
+ children: user.picture ? /* @__PURE__ */ jsx("img", { src: user.picture, alt: user.name, style: { width: "100%", height: "100%", borderRadius: "50%" } }) : initialsOf(user.name, user.email)
974
+ }
975
+ ),
976
+ open ? /* @__PURE__ */ jsxs("div", { role: "menu", style: {
977
+ position: "absolute",
978
+ right: 0,
979
+ top: 40,
980
+ minWidth: 200,
981
+ background: "#fff",
982
+ border: "1px solid rgba(15,23,42,0.12)",
983
+ borderRadius: 8,
984
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
985
+ padding: 8,
986
+ zIndex: 100
987
+ }, children: [
988
+ /* @__PURE__ */ jsxs("div", { style: { padding: "8px 10px", fontSize: 12, opacity: 0.7, borderBottom: "1px solid rgba(15,23,42,0.06)" }, children: [
989
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, color: "#0f172a" }, children: user.name }),
990
+ /* @__PURE__ */ jsx("div", { children: user.email })
991
+ ] }),
992
+ /* @__PURE__ */ jsx("a", { href: target, role: "menuitem", style: { display: "block", padding: "8px 10px", fontSize: 13, color: primary, textDecoration: "none" }, children: "Account" }),
993
+ /* @__PURE__ */ jsx(
994
+ "button",
995
+ {
996
+ role: "menuitem",
997
+ type: "button",
998
+ onClick: signOut2,
999
+ style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", fontSize: 13, background: "transparent", border: "none", cursor: "pointer", color: "#b91c1c" },
1000
+ children: "Sign out"
1001
+ }
1002
+ )
1003
+ ] }) : null
1004
+ ]
1005
+ }
1006
+ );
770
1007
  }
771
1008
  function UserProfile({ iqAuthBaseUrl, className }) {
1009
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
772
1010
  const [user, setUser] = useState(null);
773
1011
  const [oldPassword, setOldPassword] = useState("");
774
1012
  const [newPassword, setNewPassword] = useState("");
@@ -802,8 +1040,8 @@ function UserProfile({ iqAuthBaseUrl, className }) {
802
1040
  await fetch(`${iqAuthBaseUrl.replace(/\/$/, "")}/api/v1/auth/sessions/${sessionId}`, { method: "DELETE", credentials: "include" });
803
1041
  setSessions((prev) => prev.filter((s) => s.id !== sessionId));
804
1042
  };
805
- if (!user) return /* @__PURE__ */ jsx(Shell, { branding: null, className, children: /* @__PURE__ */ jsx("p", { children: "Loading account\u2026" }) });
806
- return /* @__PURE__ */ jsxs(Shell, { branding: null, className, children: [
1043
+ if (!user) return /* @__PURE__ */ jsx(Shell, { branding, className, children: /* @__PURE__ */ jsx("p", { children: "Loading account\u2026" }) });
1044
+ return /* @__PURE__ */ jsxs(Shell, { branding, className, children: [
807
1045
  /* @__PURE__ */ jsx("h2", { style: { fontSize: 20, fontWeight: 600, margin: "0 0 12px" }, children: "Your account" }),
808
1046
  /* @__PURE__ */ jsxs("section", { "aria-labelledby": "iqauth-profile", style: { marginBottom: 20 }, children: [
809
1047
  /* @__PURE__ */ jsx("h3", { id: "iqauth-profile", style: { fontSize: 14, fontWeight: 600 }, children: "Profile" }),
@@ -838,6 +1076,8 @@ function UserProfile({ iqAuthBaseUrl, className }) {
838
1076
  ] });
839
1077
  }
840
1078
  function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1079
+ const branding = useResolvedSdkBranding(iqAuthBaseUrl);
1080
+ const accent = branding?.accentColor || "#6366f1";
841
1081
  const [memberships, setMemberships] = useState([]);
842
1082
  const [activeTenantId, setActiveTenantId] = useState(null);
843
1083
  const [open, setOpen] = useState(false);
@@ -863,55 +1103,64 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
863
1103
  }
864
1104
  };
865
1105
  const active = memberships.find((m) => m.tenantId === activeTenantId);
866
- return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", display: "inline-block" }, children: [
867
- /* @__PURE__ */ jsx(
868
- "button",
869
- {
870
- type: "button",
871
- "aria-haspopup": "menu",
872
- "aria-expanded": open,
873
- onClick: () => setOpen((o) => !o),
874
- style: { background: "transparent", border: "1px solid rgba(15,23,42,0.15)", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
875
- children: active?.tenantName || active?.tenantSlug || "Select organization"
876
- }
877
- ),
878
- open ? /* @__PURE__ */ jsx("div", { role: "menu", style: {
879
- position: "absolute",
880
- left: 0,
881
- top: 36,
882
- minWidth: 220,
883
- background: "#fff",
884
- border: "1px solid rgba(15,23,42,0.12)",
885
- borderRadius: 8,
886
- boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
887
- padding: 8,
888
- zIndex: 100
889
- }, children: memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ jsxs(
890
- "button",
891
- {
892
- role: "menuitem",
893
- type: "button",
894
- onClick: () => switchTo(m.tenantId),
895
- style: {
896
- display: "block",
897
- width: "100%",
898
- textAlign: "left",
899
- padding: "8px 10px",
900
- background: m.tenantId === activeTenantId ? "rgba(99,102,241,0.08)" : "transparent",
901
- border: "none",
902
- borderRadius: 4,
903
- cursor: "pointer",
904
- fontSize: 13,
905
- color: "#0f172a"
906
- },
907
- children: [
908
- /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
909
- /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
910
- ]
911
- },
912
- m.tenantId
913
- )) }) : null
914
- ] });
1106
+ return /* @__PURE__ */ jsxs(
1107
+ "div",
1108
+ {
1109
+ className,
1110
+ style: { position: "relative", display: "inline-block", ...brandStyle(branding) },
1111
+ "data-iqauth-sdk-orgswitcher": "",
1112
+ "data-branding-rev": branding?.brandingRev || "",
1113
+ children: [
1114
+ /* @__PURE__ */ jsx(
1115
+ "button",
1116
+ {
1117
+ type: "button",
1118
+ "aria-haspopup": "menu",
1119
+ "aria-expanded": open,
1120
+ onClick: () => setOpen((o) => !o),
1121
+ style: { background: "transparent", border: `1px solid ${accent}55`, color: branding?.primaryColor || "#0f172a", padding: "6px 12px", borderRadius: 6, cursor: "pointer", fontSize: 13 },
1122
+ children: active?.tenantName || active?.tenantSlug || "Select organization"
1123
+ }
1124
+ ),
1125
+ open ? /* @__PURE__ */ jsx("div", { role: "menu", style: {
1126
+ position: "absolute",
1127
+ left: 0,
1128
+ top: 36,
1129
+ minWidth: 220,
1130
+ background: "#fff",
1131
+ border: "1px solid rgba(15,23,42,0.12)",
1132
+ borderRadius: 8,
1133
+ boxShadow: "0 4px 12px rgba(0,0,0,0.08)",
1134
+ padding: 8,
1135
+ zIndex: 100
1136
+ }, children: memberships.length === 0 ? /* @__PURE__ */ jsx("p", { style: { fontSize: 13, opacity: 0.6, padding: "4px 6px" }, children: "No memberships" }) : memberships.map((m) => /* @__PURE__ */ jsxs(
1137
+ "button",
1138
+ {
1139
+ role: "menuitem",
1140
+ type: "button",
1141
+ onClick: () => switchTo(m.tenantId),
1142
+ style: {
1143
+ display: "block",
1144
+ width: "100%",
1145
+ textAlign: "left",
1146
+ padding: "8px 10px",
1147
+ background: m.tenantId === activeTenantId ? `${accent}14` : "transparent",
1148
+ border: "none",
1149
+ borderRadius: 4,
1150
+ cursor: "pointer",
1151
+ fontSize: 13,
1152
+ color: "#0f172a"
1153
+ },
1154
+ children: [
1155
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 500 }, children: m.tenantName || m.tenantSlug || m.tenantId }),
1156
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.6 }, children: m.roles.join(", ") })
1157
+ ]
1158
+ },
1159
+ m.tenantId
1160
+ )) }) : null
1161
+ ]
1162
+ }
1163
+ );
915
1164
  }
916
1165
  var __version__ = "phase-bc-1.0.0";
917
1166
  export {
@@ -926,10 +1175,13 @@ export {
926
1175
  UserButton,
927
1176
  UserProfile,
928
1177
  __version__,
1178
+ isSilentSsoEligible,
1179
+ sanitizeBrandCss,
929
1180
  useAuth,
930
1181
  useAuthFetch,
931
1182
  useIQAuthSignInContext,
932
1183
  useOrganization,
1184
+ useResolvedSdkBranding,
933
1185
  useSession,
934
1186
  useUser
935
1187
  };