@returningai/widget-sdk 1.8.4 → 1.8.6

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.
@@ -12343,6 +12343,109 @@ function isTokenExpired(expiry) {
12343
12343
  if (!expiry) return true;
12344
12344
  return Date.now() >= expiry - 6e4;
12345
12345
  }
12346
+ const RETRY_DELAYS_MS = [5e3, 15e3, 3e4];
12347
+ const RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
12348
+ function isPermanentAuthFailure(error) {
12349
+ if (!error) return false;
12350
+ const { status } = error;
12351
+ return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
12352
+ }
12353
+ function createDefaultEnvironment$1() {
12354
+ const visibilityDocument = typeof document === "undefined" ? void 0 : document;
12355
+ const pageWindow = typeof window === "undefined" ? void 0 : window;
12356
+ return {
12357
+ isHidden: () => (visibilityDocument == null ? void 0 : visibilityDocument.visibilityState) === "hidden",
12358
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
12359
+ clearTimeout: (timerId) => globalThis.clearTimeout(timerId),
12360
+ addVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.addEventListener("visibilitychange", listener),
12361
+ removeVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.removeEventListener("visibilitychange", listener),
12362
+ addPageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("pageshow", listener),
12363
+ removePageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("pageshow", listener),
12364
+ addOnlineListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("online", listener),
12365
+ removeOnlineListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("online", listener)
12366
+ };
12367
+ }
12368
+ function createAuthRenewalRecoveryController(retry, environment = createDefaultEnvironment$1()) {
12369
+ let pending = false;
12370
+ let retryInFlight = false;
12371
+ let rescheduleRequested = false;
12372
+ let retryAttempt = 0;
12373
+ let timerId = null;
12374
+ let listening = false;
12375
+ const clearTimer = () => {
12376
+ if (timerId === null) return;
12377
+ environment.clearTimeout(timerId);
12378
+ timerId = null;
12379
+ };
12380
+ const disarmSignals = () => {
12381
+ if (!listening) return;
12382
+ listening = false;
12383
+ environment.removeVisibilityChangeListener(handleForeground);
12384
+ environment.removePageShowListener(handleForeground);
12385
+ environment.removeOnlineListener(handleOnline);
12386
+ };
12387
+ const reset = () => {
12388
+ pending = false;
12389
+ retryAttempt = 0;
12390
+ rescheduleRequested = false;
12391
+ clearTimer();
12392
+ disarmSignals();
12393
+ };
12394
+ const requestRetry = (resetBudget, allowWhileHidden) => {
12395
+ if (!pending || retryInFlight || !allowWhileHidden && environment.isHidden()) {
12396
+ return;
12397
+ }
12398
+ if (resetBudget) retryAttempt = 0;
12399
+ clearTimer();
12400
+ retryInFlight = true;
12401
+ Promise.resolve().then(retry).catch((error) => {
12402
+ console.error("[rai-widget] Session renewal retry failed:", error);
12403
+ }).finally(() => {
12404
+ retryInFlight = false;
12405
+ if (rescheduleRequested) {
12406
+ rescheduleRequested = false;
12407
+ scheduleRetry();
12408
+ }
12409
+ });
12410
+ };
12411
+ function handleForeground() {
12412
+ if (environment.isHidden()) return;
12413
+ requestRetry(true, false);
12414
+ }
12415
+ function handleOnline() {
12416
+ requestRetry(true, true);
12417
+ }
12418
+ const armSignals = () => {
12419
+ if (listening) return;
12420
+ listening = true;
12421
+ environment.addVisibilityChangeListener(handleForeground);
12422
+ environment.addPageShowListener(handleForeground);
12423
+ environment.addOnlineListener(handleOnline);
12424
+ };
12425
+ const scheduleRetry = () => {
12426
+ if (retryInFlight && !pending) return;
12427
+ pending = true;
12428
+ armSignals();
12429
+ if (retryInFlight) {
12430
+ rescheduleRequested = true;
12431
+ return;
12432
+ }
12433
+ if (timerId !== null || environment.isHidden() || retryAttempt >= RETRY_DELAYS_MS.length) {
12434
+ return;
12435
+ }
12436
+ const delayMs = RETRY_DELAYS_MS[retryAttempt];
12437
+ retryAttempt += 1;
12438
+ timerId = environment.setTimeout(() => {
12439
+ timerId = null;
12440
+ requestRetry(false, false);
12441
+ }, delayMs);
12442
+ };
12443
+ return {
12444
+ schedule: scheduleRetry,
12445
+ succeeded: reset,
12446
+ cancel: reset
12447
+ };
12448
+ }
12346
12449
  function setTokens(config, state, data, onRefreshScheduled) {
12347
12450
  state.accessToken = data.accessToken;
12348
12451
  state.refreshToken = data.refreshToken;
@@ -12368,6 +12471,12 @@ function buildIdentityProof(config) {
12368
12471
  }
12369
12472
  return proof;
12370
12473
  }
12474
+ function calculateRefreshDelay(accessTokenExpiry, now = Date.now()) {
12475
+ const remainingLifetime = accessTokenExpiry - now;
12476
+ if (remainingLifetime <= 0) return 0;
12477
+ const refreshLeadTime = Math.min(6e4, remainingLifetime / 2);
12478
+ return Math.max(0, Math.floor(remainingLifetime - refreshLeadTime));
12479
+ }
12371
12480
  async function authenticateServerless(config, state, onRefreshScheduled) {
12372
12481
  const nonce = crypto.randomUUID();
12373
12482
  const timestamp = Date.now();
@@ -12380,6 +12489,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
12380
12489
  );
