@entropy-softworks/ui 2026.8.17 → 2026.8.19

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.
@@ -12,11 +12,32 @@ export interface UseAutoLockOptions {
12
12
  /** Idle threshold in milliseconds. Defaults to 10 minutes. */
13
13
  idleLockMs?: number;
14
14
  }
15
+ export interface UseAutoLockResult {
16
+ /**
17
+ * Call on any real user interaction to reset the idle clock. Required
18
+ * on native: unlike web (which listens on `window` for
19
+ * mousemove/touchstart/scroll/keydown directly), RN has no global
20
+ * "any touch happened anywhere" event — a touch inside a `Pressable`,
21
+ * `ScrollView`, etc. never reaches `window`, because there is no
22
+ * `window` to reach. Without a caller wiring this up (e.g. a capture-
23
+ * phase `onStartShouldSetResponderCapture` at the app root, returning
24
+ * `false` so it never actually claims the touch), the idle timer is
25
+ * armed once at mount and never reset — the session locks exactly
26
+ * `idleLockMs` after mount/foreground REGARDLESS of how actively the
27
+ * app is being used. Confirmed for real: a vault mid-active-use lost
28
+ * its key mid-session, and every item silently failed to decrypt
29
+ * (`Promise.allSettled` in the caller's list-fetch treats a decrypt
30
+ * failure as "drop this item", not "the vault relocked"), which reads
31
+ * to the user as vault items simply vanishing — no error, no visible
32
+ * cause, on 2026-08-17.
33
+ */
34
+ notifyActivity: () => void;
35
+ }
15
36
  /**
16
37
  * Auto-lock a session after a period of inactivity. Generic over the
17
38
  * lock predicate — vault wires `KeyManager.isUnlocked` / `KeyManager.lock`,
18
39
  * other apps wire their own state. Web uses an idle-timer only; native
19
40
  * additionally locks immediately when the app goes to the background.
20
41
  */
21
- export declare function useAutoLock(options: UseAutoLockOptions): void;
42
+ export declare function useAutoLock(options: UseAutoLockOptions): UseAutoLockResult;
22
43
  //# sourceMappingURL=useAutoLock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAutoLock.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoLock.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAmF7D"}
1
+ {"version":3,"file":"useAutoLock.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoLock.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;;;;;;OAiBG;IACH,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CA2F1E"}
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
2
2
  import { AppState as RNAppState, Platform } from "react-native";
3
3
  const DEFAULT_IDLE_LOCK_MS = 10 * 60 * 1000;
4
4
  /**
@@ -9,6 +9,7 @@ const DEFAULT_IDLE_LOCK_MS = 10 * 60 * 1000;
9
9
  */
10
10
  export function useAutoLock(options) {
11
11
  const { isUnlocked, onLock, isEnabled, idleLockMs = DEFAULT_IDLE_LOCK_MS } = options;
12
+ const onActivityRef = useRef(() => { });
12
13
  useEffect(() => {
13
14
  let idleTimer = null;
14
15
  let lastActivityAt = Date.now();
@@ -43,6 +44,7 @@ export function useAutoLock(options) {
43
44
  lastActivityAt = Date.now();
44
45
  armIdleTimer();
45
46
  };
47
+ onActivityRef.current = onActivity;
46
48
  // On web, RN Web's AppState mirrors document visibility — tab-hidden
47
49
  // becomes 'background', which would otherwise fire an immediate lock.
48
50
  // Only treat 'background' as a lock signal on real native platforms.
@@ -81,5 +83,9 @@ export function useAutoLock(options) {
81
83
  };
82
84
  // eslint-disable-next-line react-hooks/exhaustive-deps
83
85
  }, []);
86
+ const notifyActivity = useCallback(() => {
87
+ onActivityRef.current();
88
+ }, []);
89
+ return { notifyActivity };
84
90
  }
