@returningai/widget-sdk 1.8.13 → 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.
- package/CHANGELOG.md +38 -6
- package/README.md +106 -10
- package/dist/rai-banner.iife.js +1 -1
- package/dist/rai-banner.js +1 -1
- package/dist/rai-widget.iife.js +2 -2
- package/dist/rai-widget.js +768 -394
- package/dist/types/BaseWidget.d.ts +26 -0
- package/dist/types/core/auth.d.ts +18 -3
- package/dist/types/core/embed-token-renewal.d.ts +116 -0
- package/dist/types/core/host-callback.d.ts +4 -0
- package/dist/types/core/storage.d.ts +3 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types.d.ts +30 -0
- package/package.json +7 -3
package/dist/rai-widget.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
12692
|
-
if (
|
|
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
|
-
|
|
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;
|
|
@@ -13327,321 +13767,127 @@ function syncBundleAccessToken(config, state, runtime = window) {
|
|
|
13327
13767
|
return true;
|
|
13328
13768
|
}
|
|
13329
13769
|
function assertCallbackName(callbackName) {
|
|
13330
|
-
if (!callbackName || typeof callbackName !== "string") {
|
|
13331
|
-
throw new TypeError("ReturningAI callback name must be a non-empty string");
|
|
13332
|
-
}
|
|
13333
|
-
}
|
|
13334
|
-
function assertCallback(callback) {
|
|
13335
|
-
if (typeof callback !== "function") {
|
|
13336
|
-
throw new TypeError("ReturningAI callback must be a function");
|
|
13337
|
-
}
|
|
13338
|
-
}
|
|
13339
|
-
class WidgetCallbackRegistry {
|
|
13340
|
-
constructor() {
|
|
13341
|
-
__publicField(this, "registeredCallbacks", {});
|
|
13342
|
-
__publicField(this, "locked", false);
|
|
13343
|
-
}
|
|
13344
|
-
get isLocked() {
|
|
13345
|
-
return this.locked;
|
|
13346
|
-
}
|
|
13347
|
-
registerCallback(callbackName, callback) {
|
|
13348
|
-
if (this.locked) {
|
|
13349
|
-
return;
|
|
13350
|
-
}
|
|
13351
|
-
assertCallbackName(callbackName);
|
|
13352
|
-
assertCallback(callback);
|
|
13353
|
-
this.registeredCallbacks = {
|
|
13354
|
-
...this.registeredCallbacks,
|
|
13355
|
-
[callbackName]: callback
|
|
13356
|
-
};
|
|
13357
|
-
}
|
|
13358
|
-
lock() {
|
|
13359
|
-
if (this.locked) {
|
|
13360
|
-
return;
|
|
13361
|
-
}
|
|
13362
|
-
this.locked = true;
|
|
13363
|
-
}
|
|
13364
|
-
read() {
|
|
13365
|
-
return { ...this.registeredCallbacks };
|
|
13366
|
-
}
|
|
13367
|
-
}
|
|
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
|
|
13770
|
+
if (!callbackName || typeof callbackName !== "string") {
|
|
13771
|
+
throw new TypeError("ReturningAI callback name must be a non-empty string");
|
|
13561
13772
|
}
|
|
13562
|
-
};
|
|
13563
|
-
function createInvocationId() {
|
|
13564
|
-
return `host_callback_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
13565
13773
|
}
|
|
13566
|
-
function
|
|
13567
|
-
if (
|
|
13568
|
-
|
|
13569
|
-
observer(event);
|
|
13570
|
-
} catch {
|
|
13774
|
+
function assertCallback(callback) {
|
|
13775
|
+
if (typeof callback !== "function") {
|
|
13776
|
+
throw new TypeError("ReturningAI callback must be a function");
|
|
13571
13777
|
}
|
|
13572
13778
|
}
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13779
|
+
class WidgetCallbackRegistry {
|
|
13780
|
+
constructor() {
|
|
13781
|
+
__publicField(this, "registeredCallbacks", {});
|
|
13782
|
+
__publicField(this, "locked", false);
|
|
13783
|
+
}
|
|
13784
|
+
get isLocked() {
|
|
13785
|
+
return this.locked;
|
|
13786
|
+
}
|
|
13787
|
+
registerCallback(callbackName, callback) {
|
|
13788
|
+
if (this.locked) {
|
|
13789
|
+
return;
|
|
13790
|
+
}
|
|
13791
|
+
assertCallbackName(callbackName);
|
|
13792
|
+
assertCallback(callback);
|
|
13793
|
+
this.registeredCallbacks = {
|
|
13794
|
+
...this.registeredCallbacks,
|
|
13795
|
+
[callbackName]: callback
|
|
13796
|
+
};
|
|
13797
|
+
}
|
|
13798
|
+
lock() {
|
|
13799
|
+
if (this.locked) {
|
|
13800
|
+
return;
|
|
13801
|
+
}
|
|
13802
|
+
this.locked = true;
|
|
13803
|
+
}
|
|
13804
|
+
read() {
|
|
13805
|
+
return { ...this.registeredCallbacks };
|
|
13806
|
+
}
|
|
13576
13807
|
}
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
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
|
|
13582
|
-
const
|
|
13583
|
-
if (
|
|
13584
|
-
|
|
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
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
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
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
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
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
|
|
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
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
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(
|
|
13790
|
-
|
|
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", []);
|
|
@@ -14314,6 +14593,10 @@ class BaseWidget extends HTMLElement {
|
|
|
14314
14593
|
}
|
|
14315
14594
|
registerCallback(callbackName, callback) {
|
|
14316
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
|
+
}
|
|
14317
14600
|
}
|
|
14318
14601
|
lockCallbacks() {
|
|
14319
14602
|
this.callbackRegistry.lock();
|
|
@@ -14321,6 +14604,7 @@ class BaseWidget extends HTMLElement {
|
|
|
14321
14604
|
connectedCallback() {
|
|
14322
14605
|
this.config = readConfig(this, void 0);
|
|
14323
14606
|
this.identitySignature = identitySignatureForConfig(this.config);
|
|
14607
|
+
this.lastEmbedTokenAttribute = this.config.embedToken;
|
|
14324
14608
|
this.setupIdentityAttributeObserver();
|
|
14325
14609
|
ensureWidgetPreconnects(
|
|
14326
14610
|
resolveWidgetPreconnectOrigins(this.config, document.baseURI)
|
|
@@ -14394,7 +14678,22 @@ class BaseWidget extends HTMLElement {
|
|
|
14394
14678
|
if (!this.isConnected || !this.config) return;
|
|
14395
14679
|
const nextConfig = readConfig(this, this.config.communityId);
|
|
14396
14680
|
const nextIdentitySignature = identitySignatureForConfig(nextConfig);
|
|
14397
|
-
|
|
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;
|
|
14398
14697
|
this.config = nextConfig;
|
|
14399
14698
|
return;
|
|
14400
14699
|
}
|
|
@@ -14403,10 +14702,28 @@ class BaseWidget extends HTMLElement {
|
|
|
14403
14702
|
this.identitySignature = nextIdentitySignature;
|
|
14404
14703
|
clearStorage(previousConfig);
|
|
14405
14704
|
clearStorage(this.config);
|
|
14406
|
-
if (
|
|
14705
|
+
if (decision === "reload") {
|
|
14407
14706
|
void this.reload();
|
|
14408
14707
|
}
|
|
14409
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
|
+
}
|
|
14410
14727
|
// ── DOM event helper (#1) ─────────────────────────────────────────────
|
|
14411
14728
|
emit(type, detail) {
|
|
14412
14729
|
this.dispatchEvent(
|
|
@@ -14596,10 +14913,15 @@ class BaseWidget extends HTMLElement {
|
|
|
14596
14913
|
this.renderLoaderAnimation();
|
|
14597
14914
|
return;
|
|
14598
14915
|
}
|
|
14916
|
+
const target = this.state;
|
|
14599
14917
|
try {
|
|
14600
|
-
await fetchErrorSettings(this.config,
|
|
14918
|
+
await fetchErrorSettings(this.config, target);
|
|
14601
14919
|
} catch {
|
|
14602
14920
|
}
|
|
14921
|
+
if (target !== this.state) {
|
|
14922
|
+
const fetched = loadErrorSettingsFromStorage(this.config);
|
|
14923
|
+
if (fetched) applyErrorSettingsToState(this.state, fetched);
|
|
14924
|
+
}
|
|
14603
14925
|
if (!loaderEl || loaderEl !== this.loaderEl || !this.isConnected) return;
|
|
14604
14926
|
if (!this.config.themeExplicit && (this.state.defaultColorSchema === "light" || this.state.defaultColorSchema === "dark") && this.state.defaultColorSchema !== this.config.theme) {
|
|
14605
14927
|
this.config.theme = this.state.defaultColorSchema;
|
|
@@ -14808,6 +15130,8 @@ class BaseWidget extends HTMLElement {
|
|
|
14808
15130
|
}
|
|
14809
15131
|
showError(failureKind = "authentication") {
|
|
14810
15132
|
this.hideLoader();
|
|
15133
|
+
this.errorScreenShown = true;
|
|
15134
|
+
this.lateEmbedTokenRegistrationReload = failureKind === "authentication" && isEmbedTokenRejected(this.state.lastAuthError) && !this.readEmbedTokenProvider();
|
|
14811
15135
|
const settings = this.state.errorSettings;
|
|
14812
15136
|
const presentation = selectWidgetErrorPresentation({
|
|
14813
15137
|
failureKind,
|
|
@@ -14978,29 +15302,61 @@ class BaseWidget extends HTMLElement {
|
|
|
14978
15302
|
}
|
|
14979
15303
|
}
|
|
14980
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
|
+
}
|
|
14981
15349
|
async init() {
|
|
14982
15350
|
var _a, _b;
|
|
14983
15351
|
(_a = this.cancelBundleForegroundRecovery) == null ? void 0 : _a.call(this);
|
|
14984
15352
|
this.cancelBundleForegroundRecovery = void 0;
|
|
14985
15353
|
this.authRenewalRecovery.cancel();
|
|
14986
|
-
const initId =
|
|
15354
|
+
const initId = this.beginInit();
|
|
14987
15355
|
this.hasAttemptedInit = true;
|
|
14988
15356
|
if (!this.config.communityId) {
|
|
14989
15357
|
this.showError();
|
|
14990
15358
|
return;
|
|
14991
15359
|
}
|
|
14992
|
-
if (!this.config.authUrl && !this.authSession) {
|
|
14993
|
-
this.authSession = subscribeAuthSession(this.config, this.state, () => {
|
|
14994
|
-
if (this.state.isAuthenticated && !this.state.lastAuthError) {
|
|
14995
|
-
this.authRenewalRecovery.succeeded();
|
|
14996
|
-
}
|
|
14997
|
-
if (this.config.autoRefresh) this.scheduleRefresh();
|
|
14998
|
-
if (this.state.iframe) {
|
|
14999
|
-
sendTokenToWidget(this.config, this.state, this.state.iframe);
|
|
15000
|
-
}
|
|
15001
|
-
syncBundleAccessToken(this.config, this.state);
|
|
15002
|
-
});
|
|
15003
|
-
}
|
|
15004
15360
|
if (this.config.bundleUrl) {
|
|
15005
15361
|
this.prepareBundleRuntimeEnvironment();
|
|
15006
15362
|
warmBundleRuntime(
|
|
@@ -15008,6 +15364,7 @@ class BaseWidget extends HTMLElement {
|
|
|
15008
15364
|
this.config.bundleUrl
|
|
15009
15365
|
);
|
|
15010
15366
|
}
|
|
15367
|
+
const renewal = this.embedTokenRenewal(initId);
|
|
15011
15368
|
const finishStartup = (authentication) => resolveStartupAuthentication(
|
|
15012
15369
|
authentication,
|
|
15013
15370
|
(options) => fetchErrorSettings(this.config, this.state, options)
|
|
@@ -15015,6 +15372,9 @@ class BaseWidget extends HTMLElement {
|
|
|
15015
15372
|
const launchWidget = () => {
|
|
15016
15373
|
var _a2;
|
|
15017
15374
|
if (!this.isCurrentInit(initId)) return;
|
|
15375
|
+
this.launchedInitId = initId;
|
|
15376
|
+
this.lateEmbedTokenRegistrationReload = false;
|
|
15377
|
+
this.errorScreenShown = false;
|
|
15018
15378
|
(_a2 = this.errorEl) == null ? void 0 : _a2.classList.remove("visible");
|
|
15019
15379
|
this.shadow.querySelectorAll(".rai-custom-error").forEach((el2) => el2.remove());
|
|
15020
15380
|
let bannerGated = false;
|
|
@@ -15038,11 +15398,9 @@ class BaseWidget extends HTMLElement {
|
|
|
15038
15398
|
};
|
|
15039
15399
|
if (this.config.authUrl) {
|
|
15040
15400
|
const authed2 = await finishStartup(
|
|
15041
|
-
authenticateViaProxy(
|
|
15042
|
-
this.
|
|
15043
|
-
|
|
15044
|
-
() => this.scheduleRefresh()
|
|
15045
|
-
)
|
|
15401
|
+
authenticateViaProxy(this.config, this.state, () => {
|
|
15402
|
+
if (initId === this.initGeneration) this.scheduleRefresh();
|
|
15403
|
+
})
|
|
15046
15404
|
);
|
|
15047
15405
|
if (!this.isCurrentInit(initId)) return;
|
|
15048
15406
|
if (authed2) {
|
|
@@ -15059,10 +15417,11 @@ class BaseWidget extends HTMLElement {
|
|
|
15059
15417
|
const authed2 = await finishStartup(
|
|
15060
15418
|
this.authSession ? this.authSession.run(
|
|
15061
15419
|
"authenticate",
|
|
15062
|
-
() => authenticateServerless(this.config, this.state)
|
|
15420
|
+
() => authenticateServerless(this.config, this.state, renewal)
|
|
15063
15421
|
) : authenticateServerless(
|
|
15064
15422
|
this.config,
|
|
15065
15423
|
this.state,
|
|
15424
|
+
renewal,
|
|
15066
15425
|
() => this.scheduleRefresh()
|
|
15067
15426
|
)
|
|
15068
15427
|
);
|
|
@@ -15089,10 +15448,11 @@ class BaseWidget extends HTMLElement {
|
|
|
15089
15448
|
const refreshed = await finishStartup(
|
|
15090
15449
|
this.authSession ? this.authSession.run(
|
|
15091
15450
|
"refresh",
|
|
15092
|
-
() => refreshAccessToken(this.config, this.state)
|
|
15451
|
+
() => refreshAccessToken(this.config, this.state, renewal)
|
|
15093
15452
|
) : refreshAccessToken(
|
|
15094
15453
|
this.config,
|
|
15095
15454
|
this.state,
|
|
15455
|
+
renewal,
|
|
15096
15456
|
() => this.scheduleRefresh()
|
|
15097
15457
|
)
|
|
15098
15458
|
);
|
|
@@ -15109,10 +15469,11 @@ class BaseWidget extends HTMLElement {
|
|
|
15109
15469
|
const authed = await finishStartup(
|
|
15110
15470
|
this.authSession ? this.authSession.run(
|
|
15111
15471
|
"authenticate",
|
|
15112
|
-
() => authenticateServerless(this.config, this.state)
|
|
15472
|
+
() => authenticateServerless(this.config, this.state, renewal)
|
|
15113
15473
|
) : authenticateServerless(
|
|
15114
15474
|
this.config,
|
|
15115
15475
|
this.state,
|
|
15476
|
+
renewal,
|
|
15116
15477
|
() => this.scheduleRefresh()
|
|
15117
15478
|
)
|
|
15118
15479
|
);
|
|
@@ -15154,6 +15515,8 @@ class BaseWidget extends HTMLElement {
|
|
|
15154
15515
|
}
|
|
15155
15516
|
createIframe() {
|
|
15156
15517
|
this.teardownMountedWidget();
|
|
15518
|
+
const session = this.authSession;
|
|
15519
|
+
const initId = this.initGeneration;
|
|
15157
15520
|
const iframe = document.createElement("iframe");
|
|
15158
15521
|
iframe.src = this.buildWidgetUrl(this.config);
|
|
15159
15522
|
iframe.allow = "clipboard-write";
|
|
@@ -15181,7 +15544,14 @@ class BaseWidget extends HTMLElement {
|
|
|
15181
15544
|
this.state,
|
|
15182
15545
|
this.shadow,
|
|
15183
15546
|
iframe,
|
|
15184
|
-
|
|
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
|
+
},
|
|
15185
15555
|
() => this.logoutAndClear(),
|
|
15186
15556
|
() => this.hideLoader(),
|
|
15187
15557
|
(type, detail) => this.emit(type, detail),
|
|
@@ -15286,7 +15656,7 @@ class BaseWidget extends HTMLElement {
|
|
|
15286
15656
|
// host page. Bundle consumers use this explicit contract instead of
|
|
15287
15657
|
// inferring the SDK host element from their rendered DOM.
|
|
15288
15658
|
autoHeight,
|
|
15289
|
-
requestHostCallback: (callbackName, payload) => dispatchHostCallback({
|
|
15659
|
+
requestHostCallback: Object.defineProperty((callbackName, payload) => dispatchHostCallback({
|
|
15290
15660
|
callbacks: this.readRuntimeCallbacks(),
|
|
15291
15661
|
callbackName,
|
|
15292
15662
|
payload: enrichHostCallbackPayload(
|
|
@@ -15295,6 +15665,8 @@ class BaseWidget extends HTMLElement {
|
|
|
15295
15665
|
),
|
|
15296
15666
|
hostWidgetType: this.config.widgetType,
|
|
15297
15667
|
transport: "bundle"
|
|
15668
|
+
}), "supportedCallbacks", {
|
|
15669
|
+
get: () => getSupportedHostCallbacks(this.readRuntimeCallbacks(), this.config.widgetType)
|
|
15298
15670
|
})
|
|
15299
15671
|
});
|
|
15300
15672
|
const resolvedMountResult = await resolveBundleMountResult(mountResult);
|
|
@@ -15361,30 +15733,29 @@ class BaseWidget extends HTMLElement {
|
|
|
15361
15733
|
}
|
|
15362
15734
|
async performScheduledRefresh() {
|
|
15363
15735
|
this.state.refreshTimer = null;
|
|
15736
|
+
const initId = this.initGeneration;
|
|
15737
|
+
const { state, config, authSession } = this;
|
|
15738
|
+
const current = () => initId === this.initGeneration;
|
|
15364
15739
|
const pushToken = () => {
|
|
15365
|
-
if (
|
|
15366
|
-
|
|
15367
|
-
syncBundleAccessToken(
|
|
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();
|
|
15368
15746
|
};
|
|
15369
15747
|
let ok;
|
|
15370
|
-
if (
|
|
15371
|
-
ok = await authenticateViaProxy(
|
|
15372
|
-
this.config,
|
|
15373
|
-
this.state,
|
|
15374
|
-
() => this.scheduleRefresh()
|
|
15375
|
-
);
|
|
15748
|
+
if (config.authUrl) {
|
|
15749
|
+
ok = await authenticateViaProxy(config, state, rearm);
|
|
15376
15750
|
if (ok) pushToken();
|
|
15377
15751
|
} else {
|
|
15378
|
-
|
|
15752
|
+
const renewal = this.embedTokenRenewal(initId);
|
|
15753
|
+
ok = authSession ? await authSession.run(
|
|
15379
15754
|
"refresh",
|
|
15380
|
-
() => refreshAccessToken(
|
|
15381
|
-
) : await refreshAccessToken(
|
|
15382
|
-
this.config,
|
|
15383
|
-
this.state,
|
|
15384
|
-
() => this.scheduleRefresh(),
|
|
15385
|
-
pushToken
|
|
15386
|
-
);
|
|
15755
|
+
() => refreshAccessToken(config, state, renewal)
|
|
15756
|
+
) : await refreshAccessToken(config, state, renewal, rearm, pushToken);
|
|
15387
15757
|
}
|
|
15758
|
+
if (initId !== this.initGeneration) return;
|
|
15388
15759
|
if (ok) {
|
|
15389
15760
|
this.authRenewalRecovery.succeeded();
|
|
15390
15761
|
} else if (isPermanentAuthFailure(this.state.lastAuthError)) {
|
|
@@ -15427,12 +15798,15 @@ class BaseWidget extends HTMLElement {
|
|
|
15427
15798
|
}
|
|
15428
15799
|
// ── Logout helper ─────────────────────────────────────────────────────
|
|
15429
15800
|
async logoutAndClear() {
|
|
15430
|
-
|
|
15801
|
+
const initId = this.initGeneration;
|
|
15802
|
+
const session = this.authSession;
|
|
15431
15803
|
this.authRenewalRecovery.cancel();
|
|
15432
15804
|
await logout(this.config, this.state);
|
|
15433
|
-
|
|
15434
|
-
this.
|
|
15435
|
-
|
|
15805
|
+
session == null ? void 0 : session.clear();
|
|
15806
|
+
if (initId === this.initGeneration) {
|
|
15807
|
+
this.teardownMountedWidget();
|
|
15808
|
+
this.removeBanner();
|
|
15809
|
+
}
|
|
15436
15810
|
this.emit("rai-logout");
|
|
15437
15811
|
}
|
|
15438
15812
|
// ── Public API ────────────────────────────────────────────────────────
|
|
@@ -15444,13 +15818,13 @@ class BaseWidget extends HTMLElement {
|
|
|
15444
15818
|
this.authSession = void 0;
|
|
15445
15819
|
this.config = readConfig(this, (_b = this.config) == null ? void 0 : _b.communityId);
|
|
15446
15820
|
this.identitySignature = identitySignatureForConfig(this.config);
|
|
15821
|
+
this.lastEmbedTokenAttribute = this.config.embedToken;
|
|
15447
15822
|
this.style.width = this.config.width;
|
|
15448
15823
|
this.style.height = this.config.height;
|
|
15449
15824
|
clearStorage(previousConfig);
|
|
15450
15825
|
clearStorage(this.config);
|
|
15451
15826
|
this.teardownMountedWidget();
|
|
15452
15827
|
this.removeBanner();
|
|
15453
|
-
clearState(this.state);
|
|
15454
15828
|
this.applyFirstPaintColors();
|
|
15455
15829
|
this.showLoader();
|
|
15456
15830
|
await this.init();
|
|
@@ -15588,7 +15962,7 @@ class CustomWidget extends BaseWidget {
|
|
|
15588
15962
|
}
|
|
15589
15963
|
}
|
|
15590
15964
|
}
|
|
15591
|
-
console.log(`[rai-widget] v${"1.8.
|
|
15965
|
+
console.log(`[rai-widget] v${"1.8.14"}`);
|
|
15592
15966
|
class StoreWidgetCompat extends StoreWidget {
|
|
15593
15967
|
}
|
|
15594
15968
|
const WIDGET_REGISTRY = [
|
|
@@ -15652,7 +16026,7 @@ function exposePublicApi() {
|
|
|
15652
16026
|
})();
|
|
15653
16027
|
const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
|
|
15654
16028
|
window.ReturningAIWidget = {
|
|
15655
|
-
version: "1.8.
|
|
16029
|
+
version: "1.8.14",
|
|
15656
16030
|
reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
|
|
15657
16031
|
logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
|
|
15658
16032
|
isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
|