@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 +10 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +46 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/headless/context-crypto.ts +3 -1
- package/src/headless/identity-context-types.ts +9 -0
- package/src/headless/types.ts +7 -0
- package/src/react/Me2emProvider.tsx +40 -0
- package/src/react/SeedPhraseVerify.tsx +9 -0
- package/src/react/context.ts +16 -0
- package/src/react/useHandle.ts +8 -0
- package/src/react/useSession.ts +31 -1
- package/src/react/useSubHandle.ts +8 -0
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
720
|
-
const lastImportedRef =
|
|
721
|
-
|
|
740
|
+
const prevStatusRef = useRef3(state.status);
|
|
741
|
+
const lastImportedRef = useRef3(null);
|
|
742
|
+
useEffect5(() => {
|
|
722
743
|
dispatch({ type: "CHECK" });
|
|
723
744
|
}, [state.words]);
|
|
724
|
-
|
|
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
|
|
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 =
|
|
834
|
-
const opRef =
|
|
835
|
-
const identityRef =
|
|
854
|
+
const prevIdentityIdRef = useRef4(null);
|
|
855
|
+
const opRef = useRef4(0);
|
|
856
|
+
const identityRef = useRef4(null);
|
|
836
857
|
identityRef.current = identity;
|
|
837
|
-
|
|
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
|
|
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 =
|
|
901
|
-
const optionsRef =
|
|
902
|
-
const mountedRef =
|
|
903
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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>;
|
package/src/headless/types.ts
CHANGED
|
@@ -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,
|
package/src/react/context.ts
CHANGED
|
@@ -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
|
|
package/src/react/useHandle.ts
CHANGED
|
@@ -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);
|
package/src/react/useSession.ts
CHANGED
|
@@ -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
|