@zeniai/client-epic-state 5.0.12-beta1ND → 5.0.12-beta2ND

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.
@@ -27,6 +27,8 @@ export declare class SessionManager {
27
27
  private running;
28
28
  private warningActive;
29
29
  private secondsRemaining;
30
+ /** Wall-clock timestamp when the warning countdown began (for background-tab accuracy). */
31
+ private warningStartTime;
30
32
  /** Cross-tab activity sync */
31
33
  private broadcastChannel;
32
34
  /** Bound handlers for event listener cleanup. */
@@ -44,6 +44,8 @@ class SessionManager {
44
44
  this.running = false;
45
45
  this.warningActive = false;
46
46
  this.secondsRemaining = 0;
47
+ /** Wall-clock timestamp when the warning countdown began (for background-tab accuracy). */
48
+ this.warningStartTime = 0;
47
49
  /** Cross-tab activity sync */
48
50
  this.broadcastChannel = null;
49
51
  /** Bound handlers for event listener cleanup. */
@@ -163,8 +165,19 @@ class SessionManager {
163
165
  if (!this.running) {
164
166
  return;
165
167
  }
166
- // Industry-standard idle detection: resetting on tab focus is expected.
167
168
  if (document.visibilityState === 'visible') {
169
+ if (this.warningActive) {
170
+ // Tab was hidden while countdown was running — check if it already expired.
171
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / 1000);
172
+ if (elapsed >= this.config.warningDurationSeconds) {
173
+ this.secondsRemaining = 0;
174
+ this.clearTimer('countdownInterval');
175
+ this.warningActive = false;
176
+ this.callbacks?.onAutoLogout();
177
+ }
178
+ return;
179
+ }
180
+ // Industry-standard idle detection: resetting on tab focus is expected.
168
181
  this.registerActivity(Date.now());
169
182
  this.checkIdle();
170
183
  }
@@ -186,18 +199,25 @@ class SessionManager {
186
199
  }
187
200
  startWarning() {
188
201
  this.warningActive = true;
202
+ this.warningStartTime = Date.now();
189
203
  this.secondsRemaining = this.config.warningDurationSeconds;
190
204
  // Don't extend session while warning is showing.
191
205
  this.clearTimer('heartbeatInterval');
192
206
  this.callbacks?.onWarningStart(this.secondsRemaining);
207
+ // Use wall-clock elapsed time so countdown stays accurate in background tabs.
208
+ // Browsers throttle setInterval when the tab is hidden, but each tick
209
+ // recomputes from the real start time so no seconds are "lost".
193
210
  this.countdownInterval = setInterval(() => {
194
- this.secondsRemaining -= 1;
195
- if (this.secondsRemaining <= 0) {
211
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / 1000);
212
+ const remaining = this.config.warningDurationSeconds - elapsed;
213
+ if (remaining <= 0) {
214
+ this.secondsRemaining = 0;
196
215
  this.clearTimer('countdownInterval');
197
216
  this.warningActive = false;
198
217
  this.callbacks?.onAutoLogout();
199
218
  }
200
219
  else {
220
+ this.secondsRemaining = remaining;
201
221
  this.callbacks?.onWarningTick(this.secondsRemaining);
202
222
  }
203
223
  }, 1000);
@@ -41,6 +41,8 @@ export class SessionManager {
41
41
  this.running = false;
42
42
  this.warningActive = false;
43
43
  this.secondsRemaining = 0;
44
+ /** Wall-clock timestamp when the warning countdown began (for background-tab accuracy). */
45
+ this.warningStartTime = 0;
44
46
  /** Cross-tab activity sync */
45
47
  this.broadcastChannel = null;
46
48
  /** Bound handlers for event listener cleanup. */
@@ -160,8 +162,19 @@ export class SessionManager {
160
162
  if (!this.running) {
161
163
  return;
162
164
  }
163
- // Industry-standard idle detection: resetting on tab focus is expected.
164
165
  if (document.visibilityState === 'visible') {
166
+ if (this.warningActive) {
167
+ // Tab was hidden while countdown was running — check if it already expired.
168
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / 1000);
169
+ if (elapsed >= this.config.warningDurationSeconds) {
170
+ this.secondsRemaining = 0;
171
+ this.clearTimer('countdownInterval');
172
+ this.warningActive = false;
173
+ this.callbacks?.onAutoLogout();
174
+ }
175
+ return;
176
+ }
177
+ // Industry-standard idle detection: resetting on tab focus is expected.
165
178
  this.registerActivity(Date.now());
166
179
  this.checkIdle();
167
180
  }
@@ -183,18 +196,25 @@ export class SessionManager {
183
196
  }
184
197
  startWarning() {
185
198
  this.warningActive = true;
199
+ this.warningStartTime = Date.now();
186
200
  this.secondsRemaining = this.config.warningDurationSeconds;
187
201
  // Don't extend session while warning is showing.
188
202
  this.clearTimer('heartbeatInterval');
189
203
  this.callbacks?.onWarningStart(this.secondsRemaining);
204
+ // Use wall-clock elapsed time so countdown stays accurate in background tabs.
205
+ // Browsers throttle setInterval when the tab is hidden, but each tick
206
+ // recomputes from the real start time so no seconds are "lost".
190
207
  this.countdownInterval = setInterval(() => {
191
- this.secondsRemaining -= 1;
192
- if (this.secondsRemaining <= 0) {
208
+ const elapsed = Math.floor((Date.now() - this.warningStartTime) / 1000);
209
+ const remaining = this.config.warningDurationSeconds - elapsed;
210
+ if (remaining <= 0) {
211
+ this.secondsRemaining = 0;
193
212
  this.clearTimer('countdownInterval');
194
213
  this.warningActive = false;
195
214
  this.callbacks?.onAutoLogout();
196
215
  }
197
216
  else {
217
+ this.secondsRemaining = remaining;
198
218
  this.callbacks?.onWarningTick(this.secondsRemaining);
199
219
  }
200
220
  }, 1000);