85
91
  //# sourceMappingURL=useAutoLock.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useAutoLock.js","sourceRoot":"","sources":["../../src/hooks/useAutoLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAiB5C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC;IAErF,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAyC,IAAI,CAAC;QAC3D,IAAI,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAC5C,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChB,IAAI,OAAO,IAAI,UAAU,EAAE,EAAE,CAAC;oBAC5B,MAAM,EAAE,CAAC;gBACX,CAAC;YACH,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrB,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;YACD,MAAM,SAAS,GAAG,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC,CAAC;YAC7D,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBACnB,aAAa,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,SAAS,GAAG,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,GAAG,EAAE;YACtB,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,YAAY,EAAE,CAAC;QACjB,CAAC,CAAC;QAEF,qEAAqE;QACrE,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE;YACvE,IAAI,SAAS,KAAK,YAAY,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBACxD,aAAa,EAAE,CAAC;YAClB,CAAC;iBAAM,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAClC,YAAY,EAAE,CAAC;YACjB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAmB,EAAE,CAAC;QAEpC,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC7D,MAAM,YAAY,GAAG,GAAG,EAAE;gBACxB,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;oBAC3C,YAAY,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC;YACF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC,CAAC;YAEpF,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAU,CAAC;YAC9F,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;gBAC/B,MAAM,CAAC,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1D,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QAED,YAAY,EAAE,CAAC;QAEf,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,EAAE,EAAE,CAAC;YACP,CAAC;YACD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;QACF,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"useAutoLock.js","sourceRoot":"","sources":["../../src/hooks/useAutoLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,oBAAoB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAuC5C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,GAAG,oBAAoB,EAAE,GAAG,OAAO,CAAC;IACrF,MAAM,aAAa,GAAG,MAAM,CAAa,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEnD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAyC,IAAI,CAAC;QAC3D,IAAI,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEhC,MAAM,aAAa,GAAG,GAAG,EAAE;YACzB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBAC5C,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChB,IAAI,OAAO,IAAI,UAAU,EAAE,EAAE,CAAC;oBAC5B,MAAM,EAAE,CAAC;gBACX,CAAC;YACH,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrB,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;YACD,MAAM,SAAS,GAAG,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc,CAAC,CAAC;YAC7D,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;gBACnB,aAAa,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,SAAS,GAAG,UAAU,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,GAAG,EAAE;YACtB,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,YAAY,EAAE,CAAC;QACjB,CAAC,CAAC;QACF,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC;QAEnC,qEAAqE;QACrE,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,EAAE;YACvE,IAAI,SAAS,KAAK,YAAY,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;gBACxD,aAAa,EAAE,CAAC;YAClB,CAAC;iBAAM,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;gBAClC,YAAY,EAAE,CAAC;YACjB,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAmB,EAAE,CAAC;QAEpC,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YAC7D,MAAM,YAAY,GAAG,GAAG,EAAE;gBACxB,IAAI,QAAQ,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;oBAC3C,YAAY,EAAE,CAAC;gBACjB,CAAC;YACH,CAAC,CAAC;YACF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;YAC5D,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC,CAAC;YAEpF,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,CAAU,CAAC;YAC9F,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;gBAC/B,MAAM,CAAC,gBAAgB,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1D,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;YACjE,CAAC;QACH,CAAC;QAED,YAAY,EAAE,CAAC;QAEf,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC1B,EAAE,EAAE,CAAC;YACP,CAAC;YACD,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC;QACF,uDAAuD;IACzD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,aAAa,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,cAAc,EAAE,CAAC;AAC5B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/utils/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAAgB,EACzB,SAAS,SAAQ,GAChB,OAAO,CAAC,QAAQ,CAAC,CAanB"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/utils/fetch.ts"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAAgB,EACzB,SAAS,SAAQ,GAChB,OAAO,CAAC,QAAQ,CAAC,CAanB"}
