@returningai/widget-sdk 1.8.12 → 1.8.14

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.
@@ -12275,7 +12275,12 @@ function legacyStorageKey(config) {
12275
12275
  function errorSettingsKey(config) {
12276
12276
  return `${config.storagePrefix}-${config.widgetId}-error-settings`;
12277
12277
  }
12278
+ const retiredStates = /* @__PURE__ */ new WeakSet();
12279
+ function retireWidgetState(state) {
12280
+ retiredStates.add(state);
12281
+ }
12278
12282
  function saveToStorage(config, state) {
12283
+ if (retiredStates.has(state)) return;
12279
12284
  try {
12280
12285
  const data = {
12281
12286
  refreshToken: state.refreshToken,
@@ -12311,7 +12316,8 @@ function loadFromStorage(config, state) {
12311
12316
  return false;
12312
12317
  }
12313
12318
  }
12314
- function clearStorage(config) {
12319
+ function clearStorage(config, state) {
12320
+ if (state && retiredStates.has(state)) return;
12315
12321
  try {
12316
12322
  localStorage.removeItem(storageKey(config));
12317
12323
  localStorage.removeItem(legacyStorageKey(config));
@@ -12446,6 +12452,363 @@ function createAuthRenewalRecoveryController(retry, environment = createDefaultE
12446
12452
  cancel: reset
12447
12453
  };
12448
12454
  }
12455
+ const STORE_CALLBACK_FIELD_OPTIONS_SCOPE = "store.callback_field_options";
12456
+ const STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME = "callbackFieldOptions";
12457
+ const STORE_CALLBACK_FIELD_OPTIONS_TIMEOUT_MS = 15e3;
12458
+ const STORE_PURCHASE_SUCCESS_SCOPE = "store.purchase_success";
12459
+ const STORE_PURCHASE_SUCCESS_CALLBACK_NAME = "storePurchaseSuccess";
12460
+ const MILESTONE_CTA_CLICK_SCOPE = "milestone.cta_click";
12461
+ const MILESTONE_CTA_CLICK_CALLBACK_NAME = "milestoneCtaClick";
12462
+ const MILESTONE_CTA_CLICK_EVENT = "milestone_cta_clicked";
12463
+ function isRecord$1(value) {
12464
+ return typeof value === "object" && value !== null;
12465
+ }
12466
+ function isStringRecord$1(value) {
12467
+ return isRecord$1(value) && Object.values(value).every((item) => typeof item === "string");
12468
+ }
12469
+ function readEmbedTokenClaims(embedToken) {
12470
+ if (!embedToken) return null;
12471
+ const encodedPayload = embedToken.split(".")[1];
12472
+ if (!encodedPayload) return null;
12473
+ try {
12474
+ const normalizedPayload = encodedPayload.replace(/-/g, "+").replace(/_/g, "/");
12475
+ const paddedPayload = normalizedPayload.padEnd(
12476
+ Math.ceil(normalizedPayload.length / 4) * 4,
12477
+ "="
12478
+ );
12479
+ const bytes = Uint8Array.from(atob(paddedPayload), (character) => character.charCodeAt(0));
12480
+ const claims = JSON.parse(new TextDecoder().decode(bytes));
12481
+ return isRecord$1(claims) ? claims : null;
12482
+ } catch {
12483
+ return null;
12484
+ }
12485
+ }
12486
+ function readEmbedTokenUserIdentifiers(embedToken) {
12487
+ const claims = readEmbedTokenClaims(embedToken);
12488
+ if (!claims || !isStringRecord$1(claims.userIdentifiers)) return null;
12489
+ return { ...claims.userIdentifiers };
12490
+ }
12491
+ function resolveHostUserIdentifiers(config) {
12492
+ return readEmbedTokenUserIdentifiers(config.embedToken) ?? { ...config.userIdentifiers };
12493
+ }
12494
+ function hasCallbackContract(payload, callbackName, scope) {
12495
+ return isRecord$1(payload) && payload.callbackName === callbackName && payload.scope === scope;
12496
+ }
12497
+ function isMilestoneCtaClickPayload(payload) {
12498
+ if (!hasCallbackContract(
12499
+ payload,
12500
+ MILESTONE_CTA_CLICK_CALLBACK_NAME,
12501
+ MILESTONE_CTA_CLICK_SCOPE
12502
+ )) return false;
12503
+ const context = payload.context;
12504
+ if (!isRecord$1(context)) return false;
12505
+ const community = context.community;
12506
+ const milestone = context.milestone;
12507
+ const stage = context.stage;
12508
+ const button = context.button;
12509
+ const user = context.user;
12510
+ return context.event === MILESTONE_CTA_CLICK_EVENT && isRecord$1(community) && typeof community.id === "string" && community.id.length > 0 && (community.name === void 0 || community.name === null || typeof community.name === "string") && isRecord$1(milestone) && typeof milestone.id === "string" && milestone.id.length > 0 && typeof milestone.name === "string" && milestone.name.length > 0 && isRecord$1(stage) && typeof stage.id === "string" && stage.id.length > 0 && typeof stage.name === "string" && stage.name.length > 0 && isRecord$1(button) && typeof button.name === "string" && button.name.length > 0 && isRecord$1(user) && isStringRecord$1(user.identifiers) && typeof context.clickedAt === "string" && context.clickedAt.length > 0 && !Number.isNaN(Date.parse(context.clickedAt));
12511
+ }
12512
+ function enrichHostCallbackPayload(payload, userIdentifiers) {
12513
+ if (!userIdentifiers || Object.keys(userIdentifiers).length === 0) return payload;
12514
+ if (!isRecord$1(payload)) return payload;
12515
+ const context = payload.context;
12516
+ if (!isRecord$1(context)) return payload;
12517
+ const user = context.user;
12518
+ if (!isRecord$1(user)) return payload;
12519
+ return {
12520
+ ...payload,
12521
+ context: {
12522
+ ...context,
12523
+ user: {
12524
+ ...user,
12525
+ identifiers: { ...userIdentifiers }
12526
+ }
12527
+ }
12528
+ };
12529
+ }
12530
+ const HOST_CALLBACK_DEFINITIONS = {
12531
+ [STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME]: {
12532
+ callbackName: STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME,
12533
+ scope: STORE_CALLBACK_FIELD_OPTIONS_SCOPE,
12534
+ sourceWidgetType: "store",
12535
+ allowedHostWidgetTypes: ["store", "custom"],
12536
+ completionMode: "result",
12537
+ missingHandler: "reject",
12538
+ legacyActions: ["store:host-callback"],
12539
+ validatePayload: (payload) => hasCallbackContract(
12540
+ payload,
12541
+ STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME,
12542
+ STORE_CALLBACK_FIELD_OPTIONS_SCOPE
12543
+ )
12544
+ },
12545
+ [STORE_PURCHASE_SUCCESS_CALLBACK_NAME]: {
12546
+ callbackName: STORE_PURCHASE_SUCCESS_CALLBACK_NAME,
12547
+ scope: STORE_PURCHASE_SUCCESS_SCOPE,
12548
+ sourceWidgetType: "store",
12549
+ allowedHostWidgetTypes: ["store", "custom"],
12550
+ completionMode: "acknowledged",
12551
+ missingHandler: "reject",
12552
+ legacyActions: ["store:host-callback"],
12553
+ validatePayload: (payload) => hasCallbackContract(
12554
+ payload,
12555
+ STORE_PURCHASE_SUCCESS_CALLBACK_NAME,
12556
+ STORE_PURCHASE_SUCCESS_SCOPE
12557
+ )
12558
+ },
12559
+ [MILESTONE_CTA_CLICK_CALLBACK_NAME]: {
12560
+ callbackName: MILESTONE_CTA_CLICK_CALLBACK_NAME,
12561
+ scope: MILESTONE_CTA_CLICK_SCOPE,
12562
+ sourceWidgetType: "milestone",
12563
+ allowedHostWidgetTypes: ["milestone", "custom"],
12564
+ completionMode: "detached",
12565
+ missingHandler: "ignore",
12566
+ legacyActions: [],
12567
+ validatePayload: isMilestoneCtaClickPayload
12568
+ }
12569
+ };
12570
+ function createInvocationId() {
12571
+ return `host_callback_${Date.now()}_${Math.random().toString(36).slice(2)}`;
12572
+ }
12573
+ function observe(observer, event) {
12574
+ if (!observer) return;
12575
+ try {
12576
+ observer(event);
12577
+ } catch {
12578
+ }
12579
+ }
12580
+ function getHostCallbackDefinition(callbackName, payload) {
12581
+ const definition = HOST_CALLBACK_DEFINITIONS[callbackName];
12582
+ return (definition == null ? void 0 : definition.validatePayload(payload)) ? definition : void 0;
12583
+ }
12584
+ function getSupportedHostCallbacks(callbacks, hostWidgetType) {
12585
+ return Object.values(HOST_CALLBACK_DEFINITIONS).filter(
12586
+ (definition) => definition.allowedHostWidgetTypes.includes(hostWidgetType) && Object.prototype.hasOwnProperty.call(callbacks, definition.callbackName) && typeof callbacks[definition.callbackName] === "function"
12587
+ ).map((definition) => definition.callbackName);
12588
+ }
12589
+ function isHostCallbackMessageSupported(definition, action, sourceWidgetType, hostWidgetType) {
12590
+ const actionSupported = action === "widget:host-callback" || definition.legacyActions.includes(action);
12591
+ return actionSupported && sourceWidgetType === definition.sourceWidgetType && definition.allowedHostWidgetTypes.includes(hostWidgetType);
12592
+ }
12593
+ function dispatchHostCallback(input) {
12594
+ const definition = getHostCallbackDefinition(input.callbackName, input.payload);
12595
+ if (!definition || !definition.allowedHostWidgetTypes.includes(input.hostWidgetType)) {
12596
+ throw new Error("Host callback is not supported for this widget");
12597
+ }
12598
+ const invocationId = input.invocationId || createInvocationId();
12599
+ const eventBase = {
12600
+ invocationId,
12601
+ callbackName: definition.callbackName,
12602
+ scope: definition.scope,
12603
+ sourceWidgetType: definition.sourceWidgetType,
12604
+ hostWidgetType: input.hostWidgetType,
12605
+ completionMode: definition.completionMode,
12606
+ transport: input.transport
12607
+ };
12608
+ observe(input.observer, { type: "callback.received", ...eventBase });
12609
+ const callback = input.callbacks[definition.callbackName];
12610
+ if (!callback) {
12611
+ observe(input.observer, { type: "callback.handler_missing", ...eventBase });
12612
+ if (definition.missingHandler === "ignore") return;
12613
+ return Promise.reject(new Error("Host callback is not registered"));
12614
+ }
12615
+ observe(input.observer, { type: "callback.handler_started", ...eventBase });
12616
+ let result;
12617
+ try {
12618
+ result = callback(input.payload);
12619
+ } catch (error) {
12620
+ observe(input.observer, {
12621
+ type: "callback.handler_rejected",
12622
+ ...eventBase,
12623
+ error
12624
+ });
12625
+ if (definition.completionMode === "detached") return;
12626
+ return Promise.reject(new Error("Host callback failed"));
12627
+ }
12628
+ if (definition.completionMode === "detached") {
12629
+ if (result && typeof result.then === "function") {
12630
+ void Promise.resolve(result).then(
12631
+ () => observe(input.observer, { type: "callback.handler_resolved", ...eventBase }),
12632
+ (error) => observe(input.observer, {
12633
+ type: "callback.handler_rejected",
12634
+ ...eventBase,
12635
+ error
12636
+ })
12637
+ );
12638
+ } else {
12639
+ observe(input.observer, { type: "callback.handler_resolved", ...eventBase });
12640
+ }
12641
+ return;
12642
+ }
12643
+ return Promise.resolve(result).then(
12644
+ (value) => {
12645
+ observe(input.observer, { type: "callback.handler_resolved", ...eventBase });
12646
+ return definition.completionMode === "result" ? value : void 0;
12647
+ },
12648
+ (error) => {
12649
+ observe(input.observer, {
12650
+ type: "callback.handler_rejected",
12651
+ ...eventBase,
12652
+ error
12653
+ });
12654
+ throw new Error("Host callback failed");
12655
+ }
12656
+ );
12657
+ }
12658
+ const EMBED_TOKEN_REJECTED_ERROR = "Invalid or expired embed token";
12659
+ const EMBED_TOKEN_EXPIRY_WINDOW_MS = 3e4;
12660
+ const EMBED_TOKEN_REQUEST_TIMEOUT_MS = 1e4;
12661
+ const EMBED_TOKEN_REQUEST_COOLDOWN_MS = 3e4;
12662
+ const EMBED_TOKEN_CACHE_MARGIN_MS = 6e4;
12663
+ const defaultClock = {
12664
+ now: () => Date.now(),
12665
+ setTimeout: (callback, ms) => setTimeout(callback, ms),
12666
+ clearTimeout: (handle) => clearTimeout(handle)
12667
+ };
12668
+ function isRecord(value) {
12669
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12670
+ }
12671
+ function isStringRecord(value) {
12672
+ return isRecord(value) && Object.values(value).every((item) => typeof item === "string");
12673
+ }
12674
+ function decodeEmbedTokenClaims(embedToken) {
12675
+ const payload = readEmbedTokenClaims(embedToken ?? void 0);
12676
+ if (!payload) return null;
12677
+ return {
12678
+ accessId: typeof payload.accessId === "string" ? payload.accessId : void 0,
12679
+ communityId: typeof payload.communityId === "string" ? payload.communityId : void 0,
12680
+ userIdentifiers: isStringRecord(payload.userIdentifiers) ? { ...payload.userIdentifiers } : void 0,
12681
+ exp: typeof payload.exp === "number" && Number.isFinite(payload.exp) ? payload.exp : void 0
12682
+ };
12683
+ }
12684
+ function sortedIdentifiers(identifiers) {
12685
+ return Object.keys(identifiers || {}).sort().map((key) => [key, identifiers[key]]);
12686
+ }
12687
+ function hasIdentifiers(identifiers) {
12688
+ return !!identifiers && Object.keys(identifiers).length > 0;
12689
+ }
12690
+ function embedIdentityKey(embedToken) {
12691
+ if (!embedToken) return "";
12692
+ const claims = decodeEmbedTokenClaims(embedToken);
12693
+ if (!claims || !hasIdentifiers(claims.userIdentifiers)) return embedToken;
12694
+ return JSON.stringify({
12695
+ accessId: claims.accessId ?? "",
12696
+ communityId: claims.communityId ?? "",
12697
+ userIdentifiers: sortedIdentifiers(claims.userIdentifiers)
12698
+ });
12699
+ }
12700
+ function identitySignatureForConfig(config) {
12701
+ return JSON.stringify({
12702
+ embedToken: embedIdentityKey(config.embedToken),
12703
+ userIdentifiers: sortedIdentifiers(config.userIdentifiers)
12704
+ });
12705
+ }
12706
+ function isEmbedTokenRejected(error) {
12707
+ return !!error && error.status === 401 && error.error === EMBED_TOKEN_REJECTED_ERROR;
12708
+ }
12709
+ function isEmbedTokenExpiring(embedToken, now = Date.now()) {
12710
+ var _a;
12711
+ const exp = (_a = decodeEmbedTokenClaims(embedToken)) == null ? void 0 : _a.exp;
12712
+ if (exp === void 0) return false;
12713
+ return exp * 1e3 <= now + EMBED_TOKEN_EXPIRY_WINDOW_MS;
12714
+ }
12715
+ function readEmbedTokenProvider(callbacks) {
12716
+ const provider = callbacks == null ? void 0 : callbacks.embedTokenRequest;
12717
+ return typeof provider === "function" ? provider : null;
12718
+ }
12719
+ function sameIdentifiers(left, right) {
12720
+ return JSON.stringify(sortedIdentifiers(left)) === JSON.stringify(sortedIdentifiers(right));
12721
+ }
12722
+ function acceptRenewedEmbedToken(staleToken, result, now = Date.now()) {
12723
+ if (!isRecord(result)) return null;
12724
+ const candidate = result.embedToken;
12725
+ if (typeof candidate !== "string" || !candidate || candidate === staleToken) return null;
12726
+ const stale = decodeEmbedTokenClaims(staleToken);
12727
+ const next = decodeEmbedTokenClaims(candidate);
12728
+ if (!stale || !next) return null;
12729
+ if (next.exp === void 0 || next.exp * 1e3 <= now + EMBED_TOKEN_EXPIRY_WINDOW_MS) return null;
12730
+ if (!stale.communityId || stale.communityId !== next.communityId) return null;
12731
+ if (hasIdentifiers(stale.userIdentifiers)) {
12732
+ if (!sameIdentifiers(stale.userIdentifiers, next.userIdentifiers)) return null;
12733
+ } else if (hasIdentifiers(next.userIdentifiers)) {
12734
+ return null;
12735
+ }
12736
+ return candidate;
12737
+ }
12738
+ const REGISTRY_SYMBOL$1 = Symbol.for("returningai.widget-sdk.embed-token-renewal.v1");
12739
+ function registry$1() {
12740
+ const root = globalThis;
12741
+ root[REGISTRY_SYMBOL$1] ?? (root[REGISTRY_SYMBOL$1] = /* @__PURE__ */ new Map());
12742
+ return root[REGISTRY_SYMBOL$1];
12743
+ }
12744
+ function registryEntry(key) {
12745
+ const entries2 = registry$1();
12746
+ let entry = entries2.get(key);
12747
+ if (!entry) {
12748
+ entry = { inFlight: null, cachedToken: null, cachedUntil: 0, cooldownUntil: 0 };
12749
+ entries2.set(key, entry);
12750
+ }
12751
+ return entry;
12752
+ }
12753
+ function markRenewedEmbedTokenRefused(staleToken, refusedToken, clock = defaultClock) {
12754
+ const cooldownUntil = clock.now() + EMBED_TOKEN_REQUEST_COOLDOWN_MS;
12755
+ for (const token of /* @__PURE__ */ new Set([staleToken, refusedToken])) {
12756
+ const entry = registryEntry(embedIdentityKey(token));
12757
+ if (entry.cachedToken === refusedToken) {
12758
+ entry.cachedToken = null;
12759
+ entry.cachedUntil = 0;
12760
+ }
12761
+ entry.cooldownUntil = Math.max(entry.cooldownUntil, cooldownUntil);
12762
+ }
12763
+ }
12764
+ function renewEmbedToken(request) {
12765
+ const clock = request.clock ?? defaultClock;
12766
+ const entry = registryEntry(embedIdentityKey(request.staleToken));
12767
+ const now = clock.now();
12768
+ if (entry.cachedToken && entry.cachedToken !== request.staleToken && now < entry.cachedUntil) {
12769
+ return Promise.resolve(entry.cachedToken);
12770
+ }
12771
+ if (entry.inFlight) return entry.inFlight;
12772
+ if (now < entry.cooldownUntil) return Promise.resolve(null);
12773
+ const current = entry;
12774
+ const inFlight = (async () => {
12775
+ var _a;
12776
+ let timer;
12777
+ const timeout = new Promise((resolve) => {
12778
+ timer = clock.setTimeout(() => resolve(void 0), EMBED_TOKEN_REQUEST_TIMEOUT_MS);
12779
+ });
12780
+ let accepted = null;
12781
+ try {
12782
+ const answer = await Promise.race([
12783
+ Promise.resolve().then(() => request.provider({ reason: request.reason })),
12784
+ timeout
12785
+ ]);
12786
+ accepted = acceptRenewedEmbedToken(request.staleToken, answer, clock.now());
12787
+ } catch {
12788
+ accepted = null;
12789
+ } finally {
12790
+ clock.clearTimeout(timer);
12791
+ }
12792
+ if (accepted) {
12793
+ const exp = ((_a = decodeEmbedTokenClaims(accepted)) == null ? void 0 : _a.exp) ?? 0;
12794
+ current.cachedToken = accepted;
12795
+ current.cachedUntil = exp * 1e3 - EMBED_TOKEN_CACHE_MARGIN_MS;
12796
+ current.cooldownUntil = 0;
12797
+ } else {
12798
+ current.cooldownUntil = clock.now() + EMBED_TOKEN_REQUEST_COOLDOWN_MS;
12799
+ }
12800
+ current.inFlight = null;
12801
+ return accepted;
12802
+ })();
12803
+ current.inFlight = inFlight;
12804
+ return inFlight;
12805
+ }
12806
+ function decideEmbedTokenChange(input) {
12807
+ if (!input.hasAttemptedInit) return "update";
12808
+ if (!input.sameSignature) return "reload";
12809
+ if (!input.tokenChanged) return "update";
12810
+ return input.healthy && input.hasProvider ? "swap" : "reload";
12811
+ }
12449
12812
  function setTokens(config, state, data, onRefreshScheduled) {
12450
12813
  state.accessToken = data.accessToken;
12451
12814
  state.refreshToken = data.refreshToken;
@@ -12471,13 +12834,62 @@ function buildIdentityProof(config) {
12471
12834
  }
12472
12835
  return proof;
12473
12836
  }
12837
+ function canReauthenticateAfterTransientFailure(config) {
12838
+ var _a;
12839
+ if (!config.embedToken) return true;
12840
+ const exp = (_a = readEmbedTokenClaims(config.embedToken)) == null ? void 0 : _a.exp;
12841
+ return typeof exp === "number" && exp * 1e3 > Date.now();
12842
+ }
12474
12843
  function calculateRefreshDelay(accessTokenExpiry, now = Date.now()) {
12475
12844
  const remainingLifetime = accessTokenExpiry - now;
12476
12845
  if (remainingLifetime <= 0) return 0;
12477
12846
  const refreshLeadTime = Math.min(6e4, remainingLifetime / 2);
12478
12847
  return Math.max(0, Math.floor(remainingLifetime - refreshLeadTime));
12479
12848
  }
12480
- async function authenticateServerless(config, state, onRefreshScheduled) {
12849
+ async function authenticateServerless(config, state, renewal, onRefreshScheduled) {
12850
+ var _a;
12851
+ const provider = ((_a = renewal.getEmbedTokenProvider) == null ? void 0 : _a.call(renewal)) ?? null;
12852
+ const isCurrent = () => !renewal.isCurrent || renewal.isCurrent();
12853
+ let renewalAttempted = false;
12854
+ let renewed = null;
12855
+ let presentedToken;
12856
+ const renewEmbedTokenOnce = async () => {
12857
+ const staleToken = config.embedToken;
12858
+ if (!provider || !staleToken || renewalAttempted) return false;
12859
+ renewalAttempted = true;
12860
+ const fresh = await renewEmbedToken({
12861
+ staleToken,
12862
+ reason: renewal.reason ?? "expired",
12863
+ provider
12864
+ });
12865
+ if (!fresh) return false;
12866
+ if (config.embedToken === staleToken) config.embedToken = fresh;
12867
+ renewed = { from: staleToken, to: fresh };
12868
+ return true;
12869
+ };
12870
+ const signIn = async () => {
12871
+ const presented = config.embedToken;
12872
+ presentedToken = presented;
12873
+ const authed = await signInOnce(config, state, onRefreshScheduled);
12874
+ if (!authed && renewed && presented === renewed.to && isEmbedTokenRejected(state.lastAuthError)) {
12875
+ markRenewedEmbedTokenRefused(renewed.from, renewed.to);
12876
+ }
12877
+ return authed;
12878
+ };
12879
+ if (provider && config.embedToken && isEmbedTokenExpiring(config.embedToken)) {
12880
+ await renewEmbedTokenOnce();
12881
+ }
12882
+ if (await signIn()) return true;
12883
+ if (!isEmbedTokenRejected(state.lastAuthError)) return false;
12884
+ if (provider && config.embedToken && config.embedToken !== presentedToken) {
12885
+ if (!isCurrent()) return false;
12886
+ return signIn();
12887
+ }
12888
+ if (!await renewEmbedTokenOnce() || !isCurrent()) return false;
12889
+ return signIn();
12890
+ }
12891
+ async function signInOnce(config, state, onRefreshScheduled) {
12892
+ state.lastAuthError = null;
12481
12893
  const nonce = crypto.randomUUID();
12482
12894
  const timestamp = Date.now();
12483
12895
  const maxRetries = config.maxRetries ?? 3;
@@ -12610,7 +13022,7 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
12610
13022
  state.isAuthenticated = false;
12611
13023
  return false;
12612
13024
  }
12613
- async function refreshAccessToken(config, state, onRefreshScheduled, sendToken) {
13025
+ async function refreshAccessToken(config, state, renewal, onRefreshScheduled, sendToken) {
12614
13026
  if (state.isRefreshing) {
12615
13027
  return state.refreshPromise;
12616
13028
  }
@@ -12626,50 +13038,81 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12626
13038
  };
12627
13039
  state.isRefreshing = true;
12628
13040
  state.refreshPromise = (async () => {
12629
- const reauthenticateAfterRefreshFailure = async () => {
13041
+ const adoptNewerGeneration = () => {
12630
13042
  if (state.refreshToken && state.refreshToken !== presentedRefreshToken && state.accessToken && state.accessTokenExpiry && Date.now() < state.accessTokenExpiry) {
12631
13043
  state.isAuthenticated = true;
12632
13044
  if (sendToken) sendToken();
12633
13045
  return true;
12634
13046
  }
12635
- const authed = await authenticateServerless(config, state, onRefreshScheduled);
13047
+ return false;
13048
+ };
13049
+ const reauthenticateAfterRefreshFailure = async () => {
13050
+ if (adoptNewerGeneration()) return true;
13051
+ const authed = await authenticateServerless(
13052
+ config,
13053
+ state,
13054
+ { ...renewal, reason: "session-expired" },
13055
+ onRefreshScheduled
13056
+ );
12636
13057
  if (authed && sendToken) sendToken();
12637
13058
  if (!authed && isPermanentAuthFailure(state.lastAuthError)) {
12638
13059
  const permanentError = state.lastAuthError;
12639
13060
  clearAuthenticationData(state);
12640
13061
  state.lastAuthError = permanentError;
12641
- clearStorage(config);
13062
+ clearStorage(config, state);
12642
13063
  } else if (!authed) {
12643
13064
  Object.assign(state, previousAuthentication);
12644
13065
  }
12645
13066
  return authed;
12646
13067
  };
13068
+ const retainSessionAfterTransientFailure = async (failure) => {
13069
+ if (adoptNewerGeneration()) return true;
13070
+ if (canReauthenticateAfterTransientFailure(config)) {
13071
+ return reauthenticateAfterRefreshFailure();
13072
+ }
13073
+ Object.assign(state, previousAuthentication);
13074
+ state.lastAuthError = failure;
13075
+ return false;
13076
+ };
12647
13077
  try {
12648
13078
  if (!presentedRefreshToken) {
12649
13079
  return reauthenticateAfterRefreshFailure();
12650
13080
  }
12651
- const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/refresh`, {
12652
- method: "POST",
12653
- headers: {
12654
- "Content-Type": "application/json",
12655
- Authorization: `Bearer ${presentedRefreshToken}`
12656
- },
12657
- body: JSON.stringify({
12658
- widgetType: config.widgetType,
12659
- ...buildIdentityProof(config)
12660
- })
12661
- });
13081
+ let response;
13082
+ try {
13083
+ response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/refresh`, {
13084
+ method: "POST",
13085
+ headers: {
13086
+ "Content-Type": "application/json",
13087
+ Authorization: `Bearer ${presentedRefreshToken}`
13088
+ },
13089
+ body: JSON.stringify({
13090
+ widgetType: config.widgetType,
13091
+ ...buildIdentityProof(config)
13092
+ })
13093
+ });
13094
+ } catch (err) {
13095
+ return retainSessionAfterTransientFailure({
13096
+ status: 0,
13097
+ error: err instanceof Error ? err.message : "Token refresh failed"
13098
+ });
13099
+ }
12662
13100
  if (!response.ok) {
12663
- if (response.status === 400 || response.status === 401 || response.status === 403) {
13101
+ const error = await response.json().catch(() => ({ error: "Token refresh failed" }));
13102
+ const failure = {
13103
+ status: response.status,
13104
+ error: (error == null ? void 0 : error.error) || `HTTP ${response.status}`
13105
+ };
13106
+ if (isPermanentAuthFailure(failure)) {
12664
13107
  return reauthenticateAfterRefreshFailure();
12665
13108
  }
12666
- const error = await response.json().catch(() => ({ error: "Token refresh failed" }));
12667
- throw new Error(error.error || `HTTP ${response.status}`);
13109
+ return retainSessionAfterTransientFailure(failure);
12668
13110
  }
12669
13111
  const data = await response.json();
12670
13112
  if (!data.accessToken || !data.refreshToken) {
12671
13113
  throw new Error("Invalid refresh response");
12672
13114
  }
13115
+ state.lastAuthError = null;
12673
13116
  setTokens(config, state, data, onRefreshScheduled);
12674
13117
  state.isAuthenticated = true;
12675
13118
  if (sendToken) sendToken();
@@ -12683,19 +13126,17 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
12683
13126
  })();
12684
13127
  return state.refreshPromise;
12685
13128
  }
12686
- async function getValidToken(config, state, onRefreshScheduled) {
13129
+ async function getValidToken(config, state, renewal, onRefreshScheduled) {
12687
13130
  if (state.accessToken && !isTokenExpired(state.accessTokenExpiry)) {
12688
13131
  return state.accessToken;
12689
13132
  }
12690
13133
  if (config.authUrl) {
12691
- const authed2 = await authenticateViaProxy(config, state, onRefreshScheduled);
12692
- if (authed2 && state.accessToken) return state.accessToken;
13134
+ const authed = await authenticateViaProxy(config, state, onRefreshScheduled);
13135
+ if (authed && state.accessToken) return state.accessToken;
12693
13136
  throw new Error("Unable to obtain valid token");
12694
13137
  }
12695
- const refreshed = await refreshAccessToken(config, state, onRefreshScheduled);
13138
+ const refreshed = await refreshAccessToken(config, state, renewal, onRefreshScheduled);
12696
13139
  if (refreshed && state.accessToken) return state.accessToken;
12697
- const authed = await authenticateServerless(config, state, onRefreshScheduled);
12698
- if (authed && state.accessToken) return state.accessToken;
12699
13140
  throw new Error("Unable to obtain valid token");
12700
13141
  }
12701
13142
  async function logout(config, state) {
@@ -12713,7 +13154,7 @@ async function logout(config, state) {
12713
13154
  }
12714
13155
  }
12715
13156
  clearState(state);
12716
- clearStorage(config);
13157
+ clearStorage(config, state);
12717
13158
  }
12718
13159
  const inflightErrorSettings = /* @__PURE__ */ new Map();
12719
13160
  function applyErrorSettingsToState(state, data) {
@@ -12882,7 +13323,7 @@ function applySnapshot(subscriber, snapshot, notify = true) {
12882
13323
  var _a;
12883
13324
  Object.assign(subscriber.state, snapshot);
12884
13325
  if (snapshot.refreshToken) saveToStorage(subscriber.config, subscriber.state);
12885
- else clearStorage(subscriber.config);
13326
+ else clearStorage(subscriber.config, subscriber.state);
12886
13327
  if (!notify) return;
12887
13328
  try {
12888
13329
  (_a = subscriber.onSnapshot) == null ? void 0 : _a.call(subscriber);
@@ -12944,10 +13385,9 @@ function subscribeAuthSession(config, state, onSnapshot) {
12944
13385
  return inFlight;
12945
13386
  },
12946
13387
  publish() {
12947
- if (!released) publishSnapshot(session, state);
13388
+ publishSnapshot(session, state);
12948
13389
  },
12949
13390
  clear() {
12950
- if (released) return;
12951
13391
  state.accessToken = null;
12952
13392
  state.refreshToken = null;
12953
13393
  state.tokenFamily = null;
@@ -13365,283 +13805,89 @@ class WidgetCallbackRegistry {
13365
13805
  return { ...this.registeredCallbacks };
13366
13806
  }
13367
13807
  }
13368
- const loadedGoogleFontFamilies = /* @__PURE__ */ new Set();
13369
- const LOCAL_OR_SYSTEM_FONT_FAMILIES = /* @__PURE__ */ new Set([
13370
- "aeonik",
13371
- "courier",
13372
- "courier new",
13373
- "monaco",
13374
- "monospace",
13375
- "sans-serif",
13376
- "serif",
13377
- "system-ui"
13378
- ]);
13379
- const SERIF_FONT_FAMILIES = /* @__PURE__ */ new Set(["lora", "playfair display", "prata"]);
13380
- const GOOGLE_FONT_VARIANTS = ":ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,700";
13381
- function normalizeFontFamily(family) {
13382
- return family.trim() === "Noto" ? "Noto Sans" : family.trim();
13383
- }
13384
- function getFontStack(family) {
13385
- const normalizedFamily = normalizeFontFamily(family);
13386
- if (normalizedFamily.includes(",")) return normalizedFamily;
13387
- const normalizedKey = normalizedFamily.toLowerCase();
13388
- switch (normalizedKey) {
13389
- case "monospace":
13390
- return "monospace";
13391
- case "courier":
13392
- case "courier new":
13393
- return '"Courier New", Courier, monospace';
13394
- case "monaco":
13395
- return "Monaco, Menlo, Consolas, monospace";
13396
- case "system-ui":
13397
- return "system-ui, -apple-system, sans-serif";
13398
- case "sans-serif":
13399
- case "serif":
13400
- return normalizedKey;
13401
- default:
13402
- return `'${normalizedFamily.replace(/[\\']/g, "\\\\$&")}', ${SERIF_FONT_FAMILIES.has(normalizedKey) ? "Georgia, 'Times New Roman', serif" : "system-ui, -apple-system, sans-serif"}`;
13403
- }
13404
- }
13405
- function ensureGoogleFontPreconnect() {
13406
- if (!document.getElementById("rai-fonts-preconnect")) {
13407
- const link = document.createElement("link");
13408
- link.id = "rai-fonts-preconnect";
13409
- link.rel = "preconnect";
13410
- link.href = "https://fonts.googleapis.com";
13411
- document.head.appendChild(link);
13412
- }
13413
- if (!document.getElementById("rai-fonts-static-preconnect")) {
13414
- const link = document.createElement("link");
13415
- link.id = "rai-fonts-static-preconnect";
13416
- link.rel = "preconnect";
13417
- link.href = "https://fonts.gstatic.com";
13418
- link.crossOrigin = "anonymous";
13419
- document.head.appendChild(link);
13420
- }
13421
- }
13422
- function ensureGoogleFontLoaded(family) {
13423
- const normalizedFamily = normalizeFontFamily(family);
13424
- const fontKey = normalizedFamily.toLowerCase();
13425
- if (!normalizedFamily || normalizedFamily.includes(",") || LOCAL_OR_SYSTEM_FONT_FAMILIES.has(fontKey)) {
13426
- return;
13427
- }
13428
- if (loadedGoogleFontFamilies.has(fontKey)) return;
13429
- ensureGoogleFontPreconnect();
13430
- const linkId = `rai-font-${normalizedFamily.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
13431
- const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(
13432
- normalizedFamily
13433
- )}${GOOGLE_FONT_VARIANTS}&display=swap`;
13434
- let link = document.getElementById(linkId);
13435
- if (link && link.href !== fontUrl) {
13436
- link.remove();
13437
- link = null;
13438
- }
13439
- if (!link) {
13440
- link = document.createElement("link");
13441
- link.id = linkId;
13442
- link.rel = "stylesheet";
13443
- link.href = fontUrl;
13444
- document.head.appendChild(link);
13445
- }
13446
- loadedGoogleFontFamilies.add(fontKey);
13447
- link.onerror = () => {
13448
- loadedGoogleFontFamilies.delete(fontKey);
13449
- link == null ? void 0 : link.remove();
13450
- };
13451
- }
13452
- const STORE_CALLBACK_FIELD_OPTIONS_SCOPE = "store.callback_field_options";
13453
- const STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME = "callbackFieldOptions";
13454
- const STORE_CALLBACK_FIELD_OPTIONS_TIMEOUT_MS = 15e3;
13455
- const STORE_PURCHASE_SUCCESS_SCOPE = "store.purchase_success";
13456
- const STORE_PURCHASE_SUCCESS_CALLBACK_NAME = "storePurchaseSuccess";
13457
- const MILESTONE_CTA_CLICK_SCOPE = "milestone.cta_click";
13458
- const MILESTONE_CTA_CLICK_CALLBACK_NAME = "milestoneCtaClick";
13459
- const MILESTONE_CTA_CLICK_EVENT = "milestone_cta_clicked";
13460
- function isRecord(value) {
13461
- return typeof value === "object" && value !== null;
13462
- }
13463
- function isStringRecord(value) {
13464
- return isRecord(value) && Object.values(value).every((item) => typeof item === "string");
13465
- }
13466
- function readEmbedTokenUserIdentifiers(embedToken) {
13467
- if (!embedToken) return null;
13468
- const encodedPayload = embedToken.split(".")[1];
13469
- if (!encodedPayload) return null;
13470
- try {
13471
- const normalizedPayload = encodedPayload.replace(/-/g, "+").replace(/_/g, "/");
13472
- const paddedPayload = normalizedPayload.padEnd(
13473
- Math.ceil(normalizedPayload.length / 4) * 4,
13474
- "="
13475
- );
13476
- const bytes = Uint8Array.from(atob(paddedPayload), (character) => character.charCodeAt(0));
13477
- const claims = JSON.parse(new TextDecoder().decode(bytes));
13478
- if (!isRecord(claims) || !isStringRecord(claims.userIdentifiers)) return null;
13479
- return { ...claims.userIdentifiers };
13480
- } catch {
13481
- return null;
13482
- }
13483
- }
13484
- function resolveHostUserIdentifiers(config) {
13485
- return readEmbedTokenUserIdentifiers(config.embedToken) ?? { ...config.userIdentifiers };
13486
- }
13487
- function hasCallbackContract(payload, callbackName, scope) {
13488
- return isRecord(payload) && payload.callbackName === callbackName && payload.scope === scope;
13489
- }
13490
- function isMilestoneCtaClickPayload(payload) {
13491
- if (!hasCallbackContract(
13492
- payload,
13493
- MILESTONE_CTA_CLICK_CALLBACK_NAME,
13494
- MILESTONE_CTA_CLICK_SCOPE
13495
- )) return false;
13496
- const context = payload.context;
13497
- if (!isRecord(context)) return false;
13498
- const community = context.community;
13499
- const milestone = context.milestone;
13500
- const stage = context.stage;
13501
- const button = context.button;
13502
- const user = context.user;
13503
- return context.event === MILESTONE_CTA_CLICK_EVENT && isRecord(community) && typeof community.id === "string" && community.id.length > 0 && (community.name === void 0 || community.name === null || typeof community.name === "string") && isRecord(milestone) && typeof milestone.id === "string" && milestone.id.length > 0 && typeof milestone.name === "string" && milestone.name.length > 0 && isRecord(stage) && typeof stage.id === "string" && stage.id.length > 0 && typeof stage.name === "string" && stage.name.length > 0 && isRecord(button) && typeof button.name === "string" && button.name.length > 0 && isRecord(user) && isStringRecord(user.identifiers) && typeof context.clickedAt === "string" && context.clickedAt.length > 0 && !Number.isNaN(Date.parse(context.clickedAt));
13504
- }
13505
- function enrichHostCallbackPayload(payload, userIdentifiers) {
13506
- if (!userIdentifiers || Object.keys(userIdentifiers).length === 0) return payload;
13507
- if (!isRecord(payload)) return payload;
13508
- const context = payload.context;
13509
- if (!isRecord(context)) return payload;
13510
- const user = context.user;
13511
- if (!isRecord(user)) return payload;
13512
- return {
13513
- ...payload,
13514
- context: {
13515
- ...context,
13516
- user: {
13517
- ...user,
13518
- identifiers: { ...userIdentifiers }
13519
- }
13520
- }
13521
- };
13522
- }
13523
- const HOST_CALLBACK_DEFINITIONS = {
13524
- [STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME]: {
13525
- callbackName: STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME,
13526
- scope: STORE_CALLBACK_FIELD_OPTIONS_SCOPE,
13527
- sourceWidgetType: "store",
13528
- allowedHostWidgetTypes: ["store", "custom"],
13529
- completionMode: "result",
13530
- missingHandler: "reject",
13531
- legacyActions: ["store:host-callback"],
13532
- validatePayload: (payload) => hasCallbackContract(
13533
- payload,
13534
- STORE_CALLBACK_FIELD_OPTIONS_CALLBACK_NAME,
13535
- STORE_CALLBACK_FIELD_OPTIONS_SCOPE
13536
- )
13537
- },
13538
- [STORE_PURCHASE_SUCCESS_CALLBACK_NAME]: {
13539
- callbackName: STORE_PURCHASE_SUCCESS_CALLBACK_NAME,
13540
- scope: STORE_PURCHASE_SUCCESS_SCOPE,
13541
- sourceWidgetType: "store",
13542
- allowedHostWidgetTypes: ["store", "custom"],
13543
- completionMode: "acknowledged",
13544
- missingHandler: "reject",
13545
- legacyActions: ["store:host-callback"],
13546
- validatePayload: (payload) => hasCallbackContract(
13547
- payload,
13548
- STORE_PURCHASE_SUCCESS_CALLBACK_NAME,
13549
- STORE_PURCHASE_SUCCESS_SCOPE
13550
- )
13551
- },
13552
- [MILESTONE_CTA_CLICK_CALLBACK_NAME]: {
13553
- callbackName: MILESTONE_CTA_CLICK_CALLBACK_NAME,
13554
- scope: MILESTONE_CTA_CLICK_SCOPE,
13555
- sourceWidgetType: "milestone",
13556
- allowedHostWidgetTypes: ["milestone", "custom"],
13557
- completionMode: "detached",
13558
- missingHandler: "ignore",
13559
- legacyActions: [],
13560
- validatePayload: isMilestoneCtaClickPayload
13561
- }
13562
- };
13563
- function createInvocationId() {
13564
- return `host_callback_${Date.now()}_${Math.random().toString(36).slice(2)}`;
13565
- }
13566
- function observe(observer, event) {
13567
- if (!observer) return;
13568
- try {
13569
- observer(event);
13570
- } catch {
13571
- }
13572
- }
13573
- function getHostCallbackDefinition(callbackName, payload) {
13574
- const definition = HOST_CALLBACK_DEFINITIONS[callbackName];
13575
- return (definition == null ? void 0 : definition.validatePayload(payload)) ? definition : void 0;
13576
- }
13577
- function isHostCallbackMessageSupported(definition, action, sourceWidgetType, hostWidgetType) {
13578
- const actionSupported = action === "widget:host-callback" || definition.legacyActions.includes(action);
13579
- return actionSupported && sourceWidgetType === definition.sourceWidgetType && definition.allowedHostWidgetTypes.includes(hostWidgetType);
13808
+ const loadedGoogleFontFamilies = /* @__PURE__ */ new Set();
13809
+ const LOCAL_OR_SYSTEM_FONT_FAMILIES = /* @__PURE__ */ new Set([
13810
+ "aeonik",
13811
+ "courier",
13812
+ "courier new",
13813
+ "monaco",
13814
+ "monospace",
13815
+ "sans-serif",
13816
+ "serif",
13817
+ "system-ui"
13818
+ ]);
13819
+ const SERIF_FONT_FAMILIES = /* @__PURE__ */ new Set(["lora", "playfair display", "prata"]);
13820
+ const GOOGLE_FONT_VARIANTS = ":ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,700";
13821
+ function normalizeFontFamily(family) {
13822
+ return family.trim() === "Noto" ? "Noto Sans" : family.trim();
13580
13823
  }
13581
- function dispatchHostCallback(input) {
13582
- const definition = getHostCallbackDefinition(input.callbackName, input.payload);
13583
- if (!definition || !definition.allowedHostWidgetTypes.includes(input.hostWidgetType)) {
13584
- throw new Error("Host callback is not supported for this widget");
13824
+ function getFontStack(family) {
13825
+ const normalizedFamily = normalizeFontFamily(family);
13826
+ if (normalizedFamily.includes(",")) return normalizedFamily;
13827
+ const normalizedKey = normalizedFamily.toLowerCase();
13828
+ switch (normalizedKey) {
13829
+ case "monospace":
13830
+ return "monospace";
13831
+ case "courier":
13832
+ case "courier new":
13833
+ return '"Courier New", Courier, monospace';
13834
+ case "monaco":
13835
+ return "Monaco, Menlo, Consolas, monospace";
13836
+ case "system-ui":
13837
+ return "system-ui, -apple-system, sans-serif";
13838
+ case "sans-serif":
13839
+ case "serif":
13840
+ return normalizedKey;
13841
+ default:
13842
+ return `'${normalizedFamily.replace(/[\\']/g, "\\\\$&")}', ${SERIF_FONT_FAMILIES.has(normalizedKey) ? "Georgia, 'Times New Roman', serif" : "system-ui, -apple-system, sans-serif"}`;
13585
13843
  }
13586
- const invocationId = input.invocationId || createInvocationId();
13587
- const eventBase = {
13588
- invocationId,
13589
- callbackName: definition.callbackName,
13590
- scope: definition.scope,
13591
- sourceWidgetType: definition.sourceWidgetType,
13592
- hostWidgetType: input.hostWidgetType,
13593
- completionMode: definition.completionMode,
13594
- transport: input.transport
13595
- };
13596
- observe(input.observer, { type: "callback.received", ...eventBase });
13597
- const callback = input.callbacks[definition.callbackName];
13598
- if (!callback) {
13599
- observe(input.observer, { type: "callback.handler_missing", ...eventBase });
13600
- if (definition.missingHandler === "ignore") return;
13601
- return Promise.reject(new Error("Host callback is not registered"));
13844
+ }
13845
+ function ensureGoogleFontPreconnect() {
13846
+ if (!document.getElementById("rai-fonts-preconnect")) {
13847
+ const link = document.createElement("link");
13848
+ link.id = "rai-fonts-preconnect";
13849
+ link.rel = "preconnect";
13850
+ link.href = "https://fonts.googleapis.com";
13851
+ document.head.appendChild(link);
13602
13852
  }
13603
- observe(input.observer, { type: "callback.handler_started", ...eventBase });
13604
- let result;
13605
- try {
13606
- result = callback(input.payload);
13607
- } catch (error) {
13608
- observe(input.observer, {
13609
- type: "callback.handler_rejected",
13610
- ...eventBase,
13611
- error
13612
- });
13613
- if (definition.completionMode === "detached") return;
13614
- return Promise.reject(new Error("Host callback failed"));
13853
+ if (!document.getElementById("rai-fonts-static-preconnect")) {
13854
+ const link = document.createElement("link");
13855
+ link.id = "rai-fonts-static-preconnect";
13856
+ link.rel = "preconnect";
13857
+ link.href = "https://fonts.gstatic.com";
13858
+ link.crossOrigin = "anonymous";
13859
+ document.head.appendChild(link);
13615
13860
  }
13616
- if (definition.completionMode === "detached") {
13617
- if (result && typeof result.then === "function") {
13618
- void Promise.resolve(result).then(
13619
- () => observe(input.observer, { type: "callback.handler_resolved", ...eventBase }),
13620
- (error) => observe(input.observer, {
13621
- type: "callback.handler_rejected",
13622
- ...eventBase,
13623
- error
13624
- })
13625
- );
13626
- } else {
13627
- observe(input.observer, { type: "callback.handler_resolved", ...eventBase });
13628
- }
13861
+ }
13862
+ function ensureGoogleFontLoaded(family) {
13863
+ const normalizedFamily = normalizeFontFamily(family);
13864
+ const fontKey = normalizedFamily.toLowerCase();
13865
+ if (!normalizedFamily || normalizedFamily.includes(",") || LOCAL_OR_SYSTEM_FONT_FAMILIES.has(fontKey)) {
13629
13866
  return;
13630
13867
  }
13631
- return Promise.resolve(result).then(
13632
- (value) => {
13633
- observe(input.observer, { type: "callback.handler_resolved", ...eventBase });
13634
- return definition.completionMode === "result" ? value : void 0;
13635
- },
13636
- (error) => {
13637
- observe(input.observer, {
13638
- type: "callback.handler_rejected",
13639
- ...eventBase,
13640
- error
13641
- });
13642
- throw new Error("Host callback failed");
13643
- }
13644
- );
13868
+ if (loadedGoogleFontFamilies.has(fontKey)) return;
13869
+ ensureGoogleFontPreconnect();
13870
+ const linkId = `rai-font-${normalizedFamily.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
13871
+ const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(
13872
+ normalizedFamily
13873
+ )}${GOOGLE_FONT_VARIANTS}&display=swap`;
13874
+ let link = document.getElementById(linkId);
13875
+ if (link && link.href !== fontUrl) {
13876
+ link.remove();
13877
+ link = null;
13878
+ }
13879
+ if (!link) {
13880
+ link = document.createElement("link");
13881
+ link.id = linkId;
13882
+ link.rel = "stylesheet";
13883
+ link.href = fontUrl;
13884
+ document.head.appendChild(link);
13885
+ }
13886
+ loadedGoogleFontFamilies.add(fontKey);
13887
+ link.onerror = () => {
13888
+ loadedGoogleFontFamilies.delete(fontKey);
13889
+ link == null ? void 0 : link.remove();
13890
+ };
13645
13891
  }
13646
13892
  const SUPPORTED_WIDGET_LANGUAGES = [
13647
13893
  "en",
@@ -13779,15 +14025,36 @@ function setupMessageListener(config, state, _shadow, iframe, onRefreshScheduled
13779
14025
  emit == null ? void 0 : emit("rai-height-change", { height: h });
13780
14026
  }, config.heightDebounce ?? 100);
13781
14027
  const handler = async (event) => {
13782
- var _a;
14028
+ var _a, _b;
13783
14029
  if (event.origin !== config.widgetDomain) return;
13784
14030
  if (!event.data || typeof event.data.type !== "string") return;
13785
14031
  const { type, containerId, widgetId, payload } = event.data;
13786
14032
  switch (type) {
14033
+ case "RETURNINGAI_WIDGET_REQUEST_CALLBACK_CAPABILITIES": {
14034
+ if (event.source !== iframe.contentWindow) break;
14035
+ if (widgetId !== void 0 && widgetId !== config.widgetId) break;
14036
+ if (containerId !== void 0 && containerId !== config.container) break;
14037
+ const requestId = event.data.requestId;
14038
+ if (typeof requestId !== "string" || !requestId || requestId.length > 256) break;
14039
+ (_a = iframe.contentWindow) == null ? void 0 : _a.postMessage({
14040
+ type: "RETURNINGAI_WIDGET_CALLBACK_CAPABILITIES",
14041
+ requestId,
14042
+ supportedCallbacks: getSupportedHostCallbacks((getCallbacks == null ? void 0 : getCallbacks()) ?? {}, config.widgetType)
14043
+ }, config.widgetDomain);
14044
+ break;
14045
+ }
13787
14046
  case "RETURNINGAI_WIDGET_REQUEST_TOKEN": {
13788
14047
  try {
13789
- const token = await getValidToken(config, state, onRefreshScheduled);
13790
- (_a = iframe.contentWindow) == null ? void 0 : _a.postMessage(
14048
+ const token = await getValidToken(
14049
+ config,
14050
+ state,
14051
+ {
14052
+ getEmbedTokenProvider: () => readEmbedTokenProvider(getCallbacks == null ? void 0 : getCallbacks()),
14053
+ isCurrent: () => iframe.isConnected
14054
+ },
14055
+ onRefreshScheduled
14056
+ );
14057
+ (_b = iframe.contentWindow) == null ? void 0 : _b.postMessage(
13791
14058
  {
13792
14059
  type: "RETURNINGAI_WIDGET_TOKEN",
13793
14060
  value: {
@@ -14238,13 +14505,6 @@ function readConfig(el2, existingId) {
14238
14505
  firstPaintFont: get("font") || void 0
14239
14506
  };
14240
14507
  }
14241
- function identitySignatureForConfig(config) {
14242
- const identifiers = Object.keys(config.userIdentifiers || {}).sort().map((key) => [key, config.userIdentifiers[key]]);
14243
- return JSON.stringify({
14244
- embedToken: config.embedToken || "",
14245
- userIdentifiers: identifiers
14246
- });
14247
- }
14248
14508
  function createInitialState() {
14249
14509
  return {
14250
14510
  accessToken: null,
@@ -14268,6 +14528,17 @@ function createInitialState() {
14268
14528
  widgetData: null
14269
14529
  };
14270
14530
  }
14531
+ function forkWidgetState(previous) {
14532
+ return {
14533
+ ...createInitialState(),
14534
+ errorSettings: previous.errorSettings ?? null,
14535
+ loadingAnimation: previous.loadingAnimation ?? null,
14536
+ loadingSettings: previous.loadingSettings ?? null,
14537
+ themePalette: previous.themePalette ?? null,
14538
+ font: previous.font ?? null,
14539
+ defaultColorSchema: previous.defaultColorSchema ?? null
14540
+ };
14541
+ }
14271
14542
  class BaseWidget extends HTMLElement {
14272
14543
  constructor() {
14273
14544
  super();
@@ -14285,6 +14556,14 @@ class BaseWidget extends HTMLElement {
14285
14556
  __publicField(this, "themeObserver");
14286
14557
  __publicField(this, "identityAttributeObserver");
14287
14558
  __publicField(this, "identitySignature", "");
14559
+ /** The `embed-token` attribute as last read; `config.embedToken` may hold a renewed pass instead. */
14560
+ __publicField(this, "lastEmbedTokenAttribute");
14561
+ /** The init generation whose widget launched; part of the silent-swap health check. */
14562
+ __publicField(this, "launchedInitId", 0);
14563
+ /** Set when the expired-pass error was shown with no `embedTokenRequest` registered. */
14564
+ __publicField(this, "lateEmbedTokenRegistrationReload", false);
14565
+ /** An error screen is showing for the current init (any cause); clears on the next launch. */
14566
+ __publicField(this, "errorScreenShown", false);
14288
14567
  __publicField(this, "hasAttemptedInit", false);
14289
14568
  __publicField(this, "initGeneration", 0);
14290
14569
  __publicField(this, "lottieInstances", []);
@@ -14304,6 +14583,8 @@ class BaseWidget extends HTMLElement {
14304
14583
  return [
14305
14584
  "embed-token",
14306
14585
  "data-embed-token",
14586
+ "theme",
14587
+ "data-theme",
14307
14588
  "language",
14308
14589
  "data-language",
14309
14590
  "locale",
@@ -14312,6 +14593,10 @@ class BaseWidget extends HTMLElement {
14312
14593
  }
14313
14594
  registerCallback(callbackName, callback) {
14314
14595
  this.callbackRegistry.registerCallback(callbackName, callback);
14596
+ if (callbackName === "embedTokenRequest" && this.lateEmbedTokenRegistrationReload && !this.callbackRegistry.isLocked && this.readEmbedTokenProvider() && isEmbedTokenRejected(this.state.lastAuthError) && this.isConnected) {
14597
+ this.lateEmbedTokenRegistrationReload = false;
14598
+ void this.reload();
14599
+ }
14315
14600
  }
14316
14601
  lockCallbacks() {
14317
14602
  this.callbackRegistry.lock();
@@ -14319,6 +14604,7 @@ class BaseWidget extends HTMLElement {
14319
14604
  connectedCallback() {
14320
14605
  this.config = readConfig(this, void 0);
14321
14606
  this.identitySignature = identitySignatureForConfig(this.config);
14607
+ this.lastEmbedTokenAttribute = this.config.embedToken;
14322
14608
  this.setupIdentityAttributeObserver();
14323
14609
  ensureWidgetPreconnects(
14324
14610
  resolveWidgetPreconnectOrigins(this.config, document.baseURI)
@@ -14326,16 +14612,8 @@ class BaseWidget extends HTMLElement {
14326
14612
  const style = document.createElement("style");
14327
14613
  style.textContent = widgetCSS;
14328
14614
  this.shadow.appendChild(style);
14329
- const theme = this.config.theme;
14330
- const accentColor = theme === "dark" ? "#ffffff" : "#000000";
14331
- const text4Color = theme === "dark" ? "#9ca3af" : "#6b7280";
14332
- const bgColor = theme === "dark" ? "#1a1a1a" : "#ffffff";
14333
14615
  this.style.width = this.config.width;
14334
14616
  this.style.height = this.config.height;
14335
- this.style.setProperty("--rai-accent", accentColor);
14336
- this.style.setProperty("--rai-text4", text4Color);
14337
- this.style.setProperty("--rai-loader-bg", bgColor);
14338
- this.style.setProperty("--rai-error-bg", bgColor);
14339
14617
  this.applyFirstPaintColors();
14340
14618
  this.renderShell();
14341
14619
  if (this.hasAttribute("eager")) {
@@ -14378,6 +14656,8 @@ class BaseWidget extends HTMLElement {
14378
14656
  void this.reload();
14379
14657
  } else {
14380
14658
  this.config = readConfig(this, this.config.communityId);
14659
+ this.applyFirstPaintColors();
14660
+ this.renderLoaderAnimation();
14381
14661
  }
14382
14662
  }
14383
14663
  }
@@ -14398,7 +14678,22 @@ class BaseWidget extends HTMLElement {
14398
14678
  if (!this.isConnected || !this.config) return;
14399
14679
  const nextConfig = readConfig(this, this.config.communityId);
14400
14680
  const nextIdentitySignature = identitySignatureForConfig(nextConfig);
14401
- if (nextIdentitySignature === this.identitySignature) {
14681
+ const tokenChanged = nextConfig.embedToken !== this.lastEmbedTokenAttribute;
14682
+ const sameSignature = nextIdentitySignature === this.identitySignature;
14683
+ const decision = decideEmbedTokenChange({
14684
+ tokenChanged,
14685
+ sameSignature,
14686
+ hasAttemptedInit: this.hasAttemptedInit,
14687
+ healthy: this.isHealthyForEmbedTokenSwap(),
14688
+ hasProvider: !!this.readEmbedTokenProvider()
14689
+ });
14690
+ this.lastEmbedTokenAttribute = nextConfig.embedToken;
14691
+ if (decision === "swap") {
14692
+ this.config.embedToken = nextConfig.embedToken;
14693
+ return;
14694
+ }
14695
+ if (decision === "update" && sameSignature) {
14696
+ if (!tokenChanged) nextConfig.embedToken = this.config.embedToken;
14402
14697
  this.config = nextConfig;
14403
14698
  return;
14404
14699
  }
@@ -14407,10 +14702,28 @@ class BaseWidget extends HTMLElement {
14407
14702
  this.identitySignature = nextIdentitySignature;
14408
14703
  clearStorage(previousConfig);
14409
14704
  clearStorage(this.config);
14410
- if (this.hasAttemptedInit) {
14705
+ if (decision === "reload") {
14411
14706
  void this.reload();
14412
14707
  }
14413
14708
  }
14709
+ readEmbedTokenProvider() {
14710
+ return readEmbedTokenProvider(this.callbackRegistry.read());
14711
+ }
14712
+ /**
14713
+ * Renewal options for auth calls started by init `initId`; the provider is
14714
+ * read at call time. A disconnect or a newer init stops the retry. Writes
14715
+ * from this call land only in the state object init `initId` owns.
14716
+ */
14717
+ embedTokenRenewal(initId) {
14718
+ return {
14719
+ getEmbedTokenProvider: () => this.readEmbedTokenProvider(),
14720
+ isCurrent: () => this.isCurrentInit(initId)
14721
+ };
14722
+ }
14723
+ /** Authenticated, no error state or error screen, and launched for the current init. */
14724
+ isHealthyForEmbedTokenSwap() {
14725
+ return this.state.isAuthenticated && !this.state.lastAuthError && !this.errorScreenShown && this.launchedInitId !== 0 && this.launchedInitId === this.initGeneration;
14726
+ }
14414
14727
  // ── DOM event helper (#1) ─────────────────────────────────────────────
14415
14728
  emit(type, detail) {
14416
14729
  this.dispatchEvent(
@@ -14453,6 +14766,17 @@ class BaseWidget extends HTMLElement {
14453
14766
  }
14454
14767
  }
14455
14768
  const theme = this.config.theme === "dark" ? "dark" : "light";
14769
+ this.style.setProperty(
14770
+ "--rai-accent",
14771
+ theme === "dark" ? "#ffffff" : "#000000"
14772
+ );
14773
+ this.style.setProperty(
14774
+ "--rai-text4",
14775
+ theme === "dark" ? "#9ca3af" : "#6b7280"
14776
+ );
14777
+ const bgColor = theme === "dark" ? "#1a1a1a" : "#ffffff";
14778
+ this.style.setProperty("--rai-loader-bg", bgColor);
14779
+ this.style.setProperty("--rai-error-bg", bgColor);
14456
14780
  const cachedPalette = (_a = cached == null ? void 0 : cached.themePalette) == null ? void 0 : _a[theme];
14457
14781
  if (cachedPalette) this.applyPalette(cachedPalette);
14458
14782
  const cachedFont = typeof (cached == null ? void 0 : cached.font) === "string" ? cached.font : void 0;
@@ -14536,19 +14860,7 @@ class BaseWidget extends HTMLElement {
14536
14860
  }
14537
14861
  }
14538
14862
  renderShell() {
14539
- this.loaderEl = document.createElement("div");
14540
- this.loaderEl.className = "rai-loader";
14541
- if (this.config.widgetType === "custom" && !loadErrorSettingsFromStorage(this.config)) {
14542
- this.loaderEl.classList.add("rai-loader-pending-settings");
14543
- }
14544
- const textEl = document.createElement("h1");
14545
- textEl.className = "rai-loader-text";
14546
- textEl.textContent = this.config.loadingText;
14547
- this.loaderEl.appendChild(textEl);
14548
- this.shadow.appendChild(this.loaderEl);
14549
- this.freezeIframeLoaderHeight();
14550
- this.applyLoadingSettings();
14551
- this.fetchAndRenderLoader();
14863
+ this.showLoader();
14552
14864
  this.errorEl = document.createElement("div");
14553
14865
  this.errorEl.className = "rai-error";
14554
14866
  this.msgEl = document.createElement("span");
@@ -14563,6 +14875,26 @@ class BaseWidget extends HTMLElement {
14563
14875
  this.errorEl.appendChild(btn);
14564
14876
  this.shadow.appendChild(this.errorEl);
14565
14877
  }
14878
+ showLoader() {
14879
+ var _a;
14880
+ this.clearLottieInstances();
14881
+ this.shadow.querySelectorAll(".rai-loader").forEach((el2) => el2.remove());
14882
+ (_a = this.errorEl) == null ? void 0 : _a.classList.remove("visible");
14883
+ this.shadow.querySelectorAll(".rai-custom-error").forEach((el2) => el2.remove());
14884
+ this.loaderEl = document.createElement("div");
14885
+ this.loaderEl.className = "rai-loader";
14886
+ if (this.config.widgetType === "custom" && !loadErrorSettingsFromStorage(this.config)) {
14887
+ this.loaderEl.classList.add("rai-loader-pending-settings");
14888
+ }
14889
+ const textEl = document.createElement("h1");
14890
+ textEl.className = "rai-loader-text";
14891
+ textEl.textContent = this.config.loadingText;
14892
+ this.loaderEl.appendChild(textEl);
14893
+ this.shadow.appendChild(this.loaderEl);
14894
+ this.freezeIframeLoaderHeight();
14895
+ this.applyLoadingSettings();
14896
+ void this.fetchAndRenderLoader();
14897
+ }
14566
14898
  /**
14567
14899
  * Loads the community loading animation + theme palette from cache or API
14568
14900
  * and renders the configured animation above the loading text.
@@ -14573,6 +14905,7 @@ class BaseWidget extends HTMLElement {
14573
14905
  */
14574
14906
  async fetchAndRenderLoader() {
14575
14907
  var _a;
14908
+ const loaderEl = this.loaderEl;
14576
14909
  const cached = loadErrorSettingsFromStorage(this.config);
14577
14910
  if (cached) {
14578
14911
  applyErrorSettingsToState(this.state, cached);
@@ -14580,12 +14913,16 @@ class BaseWidget extends HTMLElement {
14580
14913
  this.renderLoaderAnimation();
14581
14914
  return;
14582
14915
  }
14916
+ const target = this.state;
14583
14917
  try {
14584
- await fetchErrorSettings(this.config, this.state);
14918
+ await fetchErrorSettings(this.config, target);
14585
14919
  } catch {
14586
14920
  }
14587
- const loaderEl = this.loaderEl;
14588
- if (!loaderEl) return;
14921
+ if (target !== this.state) {
14922
+ const fetched = loadErrorSettingsFromStorage(this.config);
14923
+ if (fetched) applyErrorSettingsToState(this.state, fetched);
14924
+ }
14925
+ if (!loaderEl || loaderEl !== this.loaderEl || !this.isConnected) return;
14589
14926
  if (!this.config.themeExplicit && (this.state.defaultColorSchema === "light" || this.state.defaultColorSchema === "dark") && this.state.defaultColorSchema !== this.config.theme) {
14590
14927
  this.config.theme = this.state.defaultColorSchema;
14591
14928
  }
@@ -14690,7 +15027,7 @@ class BaseWidget extends HTMLElement {
14690
15027
  setTimeout(() => {
14691
15028
  loader.remove();
14692
15029
  lottieInstances.forEach((instance) => instance.destroy());
14693
- this.restoreLoaderHeight();
15030
+ if (!this.loaderEl) this.restoreLoaderHeight();
14694
15031
  }, 300);
14695
15032
  this.loaderEl = null;
14696
15033
  }
@@ -14793,6 +15130,8 @@ class BaseWidget extends HTMLElement {
14793
15130
  }
14794
15131
  showError(failureKind = "authentication") {
14795
15132
  this.hideLoader();
15133
+ this.errorScreenShown = true;
15134
+ this.lateEmbedTokenRegistrationReload = failureKind === "authentication" && isEmbedTokenRejected(this.state.lastAuthError) && !this.readEmbedTokenProvider();
14796
15135
  const settings = this.state.errorSettings;
14797
15136
  const presentation = selectWidgetErrorPresentation({
14798
15137
  failureKind,
@@ -14963,29 +15302,61 @@ class BaseWidget extends HTMLElement {
14963
15302
  }
14964
15303
  }
14965
15304
  // ── Initialization ─────────────────────────────────────────────────────
15305
+ /**
15306
+ * Starts a new init: it owns a fresh state object and its own auth-session
15307
+ * subscription. The previous init's in-flight work keeps the retired state,
15308
+ * so it can finish but never writes this init's state or storage. It still
15309
+ * publishes to the session it started on; after a same-member reload that is
15310
+ * the same shared session this init joined, which is intended (siblings and
15311
+ * this init get the result).
15312
+ */
15313
+ beginInit() {
15314
+ const initId = ++this.initGeneration;
15315
+ const retired = this.state;
15316
+ if (retired.refreshTimer) clearTimeout(retired.refreshTimer);
15317
+ if (retired.syncTimer) clearInterval(retired.syncTimer);
15318
+ retired.refreshTimer = null;
15319
+ retired.syncTimer = null;
15320
+ retireWidgetState(retired);
15321
+ const state = forkWidgetState(retired);
15322
+ this.state = state;
15323
+ const previousSession = this.authSession;
15324
+ this.authSession = void 0;
15325
+ if (this.config.communityId && !this.config.authUrl) {
15326
+ let subscribing = true;
15327
+ this.authSession = subscribeAuthSession(this.config, state, () => {
15328
+ if (this.state !== state) return;
15329
+ if (state.isAuthenticated && !state.lastAuthError) {
15330
+ this.authRenewalRecovery.succeeded();
15331
+ }
15332
+ if (this.config.autoRefresh && !state.lastAuthError) {
15333
+ if (!subscribing) {
15334
+ this.scheduleRefresh();
15335
+ } else if (state.accessTokenExpiry && calculateRefreshDelay(state.accessTokenExpiry) > 0) {
15336
+ this.scheduleRefresh();
15337
+ }
15338
+ }
15339
+ if (state.iframe) {
15340
+ sendTokenToWidget(this.config, state, state.iframe);
15341
+ }
15342
+ syncBundleAccessToken(this.config, state);
15343
+ });
15344
+ subscribing = false;
15345
+ }
15346
+ previousSession == null ? void 0 : previousSession.release();
15347
+ return initId;
15348
+ }
14966
15349
  async init() {
14967
15350
  var _a, _b;
14968
15351
  (_a = this.cancelBundleForegroundRecovery) == null ? void 0 : _a.call(this);
14969
15352
  this.cancelBundleForegroundRecovery = void 0;
14970
15353
  this.authRenewalRecovery.cancel();
14971
- const initId = ++this.initGeneration;
15354
+ const initId = this.beginInit();
14972
15355
  this.hasAttemptedInit = true;
14973
15356
  if (!this.config.communityId) {
14974
15357
  this.showError();
14975
15358
  return;
14976
15359
  }
14977
- if (!this.config.authUrl && !this.authSession) {
14978
- this.authSession = subscribeAuthSession(this.config, this.state, () => {
14979
- if (this.state.isAuthenticated && !this.state.lastAuthError) {
14980
- this.authRenewalRecovery.succeeded();
14981
- }
14982
- if (this.config.autoRefresh) this.scheduleRefresh();
14983
- if (this.state.iframe) {
14984
- sendTokenToWidget(this.config, this.state, this.state.iframe);
14985
- }
14986
- syncBundleAccessToken(this.config, this.state);
14987
- });
14988
- }
14989
15360
  if (this.config.bundleUrl) {
14990
15361
  this.prepareBundleRuntimeEnvironment();
14991
15362
  warmBundleRuntime(
@@ -14993,6 +15364,7 @@ class BaseWidget extends HTMLElement {
14993
15364
  this.config.bundleUrl
14994
15365
  );
14995
15366
  }
15367
+ const renewal = this.embedTokenRenewal(initId);
14996
15368
  const finishStartup = (authentication) => resolveStartupAuthentication(
14997
15369
  authentication,
14998
15370
  (options) => fetchErrorSettings(this.config, this.state, options)
@@ -15000,6 +15372,9 @@ class BaseWidget extends HTMLElement {
15000
15372
  const launchWidget = () => {
15001
15373
  var _a2;
15002
15374
  if (!this.isCurrentInit(initId)) return;
15375
+ this.launchedInitId = initId;
15376
+ this.lateEmbedTokenRegistrationReload = false;
15377
+ this.errorScreenShown = false;
15003
15378
  (_a2 = this.errorEl) == null ? void 0 : _a2.classList.remove("visible");
15004
15379
  this.shadow.querySelectorAll(".rai-custom-error").forEach((el2) => el2.remove());
15005
15380
  let bannerGated = false;
@@ -15023,11 +15398,9 @@ class BaseWidget extends HTMLElement {
15023
15398
  };
15024
15399
  if (this.config.authUrl) {
15025
15400
  const authed2 = await finishStartup(
15026
- authenticateViaProxy(
15027
- this.config,
15028
- this.state,
15029
- () => this.scheduleRefresh()
15030
- )
15401
+ authenticateViaProxy(this.config, this.state, () => {
15402
+ if (initId === this.initGeneration) this.scheduleRefresh();
15403
+ })
15031
15404
  );
15032
15405
  if (!this.isCurrentInit(initId)) return;
15033
15406
  if (authed2) {
@@ -15044,10 +15417,11 @@ class BaseWidget extends HTMLElement {
15044
15417
  const authed2 = await finishStartup(
15045
15418
  this.authSession ? this.authSession.run(
15046
15419
  "authenticate",
15047
- () => authenticateServerless(this.config, this.state)
15420
+ () => authenticateServerless(this.config, this.state, renewal)
15048
15421
  ) : authenticateServerless(
15049
15422
  this.config,
15050
15423
  this.state,
15424
+ renewal,
15051
15425
  () => this.scheduleRefresh()
15052
15426
  )
15053
15427
  );
@@ -15074,10 +15448,11 @@ class BaseWidget extends HTMLElement {
15074
15448
  const refreshed = await finishStartup(
15075
15449
  this.authSession ? this.authSession.run(
15076
15450
  "refresh",
15077
- () => refreshAccessToken(this.config, this.state)
15451
+ () => refreshAccessToken(this.config, this.state, renewal)
15078
15452
  ) : refreshAccessToken(
15079
15453
  this.config,
15080
15454
  this.state,
15455
+ renewal,
15081
15456
  () => this.scheduleRefresh()
15082
15457
  )
15083
15458
  );
@@ -15094,10 +15469,11 @@ class BaseWidget extends HTMLElement {
15094
15469
  const authed = await finishStartup(
15095
15470
  this.authSession ? this.authSession.run(
15096
15471
  "authenticate",
15097
- () => authenticateServerless(this.config, this.state)
15472
+ () => authenticateServerless(this.config, this.state, renewal)
15098
15473
  ) : authenticateServerless(
15099
15474
  this.config,
15100
15475
  this.state,
15476
+ renewal,
15101
15477
  () => this.scheduleRefresh()
15102
15478
  )
15103
15479
  );
@@ -15139,6 +15515,8 @@ class BaseWidget extends HTMLElement {
15139
15515
  }
15140
15516
  createIframe() {
15141
15517
  this.teardownMountedWidget();
15518
+ const session = this.authSession;
15519
+ const initId = this.initGeneration;
15142
15520
  const iframe = document.createElement("iframe");
15143
15521
  iframe.src = this.buildWidgetUrl(this.config);
15144
15522
  iframe.allow = "clipboard-write";
@@ -15166,7 +15544,14 @@ class BaseWidget extends HTMLElement {
15166
15544
  this.state,
15167
15545
  this.shadow,
15168
15546
  iframe,
15169
- () => this.scheduleRefresh(),
15547
+ // A token the iframe asks for can renew the session outside the shared
15548
+ // auth session. Publish it: the next session refresh would otherwise
15549
+ // copy the old snapshot back and present a rotated-away refresh token,
15550
+ // which the server treats as reuse once its 60 s grace has passed.
15551
+ () => {
15552
+ if (session) session.publish();
15553
+ else if (initId === this.initGeneration) this.scheduleRefresh();
15554
+ },
15170
15555
  () => this.logoutAndClear(),
15171
15556
  () => this.hideLoader(),
15172
15557
  (type, detail) => this.emit(type, detail),
@@ -15271,7 +15656,7 @@ class BaseWidget extends HTMLElement {
15271
15656
  // host page. Bundle consumers use this explicit contract instead of
15272
15657
  // inferring the SDK host element from their rendered DOM.
15273
15658
  autoHeight,
15274
- requestHostCallback: (callbackName, payload) => dispatchHostCallback({
15659
+ requestHostCallback: Object.defineProperty((callbackName, payload) => dispatchHostCallback({
15275
15660
  callbacks: this.readRuntimeCallbacks(),
15276
15661
  callbackName,
15277
15662
  payload: enrichHostCallbackPayload(
@@ -15280,6 +15665,8 @@ class BaseWidget extends HTMLElement {
15280
15665
  ),
15281
15666
  hostWidgetType: this.config.widgetType,
15282
15667
  transport: "bundle"
15668
+ }), "supportedCallbacks", {
15669
+ get: () => getSupportedHostCallbacks(this.readRuntimeCallbacks(), this.config.widgetType)
15283
15670
  })
15284
15671
  });
15285
15672
  const resolvedMountResult = await resolveBundleMountResult(mountResult);
@@ -15346,30 +15733,29 @@ class BaseWidget extends HTMLElement {
15346
15733
  }
15347
15734
  async performScheduledRefresh() {
15348
15735
  this.state.refreshTimer = null;
15736
+ const initId = this.initGeneration;
15737
+ const { state, config, authSession } = this;
15738
+ const current = () => initId === this.initGeneration;
15349
15739
  const pushToken = () => {
15350
- if (this.state.iframe)
15351
- sendTokenToWidget(this.config, this.state, this.state.iframe);
15352
- syncBundleAccessToken(this.config, this.state);
15740
+ if (!current()) return;
15741
+ if (state.iframe) sendTokenToWidget(config, state, state.iframe);
15742
+ syncBundleAccessToken(config, state);
15743
+ };
15744
+ const rearm = () => {
15745
+ if (current()) this.scheduleRefresh();
15353
15746
  };
15354
15747
  let ok;
15355
- if (this.config.authUrl) {
15356
- ok = await authenticateViaProxy(
15357
- this.config,
15358
- this.state,
15359
- () => this.scheduleRefresh()
15360
- );
15748
+ if (config.authUrl) {
15749
+ ok = await authenticateViaProxy(config, state, rearm);
15361
15750
  if (ok) pushToken();
15362
15751
  } else {
15363
- ok = this.authSession ? await this.authSession.run(
15752
+ const renewal = this.embedTokenRenewal(initId);
15753
+ ok = authSession ? await authSession.run(
15364
15754
  "refresh",
15365
- () => refreshAccessToken(this.config, this.state)
15366
- ) : await refreshAccessToken(
15367
- this.config,
15368
- this.state,
15369
- () => this.scheduleRefresh(),
15370
- pushToken
15371
- );
15755
+ () => refreshAccessToken(config, state, renewal)
15756
+ ) : await refreshAccessToken(config, state, renewal, rearm, pushToken);
15372
15757
  }
15758
+ if (initId !== this.initGeneration) return;
15373
15759
  if (ok) {
15374
15760
  this.authRenewalRecovery.succeeded();
15375
15761
  } else if (isPermanentAuthFailure(this.state.lastAuthError)) {
@@ -15412,12 +15798,15 @@ class BaseWidget extends HTMLElement {
15412
15798
  }
15413
15799
  // ── Logout helper ─────────────────────────────────────────────────────
15414
15800
  async logoutAndClear() {
15415
- var _a;
15801
+ const initId = this.initGeneration;
15802
+ const session = this.authSession;
15416
15803
  this.authRenewalRecovery.cancel();
15417
15804
  await logout(this.config, this.state);
15418
- (_a = this.authSession) == null ? void 0 : _a.clear();
15419
- this.teardownMountedWidget();
15420
- this.removeBanner();
15805
+ session == null ? void 0 : session.clear();
15806
+ if (initId === this.initGeneration) {
15807
+ this.teardownMountedWidget();
15808
+ this.removeBanner();
15809
+ }
15421
15810
  this.emit("rai-logout");
15422
15811
  }
15423
15812
  // ── Public API ────────────────────────────────────────────────────────
@@ -15429,13 +15818,15 @@ class BaseWidget extends HTMLElement {
15429
15818
  this.authSession = void 0;
15430
15819
  this.config = readConfig(this, (_b = this.config) == null ? void 0 : _b.communityId);
15431
15820
  this.identitySignature = identitySignatureForConfig(this.config);
15821
+ this.lastEmbedTokenAttribute = this.config.embedToken;
15432
15822
  this.style.width = this.config.width;
15433
15823
  this.style.height = this.config.height;
15434
15824
  clearStorage(previousConfig);
15435
15825
  clearStorage(this.config);
15436
15826
  this.teardownMountedWidget();
15437
15827
  this.removeBanner();
15438
- clearState(this.state);
15828
+ this.applyFirstPaintColors();
15829
+ this.showLoader();
15439
15830
  await this.init();
15440
15831
  }
15441
15832
  async logoutPublic() {
@@ -15571,7 +15962,7 @@ class CustomWidget extends BaseWidget {
15571
15962
  }
15572
15963
  }
15573
15964
  }
15574
- console.log(`[rai-widget] v${"1.8.12"}`);
15965
+ console.log(`[rai-widget] v${"1.8.14"}`);
15575
15966
  class StoreWidgetCompat extends StoreWidget {
15576
15967
  }
15577
15968
  const WIDGET_REGISTRY = [
@@ -15635,7 +16026,7 @@ function exposePublicApi() {
15635
16026
  })();
15636
16027
  const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
15637
16028
  window.ReturningAIWidget = {
15638
- version: "1.8.12",
16029
+ version: "1.8.14",
15639
16030
  reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
15640
16031
  logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
15641
16032
  isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,