@me2em/react 1.0.0-rc.2 → 1.0.0-rc.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@me2em/react",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "React bindings for the Me2eM protocol: identity lifecycle, seed phrase UX, hierarchical handles",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -43,7 +43,11 @@ export interface UseSessionResult {
43
43
  * Supports auto-renewal: when `autoRenew` is true (default), a timer
44
44
  * fires at half the session TTL to create a fresh session with a new
45
45
  * jti (the previous token remains valid until its own expiry, but the
46
- * lineage identity is preserved through `handle`).
46
+ * lineage identity is preserved through `handle`). Background tabs
47
+ * throttle (clamped wakeups) or fully suspend timers, so on return to
48
+ * visibility the hook additionally renews proactively when the session
49
+ * is already past its half-life point (BL-51) — the overdue timer path
50
+ * alone could leave the first user actions racing an expired session.
47
51
  *
48
52
  * On unmount, all timers are cleaned up to prevent state updates after
49
53
  * the component is gone.
@@ -149,6 +153,32 @@ export function useSession(
149
153
  }
150
154
  }, [handle, renew]);
151
155
 
156
+ // BL-51: background tabs throttle (clamped wakeups) or fully
157
+ // suspend timers (Memory Saver, mobile backgrounds), so the half-life
158
+ // setTimeout alone can fire late — or not at all until the tab
159
+ // resumes. When the document becomes visible again and the session
160
+ // is already past its half-life point (expiresAt − ttl/2, the same
161
+ // threshold the timer uses; expired is a superset), renew
162
+ // proactively. renew() clears and reschedules the timer, so the
163
+ // overdue timer cannot double-renew. autoRenew:false opts out
164
+ // entirely, exactly like the timer path.
165
+ useEffect(() => {
166
+ if (!autoRenew || typeof document === 'undefined') return;
167
+ const onVisibilityChange = () => {
168
+ if (document.visibilityState !== 'visible') return;
169
+ const s = session;
170
+ if (!s) return;
171
+ const halfLifePoint = (s.expiresAt - optionsRef.current.ttl / 2) * 1000;
172
+ if (Date.now() >= halfLifePoint) {
173
+ renew();
174
+ }
175
+ };
176
+ document.addEventListener('visibilitychange', onVisibilityChange);
177
+ return () => {
178
+ document.removeEventListener('visibilitychange', onVisibilityChange);
179
+ };
180
+ }, [autoRenew, renew, session]);
181
+
152
182
  return {
153
183
  session,
154
184
  isLoaded,