@me2em/react 0.1.0-alpha.2 → 1.0.0-rc.1
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 +6 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +55 -52
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
- package/src/headless/context-crypto.ts +10 -29
- package/src/headless/identity-context-storage.ts +2 -2
- package/src/headless/import-flow.ts +1 -1
- package/src/react/PassphraseInput.tsx +1 -1
- package/src/react/SeedPhraseDisplay.tsx +29 -8
- package/src/react/SeedPhraseImport.tsx +1 -1
- package/src/react/SeedPhraseVerify.tsx +0 -1
- package/src/react/useCreateIdentity.ts +2 -2
- package/src/react/useHandle.ts +1 -0
- package/src/react/useIdentityContext.ts +37 -1
- package/src/react/useImportSeed.ts +2 -2
- package/src/react/useSession.ts +7 -5
- package/src/react/useSubHandle.ts +1 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 📦 @me2em/react — React bindings
|
|
6
6
|
|
|
7
|
-
React bindings for the
|
|
7
|
+
React bindings for the Me2eM protocol: identity lifecycle, seed phrase UX, and hierarchical handle derivation.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
@@ -24,13 +24,13 @@ Requires React >= 18.0.0 as a peer dependency.
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
26
|
```tsx
|
|
27
|
-
import {
|
|
27
|
+
import { Me2eMProvider, useCreateIdentity } from '@me2em/react';
|
|
28
28
|
|
|
29
29
|
function App() {
|
|
30
30
|
return (
|
|
31
|
-
<
|
|
31
|
+
<Me2eMProvider>
|
|
32
32
|
<CreateIdentityScreen />
|
|
33
|
-
</
|
|
33
|
+
</Me2eMProvider>
|
|
34
34
|
);
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -90,7 +90,7 @@ this package:
|
|
|
90
90
|
| **Context cache** | Per-identity encrypted storage of derived Handle context. |
|
|
91
91
|
|
|
92
92
|
Full glossary (including Attestation, SubHandle, Ephemeral material):
|
|
93
|
-
see the
|
|
93
|
+
see the root [GLOSSARY.md](../../GLOSSARY.md).
|
|
94
94
|
|
|
95
95
|
**This package never persists your seed.** The seed phrase exists in
|
|
96
96
|
two places only — your paper and transient memory during login. What
|
|
@@ -221,4 +221,4 @@ Each identity gets a separate IndexedDB database (name = SHA-256 hash of identit
|
|
|
221
221
|
|
|
222
222
|
Alpha. API may change between 0.1.x releases. See [packages/core/README.md](../../packages/core/README.md) for the underlying protocol.
|
|
223
223
|
|
|
224
|
-
Roadmap:
|
|
224
|
+
Roadmap: `@me2em/crypto` shipped (0.1.0-alpha.1); `@me2em/messenger` planned (BACKLOG BL-29).
|
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,7 @@ declare function useHandle(name: string): Handle | null;
|
|
|
65
65
|
declare function useSubHandle(handleName: string, subName: string): SubHandle | null;
|
|
66
66
|
|
|
67
67
|
interface SeedPhraseDisplayProps {
|
|
68
|
+
/** Seed material is reachable via React DevTools in this window; by design. */
|
|
68
69
|
words: string[];
|
|
69
70
|
onDismiss?: () => void;
|
|
70
71
|
onCopyAllowed?: () => void;
|
package/dist/index.js
CHANGED
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
} from "react";
|
|
54
54
|
import {
|
|
55
55
|
generateSeedPhrase,
|
|
56
|
-
|
|
56
|
+
get64ByteSeedFromMnemonic
|
|
57
57
|
} from "@me2em/core";
|
|
58
58
|
|
|
59
59
|
// src/headless/identity-flow.ts
|
|
@@ -159,7 +159,7 @@ function useCreateIdentity() {
|
|
|
159
159
|
const generate = useCallback2(
|
|
160
160
|
(opts) => {
|
|
161
161
|
const words = generateSeedPhrase(opts?.strength ?? 128);
|
|
162
|
-
|
|
162
|
+
get64ByteSeedFromMnemonic(words).then((derivedSeed) => {
|
|
163
163
|
dispatch({ type: "GENERATE", words, seed: derivedSeed });
|
|
164
164
|
}).catch(() => {
|
|
165
165
|
dispatch({ type: "FAIL", message: "Failed to generate seed" });
|
|
@@ -198,7 +198,7 @@ function useCreateIdentity() {
|
|
|
198
198
|
import { useReducer as useReducer2, useCallback as useCallback3 } from "react";
|
|
199
199
|
import {
|
|
200
200
|
validateSeedPhrase,
|
|
201
|
-
|
|
201
|
+
get64ByteSeedFromMnemonic as get64ByteSeedFromMnemonic2
|
|
202
202
|
} from "@me2em/core";
|
|
203
203
|
function useImportSeed() {
|
|
204
204
|
const { activateIdentity, clearIdentity, identity } = useMe2emContext();
|
|
@@ -211,7 +211,7 @@ function useImportSeed() {
|
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
try {
|
|
214
|
-
const seed = await
|
|
214
|
+
const seed = await get64ByteSeedFromMnemonic2(words, passphrase ?? "");
|
|
215
215
|
dispatch({ type: "IMPORT", seed });
|
|
216
216
|
await activateIdentity(seed);
|
|
217
217
|
} catch (err) {
|
|
@@ -371,13 +371,16 @@ function displayFlowReducer(state, event) {
|
|
|
371
371
|
|
|
372
372
|
// src/react/SeedPhraseDisplay.tsx
|
|
373
373
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
374
|
+
function clipboardWriteAvailable() {
|
|
375
|
+
return typeof navigator !== "undefined" && !!navigator.clipboard && typeof navigator.clipboard.writeText === "function";
|
|
376
|
+
}
|
|
374
377
|
function SeedPhraseDisplay({
|
|
375
378
|
words,
|
|
376
379
|
onDismiss,
|
|
377
380
|
onCopyAllowed
|
|
378
381
|
}) {
|
|
379
382
|
const [state, dispatch] = useReducer3(displayFlowReducer, initialDisplayState);
|
|
380
|
-
const [
|
|
383
|
+
const [copyError, setCopyError] = useState4(null);
|
|
381
384
|
const { status, copyCount } = state;
|
|
382
385
|
const handleReveal = () => {
|
|
383
386
|
dispatch({ type: "REVEAL", at: Date.now() });
|
|
@@ -387,10 +390,10 @@ function SeedPhraseDisplay({
|
|
|
387
390
|
try {
|
|
388
391
|
await navigator.clipboard.writeText(words.join(" "));
|
|
389
392
|
dispatch({ type: "COPY", at: Date.now() });
|
|
390
|
-
|
|
393
|
+
setCopyError(null);
|
|
391
394
|
onCopyAllowed?.();
|
|
392
395
|
} catch {
|
|
393
|
-
|
|
396
|
+
setCopyError(clipboardWriteAvailable() ? "transient" : "permanent");
|
|
394
397
|
}
|
|
395
398
|
}
|
|
396
399
|
};
|
|
@@ -414,13 +417,13 @@ function SeedPhraseDisplay({
|
|
|
414
417
|
},
|
|
415
418
|
i
|
|
416
419
|
)) }),
|
|
417
|
-
|
|
420
|
+
copyError && /* @__PURE__ */ jsx2("div", { "data-testid": "copy-error", "data-kind": copyError, children: copyError === "permanent" ? "Copy unavailable in this context \u2014 select the words manually" : "Copy failed \u2014 try again" }),
|
|
418
421
|
/* @__PURE__ */ jsx2(
|
|
419
422
|
"button",
|
|
420
423
|
{
|
|
421
424
|
"data-testid": "copy-all",
|
|
422
425
|
onClick: handleCopy,
|
|
423
|
-
disabled: copyCount === 1,
|
|
426
|
+
disabled: copyCount === 1 || copyError === "permanent",
|
|
424
427
|
children: copyCount === 1 ? "Copied" : "Copy all"
|
|
425
428
|
}
|
|
426
429
|
),
|
|
@@ -787,13 +790,13 @@ function SeedPhraseImport({
|
|
|
787
790
|
}
|
|
788
791
|
|
|
789
792
|
// src/react/PassphraseInput.tsx
|
|
790
|
-
import { useCallback as
|
|
793
|
+
import { useCallback as useCallback5 } from "react";
|
|
791
794
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
792
795
|
function PassphraseInput({
|
|
793
796
|
onPassphraseChange,
|
|
794
797
|
showWarnings = true
|
|
795
798
|
}) {
|
|
796
|
-
const handleChange =
|
|
799
|
+
const handleChange = useCallback5(
|
|
797
800
|
(e) => {
|
|
798
801
|
const value = e.target.value.normalize("NFKC");
|
|
799
802
|
onPassphraseChange(value);
|
|
@@ -815,16 +818,22 @@ function PassphraseInput({
|
|
|
815
818
|
}
|
|
816
819
|
|
|
817
820
|
// src/react/useIdentityContext.ts
|
|
818
|
-
import { useState as
|
|
821
|
+
import { useState as useState6, useCallback as useCallback6, useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
819
822
|
function publicKeyToId(publicKey) {
|
|
820
823
|
return Array.from(publicKey).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
821
824
|
}
|
|
825
|
+
function identityToId(identity) {
|
|
826
|
+
return identity ? publicKeyToId(identity.getPublicKey()) : null;
|
|
827
|
+
}
|
|
822
828
|
function useIdentityContext(storage) {
|
|
823
829
|
const { identity } = useMe2emContext();
|
|
824
|
-
const [context, setContext] =
|
|
825
|
-
const [isLoaded, setIsLoaded] =
|
|
826
|
-
const [error, setError] =
|
|
830
|
+
const [context, setContext] = useState6(null);
|
|
831
|
+
const [isLoaded, setIsLoaded] = useState6(false);
|
|
832
|
+
const [error, setError] = useState6(null);
|
|
827
833
|
const prevIdentityIdRef = useRef3(null);
|
|
834
|
+
const opRef = useRef3(0);
|
|
835
|
+
const identityRef = useRef3(null);
|
|
836
|
+
identityRef.current = identity;
|
|
828
837
|
useEffect5(() => {
|
|
829
838
|
let cancelled = false;
|
|
830
839
|
if (!identity) {
|
|
@@ -839,8 +848,9 @@ function useIdentityContext(storage) {
|
|
|
839
848
|
});
|
|
840
849
|
}
|
|
841
850
|
prevIdentityIdRef.current = identityId;
|
|
851
|
+
const op = ++opRef.current;
|
|
842
852
|
storage.openAndLoad(identityId).then((data) => {
|
|
843
|
-
if (cancelled) return;
|
|
853
|
+
if (cancelled || op !== opRef.current) return;
|
|
844
854
|
setContext(data);
|
|
845
855
|
setIsLoaded(true);
|
|
846
856
|
setError(null);
|
|
@@ -853,36 +863,47 @@ function useIdentityContext(storage) {
|
|
|
853
863
|
cancelled = true;
|
|
854
864
|
};
|
|
855
865
|
}, [identity, storage]);
|
|
856
|
-
const save =
|
|
866
|
+
const save = useCallback6(async (data) => {
|
|
867
|
+
const idAtStart = identityToId(identityRef.current);
|
|
868
|
+
opRef.current += 1;
|
|
857
869
|
await storage.save(data);
|
|
870
|
+
if (identityToId(identityRef.current) !== idAtStart) return;
|
|
858
871
|
setContext(data);
|
|
872
|
+
setError(null);
|
|
859
873
|
}, [storage]);
|
|
860
|
-
const clear =
|
|
874
|
+
const clear = useCallback6(async () => {
|
|
875
|
+
const idAtStart = identityToId(identityRef.current);
|
|
876
|
+
opRef.current += 1;
|
|
861
877
|
await storage.clear();
|
|
878
|
+
if (identityToId(identityRef.current) !== idAtStart) return;
|
|
862
879
|
setContext(null);
|
|
880
|
+
setError(null);
|
|
863
881
|
}, [storage]);
|
|
864
|
-
const refresh =
|
|
882
|
+
const refresh = useCallback6(async () => {
|
|
865
883
|
if (!identity) return;
|
|
866
884
|
const identityId = publicKeyToId(identity.getPublicKey());
|
|
885
|
+
const op = ++opRef.current;
|
|
867
886
|
const data = await storage.openAndLoad(identityId);
|
|
887
|
+
if (op !== opRef.current) return;
|
|
868
888
|
setContext(data);
|
|
869
889
|
}, [identity, storage]);
|
|
870
890
|
return { context, isLoaded, error, save, clear, refresh };
|
|
871
891
|
}
|
|
872
892
|
|
|
873
893
|
// src/react/useSession.ts
|
|
874
|
-
import { useState as
|
|
894
|
+
import { useState as useState7, useEffect as useEffect6, useCallback as useCallback7, useRef as useRef4 } from "react";
|
|
875
895
|
import { Session } from "@me2em/core";
|
|
876
896
|
function useSession(handle, options, autoRenew = true) {
|
|
877
|
-
const
|
|
878
|
-
const [
|
|
879
|
-
const [
|
|
880
|
-
const [error, setError] = useState8(null);
|
|
897
|
+
const [session, setSession] = useState7(null);
|
|
898
|
+
const [isLoaded, setIsLoaded] = useState7(false);
|
|
899
|
+
const [error, setError] = useState7(null);
|
|
881
900
|
const timerRef = useRef4(null);
|
|
882
901
|
const optionsRef = useRef4(options);
|
|
883
902
|
const mountedRef = useRef4(true);
|
|
884
|
-
|
|
885
|
-
|
|
903
|
+
useEffect6(() => {
|
|
904
|
+
optionsRef.current = options;
|
|
905
|
+
});
|
|
906
|
+
const clearTimer = useCallback7(() => {
|
|
886
907
|
if (timerRef.current) {
|
|
887
908
|
clearTimeout(timerRef.current);
|
|
888
909
|
timerRef.current = null;
|
|
@@ -895,7 +916,7 @@ function useSession(handle, options, autoRenew = true) {
|
|
|
895
916
|
clearTimer();
|
|
896
917
|
};
|
|
897
918
|
}, [clearTimer]);
|
|
898
|
-
const renew =
|
|
919
|
+
const renew = useCallback7(() => {
|
|
899
920
|
clearTimer();
|
|
900
921
|
const opts = optionsRef.current;
|
|
901
922
|
if (!handle) {
|
|
@@ -964,25 +985,7 @@ __export(headless_exports, {
|
|
|
964
985
|
});
|
|
965
986
|
|
|
966
987
|
// src/headless/context-crypto.ts
|
|
967
|
-
|
|
968
|
-
let binary = "";
|
|
969
|
-
for (const byte of bytes) {
|
|
970
|
-
binary += String.fromCharCode(byte);
|
|
971
|
-
}
|
|
972
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
973
|
-
}
|
|
974
|
-
function fromB64(s) {
|
|
975
|
-
let base64 = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
976
|
-
while (base64.length % 4 !== 0) {
|
|
977
|
-
base64 += "=";
|
|
978
|
-
}
|
|
979
|
-
const binary = atob(base64);
|
|
980
|
-
const bytes = new Uint8Array(binary.length);
|
|
981
|
-
for (let i = 0; i < binary.length; i++) {
|
|
982
|
-
bytes[i] = binary.charCodeAt(i);
|
|
983
|
-
}
|
|
984
|
-
return bytes;
|
|
985
|
-
}
|
|
988
|
+
import { base64urlEncode, base64urlDecode } from "@me2em/core";
|
|
986
989
|
async function deriveCacheKey(loginMaterial, salt) {
|
|
987
990
|
const base = await crypto.subtle.importKey(
|
|
988
991
|
"raw",
|
|
@@ -1020,17 +1023,17 @@ async function encryptContext(key, data) {
|
|
|
1020
1023
|
);
|
|
1021
1024
|
return {
|
|
1022
1025
|
v: 1,
|
|
1023
|
-
salt:
|
|
1024
|
-
iv:
|
|
1025
|
-
ct:
|
|
1026
|
+
salt: base64urlEncode(salt),
|
|
1027
|
+
iv: base64urlEncode(iv),
|
|
1028
|
+
ct: base64urlEncode(new Uint8Array(ct))
|
|
1026
1029
|
};
|
|
1027
1030
|
}
|
|
1028
1031
|
async function decryptContext(key, envelope) {
|
|
1029
1032
|
if (envelope.v !== 1) throw new Error("Unsupported envelope version");
|
|
1030
1033
|
const pt = await crypto.subtle.decrypt(
|
|
1031
|
-
{ name: "AES-GCM", iv:
|
|
1034
|
+
{ name: "AES-GCM", iv: base64urlDecode(envelope.iv) },
|
|
1032
1035
|
key,
|
|
1033
|
-
|
|
1036
|
+
base64urlDecode(envelope.ct)
|
|
1034
1037
|
);
|
|
1035
1038
|
return JSON.parse(new TextDecoder().decode(pt));
|
|
1036
1039
|
}
|
|
@@ -1189,7 +1192,7 @@ var IdentityContextIDBStorage = class {
|
|
|
1189
1192
|
if (!db) {
|
|
1190
1193
|
return null;
|
|
1191
1194
|
}
|
|
1192
|
-
return new Promise((resolve
|
|
1195
|
+
return new Promise((resolve) => {
|
|
1193
1196
|
const tx = db.transaction(KEYS_STORE_NAME, "readonly");
|
|
1194
1197
|
const store = tx.objectStore(KEYS_STORE_NAME);
|
|
1195
1198
|
const request = store.get("cache");
|
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 get32ByteSeedFromMnemonic,\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 get32ByteSeedFromMnemonic(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 get32ByteSeedFromMnemonic,\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 get32ByteSeedFromMnemonic(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 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 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 words: string[];\n onDismiss?: () => void;\n onCopyAllowed?: () => void;\n}\n\nexport function SeedPhraseDisplay({\n words,\n onDismiss,\n onCopyAllowed,\n}: SeedPhraseDisplayProps) {\n const [state, dispatch] = useReducer(displayFlowReducer, initialDisplayState);\n const [copyFailed, setCopyFailed] = useState(false);\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 as any).clipboard.writeText(words.join(' '));\n dispatch({ type: 'COPY', at: Date.now() });\n setCopyFailed(false);\n onCopyAllowed?.();\n } catch {\n setCopyFailed(true);\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 {copyFailed && (\n <div data-testid=\"copy-error\">\n Copy failed — try again\n </div>\n )}\n <button\n data-testid=\"copy-all\"\n onClick={handleCopy}\n disabled={copyCount === 1}\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 type VerifyState,\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, useCallback, 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, type SeedStrength } 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 { useState, 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 { 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\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\n useEffect(() => {\n let cancelled = false;\n\n if (!identity) {\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 storage.openAndLoad(identityId)\n .then((data) => {\n if (cancelled) 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 await storage.save(data);\n setContext(data);\n }, [storage]);\n\n const clear = useCallback(async (): Promise<void> => {\n await storage.clear();\n setContext(null);\n }, [storage]);\n\n const refresh = useCallback(async (): Promise<void> => {\n if (!identity) return;\n const identityId = publicKeyToId(identity.getPublicKey());\n const data = await storage.openAndLoad(identityId);\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, type SessionOptions, type SessionPayload } from '@me2em/core';\nimport { useMe2emContext } from './context.js';\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 { identity } = useMe2emContext();\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 // Update options ref without triggering re-renders\n optionsRef.current = options;\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 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 type { IdentityContextData } from './identity-context-types.js';\n\nfunction toB64(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary)\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\nfunction fromB64(s: string): Uint8Array {\n let base64 = s\n .replace(/-/g, '+')\n .replace(/_/g, '/');\n while (base64.length % 4 !== 0) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\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: toB64(salt), iv: toB64(iv),\n ct: toB64(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: fromB64(envelope.iv) as BufferSource },\n key, fromB64(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, type ContextEnvelope } 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, reject) => {\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","get32ByteSeedFromMnemonic","useReducer","useCallback","get32ByteSeedFromMnemonic","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","useState","useRef","useCallback","useEffect"]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@me2em/react",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "React bindings for the
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
|
+
"description": "React bindings for the Me2eM protocol: identity lifecycle, seed phrase UX, hierarchical handles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.6.0"
|
|
23
|
+
},
|
|
21
24
|
"publishConfig": {
|
|
22
25
|
"access": "public"
|
|
23
26
|
},
|
|
24
27
|
"dependencies": {
|
|
25
|
-
"@me2em/core": "^0.
|
|
28
|
+
"@me2em/core": "^1.0.0-rc.2"
|
|
26
29
|
},
|
|
27
30
|
"peerDependencies": {
|
|
28
31
|
"react": ">=18.0.0"
|
|
@@ -34,19 +37,21 @@
|
|
|
34
37
|
"@types/react": "^18.3.0",
|
|
35
38
|
"@types/react-dom": "^18.3.0",
|
|
36
39
|
"@vitejs/plugin-react": "^4.3.0",
|
|
40
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
41
|
+
"fake-indexeddb": "^6.0.0",
|
|
37
42
|
"jsdom": "^25.0.0",
|
|
38
43
|
"react": "^18.3.0",
|
|
39
44
|
"react-dom": "^18.3.0",
|
|
40
45
|
"tsup": "^8.0.0",
|
|
41
46
|
"typescript": "^5.6.0",
|
|
42
|
-
"fake-indexeddb": "^6.0.0",
|
|
43
47
|
"vitest": "^4.1.7"
|
|
44
48
|
},
|
|
45
49
|
"scripts": {
|
|
46
50
|
"build": "tsup",
|
|
47
51
|
"test": "vitest run",
|
|
48
|
-
"lint": "eslint
|
|
52
|
+
"lint": "eslint .",
|
|
49
53
|
"typecheck": "tsc --noEmit",
|
|
50
|
-
"build-docs": "typedoc"
|
|
54
|
+
"build-docs": "typedoc",
|
|
55
|
+
"test:coverage": "vitest run --coverage"
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -1,31 +1,12 @@
|
|
|
1
1
|
// packages/react/src/headless/context-crypto.ts
|
|
2
|
+
import { base64urlEncode, base64urlDecode } from '@me2em/core';
|
|
2
3
|
import type { IdentityContextData } from './identity-context-types.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return btoa(binary)
|
|
10
|
-
.replace(/\+/g, '-')
|
|
11
|
-
.replace(/\//g, '_')
|
|
12
|
-
.replace(/=+$/, '');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function fromB64(s: string): Uint8Array {
|
|
16
|
-
let base64 = s
|
|
17
|
-
.replace(/-/g, '+')
|
|
18
|
-
.replace(/_/g, '/');
|
|
19
|
-
while (base64.length % 4 !== 0) {
|
|
20
|
-
base64 += '=';
|
|
21
|
-
}
|
|
22
|
-
const binary = atob(base64);
|
|
23
|
-
const bytes = new Uint8Array(binary.length);
|
|
24
|
-
for (let i = 0; i < binary.length; i++) {
|
|
25
|
-
bytes[i] = binary.charCodeAt(i);
|
|
26
|
-
}
|
|
27
|
-
return bytes;
|
|
28
|
-
}
|
|
5
|
+
// Base64url encoding for ContextEnvelope fields goes through the audited
|
|
6
|
+
// @scure/base codec re-exported by @me2em/core (audit A3 / R4) — one
|
|
7
|
+
// base64 implementation across packages, no hand-rolled alphabet handling.
|
|
8
|
+
// Envelope fields are always unpadded: the only writer is encryptContext
|
|
9
|
+
// below, so base64urlDecode's strict no-padding rule never bites.
|
|
29
10
|
|
|
30
11
|
export interface ContextEnvelope {
|
|
31
12
|
v: 1;
|
|
@@ -75,8 +56,8 @@ export async function encryptContext(
|
|
|
75
56
|
const ct = await crypto.subtle.encrypt(
|
|
76
57
|
{ name: 'AES-GCM', iv: iv as BufferSource }, key, pt
|
|
77
58
|
);
|
|
78
|
-
return { v: 1, salt:
|
|
79
|
-
ct:
|
|
59
|
+
return { v: 1, salt: base64urlEncode(salt), iv: base64urlEncode(iv),
|
|
60
|
+
ct: base64urlEncode(new Uint8Array(ct)) };
|
|
80
61
|
}
|
|
81
62
|
|
|
82
63
|
export async function decryptContext(
|
|
@@ -84,8 +65,8 @@ export async function decryptContext(
|
|
|
84
65
|
): Promise<IdentityContextData> {
|
|
85
66
|
if (envelope.v !== 1) throw new Error('Unsupported envelope version');
|
|
86
67
|
const pt = await crypto.subtle.decrypt(
|
|
87
|
-
{ name: 'AES-GCM', iv:
|
|
88
|
-
key,
|
|
68
|
+
{ name: 'AES-GCM', iv: base64urlDecode(envelope.iv) as BufferSource },
|
|
69
|
+
key, base64urlDecode(envelope.ct) as BufferSource
|
|
89
70
|
);
|
|
90
71
|
return JSON.parse(new TextDecoder().decode(pt)) as IdentityContextData;
|
|
91
72
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// packages/react/src/headless/identity-context-storage.ts
|
|
2
|
-
import { encryptContext, decryptContext
|
|
2
|
+
import { encryptContext, decryptContext } from './context-crypto.js';
|
|
3
3
|
import type { IdentityContextData, IdentityContextStorage } from './identity-context-types.js';
|
|
4
4
|
export type { IdentityContextData };
|
|
5
5
|
|
|
@@ -183,7 +183,7 @@ export class IdentityContextIDBStorage implements IdentityContextStorage {
|
|
|
183
183
|
if (!db) {
|
|
184
184
|
return null;
|
|
185
185
|
}
|
|
186
|
-
return new Promise<CryptoKey | null>((resolve
|
|
186
|
+
return new Promise<CryptoKey | null>((resolve) => {
|
|
187
187
|
const tx = db.transaction(KEYS_STORE_NAME, 'readonly');
|
|
188
188
|
const store = tx.objectStore(KEYS_STORE_NAME);
|
|
189
189
|
const request = store.get('cache');
|
|
@@ -6,18 +6,37 @@ import {
|
|
|
6
6
|
} from '../headless/display-flow.js';
|
|
7
7
|
|
|
8
8
|
export interface SeedPhraseDisplayProps {
|
|
9
|
+
/** Seed material is reachable via React DevTools in this window; by design. */
|
|
9
10
|
words: string[];
|
|
10
11
|
onDismiss?: () => void;
|
|
11
12
|
onCopyAllowed?: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* R8: the permanent/transient line for a failed clipboard write is
|
|
17
|
+
* "can a retry in this window ever succeed?". Without the async
|
|
18
|
+
* Clipboard API (non-secure context, unsupported browser) it cannot —
|
|
19
|
+
* the failure is permanent, the copy-all button is disabled and the
|
|
20
|
+
* user is pointed at manual word selection. With the API present any
|
|
21
|
+
* rejection (lost focus, dismissed permission prompt, OS hiccup)
|
|
22
|
+
* stays transient: the button remains enabled and a fresh attempt
|
|
23
|
+
* may go through.
|
|
24
|
+
*/
|
|
25
|
+
function clipboardWriteAvailable(): boolean {
|
|
26
|
+
return (
|
|
27
|
+
typeof navigator !== 'undefined' &&
|
|
28
|
+
!!navigator.clipboard &&
|
|
29
|
+
typeof navigator.clipboard.writeText === 'function'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
14
33
|
export function SeedPhraseDisplay({
|
|
15
34
|
words,
|
|
16
35
|
onDismiss,
|
|
17
36
|
onCopyAllowed,
|
|
18
37
|
}: SeedPhraseDisplayProps) {
|
|
19
38
|
const [state, dispatch] = useReducer(displayFlowReducer, initialDisplayState);
|
|
20
|
-
const [
|
|
39
|
+
const [copyError, setCopyError] = useState<'transient' | 'permanent' | null>(null);
|
|
21
40
|
|
|
22
41
|
const { status, copyCount } = state;
|
|
23
42
|
|
|
@@ -28,12 +47,12 @@ export function SeedPhraseDisplay({
|
|
|
28
47
|
const handleCopy = async () => {
|
|
29
48
|
if (status === 'revealed' && copyCount === 0) {
|
|
30
49
|
try {
|
|
31
|
-
await
|
|
50
|
+
await navigator.clipboard.writeText(words.join(' '));
|
|
32
51
|
dispatch({ type: 'COPY', at: Date.now() });
|
|
33
|
-
|
|
52
|
+
setCopyError(null);
|
|
34
53
|
onCopyAllowed?.();
|
|
35
54
|
} catch {
|
|
36
|
-
|
|
55
|
+
setCopyError(clipboardWriteAvailable() ? 'transient' : 'permanent');
|
|
37
56
|
}
|
|
38
57
|
}
|
|
39
58
|
};
|
|
@@ -72,15 +91,17 @@ export function SeedPhraseDisplay({
|
|
|
72
91
|
</li>
|
|
73
92
|
))}
|
|
74
93
|
</ol>
|
|
75
|
-
{
|
|
76
|
-
<div data-testid="copy-error">
|
|
77
|
-
|
|
94
|
+
{copyError && (
|
|
95
|
+
<div data-testid="copy-error" data-kind={copyError}>
|
|
96
|
+
{copyError === 'permanent'
|
|
97
|
+
? 'Copy unavailable in this context — select the words manually'
|
|
98
|
+
: 'Copy failed — try again'}
|
|
78
99
|
</div>
|
|
79
100
|
)}
|
|
80
101
|
<button
|
|
81
102
|
data-testid="copy-all"
|
|
82
103
|
onClick={handleCopy}
|
|
83
|
-
disabled={copyCount === 1}
|
|
104
|
+
disabled={copyCount === 1 || copyError === 'permanent'}
|
|
84
105
|
>
|
|
85
106
|
{copyCount === 1 ? 'Copied' : 'Copy all'}
|
|
86
107
|
</button>
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from 'react';
|
|
7
7
|
import {
|
|
8
8
|
generateSeedPhrase,
|
|
9
|
-
|
|
9
|
+
get64ByteSeedFromMnemonic,
|
|
10
10
|
type SeedStrength,
|
|
11
11
|
type Identity,
|
|
12
12
|
} from '@me2em/core';
|
|
@@ -37,7 +37,7 @@ export function useCreateIdentity(): UseCreateIdentityResult {
|
|
|
37
37
|
const generate = useCallback(
|
|
38
38
|
(opts?: { strength?: SeedStrength }) => {
|
|
39
39
|
const words = generateSeedPhrase(opts?.strength ?? 128);
|
|
40
|
-
|
|
40
|
+
get64ByteSeedFromMnemonic(words).then((derivedSeed) => {
|
|
41
41
|
dispatch({ type: 'GENERATE', words, seed: derivedSeed });
|
|
42
42
|
}).catch(() => {
|
|
43
43
|
dispatch({ type: 'FAIL', message: 'Failed to generate seed' });
|
package/src/react/useHandle.ts
CHANGED
|
@@ -10,6 +10,7 @@ export function useHandle(name: string): Handle | null {
|
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
let cancelled = false;
|
|
12
12
|
if (!identity) {
|
|
13
|
+
// 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
|
|
13
14
|
setHandle(null);
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// packages/react/src/react/useIdentityContext.ts
|
|
2
2
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import type { Identity } from '@me2em/core';
|
|
3
4
|
import type { IdentityContextStorage, IdentityContextData } from '../headless/identity-context-types.js';
|
|
4
5
|
import { useMe2emContext } from './context.js';
|
|
5
6
|
|
|
@@ -9,6 +10,10 @@ function publicKeyToId(publicKey: Uint8Array): string {
|
|
|
9
10
|
.join('');
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
function identityToId(identity: Identity | null): string | null {
|
|
14
|
+
return identity ? publicKeyToId(identity.getPublicKey()) : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
export interface UseIdentityContextResult {
|
|
13
18
|
context: IdentityContextData | null;
|
|
14
19
|
isLoaded: boolean;
|
|
@@ -28,11 +33,23 @@ export function useIdentityContext(
|
|
|
28
33
|
const [error, setError] = useState<string | null>(null);
|
|
29
34
|
|
|
30
35
|
const prevIdentityIdRef = useRef<string | null>(null);
|
|
36
|
+
// Generation ticket for async state transitions. Load (identity
|
|
37
|
+
// change / `refresh()`), `save()` and `clear()` each take a ticket;
|
|
38
|
+
// a resolution may only commit state while its ticket is the newest
|
|
39
|
+
// one. Without this, an in-flight load started before a save could
|
|
40
|
+
// resolve after it and clobber the just-saved state with pre-save
|
|
41
|
+
// data (out-of-order async update — observed as a rare CI flake in
|
|
42
|
+
// the rerender spec; pinned there by a gated-storage test).
|
|
43
|
+
const opRef = useRef(0);
|
|
44
|
+
const identityRef = useRef<Identity | null>(null);
|
|
45
|
+
// 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
|
|
46
|
+
identityRef.current = identity;
|
|
31
47
|
|
|
32
48
|
useEffect(() => {
|
|
33
49
|
let cancelled = false;
|
|
34
50
|
|
|
35
51
|
if (!identity) {
|
|
52
|
+
// 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
|
|
36
53
|
setContext(null);
|
|
37
54
|
setIsLoaded(true);
|
|
38
55
|
setError(null);
|
|
@@ -49,9 +66,10 @@ export function useIdentityContext(
|
|
|
49
66
|
prevIdentityIdRef.current = identityId;
|
|
50
67
|
|
|
51
68
|
// Open storage for this identity and load cached context (atomic)
|
|
69
|
+
const op = ++opRef.current;
|
|
52
70
|
storage.openAndLoad(identityId)
|
|
53
71
|
.then((data) => {
|
|
54
|
-
if (cancelled) return;
|
|
72
|
+
if (cancelled || op !== opRef.current) return;
|
|
55
73
|
setContext(data);
|
|
56
74
|
setIsLoaded(true);
|
|
57
75
|
setError(null);
|
|
@@ -68,19 +86,37 @@ export function useIdentityContext(
|
|
|
68
86
|
}, [identity, storage]);
|
|
69
87
|
|
|
70
88
|
const save = useCallback(async (data: IdentityContextData): Promise<void> => {
|
|
89
|
+
const idAtStart = identityToId(identityRef.current);
|
|
90
|
+
opRef.current += 1; // invalidate any in-flight load/refresh
|
|
71
91
|
await storage.save(data);
|
|
92
|
+
// If the identity switched while the write was in flight, state
|
|
93
|
+
// ownership has moved to the new identity's load effect — committing
|
|
94
|
+
// here would surface identity A's context under identity B. The
|
|
95
|
+
// generation ticket above only orders this op against in-flight
|
|
96
|
+
// loads; it does not know about identity switches.
|
|
97
|
+
if (identityToId(identityRef.current) !== idAtStart) return;
|
|
72
98
|
setContext(data);
|
|
99
|
+
setError(null);
|
|
73
100
|
}, [storage]);
|
|
74
101
|
|
|
75
102
|
const clear = useCallback(async (): Promise<void> => {
|
|
103
|
+
const idAtStart = identityToId(identityRef.current);
|
|
104
|
+
opRef.current += 1; // invalidate any in-flight load/refresh
|
|
76
105
|
await storage.clear();
|
|
106
|
+
// Same ownership rule as save(): a clear() resolving after an
|
|
107
|
+
// identity switch must not blank the new identity's freshly
|
|
108
|
+
// loaded context.
|
|
109
|
+
if (identityToId(identityRef.current) !== idAtStart) return;
|
|
77
110
|
setContext(null);
|
|
111
|
+
setError(null);
|
|
78
112
|
}, [storage]);
|
|
79
113
|
|
|
80
114
|
const refresh = useCallback(async (): Promise<void> => {
|
|
81
115
|
if (!identity) return;
|
|
82
116
|
const identityId = publicKeyToId(identity.getPublicKey());
|
|
117
|
+
const op = ++opRef.current;
|
|
83
118
|
const data = await storage.openAndLoad(identityId);
|
|
119
|
+
if (op !== opRef.current) return;
|
|
84
120
|
setContext(data);
|
|
85
121
|
}, [identity, storage]);
|
|
86
122
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useReducer, useCallback, type Reducer } from 'react';
|
|
3
3
|
import {
|
|
4
4
|
validateSeedPhrase,
|
|
5
|
-
|
|
5
|
+
get64ByteSeedFromMnemonic,
|
|
6
6
|
type Identity,
|
|
7
7
|
} from '@me2em/core';
|
|
8
8
|
import {
|
|
@@ -34,7 +34,7 @@ export function useImportSeed(): UseImportSeedResult {
|
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
try {
|
|
37
|
-
const seed = await
|
|
37
|
+
const seed = await get64ByteSeedFromMnemonic(words, passphrase ?? '');
|
|
38
38
|
dispatch({ type: 'IMPORT', seed });
|
|
39
39
|
await activateIdentity(seed);
|
|
40
40
|
} catch (err) {
|
package/src/react/useSession.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// packages/react/src/react/useSession.ts
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
3
|
import type { Handle } from '@me2em/core';
|
|
4
|
-
import { Session
|
|
5
|
-
import { useMe2emContext } from './context.js';
|
|
4
|
+
import { Session } from '@me2em/core';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Options for useSession, memoized via deep comparison of
|
|
@@ -74,7 +73,6 @@ export function useSession(
|
|
|
74
73
|
options: UseSessionOptions,
|
|
75
74
|
autoRenew: boolean = true
|
|
76
75
|
): UseSessionResult {
|
|
77
|
-
const { identity } = useMe2emContext();
|
|
78
76
|
const [session, setSession] = useState<Session | null>(null);
|
|
79
77
|
const [isLoaded, setIsLoaded] = useState(false);
|
|
80
78
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -82,8 +80,11 @@ export function useSession(
|
|
|
82
80
|
const optionsRef = useRef(options);
|
|
83
81
|
const mountedRef = useRef(true);
|
|
84
82
|
|
|
85
|
-
//
|
|
86
|
-
|
|
83
|
+
// Mirror the latest options into the ref after every commit —
|
|
84
|
+
// ref writes belong in effects, not during render (react-hooks/refs).
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
optionsRef.current = options;
|
|
87
|
+
});
|
|
87
88
|
|
|
88
89
|
const clearTimer = useCallback(() => {
|
|
89
90
|
if (timerRef.current) {
|
|
@@ -128,6 +129,7 @@ export function useSession(
|
|
|
128
129
|
const halfLife = Math.floor((s.expiresAt * 1000 - Date.now()) / 2);
|
|
129
130
|
if (halfLife > 0) {
|
|
130
131
|
timerRef.current = setTimeout(() => {
|
|
132
|
+
// 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
|
|
131
133
|
renew();
|
|
132
134
|
}, Math.max(halfLife, 1000));
|
|
133
135
|
}
|
|
@@ -12,6 +12,7 @@ export function useSubHandle(
|
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
let cancelled = false;
|
|
14
14
|
if (!identity) {
|
|
15
|
+
// 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
|
|
15
16
|
setSubHandle(null);
|
|
16
17
|
return;
|
|
17
18
|
}
|