@@ -1,3 +1,39 @@
1
+ /**
2
+ * Combines multiple AbortSignals into one that aborts when any input does.
3
+ * Prefers the native `AbortSignal.any` where present (modern browsers);
4
+ * falls back to manual event-listener wiring where it isn't.
5
+ *
6
+ * That fallback is load-bearing, not defensive padding: React Native
7
+ * doesn't provide a native AbortController at all — `setUpXHR.js` installs
8
+ * one from the third-party `abort-controller` npm package (pinned at
9
+ * v3.0.0 in this tree), which predates `AbortSignal.any` entering the
10
+ * WHATWG spec. Calling it unconditionally on native evaluates to invoking
11
+ * `undefined` as a function, which Hermes reports as the unhelpfully
12
+ * generic "undefined is not a function" — confirmed for real on
13
+ * 2026-08-17, the first time a native call site (a vault item detail
14
+ * screen, via its own cancellation AbortSignal) exercised the
15
+ * `signals.length > 1` branch this function has always had; every
16
+ * previous native caller only ever supplied the internal timeout signal,
17
+ * so `AbortSignal.any` had simply never been reached before.
18
+ */
19
+ function combineSignals(signals) {
20
+ if (signals.length === 1) {
21
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked
22
+ return signals[0];
23
+ }
24
+ if (typeof AbortSignal.any === "function") {
25
+ return AbortSignal.any([...signals]);
26
+ }
27
+ const combined = new AbortController();
28
+ for (const s of signals) {
29
+ if (s.aborted) {
30
+ combined.abort();
31
+ break;
32
+ }
33
+ s.addEventListener("abort", () => combined.abort(), { once: true });
34
+ }
35
+ return combined.signal;
36
+ }
1
37
  /**
2
38
  * `fetch` with a timeout abort signal merged onto the caller's own signal.
3
39
  * The default timeout is 15s. Pass `Infinity` to disable the timeout
@@ -19,7 +55,7 @@ export function fetchWithTimeout(url, options = {}, timeoutMs = 15000) {
19
55
  ? setTimeout(() => controller.abort(), timeoutMs)
20
56
  : null;
21
57
  const signals = [controller.signal, options.signal].filter((s) => s != null);
22
- const combinedSignal = signals.length > 1 ? AbortSignal.any(signals) : controller.signal;
58
+ const combinedSignal = combineSignals(signals);
23
59
  return fetch(url, { ...options, signal: combinedSignal }).finally(() => {
24
60
  if (id !== null) {
25
61
  clearTimeout(id);
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/utils/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAW,EACX,UAAuB,EAAE,EACzB,SAAS,GAAG,KAAK;IAEjB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,EAAE,GACN,SAAS,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;IAC/F,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;IACzF,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACrE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,YAAY,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/utils/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,cAAc,CAAC,OAA+B;IACrD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,sFAAsF;QACtF,OAAO,OAAO,CAAC,CAAC,CAAE,CAAC;IACrB,CAAC;IACD,IAAI,OAAO,WAAW,CAAC,GAAG,KAAK,UAAU,EAAE,CAAC;QAC1C,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,QAAQ,CAAC,KAAK,EAAE,CAAC;YACjB,MAAM;QACR,CAAC;QACD,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAW,EACX,UAAuB,EAAE,EACzB,SAAS,GAAG,KAAK;IAEjB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,EAAE,GACN,SAAS,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzC,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;IAC/F,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACrE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,YAAY,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entropy-softworks/ui",
3
- "version": "2026.8.17",
3
+ "version": "2026.8.19",
4
4
  "private": false,
5
5
  "description": "Shared React Native UI primitives, auth screens, hooks, and services for Entropy Softworks Expo apps",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- import { useEffect } from "react";
1
+ import { useCallback, useEffect, useRef } from "react";
2
2
  import { AppState as RNAppState, Platform } from "react-native";
3
3
 
4
4
  const DEFAULT_IDLE_LOCK_MS = 10 * 60 * 1000;
@@ -18,14 +18,37 @@ export interface UseAutoLockOptions {
18
18
  idleLockMs?: number;
19
19
  }
20
20
 
21
+ export interface UseAutoLockResult {
22
+ /**
23
+ * Call on any real user interaction to reset the idle clock. Required
24
+ * on native: unlike web (which listens on `window` for
25
+ * mousemove/touchstart/scroll/keydown directly), RN has no global
26
+ * "any touch happened anywhere" event — a touch inside a `Pressable`,
27
+ * `ScrollView`, etc. never reaches `window`, because there is no
28
+ * `window` to reach. Without a caller wiring this up (e.g. a capture-
29
+ * phase `onStartShouldSetResponderCapture` at the app root, returning
30
+ * `false` so it never actually claims the touch), the idle timer is
31
+ * armed once at mount and never reset — the session locks exactly
32
+ * `idleLockMs` after mount/foreground REGARDLESS of how actively the
33
+ * app is being used. Confirmed for real: a vault mid-active-use lost
34
+ * its key mid-session, and every item silently failed to decrypt
35
+ * (`Promise.allSettled` in the caller's list-fetch treats a decrypt
36
+ * failure as "drop this item", not "the vault relocked"), which reads
37
+ * to the user as vault items simply vanishing — no error, no visible
38
+ * cause, on 2026-08-17.
39
+ */
40
+ notifyActivity: () => void;
41
+ }
42
+
21
43
  /**
22
44
  * Auto-lock a session after a period of inactivity. Generic over the
23
45
  * lock predicate — vault wires `KeyManager.isUnlocked` / `KeyManager.lock`,
24
46
  * other apps wire their own state. Web uses an idle-timer only; native
25
47
  * additionally locks immediately when the app goes to the background.
26
48
  */
