@returningai/widget-sdk 1.8.2 → 1.8.4
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 +35 -0
- package/dist/rai-banner.iife.js +1 -1
- package/dist/rai-banner.js +1 -1
- package/dist/rai-widget.iife.js +1 -1
- package/dist/rai-widget.js +248 -19
- package/dist/types/BaseWidget.d.ts +13 -0
- package/dist/types/core/auth-session-coordinator.d.ts +11 -0
- package/package.json +6 -3
package/dist/rai-widget.js
CHANGED
|
@@ -12418,8 +12418,12 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
12418
12418
|
setTokens(config, state, data, onRefreshScheduled);
|
|
12419
12419
|
state.isAuthenticated = true;
|
|
12420
12420
|
return true;
|
|
12421
|
-
} catch {
|
|
12421
|
+
} catch (err) {
|
|
12422
12422
|
if (is4xx || attempt === maxRetries) {
|
|
12423
|
+
state.lastAuthError ?? (state.lastAuthError = {
|
|
12424
|
+
status: 0,
|
|
12425
|
+
error: err instanceof Error ? err.message : "Authentication failed"
|
|
12426
|
+
});
|
|
12423
12427
|
state.isAuthenticated = false;
|
|
12424
12428
|
return false;
|
|
12425
12429
|
}
|
|
@@ -12438,6 +12442,7 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
|
|
|
12438
12442
|
);
|
|
12439
12443
|
}
|
|
12440
12444
|
let is4xx = false;
|
|
12445
|
+
let status = 0;
|
|
12441
12446
|
try {
|
|
12442
12447
|
const response = await fetch(config.authUrl, {
|
|
12443
12448
|
method: "POST",
|
|
@@ -12445,6 +12450,7 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
|
|
|
12445
12450
|
headers: { "Content-Type": "application/json" }
|
|
12446
12451
|
});
|
|
12447
12452
|
if (!response.ok) {
|
|
12453
|
+
status = response.status;
|
|
12448
12454
|
is4xx = response.status >= 400 && response.status < 500;
|
|
12449
12455
|
throw new Error(`HTTP ${response.status}`);
|
|
12450
12456
|
}
|
|
@@ -12459,12 +12465,17 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
|
|
|
12459
12465
|
state.accessTokenExpiry = Date.now() + ttl * 1e3;
|
|
12460
12466
|
state.refreshTokenExpiry = null;
|
|
12461
12467
|
state.isAuthenticated = true;
|
|
12468
|
+
state.lastAuthError = null;
|
|
12462
12469
|
if (config.autoRefresh && onRefreshScheduled) {
|
|
12463
12470
|
onRefreshScheduled();
|
|
12464
12471
|
}
|
|
12465
12472
|
return true;
|
|
12466
|
-
} catch {
|
|
12473
|
+
} catch (err) {
|
|
12467
12474
|
if (is4xx || attempt === maxRetries) {
|
|
12475
|
+
state.lastAuthError = {
|
|
12476
|
+
status,
|
|
12477
|
+
error: err instanceof Error ? err.message : "Authentication failed"
|
|
12478
|
+
};
|
|
12468
12479
|
state.isAuthenticated = false;
|
|
12469
12480
|
return false;
|
|
12470
12481
|
}
|
|
@@ -12480,10 +12491,16 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
|
|
|
12480
12491
|
if (!state.refreshToken) {
|
|
12481
12492
|
return false;
|
|
12482
12493
|
}
|
|
12494
|
+
const presentedRefreshToken = state.refreshToken;
|
|
12483
12495
|
state.isRefreshing = true;
|
|
12484
12496
|
state.refreshPromise = (async () => {
|
|
12485
12497
|
const reauthenticateAfterRefreshFailure = async () => {
|
|
12486
|
-
|
|
12498
|
+
if (state.refreshToken && state.refreshToken !== presentedRefreshToken && state.accessToken && state.accessTokenExpiry && Date.now() < state.accessTokenExpiry) {
|
|
12499
|
+
state.isAuthenticated = true;
|
|
12500
|
+
if (sendToken) sendToken();
|
|
12501
|
+
return true;
|
|
12502
|
+
}
|
|
12503
|
+
clearAuthenticationData(state);
|
|
12487
12504
|
clearStorage(config);
|
|
12488
12505
|
const authed = await authenticateServerless(config, state, onRefreshScheduled);
|
|
12489
12506
|
if (authed && sendToken) sendToken();
|
|
@@ -12494,7 +12511,7 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
|
|
|
12494
12511
|
method: "POST",
|
|
12495
12512
|
headers: {
|
|
12496
12513
|
"Content-Type": "application/json",
|
|
12497
|
-
Authorization: `Bearer ${
|
|
12514
|
+
Authorization: `Bearer ${presentedRefreshToken}`
|
|
12498
12515
|
},
|
|
12499
12516
|
body: JSON.stringify({
|
|
12500
12517
|
widgetType: config.widgetType,
|
|
@@ -12513,6 +12530,7 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
|
|
|
12513
12530
|
throw new Error("Invalid refresh response");
|
|
12514
12531
|
}
|
|
12515
12532
|
setTokens(config, state, data, onRefreshScheduled);
|
|
12533
|
+
state.isAuthenticated = true;
|
|
12516
12534
|
if (sendToken) sendToken();
|
|
12517
12535
|
return true;
|
|
12518
12536
|
} catch {
|
|
@@ -12615,15 +12633,13 @@ async function fetchErrorSettings(config, state, opts = {}) {
|
|
|
12615
12633
|
applyErrorSettingsToState(state, data);
|
|
12616
12634
|
return state.errorSettings;
|
|
12617
12635
|
}
|
|
12618
|
-
function
|
|
12636
|
+
function clearAuthenticationData(state) {
|
|
12619
12637
|
state.accessToken = null;
|
|
12620
12638
|
state.refreshToken = null;
|
|
12621
12639
|
state.tokenFamily = null;
|
|
12622
12640
|
state.accessTokenExpiry = null;
|
|
12623
12641
|
state.refreshTokenExpiry = null;
|
|
12624
12642
|
state.isAuthenticated = false;
|
|
12625
|
-
state.isRefreshing = false;
|
|
12626
|
-
state.refreshPromise = null;
|
|
12627
12643
|
state.lastAuthError = null;
|
|
12628
12644
|
state.widgetData = null;
|
|
12629
12645
|
if (state.refreshTimer) {
|
|
@@ -12635,6 +12651,11 @@ function clearState(state) {
|
|
|
12635
12651
|
state.syncTimer = null;
|
|
12636
12652
|
}
|
|
12637
12653
|
}
|
|
12654
|
+
function clearState(state) {
|
|
12655
|
+
clearAuthenticationData(state);
|
|
12656
|
+
state.isRefreshing = false;
|
|
12657
|
+
state.refreshPromise = null;
|
|
12658
|
+
}
|
|
12638
12659
|
const AUTO_THEME_LEASE_STATE = "__RAI_AUTO_THEME_LEASE_STATE__";
|
|
12639
12660
|
const DISABLE_AUTO_THEME = "__DISABLE_AUTO_THEME__";
|
|
12640
12661
|
function acquireAutoThemeDisableLease(target = window) {
|
|
@@ -12662,6 +12683,145 @@ function acquireAutoThemeDisableLease(target = window) {
|
|
|
12662
12683
|
delete target[AUTO_THEME_LEASE_STATE];
|
|
12663
12684
|
};
|
|
12664
12685
|
}
|
|
12686
|
+
const REGISTRY_SYMBOL = Symbol.for("returningai.widget-sdk.auth-sessions.v1");
|
|
12687
|
+
function registry() {
|
|
12688
|
+
const root = globalThis;
|
|
12689
|
+
root[REGISTRY_SYMBOL] ?? (root[REGISTRY_SYMBOL] = /* @__PURE__ */ new Map());
|
|
12690
|
+
return root[REGISTRY_SYMBOL];
|
|
12691
|
+
}
|
|
12692
|
+
function stableIdentity(config) {
|
|
12693
|
+
const identifiers = Object.keys(config.userIdentifiers || {}).sort().map((key) => [key, config.userIdentifiers[key]]);
|
|
12694
|
+
return JSON.stringify({
|
|
12695
|
+
embedToken: config.embedToken || "",
|
|
12696
|
+
userIdentifiers: identifiers
|
|
12697
|
+
});
|
|
12698
|
+
}
|
|
12699
|
+
function stableHash(value) {
|
|
12700
|
+
let first = 2166136261;
|
|
12701
|
+
let second = 2654435769;
|
|
12702
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
12703
|
+
const code = value.charCodeAt(index);
|
|
12704
|
+
first = Math.imul(first ^ code, 16777619) >>> 0;
|
|
12705
|
+
second = Math.imul(second ^ code, 2246822507) >>> 0;
|
|
12706
|
+
}
|
|
12707
|
+
return `${first.toString(36)}${second.toString(36)}`;
|
|
12708
|
+
}
|
|
12709
|
+
function normalizeUrl(value) {
|
|
12710
|
+
if (!value) return "";
|
|
12711
|
+
try {
|
|
12712
|
+
const parsed = new URL(value);
|
|
12713
|
+
return `${parsed.origin.toLowerCase()}${parsed.pathname.replace(/\/+$/, "")}`;
|
|
12714
|
+
} catch {
|
|
12715
|
+
return value.replace(/\/+$/, "");
|
|
12716
|
+
}
|
|
12717
|
+
}
|
|
12718
|
+
function createAuthScopeKey(config) {
|
|
12719
|
+
const scope = JSON.stringify({
|
|
12720
|
+
apiUrl: normalizeUrl(config.apiUrl),
|
|
12721
|
+
authUrl: normalizeUrl(config.authUrl),
|
|
12722
|
+
widgetId: config.widgetId,
|
|
12723
|
+
widgetType: config.widgetType,
|
|
12724
|
+
identity: stableIdentity(config)
|
|
12725
|
+
});
|
|
12726
|
+
return `rai-auth-v1:${stableHash(scope)}`;
|
|
12727
|
+
}
|
|
12728
|
+
function captureSnapshot(state) {
|
|
12729
|
+
return {
|
|
12730
|
+
accessToken: state.accessToken,
|
|
12731
|
+
refreshToken: state.refreshToken,
|
|
12732
|
+
tokenFamily: state.tokenFamily,
|
|
12733
|
+
accessTokenExpiry: state.accessTokenExpiry,
|
|
12734
|
+
refreshTokenExpiry: state.refreshTokenExpiry,
|
|
12735
|
+
isAuthenticated: state.isAuthenticated,
|
|
12736
|
+
lastAuthError: state.lastAuthError ? { ...state.lastAuthError } : null,
|
|
12737
|
+
widgetData: state.widgetData
|
|
12738
|
+
};
|
|
12739
|
+
}
|
|
12740
|
+
function applySnapshot(subscriber, snapshot) {
|
|
12741
|
+
var _a;
|
|
12742
|
+
Object.assign(subscriber.state, snapshot);
|
|
12743
|
+
if (snapshot.refreshToken) saveToStorage(subscriber.config, subscriber.state);
|
|
12744
|
+
else clearStorage(subscriber.config);
|
|
12745
|
+
try {
|
|
12746
|
+
(_a = subscriber.onSnapshot) == null ? void 0 : _a.call(subscriber);
|
|
12747
|
+
} catch (error) {
|
|
12748
|
+
console.error("[rai-widget] Auth session subscriber failed:", error);
|
|
12749
|
+
}
|
|
12750
|
+
}
|
|
12751
|
+
function publishSnapshot(session, state) {
|
|
12752
|
+
session.generation += 1;
|
|
12753
|
+
session.snapshot = captureSnapshot(state);
|
|
12754
|
+
for (const subscriber of session.subscribers) {
|
|
12755
|
+
applySnapshot(subscriber, session.snapshot);
|
|
12756
|
+
}
|
|
12757
|
+
}
|
|
12758
|
+
function hasUsableAuthenticatedSnapshot(session) {
|
|
12759
|
+
const snapshot = session.snapshot;
|
|
12760
|
+
return Boolean(
|
|
12761
|
+
(snapshot == null ? void 0 : snapshot.isAuthenticated) && snapshot.accessToken && snapshot.accessTokenExpiry && Date.now() < snapshot.accessTokenExpiry
|
|
12762
|
+
);
|
|
12763
|
+
}
|
|
12764
|
+
function subscribeAuthSession(config, state, onSnapshot) {
|
|
12765
|
+
const sessions = registry();
|
|
12766
|
+
const scopeKey = createAuthScopeKey(config);
|
|
12767
|
+
let session = sessions.get(scopeKey);
|
|
12768
|
+
if (!session) {
|
|
12769
|
+
session = {
|
|
12770
|
+
generation: 0,
|
|
12771
|
+
snapshot: null,
|
|
12772
|
+
inFlight: null,
|
|
12773
|
+
subscribers: /* @__PURE__ */ new Set()
|
|
12774
|
+
};
|
|
12775
|
+
sessions.set(scopeKey, session);
|
|
12776
|
+
}
|
|
12777
|
+
const subscriber = { config, state, onSnapshot };
|
|
12778
|
+
session.subscribers.add(subscriber);
|
|
12779
|
+
if (session.snapshot) applySnapshot(subscriber, session.snapshot);
|
|
12780
|
+
let released = false;
|
|
12781
|
+
return {
|
|
12782
|
+
run(operation, execute) {
|
|
12783
|
+
if (released) return Promise.resolve(false);
|
|
12784
|
+
if (operation === "authenticate" && hasUsableAuthenticatedSnapshot(session)) {
|
|
12785
|
+
applySnapshot(subscriber, session.snapshot);
|
|
12786
|
+
return Promise.resolve(true);
|
|
12787
|
+
}
|
|
12788
|
+
if (session.inFlight) return session.inFlight;
|
|
12789
|
+
if (session.snapshot) applySnapshot(subscriber, session.snapshot);
|
|
12790
|
+
const startingGeneration = session.generation;
|
|
12791
|
+
const inFlight = (async () => {
|
|
12792
|
+
const succeeded = await execute();
|
|
12793
|
+
if (succeeded || session.generation === startingGeneration) {
|
|
12794
|
+
publishSnapshot(session, state);
|
|
12795
|
+
}
|
|
12796
|
+
return succeeded;
|
|
12797
|
+
})().finally(() => {
|
|
12798
|
+
if (session.inFlight === inFlight) session.inFlight = null;
|
|
12799
|
+
if (session.subscribers.size === 0) sessions.delete(scopeKey);
|
|
12800
|
+
});
|
|
12801
|
+
session.inFlight = inFlight;
|
|
12802
|
+
return inFlight;
|
|
12803
|
+
},
|
|
12804
|
+
publish() {
|
|
12805
|
+
if (!released) publishSnapshot(session, state);
|
|
12806
|
+
},
|
|
12807
|
+
clear() {
|
|
12808
|
+
if (released) return;
|
|
12809
|
+
state.accessToken = null;
|
|
12810
|
+
state.refreshToken = null;
|
|
12811
|
+
state.tokenFamily = null;
|
|
12812
|
+
state.accessTokenExpiry = null;
|
|
12813
|
+
state.refreshTokenExpiry = null;
|
|
12814
|
+
state.isAuthenticated = false;
|
|
12815
|
+
publishSnapshot(session, state);
|
|
12816
|
+
},
|
|
12817
|
+
release() {
|
|
12818
|
+
if (released) return;
|
|
12819
|
+
released = true;
|
|
12820
|
+
session.subscribers.delete(subscriber);
|
|
12821
|
+
if (session.subscribers.size === 0 && !session.inFlight) sessions.delete(scopeKey);
|
|
12822
|
+
}
|
|
12823
|
+
};
|
|
12824
|
+
}
|
|
12665
12825
|
const BUNDLE_READY_TIMEOUT_MS = 3e4;
|
|
12666
12826
|
function isPromiseLike(value) {
|
|
12667
12827
|
return (typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function";
|
|
@@ -13847,6 +14007,7 @@ class BaseWidget extends HTMLElement {
|
|
|
13847
14007
|
__publicField(this, "bannerNode", null);
|
|
13848
14008
|
__publicField(this, "releaseAutoThemeDisableLease");
|
|
13849
14009
|
__publicField(this, "bundleUnmount");
|
|
14010
|
+
__publicField(this, "authSession");
|
|
13850
14011
|
this.shadow = this.attachShadow({ mode: "closed" });
|
|
13851
14012
|
}
|
|
13852
14013
|
static get observedAttributes() {
|
|
@@ -13901,7 +14062,7 @@ class BaseWidget extends HTMLElement {
|
|
|
13901
14062
|
}
|
|
13902
14063
|
}
|
|
13903
14064
|
disconnectedCallback() {
|
|
13904
|
-
var _a, _b, _c;
|
|
14065
|
+
var _a, _b, _c, _d;
|
|
13905
14066
|
(_a = this.intersectionObserver) == null ? void 0 : _a.disconnect();
|
|
13906
14067
|
(_b = this.themeObserver) == null ? void 0 : _b.disconnect();
|
|
13907
14068
|
(_c = this.identityAttributeObserver) == null ? void 0 : _c.disconnect();
|
|
@@ -13909,6 +14070,8 @@ class BaseWidget extends HTMLElement {
|
|
|
13909
14070
|
this.clearLottieInstances();
|
|
13910
14071
|
this.restoreLoaderHeight();
|
|
13911
14072
|
this.removeBanner();
|
|
14073
|
+
(_d = this.authSession) == null ? void 0 : _d.release();
|
|
14074
|
+
this.authSession = void 0;
|
|
13912
14075
|
clearState(this.state);
|
|
13913
14076
|
}
|
|
13914
14077
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
@@ -14509,12 +14672,22 @@ class BaseWidget extends HTMLElement {
|
|
|
14509
14672
|
}
|
|
14510
14673
|
// ── Initialization ─────────────────────────────────────────────────────
|
|
14511
14674
|
async init() {
|
|
14675
|
+
var _a;
|
|
14512
14676
|
const initId = ++this.initGeneration;
|
|
14513
14677
|
this.hasAttemptedInit = true;
|
|
14514
14678
|
if (!this.config.communityId) {
|
|
14515
14679
|
this.showError();
|
|
14516
14680
|
return;
|
|
14517
14681
|
}
|
|
14682
|
+
if (!this.config.authUrl && !this.authSession) {
|
|
14683
|
+
this.authSession = subscribeAuthSession(this.config, this.state, () => {
|
|
14684
|
+
if (this.config.autoRefresh) this.scheduleRefresh();
|
|
14685
|
+
if (this.state.iframe) {
|
|
14686
|
+
sendTokenToWidget(this.config, this.state, this.state.iframe);
|
|
14687
|
+
}
|
|
14688
|
+
syncBundleAccessToken(this.config, this.state);
|
|
14689
|
+
});
|
|
14690
|
+
}
|
|
14518
14691
|
if (this.config.bundleUrl) {
|
|
14519
14692
|
this.prepareBundleRuntimeEnvironment();
|
|
14520
14693
|
warmBundleRuntime(
|
|
@@ -14527,9 +14700,9 @@ class BaseWidget extends HTMLElement {
|
|
|
14527
14700
|
(options) => fetchErrorSettings(this.config, this.state, options)
|
|
14528
14701
|
);
|
|
14529
14702
|
const launchWidget = () => {
|
|
14530
|
-
var
|
|
14703
|
+
var _a2;
|
|
14531
14704
|
if (!this.isCurrentInit(initId)) return;
|
|
14532
|
-
(
|
|
14705
|
+
(_a2 = this.errorEl) == null ? void 0 : _a2.classList.remove("visible");
|
|
14533
14706
|
this.shadow.querySelectorAll(".rai-custom-error").forEach((el2) => el2.remove());
|
|
14534
14707
|
let bannerGated = false;
|
|
14535
14708
|
let bannerGateTimer = 0;
|
|
@@ -14571,7 +14744,10 @@ class BaseWidget extends HTMLElement {
|
|
|
14571
14744
|
clearState(this.state);
|
|
14572
14745
|
clearStorage(this.config);
|
|
14573
14746
|
const authed2 = await finishStartup(
|
|
14574
|
-
|
|
14747
|
+
this.authSession ? this.authSession.run(
|
|
14748
|
+
"authenticate",
|
|
14749
|
+
() => authenticateServerless(this.config, this.state)
|
|
14750
|
+
) : authenticateServerless(
|
|
14575
14751
|
this.config,
|
|
14576
14752
|
this.state,
|
|
14577
14753
|
() => this.scheduleRefresh()
|
|
@@ -14586,10 +14762,22 @@ class BaseWidget extends HTMLElement {
|
|
|
14586
14762
|
}
|
|
14587
14763
|
return;
|
|
14588
14764
|
}
|
|
14589
|
-
const
|
|
14765
|
+
const hasSharedSession = Boolean(
|
|
14766
|
+
this.state.isAuthenticated && this.state.accessToken && this.state.accessTokenExpiry && Date.now() < this.state.accessTokenExpiry
|
|
14767
|
+
);
|
|
14768
|
+
const hasStored = hasSharedSession || loadFromStorage(this.config, this.state);
|
|
14590
14769
|
if (hasStored) {
|
|
14770
|
+
if (hasSharedSession) {
|
|
14771
|
+
this.emit("rai-authenticated");
|
|
14772
|
+
launchWidget();
|
|
14773
|
+
return;
|
|
14774
|
+
}
|
|
14775
|
+
(_a = this.authSession) == null ? void 0 : _a.publish();
|
|
14591
14776
|
const refreshed = await finishStartup(
|
|
14592
|
-
|
|
14777
|
+
this.authSession ? this.authSession.run(
|
|
14778
|
+
"refresh",
|
|
14779
|
+
() => refreshAccessToken(this.config, this.state)
|
|
14780
|
+
) : refreshAccessToken(
|
|
14593
14781
|
this.config,
|
|
14594
14782
|
this.state,
|
|
14595
14783
|
() => this.scheduleRefresh()
|
|
@@ -14606,7 +14794,10 @@ class BaseWidget extends HTMLElement {
|
|
|
14606
14794
|
return;
|
|
14607
14795
|
}
|
|
14608
14796
|
const authed = await finishStartup(
|
|
14609
|
-
|
|
14797
|
+
this.authSession ? this.authSession.run(
|
|
14798
|
+
"authenticate",
|
|
14799
|
+
() => authenticateServerless(this.config, this.state)
|
|
14800
|
+
) : authenticateServerless(
|
|
14610
14801
|
this.config,
|
|
14611
14802
|
this.state,
|
|
14612
14803
|
() => this.scheduleRefresh()
|
|
@@ -14847,13 +15038,18 @@ class BaseWidget extends HTMLElement {
|
|
|
14847
15038
|
() => this.scheduleRefresh()
|
|
14848
15039
|
);
|
|
14849
15040
|
if (ok) pushToken();
|
|
15041
|
+
else this.handleSessionExpired();
|
|
14850
15042
|
} else {
|
|
14851
|
-
await
|
|
15043
|
+
const ok = this.authSession ? await this.authSession.run(
|
|
15044
|
+
"refresh",
|
|
15045
|
+
() => refreshAccessToken(this.config, this.state)
|
|
15046
|
+
) : await refreshAccessToken(
|
|
14852
15047
|
this.config,
|
|
14853
15048
|
this.state,
|
|
14854
15049
|
() => this.scheduleRefresh(),
|
|
14855
15050
|
pushToken
|
|
14856
15051
|
);
|
|
15052
|
+
if (!ok) this.handleSessionExpired();
|
|
14857
15053
|
}
|
|
14858
15054
|
};
|
|
14859
15055
|
if (delay > 0) {
|
|
@@ -14869,18 +15065,51 @@ class BaseWidget extends HTMLElement {
|
|
|
14869
15065
|
sendTokenToWidget(this.config, this.state, iframe);
|
|
14870
15066
|
}, 12e4);
|
|
14871
15067
|
}
|
|
15068
|
+
/**
|
|
15069
|
+
* A background refresh exhausted every recovery path — /auth/refresh failed
|
|
15070
|
+
* and the re-auth that follows it failed too, so `state.isAuthenticated` is
|
|
15071
|
+
* now false and there is no live token.
|
|
15072
|
+
*
|
|
15073
|
+
* Every startup auth path already calls showError() on failure; this one is
|
|
15074
|
+
* reached from a timer, so without it the widget stays mounted showing stale
|
|
15075
|
+
* content while isAuthenticated() reports false to the host page — which is
|
|
15076
|
+
* how a signed-in user ends up looking at content meant for signed-out users.
|
|
15077
|
+
* The event gives hosts a signal to re-mint credentials instead of polling.
|
|
15078
|
+
*/
|
|
15079
|
+
handleSessionExpired() {
|
|
15080
|
+
var _a;
|
|
15081
|
+
if (this.state.refreshTimer) {
|
|
15082
|
+
clearTimeout(this.state.refreshTimer);
|
|
15083
|
+
this.state.refreshTimer = null;
|
|
15084
|
+
}
|
|
15085
|
+
(_a = this.state).lastAuthError ?? (_a.lastAuthError = {
|
|
15086
|
+
status: 401,
|
|
15087
|
+
error: "Session expired",
|
|
15088
|
+
hint: "The widget session could not be renewed. Reload to sign in again."
|
|
15089
|
+
});
|
|
15090
|
+
this.emit("rai-session-expired", {
|
|
15091
|
+
communityId: this.config.communityId,
|
|
15092
|
+
widgetType: this.config.widgetType,
|
|
15093
|
+
lastAuthError: this.state.lastAuthError ?? void 0
|
|
15094
|
+
});
|
|
15095
|
+
this.showError();
|
|
15096
|
+
}
|
|
14872
15097
|
// ── Logout helper ─────────────────────────────────────────────────────
|
|
14873
15098
|
async logoutAndClear() {
|
|
15099
|
+
var _a;
|
|
14874
15100
|
await logout(this.config, this.state);
|
|
15101
|
+
(_a = this.authSession) == null ? void 0 : _a.clear();
|
|
14875
15102
|
this.teardownMountedWidget();
|
|
14876
15103
|
this.removeBanner();
|
|
14877
15104
|
this.emit("rai-logout");
|
|
14878
15105
|
}
|
|
14879
15106
|
// ── Public API ────────────────────────────────────────────────────────
|
|
14880
15107
|
async reload() {
|
|
14881
|
-
var _a;
|
|
15108
|
+
var _a, _b;
|
|
14882
15109
|
const previousConfig = this.config;
|
|
14883
|
-
|
|
15110
|
+
(_a = this.authSession) == null ? void 0 : _a.release();
|
|
15111
|
+
this.authSession = void 0;
|
|
15112
|
+
this.config = readConfig(this, (_b = this.config) == null ? void 0 : _b.communityId);
|
|
14884
15113
|
this.identitySignature = identitySignatureForConfig(this.config);
|
|
14885
15114
|
this.style.width = this.config.width;
|
|
14886
15115
|
this.style.height = this.config.height;
|
|
@@ -15024,7 +15253,7 @@ class CustomWidget extends BaseWidget {
|
|
|
15024
15253
|
}
|
|
15025
15254
|
}
|
|
15026
15255
|
}
|
|
15027
|
-
console.log(`[rai-widget] v${"1.8.
|
|
15256
|
+
console.log(`[rai-widget] v${"1.8.4"}`);
|
|
15028
15257
|
class StoreWidgetCompat extends StoreWidget {
|
|
15029
15258
|
}
|
|
15030
15259
|
const WIDGET_REGISTRY = [
|
|
@@ -15088,7 +15317,7 @@ function exposePublicApi() {
|
|
|
15088
15317
|
})();
|
|
15089
15318
|
const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
|
|
15090
15319
|
window.ReturningAIWidget = {
|
|
15091
|
-
version: "1.8.
|
|
15320
|
+
version: "1.8.4",
|
|
15092
15321
|
reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
|
|
15093
15322
|
logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
|
|
15094
15323
|
isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
|
|
@@ -22,6 +22,7 @@ export declare abstract class BaseWidget extends HTMLElement {
|
|
|
22
22
|
private bannerNode;
|
|
23
23
|
private releaseAutoThemeDisableLease?;
|
|
24
24
|
private bundleUnmount?;
|
|
25
|
+
private authSession?;
|
|
25
26
|
constructor();
|
|
26
27
|
registerCallback<K extends keyof ReturningAIWidgetCallbackMap>(callbackName: K, callback: ReturningAIWidgetCallbackMap[K]): void;
|
|
27
28
|
registerCallback(callbackName: string, callback: ReturningAIWidgetCallback): void;
|
|
@@ -110,6 +111,18 @@ export declare abstract class BaseWidget extends HTMLElement {
|
|
|
110
111
|
private mountWidget;
|
|
111
112
|
private scheduleRefresh;
|
|
112
113
|
private schedulePeriodicSync;
|
|
114
|
+
/**
|
|
115
|
+
* A background refresh exhausted every recovery path — /auth/refresh failed
|
|
116
|
+
* and the re-auth that follows it failed too, so `state.isAuthenticated` is
|
|
117
|
+
* now false and there is no live token.
|
|
118
|
+
*
|
|
119
|
+
* Every startup auth path already calls showError() on failure; this one is
|
|
120
|
+
* reached from a timer, so without it the widget stays mounted showing stale
|
|
121
|
+
* content while isAuthenticated() reports false to the host page — which is
|
|
122
|
+
* how a signed-in user ends up looking at content meant for signed-out users.
|
|
123
|
+
* The event gives hosts a signal to re-mint credentials instead of polling.
|
|
124
|
+
*/
|
|
125
|
+
private handleSessionExpired;
|
|
113
126
|
private logoutAndClear;
|
|
114
127
|
reload(): Promise<void>;
|
|
115
128
|
logoutPublic(): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WidgetConfig, WidgetState } from '../types';
|
|
2
|
+
export type AuthSessionOperation = 'authenticate' | 'refresh';
|
|
3
|
+
export interface AuthSessionSubscription {
|
|
4
|
+
run(operation: AuthSessionOperation, execute: () => Promise<boolean>): Promise<boolean>;
|
|
5
|
+
publish(): void;
|
|
6
|
+
clear(): void;
|
|
7
|
+
release(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function createAuthScopeKey(config: WidgetConfig): string;
|
|
10
|
+
export declare function subscribeAuthSession(config: WidgetConfig, state: WidgetState, onSnapshot?: () => void): AuthSessionSubscription;
|
|
11
|
+
export declare function resetAuthSessionCoordinatorForTests(): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@returningai/widget-sdk",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
4
4
|
"description": "Shadow DOM isolated widget SDK for ReturningAI",
|
|
5
5
|
"main": "dist/rai-widget.iife.js",
|
|
6
6
|
"module": "dist/rai-widget.js",
|
|
@@ -25,12 +25,15 @@
|
|
|
25
25
|
"build:all": "npm run build && npm run build:banner",
|
|
26
26
|
"build:all:min": "npm run build:min && npm run build:banner:min",
|
|
27
27
|
"types": "tsc --emitDeclarationOnly --outDir dist/types && cp src/jsx.d.ts dist/types/jsx.d.ts",
|
|
28
|
-
"test": "npm run test:host-callback && npm run test:bundle-readiness && npm run test:bundle-runtime-cache && npm run test:bundle-token-sync && npm run test:auth-bootstrap && npm run test:startup-performance && npm run test:jsx-types",
|
|
28
|
+
"test": "npm run test:host-callback && npm run test:bundle-readiness && npm run test:bundle-runtime-cache && npm run test:bundle-token-sync && npm run test:auth-bootstrap && npm run test:auth-error-reporting && npm run test:auth-refresh-state && npm run test:auth-session-coordinator && npm run test:startup-performance && npm run test:jsx-types",
|
|
29
29
|
"test:host-callback": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/postmessage-host-callback.test.js",
|
|
30
30
|
"test:bundle-readiness": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/bundle-readiness.test.js",
|
|
31
31
|
"test:bundle-runtime-cache": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/bundle-runtime-cache.test.js",
|
|
32
32
|
"test:bundle-token-sync": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/bundle-token-sync.test.js",
|
|
33
33
|
"test:auth-bootstrap": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-bootstrap.test.js",
|
|
34
|
+
"test:auth-error-reporting": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-error-reporting.test.js",
|
|
35
|
+
"test:auth-refresh-state": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-refresh-state.test.js",
|
|
36
|
+
"test:auth-session-coordinator": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-session-coordinator.test.js",
|
|
34
37
|
"test:startup-performance": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/startup-performance.test.js",
|
|
35
38
|
"test:jsx-types": "tsc --noEmit --strict --lib ES2020,DOM --skipLibCheck false src/jsx.d.ts",
|
|
36
39
|
"coverage:repowise": "rm -rf coverage && npm run coverage:host-callback && npm run coverage:bundle-readiness && npm run test:jsx-types",
|
|
@@ -48,4 +51,4 @@
|
|
|
48
51
|
"dompurify": "^3.4.12",
|
|
49
52
|
"lottie-web": "^5.13.0"
|
|
50
53
|
}
|
|
51
|
-
}
|
|
54
|
+
}
|