@me2em/react 1.0.0-rc.1 → 1.0.0-rc.3

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/README.md CHANGED
@@ -188,6 +188,15 @@ import {
188
188
  function ContextPanel() {
189
189
  const storage = useMemo(() => new IdentityContextIDBStorage(), []);
190
190
 
191
+ // Caller-owned lifecycle (R5): close the IDB connection on unmount.
192
+ // close() also drops the in-memory cache key — reopen + setCacheKey
193
+ // before the next use.
194
+ useEffect(() => {
195
+ return () => {
196
+ void storage.close();
197
+ };
198
+ }, [storage]);
199
+
191
200
  // At login time, derive and set the cache key:
192
201
  useEffect(() => {
193
202
  const salt = crypto.getRandomValues(new Uint8Array(32));
@@ -215,7 +224,7 @@ function ContextPanel() {
215
224
  }
216
225
  ```
217
226
 
218
- Each identity gets a separate IndexedDB database (name = SHA-256 hash of identity ID), guaranteeing full isolation between identities.
227
+ Each identity gets a separate IndexedDB database (name = SHA-256 hash of identity ID), guaranteeing full isolation between identities. The storage connection is caller-owned: `close()` on unmount (as above) also drops the in-memory cache key — `open()`/`openAndLoad()` plus `setCacheKey()` are required before the next use.
219
228
 
220
229
  ## Status
221
230
 
package/dist/index.d.ts CHANGED
@@ -7,9 +7,25 @@ declare function Me2emProvider({ children }: {
7
7
  }): react.JSX.Element;
8
8
 
9
9
  interface Me2emContextValue {
10
+ /**
11
+ * Active identity, or null. The previously active identity is
12
+ * disposed (key material zeroed) the moment it is replaced by a new
13
+ * activation or by {@link clearIdentity} — stale references fail
14
+ * loudly with `DisposedError` per the core contract (BL-47).
15
+ */
10
16
  identity: Identity | null;
11
17
  hasIdentity: boolean;
18
+ /**
19
+ * Activates an identity from raw seed bytes, replacing (and
20
+ * disposing) any previously active identity. Resolves `false` if
21
+ * derivation failed or the activation was superseded by a newer
22
+ * activation / `clearIdentity()` while in flight.
23
+ *
24
+ * Does not consume the seed — the caller owns the buffer and must
25
+ * zero it when done (BL-48).
26
+ */
12
27
  activateIdentity: (seed: Uint8Array) => Promise<boolean>;
28
+ /** Disposes the active identity (if any) and clears it. */
13
29
  clearIdentity: () => void;
14
30
  }
15
31
  declare function useMe2emContext(): Me2emContextValue;
@@ -18,6 +34,13 @@ type IdentityFlowStatus = 'idle' | 'seed-generated' | 'verified' | 'ready' | 'im
18
34
  interface IdentityFlowState {
19
35
  status: IdentityFlowStatus;
20
36
  seedWords: string[] | null;
37
+ /**
38
+ * Raw seed material, retained in state after 'verified'/'imported'
39
+ * by design — the flow does NOT self-consume it (BL-48): headless
40
+ * consumers may still read it at this point. Reachable via devtools
41
+ * in this window (R15 category). An explicit `discardSeed()` action
42
+ * is the planned v1.1 opt-in; until then the app owns dropping it.
43
+ */
21
44
  identitySeed: Uint8Array | null;
22
45
  error: string | null;
23
46
  }
@@ -60,8 +83,24 @@ interface UseImportSeedResult {
60
83
  }
61
84
  declare function useImportSeed(): UseImportSeedResult;
62
85
 
86
+ /**
87
+ * Derives the named Handle for the active identity.
88
+ *
89
+ * Silent-failure contract (R7): on derivation failure the hook logs
90
+ * via console.error and keeps returning null — there is no thrown or
91
+ * returned error signal. The structured `{ handle, error }` shape is
92
+ * a post-freeze v1.1 candidate (BL-50).
93
+ */
63
94
  declare function useHandle(name: string): Handle | null;
64
95
 
96
+ /**
97
+ * Derives the named SubHandle under the given Handle.
98
+ *
99
+ * Silent-failure contract (R7): on derivation failure the hook logs
100
+ * via console.error and keeps returning null — there is no thrown or
101
+ * returned error signal. The structured `{ handle, error }` shape is
102
+ * a post-freeze v1.1 candidate (BL-50).
103
+ */
65
104
  declare function useSubHandle(handleName: string, subName: string): SubHandle | null;
66
105
 
67
106
  interface SeedPhraseDisplayProps {
@@ -79,6 +118,15 @@ interface SeedPhraseVerifyProps {
79
118
  onVerified: () => void;
80
119
  onFailed?: (attempts: number) => void;
81
120
  }
121
+ /**
122
+ * Confirmation grid for a generated seed phrase.
123
+ *
124
+ * The grid shuffle is deterministic by design (fixed base seed 42;
125
+ * each retry shifts it by `+retryCount + 1`) for testability and SSR
126
+ * stability — it is NOT a security control: decoy words come from the
127
+ * public wordlist, so the layout leaks nothing (BL-49). A
128
+ * `randomSeed` prop is deliberately not offered in the frozen 1.0 API.
129
+ */
82
130
  declare function SeedPhraseVerify({ realWords, gridSize, wordlist: propWordlist, onVerified, onFailed, }: SeedPhraseVerifyProps): react.JSX.Element;
83
131
 
84
132
  interface SeedPhraseImportProps {
@@ -110,6 +158,15 @@ interface IdentityContextStorage {
110
158
  save(data: IdentityContextData): Promise<void>;
111
159
  load(): Promise<IdentityContextData | null>;
112
160
  clear(): Promise<void>;
161
+ /**
162
+ * Closes the IDB connection. Caller-owned lifecycle (R5): close()
163
+ * also drops the in-memory cache key and the open identity binding,
164
+ * so every further operation throws "Storage not opened" until the
165
+ * storage is reopened via open()/openAndLoad() and the cache key is
166
+ * set again. open() with a different identityId closes the previous
167
+ * connection automatically; same-identity open() after close()
168
+ * simply opens a fresh connection.
169
+ */
113
170
  close(): Promise<void>;
114
171
  /** Open storage for identity and atomically load cached context. */
115
172
  openAndLoad(identityId: string): Promise<IdentityContextData | null>;
@@ -321,7 +378,9 @@ interface ContextEnvelope {
321
378
  * Raw key bytes exist only transiently during importKey.
322
379
  *
323
380
  * @param loginMaterial - high-entropy bytes available at login
324
- * (derived from the seed while it is still in memory).
381
+ * (derived from the seed while it is still in memory). Caller-owned:
382
+ * deriveCacheKey reads but never wipes them — zero the buffer (and
383
+ * the seed it came from) when done (R6).
325
384
  * @param salt - FRESH random bytes for this login. Never reuse a
326
385
  * salt across logins: the whole point is that a compromised
327
386
  * cacheKey from session N is useless in session N+1.
package/dist/index.js CHANGED
@@ -8,7 +8,9 @@ var __export = (target, all) => {
8
8
  import {
9
9
  useState,
10
10
  useCallback,
11
- useMemo
11
+ useMemo,
12
+ useEffect,
13
+ useRef
12
14
  } from "react";
13
15
  import { Identity } from "@me2em/core";
14
16
 
@@ -27,9 +29,18 @@ function useMe2emContext() {
27
29
  import { jsx } from "react/jsx-runtime";
28
30
  function Me2emProvider({ children }) {
29
31
  const [identity, setIdentity] = useState(null);
32
+ const identityRef = useRef(null);
33
+ const genRef = useRef(0);
30
34
  const activateIdentity = useCallback(async (seed) => {
35
+ const gen = ++genRef.current;
31
36
  try {
32
37
  const id = await Identity.fromSeed(seed);
38
+ if (gen !== genRef.current) {
39
+ id.dispose();
40
+ return false;
41
+ }
42
+ identityRef.current?.dispose();
43
+ identityRef.current = id;
33
44
  setIdentity(id);
34
45
  return true;
35
46
  } catch {
@@ -37,8 +48,18 @@ function Me2emProvider({ children }) {
37
48
  }
38
49
  }, []);
39
50
  const clearIdentity = useCallback(() => {
51
+ ++genRef.current;
52
+ identityRef.current?.dispose();
53
+ identityRef.current = null;
40
54
  setIdentity(null);
41
55
  }, []);
56
+ useEffect(() => {
57
+ return () => {
58
+ ++genRef.current;
59
+ identityRef.current?.dispose();
60
+ identityRef.current = null;
61
+ };
62
+ }, []);
42
63
  const value = useMemo(
43
64
  () => ({ identity, hasIdentity: identity !== null, activateIdentity, clearIdentity }),
44
65
  [identity, activateIdentity, clearIdentity]
@@ -235,11 +256,11 @@ function useImportSeed() {
235
256
  }
236
257
 
237
258
  // src/react/useHandle.ts
238
- import { useState as useState2, useEffect } from "react";
259
+ import { useState as useState2, useEffect as useEffect2 } from "react";
239
260
  function useHandle(name) {
240
261
  const { identity } = useMe2emContext();
241
262
  const [handle, setHandle] = useState2(null);
242
- useEffect(() => {
263
+ useEffect2(() => {
243
264
  let cancelled = false;
244
265
  if (!identity) {
245
266
  setHandle(null);
@@ -258,11 +279,11 @@ function useHandle(name) {
258
279
  }
259
280
 
260
281
  // src/react/useSubHandle.ts
261
- import { useState as useState3, useEffect as useEffect2 } from "react";
282
+ import { useState as useState3, useEffect as useEffect3 } from "react";
262
283
  function useSubHandle(handleName, subName) {
263
284
  const { identity } = useMe2emContext();
264
285
  const [subHandle, setSubHandle] = useState3(null);
265
- useEffect2(() => {
286
+ useEffect3(() => {
266
287
  let cancelled = false;
267
288
  if (!identity) {
268
289
  setSubHandle(null);
@@ -432,7 +453,7 @@ function SeedPhraseDisplay({
432
453
  }
433
454
 
434
455
  // src/react/SeedPhraseVerify.tsx
435
- import { useReducer as useReducer4, useCallback as useCallback4, useState as useState5, useEffect as useEffect3, useRef } from "react";
456
+ import { useReducer as useReducer4, useCallback as useCallback4, useState as useState5, useEffect as useEffect4, useRef as useRef2 } from "react";
436
457
 
437
458
  // src/headless/seed-words.ts
438
459
  function shuffleWithSeed(items, seed) {
@@ -582,7 +603,7 @@ function SeedPhraseVerify({
582
603
  );
583
604
  const [retryCount, setRetryCount] = useState5(0);
584
605
  const { status, grid: gridData, selected } = state;
585
- const notifiedRef = useRef(false);
606
+ const notifiedRef = useRef2(false);
586
607
  const handleSelect = useCallback4(
587
608
  (index) => {
588
609
  if (status === "correct" || status === "incorrect") return;
@@ -591,7 +612,7 @@ function SeedPhraseVerify({
591
612
  },
592
613
  [status, selected.length, gridData.correctIndices.length]
593
614
  );
594
- useEffect3(() => {
615
+ useEffect4(() => {
595
616
  if (notifiedRef.current) return;
596
617
  if (selected.length === gridData.correctIndices.length && selected.length > 0 && status === "collecting") {
597
618
  dispatch({ type: "CHECK" });
@@ -642,7 +663,7 @@ function SeedPhraseVerify({
642
663
  }
643
664
 
644
665
  // src/react/SeedPhraseImport.tsx
645
- import { useReducer as useReducer5, useEffect as useEffect4, useRef as useRef2 } from "react";
666
+ import { useReducer as useReducer5, useEffect as useEffect5, useRef as useRef3 } from "react";
646
667
 
647
668
  // src/headless/import-flow.ts
648
669
  import { validateSeedPhrase as validateSeedPhrase2 } from "@me2em/core";
@@ -716,12 +737,12 @@ function SeedPhraseImport({
716
737
  (params) => initImportState(params.expectedCount)
717
738
  );
718
739
  const count = state.expectedCount;
719
- const prevStatusRef = useRef2(state.status);
720
- const lastImportedRef = useRef2(null);
721
- useEffect4(() => {
740
+ const prevStatusRef = useRef3(state.status);
741
+ const lastImportedRef = useRef3(null);
742
+ useEffect5(() => {
722
743
  dispatch({ type: "CHECK" });
723
744
  }, [state.words]);
724
- useEffect4(() => {
745
+ useEffect5(() => {
725
746
  const prevStatus = prevStatusRef.current;
726
747
  prevStatusRef.current = state.status;
727
748
  if (state.status === "valid" && prevStatus !== "valid") {
@@ -818,7 +839,7 @@ function PassphraseInput({
818
839
  }
819
840
 
820
841
  // src/react/useIdentityContext.ts
821
- import { useState as useState6, useCallback as useCallback6, useEffect as useEffect5, useRef as useRef3 } from "react";
842
+ import { useState as useState6, useCallback as useCallback6, useEffect as useEffect6, useRef as useRef4 } from "react";
822
843
  function publicKeyToId(publicKey) {
823
844
  return Array.from(publicKey).map((b) => b.toString(16).padStart(2, "0")).join("");
824
845
  }
@@ -830,11 +851,11 @@ function useIdentityContext(storage) {
830
851
  const [context, setContext] = useState6(null);
831
852
  const [isLoaded, setIsLoaded] = useState6(false);
832
853
  const [error, setError] = useState6(null);
833
- const prevIdentityIdRef = useRef3(null);
834
- const opRef = useRef3(0);
835
- const identityRef = useRef3(null);
854
+ const prevIdentityIdRef = useRef4(null);
855
+ const opRef = useRef4(0);
856
+ const identityRef = useRef4(null);
836
857
  identityRef.current = identity;
837
- useEffect5(() => {
858
+ useEffect6(() => {
838
859
  let cancelled = false;
839
860
  if (!identity) {
840
861
  setContext(null);
@@ -891,16 +912,16 @@ function useIdentityContext(storage) {
891
912
  }
892
913
 
893
914
  // src/react/useSession.ts
894
- import { useState as useState7, useEffect as useEffect6, useCallback as useCallback7, useRef as useRef4 } from "react";
915
+ import { useState as useState7, useEffect as useEffect7, useCallback as useCallback7, useRef as useRef5 } from "react";
895
916
  import { Session } from "@me2em/core";
896
917
  function useSession(handle, options, autoRenew = true) {
897
918
  const [session, setSession] = useState7(null);
898
919
  const [isLoaded, setIsLoaded] = useState7(false);
899
920
  const [error, setError] = useState7(null);
900
- const timerRef = useRef4(null);
901
- const optionsRef = useRef4(options);
902
- const mountedRef = useRef4(true);
903
- useEffect6(() => {
921
+ const timerRef = useRef5(null);
922
+ const optionsRef = useRef5(options);
923
+ const mountedRef = useRef5(true);
924
+ useEffect7(() => {
904
925
  optionsRef.current = options;
905
926
  });
906
927
  const clearTimer = useCallback7(() => {
@@ -909,7 +930,7 @@ function useSession(handle, options, autoRenew = true) {
909
930
  timerRef.current = null;
910
931
  }
911
932
  }, []);
912
- useEffect6(() => {
933
+ useEffect7(() => {
913
934
  mountedRef.current = true;
914
935
  return () => {
915
936
  mountedRef.current = false;
@@ -950,7 +971,7 @@ function useSession(handle, options, autoRenew = true) {
950
971
  setError(err instanceof Error ? err.message : String(err));
951
972
  });
952
973
  }, [handle, autoRenew, clearTimer]);
953
- useEffect6(() => {
974
+ useEffect7(() => {
954
975
  if (mountedRef.current) {
955
976
  renew();
956
977
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react/Me2emProvider.tsx","../src/react/context.ts","../src/react/useCreateIdentity.ts","../src/headless/identity-flow.ts","../src/react/useImportSeed.ts","../src/react/useHandle.ts","../src/react/useSubHandle.ts","../src/react/SeedPhraseDisplay.tsx","../src/headless/display-flow.ts","../src/react/SeedPhraseVerify.tsx","../src/headless/seed-words.ts","../src/headless/verify-flow.ts","../src/react/SeedPhraseImport.tsx","../src/headless/import-flow.ts","../src/react/PassphraseInput.tsx","../src/react/useIdentityContext.ts","../src/react/useSession.ts","../src/headless/index.ts","../src/headless/context-crypto.ts","../src/headless/identity-context-storage.ts"],"sourcesContent":["import {\n useState,\n useCallback,\n useMemo,\n type ReactNode,\n} from 'react';\nimport { Identity } from '@me2em/core';\nimport { Me2emContext, type Me2emContextValue } from './context.js';\n\nexport function Me2emProvider({ children }: { children: ReactNode }) {\n const [identity, setIdentity] = useState<Identity | null>(null);\n\n const activateIdentity = useCallback(async (seed: Uint8Array): Promise<boolean> => {\n try {\n const id = await Identity.fromSeed(seed);\n setIdentity(id);\n return true;\n } catch {\n return false;\n }\n }, []);\n\n const clearIdentity = useCallback(() => {\n setIdentity(null);\n }, []);\n\n const value = useMemo<Me2emContextValue>(\n () => ({ identity, hasIdentity: identity !== null, activateIdentity, clearIdentity }),\n [identity, activateIdentity, clearIdentity]\n );\n\n return <Me2emContext.Provider value={value}>{children}</Me2emContext.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type { Identity } from '@me2em/core';\n\nexport interface Me2emContextValue {\n identity: Identity | null;\n hasIdentity: boolean;\n activateIdentity: (seed: Uint8Array) => Promise<boolean>;\n clearIdentity: () => void;\n}\n\nexport const Me2emContext = createContext<Me2emContextValue | null>(null);\n\nexport function useMe2emContext(): Me2emContextValue {\n const ctx = useContext(Me2emContext);\n if (!ctx) {\n throw new Error('Me2em hooks must be used inside <Me2emProvider>');\n }\n return ctx;\n}\n","// packages/react/src/react/useCreateIdentity.ts\nimport {\n useReducer,\n useCallback,\n type Reducer,\n} from 'react';\nimport {\n generateSeedPhrase,\n get64ByteSeedFromMnemonic,\n type SeedStrength,\n type Identity,\n} from '@me2em/core';\nimport {\n identityFlowReducer,\n initialIdentityFlowState,\n} from '../headless/identity-flow.js';\nimport type { IdentityFlowState, IdentityFlowEvent, IdentityFlowStatus } from '../headless/types.js';\nimport { useMe2emContext } from './context.js';\n\nexport interface UseCreateIdentityResult {\n status: IdentityFlowStatus;\n seedWords: string[] | null;\n identity: Identity | null;\n error: string | null;\n generate: (opts?: { strength?: SeedStrength }) => void;\n confirmWords: () => Promise<void>;\n reset: () => void;\n fail: (message: string) => void;\n}\n\nexport function useCreateIdentity(): UseCreateIdentityResult {\n const { activateIdentity, clearIdentity, identity } = useMe2emContext();\n const [state, dispatch] = useReducer<\n Reducer<IdentityFlowState, IdentityFlowEvent>\n >(identityFlowReducer, initialIdentityFlowState);\n\n const generate = useCallback(\n (opts?: { strength?: SeedStrength }) => {\n const words = generateSeedPhrase(opts?.strength ?? 128);\n get64ByteSeedFromMnemonic(words).then((derivedSeed) => {\n dispatch({ type: 'GENERATE', words, seed: derivedSeed });\n }).catch(() => {\n dispatch({ type: 'FAIL', message: 'Failed to generate seed' });\n });\n },\n []\n );\n\n const confirmWords = useCallback(async () => {\n if (state.status !== 'seed-generated' || !state.identitySeed) return;\n dispatch({ type: 'CONFIRM_WORDS' });\n const ok = await activateIdentity(state.identitySeed);\n if (!ok) {\n dispatch({ type: 'FAIL', message: 'Identity activation failed' });\n }\n }, [state.status, state.identitySeed, activateIdentity]);\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' });\n clearIdentity();\n }, [clearIdentity]);\n\n const fail = useCallback((message: string) => {\n dispatch({ type: 'FAIL', message });\n }, []);\n\n return {\n status: state.status,\n seedWords: state.seedWords,\n identity,\n error: state.error,\n generate,\n confirmWords,\n reset,\n fail,\n };\n}\n","import type { IdentityFlowState, IdentityFlowEvent } from './types.js';\n\nexport const initialIdentityFlowState: IdentityFlowState = {\n status: 'idle',\n seedWords: null,\n identitySeed: null,\n error: null,\n};\n\nconst UNEXPECTED_EVENTS: Record<string, string> = {\n CONFIRM_WORDS: 'CONFIRM_WORDS called from idle — no seed to confirm',\n RESET: 'RESET called from idle — nothing to reset',\n};\n\nexport function identityFlowReducer(\n state: IdentityFlowState,\n event: IdentityFlowEvent\n): IdentityFlowState {\n const { type } = event;\n\n switch (state.status) {\n case 'idle': {\n if (type === 'GENERATE') {\n return {\n status: 'seed-generated',\n seedWords: event.words,\n identitySeed: event.seed,\n error: null,\n };\n }\n if (type === 'IMPORT') {\n return {\n status: 'imported',\n seedWords: null,\n identitySeed: event.seed,\n error: null,\n };\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n if (UNEXPECTED_EVENTS[type]) {\n console.warn(UNEXPECTED_EVENTS[type]);\n return state;\n }\n return state;\n }\n\n case 'seed-generated': {\n if (type === 'CONFIRM_WORDS') {\n return {\n status: 'verified',\n seedWords: null,\n identitySeed: state.identitySeed,\n error: null,\n };\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n console.warn(`Unexpected event ${type} in seed-generated`);\n return state;\n }\n\n case 'verified':\n case 'imported': {\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n console.warn(`Unexpected event ${type} in ${state.status}`);\n return state;\n }\n\n case 'error': {\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n console.warn(`Unexpected event ${type} in error`);\n return state;\n }\n\n default:\n return state;\n }\n}\n","// packages/react/src/react/useImportSeed.ts\nimport { useReducer, useCallback, type Reducer } from 'react';\nimport {\n validateSeedPhrase,\n get64ByteSeedFromMnemonic,\n type Identity,\n} from '@me2em/core';\nimport {\n identityFlowReducer,\n initialIdentityFlowState,\n} from '../headless/identity-flow.js';\nimport type { IdentityFlowState, IdentityFlowEvent, IdentityFlowStatus } from '../headless/types.js';\nimport { useMe2emContext } from './context.js';\n\nexport interface UseImportSeedResult {\n status: IdentityFlowStatus;\n identity: Identity | null;\n error: string | null;\n importWords: (words: string[], passphrase?: string) => Promise<void>;\n reset: () => void;\n}\n\nexport function useImportSeed(): UseImportSeedResult {\n const { activateIdentity, clearIdentity, identity } = useMe2emContext();\n const [state, dispatch] = useReducer<\n Reducer<IdentityFlowState, IdentityFlowEvent>\n >(identityFlowReducer, initialIdentityFlowState);\n\n const importWords = useCallback(\n async (words: string[], passphrase?: string) => {\n const validation = validateSeedPhrase(words);\n if (!validation.isValid) {\n dispatch({ type: 'FAIL', message: validation.error ?? 'Invalid seed phrase' });\n return;\n }\n try {\n const seed = await get64ByteSeedFromMnemonic(words, passphrase ?? '');\n dispatch({ type: 'IMPORT', seed });\n await activateIdentity(seed);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Import failed';\n dispatch({ type: 'FAIL', message });\n }\n },\n [activateIdentity]\n );\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' });\n clearIdentity();\n }, [clearIdentity]);\n\n return {\n status: state.status,\n identity,\n error: state.error,\n importWords,\n reset,\n };\n}\n","// packages/react/src/react/useHandle.ts\nimport { useState, useEffect } from 'react';\nimport { type Handle } from '@me2em/core';\nimport { useMe2emContext } from './context.js';\n\nexport function useHandle(name: string): Handle | null {\n const { identity } = useMe2emContext();\n const [handle, setHandle] = useState<Handle | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async result when identity is gone; set-during-render refactor is a tracked post-rc.2 follow-up\n setHandle(null);\n return;\n }\n // Core performs full name canonicalization (NFKC, lowercase,\n // trim) and validation — no local normalization here.\n identity.deriveHandle(name).then((h) => {\n if (!cancelled) setHandle(h);\n }).catch((err) => {\n if (!cancelled) console.error('useHandle derivation failed', err);\n });\n return () => { cancelled = true; };\n }, [identity, name]);\n\n return handle;\n}\n","import { useState, useEffect } from 'react';\nimport { type SubHandle } from '@me2em/core';\nimport { useMe2emContext } from './context.js';\n\nexport function useSubHandle(\n handleName: string,\n subName: string\n): SubHandle | null {\n const { identity } = useMe2emContext();\n const [subHandle, setSubHandle] = useState<SubHandle | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async result when identity is gone; set-during-render refactor is a tracked post-rc.2 follow-up\n setSubHandle(null);\n return;\n }\n identity\n .deriveSubHandle(handleName, subName)\n .then((h) => {\n if (!cancelled) setSubHandle(h);\n })\n .catch((err) => {\n if (!cancelled) console.error('useSubHandle derivation failed', err);\n });\n return () => { cancelled = true; };\n }, [identity, handleName, subName]);\n\n return subHandle;\n}\n","// packages/react/src/react/SeedPhraseDisplay.tsx\nimport { useReducer, useState } from 'react';\nimport {\n displayFlowReducer,\n initialDisplayState,\n} from '../headless/display-flow.js';\n\nexport interface SeedPhraseDisplayProps {\n /** Seed material is reachable via React DevTools in this window; by design. */\n words: string[];\n onDismiss?: () => void;\n onCopyAllowed?: () => void;\n}\n\n/**\n * R8: the permanent/transient line for a failed clipboard write is\n * \"can a retry in this window ever succeed?\". Without the async\n * Clipboard API (non-secure context, unsupported browser) it cannot —\n * the failure is permanent, the copy-all button is disabled and the\n * user is pointed at manual word selection. With the API present any\n * rejection (lost focus, dismissed permission prompt, OS hiccup)\n * stays transient: the button remains enabled and a fresh attempt\n * may go through.\n */\nfunction clipboardWriteAvailable(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n !!navigator.clipboard &&\n typeof navigator.clipboard.writeText === 'function'\n );\n}\n\nexport function SeedPhraseDisplay({\n words,\n onDismiss,\n onCopyAllowed,\n}: SeedPhraseDisplayProps) {\n const [state, dispatch] = useReducer(displayFlowReducer, initialDisplayState);\n const [copyError, setCopyError] = useState<'transient' | 'permanent' | null>(null);\n\n const { status, copyCount } = state;\n\n const handleReveal = () => {\n dispatch({ type: 'REVEAL', at: Date.now() });\n };\n\n const handleCopy = async () => {\n if (status === 'revealed' && copyCount === 0) {\n try {\n await navigator.clipboard.writeText(words.join(' '));\n dispatch({ type: 'COPY', at: Date.now() });\n setCopyError(null);\n onCopyAllowed?.();\n } catch {\n setCopyError(clipboardWriteAvailable() ? 'transient' : 'permanent');\n }\n }\n };\n\n const handleDismiss = () => {\n dispatch({ type: 'DISMISS' });\n onDismiss?.();\n };\n\n if (status === 'hidden') {\n return (\n <button data-testid=\"reveal\" onClick={handleReveal}>\n Reveal\n </button>\n );\n }\n\n if (status === 'dismissed') {\n return (\n <div data-testid=\"dismissed\">\n Phrase dismissed\n </div>\n );\n }\n\n return (\n <div>\n <ol data-testid=\"words\">\n {words.map((word, i) => (\n <li\n key={i}\n data-testid={`word-${i}`}\n data-position={i}\n >\n {word}\n </li>\n ))}\n </ol>\n {copyError && (\n <div data-testid=\"copy-error\" data-kind={copyError}>\n {copyError === 'permanent'\n ? 'Copy unavailable in this context — select the words manually'\n : 'Copy failed — try again'}\n </div>\n )}\n <button\n data-testid=\"copy-all\"\n onClick={handleCopy}\n disabled={copyCount === 1 || copyError === 'permanent'}\n >\n {copyCount === 1 ? 'Copied' : 'Copy all'}\n </button>\n <button data-testid=\"dismiss\" onClick={handleDismiss}>\n Hide\n </button>\n </div>\n );\n}\n","// packages/react/src/headless/display-flow.ts\n\nexport type DisplayStatus =\n | 'hidden'\n | 'revealed'\n | 'copied'\n | 'dismissed';\n\nexport interface DisplayState {\n status: DisplayStatus;\n copyCount: number;\n revealedAt: number | null;\n}\n\nexport type DisplayEvent =\n | { type: 'REVEAL'; at: number }\n | { type: 'COPY'; at: number }\n | { type: 'DISMISS' }\n | { type: 'RESET' };\n\nexport const initialDisplayState: DisplayState = {\n status: 'hidden',\n copyCount: 0,\n revealedAt: null,\n};\n\nexport function displayFlowReducer(\n state: DisplayState,\n event: DisplayEvent\n): DisplayState {\n const { type } = event;\n\n switch (state.status) {\n case 'hidden': {\n if (type === 'REVEAL') {\n return {\n status: 'revealed',\n copyCount: 0,\n revealedAt: event.at,\n };\n }\n if (type === 'COPY') {\n console.warn('Cannot copy while hidden');\n return state;\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n if (type === 'DISMISS') {\n console.warn('Cannot dismiss while hidden');\n return state;\n }\n console.warn(`Unexpected event ${type} in hidden`);\n return state;\n }\n\n case 'revealed': {\n if (type === 'COPY') {\n if (state.copyCount === 0) {\n return {\n status: 'copied',\n copyCount: 1,\n revealedAt: state.revealedAt,\n };\n }\n console.warn('Already copied — duplicate copy blocked');\n return state;\n }\n if (type === 'DISMISS') {\n return {\n status: 'dismissed',\n copyCount: state.copyCount,\n revealedAt: state.revealedAt,\n };\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in revealed`);\n return state;\n }\n\n case 'copied': {\n if (type === 'COPY') {\n console.warn('Already copied — duplicate copy blocked');\n return state;\n }\n if (type === 'DISMISS') {\n return {\n status: 'dismissed',\n copyCount: 1,\n revealedAt: state.revealedAt,\n };\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in copied`);\n return state;\n }\n\n case 'dismissed': {\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in dismissed`);\n return state;\n }\n\n default:\n return state;\n }\n}\n","// packages/react/src/react/SeedPhraseVerify.tsx\nimport { useReducer, useCallback, useState, useEffect, useRef } from 'react';\nimport {\n verifyFlowReducer,\n initVerifyState,\n} from '../headless/verify-flow.js';\nimport { wordlist } from '@me2em/core';\n\nexport interface SeedPhraseVerifyProps {\n realWords: string[];\n gridSize?: number;\n wordlist?: readonly string[];\n onVerified: () => void;\n onFailed?: (attempts: number) => void;\n}\n\nconst DEFAULT_GRID_SIZE_OFFSET = 8;\n\nexport function SeedPhraseVerify({\n realWords,\n gridSize,\n wordlist: propWordlist,\n onVerified,\n onFailed,\n}: SeedPhraseVerifyProps) {\n const grid = gridSize ?? realWords.length + DEFAULT_GRID_SIZE_OFFSET;\n const allWords = propWordlist ?? wordlist;\n const baseSeed = 42;\n\n const [state, dispatch] = useReducer(\n verifyFlowReducer,\n {\n realWords,\n gridSize: grid,\n randomSeed: baseSeed,\n allWords,\n } as const,\n (params) => initVerifyState(params.realWords, params.gridSize, params.randomSeed, params.allWords)\n );\n\n const [retryCount, setRetryCount] = useState(0);\n\n const { status, grid: gridData, selected } = state;\n\n // Guard against re-entrant callbacks after status change\n const notifiedRef = useRef(false);\n\n const handleSelect = useCallback(\n (index: number) => {\n if (status === 'correct' || status === 'incorrect') return;\n if (selected.length >= gridData.correctIndices.length) return;\n dispatch({ type: 'SELECT', index });\n },\n [status, selected.length, gridData.correctIndices.length]\n );\n\n // Single effect: auto-check when enough words selected, then notify result\n useEffect(() => {\n if (notifiedRef.current) return;\n\n // Auto-check when enough words selected\n if (\n selected.length === gridData.correctIndices.length &&\n selected.length > 0 &&\n status === 'collecting'\n ) {\n dispatch({ type: 'CHECK' });\n // After dispatch, status will change synchronously in the reducer\n // but the new state won't be reflected until next render.\n // We need to check the pending result in the next effect cycle.\n return;\n }\n\n // Handle result after status update\n if (status === 'correct') {\n notifiedRef.current = true;\n onVerified();\n } else if (status === 'incorrect') {\n notifiedRef.current = true;\n onFailed?.(state.attempts);\n }\n }, [selected.length, gridData.correctIndices.length, status, state.attempts, onVerified, onFailed]);\n\n const handleRetry = () => {\n notifiedRef.current = false;\n setRetryCount((c) => c + 1);\n dispatch({\n type: 'RESET',\n realWords,\n gridSize: grid,\n randomSeed: baseSeed + retryCount + 1,\n allWords,\n });\n };\n\n return (\n <div>\n <div data-testid=\"progress\">\n Select word {selected.length + 1} of {realWords.length}\n </div>\n <ol data-testid=\"grid\">\n {gridData.words.map((word, i) => {\n const isSelected = selected.includes(i);\n const isDisabled =\n isSelected || status === 'correct' || status === 'incorrect';\n return (\n <li key={i}>\n <button\n data-testid={`cell-${i}`}\n data-word={word}\n disabled={isDisabled}\n onClick={() => handleSelect(i)}\n >\n {word}\n </button>\n </li>\n );\n })}\n </ol>\n {status === 'incorrect' && (\n <button data-testid=\"retry\" onClick={handleRetry}>\n Try again\n </button>\n )}\n </div>\n );\n}\n","// packages/react/src/headless/seed-words.ts\n\nexport function shuffleWithSeed<T>(items: T[], seed: number): T[] {\n const arr = [...items];\n let currentSeed = seed;\n function nextRand(): number {\n currentSeed = (currentSeed * 1664525 + 1013904223) >>> 0;\n return currentSeed;\n }\n for (let i = arr.length - 1; i > 0; i--) {\n const j = nextRand() % (i + 1);\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n\nexport function buildVerificationGrid(\n realWords: string[],\n gridSize: number,\n randomSeed: number,\n allWords: readonly string[]\n): { words: string[]; correctIndices: number[] } {\n if (gridSize < realWords.length) {\n throw new Error(\n `gridSize (${gridSize}) must be >= realWords.length (${realWords.length})`\n );\n }\n\n const shuffled = shuffleWithSeed(realWords, randomSeed);\n const realSet = new Set(realWords);\n const available = allWords.filter((w) => !realSet.has(w));\n const shuffledDecoys = shuffleWithSeed(available, randomSeed + 1);\n\n const needed = gridSize - realWords.length;\n const decoys = shuffledDecoys.slice(0, needed);\n\n const words = [...shuffled, ...decoys];\n if (words.length !== gridSize) {\n throw new Error(\n `Cannot fill grid: wordlist too small (got ${words.length}, need ${gridSize})`\n );\n }\n\n // Map each word occurrence to a DISTINCT grid position, so\n // repeated words in the phrase get their own cells.\n const positions = new Map<string, number[]>();\n words.forEach((w, i) => {\n const list = positions.get(w);\n if (list) list.push(i);\n else positions.set(w, [i]);\n });\n\n const correctIndices = realWords.map((w) => {\n const list = positions.get(w);\n if (!list || list.length === 0) {\n throw new Error(`Internal error: word \"${w}\" missing from grid`);\n }\n return list.shift() as number;\n });\n\n return { words, correctIndices };\n}\n\n/**\n * Selection is compared IN ORDER: selected[i] must equal\n * correctIndices[i]. The i-th click must be the i-th word of the\n * phrase. Repeated phrase words map to distinct grid cells.\n */\nexport function isGridSelectionCorrect(\n grid: { words: string[]; correctIndices: number[] },\n selected: number[]\n): boolean {\n if (selected.length !== grid.correctIndices.length) return false;\n return selected.every((v, i) => v === grid.correctIndices[i]);\n}\n","// packages/react/src/headless/verify-flow.ts\nimport {\n buildVerificationGrid,\n isGridSelectionCorrect,\n} from './seed-words.js';\n\nexport type VerifyStatus = 'collecting' | 'correct' | 'incorrect';\n\nexport interface VerifyState {\n status: VerifyStatus;\n grid: { words: string[]; correctIndices: number[] };\n selected: number[];\n attempts: number;\n}\n\nexport type VerifyEvent =\n | { type: 'SELECT'; index: number }\n | { type: 'DESELECT'; index: number }\n | { type: 'CHECK' }\n | {\n type: 'RESET';\n realWords: string[];\n gridSize: number;\n randomSeed: number;\n allWords: readonly string[];\n };\n\nexport function initVerifyState(\n realWords: string[],\n gridSize: number,\n randomSeed: number,\n allWords: readonly string[]\n): VerifyState {\n const grid = buildVerificationGrid(realWords, gridSize, randomSeed, allWords);\n return {\n status: 'collecting',\n grid,\n selected: [],\n attempts: 0,\n };\n}\n\nexport function verifyFlowReducer(\n state: VerifyState,\n event: VerifyEvent\n): VerifyState {\n const { type } = event;\n\n if (type === 'RESET') {\n return initVerifyState(\n event.realWords,\n event.gridSize,\n event.randomSeed,\n event.allWords\n );\n }\n\n // Terminal states: only RESET allowed\n if (state.status === 'correct' || state.status === 'incorrect') {\n console.warn(\n `Cannot ${type} in ${state.status} state — use RESET to retry`\n );\n return state;\n }\n\n // collecting state\n if (type === 'SELECT') {\n const { index } = event;\n const { grid, selected } = state;\n if (selected.includes(index)) {\n console.warn(`Index ${index} already selected — use DESELECT`);\n return state;\n }\n if (index < 0 || index >= grid.words.length) {\n console.warn(`Index ${index} out of grid bounds`);\n return state;\n }\n return { ...state, selected: [...selected, index] };\n }\n\n if (type === 'DESELECT') {\n const { index } = event;\n const idx = state.selected.indexOf(index);\n if (idx === -1) {\n console.warn(`Index ${index} not selected`);\n return state;\n }\n return {\n ...state,\n selected: state.selected.filter((_, i) => i !== idx),\n };\n }\n\n if (type === 'CHECK') {\n const { grid, selected, attempts } = state;\n if (selected.length !== grid.correctIndices.length) {\n console.warn(\n `Cannot check: selected ${selected.length}, need ${grid.correctIndices.length}`\n );\n return state;\n }\n const correct = isGridSelectionCorrect(grid, selected);\n return {\n ...state,\n status: correct ? 'correct' : 'incorrect',\n attempts: attempts + 1,\n };\n }\n\n console.warn(`Unexpected event ${type} in collecting`);\n return state;\n}\n","// packages/react/src/react/SeedPhraseImport.tsx\nimport { useReducer, useEffect, useRef } from 'react';\nimport {\n importFlowReducer,\n initImportState,\n type ImportState,\n} from '../headless/import-flow.js';\nimport { wordlist } from '@me2em/core';\n\nexport interface SeedPhraseImportProps {\n expectedCount?: 12 | 24;\n wordlist?: readonly string[];\n onImported: (words: string[]) => void;\n}\n\nexport function SeedPhraseImport({\n expectedCount: propCount,\n wordlist: propWordlist,\n onImported,\n}: SeedPhraseImportProps) {\n const allWords = propWordlist ?? wordlist;\n\n const [state, dispatch] = useReducer(\n importFlowReducer,\n { expectedCount: propCount ?? 12 } as const,\n (params) => initImportState(params.expectedCount)\n );\n\n const count = state.expectedCount;\n\n const prevStatusRef = useRef<ImportState['status']>(state.status);\n\n const lastImportedRef = useRef<string | null>(null);\n\n // Auto-check on every word change\n useEffect(() => {\n dispatch({ type: 'CHECK' });\n }, [state.words]);\n\n // Call onImported once when transitioning to valid\n useEffect(() => {\n const prevStatus = prevStatusRef.current;\n prevStatusRef.current = state.status;\n\n if (state.status === 'valid' && prevStatus !== 'valid') {\n const key = (state.words as string[]).join('\\0');\n if (lastImportedRef.current !== key) {\n lastImportedRef.current = key;\n onImported(state.words as string[]);\n }\n }\n }, [state.status, state.words, onImported]);\n\n const handleCountChange = (n: 12 | 24) => {\n dispatch({ type: 'INIT', expectedCount: n });\n };\n\n const handleWordChange = (position: number, value: string) => {\n dispatch({ type: 'SET_WORD', position, word: value });\n };\n\n const isWordInvalid = (position: number): boolean => {\n if (state.status !== 'invalid' && state.status !== 'valid') return false;\n const word = state.words[position];\n if (word === null) return false;\n return !allWords.includes(word);\n };\n\n const isImportEnabled = state.status === 'valid';\n\n return (\n <div>\n <div>\n <button\n data-testid=\"count-12\"\n onClick={() => handleCountChange(12)}\n disabled={count === 12}\n >\n 12 words\n </button>\n <button\n data-testid=\"count-24\"\n onClick={() => handleCountChange(24)}\n disabled={count === 24}\n >\n 24 words\n </button>\n </div>\n <div data-testid=\"inputs\">\n {Array.from({ length: count }, (_, i) => (\n <input\n key={i}\n data-testid={`word-${i}`}\n aria-label={`Word ${i + 1}`}\n value={state.words[i] ?? ''}\n onChange={(e) => handleWordChange(i, e.target.value)}\n data-invalid={isWordInvalid(i) ? 'true' : undefined}\n />\n ))}\n </div>\n <button\n data-testid=\"import\"\n disabled={!isImportEnabled}\n onClick={() => dispatch({ type: 'CHECK' })}\n >\n Import\n </button>\n </div>\n );\n}\n","// packages/react/src/headless/import-flow.ts\nimport { validateSeedPhrase } from '@me2em/core';\n\nexport type ImportStatus = 'entering' | 'valid' | 'invalid';\n\nexport interface ImportState {\n status: ImportStatus;\n words: (string | null)[];\n expectedCount: 12 | 24;\n}\n\nexport type ImportEvent =\n | { type: 'INIT'; expectedCount: 12 | 24 }\n | { type: 'SET_WORD'; position: number; word: string }\n | { type: 'CLEAR_WORD'; position: number }\n | { type: 'CHECK' }\n | { type: 'RESET' };\n\nexport function initImportState(expectedCount: 12 | 24): ImportState {\n return {\n status: 'entering',\n words: Array(expectedCount).fill(null),\n expectedCount,\n };\n}\n\nexport function importFlowReducer(\n state: ImportState,\n event: ImportEvent\n): ImportState {\n const { type } = event;\n\n if (type === 'INIT') {\n return initImportState(event.expectedCount);\n }\n\n if (type === 'RESET') {\n return initImportState(state.expectedCount);\n }\n\n if (type === 'SET_WORD') {\n const { position, word } = event;\n if (position < 0 || position >= state.expectedCount) {\n console.warn(`SET_WORD: position ${position} out of bounds`);\n return state;\n }\n const normalized = word.toLowerCase().trim();\n const newWords = [...state.words] as (string | null)[];\n newWords[position] = normalized;\n return { ...state, words: newWords, status: 'entering' };\n }\n\n if (type === 'CLEAR_WORD') {\n const { position } = event;\n if (position < 0 || position >= state.expectedCount) {\n console.warn(`CLEAR_WORD: position ${position} out of bounds`);\n return state;\n }\n const newWords = [...state.words] as (string | null)[];\n newWords[position] = null;\n return { ...state, words: newWords, status: 'entering' };\n }\n\n if (type === 'CHECK') {\n const { words } = state;\n const nullIdx = words.findIndex((w) => w === null);\n if (nullIdx !== -1) {\n return {\n ...state,\n status: 'invalid',\n };\n }\n const result = validateSeedPhrase(words as string[]);\n return {\n ...state,\n status: result.isValid ? 'valid' : 'invalid',\n };\n }\n\n console.warn(`Unexpected event ${type} in import-flow`);\n return state;\n}\n","// packages/react/src/react/PassphraseInput.tsx\nimport { useCallback } from 'react';\n\nexport interface PassphraseInputProps {\n onPassphraseChange: (p: string) => void;\n showWarnings?: boolean;\n}\n\nexport function PassphraseInput({\n onPassphraseChange,\n showWarnings = true,\n}: PassphraseInputProps) {\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value.normalize('NFKC');\n onPassphraseChange(value);\n },\n [onPassphraseChange]\n );\n\n return (\n <div>\n <input\n type=\"password\"\n data-testid=\"passphrase\"\n aria-label=\"BIP39 passphrase\"\n onChange={handleChange}\n />\n {showWarnings && (\n <div data-testid=\"warning\">\n Passphrase is case-sensitive and NOT recoverable. A different\n passphrase silently derives a different wallet.\n </div>\n )}\n </div>\n );\n}\n","// packages/react/src/react/useIdentityContext.ts\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport type { Identity } from '@me2em/core';\nimport type { IdentityContextStorage, IdentityContextData } from '../headless/identity-context-types.js';\nimport { useMe2emContext } from './context.js';\n\nfunction publicKeyToId(publicKey: Uint8Array): string {\n return Array.from(publicKey)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction identityToId(identity: Identity | null): string | null {\n return identity ? publicKeyToId(identity.getPublicKey()) : null;\n}\n\nexport interface UseIdentityContextResult {\n context: IdentityContextData | null;\n isLoaded: boolean;\n error: string | null;\n save: (data: IdentityContextData) => Promise<void>;\n clear: () => Promise<void>;\n /** Reload the cache for the current identity (after sync). */\n refresh: () => Promise<void>;\n}\n\nexport function useIdentityContext(\n storage: IdentityContextStorage\n): UseIdentityContextResult {\n const { identity } = useMe2emContext();\n const [context, setContext] = useState<IdentityContextData | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const prevIdentityIdRef = useRef<string | null>(null);\n // Generation ticket for async state transitions. Load (identity\n // change / `refresh()`), `save()` and `clear()` each take a ticket;\n // a resolution may only commit state while its ticket is the newest\n // one. Without this, an in-flight load started before a save could\n // resolve after it and clobber the just-saved state with pre-save\n // data (out-of-order async update — observed as a rare CI flake in\n // the rerender spec; pinned there by a gated-storage test).\n const opRef = useRef(0);\n const identityRef = useRef<Identity | null>(null);\n // eslint-disable-next-line react-hooks/refs -- render-time mirror of the latest identity: save()/clear() are stable callbacks (storage-only deps) and must verify at commit time, after their await, that the identity they started for is still the active one\n identityRef.current = identity;\n\n useEffect(() => {\n let cancelled = false;\n\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async state when identity is gone (generation-ticket effect from the load-race fix); set-during-render refactor is a tracked post-rc.2 follow-up\n setContext(null);\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n const identityId = publicKeyToId(identity.getPublicKey());\n\n // If identity changed, close old storage and open new\n if (prevIdentityIdRef.current !== null && prevIdentityIdRef.current !== identityId) {\n storage.close().catch(() => { /* ignore close errors */ });\n }\n\n prevIdentityIdRef.current = identityId;\n\n // Open storage for this identity and load cached context (atomic)\n const op = ++opRef.current;\n storage.openAndLoad(identityId)\n .then((data) => {\n if (cancelled || op !== opRef.current) return;\n setContext(data);\n setIsLoaded(true);\n setError(null);\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err instanceof Error ? err.message : String(err));\n setIsLoaded(true);\n });\n\n return () => {\n cancelled = true;\n };\n }, [identity, storage]);\n\n const save = useCallback(async (data: IdentityContextData): Promise<void> => {\n const idAtStart = identityToId(identityRef.current);\n opRef.current += 1; // invalidate any in-flight load/refresh\n await storage.save(data);\n // If the identity switched while the write was in flight, state\n // ownership has moved to the new identity's load effect — committing\n // here would surface identity A's context under identity B. The\n // generation ticket above only orders this op against in-flight\n // loads; it does not know about identity switches.\n if (identityToId(identityRef.current) !== idAtStart) return;\n setContext(data);\n setError(null);\n }, [storage]);\n\n const clear = useCallback(async (): Promise<void> => {\n const idAtStart = identityToId(identityRef.current);\n opRef.current += 1; // invalidate any in-flight load/refresh\n await storage.clear();\n // Same ownership rule as save(): a clear() resolving after an\n // identity switch must not blank the new identity's freshly\n // loaded context.\n if (identityToId(identityRef.current) !== idAtStart) return;\n setContext(null);\n setError(null);\n }, [storage]);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!identity) return;\n const identityId = publicKeyToId(identity.getPublicKey());\n const op = ++opRef.current;\n const data = await storage.openAndLoad(identityId);\n if (op !== opRef.current) return;\n setContext(data);\n }, [identity, storage]);\n\n return { context, isLoaded, error, save, clear, refresh };\n}\n","// packages/react/src/react/useSession.ts\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport type { Handle } from '@me2em/core';\nimport { Session } from '@me2em/core';\n\n/**\n * Options for useSession, memoized via deep comparison of\n * `handle` and `options` refs on each render.\n *\n * @category Types\n */\nexport interface UseSessionOptions {\n /** Intended audience (recipient) of this session. */\n audience: string;\n /** Permission scopes granted in this session. */\n scopes: string[];\n /** Time-to-live in seconds. */\n ttl: number;\n}\n\n/**\n * Result of the useSession hook.\n *\n * @category Types\n */\nexport interface UseSessionResult {\n /** The active session, or null if no handle or session creation failed. */\n session: Session | null;\n /** Whether the session (or its absence) has been determined. */\n isLoaded: boolean;\n /** Error string if session creation or renewal failed. */\n error: string | null;\n /** Whether the current session has expired. */\n isExpired: boolean;\n /** Manually renew the session (creates a new one with a fresh jti). */\n renew: () => void;\n}\n\n/**\n * Hook that creates and manages a signed session token for a Handle.\n *\n * When `handle` is provided, creates a Session with the given options.\n * Supports auto-renewal: when `autoRenew` is true (default), a timer\n * fires at half the session TTL to create a fresh session with a new\n * jti (the previous token remains valid until its own expiry, but the\n * lineage identity is preserved through `handle`).\n *\n * On unmount, all timers are cleaned up to prevent state updates after\n * the component is gone.\n *\n * Dependencies: `[handle, renew]` — the options object is read through\n * a ref, so passing a fresh object literal on every render does NOT\n * re-trigger session creation. Only changes to `handle` (or to the\n * renewal function identity, which follows `handle`) do.\n *\n * @param handle - The Handle to sign the session, or null.\n * @param options - Session options (audience, scopes, ttl).\n * @param autoRenew - Whether to auto-renew at half-life (default: true).\n * @returns Session state with manual `renew` control.\n *\n * @example\n * ```ts\n * const handle = useHandle('my-handle');\n * const { session, isExpired, renew } = useSession(handle, {\n * audience: 'app.example.com',\n * scopes: ['read', 'write'],\n * ttl: 3600,\n * });\n * ```\n */\nexport function useSession(\n handle: Handle | null,\n options: UseSessionOptions,\n autoRenew: boolean = true\n): UseSessionResult {\n const [session, setSession] = useState<Session | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const optionsRef = useRef(options);\n const mountedRef = useRef(true);\n\n // Mirror the latest options into the ref after every commit —\n // ref writes belong in effects, not during render (react-hooks/refs).\n useEffect(() => {\n optionsRef.current = options;\n });\n\n const clearTimer = useCallback(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n clearTimer();\n };\n }, [clearTimer]);\n\n const renew = useCallback(() => {\n clearTimer();\n const opts = optionsRef.current;\n\n if (!handle) {\n if (!mountedRef.current) return;\n setSession(null);\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n Session.create(handle, {\n audience: opts.audience,\n scopes: opts.scopes,\n ttl: opts.ttl,\n }).then((s) => {\n if (!mountedRef.current) return;\n setSession(s);\n setIsLoaded(true);\n setError(null);\n\n // Schedule auto-renewal at half-life\n if (autoRenew) {\n const halfLife = Math.floor((s.expiresAt * 1000 - Date.now()) / 2);\n if (halfLife > 0) {\n timerRef.current = setTimeout(() => {\n // eslint-disable-next-line react-hooks/immutability -- self-rescheduling renewal timer: the timer is cleared and re-created whenever renew itself changes (deps + clearTimer), so the captured binding is always the current one\n renew();\n }, Math.max(halfLife, 1000));\n }\n }\n }).catch((err) => {\n if (!mountedRef.current) return;\n setSession(null);\n setIsLoaded(true);\n setError(err instanceof Error ? err.message : String(err));\n });\n }, [handle, autoRenew, clearTimer]);\n\n // Create session when handle or options change\n useEffect(() => {\n if (mountedRef.current) {\n renew();\n }\n }, [handle, renew]);\n\n return {\n session,\n isLoaded,\n error,\n isExpired: session ? session.isExpired() : false,\n renew,\n };\n}\n","export { identityFlowReducer, initialIdentityFlowState } from './identity-flow.js';\nexport type {\n IdentityFlowState,\n IdentityFlowStatus,\n IdentityFlowEvent,\n} from './types.js';\nexport {\n buildVerificationGrid,\n isGridSelectionCorrect,\n shuffleWithSeed,\n} from './seed-words.js';\nexport {\n displayFlowReducer,\n initialDisplayState,\n} from './display-flow.js';\nexport type {\n DisplayState,\n DisplayStatus,\n DisplayEvent,\n} from './display-flow.js';\nexport {\n verifyFlowReducer,\n initVerifyState,\n} from './verify-flow.js';\nexport type {\n VerifyState,\n VerifyStatus,\n VerifyEvent,\n} from './verify-flow.js';\nexport {\n importFlowReducer,\n initImportState,\n} from './import-flow.js';\nexport type {\n ImportState,\n ImportStatus,\n ImportEvent,\n} from './import-flow.js';\nexport {\n IdentityContextIDBStorage,\n} from './identity-context-storage.js';\nexport type {\n IdentityContextData,\n IdentityContextStorage,\n} from './identity-context-types.js';\nexport {\n deriveCacheKey,\n encryptContext,\n decryptContext,\n} from './context-crypto.js';\nexport type {\n ContextEnvelope,\n} from './context-crypto.js';\n","// packages/react/src/headless/context-crypto.ts\nimport { base64urlEncode, base64urlDecode } from '@me2em/core';\nimport type { IdentityContextData } from './identity-context-types.js';\n\n// Base64url encoding for ContextEnvelope fields goes through the audited\n// @scure/base codec re-exported by @me2em/core (audit A3 / R4) — one\n// base64 implementation across packages, no hand-rolled alphabet handling.\n// Envelope fields are always unpadded: the only writer is encryptContext\n// below, so base64urlDecode's strict no-padding rule never bites.\n\nexport interface ContextEnvelope {\n v: 1;\n /** fresh HKDF salt этого логина (b64). Нужен только при\n * первичной деривации; persisted CryptoKey переживает\n * перезапуск без него. */\n salt: string;\n iv: string; // b64, 12B\n ct: string; // b64, ciphertext || GCM tag\n}\n\n/**\n * Derives a fresh, per-login cache key from login-time secret\n * material and wraps it as a NON-EXTRACTABLE persisted CryptoKey.\n * Raw key bytes exist only transiently during importKey.\n *\n * @param loginMaterial - high-entropy bytes available at login\n * (derived from the seed while it is still in memory).\n * @param salt - FRESH random bytes for this login. Never reuse a\n * salt across logins: the whole point is that a compromised\n * cacheKey from session N is useless in session N+1.\n */\nexport async function deriveCacheKey(\n loginMaterial: Uint8Array,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const base = await crypto.subtle.importKey(\n 'raw', loginMaterial as BufferSource, 'HKDF', false, ['deriveBits']\n );\n const bits = await crypto.subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256',\n salt: salt as BufferSource,\n info: new TextEncoder().encode('me2em/cache-wrap/v1') },\n base, 256\n );\n return crypto.subtle.importKey(\n 'raw', bits, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']\n );\n}\n\nexport async function encryptContext(\n key: CryptoKey, data: IdentityContextData\n): Promise<ContextEnvelope> {\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const salt = crypto.getRandomValues(new Uint8Array(32));\n const pt = new TextEncoder().encode(JSON.stringify(data));\n const ct = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource }, key, pt\n );\n return { v: 1, salt: base64urlEncode(salt), iv: base64urlEncode(iv),\n ct: base64urlEncode(new Uint8Array(ct)) };\n}\n\nexport async function decryptContext(\n key: CryptoKey, envelope: ContextEnvelope\n): Promise<IdentityContextData> {\n if (envelope.v !== 1) throw new Error('Unsupported envelope version');\n const pt = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: base64urlDecode(envelope.iv) as BufferSource },\n key, base64urlDecode(envelope.ct) as BufferSource\n );\n return JSON.parse(new TextDecoder().decode(pt)) as IdentityContextData;\n}\n","// packages/react/src/headless/identity-context-storage.ts\nimport { encryptContext, decryptContext } from './context-crypto.js';\nimport type { IdentityContextData, IdentityContextStorage } from './identity-context-types.js';\nexport type { IdentityContextData };\n\nconst DB_NAME_PREFIX = 'me2em_';\nconst STORE_NAME = 'context';\nconst KEYS_STORE_NAME = 'cryptoKeys';\nconst CACHE_KEY = 'cache';\n\nfunction hashToHex(data: Uint8Array): string {\n const hexParts: string[] = [];\n for (const byte of data) {\n hexParts.push(byte.toString(16).padStart(2, '0'));\n }\n return hexParts.join('');\n}\n\nasync function sha256Hex(data: Uint8Array): Promise<string> {\n const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n const digest: ArrayBuffer = await crypto.subtle.digest('SHA-256', buffer);\n return hashToHex(new Uint8Array(digest));\n}\n\nasync function dbNameForIdentity(identityId: string): Promise<string> {\n const hash = await sha256Hex(new TextEncoder().encode(identityId));\n return DB_NAME_PREFIX + hash.slice(0, 16);\n}\n\nexport class IdentityContextIDBStorage implements IdentityContextStorage {\n private db: IDBDatabase | null = null;\n private currentIdentityId: string | null = null;\n private cacheKey: CryptoKey | null = null;\n\n private async getDb(): Promise<IDBDatabase> {\n if (!this.db) {\n throw new Error('Storage not opened — call open() first');\n }\n return this.db;\n }\n\n async open(identityId: string): Promise<void> {\n if (this.currentIdentityId !== null && this.currentIdentityId !== identityId) {\n await this.close();\n }\n\n this.currentIdentityId = identityId;\n const name = await dbNameForIdentity(identityId);\n\n return new Promise<void>((resolve, reject) => {\n const request = indexedDB.open(name, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME, { keyPath: 'key' });\n }\n if (!db.objectStoreNames.contains(KEYS_STORE_NAME)) {\n db.createObjectStore(KEYS_STORE_NAME, { keyPath: 'key' });\n }\n };\n\n request.onsuccess = () => {\n this.db = request.result;\n resolve();\n };\n\n request.onerror = () => {\n reject(new Error('IndexedDB open failed: ' + request.error?.message));\n };\n });\n }\n\n async openAndLoad(identityId: string): Promise<IdentityContextData | null> {\n await this.open(identityId);\n const key = this.cacheKey ?? await this.loadPersistedKey();\n if (!key) {\n return null;\n }\n this.cacheKey = key;\n return this.load();\n }\n\n async save(data: IdentityContextData): Promise<void> {\n const key = this.cacheKey;\n if (!key) {\n throw new Error('No cache key set — call setCacheKey before save');\n }\n const envelope = await encryptContext(key, data);\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.put({ key: CACHE_KEY, envelope });\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('Save failed: ' + tx.error?.message));\n });\n }\n\n async load(): Promise<IdentityContextData | null> {\n const db = await this.getDb();\n const key = this.cacheKey;\n if (!key) {\n return null;\n }\n return new Promise<IdentityContextData | null>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(CACHE_KEY);\n\n request.onsuccess = () => {\n const record = request.result;\n if (!record || !record.envelope) {\n resolve(null);\n return;\n }\n decryptContext(key, record.envelope)\n .then((data) => {\n if (data.identityId !== this.currentIdentityId) {\n resolve(null);\n return;\n }\n resolve(data);\n })\n .catch(() => {\n resolve(null);\n });\n };\n\n request.onerror = () => reject(new Error('Load failed: ' + request.error?.message));\n });\n }\n\n async clear(): Promise<void> {\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.delete(CACHE_KEY);\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('Clear failed: ' + tx.error?.message));\n });\n }\n\n async close(): Promise<void> {\n const db = this.db;\n this.db = null;\n this.currentIdentityId = null;\n this.cacheKey = null;\n if (db) {\n db.close();\n }\n }\n\n setCacheKey(key: CryptoKey): void {\n this.cacheKey = key;\n }\n\n getCacheKey(): CryptoKey | null {\n return this.cacheKey;\n }\n\n async persistCacheKey(): Promise<void> {\n const key = this.cacheKey;\n if (!key) {\n throw new Error('No cache key to persist — call setCacheKey first');\n }\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(KEYS_STORE_NAME, 'readwrite');\n const store = tx.objectStore(KEYS_STORE_NAME);\n store.put({ key: 'cache', cryptoKey: key });\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('persistCacheKey failed: ' + tx.error?.message));\n });\n }\n\n async loadPersistedKey(): Promise<CryptoKey | null> {\n const db = this.db;\n if (!db) {\n return null;\n }\n return new Promise<CryptoKey | null>((resolve) => {\n const tx = db.transaction(KEYS_STORE_NAME, 'readonly');\n const store = tx.objectStore(KEYS_STORE_NAME);\n const request = store.get('cache');\n\n request.onsuccess = () => {\n const record = request.result;\n if (!record || !record.cryptoKey) {\n resolve(null);\n return;\n }\n resolve(record.cryptoKey);\n };\n\n request.onerror = () => {\n resolve(null);\n };\n });\n }\n}\n"],"mappings":"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","names":["useCallback","useCallback","useReducer","useCallback","get64ByteSeedFromMnemonic","useReducer","useCallback","get64ByteSeedFromMnemonic","useState","useState","useState","useEffect","useState","useEffect","useReducer","useState","jsx","useReducer","useState","useReducer","useCallback","useState","useEffect","jsx","jsxs","useReducer","useState","useCallback","useEffect","useReducer","useEffect","useRef","validateSeedPhrase","wordlist","jsx","jsxs","useReducer","useRef","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","useRef","useState","useRef","useEffect","useCallback","useState","useEffect","useCallback","useRef"]}
1
+ {"version":3,"sources":["../src/react/Me2emProvider.tsx","../src/react/context.ts","../src/react/useCreateIdentity.ts","../src/headless/identity-flow.ts","../src/react/useImportSeed.ts","../src/react/useHandle.ts","../src/react/useSubHandle.ts","../src/react/SeedPhraseDisplay.tsx","../src/headless/display-flow.ts","../src/react/SeedPhraseVerify.tsx","../src/headless/seed-words.ts","../src/headless/verify-flow.ts","../src/react/SeedPhraseImport.tsx","../src/headless/import-flow.ts","../src/react/PassphraseInput.tsx","../src/react/useIdentityContext.ts","../src/react/useSession.ts","../src/headless/index.ts","../src/headless/context-crypto.ts","../src/headless/identity-context-storage.ts"],"sourcesContent":["import {\n useState,\n useCallback,\n useMemo,\n useEffect,\n useRef,\n type ReactNode,\n} from 'react';\nimport { Identity } from '@me2em/core';\nimport { Me2emContext, type Me2emContextValue } from './context.js';\n\nexport function Me2emProvider({ children }: { children: ReactNode }) {\n const [identity, setIdentity] = useState<Identity | null>(null);\n\n // BL-47 dispose parity: the ref is mutated only in event handlers and\n // cleanup effects, always alongside setIdentity — never inside a state\n // updater (StrictMode double-invokes updaters; dispose is a side\n // effect). A replaced/cleared handle is disposed at the swap, so\n // stale references fail loudly with DisposedError (core contract)\n // instead of silently operating on zeroed key material.\n const identityRef = useRef<Identity | null>(null);\n // Generation ticket for in-flight Identity.fromSeed results — the\n // same load-race class fixed for useIdentityContext in rc.1: a\n // result landing after a newer activation, clearIdentity() or\n // unmount disposes its own handle instead of overwriting newer\n // state.\n const genRef = useRef(0);\n\n const activateIdentity = useCallback(async (seed: Uint8Array): Promise<boolean> => {\n const gen = ++genRef.current;\n try {\n const id = await Identity.fromSeed(seed);\n if (gen !== genRef.current) {\n // Superseded while in flight: discard this handle loudly\n // rather than leaking it into state.\n id.dispose();\n return false;\n }\n identityRef.current?.dispose(); // zero the replaced handle\n identityRef.current = id;\n setIdentity(id);\n return true;\n } catch {\n return false;\n }\n }, []);\n\n const clearIdentity = useCallback(() => {\n ++genRef.current; // invalidate in-flight activations\n identityRef.current?.dispose();\n identityRef.current = null;\n setIdentity(null);\n }, []);\n\n // Unmount: cleanup-only dispose — no side effects in render or\n // updaters (the identity was provider state, so it is unreachable\n // after unmount either way; dispose just zeroizes it eagerly).\n useEffect(() => {\n return () => {\n // eslint-disable-next-line react-hooks/exhaustive-deps -- reading the LIVE ticket at teardown is the point: invalidate whatever activation is in flight at unmount; snapshotting it would defeat the guard\n ++genRef.current;\n identityRef.current?.dispose();\n identityRef.current = null;\n };\n }, []);\n\n const value = useMemo<Me2emContextValue>(\n () => ({ identity, hasIdentity: identity !== null, activateIdentity, clearIdentity }),\n [identity, activateIdentity, clearIdentity]\n );\n\n return <Me2emContext.Provider value={value}>{children}</Me2emContext.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type { Identity } from '@me2em/core';\n\nexport interface Me2emContextValue {\n /**\n * Active identity, or null. The previously active identity is\n * disposed (key material zeroed) the moment it is replaced by a new\n * activation or by {@link clearIdentity} — stale references fail\n * loudly with `DisposedError` per the core contract (BL-47).\n */\n identity: Identity | null;\n hasIdentity: boolean;\n /**\n * Activates an identity from raw seed bytes, replacing (and\n * disposing) any previously active identity. Resolves `false` if\n * derivation failed or the activation was superseded by a newer\n * activation / `clearIdentity()` while in flight.\n *\n * Does not consume the seed — the caller owns the buffer and must\n * zero it when done (BL-48).\n */\n activateIdentity: (seed: Uint8Array) => Promise<boolean>;\n /** Disposes the active identity (if any) and clears it. */\n clearIdentity: () => void;\n}\n\nexport const Me2emContext = createContext<Me2emContextValue | null>(null);\n\nexport function useMe2emContext(): Me2emContextValue {\n const ctx = useContext(Me2emContext);\n if (!ctx) {\n throw new Error('Me2em hooks must be used inside <Me2emProvider>');\n }\n return ctx;\n}\n","// packages/react/src/react/useCreateIdentity.ts\nimport {\n useReducer,\n useCallback,\n type Reducer,\n} from 'react';\nimport {\n generateSeedPhrase,\n get64ByteSeedFromMnemonic,\n type SeedStrength,\n type Identity,\n} from '@me2em/core';\nimport {\n identityFlowReducer,\n initialIdentityFlowState,\n} from '../headless/identity-flow.js';\nimport type { IdentityFlowState, IdentityFlowEvent, IdentityFlowStatus } from '../headless/types.js';\nimport { useMe2emContext } from './context.js';\n\nexport interface UseCreateIdentityResult {\n status: IdentityFlowStatus;\n seedWords: string[] | null;\n identity: Identity | null;\n error: string | null;\n generate: (opts?: { strength?: SeedStrength }) => void;\n confirmWords: () => Promise<void>;\n reset: () => void;\n fail: (message: string) => void;\n}\n\nexport function useCreateIdentity(): UseCreateIdentityResult {\n const { activateIdentity, clearIdentity, identity } = useMe2emContext();\n const [state, dispatch] = useReducer<\n Reducer<IdentityFlowState, IdentityFlowEvent>\n >(identityFlowReducer, initialIdentityFlowState);\n\n const generate = useCallback(\n (opts?: { strength?: SeedStrength }) => {\n const words = generateSeedPhrase(opts?.strength ?? 128);\n get64ByteSeedFromMnemonic(words).then((derivedSeed) => {\n dispatch({ type: 'GENERATE', words, seed: derivedSeed });\n }).catch(() => {\n dispatch({ type: 'FAIL', message: 'Failed to generate seed' });\n });\n },\n []\n );\n\n const confirmWords = useCallback(async () => {\n if (state.status !== 'seed-generated' || !state.identitySeed) return;\n dispatch({ type: 'CONFIRM_WORDS' });\n const ok = await activateIdentity(state.identitySeed);\n if (!ok) {\n dispatch({ type: 'FAIL', message: 'Identity activation failed' });\n }\n }, [state.status, state.identitySeed, activateIdentity]);\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' });\n clearIdentity();\n }, [clearIdentity]);\n\n const fail = useCallback((message: string) => {\n dispatch({ type: 'FAIL', message });\n }, []);\n\n return {\n status: state.status,\n seedWords: state.seedWords,\n identity,\n error: state.error,\n generate,\n confirmWords,\n reset,\n fail,\n };\n}\n","import type { IdentityFlowState, IdentityFlowEvent } from './types.js';\n\nexport const initialIdentityFlowState: IdentityFlowState = {\n status: 'idle',\n seedWords: null,\n identitySeed: null,\n error: null,\n};\n\nconst UNEXPECTED_EVENTS: Record<string, string> = {\n CONFIRM_WORDS: 'CONFIRM_WORDS called from idle — no seed to confirm',\n RESET: 'RESET called from idle — nothing to reset',\n};\n\nexport function identityFlowReducer(\n state: IdentityFlowState,\n event: IdentityFlowEvent\n): IdentityFlowState {\n const { type } = event;\n\n switch (state.status) {\n case 'idle': {\n if (type === 'GENERATE') {\n return {\n status: 'seed-generated',\n seedWords: event.words,\n identitySeed: event.seed,\n error: null,\n };\n }\n if (type === 'IMPORT') {\n return {\n status: 'imported',\n seedWords: null,\n identitySeed: event.seed,\n error: null,\n };\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n if (UNEXPECTED_EVENTS[type]) {\n console.warn(UNEXPECTED_EVENTS[type]);\n return state;\n }\n return state;\n }\n\n case 'seed-generated': {\n if (type === 'CONFIRM_WORDS') {\n return {\n status: 'verified',\n seedWords: null,\n identitySeed: state.identitySeed,\n error: null,\n };\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n console.warn(`Unexpected event ${type} in seed-generated`);\n return state;\n }\n\n case 'verified':\n case 'imported': {\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n if (type === 'FAIL') {\n return {\n status: 'error',\n seedWords: null,\n identitySeed: null,\n error: event.message,\n };\n }\n console.warn(`Unexpected event ${type} in ${state.status}`);\n return state;\n }\n\n case 'error': {\n if (type === 'RESET') {\n return initialIdentityFlowState;\n }\n console.warn(`Unexpected event ${type} in error`);\n return state;\n }\n\n default:\n return state;\n }\n}\n","// packages/react/src/react/useImportSeed.ts\nimport { useReducer, useCallback, type Reducer } from 'react';\nimport {\n validateSeedPhrase,\n get64ByteSeedFromMnemonic,\n type Identity,\n} from '@me2em/core';\nimport {\n identityFlowReducer,\n initialIdentityFlowState,\n} from '../headless/identity-flow.js';\nimport type { IdentityFlowState, IdentityFlowEvent, IdentityFlowStatus } from '../headless/types.js';\nimport { useMe2emContext } from './context.js';\n\nexport interface UseImportSeedResult {\n status: IdentityFlowStatus;\n identity: Identity | null;\n error: string | null;\n importWords: (words: string[], passphrase?: string) => Promise<void>;\n reset: () => void;\n}\n\nexport function useImportSeed(): UseImportSeedResult {\n const { activateIdentity, clearIdentity, identity } = useMe2emContext();\n const [state, dispatch] = useReducer<\n Reducer<IdentityFlowState, IdentityFlowEvent>\n >(identityFlowReducer, initialIdentityFlowState);\n\n const importWords = useCallback(\n async (words: string[], passphrase?: string) => {\n const validation = validateSeedPhrase(words);\n if (!validation.isValid) {\n dispatch({ type: 'FAIL', message: validation.error ?? 'Invalid seed phrase' });\n return;\n }\n try {\n const seed = await get64ByteSeedFromMnemonic(words, passphrase ?? '');\n dispatch({ type: 'IMPORT', seed });\n await activateIdentity(seed);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Import failed';\n dispatch({ type: 'FAIL', message });\n }\n },\n [activateIdentity]\n );\n\n const reset = useCallback(() => {\n dispatch({ type: 'RESET' });\n clearIdentity();\n }, [clearIdentity]);\n\n return {\n status: state.status,\n identity,\n error: state.error,\n importWords,\n reset,\n };\n}\n","// packages/react/src/react/useHandle.ts\nimport { useState, useEffect } from 'react';\nimport { type Handle } from '@me2em/core';\nimport { useMe2emContext } from './context.js';\n\n/**\n * Derives the named Handle for the active identity.\n *\n * Silent-failure contract (R7): on derivation failure the hook logs\n * via console.error and keeps returning null — there is no thrown or\n * returned error signal. The structured `{ handle, error }` shape is\n * a post-freeze v1.1 candidate (BL-50).\n */\nexport function useHandle(name: string): Handle | null {\n const { identity } = useMe2emContext();\n const [handle, setHandle] = useState<Handle | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async result when identity is gone; set-during-render refactor is a tracked post-rc.2 follow-up\n setHandle(null);\n return;\n }\n // Core performs full name canonicalization (NFKC, lowercase,\n // trim) and validation — no local normalization here.\n identity.deriveHandle(name).then((h) => {\n if (!cancelled) setHandle(h);\n }).catch((err) => {\n if (!cancelled) console.error('useHandle derivation failed', err);\n });\n return () => { cancelled = true; };\n }, [identity, name]);\n\n return handle;\n}\n","import { useState, useEffect } from 'react';\nimport { type SubHandle } from '@me2em/core';\nimport { useMe2emContext } from './context.js';\n\n/**\n * Derives the named SubHandle under the given Handle.\n *\n * Silent-failure contract (R7): on derivation failure the hook logs\n * via console.error and keeps returning null — there is no thrown or\n * returned error signal. The structured `{ handle, error }` shape is\n * a post-freeze v1.1 candidate (BL-50).\n */\nexport function useSubHandle(\n handleName: string,\n subName: string\n): SubHandle | null {\n const { identity } = useMe2emContext();\n const [subHandle, setSubHandle] = useState<SubHandle | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async result when identity is gone; set-during-render refactor is a tracked post-rc.2 follow-up\n setSubHandle(null);\n return;\n }\n identity\n .deriveSubHandle(handleName, subName)\n .then((h) => {\n if (!cancelled) setSubHandle(h);\n })\n .catch((err) => {\n if (!cancelled) console.error('useSubHandle derivation failed', err);\n });\n return () => { cancelled = true; };\n }, [identity, handleName, subName]);\n\n return subHandle;\n}\n","// packages/react/src/react/SeedPhraseDisplay.tsx\nimport { useReducer, useState } from 'react';\nimport {\n displayFlowReducer,\n initialDisplayState,\n} from '../headless/display-flow.js';\n\nexport interface SeedPhraseDisplayProps {\n /** Seed material is reachable via React DevTools in this window; by design. */\n words: string[];\n onDismiss?: () => void;\n onCopyAllowed?: () => void;\n}\n\n/**\n * R8: the permanent/transient line for a failed clipboard write is\n * \"can a retry in this window ever succeed?\". Without the async\n * Clipboard API (non-secure context, unsupported browser) it cannot —\n * the failure is permanent, the copy-all button is disabled and the\n * user is pointed at manual word selection. With the API present any\n * rejection (lost focus, dismissed permission prompt, OS hiccup)\n * stays transient: the button remains enabled and a fresh attempt\n * may go through.\n */\nfunction clipboardWriteAvailable(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n !!navigator.clipboard &&\n typeof navigator.clipboard.writeText === 'function'\n );\n}\n\nexport function SeedPhraseDisplay({\n words,\n onDismiss,\n onCopyAllowed,\n}: SeedPhraseDisplayProps) {\n const [state, dispatch] = useReducer(displayFlowReducer, initialDisplayState);\n const [copyError, setCopyError] = useState<'transient' | 'permanent' | null>(null);\n\n const { status, copyCount } = state;\n\n const handleReveal = () => {\n dispatch({ type: 'REVEAL', at: Date.now() });\n };\n\n const handleCopy = async () => {\n if (status === 'revealed' && copyCount === 0) {\n try {\n await navigator.clipboard.writeText(words.join(' '));\n dispatch({ type: 'COPY', at: Date.now() });\n setCopyError(null);\n onCopyAllowed?.();\n } catch {\n setCopyError(clipboardWriteAvailable() ? 'transient' : 'permanent');\n }\n }\n };\n\n const handleDismiss = () => {\n dispatch({ type: 'DISMISS' });\n onDismiss?.();\n };\n\n if (status === 'hidden') {\n return (\n <button data-testid=\"reveal\" onClick={handleReveal}>\n Reveal\n </button>\n );\n }\n\n if (status === 'dismissed') {\n return (\n <div data-testid=\"dismissed\">\n Phrase dismissed\n </div>\n );\n }\n\n return (\n <div>\n <ol data-testid=\"words\">\n {words.map((word, i) => (\n <li\n key={i}\n data-testid={`word-${i}`}\n data-position={i}\n >\n {word}\n </li>\n ))}\n </ol>\n {copyError && (\n <div data-testid=\"copy-error\" data-kind={copyError}>\n {copyError === 'permanent'\n ? 'Copy unavailable in this context — select the words manually'\n : 'Copy failed — try again'}\n </div>\n )}\n <button\n data-testid=\"copy-all\"\n onClick={handleCopy}\n disabled={copyCount === 1 || copyError === 'permanent'}\n >\n {copyCount === 1 ? 'Copied' : 'Copy all'}\n </button>\n <button data-testid=\"dismiss\" onClick={handleDismiss}>\n Hide\n </button>\n </div>\n );\n}\n","// packages/react/src/headless/display-flow.ts\n\nexport type DisplayStatus =\n | 'hidden'\n | 'revealed'\n | 'copied'\n | 'dismissed';\n\nexport interface DisplayState {\n status: DisplayStatus;\n copyCount: number;\n revealedAt: number | null;\n}\n\nexport type DisplayEvent =\n | { type: 'REVEAL'; at: number }\n | { type: 'COPY'; at: number }\n | { type: 'DISMISS' }\n | { type: 'RESET' };\n\nexport const initialDisplayState: DisplayState = {\n status: 'hidden',\n copyCount: 0,\n revealedAt: null,\n};\n\nexport function displayFlowReducer(\n state: DisplayState,\n event: DisplayEvent\n): DisplayState {\n const { type } = event;\n\n switch (state.status) {\n case 'hidden': {\n if (type === 'REVEAL') {\n return {\n status: 'revealed',\n copyCount: 0,\n revealedAt: event.at,\n };\n }\n if (type === 'COPY') {\n console.warn('Cannot copy while hidden');\n return state;\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n if (type === 'DISMISS') {\n console.warn('Cannot dismiss while hidden');\n return state;\n }\n console.warn(`Unexpected event ${type} in hidden`);\n return state;\n }\n\n case 'revealed': {\n if (type === 'COPY') {\n if (state.copyCount === 0) {\n return {\n status: 'copied',\n copyCount: 1,\n revealedAt: state.revealedAt,\n };\n }\n console.warn('Already copied — duplicate copy blocked');\n return state;\n }\n if (type === 'DISMISS') {\n return {\n status: 'dismissed',\n copyCount: state.copyCount,\n revealedAt: state.revealedAt,\n };\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in revealed`);\n return state;\n }\n\n case 'copied': {\n if (type === 'COPY') {\n console.warn('Already copied — duplicate copy blocked');\n return state;\n }\n if (type === 'DISMISS') {\n return {\n status: 'dismissed',\n copyCount: 1,\n revealedAt: state.revealedAt,\n };\n }\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in copied`);\n return state;\n }\n\n case 'dismissed': {\n if (type === 'RESET') {\n return initialDisplayState;\n }\n console.warn(`Unexpected event ${type} in dismissed`);\n return state;\n }\n\n default:\n return state;\n }\n}\n","// packages/react/src/react/SeedPhraseVerify.tsx\nimport { useReducer, useCallback, useState, useEffect, useRef } from 'react';\nimport {\n verifyFlowReducer,\n initVerifyState,\n} from '../headless/verify-flow.js';\nimport { wordlist } from '@me2em/core';\n\nexport interface SeedPhraseVerifyProps {\n realWords: string[];\n gridSize?: number;\n wordlist?: readonly string[];\n onVerified: () => void;\n onFailed?: (attempts: number) => void;\n}\n\nconst DEFAULT_GRID_SIZE_OFFSET = 8;\n\n/**\n * Confirmation grid for a generated seed phrase.\n *\n * The grid shuffle is deterministic by design (fixed base seed 42;\n * each retry shifts it by `+retryCount + 1`) for testability and SSR\n * stability — it is NOT a security control: decoy words come from the\n * public wordlist, so the layout leaks nothing (BL-49). A\n * `randomSeed` prop is deliberately not offered in the frozen 1.0 API.\n */\nexport function SeedPhraseVerify({\n realWords,\n gridSize,\n wordlist: propWordlist,\n onVerified,\n onFailed,\n}: SeedPhraseVerifyProps) {\n const grid = gridSize ?? realWords.length + DEFAULT_GRID_SIZE_OFFSET;\n const allWords = propWordlist ?? wordlist;\n const baseSeed = 42;\n\n const [state, dispatch] = useReducer(\n verifyFlowReducer,\n {\n realWords,\n gridSize: grid,\n randomSeed: baseSeed,\n allWords,\n } as const,\n (params) => initVerifyState(params.realWords, params.gridSize, params.randomSeed, params.allWords)\n );\n\n const [retryCount, setRetryCount] = useState(0);\n\n const { status, grid: gridData, selected } = state;\n\n // Guard against re-entrant callbacks after status change\n const notifiedRef = useRef(false);\n\n const handleSelect = useCallback(\n (index: number) => {\n if (status === 'correct' || status === 'incorrect') return;\n if (selected.length >= gridData.correctIndices.length) return;\n dispatch({ type: 'SELECT', index });\n },\n [status, selected.length, gridData.correctIndices.length]\n );\n\n // Single effect: auto-check when enough words selected, then notify result\n useEffect(() => {\n if (notifiedRef.current) return;\n\n // Auto-check when enough words selected\n if (\n selected.length === gridData.correctIndices.length &&\n selected.length > 0 &&\n status === 'collecting'\n ) {\n dispatch({ type: 'CHECK' });\n // After dispatch, status will change synchronously in the reducer\n // but the new state won't be reflected until next render.\n // We need to check the pending result in the next effect cycle.\n return;\n }\n\n // Handle result after status update\n if (status === 'correct') {\n notifiedRef.current = true;\n onVerified();\n } else if (status === 'incorrect') {\n notifiedRef.current = true;\n onFailed?.(state.attempts);\n }\n }, [selected.length, gridData.correctIndices.length, status, state.attempts, onVerified, onFailed]);\n\n const handleRetry = () => {\n notifiedRef.current = false;\n setRetryCount((c) => c + 1);\n dispatch({\n type: 'RESET',\n realWords,\n gridSize: grid,\n randomSeed: baseSeed + retryCount + 1,\n allWords,\n });\n };\n\n return (\n <div>\n <div data-testid=\"progress\">\n Select word {selected.length + 1} of {realWords.length}\n </div>\n <ol data-testid=\"grid\">\n {gridData.words.map((word, i) => {\n const isSelected = selected.includes(i);\n const isDisabled =\n isSelected || status === 'correct' || status === 'incorrect';\n return (\n <li key={i}>\n <button\n data-testid={`cell-${i}`}\n data-word={word}\n disabled={isDisabled}\n onClick={() => handleSelect(i)}\n >\n {word}\n </button>\n </li>\n );\n })}\n </ol>\n {status === 'incorrect' && (\n <button data-testid=\"retry\" onClick={handleRetry}>\n Try again\n </button>\n )}\n </div>\n );\n}\n","// packages/react/src/headless/seed-words.ts\n\nexport function shuffleWithSeed<T>(items: T[], seed: number): T[] {\n const arr = [...items];\n let currentSeed = seed;\n function nextRand(): number {\n currentSeed = (currentSeed * 1664525 + 1013904223) >>> 0;\n return currentSeed;\n }\n for (let i = arr.length - 1; i > 0; i--) {\n const j = nextRand() % (i + 1);\n [arr[i], arr[j]] = [arr[j], arr[i]];\n }\n return arr;\n}\n\nexport function buildVerificationGrid(\n realWords: string[],\n gridSize: number,\n randomSeed: number,\n allWords: readonly string[]\n): { words: string[]; correctIndices: number[] } {\n if (gridSize < realWords.length) {\n throw new Error(\n `gridSize (${gridSize}) must be >= realWords.length (${realWords.length})`\n );\n }\n\n const shuffled = shuffleWithSeed(realWords, randomSeed);\n const realSet = new Set(realWords);\n const available = allWords.filter((w) => !realSet.has(w));\n const shuffledDecoys = shuffleWithSeed(available, randomSeed + 1);\n\n const needed = gridSize - realWords.length;\n const decoys = shuffledDecoys.slice(0, needed);\n\n const words = [...shuffled, ...decoys];\n if (words.length !== gridSize) {\n throw new Error(\n `Cannot fill grid: wordlist too small (got ${words.length}, need ${gridSize})`\n );\n }\n\n // Map each word occurrence to a DISTINCT grid position, so\n // repeated words in the phrase get their own cells.\n const positions = new Map<string, number[]>();\n words.forEach((w, i) => {\n const list = positions.get(w);\n if (list) list.push(i);\n else positions.set(w, [i]);\n });\n\n const correctIndices = realWords.map((w) => {\n const list = positions.get(w);\n if (!list || list.length === 0) {\n throw new Error(`Internal error: word \"${w}\" missing from grid`);\n }\n return list.shift() as number;\n });\n\n return { words, correctIndices };\n}\n\n/**\n * Selection is compared IN ORDER: selected[i] must equal\n * correctIndices[i]. The i-th click must be the i-th word of the\n * phrase. Repeated phrase words map to distinct grid cells.\n */\nexport function isGridSelectionCorrect(\n grid: { words: string[]; correctIndices: number[] },\n selected: number[]\n): boolean {\n if (selected.length !== grid.correctIndices.length) return false;\n return selected.every((v, i) => v === grid.correctIndices[i]);\n}\n","// packages/react/src/headless/verify-flow.ts\nimport {\n buildVerificationGrid,\n isGridSelectionCorrect,\n} from './seed-words.js';\n\nexport type VerifyStatus = 'collecting' | 'correct' | 'incorrect';\n\nexport interface VerifyState {\n status: VerifyStatus;\n grid: { words: string[]; correctIndices: number[] };\n selected: number[];\n attempts: number;\n}\n\nexport type VerifyEvent =\n | { type: 'SELECT'; index: number }\n | { type: 'DESELECT'; index: number }\n | { type: 'CHECK' }\n | {\n type: 'RESET';\n realWords: string[];\n gridSize: number;\n randomSeed: number;\n allWords: readonly string[];\n };\n\nexport function initVerifyState(\n realWords: string[],\n gridSize: number,\n randomSeed: number,\n allWords: readonly string[]\n): VerifyState {\n const grid = buildVerificationGrid(realWords, gridSize, randomSeed, allWords);\n return {\n status: 'collecting',\n grid,\n selected: [],\n attempts: 0,\n };\n}\n\nexport function verifyFlowReducer(\n state: VerifyState,\n event: VerifyEvent\n): VerifyState {\n const { type } = event;\n\n if (type === 'RESET') {\n return initVerifyState(\n event.realWords,\n event.gridSize,\n event.randomSeed,\n event.allWords\n );\n }\n\n // Terminal states: only RESET allowed\n if (state.status === 'correct' || state.status === 'incorrect') {\n console.warn(\n `Cannot ${type} in ${state.status} state — use RESET to retry`\n );\n return state;\n }\n\n // collecting state\n if (type === 'SELECT') {\n const { index } = event;\n const { grid, selected } = state;\n if (selected.includes(index)) {\n console.warn(`Index ${index} already selected — use DESELECT`);\n return state;\n }\n if (index < 0 || index >= grid.words.length) {\n console.warn(`Index ${index} out of grid bounds`);\n return state;\n }\n return { ...state, selected: [...selected, index] };\n }\n\n if (type === 'DESELECT') {\n const { index } = event;\n const idx = state.selected.indexOf(index);\n if (idx === -1) {\n console.warn(`Index ${index} not selected`);\n return state;\n }\n return {\n ...state,\n selected: state.selected.filter((_, i) => i !== idx),\n };\n }\n\n if (type === 'CHECK') {\n const { grid, selected, attempts } = state;\n if (selected.length !== grid.correctIndices.length) {\n console.warn(\n `Cannot check: selected ${selected.length}, need ${grid.correctIndices.length}`\n );\n return state;\n }\n const correct = isGridSelectionCorrect(grid, selected);\n return {\n ...state,\n status: correct ? 'correct' : 'incorrect',\n attempts: attempts + 1,\n };\n }\n\n console.warn(`Unexpected event ${type} in collecting`);\n return state;\n}\n","// packages/react/src/react/SeedPhraseImport.tsx\nimport { useReducer, useEffect, useRef } from 'react';\nimport {\n importFlowReducer,\n initImportState,\n type ImportState,\n} from '../headless/import-flow.js';\nimport { wordlist } from '@me2em/core';\n\nexport interface SeedPhraseImportProps {\n expectedCount?: 12 | 24;\n wordlist?: readonly string[];\n onImported: (words: string[]) => void;\n}\n\nexport function SeedPhraseImport({\n expectedCount: propCount,\n wordlist: propWordlist,\n onImported,\n}: SeedPhraseImportProps) {\n const allWords = propWordlist ?? wordlist;\n\n const [state, dispatch] = useReducer(\n importFlowReducer,\n { expectedCount: propCount ?? 12 } as const,\n (params) => initImportState(params.expectedCount)\n );\n\n const count = state.expectedCount;\n\n const prevStatusRef = useRef<ImportState['status']>(state.status);\n\n const lastImportedRef = useRef<string | null>(null);\n\n // Auto-check on every word change\n useEffect(() => {\n dispatch({ type: 'CHECK' });\n }, [state.words]);\n\n // Call onImported once when transitioning to valid\n useEffect(() => {\n const prevStatus = prevStatusRef.current;\n prevStatusRef.current = state.status;\n\n if (state.status === 'valid' && prevStatus !== 'valid') {\n const key = (state.words as string[]).join('\\0');\n if (lastImportedRef.current !== key) {\n lastImportedRef.current = key;\n onImported(state.words as string[]);\n }\n }\n }, [state.status, state.words, onImported]);\n\n const handleCountChange = (n: 12 | 24) => {\n dispatch({ type: 'INIT', expectedCount: n });\n };\n\n const handleWordChange = (position: number, value: string) => {\n dispatch({ type: 'SET_WORD', position, word: value });\n };\n\n const isWordInvalid = (position: number): boolean => {\n if (state.status !== 'invalid' && state.status !== 'valid') return false;\n const word = state.words[position];\n if (word === null) return false;\n return !allWords.includes(word);\n };\n\n const isImportEnabled = state.status === 'valid';\n\n return (\n <div>\n <div>\n <button\n data-testid=\"count-12\"\n onClick={() => handleCountChange(12)}\n disabled={count === 12}\n >\n 12 words\n </button>\n <button\n data-testid=\"count-24\"\n onClick={() => handleCountChange(24)}\n disabled={count === 24}\n >\n 24 words\n </button>\n </div>\n <div data-testid=\"inputs\">\n {Array.from({ length: count }, (_, i) => (\n <input\n key={i}\n data-testid={`word-${i}`}\n aria-label={`Word ${i + 1}`}\n value={state.words[i] ?? ''}\n onChange={(e) => handleWordChange(i, e.target.value)}\n data-invalid={isWordInvalid(i) ? 'true' : undefined}\n />\n ))}\n </div>\n <button\n data-testid=\"import\"\n disabled={!isImportEnabled}\n onClick={() => dispatch({ type: 'CHECK' })}\n >\n Import\n </button>\n </div>\n );\n}\n","// packages/react/src/headless/import-flow.ts\nimport { validateSeedPhrase } from '@me2em/core';\n\nexport type ImportStatus = 'entering' | 'valid' | 'invalid';\n\nexport interface ImportState {\n status: ImportStatus;\n words: (string | null)[];\n expectedCount: 12 | 24;\n}\n\nexport type ImportEvent =\n | { type: 'INIT'; expectedCount: 12 | 24 }\n | { type: 'SET_WORD'; position: number; word: string }\n | { type: 'CLEAR_WORD'; position: number }\n | { type: 'CHECK' }\n | { type: 'RESET' };\n\nexport function initImportState(expectedCount: 12 | 24): ImportState {\n return {\n status: 'entering',\n words: Array(expectedCount).fill(null),\n expectedCount,\n };\n}\n\nexport function importFlowReducer(\n state: ImportState,\n event: ImportEvent\n): ImportState {\n const { type } = event;\n\n if (type === 'INIT') {\n return initImportState(event.expectedCount);\n }\n\n if (type === 'RESET') {\n return initImportState(state.expectedCount);\n }\n\n if (type === 'SET_WORD') {\n const { position, word } = event;\n if (position < 0 || position >= state.expectedCount) {\n console.warn(`SET_WORD: position ${position} out of bounds`);\n return state;\n }\n const normalized = word.toLowerCase().trim();\n const newWords = [...state.words] as (string | null)[];\n newWords[position] = normalized;\n return { ...state, words: newWords, status: 'entering' };\n }\n\n if (type === 'CLEAR_WORD') {\n const { position } = event;\n if (position < 0 || position >= state.expectedCount) {\n console.warn(`CLEAR_WORD: position ${position} out of bounds`);\n return state;\n }\n const newWords = [...state.words] as (string | null)[];\n newWords[position] = null;\n return { ...state, words: newWords, status: 'entering' };\n }\n\n if (type === 'CHECK') {\n const { words } = state;\n const nullIdx = words.findIndex((w) => w === null);\n if (nullIdx !== -1) {\n return {\n ...state,\n status: 'invalid',\n };\n }\n const result = validateSeedPhrase(words as string[]);\n return {\n ...state,\n status: result.isValid ? 'valid' : 'invalid',\n };\n }\n\n console.warn(`Unexpected event ${type} in import-flow`);\n return state;\n}\n","// packages/react/src/react/PassphraseInput.tsx\nimport { useCallback } from 'react';\n\nexport interface PassphraseInputProps {\n onPassphraseChange: (p: string) => void;\n showWarnings?: boolean;\n}\n\nexport function PassphraseInput({\n onPassphraseChange,\n showWarnings = true,\n}: PassphraseInputProps) {\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const value = e.target.value.normalize('NFKC');\n onPassphraseChange(value);\n },\n [onPassphraseChange]\n );\n\n return (\n <div>\n <input\n type=\"password\"\n data-testid=\"passphrase\"\n aria-label=\"BIP39 passphrase\"\n onChange={handleChange}\n />\n {showWarnings && (\n <div data-testid=\"warning\">\n Passphrase is case-sensitive and NOT recoverable. A different\n passphrase silently derives a different wallet.\n </div>\n )}\n </div>\n );\n}\n","// packages/react/src/react/useIdentityContext.ts\nimport { useState, useCallback, useEffect, useRef } from 'react';\nimport type { Identity } from '@me2em/core';\nimport type { IdentityContextStorage, IdentityContextData } from '../headless/identity-context-types.js';\nimport { useMe2emContext } from './context.js';\n\nfunction publicKeyToId(publicKey: Uint8Array): string {\n return Array.from(publicKey)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n\nfunction identityToId(identity: Identity | null): string | null {\n return identity ? publicKeyToId(identity.getPublicKey()) : null;\n}\n\nexport interface UseIdentityContextResult {\n context: IdentityContextData | null;\n isLoaded: boolean;\n error: string | null;\n save: (data: IdentityContextData) => Promise<void>;\n clear: () => Promise<void>;\n /** Reload the cache for the current identity (after sync). */\n refresh: () => Promise<void>;\n}\n\nexport function useIdentityContext(\n storage: IdentityContextStorage\n): UseIdentityContextResult {\n const { identity } = useMe2emContext();\n const [context, setContext] = useState<IdentityContextData | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const prevIdentityIdRef = useRef<string | null>(null);\n // Generation ticket for async state transitions. Load (identity\n // change / `refresh()`), `save()` and `clear()` each take a ticket;\n // a resolution may only commit state while its ticket is the newest\n // one. Without this, an in-flight load started before a save could\n // resolve after it and clobber the just-saved state with pre-save\n // data (out-of-order async update — observed as a rare CI flake in\n // the rerender spec; pinned there by a gated-storage test).\n const opRef = useRef(0);\n const identityRef = useRef<Identity | null>(null);\n // eslint-disable-next-line react-hooks/refs -- render-time mirror of the latest identity: save()/clear() are stable callbacks (storage-only deps) and must verify at commit time, after their await, that the identity they started for is still the active one\n identityRef.current = identity;\n\n useEffect(() => {\n let cancelled = false;\n\n if (!identity) {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- clearing the stale async state when identity is gone (generation-ticket effect from the load-race fix); set-during-render refactor is a tracked post-rc.2 follow-up\n setContext(null);\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n const identityId = publicKeyToId(identity.getPublicKey());\n\n // If identity changed, close old storage and open new\n if (prevIdentityIdRef.current !== null && prevIdentityIdRef.current !== identityId) {\n storage.close().catch(() => { /* ignore close errors */ });\n }\n\n prevIdentityIdRef.current = identityId;\n\n // Open storage for this identity and load cached context (atomic)\n const op = ++opRef.current;\n storage.openAndLoad(identityId)\n .then((data) => {\n if (cancelled || op !== opRef.current) return;\n setContext(data);\n setIsLoaded(true);\n setError(null);\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err instanceof Error ? err.message : String(err));\n setIsLoaded(true);\n });\n\n return () => {\n cancelled = true;\n };\n }, [identity, storage]);\n\n const save = useCallback(async (data: IdentityContextData): Promise<void> => {\n const idAtStart = identityToId(identityRef.current);\n opRef.current += 1; // invalidate any in-flight load/refresh\n await storage.save(data);\n // If the identity switched while the write was in flight, state\n // ownership has moved to the new identity's load effect — committing\n // here would surface identity A's context under identity B. The\n // generation ticket above only orders this op against in-flight\n // loads; it does not know about identity switches.\n if (identityToId(identityRef.current) !== idAtStart) return;\n setContext(data);\n setError(null);\n }, [storage]);\n\n const clear = useCallback(async (): Promise<void> => {\n const idAtStart = identityToId(identityRef.current);\n opRef.current += 1; // invalidate any in-flight load/refresh\n await storage.clear();\n // Same ownership rule as save(): a clear() resolving after an\n // identity switch must not blank the new identity's freshly\n // loaded context.\n if (identityToId(identityRef.current) !== idAtStart) return;\n setContext(null);\n setError(null);\n }, [storage]);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!identity) return;\n const identityId = publicKeyToId(identity.getPublicKey());\n const op = ++opRef.current;\n const data = await storage.openAndLoad(identityId);\n if (op !== opRef.current) return;\n setContext(data);\n }, [identity, storage]);\n\n return { context, isLoaded, error, save, clear, refresh };\n}\n","// packages/react/src/react/useSession.ts\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport type { Handle } from '@me2em/core';\nimport { Session } from '@me2em/core';\n\n/**\n * Options for useSession, memoized via deep comparison of\n * `handle` and `options` refs on each render.\n *\n * @category Types\n */\nexport interface UseSessionOptions {\n /** Intended audience (recipient) of this session. */\n audience: string;\n /** Permission scopes granted in this session. */\n scopes: string[];\n /** Time-to-live in seconds. */\n ttl: number;\n}\n\n/**\n * Result of the useSession hook.\n *\n * @category Types\n */\nexport interface UseSessionResult {\n /** The active session, or null if no handle or session creation failed. */\n session: Session | null;\n /** Whether the session (or its absence) has been determined. */\n isLoaded: boolean;\n /** Error string if session creation or renewal failed. */\n error: string | null;\n /** Whether the current session has expired. */\n isExpired: boolean;\n /** Manually renew the session (creates a new one with a fresh jti). */\n renew: () => void;\n}\n\n/**\n * Hook that creates and manages a signed session token for a Handle.\n *\n * When `handle` is provided, creates a Session with the given options.\n * Supports auto-renewal: when `autoRenew` is true (default), a timer\n * fires at half the session TTL to create a fresh session with a new\n * jti (the previous token remains valid until its own expiry, but the\n * lineage identity is preserved through `handle`).\n *\n * On unmount, all timers are cleaned up to prevent state updates after\n * the component is gone.\n *\n * Dependencies: `[handle, renew]` — the options object is read through\n * a ref, so passing a fresh object literal on every render does NOT\n * re-trigger session creation. Only changes to `handle` (or to the\n * renewal function identity, which follows `handle`) do.\n *\n * @param handle - The Handle to sign the session, or null.\n * @param options - Session options (audience, scopes, ttl).\n * @param autoRenew - Whether to auto-renew at half-life (default: true).\n * @returns Session state with manual `renew` control.\n *\n * @example\n * ```ts\n * const handle = useHandle('my-handle');\n * const { session, isExpired, renew } = useSession(handle, {\n * audience: 'app.example.com',\n * scopes: ['read', 'write'],\n * ttl: 3600,\n * });\n * ```\n */\nexport function useSession(\n handle: Handle | null,\n options: UseSessionOptions,\n autoRenew: boolean = true\n): UseSessionResult {\n const [session, setSession] = useState<Session | null>(null);\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const optionsRef = useRef(options);\n const mountedRef = useRef(true);\n\n // Mirror the latest options into the ref after every commit —\n // ref writes belong in effects, not during render (react-hooks/refs).\n useEffect(() => {\n optionsRef.current = options;\n });\n\n const clearTimer = useCallback(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n // Cleanup on unmount\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n clearTimer();\n };\n }, [clearTimer]);\n\n const renew = useCallback(() => {\n clearTimer();\n const opts = optionsRef.current;\n\n if (!handle) {\n if (!mountedRef.current) return;\n setSession(null);\n setIsLoaded(true);\n setError(null);\n return;\n }\n\n Session.create(handle, {\n audience: opts.audience,\n scopes: opts.scopes,\n ttl: opts.ttl,\n }).then((s) => {\n if (!mountedRef.current) return;\n setSession(s);\n setIsLoaded(true);\n setError(null);\n\n // Schedule auto-renewal at half-life\n if (autoRenew) {\n const halfLife = Math.floor((s.expiresAt * 1000 - Date.now()) / 2);\n if (halfLife > 0) {\n timerRef.current = setTimeout(() => {\n // eslint-disable-next-line react-hooks/immutability -- self-rescheduling renewal timer: the timer is cleared and re-created whenever renew itself changes (deps + clearTimer), so the captured binding is always the current one\n renew();\n }, Math.max(halfLife, 1000));\n }\n }\n }).catch((err) => {\n if (!mountedRef.current) return;\n setSession(null);\n setIsLoaded(true);\n setError(err instanceof Error ? err.message : String(err));\n });\n }, [handle, autoRenew, clearTimer]);\n\n // Create session when handle or options change\n useEffect(() => {\n if (mountedRef.current) {\n renew();\n }\n }, [handle, renew]);\n\n return {\n session,\n isLoaded,\n error,\n isExpired: session ? session.isExpired() : false,\n renew,\n };\n}\n","export { identityFlowReducer, initialIdentityFlowState } from './identity-flow.js';\nexport type {\n IdentityFlowState,\n IdentityFlowStatus,\n IdentityFlowEvent,\n} from './types.js';\nexport {\n buildVerificationGrid,\n isGridSelectionCorrect,\n shuffleWithSeed,\n} from './seed-words.js';\nexport {\n displayFlowReducer,\n initialDisplayState,\n} from './display-flow.js';\nexport type {\n DisplayState,\n DisplayStatus,\n DisplayEvent,\n} from './display-flow.js';\nexport {\n verifyFlowReducer,\n initVerifyState,\n} from './verify-flow.js';\nexport type {\n VerifyState,\n VerifyStatus,\n VerifyEvent,\n} from './verify-flow.js';\nexport {\n importFlowReducer,\n initImportState,\n} from './import-flow.js';\nexport type {\n ImportState,\n ImportStatus,\n ImportEvent,\n} from './import-flow.js';\nexport {\n IdentityContextIDBStorage,\n} from './identity-context-storage.js';\nexport type {\n IdentityContextData,\n IdentityContextStorage,\n} from './identity-context-types.js';\nexport {\n deriveCacheKey,\n encryptContext,\n decryptContext,\n} from './context-crypto.js';\nexport type {\n ContextEnvelope,\n} from './context-crypto.js';\n","// packages/react/src/headless/context-crypto.ts\nimport { base64urlEncode, base64urlDecode } from '@me2em/core';\nimport type { IdentityContextData } from './identity-context-types.js';\n\n// Base64url encoding for ContextEnvelope fields goes through the audited\n// @scure/base codec re-exported by @me2em/core (audit A3 / R4) — one\n// base64 implementation across packages, no hand-rolled alphabet handling.\n// Envelope fields are always unpadded: the only writer is encryptContext\n// below, so base64urlDecode's strict no-padding rule never bites.\n\nexport interface ContextEnvelope {\n v: 1;\n /** fresh HKDF salt этого логина (b64). Нужен только при\n * первичной деривации; persisted CryptoKey переживает\n * перезапуск без него. */\n salt: string;\n iv: string; // b64, 12B\n ct: string; // b64, ciphertext || GCM tag\n}\n\n/**\n * Derives a fresh, per-login cache key from login-time secret\n * material and wraps it as a NON-EXTRACTABLE persisted CryptoKey.\n * Raw key bytes exist only transiently during importKey.\n *\n * @param loginMaterial - high-entropy bytes available at login\n * (derived from the seed while it is still in memory). Caller-owned:\n * deriveCacheKey reads but never wipes them — zero the buffer (and\n * the seed it came from) when done (R6).\n * @param salt - FRESH random bytes for this login. Never reuse a\n * salt across logins: the whole point is that a compromised\n * cacheKey from session N is useless in session N+1.\n */\nexport async function deriveCacheKey(\n loginMaterial: Uint8Array,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const base = await crypto.subtle.importKey(\n 'raw', loginMaterial as BufferSource, 'HKDF', false, ['deriveBits']\n );\n const bits = await crypto.subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256',\n salt: salt as BufferSource,\n info: new TextEncoder().encode('me2em/cache-wrap/v1') },\n base, 256\n );\n return crypto.subtle.importKey(\n 'raw', bits, { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']\n );\n}\n\nexport async function encryptContext(\n key: CryptoKey, data: IdentityContextData\n): Promise<ContextEnvelope> {\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const salt = crypto.getRandomValues(new Uint8Array(32));\n const pt = new TextEncoder().encode(JSON.stringify(data));\n const ct = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv as BufferSource }, key, pt\n );\n return { v: 1, salt: base64urlEncode(salt), iv: base64urlEncode(iv),\n ct: base64urlEncode(new Uint8Array(ct)) };\n}\n\nexport async function decryptContext(\n key: CryptoKey, envelope: ContextEnvelope\n): Promise<IdentityContextData> {\n if (envelope.v !== 1) throw new Error('Unsupported envelope version');\n const pt = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: base64urlDecode(envelope.iv) as BufferSource },\n key, base64urlDecode(envelope.ct) as BufferSource\n );\n return JSON.parse(new TextDecoder().decode(pt)) as IdentityContextData;\n}\n","// packages/react/src/headless/identity-context-storage.ts\nimport { encryptContext, decryptContext } from './context-crypto.js';\nimport type { IdentityContextData, IdentityContextStorage } from './identity-context-types.js';\nexport type { IdentityContextData };\n\nconst DB_NAME_PREFIX = 'me2em_';\nconst STORE_NAME = 'context';\nconst KEYS_STORE_NAME = 'cryptoKeys';\nconst CACHE_KEY = 'cache';\n\nfunction hashToHex(data: Uint8Array): string {\n const hexParts: string[] = [];\n for (const byte of data) {\n hexParts.push(byte.toString(16).padStart(2, '0'));\n }\n return hexParts.join('');\n}\n\nasync function sha256Hex(data: Uint8Array): Promise<string> {\n const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n const digest: ArrayBuffer = await crypto.subtle.digest('SHA-256', buffer);\n return hashToHex(new Uint8Array(digest));\n}\n\nasync function dbNameForIdentity(identityId: string): Promise<string> {\n const hash = await sha256Hex(new TextEncoder().encode(identityId));\n return DB_NAME_PREFIX + hash.slice(0, 16);\n}\n\nexport class IdentityContextIDBStorage implements IdentityContextStorage {\n private db: IDBDatabase | null = null;\n private currentIdentityId: string | null = null;\n private cacheKey: CryptoKey | null = null;\n\n private async getDb(): Promise<IDBDatabase> {\n if (!this.db) {\n throw new Error('Storage not opened — call open() first');\n }\n return this.db;\n }\n\n async open(identityId: string): Promise<void> {\n if (this.currentIdentityId !== null && this.currentIdentityId !== identityId) {\n await this.close();\n }\n\n this.currentIdentityId = identityId;\n const name = await dbNameForIdentity(identityId);\n\n return new Promise<void>((resolve, reject) => {\n const request = indexedDB.open(name, 1);\n\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(STORE_NAME)) {\n db.createObjectStore(STORE_NAME, { keyPath: 'key' });\n }\n if (!db.objectStoreNames.contains(KEYS_STORE_NAME)) {\n db.createObjectStore(KEYS_STORE_NAME, { keyPath: 'key' });\n }\n };\n\n request.onsuccess = () => {\n this.db = request.result;\n resolve();\n };\n\n request.onerror = () => {\n reject(new Error('IndexedDB open failed: ' + request.error?.message));\n };\n });\n }\n\n async openAndLoad(identityId: string): Promise<IdentityContextData | null> {\n await this.open(identityId);\n const key = this.cacheKey ?? await this.loadPersistedKey();\n if (!key) {\n return null;\n }\n this.cacheKey = key;\n return this.load();\n }\n\n async save(data: IdentityContextData): Promise<void> {\n const key = this.cacheKey;\n if (!key) {\n throw new Error('No cache key set — call setCacheKey before save');\n }\n const envelope = await encryptContext(key, data);\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.put({ key: CACHE_KEY, envelope });\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('Save failed: ' + tx.error?.message));\n });\n }\n\n async load(): Promise<IdentityContextData | null> {\n const db = await this.getDb();\n const key = this.cacheKey;\n if (!key) {\n return null;\n }\n return new Promise<IdentityContextData | null>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readonly');\n const store = tx.objectStore(STORE_NAME);\n const request = store.get(CACHE_KEY);\n\n request.onsuccess = () => {\n const record = request.result;\n if (!record || !record.envelope) {\n resolve(null);\n return;\n }\n decryptContext(key, record.envelope)\n .then((data) => {\n if (data.identityId !== this.currentIdentityId) {\n resolve(null);\n return;\n }\n resolve(data);\n })\n .catch(() => {\n resolve(null);\n });\n };\n\n request.onerror = () => reject(new Error('Load failed: ' + request.error?.message));\n });\n }\n\n async clear(): Promise<void> {\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(STORE_NAME, 'readwrite');\n const store = tx.objectStore(STORE_NAME);\n store.delete(CACHE_KEY);\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('Clear failed: ' + tx.error?.message));\n });\n }\n\n async close(): Promise<void> {\n const db = this.db;\n this.db = null;\n this.currentIdentityId = null;\n this.cacheKey = null;\n if (db) {\n db.close();\n }\n }\n\n setCacheKey(key: CryptoKey): void {\n this.cacheKey = key;\n }\n\n getCacheKey(): CryptoKey | null {\n return this.cacheKey;\n }\n\n async persistCacheKey(): Promise<void> {\n const key = this.cacheKey;\n if (!key) {\n throw new Error('No cache key to persist — call setCacheKey first');\n }\n const db = await this.getDb();\n return new Promise<void>((resolve, reject) => {\n const tx = db.transaction(KEYS_STORE_NAME, 'readwrite');\n const store = tx.objectStore(KEYS_STORE_NAME);\n store.put({ key: 'cache', cryptoKey: key });\n\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(new Error('persistCacheKey failed: ' + tx.error?.message));\n });\n }\n\n async loadPersistedKey(): Promise<CryptoKey | null> {\n const db = this.db;\n if (!db) {\n return null;\n }\n return new Promise<CryptoKey | null>((resolve) => {\n const tx = db.transaction(KEYS_STORE_NAME, 'readonly');\n const store = tx.objectStore(KEYS_STORE_NAME);\n const request = store.get('cache');\n\n request.onsuccess = () => {\n const record = request.result;\n if (!record || !record.cryptoKey) {\n resolve(null);\n return;\n }\n resolve(record.cryptoKey);\n };\n\n request.onerror = () => {\n resolve(null);\n };\n });\n }\n}\n"],"mappings":"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","names":["useCallback","useCallback","useReducer","useCallback","get64ByteSeedFromMnemonic","useReducer","useCallback","get64ByteSeedFromMnemonic","useState","useEffect","useState","useEffect","useState","useEffect","useState","useEffect","useReducer","useState","jsx","useReducer","useState","useReducer","useCallback","useState","useEffect","useRef","jsx","jsxs","useReducer","useState","useRef","useCallback","useEffect","useReducer","useEffect","useRef","validateSeedPhrase","wordlist","jsx","jsxs","useReducer","useRef","useEffect","useCallback","jsx","jsxs","useState","useCallback","useEffect","useRef","useState","useRef","useEffect","useCallback","useState","useEffect","useCallback","useRef"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@me2em/react",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.3",
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",
@@ -24,7 +24,9 @@ export interface ContextEnvelope {
24
24
  * Raw key bytes exist only transiently during importKey.
25
25
  *
26
26
  * @param loginMaterial - high-entropy bytes available at login
27
- * (derived from the seed while it is still in memory).
27
+ * (derived from the seed while it is still in memory). Caller-owned:
28
+ * deriveCacheKey reads but never wipes them — zero the buffer (and
29
+ * the seed it came from) when done (R6).
28
30
  * @param salt - FRESH random bytes for this login. Never reuse a
29
31
  * salt across logins: the whole point is that a compromised
30
32
  * cacheKey from session N is useless in session N+1.
@@ -16,6 +16,15 @@ export interface IdentityContextStorage {
16
16
  save(data: IdentityContextData): Promise<void>;
17
17
  load(): Promise<IdentityContextData | null>;
18
18
  clear(): Promise<void>;
19
+ /**
20
+ * Closes the IDB connection. Caller-owned lifecycle (R5): close()
21
+ * also drops the in-memory cache key and the open identity binding,
22
+ * so every further operation throws "Storage not opened" until the
23
+ * storage is reopened via open()/openAndLoad() and the cache key is
24
+ * set again. open() with a different identityId closes the previous
25
+ * connection automatically; same-identity open() after close()
26
+ * simply opens a fresh connection.
27
+ */
19
28
  close(): Promise<void>;
20
29
  /** Open storage for identity and atomically load cached context. */
21
30
  openAndLoad(identityId: string): Promise<IdentityContextData | null>;
@@ -9,6 +9,13 @@ export type IdentityFlowStatus =
9
9
  export interface IdentityFlowState {
10
10
  status: IdentityFlowStatus;
11
11
  seedWords: string[] | null;
12
+ /**
13
+ * Raw seed material, retained in state after 'verified'/'imported'
14
+ * by design — the flow does NOT self-consume it (BL-48): headless
15
+ * consumers may still read it at this point. Reachable via devtools
16
+ * in this window (R15 category). An explicit `discardSeed()` action
17
+ * is the planned v1.1 opt-in; until then the app owns dropping it.
18
+ */
12
19
  identitySeed: Uint8Array | null;
13
20
  error: string | null;
14
21
  }
@@ -2,6 +2,8 @@ import {
2
2
  useState,
3
3
  useCallback,
4
4
  useMemo,
5
+ useEffect,
6
+ useRef,
5
7
  type ReactNode,
6
8
  } from 'react';
7
9
  import { Identity } from '@me2em/core';
@@ -10,9 +12,32 @@ import { Me2emContext, type Me2emContextValue } from './context.js';
10
12
  export function Me2emProvider({ children }: { children: ReactNode }) {
11
13
  const [identity, setIdentity] = useState<Identity | null>(null);
12
14
 
15
+ // BL-47 dispose parity: the ref is mutated only in event handlers and
16
+ // cleanup effects, always alongside setIdentity — never inside a state
17
+ // updater (StrictMode double-invokes updaters; dispose is a side
18
+ // effect). A replaced/cleared handle is disposed at the swap, so
19
+ // stale references fail loudly with DisposedError (core contract)
20
+ // instead of silently operating on zeroed key material.
21
+ const identityRef = useRef<Identity | null>(null);
22
+ // Generation ticket for in-flight Identity.fromSeed results — the
23
+ // same load-race class fixed for useIdentityContext in rc.1: a
24
+ // result landing after a newer activation, clearIdentity() or
25
+ // unmount disposes its own handle instead of overwriting newer
26
+ // state.
27
+ const genRef = useRef(0);
28
+
13
29
  const activateIdentity = useCallback(async (seed: Uint8Array): Promise<boolean> => {
30
+ const gen = ++genRef.current;
14
31
  try {
15
32
  const id = await Identity.fromSeed(seed);
33
+ if (gen !== genRef.current) {
34
+ // Superseded while in flight: discard this handle loudly
35
+ // rather than leaking it into state.
36
+ id.dispose();
37
+ return false;
38
+ }
39
+ identityRef.current?.dispose(); // zero the replaced handle
40
+ identityRef.current = id;
16
41
  setIdentity(id);
17
42
  return true;
18
43
  } catch {
@@ -21,9 +46,24 @@ export function Me2emProvider({ children }: { children: ReactNode }) {
21
46
  }, []);
22
47
 
23
48
  const clearIdentity = useCallback(() => {
49
+ ++genRef.current; // invalidate in-flight activations
50
+ identityRef.current?.dispose();
51
+ identityRef.current = null;
24
52
  setIdentity(null);
25
53
  }, []);
26
54
 
55
+ // Unmount: cleanup-only dispose — no side effects in render or
56
+ // updaters (the identity was provider state, so it is unreachable
57
+ // after unmount either way; dispose just zeroizes it eagerly).
58
+ useEffect(() => {
59
+ return () => {
60
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- reading the LIVE ticket at teardown is the point: invalidate whatever activation is in flight at unmount; snapshotting it would defeat the guard
61
+ ++genRef.current;
62
+ identityRef.current?.dispose();
63
+ identityRef.current = null;
64
+ };
65
+ }, []);
66
+
27
67
  const value = useMemo<Me2emContextValue>(
28
68
  () => ({ identity, hasIdentity: identity !== null, activateIdentity, clearIdentity }),
29
69
  [identity, activateIdentity, clearIdentity]
@@ -16,6 +16,15 @@ export interface SeedPhraseVerifyProps {
16
16
 
17
17
  const DEFAULT_GRID_SIZE_OFFSET = 8;
18
18
 
19
+ /**
20
+ * Confirmation grid for a generated seed phrase.
21
+ *
22
+ * The grid shuffle is deterministic by design (fixed base seed 42;
23
+ * each retry shifts it by `+retryCount + 1`) for testability and SSR
24
+ * stability — it is NOT a security control: decoy words come from the
25
+ * public wordlist, so the layout leaks nothing (BL-49). A
26
+ * `randomSeed` prop is deliberately not offered in the frozen 1.0 API.
27
+ */
19
28
  export function SeedPhraseVerify({
20
29
  realWords,
21
30
  gridSize,
@@ -2,9 +2,25 @@ import { createContext, useContext } from 'react';
2
2
  import type { Identity } from '@me2em/core';
3
3
 
4
4
  export interface Me2emContextValue {
5
+ /**
6
+ * Active identity, or null. The previously active identity is
7
+ * disposed (key material zeroed) the moment it is replaced by a new
8
+ * activation or by {@link clearIdentity} — stale references fail
9
+ * loudly with `DisposedError` per the core contract (BL-47).
10
+ */
5
11
  identity: Identity | null;
6
12
  hasIdentity: boolean;
13
+ /**
14
+ * Activates an identity from raw seed bytes, replacing (and
15
+ * disposing) any previously active identity. Resolves `false` if
16
+ * derivation failed or the activation was superseded by a newer
17
+ * activation / `clearIdentity()` while in flight.
18
+ *
19
+ * Does not consume the seed — the caller owns the buffer and must
20
+ * zero it when done (BL-48).
21
+ */
7
22
  activateIdentity: (seed: Uint8Array) => Promise<boolean>;
23
+ /** Disposes the active identity (if any) and clears it. */
8
24
  clearIdentity: () => void;
9
25
  }
10
26
 
@@ -3,6 +3,14 @@ import { useState, useEffect } from 'react';
3
3
  import { type Handle } from '@me2em/core';
4
4
  import { useMe2emContext } from './context.js';
5
5
 
6
+ /**
7
+ * Derives the named Handle for the active identity.
8
+ *
9
+ * Silent-failure contract (R7): on derivation failure the hook logs
10
+ * via console.error and keeps returning null — there is no thrown or
11
+ * returned error signal. The structured `{ handle, error }` shape is
12
+ * a post-freeze v1.1 candidate (BL-50).
13
+ */
6
14
  export function useHandle(name: string): Handle | null {
7
15
  const { identity } = useMe2emContext();
8
16
  const [handle, setHandle] = useState<Handle | null>(null);
@@ -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,
@@ -2,6 +2,14 @@ import { useState, useEffect } from 'react';
2
2
  import { type SubHandle } from '@me2em/core';
3
3
  import { useMe2emContext } from './context.js';
4
4
 
5
+ /**
6
+ * Derives the named SubHandle under the given Handle.
7
+ *
8
+ * Silent-failure contract (R7): on derivation failure the hook logs
9
+ * via console.error and keeps returning null — there is no thrown or
10
+ * returned error signal. The structured `{ handle, error }` shape is
11
+ * a post-freeze v1.1 candidate (BL-50).
12
+ */
5
13
  export function useSubHandle(
6
14
  handleName: string,
7
15
  subName: string