@zeniai/client-epic-state 5.0.14 → 5.0.15

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.
@@ -22,10 +22,13 @@ const ACTIVITY_EVENTS = [
22
22
  'touchstart',
23
23
  'click',
24
24
  ];
25
- /** Debounce interval for activity events (ms). Also reused as the cadence
26
- * for idle checks and the countdown ticker — all are 1-second taps. */
25
+ /** Debounce interval for activity events. Also reused as the cadence for
26
+ * idle checks and the countdown ticker — all are 1-second taps. */
27
27
  const ACTIVITY_DEBOUNCE_MS = 1000;
28
- const MS_PER_MINUTE = 60 * ACTIVITY_DEBOUNCE_MS;
28
+ /** Pure unit-conversion constants. Kept separate from ACTIVITY_DEBOUNCE_MS
29
+ * so changing the debounce cadence cannot silently change time math. */
30
+ const MS_PER_SECOND = 1000;
31
+ const MS_PER_MINUTE = 60 * MS_PER_SECOND;
29
32
  /** Name of the BroadcastChannel used for cross-tab activity sync. */
30
33
  const BROADCAST_CHANNEL_NAME = 'zeni-session-activity';
31
34
  class SessionManager {
@@ -160,9 +163,21 @@ class SessionManager {
160
163
  return;
161
164
  }
162
165
  if (document.visibilityState === 'visible') {
166
+ // Sleep/wake case: before resetting activity for the "user switched
167
+ // tabs" path, check whether the full grace period has already elapsed.
168
+ // If so, log out immediately rather than handing back another window.
169
+ const idleMs = Date.now() - this.lastActivityTime;
170
+ const fullGracePeriodMs = this.config.idleTimeoutMinutes * MS_PER_MINUTE +
171
+ this.config.warningDurationSeconds * MS_PER_SECOND;
172
+ if (idleMs >= fullGracePeriodMs) {
173
+ this.secondsRemaining = 0;
174
+ this.stop();
175
+ this.callbacks?.onAutoLogout();
176
+ return;
177
+ }
163
178
  if (this.warningActive) {
164
179
  // Tab was hidden while countdown was running — check if it already expired.
165
- const elapsed = Math.floor((Date.now() - this.warningStartTime) / ACTIVITY_DEBOUNCE_MS);
180
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / MS_PER_SECOND);
166
181
  if (elapsed >= this.config.warningDurationSeconds) {
167
182
  this.secondsRemaining = 0;
168
183
  this.stop();
@@ -186,6 +201,18 @@ class SessionManager {
186
201
  }
187
202
  const idleMs = Date.now() - this.lastActivityTime;
188
203
  const idleTimeoutMs = this.config.idleTimeoutMinutes * MS_PER_MINUTE;
204
+ const warningDurationMs = this.config.warningDurationSeconds * MS_PER_SECOND;
205
+ const fullGracePeriodMs = idleTimeoutMs + warningDurationMs;
206
+ // System-sleep / long-idle case: if the full grace period (idle timeout +
207
+ // warning countdown) has already elapsed, skip the popup and log out
208
+ // immediately. This catches the wake-from-sleep case where JS was paused
209
+ // long enough that the user has effectively missed both windows.
210
+ if (idleMs >= fullGracePeriodMs) {
211
+ this.secondsRemaining = 0;
212
+ this.stop();
213
+ this.callbacks?.onAutoLogout();
214
+ return;
215
+ }
189
216
  if (idleMs >= idleTimeoutMs) {
190
217
  this.startWarning();
191
218
  }
@@ -201,7 +228,7 @@ class SessionManager {
201
228
  // Browsers throttle setInterval when the tab is hidden, but each tick
202
229
  // recomputes from the real start time so no seconds are "lost".
203
230
  this.countdownInterval = setInterval(() => {
204
- const elapsed = Math.floor((Date.now() - this.warningStartTime) / ACTIVITY_DEBOUNCE_MS);
231
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / MS_PER_SECOND);
205
232
  const remaining = this.config.warningDurationSeconds - elapsed;
206
233
  if (remaining <= 0) {
207
234
  this.secondsRemaining = 0;
@@ -4,7 +4,7 @@
4
4
  export interface SessionConfig {
5
5
  /** Minutes between heartbeat API calls while user is active. Default: 10 */
6
6
  heartbeatIntervalMinutes: number;
7
- /** Minutes of inactivity before the warning popup appears. Default: 30 */
7
+ /** Minutes of inactivity before the warning popup appears. Default: 60 */
8
8
  idleTimeoutMinutes: number;
9
9
  /** Whether auto-logout is enabled. Default: true */
10
10
  isAutoLogoutEnabled: boolean;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_SESSION_CONFIG = void 0;
7
7
  exports.DEFAULT_SESSION_CONFIG = {
8
8
  heartbeatIntervalMinutes: 10,
9
- idleTimeoutMinutes: 30,
9
+ idleTimeoutMinutes: 60,
10
10
  isAutoLogoutEnabled: true,
11
11
  warningDurationSeconds: 60,
12
12
  };
@@ -19,10 +19,13 @@ const ACTIVITY_EVENTS = [
19
19
  'touchstart',
20
20
  'click',
21
21
  ];
22
- /** Debounce interval for activity events (ms). Also reused as the cadence
23
- * for idle checks and the countdown ticker — all are 1-second taps. */
22
+ /** Debounce interval for activity events. Also reused as the cadence for
23
+ * idle checks and the countdown ticker — all are 1-second taps. */
24
24
  const ACTIVITY_DEBOUNCE_MS = 1000;
25
- const MS_PER_MINUTE = 60 * ACTIVITY_DEBOUNCE_MS;
25
+ /** Pure unit-conversion constants. Kept separate from ACTIVITY_DEBOUNCE_MS
26
+ * so changing the debounce cadence cannot silently change time math. */
27
+ const MS_PER_SECOND = 1000;
28
+ const MS_PER_MINUTE = 60 * MS_PER_SECOND;
26
29
  /** Name of the BroadcastChannel used for cross-tab activity sync. */
27
30
  const BROADCAST_CHANNEL_NAME = 'zeni-session-activity';
28
31
  export class SessionManager {
@@ -157,9 +160,21 @@ export class SessionManager {
157
160
  return;
158
161
  }
159
162
  if (document.visibilityState === 'visible') {
163
+ // Sleep/wake case: before resetting activity for the "user switched
164
+ // tabs" path, check whether the full grace period has already elapsed.
165
+ // If so, log out immediately rather than handing back another window.
166
+ const idleMs = Date.now() - this.lastActivityTime;
167
+ const fullGracePeriodMs = this.config.idleTimeoutMinutes * MS_PER_MINUTE +
168
+ this.config.warningDurationSeconds * MS_PER_SECOND;
169
+ if (idleMs >= fullGracePeriodMs) {
170
+ this.secondsRemaining = 0;
171
+ this.stop();
172
+ this.callbacks?.onAutoLogout();
173
+ return;
174
+ }
160
175
  if (this.warningActive) {
161
176
  // Tab was hidden while countdown was running — check if it already expired.
162
- const elapsed = Math.floor((Date.now() - this.warningStartTime) / ACTIVITY_DEBOUNCE_MS);
177
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / MS_PER_SECOND);
163
178
  if (elapsed >= this.config.warningDurationSeconds) {
164
179
  this.secondsRemaining = 0;
165
180
  this.stop();
@@ -183,6 +198,18 @@ export class SessionManager {
183
198
  }
184
199
  const idleMs = Date.now() - this.lastActivityTime;
185
200
  const idleTimeoutMs = this.config.idleTimeoutMinutes * MS_PER_MINUTE;
201
+ const warningDurationMs = this.config.warningDurationSeconds * MS_PER_SECOND;
202
+ const fullGracePeriodMs = idleTimeoutMs + warningDurationMs;
203
+ // System-sleep / long-idle case: if the full grace period (idle timeout +
204
+ // warning countdown) has already elapsed, skip the popup and log out
205
+ // immediately. This catches the wake-from-sleep case where JS was paused
206
+ // long enough that the user has effectively missed both windows.
207
+ if (idleMs >= fullGracePeriodMs) {
208
+ this.secondsRemaining = 0;
209
+ this.stop();
210
+ this.callbacks?.onAutoLogout();
211
+ return;
212
+ }
186
213
  if (idleMs >= idleTimeoutMs) {
187
214
  this.startWarning();
188
215
  }
@@ -198,7 +225,7 @@ export class SessionManager {
198
225
  // Browsers throttle setInterval when the tab is hidden, but each tick
199
226
  // recomputes from the real start time so no seconds are "lost".
200
227
  this.countdownInterval = setInterval(() => {
201
- const elapsed = Math.floor((Date.now() - this.warningStartTime) / ACTIVITY_DEBOUNCE_MS);
228
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / MS_PER_SECOND);
202
229
  const remaining = this.config.warningDurationSeconds - elapsed;
203
230
  if (remaining <= 0) {
204
231
  this.secondsRemaining = 0;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const DEFAULT_SESSION_CONFIG = {
5
5
  heartbeatIntervalMinutes: 10,
6
- idleTimeoutMinutes: 30,
6
+ idleTimeoutMinutes: 60,
7
7
  isAutoLogoutEnabled: true,
8
8
  warningDurationSeconds: 60,
9
9
  };