@me2em/react 0.1.0-alpha.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/LICENSE +201 -0
- package/README.md +195 -0
- package/dist/index.d.ts +367 -0
- package/dist/index.js +1225 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/headless/context-crypto.ts +91 -0
- package/src/headless/context-key-store.ts +124 -0
- package/src/headless/display-flow.ts +113 -0
- package/src/headless/identity-context-storage.ts +205 -0
- package/src/headless/identity-context-types.ts +30 -0
- package/src/headless/identity-flow.ts +106 -0
- package/src/headless/import-flow.ts +82 -0
- package/src/headless/index.ts +53 -0
- package/src/headless/seed-words.ts +75 -0
- package/src/headless/types.ts +21 -0
- package/src/headless/verify-flow.ts +112 -0
- package/src/index.ts +2 -0
- package/src/react/Me2emProvider.tsx +33 -0
- package/src/react/PassphraseInput.tsx +37 -0
- package/src/react/SeedPhraseDisplay.tsx +92 -0
- package/src/react/SeedPhraseImport.tsx +110 -0
- package/src/react/SeedPhraseVerify.tsx +128 -0
- package/src/react/context.ts +19 -0
- package/src/react/index.ts +23 -0
- package/src/react/useCreateIdentity.ts +77 -0
- package/src/react/useHandle.ts +27 -0
- package/src/react/useIdentityContext.ts +88 -0
- package/src/react/useImportSeed.ts +60 -0
- package/src/react/useSession.ts +157 -0
- package/src/react/useSubHandle.ts +30 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Identity, SeedStrength, Handle, SubHandle, Session } from '@me2em/core';
|
|
4
|
+
|
|
5
|
+
declare function Me2emProvider({ children }: {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}): react.JSX.Element;
|
|
8
|
+
|
|
9
|
+
interface Me2emContextValue {
|
|
10
|
+
identity: Identity | null;
|
|
11
|
+
hasIdentity: boolean;
|
|
12
|
+
activateIdentity: (seed: Uint8Array) => Promise<boolean>;
|
|
13
|
+
clearIdentity: () => void;
|
|
14
|
+
}
|
|
15
|
+
declare function useMe2emContext(): Me2emContextValue;
|
|
16
|
+
|
|
17
|
+
type IdentityFlowStatus = 'idle' | 'seed-generated' | 'verified' | 'ready' | 'imported' | 'error';
|
|
18
|
+
interface IdentityFlowState {
|
|
19
|
+
status: IdentityFlowStatus;
|
|
20
|
+
seedWords: string[] | null;
|
|
21
|
+
identitySeed: Uint8Array | null;
|
|
22
|
+
error: string | null;
|
|
23
|
+
}
|
|
24
|
+
type IdentityFlowEvent = {
|
|
25
|
+
type: 'GENERATE';
|
|
26
|
+
words: string[];
|
|
27
|
+
seed: Uint8Array;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'IMPORT';
|
|
30
|
+
seed: Uint8Array;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'CONFIRM_WORDS';
|
|
33
|
+
} | {
|
|
34
|
+
type: 'RESET';
|
|
35
|
+
} | {
|
|
36
|
+
type: 'FAIL';
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
interface UseCreateIdentityResult {
|
|
41
|
+
status: IdentityFlowStatus;
|
|
42
|
+
seedWords: string[] | null;
|
|
43
|
+
identity: Identity | null;
|
|
44
|
+
error: string | null;
|
|
45
|
+
generate: (opts?: {
|
|
46
|
+
strength?: SeedStrength;
|
|
47
|
+
}) => void;
|
|
48
|
+
confirmWords: () => Promise<void>;
|
|
49
|
+
reset: () => void;
|
|
50
|
+
fail: (message: string) => void;
|
|
51
|
+
}
|
|
52
|
+
declare function useCreateIdentity(): UseCreateIdentityResult;
|
|
53
|
+
|
|
54
|
+
interface UseImportSeedResult {
|
|
55
|
+
status: IdentityFlowStatus;
|
|
56
|
+
identity: Identity | null;
|
|
57
|
+
error: string | null;
|
|
58
|
+
importWords: (words: string[], passphrase?: string) => Promise<void>;
|
|
59
|
+
reset: () => void;
|
|
60
|
+
}
|
|
61
|
+
declare function useImportSeed(): UseImportSeedResult;
|
|
62
|
+
|
|
63
|
+
declare function useHandle(name: string): Handle | null;
|
|
64
|
+
|
|
65
|
+
declare function useSubHandle(handleName: string, subName: string): SubHandle | null;
|
|
66
|
+
|
|
67
|
+
interface SeedPhraseDisplayProps {
|
|
68
|
+
words: string[];
|
|
69
|
+
onDismiss?: () => void;
|
|
70
|
+
onCopyAllowed?: () => void;
|
|
71
|
+
}
|
|
72
|
+
declare function SeedPhraseDisplay({ words, onDismiss, onCopyAllowed, }: SeedPhraseDisplayProps): react.JSX.Element;
|
|
73
|
+
|
|
74
|
+
interface SeedPhraseVerifyProps {
|
|
75
|
+
realWords: string[];
|
|
76
|
+
gridSize?: number;
|
|
77
|
+
wordlist?: readonly string[];
|
|
78
|
+
onVerified: () => void;
|
|
79
|
+
onFailed?: (attempts: number) => void;
|
|
80
|
+
}
|
|
81
|
+
declare function SeedPhraseVerify({ realWords, gridSize, wordlist: propWordlist, onVerified, onFailed, }: SeedPhraseVerifyProps): react.JSX.Element;
|
|
82
|
+
|
|
83
|
+
interface SeedPhraseImportProps {
|
|
84
|
+
expectedCount?: 12 | 24;
|
|
85
|
+
wordlist?: readonly string[];
|
|
86
|
+
onImported: (words: string[]) => void;
|
|
87
|
+
}
|
|
88
|
+
declare function SeedPhraseImport({ expectedCount: propCount, wordlist: propWordlist, onImported, }: SeedPhraseImportProps): react.JSX.Element;
|
|
89
|
+
|
|
90
|
+
interface PassphraseInputProps {
|
|
91
|
+
onPassphraseChange: (p: string) => void;
|
|
92
|
+
showWarnings?: boolean;
|
|
93
|
+
}
|
|
94
|
+
declare function PassphraseInput({ onPassphraseChange, showWarnings, }: PassphraseInputProps): react.JSX.Element;
|
|
95
|
+
|
|
96
|
+
interface IdentityContextData {
|
|
97
|
+
/** base64url identity public key — владелец кэша. */
|
|
98
|
+
identityId: string;
|
|
99
|
+
/** Реестр Handle-контекстов этой Identity. */
|
|
100
|
+
handles: Array<{
|
|
101
|
+
name: string;
|
|
102
|
+
subHandles?: string[];
|
|
103
|
+
meta?: Record<string, unknown>;
|
|
104
|
+
}>;
|
|
105
|
+
savedAt: number;
|
|
106
|
+
}
|
|
107
|
+
interface IdentityContextStorage {
|
|
108
|
+
open(identityId: string): Promise<void>;
|
|
109
|
+
save(data: IdentityContextData): Promise<void>;
|
|
110
|
+
load(): Promise<IdentityContextData | null>;
|
|
111
|
+
clear(): Promise<void>;
|
|
112
|
+
close(): Promise<void>;
|
|
113
|
+
/** Open storage for identity and atomically load cached context. */
|
|
114
|
+
openAndLoad(identityId: string): Promise<IdentityContextData | null>;
|
|
115
|
+
/** Set the persisted cache key (called at login). */
|
|
116
|
+
setCacheKey(key: CryptoKey): void;
|
|
117
|
+
/** Get the currently set cache key, or null. */
|
|
118
|
+
getCacheKey(): CryptoKey | null;
|
|
119
|
+
/** Persist the CryptoKey to IndexedDB for warm return after browser restart. */
|
|
120
|
+
persistCacheKey(): Promise<void>;
|
|
121
|
+
/** Load a previously persisted CryptoKey from IndexedDB. */
|
|
122
|
+
loadPersistedKey(): Promise<CryptoKey | null>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface UseIdentityContextResult {
|
|
126
|
+
context: IdentityContextData | null;
|
|
127
|
+
isLoaded: boolean;
|
|
128
|
+
error: string | null;
|
|
129
|
+
save: (data: IdentityContextData) => Promise<void>;
|
|
130
|
+
clear: () => Promise<void>;
|
|
131
|
+
/** Reload the cache for the current identity (after sync). */
|
|
132
|
+
refresh: () => Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
declare function useIdentityContext(storage: IdentityContextStorage): UseIdentityContextResult;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Options for useSession, memoized via deep comparison of
|
|
138
|
+
* `handle` and `options` refs on each render.
|
|
139
|
+
*
|
|
140
|
+
* @category Types
|
|
141
|
+
*/
|
|
142
|
+
interface UseSessionOptions {
|
|
143
|
+
/** Intended audience (recipient) of this session. */
|
|
144
|
+
audience: string;
|
|
145
|
+
/** Permission scopes granted in this session. */
|
|
146
|
+
scopes: string[];
|
|
147
|
+
/** Time-to-live in seconds. */
|
|
148
|
+
ttl: number;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Result of the useSession hook.
|
|
152
|
+
*
|
|
153
|
+
* @category Types
|
|
154
|
+
*/
|
|
155
|
+
interface UseSessionResult {
|
|
156
|
+
/** The active session, or null if no handle or session creation failed. */
|
|
157
|
+
session: Session | null;
|
|
158
|
+
/** Whether the session (or its absence) has been determined. */
|
|
159
|
+
isLoaded: boolean;
|
|
160
|
+
/** Error string if session creation or renewal failed. */
|
|
161
|
+
error: string | null;
|
|
162
|
+
/** Whether the current session has expired. */
|
|
163
|
+
isExpired: boolean;
|
|
164
|
+
/** Manually renew the session (creates a new one with a fresh jti). */
|
|
165
|
+
renew: () => void;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Hook that creates and manages a signed session token for a Handle.
|
|
169
|
+
*
|
|
170
|
+
* When `handle` is provided, creates a Session with the given options.
|
|
171
|
+
* Supports auto-renewal: when `autoRenew` is true (default), a timer
|
|
172
|
+
* fires at half the session TTL to create a fresh session with a new
|
|
173
|
+
* jti (the previous token remains valid until its own expiry, but the
|
|
174
|
+
* lineage identity is preserved through `handle`).
|
|
175
|
+
*
|
|
176
|
+
* On unmount, all timers are cleaned up to prevent state updates after
|
|
177
|
+
* the component is gone.
|
|
178
|
+
*
|
|
179
|
+
* Dependencies: `[handle, renew]` — the options object is read through
|
|
180
|
+
* a ref, so passing a fresh object literal on every render does NOT
|
|
181
|
+
* re-trigger session creation. Only changes to `handle` (or to the
|
|
182
|
+
* renewal function identity, which follows `handle`) do.
|
|
183
|
+
*
|
|
184
|
+
* @param handle - The Handle to sign the session, or null.
|
|
185
|
+
* @param options - Session options (audience, scopes, ttl).
|
|
186
|
+
* @param autoRenew - Whether to auto-renew at half-life (default: true).
|
|
187
|
+
* @returns Session state with manual `renew` control.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* const handle = useHandle('my-handle');
|
|
192
|
+
* const { session, isExpired, renew } = useSession(handle, {
|
|
193
|
+
* audience: 'app.example.com',
|
|
194
|
+
* scopes: ['read', 'write'],
|
|
195
|
+
* ttl: 3600,
|
|
196
|
+
* });
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
declare function useSession(handle: Handle | null, options: UseSessionOptions, autoRenew?: boolean): UseSessionResult;
|
|
200
|
+
|
|
201
|
+
declare const initialIdentityFlowState: IdentityFlowState;
|
|
202
|
+
declare function identityFlowReducer(state: IdentityFlowState, event: IdentityFlowEvent): IdentityFlowState;
|
|
203
|
+
|
|
204
|
+
declare function shuffleWithSeed<T>(items: T[], seed: number): T[];
|
|
205
|
+
declare function buildVerificationGrid(realWords: string[], gridSize: number, randomSeed: number, allWords: readonly string[]): {
|
|
206
|
+
words: string[];
|
|
207
|
+
correctIndices: number[];
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Selection is compared IN ORDER: selected[i] must equal
|
|
211
|
+
* correctIndices[i]. The i-th click must be the i-th word of the
|
|
212
|
+
* phrase. Repeated phrase words map to distinct grid cells.
|
|
213
|
+
*/
|
|
214
|
+
declare function isGridSelectionCorrect(grid: {
|
|
215
|
+
words: string[];
|
|
216
|
+
correctIndices: number[];
|
|
217
|
+
}, selected: number[]): boolean;
|
|
218
|
+
|
|
219
|
+
type DisplayStatus = 'hidden' | 'revealed' | 'copied' | 'dismissed';
|
|
220
|
+
interface DisplayState {
|
|
221
|
+
status: DisplayStatus;
|
|
222
|
+
copyCount: number;
|
|
223
|
+
revealedAt: number | null;
|
|
224
|
+
}
|
|
225
|
+
type DisplayEvent = {
|
|
226
|
+
type: 'REVEAL';
|
|
227
|
+
at: number;
|
|
228
|
+
} | {
|
|
229
|
+
type: 'COPY';
|
|
230
|
+
at: number;
|
|
231
|
+
} | {
|
|
232
|
+
type: 'DISMISS';
|
|
233
|
+
} | {
|
|
234
|
+
type: 'RESET';
|
|
235
|
+
};
|
|
236
|
+
declare const initialDisplayState: DisplayState;
|
|
237
|
+
declare function displayFlowReducer(state: DisplayState, event: DisplayEvent): DisplayState;
|
|
238
|
+
|
|
239
|
+
type VerifyStatus = 'collecting' | 'correct' | 'incorrect';
|
|
240
|
+
interface VerifyState {
|
|
241
|
+
status: VerifyStatus;
|
|
242
|
+
grid: {
|
|
243
|
+
words: string[];
|
|
244
|
+
correctIndices: number[];
|
|
245
|
+
};
|
|
246
|
+
selected: number[];
|
|
247
|
+
attempts: number;
|
|
248
|
+
}
|
|
249
|
+
type VerifyEvent = {
|
|
250
|
+
type: 'SELECT';
|
|
251
|
+
index: number;
|
|
252
|
+
} | {
|
|
253
|
+
type: 'DESELECT';
|
|
254
|
+
index: number;
|
|
255
|
+
} | {
|
|
256
|
+
type: 'CHECK';
|
|
257
|
+
} | {
|
|
258
|
+
type: 'RESET';
|
|
259
|
+
realWords: string[];
|
|
260
|
+
gridSize: number;
|
|
261
|
+
randomSeed: number;
|
|
262
|
+
allWords: readonly string[];
|
|
263
|
+
};
|
|
264
|
+
declare function initVerifyState(realWords: string[], gridSize: number, randomSeed: number, allWords: readonly string[]): VerifyState;
|
|
265
|
+
declare function verifyFlowReducer(state: VerifyState, event: VerifyEvent): VerifyState;
|
|
266
|
+
|
|
267
|
+
type ImportStatus = 'entering' | 'valid' | 'invalid';
|
|
268
|
+
interface ImportState {
|
|
269
|
+
status: ImportStatus;
|
|
270
|
+
words: (string | null)[];
|
|
271
|
+
expectedCount: 12 | 24;
|
|
272
|
+
}
|
|
273
|
+
type ImportEvent = {
|
|
274
|
+
type: 'INIT';
|
|
275
|
+
expectedCount: 12 | 24;
|
|
276
|
+
} | {
|
|
277
|
+
type: 'SET_WORD';
|
|
278
|
+
position: number;
|
|
279
|
+
word: string;
|
|
280
|
+
} | {
|
|
281
|
+
type: 'CLEAR_WORD';
|
|
282
|
+
position: number;
|
|
283
|
+
} | {
|
|
284
|
+
type: 'CHECK';
|
|
285
|
+
} | {
|
|
286
|
+
type: 'RESET';
|
|
287
|
+
};
|
|
288
|
+
declare function initImportState(expectedCount: 12 | 24): ImportState;
|
|
289
|
+
declare function importFlowReducer(state: ImportState, event: ImportEvent): ImportState;
|
|
290
|
+
|
|
291
|
+
declare class IdentityContextIDBStorage implements IdentityContextStorage {
|
|
292
|
+
private db;
|
|
293
|
+
private currentIdentityId;
|
|
294
|
+
private cacheKey;
|
|
295
|
+
private getDb;
|
|
296
|
+
open(identityId: string): Promise<void>;
|
|
297
|
+
openAndLoad(identityId: string): Promise<IdentityContextData | null>;
|
|
298
|
+
save(data: IdentityContextData): Promise<void>;
|
|
299
|
+
load(): Promise<IdentityContextData | null>;
|
|
300
|
+
clear(): Promise<void>;
|
|
301
|
+
close(): Promise<void>;
|
|
302
|
+
setCacheKey(key: CryptoKey): void;
|
|
303
|
+
getCacheKey(): CryptoKey | null;
|
|
304
|
+
persistCacheKey(): Promise<void>;
|
|
305
|
+
loadPersistedKey(): Promise<CryptoKey | null>;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface ContextEnvelope {
|
|
309
|
+
v: 1;
|
|
310
|
+
/** fresh HKDF salt этого логина (b64). Нужен только при
|
|
311
|
+
* первичной деривации; persisted CryptoKey переживает
|
|
312
|
+
* перезапуск без него. */
|
|
313
|
+
salt: string;
|
|
314
|
+
iv: string;
|
|
315
|
+
ct: string;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Derives a fresh, per-login cache key from login-time secret
|
|
319
|
+
* material and wraps it as a NON-EXTRACTABLE persisted CryptoKey.
|
|
320
|
+
* Raw key bytes exist only transiently during importKey.
|
|
321
|
+
*
|
|
322
|
+
* @param loginMaterial - high-entropy bytes available at login
|
|
323
|
+
* (derived from the seed while it is still in memory).
|
|
324
|
+
* @param salt - FRESH random bytes for this login. Never reuse a
|
|
325
|
+
* salt across logins: the whole point is that a compromised
|
|
326
|
+
* cacheKey from session N is useless in session N+1.
|
|
327
|
+
*/
|
|
328
|
+
declare function deriveCacheKey(loginMaterial: Uint8Array, salt: Uint8Array): Promise<CryptoKey>;
|
|
329
|
+
declare function encryptContext(key: CryptoKey, data: IdentityContextData): Promise<ContextEnvelope>;
|
|
330
|
+
declare function decryptContext(key: CryptoKey, envelope: ContextEnvelope): Promise<IdentityContextData>;
|
|
331
|
+
|
|
332
|
+
type index_ContextEnvelope = ContextEnvelope;
|
|
333
|
+
type index_DisplayEvent = DisplayEvent;
|
|
334
|
+
type index_DisplayState = DisplayState;
|
|
335
|
+
type index_DisplayStatus = DisplayStatus;
|
|
336
|
+
type index_IdentityContextData = IdentityContextData;
|
|
337
|
+
type index_IdentityContextIDBStorage = IdentityContextIDBStorage;
|
|
338
|
+
declare const index_IdentityContextIDBStorage: typeof IdentityContextIDBStorage;
|
|
339
|
+
type index_IdentityContextStorage = IdentityContextStorage;
|
|
340
|
+
type index_IdentityFlowEvent = IdentityFlowEvent;
|
|
341
|
+
type index_IdentityFlowState = IdentityFlowState;
|
|
342
|
+
type index_IdentityFlowStatus = IdentityFlowStatus;
|
|
343
|
+
type index_ImportEvent = ImportEvent;
|
|
344
|
+
type index_ImportState = ImportState;
|
|
345
|
+
type index_ImportStatus = ImportStatus;
|
|
346
|
+
type index_VerifyEvent = VerifyEvent;
|
|
347
|
+
type index_VerifyState = VerifyState;
|
|
348
|
+
type index_VerifyStatus = VerifyStatus;
|
|
349
|
+
declare const index_buildVerificationGrid: typeof buildVerificationGrid;
|
|
350
|
+
declare const index_decryptContext: typeof decryptContext;
|
|
351
|
+
declare const index_deriveCacheKey: typeof deriveCacheKey;
|
|
352
|
+
declare const index_displayFlowReducer: typeof displayFlowReducer;
|
|
353
|
+
declare const index_encryptContext: typeof encryptContext;
|
|
354
|
+
declare const index_identityFlowReducer: typeof identityFlowReducer;
|
|
355
|
+
declare const index_importFlowReducer: typeof importFlowReducer;
|
|
356
|
+
declare const index_initImportState: typeof initImportState;
|
|
357
|
+
declare const index_initVerifyState: typeof initVerifyState;
|
|
358
|
+
declare const index_initialDisplayState: typeof initialDisplayState;
|
|
359
|
+
declare const index_initialIdentityFlowState: typeof initialIdentityFlowState;
|
|
360
|
+
declare const index_isGridSelectionCorrect: typeof isGridSelectionCorrect;
|
|
361
|
+
declare const index_shuffleWithSeed: typeof shuffleWithSeed;
|
|
362
|
+
declare const index_verifyFlowReducer: typeof verifyFlowReducer;
|
|
363
|
+
declare namespace index {
|
|
364
|
+
export { type index_ContextEnvelope as ContextEnvelope, type index_DisplayEvent as DisplayEvent, type index_DisplayState as DisplayState, type index_DisplayStatus as DisplayStatus, type index_IdentityContextData as IdentityContextData, index_IdentityContextIDBStorage as IdentityContextIDBStorage, type index_IdentityContextStorage as IdentityContextStorage, type index_IdentityFlowEvent as IdentityFlowEvent, type index_IdentityFlowState as IdentityFlowState, type index_IdentityFlowStatus as IdentityFlowStatus, type index_ImportEvent as ImportEvent, type index_ImportState as ImportState, type index_ImportStatus as ImportStatus, type index_VerifyEvent as VerifyEvent, type index_VerifyState as VerifyState, type index_VerifyStatus as VerifyStatus, index_buildVerificationGrid as buildVerificationGrid, index_decryptContext as decryptContext, index_deriveCacheKey as deriveCacheKey, index_displayFlowReducer as displayFlowReducer, index_encryptContext as encryptContext, index_identityFlowReducer as identityFlowReducer, index_importFlowReducer as importFlowReducer, index_initImportState as initImportState, index_initVerifyState as initVerifyState, index_initialDisplayState as initialDisplayState, index_initialIdentityFlowState as initialIdentityFlowState, index_isGridSelectionCorrect as isGridSelectionCorrect, index_shuffleWithSeed as shuffleWithSeed, index_verifyFlowReducer as verifyFlowReducer };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export { Me2emProvider, PassphraseInput, type PassphraseInputProps, SeedPhraseDisplay, type SeedPhraseDisplayProps, SeedPhraseImport, type SeedPhraseImportProps, SeedPhraseVerify, type SeedPhraseVerifyProps, type UseIdentityContextResult, type UseSessionOptions, type UseSessionResult, index as headless, useCreateIdentity, useHandle, useIdentityContext, useImportSeed, useMe2emContext, useSession, useSubHandle };
|