@returningai/widget-sdk 1.8.3 → 1.8.5

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.
@@ -12491,10 +12491,16 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12491
12491
  if (!state.refreshToken) {
12492
12492
  return false;
12493
12493
  }
12494
+ const presentedRefreshToken = state.refreshToken;
12494
12495
  state.isRefreshing = true;
12495
12496
  state.refreshPromise = (async () => {
12496
12497
  const reauthenticateAfterRefreshFailure = async () => {
12497
- clearState(state);
12498
+ if (state.refreshToken && state.refreshToken !== presentedRefreshToken && state.accessToken && state.accessTokenExpiry && Date.now() < state.accessTokenExpiry) {
12499
+ state.isAuthenticated = true;
12500
+ if (sendToken) sendToken();
12501
+ return true;
12502
+ }
12503
+ clearAuthenticationData(state);
12498
12504
  clearStorage(config);
12499
12505
  const authed = await authenticateServerless(config, state, onRefreshScheduled);
12500
12506
  if (authed && sendToken) sendToken();
@@ -12505,7 +12511,7 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12505
12511
  method: "POST",
12506
12512
  headers: {
12507
12513
  "Content-Type": "application/json",
12508
- Authorization: `Bearer ${state.refreshToken}`
12514
+ Authorization: `Bearer ${presentedRefreshToken}`
12509
12515
  },
12510
12516
  body: JSON.stringify({
12511
12517
  widgetType: config.widgetType,
@@ -12627,15 +12633,13 @@ async function fetchErrorSettings(config, state, opts = {}) {
12627
12633
  applyErrorSettingsToState(state, data);
12628
12634
  return state.errorSettings;
12629
12635
  }
12630
- function clearState(state) {
12636
+ function clearAuthenticationData(state) {
12631
12637
  state.accessToken = null;
12632
12638
  state.refreshToken = null;
12633
12639
  state.tokenFamily = null;
12634
12640
  state.accessTokenExpiry = null;
12635
12641
  state.refreshTokenExpiry = null;
12636
12642
  state.isAuthenticated = false;
12637
- state.isRefreshing = false;
12638
- state.refreshPromise = null;
12639
12643
  state.lastAuthError = null;
12640
12644
  state.widgetData = null;
12641
12645
  if (state.refreshTimer) {
@@ -12647,6 +12651,11 @@ function clearState(state) {
12647
12651
  state.syncTimer = null;
12648
12652
  }
12649
12653
  }
12654
+ function clearState(state) {
12655
+ clearAuthenticationData(state);
12656
+ state.isRefreshing = false;
12657
+ state.refreshPromise = null;
12658
+ }
12650
12659
  const AUTO_THEME_LEASE_STATE = "__RAI_AUTO_THEME_LEASE_STATE__";
12651
12660
  const DISABLE_AUTO_THEME = "__DISABLE_AUTO_THEME__";
12652
12661
  function acquireAutoThemeDisableLease(target = window) {
@@ -12674,7 +12683,171 @@ function acquireAutoThemeDisableLease(target = window) {
12674
12683
  delete target[AUTO_THEME_LEASE_STATE];
12675
12684
  };
12676
12685
  }
12686
+ const REGISTRY_SYMBOL = Symbol.for("returningai.widget-sdk.auth-sessions.v1");
12687
+ function registry() {
12688
+ const root = globalThis;
12689
+ root[REGISTRY_SYMBOL] ?? (root[REGISTRY_SYMBOL] = /* @__PURE__ */ new Map());
12690
+ return root[REGISTRY_SYMBOL];
12691
+ }
12692
+ function stableIdentity(config) {
12693
+ const identifiers = Object.keys(config.userIdentifiers || {}).sort().map((key) => [key, config.userIdentifiers[key]]);
12694
+ return JSON.stringify({
12695
+ embedToken: config.embedToken || "",
12696
+ userIdentifiers: identifiers
12697
+ });
12698
+ }
12699
+ function stableHash(value) {
12700
+ let first = 2166136261;
12701
+ let second = 2654435769;
12702
+ for (let index = 0; index < value.length; index += 1) {
12703
+ const code = value.charCodeAt(index);
12704
+ first = Math.imul(first ^ code, 16777619) >>> 0;
12705
+ second = Math.imul(second ^ code, 2246822507) >>> 0;
12706
+ }
12707
+ return `${first.toString(36)}${second.toString(36)}`;
12708
+ }
12709
+ function normalizeUrl(value) {
12710
+ if (!value) return "";
12711
+ try {
12712
+ const parsed = new URL(value);
12713
+ return `${parsed.origin.toLowerCase()}${parsed.pathname.replace(/\/+$/, "")}`;
12714
+ } catch {
12715
+ return value.replace(/\/+$/, "");
12716
+ }
12717
+ }
12718
+ function createAuthScopeKey(config) {
12719
+ const scope = JSON.stringify({
12720
+ apiUrl: normalizeUrl(config.apiUrl),
12721
+ authUrl: normalizeUrl(config.authUrl),
12722
+ widgetId: config.widgetId,
12723
+ widgetType: config.widgetType,
12724
+ identity: stableIdentity(config)
12725
+ });
12726
+ return `rai-auth-v1:${stableHash(scope)}`;
12727
+ }
12728
+ function captureSnapshot(state) {
12729
+ return {
12730
+ accessToken: state.accessToken,
12731
+ refreshToken: state.refreshToken,
12732
+ tokenFamily: state.tokenFamily,
12733
+ accessTokenExpiry: state.accessTokenExpiry,
12734
+ refreshTokenExpiry: state.refreshTokenExpiry,
12735
+ isAuthenticated: state.isAuthenticated,
12736
+ lastAuthError: state.lastAuthError ? { ...state.lastAuthError } : null,
12737
+ widgetData: state.widgetData
12738
+ };
12739
+ }
12740
+ function applySnapshot(subscriber, snapshot) {
12741
+ var _a;
12742
+ Object.assign(subscriber.state, snapshot);
12743
+ if (snapshot.refreshToken) saveToStorage(subscriber.config, subscriber.state);
12744
+ else clearStorage(subscriber.config);
12745
+ try {
12746
+ (_a = subscriber.onSnapshot) == null ? void 0 : _a.call(subscriber);
12747
+ } catch (error) {
12748
+ console.error("[rai-widget] Auth session subscriber failed:", error);
12749
+ }
12750
+ }
12751
+ function publishSnapshot(session, state) {
12752
+ session.generation += 1;
12753
+ session.snapshot = captureSnapshot(state);
12754
+ for (const subscriber of session.subscribers) {
12755
+ applySnapshot(subscriber, session.snapshot);
12756
+ }
12757
+ }
12758
+ function hasUsableAuthenticatedSnapshot(session) {
12759
+ const snapshot = session.snapshot;
12760
+ return Boolean(
12761
+ (snapshot == null ? void 0 : snapshot.isAuthenticated) && snapshot.accessToken && snapshot.accessTokenExpiry && Date.now() < snapshot.accessTokenExpiry
12762
+ );
12763
+ }
12764
+ function subscribeAuthSession(config, state, onSnapshot) {
12765
+ const sessions = registry();
12766
+ const scopeKey = createAuthScopeKey(config);
12767
+ let session = sessions.get(scopeKey);
12768
+ if (!session) {
12769
+ session = {
12770
+ generation: 0,
12771
+ snapshot: null,
12772
+ inFlight: null,
12773
+ subscribers: /* @__PURE__ */ new Set()
12774
+ };
12775
+ sessions.set(scopeKey, session);
12776
+ }
12777
+ const subscriber = { config, state, onSnapshot };
12778
+ session.subscribers.add(subscriber);
12779
+ if (session.snapshot) applySnapshot(subscriber, session.snapshot);
12780
+ let released = false;
12781
+ return {
12782
+ run(operation, execute) {
12783
+ if (released) return Promise.resolve(false);
12784
+ if (operation === "authenticate" && hasUsableAuthenticatedSnapshot(session)) {
12785
+ applySnapshot(subscriber, session.snapshot);
12786
+ return Promise.resolve(true);
12787
+ }
12788
+ if (session.inFlight) return session.inFlight;
12789
+ if (session.snapshot) applySnapshot(subscriber, session.snapshot);
12790
+ const startingGeneration = session.generation;
12791
+ const inFlight = (async () => {
12792
+ const succeeded = await execute();
12793
+ if (succeeded || session.generation === startingGeneration) {
12794
+ publishSnapshot(session, state);
12795
+ }
12796
+ return succeeded;
12797
+ })().finally(() => {
12798
+ if (session.inFlight === inFlight) session.inFlight = null;
12799
+ if (session.subscribers.size === 0) sessions.delete(scopeKey);
12800
+ });
12801
+ session.inFlight = inFlight;
12802
+ return inFlight;
12803
+ },
12804
+ publish() {
12805
+ if (!released) publishSnapshot(session, state);
12806
+ },
12807
+ clear() {
12808
+ if (released) return;
12809
+ state.accessToken = null;
12810
+ state.refreshToken = null;
12811
+ state.tokenFamily = null;
12812
+ state.accessTokenExpiry = null;
12813
+ state.refreshTokenExpiry = null;
12814
+ state.isAuthenticated = false;
12815
+ publishSnapshot(session, state);
12816
+ },
12817
+ release() {
12818
+ if (released) return;
12819
+ released = true;
12820
+ session.subscribers.delete(subscriber);
12821
+ if (session.subscribers.size === 0 && !session.inFlight) sessions.delete(scopeKey);
12822
+ }
12823
+ };
12824
+ }
12677
12825
  const BUNDLE_READY_TIMEOUT_MS = 3e4;
12826
+ class BundleReadinessTimeoutError extends Error {
12827
+ constructor(phase, message) {
12828
+ super(message);
12829
+ __publicField(this, "phase");
12830
+ this.name = "BundleReadinessTimeoutError";
12831
+ this.phase = phase;
12832
+ }
12833
+ }
12834
+ function isBundleReadinessTimeoutError(error) {
12835
+ return error instanceof BundleReadinessTimeoutError;
12836
+ }
12837
+ function createDefaultEnvironment() {
12838
+ const visibilityDocument = typeof document === "undefined" ? void 0 : document;
12839
+ const pageWindow = typeof window === "undefined" ? void 0 : window;
12840
+ return {
12841
+ isHidden: () => (visibilityDocument == null ? void 0 : visibilityDocument.visibilityState) === "hidden",
12842
+ now: () => Date.now(),
12843
+ setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
12844
+ clearTimeout: (timerId) => globalThis.clearTimeout(timerId),
12845
+ addVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.addEventListener("visibilitychange", listener),
12846
+ removeVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.removeEventListener("visibilitychange", listener),
12847
+ addPageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("pageshow", listener),
12848
+ removePageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("pageshow", listener)
12849
+ };
12850
+ }
12678
12851
  function isPromiseLike(value) {
12679
12852
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
12680
12853
  }
@@ -12684,28 +12857,101 @@ function isReadyHandle(value) {
12684
12857
  }
12685
12858
  return isPromiseLike(value.ready);
12686
12859
  }
12687
- function waitWithTimeout(promise, timeoutMs, timeoutMessage) {
12860
+ function waitWithActiveTimeTimeout(promise, timeoutMs, timeoutError, environment) {
12688
12861
  return new Promise((resolve, reject) => {
12689
- const timeoutId = setTimeout(() => {
12690
- reject(new Error(timeoutMessage));
12691
- }, timeoutMs);
12862
+ let settled = false;
12863
+ let remainingMs = Math.max(0, timeoutMs);
12864
+ let visibleStartedAt = null;
12865
+ let timeoutId = null;
12866
+ const clearActiveTimer = () => {
12867
+ if (timeoutId !== null) {
12868
+ environment.clearTimeout(timeoutId);
12869
+ timeoutId = null;
12870
+ }
12871
+ };
12872
+ const cleanup = () => {
12873
+ clearActiveTimer();
12874
+ environment.removeVisibilityChangeListener(handleVisibilityChange);
12875
+ };
12876
+ const finish = (callback) => {
12877
+ if (settled) return;
12878
+ settled = true;
12879
+ cleanup();
12880
+ callback();
12881
+ };
12882
+ const pauseDeadline = () => {
12883
+ clearActiveTimer();
12884
+ if (visibleStartedAt === null) return;
12885
+ remainingMs = Math.max(
12886
+ 0,
12887
+ remainingMs - Math.max(0, environment.now() - visibleStartedAt)
12888
+ );
12889
+ visibleStartedAt = null;
12890
+ };
12891
+ const startDeadline = () => {
12892
+ if (settled || environment.isHidden() || timeoutId !== null) return;
12893
+ if (remainingMs <= 0) {
12894
+ finish(() => reject(timeoutError()));
12895
+ return;
12896
+ }
12897
+ visibleStartedAt = environment.now();
12898
+ timeoutId = environment.setTimeout(() => {
12899
+ timeoutId = null;
12900
+ if (environment.isHidden()) {
12901
+ visibleStartedAt = null;
12902
+ return;
12903
+ }
12904
+ visibleStartedAt = null;
12905
+ finish(() => reject(timeoutError()));
12906
+ }, remainingMs);
12907
+ };
12908
+ function handleVisibilityChange() {
12909
+ if (environment.isHidden()) {
12910
+ pauseDeadline();
12911
+ } else {
12912
+ startDeadline();
12913
+ }
12914
+ }
12915
+ environment.addVisibilityChangeListener(handleVisibilityChange);
12916
+ startDeadline();
12692
12917
  Promise.resolve(promise).then(
12693
12918
  (value) => {
12694
- clearTimeout(timeoutId);
12695
- resolve(value);
12919
+ finish(() => resolve(value));
12696
12920
  },
12697
12921
  (error) => {
12698
- clearTimeout(timeoutId);
12699
- reject(error);
12922
+ finish(() => reject(error));
12700
12923
  }
12701
12924
  );
12702
12925
  });
12703
12926
  }
12704
- function resolveBundleMountResult(mountResult, timeoutMs = BUNDLE_READY_TIMEOUT_MS) {
12705
- return isPromiseLike(mountResult) ? waitWithTimeout(
12927
+ function scheduleForegroundRecovery(recover, environment = createDefaultEnvironment()) {
12928
+ if (!environment.isHidden()) return () => {
12929
+ };
12930
+ let active = true;
12931
+ const cleanup = () => {
12932
+ if (!active) return;
12933
+ active = false;
12934
+ environment.removeVisibilityChangeListener(handleForeground);
12935
+ environment.removePageShowListener(handleForeground);
12936
+ };
12937
+ const handleForeground = () => {
12938
+ if (!active || environment.isHidden()) return;
12939
+ cleanup();
12940
+ recover();
12941
+ };
12942
+ environment.addVisibilityChangeListener(handleForeground);
12943
+ environment.addPageShowListener(handleForeground);
12944
+ return cleanup;
12945
+ }
12946
+ function resolveBundleMountResult(mountResult, timeoutMs = BUNDLE_READY_TIMEOUT_MS, environment = createDefaultEnvironment()) {
12947
+ return isPromiseLike(mountResult) ? waitWithActiveTimeTimeout(
12706
12948
  mountResult,
12707
12949
  timeoutMs,
12708
- `Widget bundle mount did not resolve within ${timeoutMs}ms`
12950
+ () => new BundleReadinessTimeoutError(
12951
+ "mount",
12952
+ `Widget bundle mount did not resolve within ${timeoutMs}ms`
12953
+ ),
12954
+ environment
12709
12955
  ) : Promise.resolve(mountResult);
12710
12956
  }
12711
12957
  function createBundleMountCleanup(mountResult) {
@@ -12721,19 +12967,24 @@ function createBundleMountCleanup(mountResult) {
12721
12967
  unmount.call(mountResult);
12722
12968
  };
12723
12969
  }
12724
- async function waitForBundleMountReadiness(mountResult, firstPaint, timeoutMs = BUNDLE_READY_TIMEOUT_MS) {
12970
+ async function waitForBundleMountReadiness(mountResult, firstPaint, timeoutMs = BUNDLE_READY_TIMEOUT_MS, environment = createDefaultEnvironment()) {
12725
12971
  const resolvedMountResult = await resolveBundleMountResult(
12726
12972
  mountResult,
12727
- timeoutMs
12973
+ timeoutMs,
12974
+ environment
12728
12975
  );
12729
12976
  if (!isReadyHandle(resolvedMountResult)) {
12730
12977
  await firstPaint;
12731
12978
  return "first-paint";
12732
12979
  }
12733
- await waitWithTimeout(
12980
+ await waitWithActiveTimeTimeout(
12734
12981
  resolvedMountResult.ready,
12735
12982
  timeoutMs,
12736
- `Widget bundle did not report deep readiness within ${timeoutMs}ms`
12983
+ () => new BundleReadinessTimeoutError(
12984
+ "deep-ready",
12985
+ `Widget bundle did not report deep readiness within ${timeoutMs}ms`
12986
+ ),
12987
+ environment
12737
12988
  );
12738
12989
  return "deep-ready";
12739
12990
  }
@@ -13462,6 +13713,45 @@ function ensureWidgetPreconnects(origins, targetDocument) {
13462
13713
  documentTarget.head.appendChild(link);
13463
13714
  }
13464
13715
  }
13716
+ const USER_LOOKUP_ERRORS = /* @__PURE__ */ new Set([
13717
+ "User not found",
13718
+ "Missing required user identifier fields",
13719
+ "Missing user identifier fields"
13720
+ ]);
13721
+ function selectWidgetErrorPresentation({
13722
+ failureKind,
13723
+ customMessage,
13724
+ lastAuthError
13725
+ }) {
13726
+ if (failureKind === "bundle") {
13727
+ const message = "Widget failed to load. Please try again.";
13728
+ return {
13729
+ useCustomMessage: false,
13730
+ displayMessage: message,
13731
+ emittedMessage: message
13732
+ };
13733
+ }
13734
+ const useCustomMessage = Boolean(
13735
+ customMessage && (lastAuthError === null || (lastAuthError.error ? USER_LOOKUP_ERRORS.has(lastAuthError.error) : false))
13736
+ );
13737
+ if (useCustomMessage) {
13738
+ return {
13739
+ useCustomMessage: true,
13740
+ displayMessage: customMessage,
13741
+ emittedMessage: customMessage,
13742
+ hint: lastAuthError == null ? void 0 : lastAuthError.hint,
13743
+ status: lastAuthError == null ? void 0 : lastAuthError.status
13744
+ };
13745
+ }
13746
+ const emittedMessage = (lastAuthError == null ? void 0 : lastAuthError.error) || "Authentication failed";
13747
+ return {
13748
+ useCustomMessage: false,
13749
+ displayMessage: (lastAuthError == null ? void 0 : lastAuthError.hint) ? `${emittedMessage}: ${lastAuthError.hint}` : (lastAuthError == null ? void 0 : lastAuthError.error) ? emittedMessage : "Authentication failed. Please try again later.",
13750
+ emittedMessage,
13751
+ hint: lastAuthError == null ? void 0 : lastAuthError.hint,
13752
+ status: lastAuthError == null ? void 0 : lastAuthError.status
13753
+ };
13754
+ }
13465
13755
  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}';
13466
13756
  const DEFINED_ATTRS = /* @__PURE__ */ new Set([
13467
13757
  "community-id",
@@ -13859,6 +14149,8 @@ class BaseWidget extends HTMLElement {
13859
14149
  __publicField(this, "bannerNode", null);
13860
14150
  __publicField(this, "releaseAutoThemeDisableLease");
13861
14151
  __publicField(this, "bundleUnmount");
14152
+ __publicField(this, "cancelBundleForegroundRecovery");
14153
+ __publicField(this, "authSession");
13862
14154
  this.shadow = this.attachShadow({ mode: "closed" });
13863
14155
  }
13864
14156
  static get observedAttributes() {
@@ -13913,14 +14205,18 @@ class BaseWidget extends HTMLElement {
13913
14205
  }
13914
14206
  }
13915
14207
  disconnectedCallback() {
13916
- var _a, _b, _c;
14208
+ var _a, _b, _c, _d, _e;
13917
14209
  (_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
13918
14210
  (_b = this.themeObserver) == null ? void 0 : _b.disconnect();
13919
14211
  (_c = this.identityAttributeObserver) == null ? void 0 : _c.disconnect();
14212
+ (_d = this.cancelBundleForegroundRecovery) == null ? void 0 : _d.call(this);
14213
+ this.cancelBundleForegroundRecovery = void 0;
13920
14214
  this.teardownMountedWidget();
13921
14215
  this.clearLottieInstances();
13922
14216
  this.restoreLoaderHeight();
13923
14217
  this.removeBanner();
14218
+ (_e = this.authSession) == null ? void 0 : _e.release();
14219
+ this.authSession = void 0;
13924
14220
  clearState(this.state);
13925
14221
  }
13926
14222
  attributeChangedCallback(name, oldValue, newValue) {
@@ -14347,25 +14643,24 @@ class BaseWidget extends HTMLElement {
14347
14643
  if (container.children.length > 0) finish();
14348
14644
  });
14349
14645
  }
14350
- showError() {
14646
+ showError(failureKind = "authentication") {
14351
14647
  this.hideLoader();
14352
14648
  const settings = this.state.errorSettings;
14353
- const lastAuthError = this.state.lastAuthError;
14354
- 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";
14355
- const shouldUseCustomMessage = !!(settings == null ? void 0 : settings.errorMessage) && (isUserLookupError || !lastAuthError);
14356
- if (shouldUseCustomMessage) {
14649
+ const presentation = selectWidgetErrorPresentation({
14650
+ failureKind,
14651
+ customMessage: settings == null ? void 0 : settings.errorMessage,
14652
+ lastAuthError: this.state.lastAuthError
14653
+ });
14654
+ if (presentation.useCustomMessage) {
14357
14655
  this.renderCustomError(settings);
14358
14656
  } else if (this.errorEl) {
14359
- if ((lastAuthError == null ? void 0 : lastAuthError.error) && this.msgEl) {
14360
- this.msgEl.textContent = lastAuthError.hint ? `${lastAuthError.error}: ${lastAuthError.hint}` : lastAuthError.error;
14361
- }
14657
+ if (this.msgEl) this.msgEl.textContent = presentation.displayMessage;
14362
14658
  this.errorEl.classList.add("visible");
14363
14659
  }
14364
- const emittedMessage = shouldUseCustomMessage ? settings.errorMessage : (lastAuthError == null ? void 0 : lastAuthError.error) || "Authentication failed";
14365
14660
  this.emit("rai-error", {
14366
- message: emittedMessage,
14367
- hint: lastAuthError == null ? void 0 : lastAuthError.hint,
14368
- status: lastAuthError == null ? void 0 : lastAuthError.status
14661
+ message: presentation.emittedMessage,
14662
+ hint: presentation.hint,
14663
+ status: presentation.status
14369
14664
  });
14370
14665
  }
14371
14666
  renderCustomError(settings) {
@@ -14521,12 +14816,24 @@ class BaseWidget extends HTMLElement {
14521
14816
  }
14522
14817
  // ── Initialization ─────────────────────────────────────────────────────
14523
14818
  async init() {
14819
+ var _a, _b;
14820
+ (_a = this.cancelBundleForegroundRecovery) == null ? void 0 : _a.call(this);
14821
+ this.cancelBundleForegroundRecovery = void 0;
14524
14822
  const initId = ++this.initGeneration;
14525
14823
  this.hasAttemptedInit = true;
14526
14824
  if (!this.config.communityId) {
14527
14825
  this.showError();
14528
14826
  return;
14529
14827
  }
14828
+ if (!this.config.authUrl && !this.authSession) {
14829
+ this.authSession = subscribeAuthSession(this.config, this.state, () => {
14830
+ if (this.config.autoRefresh) this.scheduleRefresh();
14831
+ if (this.state.iframe) {
14832
+ sendTokenToWidget(this.config, this.state, this.state.iframe);
14833
+ }
14834
+ syncBundleAccessToken(this.config, this.state);
14835
+ });
14836
+ }
14530
14837
  if (this.config.bundleUrl) {
14531
14838
  this.prepareBundleRuntimeEnvironment();
14532
14839
  warmBundleRuntime(
@@ -14539,9 +14846,9 @@ class BaseWidget extends HTMLElement {
14539
14846
  (options) => fetchErrorSettings(this.config, this.state, options)
14540
14847
  );
14541
14848
  const launchWidget = () => {
14542
- var _a;
14849
+ var _a2;
14543
14850
  if (!this.isCurrentInit(initId)) return;
14544
- (_a = this.errorEl) == null ? void 0 : _a.classList.remove("visible");
14851
+ (_a2 = this.errorEl) == null ? void 0 : _a2.classList.remove("visible");
14545
14852
  this.shadow.querySelectorAll(".rai-custom-error").forEach((el2) => el2.remove());
14546
14853
  let bannerGated = false;
14547
14854
  let bannerGateTimer = 0;
@@ -14583,7 +14890,10 @@ class BaseWidget extends HTMLElement {
14583
14890
  clearState(this.state);
14584
14891
  clearStorage(this.config);
14585
14892
  const authed2 = await finishStartup(
14586
- authenticateServerless(
14893
+ this.authSession ? this.authSession.run(
14894
+ "authenticate",
14895
+ () => authenticateServerless(this.config, this.state)
14896
+ ) : authenticateServerless(
14587
14897
  this.config,
14588
14898
  this.state,
14589
14899
  () => this.scheduleRefresh()
@@ -14598,10 +14908,22 @@ class BaseWidget extends HTMLElement {
14598
14908
  }
14599
14909
  return;
14600
14910
  }
14601
- const hasStored = loadFromStorage(this.config, this.state);
14911
+ const hasSharedSession = Boolean(
14912
+ this.state.isAuthenticated && this.state.accessToken && this.state.accessTokenExpiry && Date.now() < this.state.accessTokenExpiry
14913
+ );
14914
+ const hasStored = hasSharedSession || loadFromStorage(this.config, this.state);
14602
14915
  if (hasStored) {
14916
+ if (hasSharedSession) {
14917
+ this.emit("rai-authenticated");
14918
+ launchWidget();
14919
+ return;
14920
+ }
14921
+ (_b = this.authSession) == null ? void 0 : _b.publish();
14603
14922
  const refreshed = await finishStartup(
14604
- refreshAccessToken(
14923
+ this.authSession ? this.authSession.run(
14924
+ "refresh",
14925
+ () => refreshAccessToken(this.config, this.state)
14926
+ ) : refreshAccessToken(
14605
14927
  this.config,
14606
14928
  this.state,
14607
14929
  () => this.scheduleRefresh()
@@ -14618,7 +14940,10 @@ class BaseWidget extends HTMLElement {
14618
14940
  return;
14619
14941
  }
14620
14942
  const authed = await finishStartup(
14621
- authenticateServerless(
14943
+ this.authSession ? this.authSession.run(
14944
+ "authenticate",
14945
+ () => authenticateServerless(this.config, this.state)
14946
+ ) : authenticateServerless(
14622
14947
  this.config,
14623
14948
  this.state,
14624
14949
  () => this.scheduleRefresh()
@@ -14717,7 +15042,7 @@ class BaseWidget extends HTMLElement {
14717
15042
  return { bundleApiUrl, colorMode };
14718
15043
  }
14719
15044
  async mountWidget(initId) {
14720
- var _a;
15045
+ var _a, _b;
14721
15046
  let container = null;
14722
15047
  let cleanupBundleMount;
14723
15048
  try {
@@ -14838,7 +15163,19 @@ class BaseWidget extends HTMLElement {
14838
15163
  container == null ? void 0 : container.remove();
14839
15164
  (_a = this.releaseAutoThemeDisableLease) == null ? void 0 : _a.call(this);
14840
15165
  this.releaseAutoThemeDisableLease = void 0;
14841
- this.showError();
15166
+ if (isBundleReadinessTimeoutError(err) && document.visibilityState === "hidden") {
15167
+ (_b = this.cancelBundleForegroundRecovery) == null ? void 0 : _b.call(this);
15168
+ let scheduledRecovery;
15169
+ scheduledRecovery = scheduleForegroundRecovery(() => {
15170
+ if (this.cancelBundleForegroundRecovery === scheduledRecovery) {
15171
+ this.cancelBundleForegroundRecovery = void 0;
15172
+ }
15173
+ if (this.isConnected) void this.init();
15174
+ });
15175
+ this.cancelBundleForegroundRecovery = scheduledRecovery;
15176
+ return;
15177
+ }
15178
+ this.showError("bundle");
14842
15179
  }
14843
15180
  }
14844
15181
  // ── Token scheduling ──────────────────────────────────────────────────
@@ -14861,7 +15198,10 @@ class BaseWidget extends HTMLElement {
14861
15198
  if (ok) pushToken();
14862
15199
  else this.handleSessionExpired();
14863
15200
  } else {
14864
- const ok = await refreshAccessToken(
15201
+ const ok = this.authSession ? await this.authSession.run(
15202
+ "refresh",
15203
+ () => refreshAccessToken(this.config, this.state)
15204
+ ) : await refreshAccessToken(
14865
15205
  this.config,
14866
15206
  this.state,
14867
15207
  () => this.scheduleRefresh(),
@@ -14914,16 +15254,20 @@ class BaseWidget extends HTMLElement {
14914
15254
  }
14915
15255
  // ── Logout helper ─────────────────────────────────────────────────────
14916
15256
  async logoutAndClear() {
15257
+ var _a;
14917
15258
  await logout(this.config, this.state);
15259
+ (_a = this.authSession) == null ? void 0 : _a.clear();
14918
15260
  this.teardownMountedWidget();
14919
15261
  this.removeBanner();
14920
15262
  this.emit("rai-logout");
14921
15263
  }
14922
15264
  // ── Public API ────────────────────────────────────────────────────────
14923
15265
  async reload() {
14924
- var _a;
15266
+ var _a, _b;
14925
15267
  const previousConfig = this.config;
14926
- this.config = readConfig(this, (_a = this.config) == null ? void 0 : _a.communityId);
15268
+ (_a = this.authSession) == null ? void 0 : _a.release();
15269
+ this.authSession = void 0;
15270
+ this.config = readConfig(this, (_b = this.config) == null ? void 0 : _b.communityId);
14927
15271
  this.identitySignature = identitySignatureForConfig(this.config);
14928
15272
  this.style.width = this.config.width;
14929
15273
  this.style.height = this.config.height;
@@ -15067,7 +15411,7 @@ class CustomWidget extends BaseWidget {
15067
15411
  }
15068
15412
  }
15069
15413
  }
15070
- console.log(`[rai-widget] v${"1.8.3"}`);
15414
+ console.log(`[rai-widget] v${"1.8.5"}`);
15071
15415
  class StoreWidgetCompat extends StoreWidget {
15072
15416
  }
15073
15417
  const WIDGET_REGISTRY = [
@@ -15131,7 +15475,7 @@ function exposePublicApi() {
15131
15475
  })();
15132
15476
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
15133
15477
  window.ReturningAIWidget = {
15134
- version: "1.8.3",
15478
+ version: "1.8.5",
15135
15479
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
15136
15480
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
15137
15481
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
@@ -22,6 +22,8 @@ export declare abstract class BaseWidget extends HTMLElement {
22
22
  private bannerNode;
23
23
  private releaseAutoThemeDisableLease?;
24
24
  private bundleUnmount?;
25
+ private cancelBundleForegroundRecovery?;
26
+ private authSession?;
25
27
  constructor();
26
28
  registerCallback<K extends keyof ReturningAIWidgetCallbackMap>(callbackName: K, callback: ReturningAIWidgetCallbackMap[K]): void;
27
29
  registerCallback(callbackName: string, callback: ReturningAIWidgetCallback): void;
@@ -0,0 +1,11 @@
1
+ import type { WidgetConfig, WidgetState } from '../types';
2
+ export type AuthSessionOperation = 'authenticate' | 'refresh';
3
+ export interface AuthSessionSubscription {
4
+ run(operation: AuthSessionOperation, execute: () => Promise<boolean>): Promise<boolean>;
5
+ publish(): void;
6
+ clear(): void;
7
+ release(): void;
8
+ }
9
+ export declare function createAuthScopeKey(config: WidgetConfig): string;
10
+ export declare function subscribeAuthSession(config: WidgetConfig, state: WidgetState, onSnapshot?: () => void): AuthSessionSubscription;
11
+ export declare function resetAuthSessionCoordinatorForTests(): void;