@returningai/widget-sdk 1.8.5 → 1.8.7
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 +39 -0
- package/README.md +22 -0
- 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 +205 -46
- package/dist/types/BaseWidget.d.ts +5 -9
- package/dist/types/core/auth-renewal-recovery.d.ts +19 -0
- package/dist/types/core/auth.d.ts +1 -0
- package/dist/types/jsx.d.ts +37 -28
- package/package.json +7 -4
package/dist/rai-widget.js
CHANGED
|
@@ -12343,6 +12343,109 @@ function isTokenExpired(expiry) {
|
|
|
12343
12343
|
if (!expiry) return true;
|
|
12344
12344
|
return Date.now() >= expiry - 6e4;
|
|
12345
12345
|
}
|
|
12346
|
+
const RETRY_DELAYS_MS = [5e3, 15e3, 3e4];
|
|
12347
|
+
const RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([408, 425, 429]);
|
|
12348
|
+
function isPermanentAuthFailure(error) {
|
|
12349
|
+
if (!error) return false;
|
|
12350
|
+
const { status } = error;
|
|
12351
|
+
return status >= 400 && status < 500 && !RETRYABLE_CLIENT_STATUSES.has(status);
|
|
12352
|
+
}
|
|
12353
|
+
function createDefaultEnvironment$1() {
|
|
12354
|
+
const visibilityDocument = typeof document === "undefined" ? void 0 : document;
|
|
12355
|
+
const pageWindow = typeof window === "undefined" ? void 0 : window;
|
|
12356
|
+
return {
|
|
12357
|
+
isHidden: () => (visibilityDocument == null ? void 0 : visibilityDocument.visibilityState) === "hidden",
|
|
12358
|
+
setTimeout: (callback, delayMs) => globalThis.setTimeout(callback, delayMs),
|
|
12359
|
+
clearTimeout: (timerId) => globalThis.clearTimeout(timerId),
|
|
12360
|
+
addVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.addEventListener("visibilitychange", listener),
|
|
12361
|
+
removeVisibilityChangeListener: (listener) => visibilityDocument == null ? void 0 : visibilityDocument.removeEventListener("visibilitychange", listener),
|
|
12362
|
+
addPageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("pageshow", listener),
|
|
12363
|
+
removePageShowListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("pageshow", listener),
|
|
12364
|
+
addOnlineListener: (listener) => pageWindow == null ? void 0 : pageWindow.addEventListener("online", listener),
|
|
12365
|
+
removeOnlineListener: (listener) => pageWindow == null ? void 0 : pageWindow.removeEventListener("online", listener)
|
|
12366
|
+
};
|
|
12367
|
+
}
|
|
12368
|
+
function createAuthRenewalRecoveryController(retry, environment = createDefaultEnvironment$1()) {
|
|
12369
|
+
let pending = false;
|
|
12370
|
+
let retryInFlight = false;
|
|
12371
|
+
let rescheduleRequested = false;
|
|
12372
|
+
let retryAttempt = 0;
|
|
12373
|
+
let timerId = null;
|
|
12374
|
+
let listening = false;
|
|
12375
|
+
const clearTimer = () => {
|
|
12376
|
+
if (timerId === null) return;
|
|
12377
|
+
environment.clearTimeout(timerId);
|
|
12378
|
+
timerId = null;
|
|
12379
|
+
};
|
|
12380
|
+
const disarmSignals = () => {
|
|
12381
|
+
if (!listening) return;
|
|
12382
|
+
listening = false;
|
|
12383
|
+
environment.removeVisibilityChangeListener(handleForeground);
|
|
12384
|
+
environment.removePageShowListener(handleForeground);
|
|
12385
|
+
environment.removeOnlineListener(handleOnline);
|
|
12386
|
+
};
|
|
12387
|
+
const reset = () => {
|
|
12388
|
+
pending = false;
|
|
12389
|
+
retryAttempt = 0;
|
|
12390
|
+
rescheduleRequested = false;
|
|
12391
|
+
clearTimer();
|
|
12392
|
+
disarmSignals();
|
|
12393
|
+
};
|
|
12394
|
+
const requestRetry = (resetBudget, allowWhileHidden) => {
|
|
12395
|
+
if (!pending || retryInFlight || !allowWhileHidden && environment.isHidden()) {
|
|
12396
|
+
return;
|
|
12397
|
+
}
|
|
12398
|
+
if (resetBudget) retryAttempt = 0;
|
|
12399
|
+
clearTimer();
|
|
12400
|
+
retryInFlight = true;
|
|
12401
|
+
Promise.resolve().then(retry).catch((error) => {
|
|
12402
|
+
console.error("[rai-widget] Session renewal retry failed:", error);
|
|
12403
|
+
}).finally(() => {
|
|
12404
|
+
retryInFlight = false;
|
|
12405
|
+
if (rescheduleRequested) {
|
|
12406
|
+
rescheduleRequested = false;
|
|
12407
|
+
scheduleRetry();
|
|
12408
|
+
}
|
|
12409
|
+
});
|
|
12410
|
+
};
|
|
12411
|
+
function handleForeground() {
|
|
12412
|
+
if (environment.isHidden()) return;
|
|
12413
|
+
requestRetry(true, false);
|
|
12414
|
+
}
|
|
12415
|
+
function handleOnline() {
|
|
12416
|
+
requestRetry(true, true);
|
|
12417
|
+
}
|
|
12418
|
+
const armSignals = () => {
|
|
12419
|
+
if (listening) return;
|
|
12420
|
+
listening = true;
|
|
12421
|
+
environment.addVisibilityChangeListener(handleForeground);
|
|
12422
|
+
environment.addPageShowListener(handleForeground);
|
|
12423
|
+
environment.addOnlineListener(handleOnline);
|
|
12424
|
+
};
|
|
12425
|
+
const scheduleRetry = () => {
|
|
12426
|
+
if (retryInFlight && !pending) return;
|
|
12427
|
+
pending = true;
|
|
12428
|
+
armSignals();
|
|
12429
|
+
if (retryInFlight) {
|
|
12430
|
+
rescheduleRequested = true;
|
|
12431
|
+
return;
|
|
12432
|
+
}
|
|
12433
|
+
if (timerId !== null || environment.isHidden() || retryAttempt >= RETRY_DELAYS_MS.length) {
|
|
12434
|
+
return;
|
|
12435
|
+
}
|
|
12436
|
+
const delayMs = RETRY_DELAYS_MS[retryAttempt];
|
|
12437
|
+
retryAttempt += 1;
|
|
12438
|
+
timerId = environment.setTimeout(() => {
|
|
12439
|
+
timerId = null;
|
|
12440
|
+
requestRetry(false, false);
|
|
12441
|
+
}, delayMs);
|
|
12442
|
+
};
|
|
12443
|
+
return {
|
|
12444
|
+
schedule: scheduleRetry,
|
|
12445
|
+
succeeded: reset,
|
|
12446
|
+
cancel: reset
|
|
12447
|
+
};
|
|
12448
|
+
}
|
|
12346
12449
|
function setTokens(config, state, data, onRefreshScheduled) {
|
|
12347
12450
|
state.accessToken = data.accessToken;
|
|
12348
12451
|
state.refreshToken = data.refreshToken;
|
|
@@ -12368,6 +12471,12 @@ function buildIdentityProof(config) {
|
|
|
12368
12471
|
}
|
|
12369
12472
|
return proof;
|
|
12370
12473
|
}
|
|
12474
|
+
function calculateRefreshDelay(accessTokenExpiry, now = Date.now()) {
|
|
12475
|
+
const remainingLifetime = accessTokenExpiry - now;
|
|
12476
|
+
if (remainingLifetime <= 0) return 0;
|
|
12477
|
+
const refreshLeadTime = Math.min(6e4, remainingLifetime / 2);
|
|
12478
|
+
return Math.max(0, Math.floor(remainingLifetime - refreshLeadTime));
|
|
12479
|
+
}
|
|
12371
12480
|
async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
12372
12481
|
const nonce = crypto.randomUUID();
|
|
12373
12482
|
const timestamp = Date.now();
|
|
@@ -12380,6 +12489,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
12380
12489
|
);
|
|
12381
12490
|
}
|
|
12382
12491
|
let is4xx = false;
|
|
12492
|
+
let responseStatus = 0;
|
|
12383
12493
|
try {
|
|
12384
12494
|
const body = {
|
|
12385
12495
|
nonce,
|
|
@@ -12393,6 +12503,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
12393
12503
|
body: JSON.stringify(body),
|
|
12394
12504
|
credentials: "include"
|
|
12395
12505
|
});
|
|
12506
|
+
responseStatus = response.status;
|
|
12396
12507
|
if (!response.ok) {
|
|
12397
12508
|
is4xx = response.status >= 400 && response.status < 500;
|
|
12398
12509
|
const errorBody = await response.json().catch(() => ({ error: "Authentication failed" }));
|
|
@@ -12421,7 +12532,7 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
12421
12532
|
} catch (err) {
|
|
12422
12533
|
if (is4xx || attempt === maxRetries) {
|
|
12423
12534
|
state.lastAuthError ?? (state.lastAuthError = {
|
|
12424
|
-
status:
|
|
12535
|
+
status: responseStatus,
|
|
12425
12536
|
error: err instanceof Error ? err.message : "Authentication failed"
|
|
12426
12537
|
});
|
|
12427
12538
|
state.isAuthenticated = false;
|
|
@@ -12435,6 +12546,15 @@ async function authenticateServerless(config, state, onRefreshScheduled) {
|
|
|
12435
12546
|
async function authenticateViaProxy(config, state, onRefreshScheduled) {
|
|
12436
12547
|
const maxRetries = config.maxRetries ?? 3;
|
|
12437
12548
|
const retryDelay = config.retryDelay ?? 500;
|
|
12549
|
+
const previousAuthentication = {
|
|
12550
|
+
accessToken: state.accessToken,
|
|
12551
|
+
refreshToken: state.refreshToken,
|
|
12552
|
+
tokenFamily: state.tokenFamily,
|
|
12553
|
+
accessTokenExpiry: state.accessTokenExpiry,
|
|
12554
|
+
refreshTokenExpiry: state.refreshTokenExpiry,
|
|
12555
|
+
isAuthenticated: state.isAuthenticated,
|
|
12556
|
+
widgetData: state.widgetData
|
|
12557
|
+
};
|
|
12438
12558
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
12439
12559
|
if (attempt > 0) {
|
|
12440
12560
|
await new Promise(
|
|
@@ -12476,7 +12596,13 @@ async function authenticateViaProxy(config, state, onRefreshScheduled) {
|
|
|
12476
12596
|
status,
|
|
12477
12597
|
error: err instanceof Error ? err.message : "Authentication failed"
|
|
12478
12598
|
};
|
|
12479
|
-
state.
|
|
12599
|
+
if (isPermanentAuthFailure(state.lastAuthError)) {
|
|
12600
|
+
const permanentError = state.lastAuthError;
|
|
12601
|
+
clearAuthenticationData(state);
|
|
12602
|
+
state.lastAuthError = permanentError;
|
|
12603
|
+
} else {
|
|
12604
|
+
Object.assign(state, previousAuthentication);
|
|
12605
|
+
}
|
|
12480
12606
|
return false;
|
|
12481
12607
|
}
|
|
12482
12608
|
}
|
|
@@ -12488,10 +12614,16 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
|
|
|
12488
12614
|
if (state.isRefreshing) {
|
|
12489
12615
|
return state.refreshPromise;
|
|
12490
12616
|
}
|
|
12491
|
-
if (!state.refreshToken) {
|
|
12492
|
-
return false;
|
|
12493
|
-
}
|
|
12494
12617
|
const presentedRefreshToken = state.refreshToken;
|
|
12618
|
+
const previousAuthentication = {
|
|
12619
|
+
accessToken: state.accessToken,
|
|
12620
|
+
refreshToken: state.refreshToken,
|
|
12621
|
+
tokenFamily: state.tokenFamily,
|
|
12622
|
+
accessTokenExpiry: state.accessTokenExpiry,
|
|
12623
|
+
refreshTokenExpiry: state.refreshTokenExpiry,
|
|
12624
|
+
isAuthenticated: state.isAuthenticated,
|
|
12625
|
+
widgetData: state.widgetData
|
|
12626
|
+
};
|
|
12495
12627
|
state.isRefreshing = true;
|
|
12496
12628
|
state.refreshPromise = (async () => {
|
|
12497
12629
|
const reauthenticateAfterRefreshFailure = async () => {
|
|
@@ -12500,13 +12632,22 @@ async function refreshAccessToken(config, state, onRefreshScheduled, sendToken)
|
|
|
12500
12632
|
if (sendToken) sendToken();
|
|
12501
12633
|
return true;
|
|
12502
12634
|
}
|
|
12503
|
-
clearAuthenticationData(state);
|
|
12504
|
-
clearStorage(config);
|
|
12505
12635
|
const authed = await authenticateServerless(config, state, onRefreshScheduled);
|
|
12506
12636
|
if (authed && sendToken) sendToken();
|
|
12637
|
+
if (!authed && isPermanentAuthFailure(state.lastAuthError)) {
|
|
12638
|
+
const permanentError = state.lastAuthError;
|
|
12639
|
+
clearAuthenticationData(state);
|
|
12640
|
+
state.lastAuthError = permanentError;
|
|
12641
|
+
clearStorage(config);
|
|
12642
|
+
} else if (!authed) {
|
|
12643
|
+
Object.assign(state, previousAuthentication);
|
|
12644
|
+
}
|
|
12507
12645
|
return authed;
|
|
12508
12646
|
};
|
|
12509
12647
|
try {
|
|
12648
|
+
if (!presentedRefreshToken) {
|
|
12649
|
+
return reauthenticateAfterRefreshFailure();
|
|
12650
|
+
}
|
|
12510
12651
|
const response = await fetch(`${config.apiUrl}/${config.widgetId}/auth/refresh`, {
|
|
12511
12652
|
method: "POST",
|
|
12512
12653
|
headers: {
|
|
@@ -12737,11 +12878,12 @@ function captureSnapshot(state) {
|
|
|
12737
12878
|
widgetData: state.widgetData
|
|
12738
12879
|
};
|
|
12739
12880
|
}
|
|
12740
|
-
function applySnapshot(subscriber, snapshot) {
|
|
12881
|
+
function applySnapshot(subscriber, snapshot, notify = true) {
|
|
12741
12882
|
var _a;
|
|
12742
12883
|
Object.assign(subscriber.state, snapshot);
|
|
12743
12884
|
if (snapshot.refreshToken) saveToStorage(subscriber.config, subscriber.state);
|
|
12744
12885
|
else clearStorage(subscriber.config);
|
|
12886
|
+
if (!notify) return;
|
|
12745
12887
|
try {
|
|
12746
12888
|
(_a = subscriber.onSnapshot) == null ? void 0 : _a.call(subscriber);
|
|
12747
12889
|
} catch (error) {
|
|
@@ -12786,7 +12928,7 @@ function subscribeAuthSession(config, state, onSnapshot) {
|
|
|
12786
12928
|
return Promise.resolve(true);
|
|
12787
12929
|
}
|
|
12788
12930
|
if (session.inFlight) return session.inFlight;
|
|
12789
|
-
if (session.snapshot) applySnapshot(subscriber, session.snapshot);
|
|
12931
|
+
if (session.snapshot) applySnapshot(subscriber, session.snapshot, false);
|
|
12790
12932
|
const startingGeneration = session.generation;
|
|
12791
12933
|
const inFlight = (async () => {
|
|
12792
12934
|
const succeeded = await execute();
|
|
@@ -14151,7 +14293,11 @@ class BaseWidget extends HTMLElement {
|
|
|
14151
14293
|
__publicField(this, "bundleUnmount");
|
|
14152
14294
|
__publicField(this, "cancelBundleForegroundRecovery");
|
|
14153
14295
|
__publicField(this, "authSession");
|
|
14296
|
+
__publicField(this, "authRenewalRecovery");
|
|
14154
14297
|
this.shadow = this.attachShadow({ mode: "closed" });
|
|
14298
|
+
this.authRenewalRecovery = createAuthRenewalRecoveryController(
|
|
14299
|
+
() => this.performScheduledRefresh()
|
|
14300
|
+
);
|
|
14155
14301
|
}
|
|
14156
14302
|
static get observedAttributes() {
|
|
14157
14303
|
return [
|
|
@@ -14211,6 +14357,7 @@ class BaseWidget extends HTMLElement {
|
|
|
14211
14357
|
(_c = this.identityAttributeObserver) == null ? void 0 : _c.disconnect();
|
|
14212
14358
|
(_d = this.cancelBundleForegroundRecovery) == null ? void 0 : _d.call(this);
|
|
14213
14359
|
this.cancelBundleForegroundRecovery = void 0;
|
|
14360
|
+
this.authRenewalRecovery.cancel();
|
|
14214
14361
|
this.teardownMountedWidget();
|
|
14215
14362
|
this.clearLottieInstances();
|
|
14216
14363
|
this.restoreLoaderHeight();
|
|
@@ -14819,6 +14966,7 @@ class BaseWidget extends HTMLElement {
|
|
|
14819
14966
|
var _a, _b;
|
|
14820
14967
|
(_a = this.cancelBundleForegroundRecovery) == null ? void 0 : _a.call(this);
|
|
14821
14968
|
this.cancelBundleForegroundRecovery = void 0;
|
|
14969
|
+
this.authRenewalRecovery.cancel();
|
|
14822
14970
|
const initId = ++this.initGeneration;
|
|
14823
14971
|
this.hasAttemptedInit = true;
|
|
14824
14972
|
if (!this.config.communityId) {
|
|
@@ -14827,6 +14975,9 @@ class BaseWidget extends HTMLElement {
|
|
|
14827
14975
|
}
|
|
14828
14976
|
if (!this.config.authUrl && !this.authSession) {
|
|
14829
14977
|
this.authSession = subscribeAuthSession(this.config, this.state, () => {
|
|
14978
|
+
if (this.state.isAuthenticated && !this.state.lastAuthError) {
|
|
14979
|
+
this.authRenewalRecovery.succeeded();
|
|
14980
|
+
}
|
|
14830
14981
|
if (this.config.autoRefresh) this.scheduleRefresh();
|
|
14831
14982
|
if (this.state.iframe) {
|
|
14832
14983
|
sendTokenToWidget(this.config, this.state, this.state.iframe);
|
|
@@ -15182,38 +15333,49 @@ class BaseWidget extends HTMLElement {
|
|
|
15182
15333
|
scheduleRefresh() {
|
|
15183
15334
|
if (this.state.refreshTimer) clearTimeout(this.state.refreshTimer);
|
|
15184
15335
|
if (!this.state.accessTokenExpiry) return;
|
|
15185
|
-
const delay = this.state.accessTokenExpiry
|
|
15336
|
+
const delay = calculateRefreshDelay(this.state.accessTokenExpiry);
|
|
15337
|
+
if (delay > 0) {
|
|
15338
|
+
this.state.refreshTimer = setTimeout(
|
|
15339
|
+
() => void this.performScheduledRefresh(),
|
|
15340
|
+
delay
|
|
15341
|
+
);
|
|
15342
|
+
} else {
|
|
15343
|
+
void this.performScheduledRefresh();
|
|
15344
|
+
}
|
|
15345
|
+
}
|
|
15346
|
+
async performScheduledRefresh() {
|
|
15347
|
+
this.state.refreshTimer = null;
|
|
15186
15348
|
const pushToken = () => {
|
|
15187
15349
|
if (this.state.iframe)
|
|
15188
15350
|
sendTokenToWidget(this.config, this.state, this.state.iframe);
|
|
15189
15351
|
syncBundleAccessToken(this.config, this.state);
|
|
15190
15352
|
};
|
|
15191
|
-
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
}
|
|
15213
|
-
|
|
15214
|
-
this.
|
|
15353
|
+
let ok;
|
|
15354
|
+
if (this.config.authUrl) {
|
|
15355
|
+
ok = await authenticateViaProxy(
|
|
15356
|
+
this.config,
|
|
15357
|
+
this.state,
|
|
15358
|
+
() => this.scheduleRefresh()
|
|
15359
|
+
);
|
|
15360
|
+
if (ok) pushToken();
|
|
15361
|
+
} else {
|
|
15362
|
+
ok = this.authSession ? await this.authSession.run(
|
|
15363
|
+
"refresh",
|
|
15364
|
+
() => refreshAccessToken(this.config, this.state)
|
|
15365
|
+
) : await refreshAccessToken(
|
|
15366
|
+
this.config,
|
|
15367
|
+
this.state,
|
|
15368
|
+
() => this.scheduleRefresh(),
|
|
15369
|
+
pushToken
|
|
15370
|
+
);
|
|
15371
|
+
}
|
|
15372
|
+
if (ok) {
|
|
15373
|
+
this.authRenewalRecovery.succeeded();
|
|
15374
|
+
} else if (isPermanentAuthFailure(this.state.lastAuthError)) {
|
|
15375
|
+
this.authRenewalRecovery.cancel();
|
|
15376
|
+
this.handleSessionExpired();
|
|
15215
15377
|
} else {
|
|
15216
|
-
|
|
15378
|
+
this.authRenewalRecovery.schedule();
|
|
15217
15379
|
}
|
|
15218
15380
|
}
|
|
15219
15381
|
schedulePeriodicSync(iframe) {
|
|
@@ -15224,18 +15386,13 @@ class BaseWidget extends HTMLElement {
|
|
|
15224
15386
|
}, 12e4);
|
|
15225
15387
|
}
|
|
15226
15388
|
/**
|
|
15227
|
-
* A background refresh
|
|
15228
|
-
*
|
|
15229
|
-
*
|
|
15230
|
-
*
|
|
15231
|
-
* Every startup auth path already calls showError() on failure; this one is
|
|
15232
|
-
* reached from a timer, so without it the widget stays mounted showing stale
|
|
15233
|
-
* content while isAuthenticated() reports false to the host page — which is
|
|
15234
|
-
* how a signed-in user ends up looking at content meant for signed-out users.
|
|
15235
|
-
* The event gives hosts a signal to re-mint credentials instead of polling.
|
|
15389
|
+
* A background refresh reached a confirmed permanent authentication failure
|
|
15390
|
+
* after exhausting its safe recovery paths. Transient transport, throttling,
|
|
15391
|
+
* and server failures are retained by authRenewalRecovery instead.
|
|
15236
15392
|
*/
|
|
15237
15393
|
handleSessionExpired() {
|
|
15238
15394
|
var _a;
|
|
15395
|
+
this.authRenewalRecovery.cancel();
|
|
15239
15396
|
if (this.state.refreshTimer) {
|
|
15240
15397
|
clearTimeout(this.state.refreshTimer);
|
|
15241
15398
|
this.state.refreshTimer = null;
|
|
@@ -15255,6 +15412,7 @@ class BaseWidget extends HTMLElement {
|
|
|
15255
15412
|
// ── Logout helper ─────────────────────────────────────────────────────
|
|
15256
15413
|
async logoutAndClear() {
|
|
15257
15414
|
var _a;
|
|
15415
|
+
this.authRenewalRecovery.cancel();
|
|
15258
15416
|
await logout(this.config, this.state);
|
|
15259
15417
|
(_a = this.authSession) == null ? void 0 : _a.clear();
|
|
15260
15418
|
this.teardownMountedWidget();
|
|
@@ -15264,6 +15422,7 @@ class BaseWidget extends HTMLElement {
|
|
|
15264
15422
|
// ── Public API ────────────────────────────────────────────────────────
|
|
15265
15423
|
async reload() {
|
|
15266
15424
|
var _a, _b;
|
|
15425
|
+
this.authRenewalRecovery.cancel();
|
|
15267
15426
|
const previousConfig = this.config;
|
|
15268
15427
|
(_a = this.authSession) == null ? void 0 : _a.release();
|
|
15269
15428
|
this.authSession = void 0;
|
|
@@ -15411,7 +15570,7 @@ class CustomWidget extends BaseWidget {
|
|
|
15411
15570
|
}
|
|
15412
15571
|
}
|
|
15413
15572
|
}
|
|
15414
|
-
console.log(`[rai-widget] v${"1.8.
|
|
15573
|
+
console.log(`[rai-widget] v${"1.8.7"}`);
|
|
15415
15574
|
class StoreWidgetCompat extends StoreWidget {
|
|
15416
15575
|
}
|
|
15417
15576
|
const WIDGET_REGISTRY = [
|
|
@@ -15475,7 +15634,7 @@ function exposePublicApi() {
|
|
|
15475
15634
|
})();
|
|
15476
15635
|
const widget = container == null ? void 0 : container.querySelector(ALL_WIDGET_SELECTOR);
|
|
15477
15636
|
window.ReturningAIWidget = {
|
|
15478
|
-
version: "1.8.
|
|
15637
|
+
version: "1.8.7",
|
|
15479
15638
|
reload: () => (widget == null ? void 0 : widget.reload()) ?? Promise.resolve(),
|
|
15480
15639
|
logout: () => (widget == null ? void 0 : widget.logoutPublic()) ?? Promise.resolve(),
|
|
15481
15640
|
isAuthenticated: () => (widget == null ? void 0 : widget.isAuthenticated()) ?? false,
|
|
@@ -24,6 +24,7 @@ export declare abstract class BaseWidget extends HTMLElement {
|
|
|
24
24
|
private bundleUnmount?;
|
|
25
25
|
private cancelBundleForegroundRecovery?;
|
|
26
26
|
private authSession?;
|
|
27
|
+
private authRenewalRecovery;
|
|
27
28
|
constructor();
|
|
28
29
|
registerCallback<K extends keyof ReturningAIWidgetCallbackMap>(callbackName: K, callback: ReturningAIWidgetCallbackMap[K]): void;
|
|
29
30
|
registerCallback(callbackName: string, callback: ReturningAIWidgetCallback): void;
|
|
@@ -111,17 +112,12 @@ export declare abstract class BaseWidget extends HTMLElement {
|
|
|
111
112
|
private prepareBundleRuntimeEnvironment;
|
|
112
113
|
private mountWidget;
|
|
113
114
|
private scheduleRefresh;
|
|
115
|
+
private performScheduledRefresh;
|
|
114
116
|
private schedulePeriodicSync;
|
|
115
117
|
/**
|
|
116
|
-
* A background refresh
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* Every startup auth path already calls showError() on failure; this one is
|
|
121
|
-
* reached from a timer, so without it the widget stays mounted showing stale
|
|
122
|
-
* content while isAuthenticated() reports false to the host page — which is
|
|
123
|
-
* how a signed-in user ends up looking at content meant for signed-out users.
|
|
124
|
-
* The event gives hosts a signal to re-mint credentials instead of polling.
|
|
118
|
+
* A background refresh reached a confirmed permanent authentication failure
|
|
119
|
+
* after exhausting its safe recovery paths. Transient transport, throttling,
|
|
120
|
+
* and server failures are retained by authRenewalRecovery instead.
|
|
125
121
|
*/
|
|
126
122
|
private handleSessionExpired;
|
|
127
123
|
private logoutAndClear;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WidgetState } from "../types";
|
|
2
|
+
export interface AuthRenewalRecoveryEnvironment {
|
|
3
|
+
isHidden: () => boolean;
|
|
4
|
+
setTimeout: (callback: () => void, delayMs: number) => unknown;
|
|
5
|
+
clearTimeout: (timerId: unknown) => void;
|
|
6
|
+
addVisibilityChangeListener: (listener: () => void) => void;
|
|
7
|
+
removeVisibilityChangeListener: (listener: () => void) => void;
|
|
8
|
+
addPageShowListener: (listener: () => void) => void;
|
|
9
|
+
removePageShowListener: (listener: () => void) => void;
|
|
10
|
+
addOnlineListener: (listener: () => void) => void;
|
|
11
|
+
removeOnlineListener: (listener: () => void) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface AuthRenewalRecoveryController {
|
|
14
|
+
schedule(): void;
|
|
15
|
+
succeeded(): void;
|
|
16
|
+
cancel(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function isPermanentAuthFailure(error: WidgetState["lastAuthError"]): boolean;
|
|
19
|
+
export declare function createAuthRenewalRecoveryController(retry: () => Promise<void> | void, environment?: AuthRenewalRecoveryEnvironment): AuthRenewalRecoveryController;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WidgetConfig, WidgetState } from '../types';
|
|
2
|
+
export declare function calculateRefreshDelay(accessTokenExpiry: number, now?: number): number;
|
|
2
3
|
export declare function authenticateServerless(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<boolean>;
|
|
3
4
|
export declare function authenticateViaProxy(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void): Promise<boolean>;
|
|
4
5
|
export declare function refreshAccessToken(config: WidgetConfig, state: WidgetState, onRefreshScheduled?: () => void, sendToken?: () => void): Promise<boolean>;
|
package/dist/types/jsx.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
interface RaiWidgetAttributes {
|
|
3
|
-
'community-id': string
|
|
4
|
-
'channel-id'?: string
|
|
5
|
-
'widget-id'?: string // deprecated — use 'community-id'
|
|
1
|
+
interface RaiSharedWidgetAttributes {
|
|
6
2
|
'data-email'?: string
|
|
7
3
|
'widget-type'?: 'store' | 'channel' | 'milestone' | 'social' | 'currency-view' | 'referral-conditions' | 'custom'
|
|
8
4
|
theme?: 'light' | 'dark'
|
|
@@ -24,45 +20,58 @@ interface RaiWidgetAttributes {
|
|
|
24
20
|
style?: string
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
interface RaiCommunityWidgetAttributes extends RaiSharedWidgetAttributes {
|
|
24
|
+
'community-id': string
|
|
25
|
+
'channel-id'?: string
|
|
26
|
+
/** @deprecated Standard widgets should use community-id. */
|
|
27
|
+
'widget-id'?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface RaiCustomWidgetAttributes extends RaiSharedWidgetAttributes {
|
|
31
|
+
'widget-id': string
|
|
32
|
+
'community-id'?: never
|
|
33
|
+
'channel-id'?: never
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type RaiWidgetElementAttributes<TAttributes> = TAttributes & {
|
|
28
37
|
[attributeName: string]: unknown
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
type RaiCommunityWidgetElementAttributes =
|
|
41
|
+
RaiWidgetElementAttributes<RaiCommunityWidgetAttributes>
|
|
42
|
+
|
|
43
|
+
type RaiCustomWidgetElementAttributes =
|
|
44
|
+
RaiWidgetElementAttributes<RaiCustomWidgetAttributes>
|
|
45
|
+
|
|
31
46
|
interface RaiWidgetIntrinsicElements {
|
|
32
|
-
'rai-store-widget':
|
|
33
|
-
'rai-channel-widget':
|
|
34
|
-
'rai-milestone-widget':
|
|
35
|
-
'rai-social-widget':
|
|
36
|
-
'rai-currency-widget':
|
|
37
|
-
'rai-referral-widget':
|
|
38
|
-
'rai-custom-widget':
|
|
39
|
-
'rai-widget':
|
|
47
|
+
'rai-store-widget': RaiCommunityWidgetElementAttributes
|
|
48
|
+
'rai-channel-widget': RaiCommunityWidgetElementAttributes
|
|
49
|
+
'rai-milestone-widget': RaiCommunityWidgetElementAttributes
|
|
50
|
+
'rai-social-widget': RaiCommunityWidgetElementAttributes
|
|
51
|
+
'rai-currency-widget': RaiCommunityWidgetElementAttributes
|
|
52
|
+
'rai-referral-widget': RaiCommunityWidgetElementAttributes
|
|
53
|
+
'rai-custom-widget': RaiCustomWidgetElementAttributes
|
|
54
|
+
'rai-widget': RaiCommunityWidgetElementAttributes
|
|
40
55
|
}
|
|
41
56
|
|
|
42
|
-
// ── React ──────────────────────────────────────────────────────────────────
|
|
43
57
|
declare global {
|
|
44
58
|
namespace React {
|
|
45
59
|
namespace JSX {
|
|
46
60
|
interface IntrinsicElements extends RaiWidgetIntrinsicElements {}
|
|
47
61
|
}
|
|
48
62
|
}
|
|
49
|
-
|
|
50
|
-
namespace JSX {
|
|
51
|
-
interface IntrinsicElements extends RaiWidgetIntrinsicElements {}
|
|
52
|
-
}
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
// ── Vue 3 (Volar / vue-tsc) ────────────────────────────────────────────────
|
|
56
65
|
declare module '@vue/runtime-core' {
|
|
57
66
|
interface GlobalComponents {
|
|
58
|
-
RaiStoreWidget:
|
|
59
|
-
RaiChannelWidget:
|
|
60
|
-
RaiMilestoneWidget: { $props:
|
|
61
|
-
RaiSocialWidget:
|
|
62
|
-
RaiCurrencyWidget:
|
|
63
|
-
RaiReferralWidget: { $props:
|
|
64
|
-
RaiCustomWidget:
|
|
65
|
-
RaiWidget:
|
|
67
|
+
RaiStoreWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
68
|
+
RaiChannelWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
69
|
+
RaiMilestoneWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
70
|
+
RaiSocialWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
71
|
+
RaiCurrencyWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
72
|
+
RaiReferralWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
73
|
+
RaiCustomWidget: { $props: RaiCustomWidgetElementAttributes }
|
|
74
|
+
RaiWidget: { $props: RaiCommunityWidgetElementAttributes }
|
|
66
75
|
}
|
|
67
76
|
}
|
|
68
77
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@returningai/widget-sdk",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.7",
|
|
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,7 +25,7 @@
|
|
|
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:auth-error-reporting && npm run test:auth-refresh-state && npm run test:auth-session-coordinator && npm run test:startup-performance && npm run test:widget-error-presentation && 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-renewal-recovery && npm run test:auth-session-coordinator && npm run test:startup-performance && npm run test:widget-error-presentation && 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",
|
|
@@ -33,20 +33,23 @@
|
|
|
33
33
|
"test:auth-bootstrap": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-bootstrap.test.js",
|
|
34
34
|
"test:auth-error-reporting": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-error-reporting.test.js",
|
|
35
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-renewal-recovery": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-renewal-recovery.test.js",
|
|
36
37
|
"test:auth-session-coordinator": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/auth-session-coordinator.test.js",
|
|
37
38
|
"test:startup-performance": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/startup-performance.test.js",
|
|
38
39
|
"test:widget-error-presentation": "rm -rf test/.tmp && tsc -p test/tsconfig.json && node test/.tmp/test/widget-error-presentation.test.js",
|
|
39
|
-
"test:jsx-types": "
|
|
40
|
+
"test:jsx-types": "npm run types && node --test test/jsx/declaration-shape.test.mjs test/jsx/packed-consumer.test.mjs && tsc -p test/jsx/tsconfig.react.json && tsc -p test/jsx/tsconfig.vue.json",
|
|
40
41
|
"coverage:repowise": "rm -rf coverage && npm run coverage:host-callback && npm run coverage:bundle-readiness && npm run test:jsx-types",
|
|
41
42
|
"coverage:host-callback": "rm -rf test/.tmp && tsc -p test/tsconfig.json && c8 --reporter=lcov --reports-dir=coverage/host-callback --exclude-after-remap --exclude='**/*.test.ts' node test/.tmp/test/postmessage-host-callback.test.js && node scripts/name-lcov-test.mjs coverage/host-callback/lcov.info test/postmessage-host-callback.test.ts",
|
|
42
43
|
"coverage:bundle-readiness": "rm -rf test/.tmp && tsc -p test/tsconfig.json && c8 --reporter=lcov --reports-dir=coverage/bundle-readiness --exclude-after-remap --exclude='**/*.test.ts' node test/.tmp/test/bundle-readiness.test.js && node scripts/name-lcov-test.mjs coverage/bundle-readiness/lcov.info test/bundle-readiness.test.ts",
|
|
43
44
|
"prepublishOnly": "npm run build:all:min && npm run types"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
47
|
+
"@types/react": "^19.1.10",
|
|
46
48
|
"c8": "^11.0.0",
|
|
47
49
|
"terser": "^5.46.0",
|
|
48
50
|
"typescript": "^5.3.3",
|
|
49
|
-
"vite": "^5.1.0"
|
|
51
|
+
"vite": "^5.1.0",
|
|
52
|
+
"vue": "^3.5.21"
|
|
50
53
|
},
|
|
51
54
|
"dependencies": {
|
|
52
55
|
"dompurify": "^3.4.12",
|