@iblai/web-utils 1.11.11 → 1.12.0
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/dist/auth/web-utils/src/features/tracking/time-tracker.d.ts +55 -4
- package/dist/index.d.ts +55 -4
- package/dist/index.esm.js +216 -16
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +216 -16
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/web-utils/src/features/tracking/time-tracker.d.ts +55 -4
- package/dist/web-utils/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9200,22 +9200,22 @@ async function syncAuthToCookies(storageService) {
|
|
|
9200
9200
|
function hasNonExpiredAuthToken() {
|
|
9201
9201
|
const token = window.localStorage.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN);
|
|
9202
9202
|
if (!token) {
|
|
9203
|
-
console.log(
|
|
9203
|
+
console.log("################### [hasNonExpiredAuthToken] axd token is not defined", token);
|
|
9204
9204
|
return false;
|
|
9205
9205
|
}
|
|
9206
9206
|
const tokenExpiry = window.localStorage.getItem(LOCAL_STORAGE_KEYS.TOKEN_EXPIRY);
|
|
9207
9207
|
if (!tokenExpiry) {
|
|
9208
|
-
console.log(
|
|
9208
|
+
console.log("################### [hasNonExpiredAuthToken] axd token expiry is not defined", tokenExpiry);
|
|
9209
9209
|
return true;
|
|
9210
9210
|
}
|
|
9211
9211
|
const expiryDate = new Date(tokenExpiry);
|
|
9212
9212
|
if (isNaN(expiryDate.getTime())) {
|
|
9213
|
-
console.log(
|
|
9213
|
+
console.log("################### [hasNonExpiredAuthToken] axd token expiry date", expiryDate);
|
|
9214
9214
|
return false;
|
|
9215
9215
|
}
|
|
9216
9216
|
const currentDate = new Date();
|
|
9217
9217
|
if (expiryDate <= currentDate) {
|
|
9218
|
-
console.log(
|
|
9218
|
+
console.log("################### [hasNonExpiredAuthToken] axd token expiry date is less than current date ", expiryDate, currentDate);
|
|
9219
9219
|
return false;
|
|
9220
9220
|
}
|
|
9221
9221
|
return true;
|
|
@@ -9478,6 +9478,20 @@ function useAuthProvider({ middleware = new Map(), onAuthSuccess, onAuthFailure,
|
|
|
9478
9478
|
console.log("[AuthProvider] Redirect already in progress, skipping");
|
|
9479
9479
|
return;
|
|
9480
9480
|
}
|
|
9481
|
+
// Defer every auth redirect while a tenant switch is in flight.
|
|
9482
|
+
// handleTenantSwitch() clears the SHARED (cross-tab) localStorage mid-switch,
|
|
9483
|
+
// so every open window transiently sees missing tokens. Without this guard a
|
|
9484
|
+
// bystander window force-logs-out on the absent DM token, which clears
|
|
9485
|
+
// storage + sets a logout-timestamp cookie that wipes the tokens the
|
|
9486
|
+
// switching tab just wrote and cascades the logout across tabs. The TTL lock
|
|
9487
|
+
// (writeTenantSwitchLock) survives the /login/complete -> /sso-login-complete
|
|
9488
|
+
// redirect and is kept fresh across tabs via useTenantSwitchSync, so it
|
|
9489
|
+
// brackets the switch window. The cross-SPA cookie-sync poll keeps running
|
|
9490
|
+
// and recovers once the new tenant's tokens land.
|
|
9491
|
+
if (isTenantSwitchInProgress()) {
|
|
9492
|
+
console.log("[AuthProvider] Tenant switch in progress, deferring auth redirect");
|
|
9493
|
+
return;
|
|
9494
|
+
}
|
|
9481
9495
|
isRedirectingRef.current = true;
|
|
9482
9496
|
redirectStartedAtRef.current = Date.now();
|
|
9483
9497
|
// NOTE: we intentionally do NOT clear the interval here.
|
|
@@ -9663,6 +9677,17 @@ function useAuthProvider({ middleware = new Map(), onAuthSuccess, onAuthFailure,
|
|
|
9663
9677
|
var _a;
|
|
9664
9678
|
try {
|
|
9665
9679
|
setIsAuthenticating(true);
|
|
9680
|
+
// During an in-flight tenant switch the shared, cross-tab localStorage is
|
|
9681
|
+
// transiently cleared (see handleTenantSwitch), so the auth/DM tokens are
|
|
9682
|
+
// legitimately absent. Bail out BEFORE onAuthFailure — some apps hard
|
|
9683
|
+
// navigate to /error/403 from that callback (e.g. skills does
|
|
9684
|
+
// `window.location.href = '/error/403'`), which would fire even though
|
|
9685
|
+
// safeRedirectToAuthSpa itself is guarded. The cookie-sync poll re-checks
|
|
9686
|
+
// once the new tenant's tokens land.
|
|
9687
|
+
if (isTenantSwitchInProgress()) {
|
|
9688
|
+
console.log("[AuthProvider] Tenant switch in progress, skipping auth check");
|
|
9689
|
+
return;
|
|
9690
|
+
}
|
|
9666
9691
|
const authToken = await (storageService === null || storageService === void 0 ? void 0 : storageService.getItem(LOCAL_STORAGE_KEYS.AUTH_TOKEN));
|
|
9667
9692
|
if (authToken && !hasNonExpiredAuthToken()) {
|
|
9668
9693
|
const reason = "Auth token expired";
|
|
@@ -11889,46 +11914,117 @@ const selectIsReasoning = (state) => state.chatSliceShared.currentStreamingMessa
|
|
|
11889
11914
|
// NEW: Get streaming tool calls
|
|
11890
11915
|
const selectStreamingToolCalls = (state) => state.chatSliceShared.currentStreamingMessage.toolCalls;
|
|
11891
11916
|
|
|
11917
|
+
const DEFAULT_IDLE_TIMEOUT_SECONDS = 120;
|
|
11918
|
+
// Minimum gap between idle-timer resets. Interaction events like mousemove fire
|
|
11919
|
+
// rapidly; there's no need to restart the (minutes-long) idle countdown more
|
|
11920
|
+
// than once per second.
|
|
11921
|
+
const ACTIVITY_THROTTLE_MS = 1000;
|
|
11922
|
+
// User interactions that count as activity and keep the user "present".
|
|
11923
|
+
const ACTIVITY_EVENTS = [
|
|
11924
|
+
"mousemove",
|
|
11925
|
+
"mousedown",
|
|
11926
|
+
"keydown",
|
|
11927
|
+
"wheel",
|
|
11928
|
+
"scroll",
|
|
11929
|
+
"touchstart",
|
|
11930
|
+
"pointerdown",
|
|
11931
|
+
];
|
|
11892
11932
|
class TimeTracker {
|
|
11893
11933
|
constructor(config) {
|
|
11934
|
+
var _a, _b, _c;
|
|
11894
11935
|
this.state = {
|
|
11895
11936
|
currentUrl: "",
|
|
11896
|
-
startTime: 0,
|
|
11897
11937
|
intervalId: null,
|
|
11898
11938
|
routeUnsubscribe: null,
|
|
11939
|
+
accumulatedMs: 0,
|
|
11940
|
+
activeSince: null,
|
|
11941
|
+
isVisible: true,
|
|
11942
|
+
isFocused: true,
|
|
11943
|
+
isIdle: false,
|
|
11944
|
+
idleTimerId: null,
|
|
11899
11945
|
};
|
|
11946
|
+
// Timestamp of the last activity that reset the idle timer (for throttling).
|
|
11947
|
+
this.lastActivityAt = 0;
|
|
11948
|
+
// Bound listeners kept so they can be removed on destroy.
|
|
11949
|
+
this.handleActivity = () => this.onActivity();
|
|
11950
|
+
this.handleVisibilityChange = () => this.onVisibilityChange();
|
|
11951
|
+
this.handleWindowFocus = () => this.onWindowFocus();
|
|
11952
|
+
this.handleWindowBlur = () => this.onWindowBlur();
|
|
11900
11953
|
this.config = config;
|
|
11954
|
+
this.idleTimeoutMs =
|
|
11955
|
+
((_a = config.idleTimeoutSeconds) !== null && _a !== void 0 ? _a : DEFAULT_IDLE_TIMEOUT_SECONDS) * 1000;
|
|
11956
|
+
this.pauseOnHidden = (_b = config.pauseOnHidden) !== null && _b !== void 0 ? _b : true;
|
|
11957
|
+
this.pauseOnBlur = (_c = config.pauseOnBlur) !== null && _c !== void 0 ? _c : true;
|
|
11958
|
+
this.idleDetectionEnabled =
|
|
11959
|
+
this.idleTimeoutMs > 0 &&
|
|
11960
|
+
typeof window !== "undefined" &&
|
|
11961
|
+
typeof document !== "undefined";
|
|
11901
11962
|
this.initialize();
|
|
11902
11963
|
}
|
|
11903
11964
|
initialize() {
|
|
11904
11965
|
this.state.currentUrl = this.config.getCurrentUrl();
|
|
11966
|
+
// Seed activity signals from the current environment.
|
|
11967
|
+
if (this.pauseOnHidden && typeof document !== "undefined") {
|
|
11968
|
+
this.state.isVisible = !document.hidden;
|
|
11969
|
+
}
|
|
11970
|
+
this.state.isFocused = true;
|
|
11971
|
+
this.state.isIdle = false;
|
|
11972
|
+
this.attachActivityListeners();
|
|
11905
11973
|
this.startTracking();
|
|
11974
|
+
this.startIdleTimer();
|
|
11906
11975
|
if (this.config.onRouteChange) {
|
|
11907
11976
|
this.state.routeUnsubscribe = this.config.onRouteChange(() => {
|
|
11908
11977
|
this.handleRouteChange();
|
|
11909
11978
|
});
|
|
11910
11979
|
}
|
|
11911
11980
|
}
|
|
11981
|
+
// --- Active time accounting -------------------------------------------------
|
|
11982
|
+
/** Whether time should currently be counted. */
|
|
11983
|
+
isActive() {
|
|
11984
|
+
return this.state.isVisible && this.state.isFocused && !this.state.isIdle;
|
|
11985
|
+
}
|
|
11986
|
+
/**
|
|
11987
|
+
* Reconcile the active window with the current activity signals: open a new
|
|
11988
|
+
* window when the user becomes active, or bank the elapsed time when they
|
|
11989
|
+
* become inactive.
|
|
11990
|
+
*/
|
|
11991
|
+
syncActiveWindow() {
|
|
11992
|
+
const active = this.isActive();
|
|
11993
|
+
if (active && this.state.activeSince === null) {
|
|
11994
|
+
this.state.activeSince = Date.now();
|
|
11995
|
+
}
|
|
11996
|
+
else if (!active && this.state.activeSince !== null) {
|
|
11997
|
+
this.state.accumulatedMs += Date.now() - this.state.activeSince;
|
|
11998
|
+
this.state.activeSince = null;
|
|
11999
|
+
}
|
|
12000
|
+
}
|
|
12001
|
+
getTimeSpent() {
|
|
12002
|
+
let total = this.state.accumulatedMs;
|
|
12003
|
+
if (this.state.activeSince !== null) {
|
|
12004
|
+
total += Date.now() - this.state.activeSince;
|
|
12005
|
+
}
|
|
12006
|
+
return total;
|
|
12007
|
+
}
|
|
12008
|
+
resetTimer() {
|
|
12009
|
+
this.state.accumulatedMs = 0;
|
|
12010
|
+
this.state.activeSince = this.isActive() ? Date.now() : null;
|
|
12011
|
+
}
|
|
11912
12012
|
startTracking() {
|
|
11913
|
-
this.
|
|
11914
|
-
this.
|
|
12013
|
+
this.clearTrackingInterval();
|
|
12014
|
+
this.resetTimer();
|
|
12015
|
+
if (typeof window === "undefined")
|
|
12016
|
+
return;
|
|
11915
12017
|
this.state.intervalId = window.setInterval(() => {
|
|
11916
12018
|
this.sendTimeUpdate();
|
|
11917
12019
|
this.resetTimer();
|
|
11918
12020
|
}, this.config.intervalSeconds * 1000);
|
|
11919
12021
|
}
|
|
11920
|
-
|
|
12022
|
+
clearTrackingInterval() {
|
|
11921
12023
|
if (this.state.intervalId !== null) {
|
|
11922
12024
|
clearInterval(this.state.intervalId);
|
|
11923
12025
|
this.state.intervalId = null;
|
|
11924
12026
|
}
|
|
11925
12027
|
}
|
|
11926
|
-
resetTimer() {
|
|
11927
|
-
this.state.startTime = Date.now();
|
|
11928
|
-
}
|
|
11929
|
-
getTimeSpent() {
|
|
11930
|
-
return Date.now() - this.state.startTime;
|
|
11931
|
-
}
|
|
11932
12028
|
sendTimeUpdate() {
|
|
11933
12029
|
const timeSpent = this.getTimeSpent();
|
|
11934
12030
|
if (timeSpent > 0) {
|
|
@@ -11943,16 +12039,116 @@ class TimeTracker {
|
|
|
11943
12039
|
this.resetTimer();
|
|
11944
12040
|
}
|
|
11945
12041
|
}
|
|
12042
|
+
// --- Activity / inactivity detection ---------------------------------------
|
|
12043
|
+
attachActivityListeners() {
|
|
12044
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
12045
|
+
if (this.idleDetectionEnabled) {
|
|
12046
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
12047
|
+
window.addEventListener(event, this.handleActivity, {
|
|
12048
|
+
passive: true,
|
|
12049
|
+
});
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
12052
|
+
if (this.pauseOnBlur) {
|
|
12053
|
+
window.addEventListener("focus", this.handleWindowFocus);
|
|
12054
|
+
window.addEventListener("blur", this.handleWindowBlur);
|
|
12055
|
+
}
|
|
12056
|
+
}
|
|
12057
|
+
if (this.pauseOnHidden &&
|
|
12058
|
+
typeof document !== "undefined" &&
|
|
12059
|
+
document.addEventListener) {
|
|
12060
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
12061
|
+
}
|
|
12062
|
+
}
|
|
12063
|
+
detachActivityListeners() {
|
|
12064
|
+
if (typeof window !== "undefined" && window.removeEventListener) {
|
|
12065
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
12066
|
+
window.removeEventListener(event, this.handleActivity);
|
|
12067
|
+
}
|
|
12068
|
+
window.removeEventListener("focus", this.handleWindowFocus);
|
|
12069
|
+
window.removeEventListener("blur", this.handleWindowBlur);
|
|
12070
|
+
}
|
|
12071
|
+
if (typeof document !== "undefined" && document.removeEventListener) {
|
|
12072
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
12073
|
+
}
|
|
12074
|
+
}
|
|
12075
|
+
startIdleTimer() {
|
|
12076
|
+
if (!this.idleDetectionEnabled)
|
|
12077
|
+
return;
|
|
12078
|
+
this.clearIdleTimer();
|
|
12079
|
+
this.lastActivityAt = Date.now();
|
|
12080
|
+
this.state.idleTimerId = window.setTimeout(() => {
|
|
12081
|
+
this.markIdle();
|
|
12082
|
+
}, this.idleTimeoutMs);
|
|
12083
|
+
}
|
|
12084
|
+
clearIdleTimer() {
|
|
12085
|
+
if (this.state.idleTimerId !== null) {
|
|
12086
|
+
clearTimeout(this.state.idleTimerId);
|
|
12087
|
+
this.state.idleTimerId = null;
|
|
12088
|
+
}
|
|
12089
|
+
}
|
|
12090
|
+
markIdle() {
|
|
12091
|
+
if (this.state.isIdle)
|
|
12092
|
+
return;
|
|
12093
|
+
this.state.isIdle = true;
|
|
12094
|
+
this.syncActiveWindow();
|
|
12095
|
+
}
|
|
12096
|
+
onActivity() {
|
|
12097
|
+
// Returning from idle must always restart the countdown immediately.
|
|
12098
|
+
if (this.state.isIdle) {
|
|
12099
|
+
this.state.isIdle = false;
|
|
12100
|
+
this.syncActiveWindow();
|
|
12101
|
+
this.startIdleTimer();
|
|
12102
|
+
return;
|
|
12103
|
+
}
|
|
12104
|
+
// Otherwise throttle: no need to reset a minutes-long timer on every
|
|
12105
|
+
// mousemove.
|
|
12106
|
+
if (Date.now() - this.lastActivityAt < ACTIVITY_THROTTLE_MS)
|
|
12107
|
+
return;
|
|
12108
|
+
this.startIdleTimer();
|
|
12109
|
+
}
|
|
12110
|
+
onVisibilityChange() {
|
|
12111
|
+
if (typeof document === "undefined")
|
|
12112
|
+
return;
|
|
12113
|
+
this.state.isVisible = !document.hidden;
|
|
12114
|
+
if (this.state.isVisible) {
|
|
12115
|
+
// Returning to the tab counts as being present again.
|
|
12116
|
+
this.state.isIdle = false;
|
|
12117
|
+
this.startIdleTimer();
|
|
12118
|
+
}
|
|
12119
|
+
this.syncActiveWindow();
|
|
12120
|
+
}
|
|
12121
|
+
onWindowFocus() {
|
|
12122
|
+
this.state.isFocused = true;
|
|
12123
|
+
this.state.isIdle = false;
|
|
12124
|
+
this.startIdleTimer();
|
|
12125
|
+
this.syncActiveWindow();
|
|
12126
|
+
}
|
|
12127
|
+
onWindowBlur() {
|
|
12128
|
+
this.state.isFocused = false;
|
|
12129
|
+
this.syncActiveWindow();
|
|
12130
|
+
}
|
|
12131
|
+
// --- Public API -------------------------------------------------------------
|
|
11946
12132
|
pause() {
|
|
11947
12133
|
this.sendTimeUpdate();
|
|
11948
|
-
this.
|
|
12134
|
+
this.clearTrackingInterval();
|
|
12135
|
+
this.clearIdleTimer();
|
|
12136
|
+
// Stop accounting until resumed.
|
|
12137
|
+
this.state.accumulatedMs = 0;
|
|
12138
|
+
this.state.activeSince = null;
|
|
11949
12139
|
}
|
|
11950
12140
|
resume() {
|
|
12141
|
+
// Treat resume as a fresh, present start so a stale idle flag can't keep
|
|
12142
|
+
// the tracker from counting.
|
|
12143
|
+
this.state.isIdle = false;
|
|
11951
12144
|
this.startTracking();
|
|
12145
|
+
this.startIdleTimer();
|
|
11952
12146
|
}
|
|
11953
12147
|
destroy() {
|
|
11954
12148
|
this.sendTimeUpdate();
|
|
11955
|
-
this.
|
|
12149
|
+
this.clearTrackingInterval();
|
|
12150
|
+
this.clearIdleTimer();
|
|
12151
|
+
this.detachActivityListeners();
|
|
11956
12152
|
if (this.state.routeUnsubscribe) {
|
|
11957
12153
|
this.state.routeUnsubscribe();
|
|
11958
12154
|
this.state.routeUnsubscribe = null;
|
|
@@ -11964,6 +12160,10 @@ class TimeTracker {
|
|
|
11964
12160
|
getTimeSpentSinceLastReset() {
|
|
11965
12161
|
return this.getTimeSpent();
|
|
11966
12162
|
}
|
|
12163
|
+
/** Whether the user is currently considered active (present & interacting). */
|
|
12164
|
+
getIsActive() {
|
|
12165
|
+
return this.isActive();
|
|
12166
|
+
}
|
|
11967
12167
|
}
|
|
11968
12168
|
|
|
11969
12169
|
function useTimeTracker(config) {
|