12381
12490
  }
12382
12491
  let is4xx = false;
12492
+ let responseStatus = 0;
12383
12493
  try {
12384
12494
  const body = {
12385
12495
  nonce,
@@ -12393,6 +12503,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
12393
12503
  body: JSON.stringify(body),
12394
12504
  credentials: "include"
12395
12505
  });
12506
+ responseStatus = response.status;
12396
12507
  if (!response.ok) {
12397
12508
  is4xx = response.status >= 400 && response.status < 500;
12398
12509
  const errorBody = await response.json().catch(() => ({ error: "Authentication failed" }));
@@ -12421,7 +12532,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
12421
12532
  } catch (err) {
12422
12533
  if (is4xx || attempt === maxRetries) {
12423
12534
  state.lastAuthError ?? (state.lastAuthError = {
12424
- status: 0,
12535
+ status: responseStatus,
12425
12536
  error: err instanceof Error ? err.message : "Authentication failed"
12426
12537
  });
12427
12538
  state.isAuthenticated = false;
@@ -12435,6 +12546,15 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
12435
12546
  async function authenticateViaProxy(config, state, onRefreshScheduled) {
12436
12547
  const maxRetries = config.maxRetries ?? 3;
12437
12548
  const retryDelay = config.retryDelay ?? 500;
12549
+ const previousAuthentication = {
12550
+ accessToken: state.accessToken,
12551
+ refreshToken: state.refreshToken,
12552
+ tokenFamily: state.tokenFamily,
12553
+ accessTokenExpiry: state.accessTokenExpiry,
12554
+ refreshTokenExpiry: state.refreshTokenExpiry,
12555
+ isAuthenticated: state.isAuthenticated,
12556
+ widgetData: state.widgetData
12557
+ };
12438
12558
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
12439
12559
  if (attempt > 0) {
12440
12560
  await new Promise(
@@ -12476,7 +12596,13 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
12476
12596
  status,
12477
12597
  error: err instanceof Error ? err.message : "Authentication failed"
12478
12598
  };
12479
- state.isAuthenticated = false;
12599
+ if (isPermanentAuthFailure(state.lastAuthError)) {
12600
+ const permanentError = state.lastAuthError;
12601
+ clearAuthenticationData(state);
12602
+ state.lastAuthError = permanentError;
12603
+ } else {
12604
+ Object.assign(state, previousAuthentication);
12605
+ }
12480
12606
  return false;
12481
12607
  }
12482
12608
  }
@@ -12488,10 +12614,16 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12488
12614
  if (state.isRefreshing) {
12489
12615
  return state.refreshPromise;
12490
12616
  }
12491
- if (!state.refreshToken) {
12492
- return false;
12493
- }
12494
12617
  const presentedRefreshToken = state.refreshToken;
12618
+ const previousAuthentication = {
12619
+ accessToken: state.accessToken,
12620
+ refreshToken: state.refreshToken,
12621
+ tokenFamily: state.tokenFamily,
12622
+ accessTokenExpiry: state.accessTokenExpiry,
12623
+ refreshTokenExpiry: state.refreshTokenExpiry,
12624
+ isAuthenticated: state.isAuthenticated,
12625
+ widgetData: state.widgetData
12626
+ };
12495
12627
  state.isRefreshing = true;
12496
12628
  state.refreshPromise = (async () => {
12497
12629
  const reauthenticateAfterRefreshFailure = async () => {
@@ -12500,13 +12632,22 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12500
12632
  if (sendToken) sendToken();
12501
12633
  return true;
12502
12634
  }
12503
- clearAuthenticationData(state);
12504
- clearStorage(config);
12505
12635
  const authed = await authenticateServerless(config, state, onRefreshScheduled);
12506
12636
  if (authed && sendToken) sendToken();
12637
+ if (!authed && isPermanentAuthFailure(state.lastAuthError)) {
12638
+ const permanentError = state.lastAuthError;
12639
+ clearAuthenticationData(state);
12640
+ state.lastAuthError = permanentError;
12641
+ clearStorage(config);
12642
+ } else if (!authed) {
12643
+ Object.assign(state, previousAuthentication);
12644
+ }
12507
12645
  return authed;
12508
12646
  };
12509
12647
  try {
12648
+ if (!presentedRefreshToken) {
12649
+ return reauthenticateAfterRefreshFailure();
12650
+ }
12510
12651
  const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/refresh`, {
12511
12652
  method: "POST",
12512
12653
  headers: {
@@ -12737,11 +12878,12 @@ function captureSnapshot(state) {
12737
12878
  widgetData: state.widgetData
12738
12879
  };
12739
12880
  }
12740
- function applySnapshot(subscriber, snapshot) {
12881
+ function applySnapshot(subscriber, snapshot, notify = true) {
12741
12882
  var _a;
12742
12883
  Object.assign(subscriber.state, snapshot);
12743
12884
  if (snapshot.refreshToken) saveToStorage(subscriber.config, subscriber.state);
12744
12885
  else clearStorage(subscriber.config);
12886
+ if (!notify) return;
12745
12887
  try {
12746
12888
  (_a = subscriber.onSnapshot) == null ? void 0 : _a.call(subscriber);
12747
12889
  } catch (error) {
@@ -12786,7 +12928,7 @@ function subscribeAuthSession(config, state, onSnapshot) {
12786
12928
  return Promise.resolve(true);
12787
12929
  }
12788
12930
  if (session.inFlight) return session.inFlight;
12789
- if (session.snapshot) applySnapshot(subscriber, session.snapshot);
12931
+ if (session.snapshot) applySnapshot(subscriber, session.snapshot, false);
12790
12932
  const startingGeneration = session.generation;
12791
12933
  const inFlight = (async () => {
12792
12934
  const succeeded = await execute();
@@ -12823,6 +12965,31 @@ function subscribeAuthSession(config, state, onSnapshot) {
12823
12965
  };
12824
12966
  }
12825
12967
  const BUNDLE_READY_TIMEOUT_MS = 3e4;
12968
+ class BundleReadinessTimeoutError extends Error {
12969
+ constructor(phase, message) {
12970
+ super(message);
12971
+ __publicField(this, "phase");
12972
+ this.name = "BundleReadinessTimeoutError";
12973
+ this.phase = phase;
12974
+ }
12975
+ }
12976
+ function isBundleReadinessTimeoutError(error) {
12977
+ return error instanceof BundleReadinessTimeoutError;
12978
+ }
12979
+ function createDefaultEnvironment() {
12980
+ const visibilityDocument = typeof document === "undefined" ? void 0 : document;
12981
+ const pageWindow = typeof window === "undefined" ? void 0 : window;
12982
+ return {
12983
+ isHidden: () => (visibilityDocument == null ? void 0 : visibilityDocument.visibilityState) === "hidden",
12984
+ now: () => Date.now(),
12985
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
12986
+ clearTimeout: (timerId) => globalThis.clearTimeout(timerId),
12987
+ addVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.addEventListener("visibilitychange", listener),
12988
+ removeVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.removeEventListener("visibilitychange", listener),
12989
+ addPageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("pageshow", listener),
12990
+ removePageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("pageshow", listener)
12991
+ };
12992
+ }
12826
12993
  function isPromiseLike(value) {
12827
12994
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
12828
12995
  }
@@ -12832,28 +12999,101 @@ function isReadyHandle(value) {
12832
12999
  }
12833
13000
  return isPromiseLike(value.ready);
12834
13001
  }
12835
- function waitWithTimeout(promise, timeoutMs, timeoutMessage) {
13002
+ function waitWithActiveTimeTimeout(promise, timeoutMs, timeoutError, environment) {
12836
13003
  return new Promise((resolve, reject) => {
12837
- const timeoutId = setTimeout(() => {
12838
- reject(new Error(timeoutMessage));
12839
- }, timeoutMs);
13004
+ let settled = false;
13005
+ let remainingMs = Math.max(0, timeoutMs);
13006
+ let visibleStartedAt = null;
13007
+ let timeoutId = null;
13008
+ const clearActiveTimer = () => {
13009
+ if (timeoutId !== null) {
13010
+ environment.clearTimeout(timeoutId);
13011
+ timeoutId = null;
13012
+ }
13013
+ };
13014
+ const cleanup = () => {
13015
+ clearActiveTimer();
13016
+ environment.removeVisibilityChangeListener(handleVisibilityChange);
13017
+ };
13018
+ const finish = (callback) => {
13019
+ if (settled) return;
13020
+ settled = true;
13021
+ cleanup();
13022
+ callback();
13023
+ };
13024
+ const pauseDeadline = () => {
13025
+ clearActiveTimer();
13026
+ if (visibleStartedAt === null) return;
13027
+ remainingMs = Math.max(
13028
+ 0,
13029
+ remainingMs - Math.max(0, environment.now() - visibleStartedAt)
13030
+ );
13031
+ visibleStartedAt = null;
13032
+ };
13033
+ const startDeadline = () => {
13034
+ if (settled || environment.isHidden() || timeoutId !== null) return;
13035
+ if (remainingMs <= 0) {
13036
+ finish(() => reject(timeoutError()));
13037
+ return;
13038
+ }
13039
+ visibleStartedAt = environment.now();
13040
+ timeoutId = environment.setTimeout(() => {
13041
+ timeoutId = null;
13042
+ if (environment.isHidden()) {
13043
+ visibleStartedAt = null;
13044
+ return;
13045
+ }
13046
+ visibleStartedAt = null;
13047
+ finish(() => reject(timeoutError()));
13048
+ }, remainingMs);
13049
+ };
13050
+ function handleVisibilityChange() {
13051
+ if (environment.isHidden()) {
13052
+ pauseDeadline();
13053
+ } else {
13054
+ startDeadline();
13055
+ }
13056
+ }
13057
+ environment.addVisibilityChangeListener(handleVisibilityChange);
13058
+ startDeadline();
12840
13059
  Promise.resolve(promise).then(
12841
13060
  (value) => {
12842
- clearTimeout(timeoutId);
12843
- resolve(value);
13061
+ finish(() => resolve(value));
12844
13062
  },
12845
13063
  (error) => {
12846
- clearTimeout(timeoutId);
12847
- reject(error);
13064
+ finish(() => reject(error));
12848
13065
  }
12849
13066
  );
12850
13067
  });
12851
13068
  }
12852
- function resolveBundleMountResult(mountResult, timeoutMs = BUNDLE_READY_TIMEOUT_MS) {
12853
- return isPromiseLike(mountResult) ? waitWithTimeout(
13069
+ function scheduleForegroundRecovery(recover, environment = createDefaultEnvironment()) {
13070
+ if (!environment.isHidden()) return () => {
13071
+ };
13072
+ let active = true;
13073
+ const cleanup = () => {
13074
+ if (!active) return;
13075
+ active = false;
13076
+ environment.removeVisibilityChangeListener(handleForeground);
13077
+ environment.removePageShowListener(handleForeground);
13078
+ };
13079
+ const handleForeground = () => {
13080
+ if (!active || environment.isHidden()) return;
13081
+ cleanup();
13082
+ recover();
13083
+ };
13084
+ environment.addVisibilityChangeListener(handleForeground);
13085
+ environment.addPageShowListener(handleForeground);
13086
+ return cleanup;
13087
+ }
13088
+ function resolveBundleMountResult(mountResult, timeoutMs = BUNDLE_READY_TIMEOUT_MS, environment = createDefaultEnvironment()) {
13089
+ return isPromiseLike(mountResult) ? waitWithActiveTimeTimeout(
12854
13090
  mountResult,
12855
13091
  timeoutMs,
12856
- `Widget bundle mount did not resolve within ${timeoutMs}ms`
13092
+ () => new BundleReadinessTimeoutError(
13093
+ "mount",
13094
+ `Widget bundle mount did not resolve within ${timeoutMs}ms`
13095
+ ),
13096
+ environment
12857
13097
  ) : Promise.resolve(mountResult);
12858
13098
  }
12859
13099
  function createBundleMountCleanup(mountResult) {
@@ -12869,19 +13109,24 @@ function createBundleMountCleanup(mountResult) {
12869
13109
  unmount.call(mountResult);
12870
13110
  };
12871
13111
  }
12872
- async function waitForBundleMountReadiness(mountResult, firstPaint, timeoutMs = BUNDLE_READY_TIMEOUT_MS) {
13112
+ async function waitForBundleMountReadiness(mountResult, firstPaint, timeoutMs = BUNDLE_READY_TIMEOUT_MS, environment = createDefaultEnvironment()) {
12873
13113
  const resolvedMountResult = await resolveBundleMountResult(
12874
13114
  mountResult,
12875
- timeoutMs
13115
+ timeoutMs,
13116
+ environment
12876
13117
  );
12877
13118
  if (!isReadyHandle(resolvedMountResult)) {
12878
13119
  await firstPaint;
12879
13120
  return "first-paint";
12880
13121
  }
12881
- await waitWithTimeout(
13122
+ await waitWithActiveTimeTimeout(
12882
13123
  resolvedMountResult.ready,
12883
13124
  timeoutMs,
12884
- `Widget bundle did not report deep readiness within ${timeoutMs}ms`
13125
+ () => new BundleReadinessTimeoutError(
13126
+ "deep-ready",
13127
+ `Widget bundle did not report deep readiness within ${timeoutMs}ms`
13128
+ ),
13129
+ environment
12885
13130
  );
12886
13131
  return "deep-ready";
12887
13132
  }
@@ -13610,6 +13855,45 @@ function ensureWidgetPreconnects(origins, targetDocument) {
13610
13855
  documentTarget.head.appendChild(link);
13611
13856
  }
13612
13857
  }
13858
+ const USER_LOOKUP_ERRORS = /* @__PURE__ */ new Set([
13859
+ "User not found",
13860
+ "Missing required user identifier fields",
13861
+ "Missing user identifier fields"
13862
+ ]);
13863
+ function selectWidgetErrorPresentation({
13864
+ failureKind,
13865
+ customMessage,
13866
+ lastAuthError
13867
+ }) {
13868
+ if (failureKind === "bundle") {
13869
+ const message = "Widget failed to load. Please try again.";
13870
+ return {
13871
+ useCustomMessage: false,
13872
+ displayMessage: message,
13873
+ emittedMessage: message
13874
+ };
13875
+ }
13876
+ const useCustomMessage = Boolean(
13877
+ customMessage && (lastAuthError === null || (lastAuthError.error ? USER_LOOKUP_ERRORS.has(lastAuthError.error) : false))
13878
+ );
13879
+ if (useCustomMessage) {
13880
+ return {
13881
+ useCustomMessage: true,
13882
+ displayMessage: customMessage,
13883
+ emittedMessage: customMessage,
13884
+ hint: lastAuthError == null ? void 0 : lastAuthError.hint,
13885
+ status: lastAuthError == null ? void 0 : lastAuthError.status
13886
+ };
13887
+ }
13888
+ const emittedMessage = (lastAuthError == null ? void 0 : lastAuthError.error) || "Authentication failed";
13889
+ return {
13890
+ useCustomMessage: false,
13891
+ displayMessage: (lastAuthError == null ? void 0 : lastAuthError.hint) ? `${emittedMessage}: ${lastAuthError.hint}` : (lastAuthError == null ? void 0 : lastAuthError.error) ? emittedMessage : "Authentication failed. Please try again later.",
13892
+ emittedMessage,
13893
+ hint: lastAuthError == null ? void 0 : lastAuthError.hint,
13894
+ status: lastAuthError == null ? void 0 : lastAuthError.status
13895
+ };
13896
+ }
13613
13897
  const widgetCSS = ':host{display:block;position:relative;width:100%;height:100%}.rai-loader{position:absolute;top:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.75rem;width:100%;height:100%;background:var(--rai-loading-background, var(--background-5, var(--rai-loader-bg, #ffffff)));border-radius:8px;z-index:10;transition:opacity .3s ease-out;font-family:var(--rai-loader-font, "Roboto", system-ui, -apple-system, sans-serif)}.rai-loader.fade-out{opacity:0;pointer-events:none}.rai-loader-pending-settings{visibility:hidden}.rai-loader-text{margin:.5rem 0 0;font-family:var(--rai-loading-text-font, var(--rai-loader-font, "Roboto", system-ui, -apple-system, sans-serif));font-size:var(--rai-loading-text-size, 1.125rem);line-height:1.75rem;font-weight:var(--rai-loading-text-weight, 400);font-style:var(--rai-loading-text-style, normal);text-decoration:var(--rai-loading-text-decoration, none);letter-spacing:normal;color:var(--rai-loading-text-color, var(--accent-1, var(--rai-accent, #000000)));text-align:center}@media (min-width: 768px){.rai-loader-text{font-size:var(--rai-loading-text-size, 1.5rem);line-height:2rem}}.rai-error{display:none;position:absolute;top:0;left:0;width:100%;height:100%;align-items:center;justify-content:center;flex-direction:column;gap:12px;background:var(--rai-error-bg, #1a1a1a);border-radius:8px;padding:24px;box-sizing:border-box;text-align:center;color:var(--rai-error-text, #9ca3af);font-family:system-ui,-apple-system,sans-serif;z-index:10}.rai-error.visible{display:flex}iframe{display:block;border:none;opacity:0;transition:opacity .3s ease-in}iframe.loaded{opacity:1}.rai-lottie-loader{width:100px;height:100px}.rai-lottie-loader svg{width:100%!important;height:100%!important}.rai-lottie-spinner{width:32px;height:32px;margin:34px auto;border:3px solid var(--rai-text4, #6b7280);border-top-color:var(--rai-accent, #000000);border-radius:50%;animation:rai-spin .7s linear infinite}@keyframes rai-spin{to{transform:rotate(360deg)}}.rai-sdk-loader{position:relative;width:75px;height:100px}.rai-sdk-loader__bar{position:absolute;bottom:0;width:10px;height:50%;background:var(--rai-accent, #000000);transform-origin:center bottom;box-shadow:1px 1px #0003}.rai-sdk-loader__bar:nth-child(1){left:0;transform:scaleY(.2);animation:rai-sdk-barUp1 4s infinite}.rai-sdk-loader__bar:nth-child(2){left:15px;transform:scaleY(.4);animation:rai-sdk-barUp2 4s infinite}.rai-sdk-loader__bar:nth-child(3){left:30px;transform:scaleY(.6);animation:rai-sdk-barUp3 4s infinite}.rai-sdk-loader__bar:nth-child(4){left:45px;transform:scaleY(.8);animation:rai-sdk-barUp4 4s infinite}.rai-sdk-loader__bar:nth-child(5){left:60px;transform:scale(1);animation:rai-sdk-barUp5 4s infinite}.rai-sdk-loader__ball{position:absolute;bottom:10px;left:0;width:10px;height:10px;background:var(--rai-accent, #000000);border-radius:50%;animation:rai-sdk-ball 4s infinite}@keyframes rai-sdk-ball{0%{transform:translate(0)}5%{transform:translate(8px,-14px)}10%{transform:translate(15px,-10px)}17%{transform:translate(23px,-24px)}20%{transform:translate(30px,-20px)}27%{transform:translate(38px,-34px)}30%{transform:translate(45px,-30px)}37%{transform:translate(53px,-44px)}40%{transform:translate(60px,-40px)}50%{transform:translate(60px)}57%{transform:translate(53px,-14px)}60%{transform:translate(45px,-10px)}67%{transform:translate(37px,-24px)}70%{transform:translate(30px,-20px)}77%{transform:translate(22px,-34px)}80%{transform:translate(15px,-30px)}87%{transform:translate(7px,-44px)}90%{transform:translateY(-40px)}to{transform:translate(0)}}@keyframes rai-sdk-barUp1{0%{transform:scaleY(.2)}40%{transform:scaleY(.2)}50%{transform:scale(1)}90%{transform:scale(1)}to{transform:scaleY(.2)}}@keyframes rai-sdk-barUp2{0%{transform:scaleY(.4)}40%{transform:scaleY(.4)}50%{transform:scaleY(.8)}90%{transform:scaleY(.8)}to{transform:scaleY(.4)}}@keyframes rai-sdk-barUp3{0%{transform:scaleY(.6)}to{transform:scaleY(.6)}}@keyframes rai-sdk-barUp4{0%{transform:scaleY(.8)}40%{transform:scaleY(.8)}50%{transform:scaleY(.4)}90%{transform:scaleY(.4)}to{transform:scaleY(.8)}}@keyframes rai-sdk-barUp5{0%{transform:scale(1)}40%{transform:scale(1)}50%{transform:scaleY(.2)}90%{transform:scaleY(.2)}to{transform:scale(1)}}#loading-square{width:75px;aspect-ratio:1;display:flex;color:var(--rai-accent, #000000);background:linear-gradient(currentColor 0 0) right / 51% 100%,linear-gradient(currentColor 0 0) bottom / 100% 51%;background-repeat:no-repeat;animation:l16-0 2s infinite linear .25s}#loading-square>div{width:50%;height:50%;background:currentColor;animation:l16-1 .5s infinite linear}@keyframes l16-0{0%,12.49%{transform:rotate(0)}12.5%,37.49%{transform:rotate(90deg)}37.5%,62.49%{transform:rotate(180deg)}62.5%,87.49%{transform:rotate(270deg)}87.5%,to{transform:rotate(360deg)}}@keyframes l16-1{0%{transform:perspective(80px) rotate3d(-1,-1,0,0)}80%,to{transform:perspective(80px) rotate3d(-1,-1,0,-180deg)}}#loading-circle{width:75px;aspect-ratio:1;display:grid;grid:50%/50%;color:var(--rai-accent, #000000);border-radius:50%;--_g: no-repeat linear-gradient(currentColor 0 0);background:var(--_g),var(--_g),var(--_g);background-size:50.1% 50.1%;animation:l9-0 1.5s infinite steps(1) alternate,l9-0-0 3s infinite steps(1) alternate}#loading-circle>div{background:var(--rai-text4, #6b7280);border-top-left-radius:100px;transform:perspective(150px) rotateY(0) rotateX(0);transform-origin:bottom right;animation:l9-1 1.5s infinite linear alternate}@keyframes l9-0{0%{background-position:0 100%,100% 100%,100% 0}33%{background-position:100% 100%,100% 100%,100% 0}66%{background-position:100% 0,100% 0,100% 0}}@keyframes l9-0-0{0%{transform:scaleX(1) rotate(0)}50%{transform:scaleX(-1) rotate(-90deg)}}@keyframes l9-1{16.5%{transform:perspective(150px) rotateX(-90deg) rotateY(0) rotateX(0);filter:grayscale(.8)}33%{transform:perspective(150px) rotateX(-180deg) rotateY(0) rotateX(0)}66%{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(0)}to{transform:perspective(150px) rotateX(-180deg) rotateY(-180deg) rotateX(-180deg);filter:grayscale(.8)}}.rai-custom-error{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10}.tiptap strong.tw-font-bold,.tiptap b.tw-font-bold{font-weight:700}.tiptap em.tw-font-italic,.tiptap i.tw-font-italic{font-style:italic}.tiptap a.tiptap-rich-text-link{color:#3b82f6;text-decoration:underline;position:relative;display:inline-block;width:fit-content}.tiptap a.tiptap-rich-text-link:hover{color:#3b82f6cc;text-decoration:none}.tiptap a.tiptap-rich-text-link:not([title=""]):before{content:attr(title);position:absolute;top:-2rem;left:50%;transform:translate(-50%);white-space:nowrap;border-radius:.25rem;background-color:#000;padding:.25rem .5rem;color:#fff;opacity:0;transition:opacity .2s;pointer-events:none}.tiptap a.tiptap-rich-text-link:not([title=""]):hover:before{opacity:1}.tiptap p[style*="text-align: left"]{text-align:left}.tiptap p[style*="text-align: center"]{text-align:center}.tiptap p[style*="text-align: right"]{text-align:right}.tiptap p[style*="text-align: justify"]{text-align:justify}.tiptap u{text-decoration:underline}.tiptap.ProseMirror{background-color:transparent;height:fit-content!important;word-wrap:break-word;white-space:pre-wrap;white-space:break-spaces;-webkit-font-variant-ligatures:none;font-variant-ligatures:none;font-feature-settings:"liga" 0;padding:0;width:100%;overflow-y:auto}.tiptap{line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.tiptap p{margin:.5em 0}.tiptap p:first-child{margin-top:0}.tiptap p:last-child{margin-bottom:0}.tiptap img{max-width:100%;height:auto;display:block}.tiptap p[style*="text-align: left"] img{margin-left:0;margin-right:auto}.tiptap p[style*="text-align: center"] img{margin-left:auto;margin-right:auto}.tiptap p[style*="text-align: right"] img{margin-left:auto;margin-right:0}.rai-retry-btn{padding:8px 20px;border:none;border-radius:6px;background:var(--rai-accent, #000000);color:var(--rai-error-bg, #ffffff);font-size:14px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer}.rai-retry-btn:hover{opacity:.85}';
13614
13898
  const DEFINED_ATTRS = /* @__PURE__ */ new Set([
13615
13899
  "community-id",
@@ -14007,8 +14291,13 @@ class BaseWidget extends HTMLElement {
14007
14291
  __publicField(this, "bannerNode", null);
14008
14292
  __publicField(this, "releaseAutoThemeDisableLease");
14009
14293
  __publicField(this, "bundleUnmount");
14294
+ __publicField(this, "cancelBundleForegroundRecovery");
14010
14295
  __publicField(this, "authSession");
14296
+ __publicField(this, "authRenewalRecovery");
14011
14297
  this.shadow = this.attachShadow({ mode: "closed" });
14298
+ this.authRenewalRecovery = createAuthRenewalRecoveryController(
14299
+ () => this.performScheduledRefresh()
14300
+ );
14012
14301
  }
14013
14302
  static get observedAttributes() {
14014
14303
  return [
@@ -14062,15 +14351,18 @@ class BaseWidget extends HTMLElement {
14062
14351
  }
14063
14352
  }
14064
14353
  disconnectedCallback() {
14065
- var _a, _b, _c, _d;
14354
+ var _a, _b, _c, _d, _e;
14066
14355
  (_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
14067
14356
  (_b = this.themeObserver) == null ? void 0 : _b.disconnect();
14068
14357
  (_c = this.identityAttributeObserver) == null ? void 0 : _c.disconnect();
14358
+ (_d = this.cancelBundleForegroundRecovery) == null ? void 0 : _d.call(this);
14359
+ this.cancelBundleForegroundRecovery = void 0;
14360
+ this.authRenewalRecovery.cancel();
14069
14361
  this.teardownMountedWidget();
14070
14362
  this.clearLottieInstances();
14071
14363
  this.restoreLoaderHeight();
14072
14364
  this.removeBanner();
14073
- (_d = this.authSession) == null ? void 0 : _d.release();
14365
+ (_e = this.authSession) == null ? void 0 : _e.release();
14074
14366
  this.authSession = void 0;
14075
14367
  clearState(this.state);
14076
14368
  }
@@ -14498,25 +14790,24 @@ class BaseWidget extends HTMLElement {
14498
14790
  if (container.children.length > 0) finish();
14499
14791
  });
14500
14792
  }
14501
- showError() {
14793
+ showError(failureKind = "authentication") {
14502
14794
  this.hideLoader();
14503
14795
  const settings = this.state.errorSettings;
14504
- const lastAuthError = this.state.lastAuthError;
14505
- const isUserLookupError = (lastAuthError == null ? void 0 : lastAuthError.error) === "User not found" || (lastAuthError == null ? void 0 : lastAuthError.error) === "Missing required user identifier fields" || (lastAuthError == null ? void 0 : lastAuthError.error) === "Missing user identifier fields";
14506
- const shouldUseCustomMessage = !!(settings == null ? void 0 : settings.errorMessage) && (isUserLookupError || !lastAuthError);
14507
- if (shouldUseCustomMessage) {
14796
+ const presentation = selectWidgetErrorPresentation({
14797
+ failureKind,
14798
+ customMessage: settings == null ? void 0 : settings.errorMessage,
14799
+ lastAuthError: this.state.lastAuthError
14800
+ });
14801
+ if (presentation.useCustomMessage) {
14508
14802
  this.renderCustomError(settings);
14509
14803
  } else if (this.errorEl) {
14510
- if ((lastAuthError == null ? void 0 : lastAuthError.error) && this.msgEl) {
14511
- this.msgEl.textContent = lastAuthError.hint ? `${lastAuthError.error}: ${lastAuthError.hint}` : lastAuthError.error;
14512
- }
14804
+ if (this.msgEl) this.msgEl.textContent = presentation.displayMessage;
14513
14805
  this.errorEl.classList.add("visible");
14514
14806
  }
14515
- const emittedMessage = shouldUseCustomMessage ? settings.errorMessage : (lastAuthError == null ? void 0 : lastAuthError.error) || "Authentication failed";
14516
14807
  this.emit("rai-error", {
14517
- message: emittedMessage,
14518
- hint: lastAuthError == null ? void 0 : lastAuthError.hint,
14519
- status: lastAuthError == null ? void 0 : lastAuthError.status
14808
+ message: presentation.emittedMessage,
14809
+ hint: presentation.hint,
14810
+ status: presentation.status
14520
14811
  });
14521
14812
  }
14522
14813
  renderCustomError(settings) {
@@ -14672,7 +14963,10 @@ class BaseWidget extends HTMLElement {
14672
14963
  }
14673
14964
  // ── Initialization ─────────────────────────────────────────────────────
14674
14965
  async init() {
14675
- var _a;
14966
+ var _a, _b;
14967
+ (_a = this.cancelBundleForegroundRecovery) == null ? void 0 : _a.call(this);
14968
+ this.cancelBundleForegroundRecovery = void 0;
14969
+ this.authRenewalRecovery.cancel();
14676
14970
  const initId = ++this.initGeneration;
14677
14971
  this.hasAttemptedInit = true;
14678
14972
  if (!this.config.communityId) {
@@ -14681,6 +14975,9 @@ class BaseWidget extends HTMLElement {
14681
14975
  }
14682
14976
  if (!this.config.authUrl && !this.authSession) {
14683
14977
  this.authSession = subscribeAuthSession(this.config, this.state, () => {
14978
+ if (this.state.isAuthenticated && !this.state.lastAuthError) {
14979
+ this.authRenewalRecovery.succeeded();
14980
+ }
14684
14981
  if (this.config.autoRefresh) this.scheduleRefresh();
14685
14982
  if (this.state.iframe) {
14686
14983
  sendTokenToWidget(this.config, this.state, this.state.iframe);
@@ -14772,7 +15069,7 @@ class BaseWidget extends HTMLElement {
14772
15069
  launchWidget();
14773
15070
  return;
14774
15071
  }
14775
- (_a = this.authSession) == null ? void 0 : _a.publish();
15072
+ (_b = this.authSession) == null ? void 0 : _b.publish();
14776
15073
  const refreshed = await finishStartup(
14777
15074
  this.authSession ? this.authSession.run(
14778
15075
  "refresh",
@@ -14896,7 +15193,7 @@ class BaseWidget extends HTMLElement {
14896
15193
  return { bundleApiUrl, colorMode };
14897
15194
  }
14898
15195
  async mountWidget(initId) {
14899
- var _a;
15196
+ var _a, _b;
14900
15197
  let container = null;
14901
15198
  let cleanupBundleMount;
14902
15199
  try {
@@ -15017,45 +15314,68 @@ class BaseWidget extends HTMLElement {
15017
15314
  container == null ? void 0 : container.remove();
15018
15315
  (_a = this.releaseAutoThemeDisableLease) == null ? void 0 : _a.call(this);
15019
15316
  this.releaseAutoThemeDisableLease = void 0;
15020
- this.showError();
15317
+ if (isBundleReadinessTimeoutError(err) && document.visibilityState === "hidden") {
15318
+ (_b = this.cancelBundleForegroundRecovery) == null ? void 0 : _b.call(this);
15319
+ let scheduledRecovery;
15320
+ scheduledRecovery = scheduleForegroundRecovery(() => {
15321
+ if (this.cancelBundleForegroundRecovery === scheduledRecovery) {
15322
+ this.cancelBundleForegroundRecovery = void 0;
15323
+ }
15324
+ if (this.isConnected) void this.init();
15325
+ });
15326
+ this.cancelBundleForegroundRecovery = scheduledRecovery;
15327
+ return;
15328
+ }
15329
+ this.showError("bundle");
15021
15330
  }
15022
15331
  }
15023
15332
  // ── Token scheduling ──────────────────────────────────────────────────
15024
15333
  scheduleRefresh() {
15025
15334
  if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
15026
15335
  if (!this.state.accessTokenExpiry) return;
15027
- const delay = this.state.accessTokenExpiry - Date.now() - 6e4;
15336
+ const delay = calculateRefreshDelay(this.state.accessTokenExpiry);
15337
+ if (delay > 0) {
15338
+ this.state.refreshTimer = setTimeout(
15339
+ () => void this.performScheduledRefresh(),
15340
+ delay
15341
+ );
15342
+ } else {
15343
+ void this.performScheduledRefresh();
15344
+ }
15345
+ }
15346
+ async performScheduledRefresh() {
15347
+ this.state.refreshTimer = null;
15028
15348
  const pushToken = () => {
15029
15349
  if (this.state.iframe)
15030
15350
  sendTokenToWidget(this.config, this.state, this.state.iframe);
15031
15351
  syncBundleAccessToken(this.config, this.state);
15032
15352
  };
15033
- const doRefresh = async () => {
15034
- if (this.config.authUrl) {
15035
- const ok = await authenticateViaProxy(
15036
- this.config,
15037
- this.state,
15038
- () => this.scheduleRefresh()
15039
- );
15040
- if (ok) pushToken();
15041
- else this.handleSessionExpired();
15042
- } else {
15043
- const ok = this.authSession ? await this.authSession.run(
15044
- "refresh",
15045
- () => refreshAccessToken(this.config, this.state)
15046
- ) : await refreshAccessToken(
15047
- this.config,
15048
- this.state,
15049
- () => this.scheduleRefresh(),
15050
- pushToken
15051
- );
15052
- if (!ok) this.handleSessionExpired();
15053
- }
15054
- };
15055
- if (delay > 0) {
15056
- this.state.refreshTimer = setTimeout(doRefresh, delay);
15353
+ let ok;
15354
+ if (this.config.authUrl) {
15355
+ ok = await authenticateViaProxy(
15356
+ this.config,
15357
+ this.state,
15358
+ () => this.scheduleRefresh()
15359
+ );
15360
+ if (ok) pushToken();
15057
15361
  } else {
15058
- doRefresh();
15362
+ ok = this.authSession ? await this.authSession.run(
15363
+ "refresh",
15364
+ () => refreshAccessToken(this.config, this.state)
15365
+ ) : await refreshAccessToken(
15366
+ this.config,
15367
+ this.state,
15368
+ () => this.scheduleRefresh(),
15369
+ pushToken
15370
+ );
15371
+ }
15372
+ if (ok) {
15373
+ this.authRenewalRecovery.succeeded();
15374
+ } else if (isPermanentAuthFailure(this.state.lastAuthError)) {
15375
+ this.authRenewalRecovery.cancel();
15376
+ this.handleSessionExpired();
15377
+ } else {
15378
+ this.authRenewalRecovery.schedule();
15059
15379
  }
15060
15380
  }
15061
15381
  schedulePeriodicSync(iframe) {
@@ -15066,18 +15386,13 @@ class BaseWidget extends HTMLElement {
15066
15386
  }, 12e4);
15067
15387
  }
15068
15388
  /**
15069
- * A background refresh exhausted every recovery path — /auth/refresh failed
15070
- * and the re-auth that follows it failed too, so `state.isAuthenticated` is
15071
- * now false and there is no live token.
15072
- *
15073
- * Every startup auth path already calls showError() on failure; this one is
15074
- * reached from a timer, so without it the widget stays mounted showing stale
15075
- * content while isAuthenticated() reports false to the host page — which is
15076
- * how a signed-in user ends up looking at content meant for signed-out users.
15077
- * The event gives hosts a signal to re-mint credentials instead of polling.
15389
+ * A background refresh reached a confirmed permanent authentication failure
15390
+ * after exhausting its safe recovery paths. Transient transport, throttling,
15391
+ * and server failures are retained by authRenewalRecovery instead.
15078
15392
  */
15079
15393
  handleSessionExpired() {
15080
15394
  var _a;
15395
+ this.authRenewalRecovery.cancel();
15081
15396
  if (this.state.refreshTimer) {
15082
15397
  clearTimeout(this.state.refreshTimer);
15083
15398
  this.state.refreshTimer = null;
@@ -15097,6 +15412,7 @@ class BaseWidget extends HTMLElement {
15097
15412
  // ── Logout helper ─────────────────────────────────────────────────────
15098
15413
  async logoutAndClear() {
15099
15414
  var _a;
15415
+ this.authRenewalRecovery.cancel();
15100
15416
  await logout(this.config, this.state);
15101
15417
  (_a = this.authSession) == null ? void 0 : _a.clear();
15102
15418
  this.teardownMountedWidget();
@@ -15106,6 +15422,7 @@ class BaseWidget extends HTMLElement {
15106
15422
  // ── Public API ────────────────────────────────────────────────────────
15107
15423
  async reload() {
15108
15424
  var _a, _b;
15425
+ this.authRenewalRecovery.cancel();
15109
15426
  const previousConfig = this.config;
15110
15427
  (_a = this.authSession) == null ? void 0 : _a.release();
15111
15428
  this.authSession = void 0;
@@ -15253,7 +15570,7 @@ class CustomWidget extends BaseWidget {
15253
15570
  }
15254
15571
  }
15255
15572
  }
15256
- console.log(`[rai-widget] v${"1.8.4"}`);
15573
+ console.log(`[rai-widget] v${"1.8.6"}`);
15257
15574
  class StoreWidgetCompat extends StoreWidget {
15258
15575
  }
15259
15576
  const WIDGET_REGISTRY = [
@@ -15317,7 +15634,7 @@ function exposePublicApi() {
15317
15634
  })();
15318
15635
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
15319
15636
  window.ReturningAIWidget = {
15320
- version: "1.8.4",
15637
+ version: "1.8.6",
15321
15638
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
15322
15639
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
15323
15640
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,