@iblai/web-utils 1.11.12 → 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 +56 -5
- package/dist/index.esm.js +187 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +187 -12
- 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
|
@@ -3,27 +3,78 @@ export interface TimeTrackerConfig {
|
|
|
3
3
|
onTimeUpdate: (url: string, timeSpent: number) => void;
|
|
4
4
|
getCurrentUrl: () => string;
|
|
5
5
|
onRouteChange?: (callback: () => void) => () => void;
|
|
6
|
+
/**
|
|
7
|
+
* Seconds of no user interaction (mouse, keyboard, touch, scroll) after
|
|
8
|
+
* which the user is considered idle and time tracking pauses. Set to 0 to
|
|
9
|
+
* disable idle detection. Defaults to 120 (2 minutes).
|
|
10
|
+
*/
|
|
11
|
+
idleTimeoutSeconds?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Pause tracking while the page/tab is hidden (Page Visibility API).
|
|
14
|
+
* Defaults to true.
|
|
15
|
+
*/
|
|
16
|
+
pauseOnHidden?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Pause tracking while the window loses focus (e.g. user switched to another
|
|
19
|
+
* application or window). Defaults to true.
|
|
20
|
+
*/
|
|
21
|
+
pauseOnBlur?: boolean;
|
|
6
22
|
}
|
|
7
23
|
export interface TimeTrackerState {
|
|
8
24
|
currentUrl: string;
|
|
9
|
-
startTime: number;
|
|
10
25
|
intervalId: number | null;
|
|
11
26
|
routeUnsubscribe: (() => void) | null;
|
|
27
|
+
/** Active time (ms) banked since the last reset. */
|
|
28
|
+
accumulatedMs: number;
|
|
29
|
+
/** Timestamp the current active window started, or null while inactive. */
|
|
30
|
+
activeSince: number | null;
|
|
31
|
+
isVisible: boolean;
|
|
32
|
+
isFocused: boolean;
|
|
33
|
+
isIdle: boolean;
|
|
34
|
+
idleTimerId: number | null;
|
|
12
35
|
}
|
|
13
36
|
export declare class TimeTracker {
|
|
14
37
|
private state;
|
|
15
38
|
private config;
|
|
39
|
+
private readonly idleTimeoutMs;
|
|
40
|
+
private readonly pauseOnHidden;
|
|
41
|
+
private readonly pauseOnBlur;
|
|
42
|
+
private readonly idleDetectionEnabled;
|
|
43
|
+
private lastActivityAt;
|
|
44
|
+
private readonly handleActivity;
|
|
45
|
+
private readonly handleVisibilityChange;
|
|
46
|
+
private readonly handleWindowFocus;
|
|
47
|
+
private readonly handleWindowBlur;
|
|
16
48
|
constructor(config: TimeTrackerConfig);
|
|
17
49
|
private initialize;
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
50
|
+
/** Whether time should currently be counted. */
|
|
51
|
+
private isActive;
|
|
52
|
+
/**
|
|
53
|
+
* Reconcile the active window with the current activity signals: open a new
|
|
54
|
+
* window when the user becomes active, or bank the elapsed time when they
|
|
55
|
+
* become inactive.
|
|
56
|
+
*/
|
|
57
|
+
private syncActiveWindow;
|
|
21
58
|
private getTimeSpent;
|
|
59
|
+
private resetTimer;
|
|
60
|
+
private startTracking;
|
|
61
|
+
private clearTrackingInterval;
|
|
22
62
|
private sendTimeUpdate;
|
|
23
63
|
private handleRouteChange;
|
|
64
|
+
private attachActivityListeners;
|
|
65
|
+
private detachActivityListeners;
|
|
66
|
+
private startIdleTimer;
|
|
67
|
+
private clearIdleTimer;
|
|
68
|
+
private markIdle;
|
|
69
|
+
private onActivity;
|
|
70
|
+
private onVisibilityChange;
|
|
71
|
+
private onWindowFocus;
|
|
72
|
+
private onWindowBlur;
|
|
24
73
|
pause(): void;
|
|
25
74
|
resume(): void;
|
|
26
75
|
destroy(): void;
|
|
27
76
|
getCurrentUrl(): string;
|
|
28
77
|
getTimeSpentSinceLastReset(): number;
|
|
78
|
+
/** Whether the user is currently considered active (present & interacting). */
|
|
79
|
+
getIsActive(): boolean;
|
|
29
80
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1101,7 +1101,7 @@ declare const isNode: () => string | false;
|
|
|
1101
1101
|
declare const isExpo: () => any;
|
|
1102
1102
|
declare const isTauri: () => boolean;
|
|
1103
1103
|
declare function isSafariBrowser(): boolean;
|
|
1104
|
-
declare const getPlatform: () => "
|
|
1104
|
+
declare const getPlatform: () => "react-native" | "web" | "node" | "unknown";
|
|
1105
1105
|
declare const safeRequire: (moduleName: string) => any;
|
|
1106
1106
|
declare const getNextNavigation: () => any;
|
|
1107
1107
|
|
|
@@ -1294,29 +1294,80 @@ interface TimeTrackerConfig {
|
|
|
1294
1294
|
onTimeUpdate: (url: string, timeSpent: number) => void;
|
|
1295
1295
|
getCurrentUrl: () => string;
|
|
1296
1296
|
onRouteChange?: (callback: () => void) => () => void;
|
|
1297
|
+
/**
|
|
1298
|
+
* Seconds of no user interaction (mouse, keyboard, touch, scroll) after
|
|
1299
|
+
* which the user is considered idle and time tracking pauses. Set to 0 to
|
|
1300
|
+
* disable idle detection. Defaults to 120 (2 minutes).
|
|
1301
|
+
*/
|
|
1302
|
+
idleTimeoutSeconds?: number;
|
|
1303
|
+
/**
|
|
1304
|
+
* Pause tracking while the page/tab is hidden (Page Visibility API).
|
|
1305
|
+
* Defaults to true.
|
|
1306
|
+
*/
|
|
1307
|
+
pauseOnHidden?: boolean;
|
|
1308
|
+
/**
|
|
1309
|
+
* Pause tracking while the window loses focus (e.g. user switched to another
|
|
1310
|
+
* application or window). Defaults to true.
|
|
1311
|
+
*/
|
|
1312
|
+
pauseOnBlur?: boolean;
|
|
1297
1313
|
}
|
|
1298
1314
|
interface TimeTrackerState {
|
|
1299
1315
|
currentUrl: string;
|
|
1300
|
-
startTime: number;
|
|
1301
1316
|
intervalId: number | null;
|
|
1302
1317
|
routeUnsubscribe: (() => void) | null;
|
|
1318
|
+
/** Active time (ms) banked since the last reset. */
|
|
1319
|
+
accumulatedMs: number;
|
|
1320
|
+
/** Timestamp the current active window started, or null while inactive. */
|
|
1321
|
+
activeSince: number | null;
|
|
1322
|
+
isVisible: boolean;
|
|
1323
|
+
isFocused: boolean;
|
|
1324
|
+
isIdle: boolean;
|
|
1325
|
+
idleTimerId: number | null;
|
|
1303
1326
|
}
|
|
1304
1327
|
declare class TimeTracker {
|
|
1305
1328
|
private state;
|
|
1306
1329
|
private config;
|
|
1330
|
+
private readonly idleTimeoutMs;
|
|
1331
|
+
private readonly pauseOnHidden;
|
|
1332
|
+
private readonly pauseOnBlur;
|
|
1333
|
+
private readonly idleDetectionEnabled;
|
|
1334
|
+
private lastActivityAt;
|
|
1335
|
+
private readonly handleActivity;
|
|
1336
|
+
private readonly handleVisibilityChange;
|
|
1337
|
+
private readonly handleWindowFocus;
|
|
1338
|
+
private readonly handleWindowBlur;
|
|
1307
1339
|
constructor(config: TimeTrackerConfig);
|
|
1308
1340
|
private initialize;
|
|
1309
|
-
|
|
1310
|
-
private
|
|
1311
|
-
|
|
1341
|
+
/** Whether time should currently be counted. */
|
|
1342
|
+
private isActive;
|
|
1343
|
+
/**
|
|
1344
|
+
* Reconcile the active window with the current activity signals: open a new
|
|
1345
|
+
* window when the user becomes active, or bank the elapsed time when they
|
|
1346
|
+
* become inactive.
|
|
1347
|
+
*/
|
|
1348
|
+
private syncActiveWindow;
|
|
1312
1349
|
private getTimeSpent;
|
|
1350
|
+
private resetTimer;
|
|
1351
|
+
private startTracking;
|
|
1352
|
+
private clearTrackingInterval;
|
|
1313
1353
|
private sendTimeUpdate;
|
|
1314
1354
|
private handleRouteChange;
|
|
1355
|
+
private attachActivityListeners;
|
|
1356
|
+
private detachActivityListeners;
|
|
1357
|
+
private startIdleTimer;
|
|
1358
|
+
private clearIdleTimer;
|
|
1359
|
+
private markIdle;
|
|
1360
|
+
private onActivity;
|
|
1361
|
+
private onVisibilityChange;
|
|
1362
|
+
private onWindowFocus;
|
|
1363
|
+
private onWindowBlur;
|
|
1315
1364
|
pause(): void;
|
|
1316
1365
|
resume(): void;
|
|
1317
1366
|
destroy(): void;
|
|
1318
1367
|
getCurrentUrl(): string;
|
|
1319
1368
|
getTimeSpentSinceLastReset(): number;
|
|
1369
|
+
/** Whether the user is currently considered active (present & interacting). */
|
|
1370
|
+
getIsActive(): boolean;
|
|
1320
1371
|
}
|
|
1321
1372
|
|
|
1322
1373
|
interface UseTimeTrackerConfig {
|
package/dist/index.esm.js
CHANGED
|
@@ -11894,46 +11894,117 @@ const selectIsReasoning = (state) => state.chatSliceShared.currentStreamingMessa
|
|
|
11894
11894
|
// NEW: Get streaming tool calls
|
|
11895
11895
|
const selectStreamingToolCalls = (state) => state.chatSliceShared.currentStreamingMessage.toolCalls;
|
|
11896
11896
|
|
|
11897
|
+
const DEFAULT_IDLE_TIMEOUT_SECONDS = 120;
|
|
11898
|
+
// Minimum gap between idle-timer resets. Interaction events like mousemove fire
|
|
11899
|
+
// rapidly; there's no need to restart the (minutes-long) idle countdown more
|
|
11900
|
+
// than once per second.
|
|
11901
|
+
const ACTIVITY_THROTTLE_MS = 1000;
|
|
11902
|
+
// User interactions that count as activity and keep the user "present".
|
|
11903
|
+
const ACTIVITY_EVENTS = [
|
|
11904
|
+
"mousemove",
|
|
11905
|
+
"mousedown",
|
|
11906
|
+
"keydown",
|
|
11907
|
+
"wheel",
|
|
11908
|
+
"scroll",
|
|
11909
|
+
"touchstart",
|
|
11910
|
+
"pointerdown",
|
|
11911
|
+
];
|
|
11897
11912
|
class TimeTracker {
|
|
11898
11913
|
constructor(config) {
|
|
11914
|
+
var _a, _b, _c;
|
|
11899
11915
|
this.state = {
|
|
11900
11916
|
currentUrl: "",
|
|
11901
|
-
startTime: 0,
|
|
11902
11917
|
intervalId: null,
|
|
11903
11918
|
routeUnsubscribe: null,
|
|
11919
|
+
accumulatedMs: 0,
|
|
11920
|
+
activeSince: null,
|
|
11921
|
+
isVisible: true,
|
|
11922
|
+
isFocused: true,
|
|
11923
|
+
isIdle: false,
|
|
11924
|
+
idleTimerId: null,
|
|
11904
11925
|
};
|
|
11926
|
+
// Timestamp of the last activity that reset the idle timer (for throttling).
|
|
11927
|
+
this.lastActivityAt = 0;
|
|
11928
|
+
// Bound listeners kept so they can be removed on destroy.
|
|
11929
|
+
this.handleActivity = () => this.onActivity();
|
|
11930
|
+
this.handleVisibilityChange = () => this.onVisibilityChange();
|
|
11931
|
+
this.handleWindowFocus = () => this.onWindowFocus();
|
|
11932
|
+
this.handleWindowBlur = () => this.onWindowBlur();
|
|
11905
11933
|
this.config = config;
|
|
11934
|
+
this.idleTimeoutMs =
|
|
11935
|
+
((_a = config.idleTimeoutSeconds) !== null && _a !== void 0 ? _a : DEFAULT_IDLE_TIMEOUT_SECONDS) * 1000;
|
|
11936
|
+
this.pauseOnHidden = (_b = config.pauseOnHidden) !== null && _b !== void 0 ? _b : true;
|
|
11937
|
+
this.pauseOnBlur = (_c = config.pauseOnBlur) !== null && _c !== void 0 ? _c : true;
|
|
11938
|
+
this.idleDetectionEnabled =
|
|
11939
|
+
this.idleTimeoutMs > 0 &&
|
|
11940
|
+
typeof window !== "undefined" &&
|
|
11941
|
+
typeof document !== "undefined";
|
|
11906
11942
|
this.initialize();
|
|
11907
11943
|
}
|
|
11908
11944
|
initialize() {
|
|
11909
11945
|
this.state.currentUrl = this.config.getCurrentUrl();
|
|
11946
|
+
// Seed activity signals from the current environment.
|
|
11947
|
+
if (this.pauseOnHidden && typeof document !== "undefined") {
|
|
11948
|
+
this.state.isVisible = !document.hidden;
|
|
11949
|
+
}
|
|
11950
|
+
this.state.isFocused = true;
|
|
11951
|
+
this.state.isIdle = false;
|
|
11952
|
+
this.attachActivityListeners();
|
|
11910
11953
|
this.startTracking();
|
|
11954
|
+
this.startIdleTimer();
|
|
11911
11955
|
if (this.config.onRouteChange) {
|
|
11912
11956
|
this.state.routeUnsubscribe = this.config.onRouteChange(() => {
|
|
11913
11957
|
this.handleRouteChange();
|
|
11914
11958
|
});
|
|
11915
11959
|
}
|
|
11916
11960
|
}
|
|
11961
|
+
// --- Active time accounting -------------------------------------------------
|
|
11962
|
+
/** Whether time should currently be counted. */
|
|
11963
|
+
isActive() {
|
|
11964
|
+
return this.state.isVisible && this.state.isFocused && !this.state.isIdle;
|
|
11965
|
+
}
|
|
11966
|
+
/**
|
|
11967
|
+
* Reconcile the active window with the current activity signals: open a new
|
|
11968
|
+
* window when the user becomes active, or bank the elapsed time when they
|
|
11969
|
+
* become inactive.
|
|
11970
|
+
*/
|
|
11971
|
+
syncActiveWindow() {
|
|
11972
|
+
const active = this.isActive();
|
|
11973
|
+
if (active && this.state.activeSince === null) {
|
|
11974
|
+
this.state.activeSince = Date.now();
|
|
11975
|
+
}
|
|
11976
|
+
else if (!active && this.state.activeSince !== null) {
|
|
11977
|
+
this.state.accumulatedMs += Date.now() - this.state.activeSince;
|
|
11978
|
+
this.state.activeSince = null;
|
|
11979
|
+
}
|
|
11980
|
+
}
|
|
11981
|
+
getTimeSpent() {
|
|
11982
|
+
let total = this.state.accumulatedMs;
|
|
11983
|
+
if (this.state.activeSince !== null) {
|
|
11984
|
+
total += Date.now() - this.state.activeSince;
|
|
11985
|
+
}
|
|
11986
|
+
return total;
|
|
11987
|
+
}
|
|
11988
|
+
resetTimer() {
|
|
11989
|
+
this.state.accumulatedMs = 0;
|
|
11990
|
+
this.state.activeSince = this.isActive() ? Date.now() : null;
|
|
11991
|
+
}
|
|
11917
11992
|
startTracking() {
|
|
11918
|
-
this.
|
|
11919
|
-
this.
|
|
11993
|
+
this.clearTrackingInterval();
|
|
11994
|
+
this.resetTimer();
|
|
11995
|
+
if (typeof window === "undefined")
|
|
11996
|
+
return;
|
|
11920
11997
|
this.state.intervalId = window.setInterval(() => {
|
|
11921
11998
|
this.sendTimeUpdate();
|
|
11922
11999
|
this.resetTimer();
|
|
11923
12000
|
}, this.config.intervalSeconds * 1000);
|
|
11924
12001
|
}
|
|
11925
|
-
|
|
12002
|
+
clearTrackingInterval() {
|
|
11926
12003
|
if (this.state.intervalId !== null) {
|
|
11927
12004
|
clearInterval(this.state.intervalId);
|
|
11928
12005
|
this.state.intervalId = null;
|
|
11929
12006
|
}
|
|
11930
12007
|
}
|
|
11931
|
-
resetTimer() {
|
|
11932
|
-
this.state.startTime = Date.now();
|
|
11933
|
-
}
|
|
11934
|
-
getTimeSpent() {
|
|
11935
|
-
return Date.now() - this.state.startTime;
|
|
11936
|
-
}
|
|
11937
12008
|
sendTimeUpdate() {
|
|
11938
12009
|
const timeSpent = this.getTimeSpent();
|
|
11939
12010
|
if (timeSpent > 0) {
|
|
@@ -11948,16 +12019,116 @@ class TimeTracker {
|
|
|
11948
12019
|
this.resetTimer();
|
|
11949
12020
|
}
|
|
11950
12021
|
}
|
|
12022
|
+
// --- Activity / inactivity detection ---------------------------------------
|
|
12023
|
+
attachActivityListeners() {
|
|
12024
|
+
if (typeof window !== "undefined" && window.addEventListener) {
|
|
12025
|
+
if (this.idleDetectionEnabled) {
|
|
12026
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
12027
|
+
window.addEventListener(event, this.handleActivity, {
|
|
12028
|
+
passive: true,
|
|
12029
|
+
});
|
|
12030
|
+
}
|
|
12031
|
+
}
|
|
12032
|
+
if (this.pauseOnBlur) {
|
|
12033
|
+
window.addEventListener("focus", this.handleWindowFocus);
|
|
12034
|
+
window.addEventListener("blur", this.handleWindowBlur);
|
|
12035
|
+
}
|
|
12036
|
+
}
|
|
12037
|
+
if (this.pauseOnHidden &&
|
|
12038
|
+
typeof document !== "undefined" &&
|
|
12039
|
+
document.addEventListener) {
|
|
12040
|
+
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
detachActivityListeners() {
|
|
12044
|
+
if (typeof window !== "undefined" && window.removeEventListener) {
|
|
12045
|
+
for (const event of ACTIVITY_EVENTS) {
|
|
12046
|
+
window.removeEventListener(event, this.handleActivity);
|
|
12047
|
+
}
|
|
12048
|
+
window.removeEventListener("focus", this.handleWindowFocus);
|
|
12049
|
+
window.removeEventListener("blur", this.handleWindowBlur);
|
|
12050
|
+
}
|
|
12051
|
+
if (typeof document !== "undefined" && document.removeEventListener) {
|
|
12052
|
+
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
|
12053
|
+
}
|
|
12054
|
+
}
|
|
12055
|
+
startIdleTimer() {
|
|
12056
|
+
if (!this.idleDetectionEnabled)
|
|
12057
|
+
return;
|
|
12058
|
+
this.clearIdleTimer();
|
|
12059
|
+
this.lastActivityAt = Date.now();
|
|
12060
|
+
this.state.idleTimerId = window.setTimeout(() => {
|
|
12061
|
+
this.markIdle();
|
|
12062
|
+
}, this.idleTimeoutMs);
|
|
12063
|
+
}
|
|
12064
|
+
clearIdleTimer() {
|
|
12065
|
+
if (this.state.idleTimerId !== null) {
|
|
12066
|
+
clearTimeout(this.state.idleTimerId);
|
|
12067
|
+
this.state.idleTimerId = null;
|
|
12068
|
+
}
|
|
12069
|
+
}
|
|
12070
|
+
markIdle() {
|
|
12071
|
+
if (this.state.isIdle)
|
|
12072
|
+
return;
|
|
12073
|
+
this.state.isIdle = true;
|
|
12074
|
+
this.syncActiveWindow();
|
|
12075
|
+
}
|
|
12076
|
+
onActivity() {
|
|
12077
|
+
// Returning from idle must always restart the countdown immediately.
|
|
12078
|
+
if (this.state.isIdle) {
|
|
12079
|
+
this.state.isIdle = false;
|
|
12080
|
+
this.syncActiveWindow();
|
|
12081
|
+
this.startIdleTimer();
|
|
12082
|
+
return;
|
|
12083
|
+
}
|
|
12084
|
+
// Otherwise throttle: no need to reset a minutes-long timer on every
|
|
12085
|
+
// mousemove.
|
|
12086
|
+
if (Date.now() - this.lastActivityAt < ACTIVITY_THROTTLE_MS)
|
|
12087
|
+
return;
|
|
12088
|
+
this.startIdleTimer();
|
|
12089
|
+
}
|
|
12090
|
+
onVisibilityChange() {
|
|
12091
|
+
if (typeof document === "undefined")
|
|
12092
|
+
return;
|
|
12093
|
+
this.state.isVisible = !document.hidden;
|
|
12094
|
+
if (this.state.isVisible) {
|
|
12095
|
+
// Returning to the tab counts as being present again.
|
|
12096
|
+
this.state.isIdle = false;
|
|
12097
|
+
this.startIdleTimer();
|
|
12098
|
+
}
|
|
12099
|
+
this.syncActiveWindow();
|
|
12100
|
+
}
|
|
12101
|
+
onWindowFocus() {
|
|
12102
|
+
this.state.isFocused = true;
|
|
12103
|
+
this.state.isIdle = false;
|
|
12104
|
+
this.startIdleTimer();
|
|
12105
|
+
this.syncActiveWindow();
|
|
12106
|
+
}
|
|
12107
|
+
onWindowBlur() {
|
|
12108
|
+
this.state.isFocused = false;
|
|
12109
|
+
this.syncActiveWindow();
|
|
12110
|
+
}
|
|
12111
|
+
// --- Public API -------------------------------------------------------------
|
|
11951
12112
|
pause() {
|
|
11952
12113
|
this.sendTimeUpdate();
|
|
11953
|
-
this.
|
|
12114
|
+
this.clearTrackingInterval();
|
|
12115
|
+
this.clearIdleTimer();
|
|
12116
|
+
// Stop accounting until resumed.
|
|
12117
|
+
this.state.accumulatedMs = 0;
|
|
12118
|
+
this.state.activeSince = null;
|
|
11954
12119
|
}
|
|
11955
12120
|
resume() {
|
|
12121
|
+
// Treat resume as a fresh, present start so a stale idle flag can't keep
|
|
12122
|
+
// the tracker from counting.
|
|
12123
|
+
this.state.isIdle = false;
|
|
11956
12124
|
this.startTracking();
|
|
12125
|
+
this.startIdleTimer();
|
|
11957
12126
|
}
|
|
11958
12127
|
destroy() {
|
|
11959
12128
|
this.sendTimeUpdate();
|
|
11960
|
-
this.
|
|
12129
|
+
this.clearTrackingInterval();
|
|
12130
|
+
this.clearIdleTimer();
|
|
12131
|
+
this.detachActivityListeners();
|
|
11961
12132
|
if (this.state.routeUnsubscribe) {
|
|
11962
12133
|
this.state.routeUnsubscribe();
|
|
11963
12134
|
this.state.routeUnsubscribe = null;
|
|
@@ -11969,6 +12140,10 @@ class TimeTracker {
|
|
|
11969
12140
|
getTimeSpentSinceLastReset() {
|
|
11970
12141
|
return this.getTimeSpent();
|
|
11971
12142
|
}
|
|
12143
|
+
/** Whether the user is currently considered active (present & interacting). */
|
|
12144
|
+
getIsActive() {
|
|
12145
|
+
return this.isActive();
|
|
12146
|
+
}
|
|
11972
12147
|
}
|
|
11973
12148
|
|
|
11974
12149
|
function useTimeTracker(config) {
|