@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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// packages/react/src/react/useSession.ts
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
import type { Handle } from '@me2em/core';
|
|
4
|
+
import { Session, type SessionOptions, type SessionPayload } from '@me2em/core';
|
|
5
|
+
import { useMe2emContext } from './context.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for useSession, memoized via deep comparison of
|
|
9
|
+
* `handle` and `options` refs on each render.
|
|
10
|
+
*
|
|
11
|
+
* @category Types
|
|
12
|
+
*/
|
|
13
|
+
export interface UseSessionOptions {
|
|
14
|
+
/** Intended audience (recipient) of this session. */
|
|
15
|
+
audience: string;
|
|
16
|
+
/** Permission scopes granted in this session. */
|
|
17
|
+
scopes: string[];
|
|
18
|
+
/** Time-to-live in seconds. */
|
|
19
|
+
ttl: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Result of the useSession hook.
|
|
24
|
+
*
|
|
25
|
+
* @category Types
|
|
26
|
+
*/
|
|
27
|
+
export interface UseSessionResult {
|
|
28
|
+
/** The active session, or null if no handle or session creation failed. */
|
|
29
|
+
session: Session | null;
|
|
30
|
+
/** Whether the session (or its absence) has been determined. */
|
|
31
|
+
isLoaded: boolean;
|
|
32
|
+
/** Error string if session creation or renewal failed. */
|
|
33
|
+
error: string | null;
|
|
34
|
+
/** Whether the current session has expired. */
|
|
35
|
+
isExpired: boolean;
|
|
36
|
+
/** Manually renew the session (creates a new one with a fresh jti). */
|
|
37
|
+
renew: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hook that creates and manages a signed session token for a Handle.
|
|
42
|
+
*
|
|
43
|
+
* When `handle` is provided, creates a Session with the given options.
|
|
44
|
+
* Supports auto-renewal: when `autoRenew` is true (default), a timer
|
|
45
|
+
* fires at half the session TTL to create a fresh session with a new
|
|
46
|
+
* jti (the previous token remains valid until its own expiry, but the
|
|
47
|
+
* lineage identity is preserved through `handle`).
|
|
48
|
+
*
|
|
49
|
+
* On unmount, all timers are cleaned up to prevent state updates after
|
|
50
|
+
* the component is gone.
|
|
51
|
+
*
|
|
52
|
+
* Dependencies: `[handle, renew]` — the options object is read through
|
|
53
|
+
* a ref, so passing a fresh object literal on every render does NOT
|
|
54
|
+
* re-trigger session creation. Only changes to `handle` (or to the
|
|
55
|
+
* renewal function identity, which follows `handle`) do.
|
|
56
|
+
*
|
|
57
|
+
* @param handle - The Handle to sign the session, or null.
|
|
58
|
+
* @param options - Session options (audience, scopes, ttl).
|
|
59
|
+
* @param autoRenew - Whether to auto-renew at half-life (default: true).
|
|
60
|
+
* @returns Session state with manual `renew` control.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* const handle = useHandle('my-handle');
|
|
65
|
+
* const { session, isExpired, renew } = useSession(handle, {
|
|
66
|
+
* audience: 'app.example.com',
|
|
67
|
+
* scopes: ['read', 'write'],
|
|
68
|
+
* ttl: 3600,
|
|
69
|
+
* });
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function useSession(
|
|
73
|
+
handle: Handle | null,
|
|
74
|
+
options: UseSessionOptions,
|
|
75
|
+
autoRenew: boolean = true
|
|
76
|
+
): UseSessionResult {
|
|
77
|
+
const { identity } = useMe2emContext();
|
|
78
|
+
const [session, setSession] = useState<Session | null>(null);
|
|
79
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
80
|
+
const [error, setError] = useState<string | null>(null);
|
|
81
|
+
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
82
|
+
const optionsRef = useRef(options);
|
|
83
|
+
const mountedRef = useRef(true);
|
|
84
|
+
|
|
85
|
+
// Update options ref without triggering re-renders
|
|
86
|
+
optionsRef.current = options;
|
|
87
|
+
|
|
88
|
+
const clearTimer = useCallback(() => {
|
|
89
|
+
if (timerRef.current) {
|
|
90
|
+
clearTimeout(timerRef.current);
|
|
91
|
+
timerRef.current = null;
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
// Cleanup on unmount
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
mountedRef.current = true;
|
|
98
|
+
return () => {
|
|
99
|
+
mountedRef.current = false;
|
|
100
|
+
clearTimer();
|
|
101
|
+
};
|
|
102
|
+
}, [clearTimer]);
|
|
103
|
+
|
|
104
|
+
const renew = useCallback(() => {
|
|
105
|
+
clearTimer();
|
|
106
|
+
const opts = optionsRef.current;
|
|
107
|
+
|
|
108
|
+
if (!handle) {
|
|
109
|
+
if (!mountedRef.current) return;
|
|
110
|
+
setSession(null);
|
|
111
|
+
setIsLoaded(true);
|
|
112
|
+
setError(null);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Session.create(handle, {
|
|
117
|
+
audience: opts.audience,
|
|
118
|
+
scopes: opts.scopes,
|
|
119
|
+
ttl: opts.ttl,
|
|
120
|
+
}).then((s) => {
|
|
121
|
+
if (!mountedRef.current) return;
|
|
122
|
+
setSession(s);
|
|
123
|
+
setIsLoaded(true);
|
|
124
|
+
setError(null);
|
|
125
|
+
|
|
126
|
+
// Schedule auto-renewal at half-life
|
|
127
|
+
if (autoRenew) {
|
|
128
|
+
const halfLife = Math.floor((s.expiresAt * 1000 - Date.now()) / 2);
|
|
129
|
+
if (halfLife > 0) {
|
|
130
|
+
timerRef.current = setTimeout(() => {
|
|
131
|
+
renew();
|
|
132
|
+
}, Math.max(halfLife, 1000));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}).catch((err) => {
|
|
136
|
+
if (!mountedRef.current) return;
|
|
137
|
+
setSession(null);
|
|
138
|
+
setIsLoaded(true);
|
|
139
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
140
|
+
});
|
|
141
|
+
}, [handle, autoRenew, clearTimer]);
|
|
142
|
+
|
|
143
|
+
// Create session when handle or options change
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (mountedRef.current) {
|
|
146
|
+
renew();
|
|
147
|
+
}
|
|
148
|
+
}, [handle, renew]);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
session,
|
|
152
|
+
isLoaded,
|
|
153
|
+
error,
|
|
154
|
+
isExpired: session ? session.isExpired() : false,
|
|
155
|
+
renew,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { type SubHandle } from '@me2em/core';
|
|
3
|
+
import { useMe2emContext } from './context.js';
|
|
4
|
+
|
|
5
|
+
export function useSubHandle(
|
|
6
|
+
handleName: string,
|
|
7
|
+
subName: string
|
|
8
|
+
): SubHandle | null {
|
|
9
|
+
const { identity } = useMe2emContext();
|
|
10
|
+
const [subHandle, setSubHandle] = useState<SubHandle | null>(null);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
let cancelled = false;
|
|
14
|
+
if (!identity) {
|
|
15
|
+
setSubHandle(null);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
identity
|
|
19
|
+
.deriveSubHandle(handleName, subName)
|
|
20
|
+
.then((h) => {
|
|
21
|
+
if (!cancelled) setSubHandle(h);
|
|
22
|
+
})
|
|
23
|
+
.catch((err) => {
|
|
24
|
+
if (!cancelled) console.error('useSubHandle derivation failed', err);
|
|
25
|
+
});
|
|
26
|
+
return () => { cancelled = true; };
|
|
27
|
+
}, [identity, handleName, subName]);
|
|
28
|
+
|
|
29
|
+
return subHandle;
|
|
30
|
+
}
|