27
- export function useAutoLock(options: UseAutoLockOptions): void {
49
+ export function useAutoLock(options: UseAutoLockOptions): UseAutoLockResult {
28
50
  const { isUnlocked, onLock, isEnabled, idleLockMs = DEFAULT_IDLE_LOCK_MS } = options;
51
+ const onActivityRef = useRef<() => void>(() => {});
29
52
 
30
53
  useEffect(() => {
31
54
  let idleTimer: ReturnType<typeof setTimeout> | null = null;
@@ -64,6 +87,7 @@ export function useAutoLock(options: UseAutoLockOptions): void {
64
87
  lastActivityAt = Date.now();
65
88
  armIdleTimer();
66
89
  };
90
+ onActivityRef.current = onActivity;
67
91
 
68
92
  // On web, RN Web's AppState mirrors document visibility — tab-hidden
69
93
  // becomes 'background', which would otherwise fire an immediate lock.
@@ -107,4 +131,10 @@ export function useAutoLock(options: UseAutoLockOptions): void {
107
131
  };
108
132
  // eslint-disable-next-line react-hooks/exhaustive-deps
109
133
  }, []);
134
+
135
+ const notifyActivity = useCallback(() => {
136
+ onActivityRef.current();
137
+ }, []);
138
+
139
+ return { notifyActivity };
110
140
  }
@@ -1,3 +1,40 @@
1
+ /**
2
+ * Combines multiple AbortSignals into one that aborts when any input does.
3
+ * Prefers the native `AbortSignal.any` where present (modern browsers);
4
+ * falls back to manual event-listener wiring where it isn't.
5
+ *
6
+ * That fallback is load-bearing, not defensive padding: React Native
7
+ * doesn't provide a native AbortController at all — `setUpXHR.js` installs
8
+ * one from the third-party `abort-controller` npm package (pinned at
9
+ * v3.0.0 in this tree), which predates `AbortSignal.any` entering the
10
+ * WHATWG spec. Calling it unconditionally on native evaluates to invoking
11
+ * `undefined` as a function, which Hermes reports as the unhelpfully
12
+ * generic "undefined is not a function" — confirmed for real on
13
+ * 2026-08-17, the first time a native call site (a vault item detail
14
+ * screen, via its own cancellation AbortSignal) exercised the
15
+ * `signals.length > 1` branch this function has always had; every
16
+ * previous native caller only ever supplied the internal timeout signal,
17
+ * so `AbortSignal.any` had simply never been reached before.
18
+ */
19
+ function combineSignals(signals: readonly AbortSignal[]): AbortSignal {
20
+ if (signals.length === 1) {
21
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- length checked
22
+ return signals[0]!;
23
+ }
24
+ if (typeof AbortSignal.any === "function") {
25
+ return AbortSignal.any([...signals]);
26
+ }
27
+ const combined = new AbortController();
28
+ for (const s of signals) {
29
+ if (s.aborted) {
30
+ combined.abort();
31
+ break;
32
+ }
33
+ s.addEventListener("abort", () => combined.abort(), { once: true });
34
+ }
35
+ return combined.signal;
36
+ }
37
+
1
38
  /**
2
39
  * `fetch` with a timeout abort signal merged onto the caller's own signal.
3
40
  * The default timeout is 15s. Pass `Infinity` to disable the timeout
@@ -24,7 +61,7 @@ export function fetchWithTimeout(
24
61
  ? setTimeout(() => controller.abort(), timeoutMs)
25
62
  : null;
26
63
  const signals = [controller.signal, options.signal].filter((s): s is AbortSignal => s != null);
27
- const combinedSignal = signals.length > 1 ? AbortSignal.any(signals) : controller.signal;
64
+ const combinedSignal = combineSignals(signals);
28
65
  return fetch(url, { ...options, signal: combinedSignal }).finally(() => {
29
66
  if (id !== null) {
30
67
  clearTimeout(id);