@waaskey/react 0.3.1 → 0.4.0
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 +131 -31
- package/dist/index.cjs +439 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +139 -21
- package/dist/index.d.ts +139 -21
- package/dist/index.js +437 -137
- package/dist/index.js.map +1 -1
- package/package.json +5 -6
package/dist/index.js
CHANGED
|
@@ -1,8 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { Waaskey } from '@waaskey/sdk';
|
|
2
3
|
export * from '@waaskey/sdk';
|
|
3
|
-
import { createContext, useContext, useMemo, createElement, useState, useCallback, useEffect
|
|
4
|
+
import { createContext, useContext, useRef, useMemo, createElement, useState, useCallback, useEffect } from 'react';
|
|
4
5
|
|
|
5
6
|
// src/provider.ts
|
|
7
|
+
var AuthContext = createContext(null);
|
|
8
|
+
var STORAGE_KEY = "waaskey.session";
|
|
9
|
+
function webSessionStore(area) {
|
|
10
|
+
const storage = () => {
|
|
11
|
+
try {
|
|
12
|
+
if (typeof window === "undefined") return void 0;
|
|
13
|
+
return area === "local" ? window.localStorage : window.sessionStorage;
|
|
14
|
+
} catch {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
load() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = storage()?.getItem(STORAGE_KEY);
|
|
22
|
+
if (!raw) return void 0;
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
return parsed?.token ? parsed : void 0;
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
save(session) {
|
|
30
|
+
try {
|
|
31
|
+
storage()?.setItem(STORAGE_KEY, JSON.stringify(session));
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
clear() {
|
|
36
|
+
try {
|
|
37
|
+
storage()?.removeItem(STORAGE_KEY);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function resolveStore(persistence) {
|
|
44
|
+
if (persistence === "none") return void 0;
|
|
45
|
+
if (persistence === "session" || persistence === "local") return webSessionStore(persistence);
|
|
46
|
+
return persistence;
|
|
47
|
+
}
|
|
48
|
+
function isUnauthenticated(error) {
|
|
49
|
+
const code = error?.code;
|
|
50
|
+
const status = error?.status;
|
|
51
|
+
return code === "unauthenticated" || status === 401;
|
|
52
|
+
}
|
|
53
|
+
function useAuthStore(client, persistence) {
|
|
54
|
+
const store = useMemo(() => resolveStore(persistence), [persistence]);
|
|
55
|
+
const [session, setSessionState] = useState();
|
|
56
|
+
const [user, setUser] = useState();
|
|
57
|
+
const [error, setError] = useState();
|
|
58
|
+
const [loading, setLoading] = useState(false);
|
|
59
|
+
const [ready, setReady] = useState(!store);
|
|
60
|
+
const mounted = useRef(true);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
mounted.current = true;
|
|
63
|
+
return () => {
|
|
64
|
+
mounted.current = false;
|
|
65
|
+
};
|
|
66
|
+
}, []);
|
|
67
|
+
const setSession = useCallback(
|
|
68
|
+
(next) => {
|
|
69
|
+
if (next) {
|
|
70
|
+
client.auth.restore(next);
|
|
71
|
+
void store?.save(next);
|
|
72
|
+
} else {
|
|
73
|
+
client.auth.logout();
|
|
74
|
+
void store?.clear();
|
|
75
|
+
}
|
|
76
|
+
setSessionState(next);
|
|
77
|
+
setUser(next?.endUser);
|
|
78
|
+
setError(void 0);
|
|
79
|
+
},
|
|
80
|
+
[client, store]
|
|
81
|
+
);
|
|
82
|
+
const logout = useCallback(() => setSession(void 0), [setSession]);
|
|
83
|
+
const refreshUser = useCallback(async () => {
|
|
84
|
+
if (!client.auth.isAuthenticated) return;
|
|
85
|
+
setLoading(true);
|
|
86
|
+
try {
|
|
87
|
+
const me = await client.auth.me();
|
|
88
|
+
if (mounted.current) setUser(me);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (!mounted.current) return;
|
|
91
|
+
if (isUnauthenticated(e)) setSession(void 0);
|
|
92
|
+
else setError(e);
|
|
93
|
+
} finally {
|
|
94
|
+
if (mounted.current) setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}, [client, setSession]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (session && !client.auth.isAuthenticated) client.auth.restore(session);
|
|
99
|
+
}, [client, session]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!store) {
|
|
102
|
+
setReady(true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let cancelled = false;
|
|
106
|
+
setReady(false);
|
|
107
|
+
void (async () => {
|
|
108
|
+
let restored;
|
|
109
|
+
try {
|
|
110
|
+
restored = await store.load();
|
|
111
|
+
} catch {
|
|
112
|
+
restored = void 0;
|
|
113
|
+
}
|
|
114
|
+
if (cancelled) return;
|
|
115
|
+
if (restored) {
|
|
116
|
+
client.auth.restore(restored);
|
|
117
|
+
setSessionState(restored);
|
|
118
|
+
setUser(restored.endUser);
|
|
119
|
+
}
|
|
120
|
+
setReady(true);
|
|
121
|
+
if (restored) {
|
|
122
|
+
try {
|
|
123
|
+
const me = await client.auth.me();
|
|
124
|
+
if (!cancelled && mounted.current) setUser(me);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if (cancelled || !mounted.current) return;
|
|
127
|
+
if (isUnauthenticated(e)) {
|
|
128
|
+
client.auth.logout();
|
|
129
|
+
void store.clear();
|
|
130
|
+
setSessionState(void 0);
|
|
131
|
+
setUser(void 0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})();
|
|
136
|
+
return () => {
|
|
137
|
+
cancelled = true;
|
|
138
|
+
};
|
|
139
|
+
}, [client, store]);
|
|
140
|
+
return useMemo(() => ({ session, user, ready, loading, error, setSession, refreshUser, logout }), [session, user, ready, loading, error, setSession, refreshUser, logout]);
|
|
141
|
+
}
|
|
6
142
|
var WaaskeyContext = createContext(null);
|
|
7
143
|
|
|
8
144
|
// src/theme.ts
|
|
@@ -39,12 +175,89 @@ function useTheme() {
|
|
|
39
175
|
|
|
40
176
|
// src/provider.ts
|
|
41
177
|
function WaaskeyProvider(props) {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
178
|
+
const externalClient = "client" in props ? props.client : void 0;
|
|
179
|
+
const options = "client" in props ? void 0 : props.options;
|
|
180
|
+
const optionsKey = options ? optionsIdentity(options) : "";
|
|
181
|
+
const held = useRef(void 0);
|
|
182
|
+
if (!held.current || held.current.key !== optionsKey || externalClient !== void 0 && held.current.client !== externalClient) {
|
|
183
|
+
if (held.current) warnClientReplaced(externalClient !== void 0);
|
|
184
|
+
held.current = { key: optionsKey, client: externalClient ?? new Waaskey(options) };
|
|
185
|
+
}
|
|
186
|
+
const { client } = held.current;
|
|
44
187
|
const theme = useMemo(() => resolveTheme(props.theme), [props.theme]);
|
|
45
|
-
|
|
188
|
+
const auth = useAuthStore(client, props.persistSession ?? "none");
|
|
189
|
+
return createElement(
|
|
190
|
+
WaaskeyContext.Provider,
|
|
191
|
+
{ value: client },
|
|
192
|
+
createElement(AuthContext.Provider, { value: auth }, createElement(ThemeContext.Provider, { value: theme }, props.children))
|
|
193
|
+
);
|
|
46
194
|
}
|
|
47
195
|
var WaasProvider = WaaskeyProvider;
|
|
196
|
+
var objectIds = /* @__PURE__ */ new WeakMap();
|
|
197
|
+
var nextObjectId = 0;
|
|
198
|
+
function idOf(value) {
|
|
199
|
+
let id = objectIds.get(value);
|
|
200
|
+
if (id === void 0) {
|
|
201
|
+
id = ++nextObjectId;
|
|
202
|
+
objectIds.set(value, id);
|
|
203
|
+
}
|
|
204
|
+
return id;
|
|
205
|
+
}
|
|
206
|
+
function optionsIdentity(options) {
|
|
207
|
+
const record = options;
|
|
208
|
+
return Object.keys(record).sort().map((key) => {
|
|
209
|
+
const value = record[key];
|
|
210
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return `${key}=${String(value)}`;
|
|
211
|
+
return `${key}#${idOf(value)}`;
|
|
212
|
+
}).join("|");
|
|
213
|
+
}
|
|
214
|
+
function warnClientReplaced(external) {
|
|
215
|
+
try {
|
|
216
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
const cause = external ? "the `client` prop changed" : "an option value changed identity";
|
|
220
|
+
console.warn(
|
|
221
|
+
`[waaskey] WaaskeyProvider replaced its client because ${cause}. Any in-flight ceremony is abandoned and cached state is lost (the end-user session is re-adopted automatically). Enriching the options once after login is expected; a replacement on every render means the options are rebuilt inline \u2014 hoist or memoize them.`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function useAsyncResource(load) {
|
|
225
|
+
const [state, setState] = useState({ loading: load !== void 0 });
|
|
226
|
+
const active = useRef(void 0);
|
|
227
|
+
const mounted = useRef(true);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
mounted.current = true;
|
|
230
|
+
return () => {
|
|
231
|
+
mounted.current = false;
|
|
232
|
+
active.current?.abort();
|
|
233
|
+
};
|
|
234
|
+
}, []);
|
|
235
|
+
const refresh = useCallback(async () => {
|
|
236
|
+
if (!load) return;
|
|
237
|
+
active.current?.abort();
|
|
238
|
+
const controller = new AbortController();
|
|
239
|
+
active.current = controller;
|
|
240
|
+
setState((prev) => ({ ...prev, loading: true, error: void 0 }));
|
|
241
|
+
try {
|
|
242
|
+
const data = await load(controller.signal);
|
|
243
|
+
if (controller.signal.aborted || !mounted.current) return;
|
|
244
|
+
setState({ loading: false, data });
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if (controller.signal.aborted || !mounted.current) return;
|
|
247
|
+
setState({ loading: false, error });
|
|
248
|
+
}
|
|
249
|
+
}, [load]);
|
|
250
|
+
useEffect(() => {
|
|
251
|
+
if (!load) {
|
|
252
|
+
setState((prev) => prev.data === void 0 && prev.error === void 0 && !prev.loading ? prev : { loading: false });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
void refresh();
|
|
256
|
+
}, [load, refresh]);
|
|
257
|
+
return { ...state, refresh };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/hooks.ts
|
|
48
261
|
function useWaaskey() {
|
|
49
262
|
const client = useContext(WaaskeyContext);
|
|
50
263
|
if (!client) throw new Error("useWaaskey must be used within a <WaaskeyProvider>.");
|
|
@@ -76,70 +289,41 @@ function useCreateWallet() {
|
|
|
76
289
|
}
|
|
77
290
|
function useWallet(id) {
|
|
78
291
|
const waaskey = useWaaskey();
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}, [waaskey, id]);
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
void refresh();
|
|
91
|
-
}, [refresh]);
|
|
92
|
-
return { ...state, refresh };
|
|
292
|
+
const load = useCallback((signal) => waaskey.wallets.get(id, { signal }), [waaskey, id]);
|
|
293
|
+
return useAsyncResource(id ? load : void 0);
|
|
294
|
+
}
|
|
295
|
+
function useWallets(query = {}) {
|
|
296
|
+
const waaskey = useWaaskey();
|
|
297
|
+
const { page, limit } = query;
|
|
298
|
+
const load = useCallback((signal) => waaskey.wallets.list({ page, limit }, { signal }), [waaskey, page, limit]);
|
|
299
|
+
const { data, ...rest } = useAsyncResource(load);
|
|
300
|
+
return { ...rest, data: data?.items, total: data?.total };
|
|
93
301
|
}
|
|
94
302
|
function useBalance(chain, address) {
|
|
95
303
|
const waaskey = useWaaskey();
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
if (!chain || !address) return;
|
|
99
|
-
setState({ loading: true });
|
|
100
|
-
try {
|
|
101
|
-
setState({ loading: false, data: await waaskey.balances.getBalance(chain, address) });
|
|
102
|
-
} catch (error) {
|
|
103
|
-
setState({ loading: false, error });
|
|
104
|
-
}
|
|
105
|
-
}, [waaskey, chain, address]);
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
void refresh();
|
|
108
|
-
}, [refresh]);
|
|
109
|
-
return { ...state, refresh };
|
|
304
|
+
const load = useCallback(() => waaskey.balances.getBalance(chain, address), [waaskey, chain, address]);
|
|
305
|
+
return useAsyncResource(chain && address ? load : void 0);
|
|
110
306
|
}
|
|
111
307
|
function useBalances(chains, address) {
|
|
112
308
|
const waaskey = useWaaskey();
|
|
113
|
-
const [state, setState] = useState({ loading: Boolean(address && chains.length) });
|
|
114
309
|
const key = chains.join(",");
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const data = await Promise.all(key.split(",").map((chain) => waaskey.balances.getBalance(chain, address)));
|
|
120
|
-
setState({ loading: false, data });
|
|
121
|
-
} catch (error) {
|
|
122
|
-
setState({ loading: false, error });
|
|
123
|
-
}
|
|
124
|
-
}, [waaskey, key, address]);
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
void refresh();
|
|
127
|
-
}, [refresh]);
|
|
128
|
-
return { ...state, refresh };
|
|
310
|
+
const list = useMemo(() => key ? key.split(",") : [], [key]);
|
|
311
|
+
const load = useCallback(() => Promise.all(list.map((chain) => waaskey.balances.getBalance(chain, address))), [waaskey, list, address]);
|
|
312
|
+
return useAsyncResource(address && list.length ? load : void 0);
|
|
129
313
|
}
|
|
130
|
-
function useSend(
|
|
314
|
+
function useSend(wallet) {
|
|
131
315
|
const waaskey = useWaaskey();
|
|
132
316
|
const [status, setStatus] = useState("idle");
|
|
133
317
|
const [result, setResult] = useState();
|
|
134
318
|
const [error, setError] = useState();
|
|
135
319
|
const send = useCallback(
|
|
136
320
|
async (params) => {
|
|
137
|
-
if (!
|
|
321
|
+
if (!wallet) throw new Error("useSend: no wallet.");
|
|
138
322
|
setStatus("pending");
|
|
139
323
|
setError(void 0);
|
|
140
324
|
try {
|
|
141
|
-
const
|
|
142
|
-
const res = await
|
|
325
|
+
const target = typeof wallet === "string" ? await waaskey.wallets.get(wallet) : wallet;
|
|
326
|
+
const res = await target.send(params);
|
|
143
327
|
setResult(res);
|
|
144
328
|
setStatus("sent");
|
|
145
329
|
return res;
|
|
@@ -149,7 +333,7 @@ function useSend(walletId) {
|
|
|
149
333
|
throw e;
|
|
150
334
|
}
|
|
151
335
|
},
|
|
152
|
-
[waaskey,
|
|
336
|
+
[waaskey, wallet]
|
|
153
337
|
);
|
|
154
338
|
const reset = useCallback(() => {
|
|
155
339
|
setStatus("idle");
|
|
@@ -158,69 +342,124 @@ function useSend(walletId) {
|
|
|
158
342
|
}, []);
|
|
159
343
|
return { send, status, result, error, reset };
|
|
160
344
|
}
|
|
161
|
-
function useSignatures(
|
|
345
|
+
function useSignatures(wallet) {
|
|
162
346
|
const waaskey = useWaaskey();
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
347
|
+
const load = useCallback(
|
|
348
|
+
async (signal) => {
|
|
349
|
+
const target = typeof wallet === "string" ? await waaskey.wallets.get(wallet, { signal }) : wallet;
|
|
350
|
+
const page = await target.signatures({}, { signal });
|
|
351
|
+
return page.items;
|
|
352
|
+
},
|
|
353
|
+
[waaskey, wallet]
|
|
354
|
+
);
|
|
355
|
+
return useAsyncResource(wallet ? load : void 0);
|
|
356
|
+
}
|
|
357
|
+
function useAuth() {
|
|
358
|
+
const store = useAuthStoreContext();
|
|
359
|
+
return {
|
|
360
|
+
session: store.session,
|
|
361
|
+
user: store.user,
|
|
362
|
+
isAuthenticated: store.session !== void 0,
|
|
363
|
+
ready: store.ready,
|
|
364
|
+
loading: store.loading,
|
|
365
|
+
error: store.error,
|
|
366
|
+
setSession: store.setSession,
|
|
367
|
+
refreshUser: store.refreshUser,
|
|
368
|
+
logout: store.logout
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function useUser() {
|
|
372
|
+
return useAuthStoreContext().user;
|
|
373
|
+
}
|
|
374
|
+
function useAuthStoreContext() {
|
|
375
|
+
const store = useContext(AuthContext);
|
|
376
|
+
if (!store) throw new Error("useAuth must be used within a <WaaskeyProvider>.");
|
|
377
|
+
return store;
|
|
179
378
|
}
|
|
180
379
|
function useLogin() {
|
|
181
380
|
const waaskey = useWaaskey();
|
|
381
|
+
const auth = useAuthStoreContext();
|
|
182
382
|
const [step, setStep] = useState("email");
|
|
383
|
+
const [method, setMethod] = useState("email");
|
|
183
384
|
const [email, setEmail] = useState("");
|
|
385
|
+
const [phone, setPhone] = useState("");
|
|
184
386
|
const [session, setSession] = useState();
|
|
185
387
|
const [loading, setLoading] = useState(false);
|
|
186
388
|
const [error, setError] = useState();
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
389
|
+
const run = useCallback(async (action) => {
|
|
390
|
+
setLoading(true);
|
|
391
|
+
setError(void 0);
|
|
392
|
+
try {
|
|
393
|
+
await action();
|
|
394
|
+
} catch (err) {
|
|
395
|
+
setError(err);
|
|
396
|
+
} finally {
|
|
397
|
+
setLoading(false);
|
|
398
|
+
}
|
|
399
|
+
}, []);
|
|
400
|
+
const complete = useCallback(
|
|
401
|
+
(next) => {
|
|
402
|
+
setSession(next);
|
|
403
|
+
setStep("done");
|
|
404
|
+
auth.setSession(next);
|
|
200
405
|
},
|
|
201
|
-
[
|
|
406
|
+
[auth]
|
|
407
|
+
);
|
|
408
|
+
const start = useCallback(
|
|
409
|
+
(value) => run(async () => {
|
|
410
|
+
await waaskey.auth.email.start(value);
|
|
411
|
+
setMethod("email");
|
|
412
|
+
setEmail(value);
|
|
413
|
+
setPhone("");
|
|
414
|
+
setStep("code");
|
|
415
|
+
}),
|
|
416
|
+
[run, waaskey]
|
|
417
|
+
);
|
|
418
|
+
const startPhone = useCallback(
|
|
419
|
+
(value) => run(async () => {
|
|
420
|
+
await waaskey.auth.phone.start(value);
|
|
421
|
+
setMethod("phone");
|
|
422
|
+
setPhone(value);
|
|
423
|
+
setEmail("");
|
|
424
|
+
setStep("code");
|
|
425
|
+
}),
|
|
426
|
+
[run, waaskey]
|
|
202
427
|
);
|
|
203
428
|
const verify = useCallback(
|
|
204
|
-
async (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
429
|
+
(code) => run(async () => {
|
|
430
|
+
complete(method === "phone" ? await waaskey.auth.phone.verify(phone, code) : await waaskey.auth.email.verify(email, code));
|
|
431
|
+
}),
|
|
432
|
+
[run, waaskey, method, email, phone, complete]
|
|
433
|
+
);
|
|
434
|
+
const loginWithGoogle = useCallback(
|
|
435
|
+
(idToken) => run(async () => {
|
|
436
|
+
setMethod("google");
|
|
437
|
+
complete(await waaskey.auth.social.google(idToken));
|
|
438
|
+
}),
|
|
439
|
+
[run, waaskey, complete]
|
|
440
|
+
);
|
|
441
|
+
const loginWithFirebase = useCallback(
|
|
442
|
+
(idToken) => run(async () => {
|
|
443
|
+
complete(await waaskey.auth.social.firebase(idToken));
|
|
444
|
+
}),
|
|
445
|
+
[run, waaskey, complete]
|
|
446
|
+
);
|
|
447
|
+
const loginWithPasskey = useCallback(
|
|
448
|
+
(ceremony) => run(async () => {
|
|
449
|
+
setMethod("passkey");
|
|
450
|
+
complete(await waaskey.auth.passkey.login(ceremony));
|
|
451
|
+
}),
|
|
452
|
+
[run, waaskey, complete]
|
|
218
453
|
);
|
|
219
454
|
const reset = useCallback(() => {
|
|
220
455
|
setStep("email");
|
|
456
|
+
setMethod("email");
|
|
457
|
+
setEmail("");
|
|
458
|
+
setPhone("");
|
|
459
|
+
setSession(void 0);
|
|
221
460
|
setError(void 0);
|
|
222
461
|
}, []);
|
|
223
|
-
return { step, email, session, loading, error, start, verify, reset };
|
|
462
|
+
return { step, method, email, phone, session, loading, error, start, startPhone, verify, loginWithGoogle, loginWithFirebase, loginWithPasskey, reset };
|
|
224
463
|
}
|
|
225
464
|
function useQrCode(text) {
|
|
226
465
|
const [dataUrl, setDataUrl] = useState();
|
|
@@ -242,26 +481,54 @@ function useQrCode(text) {
|
|
|
242
481
|
}, [text]);
|
|
243
482
|
return dataUrl;
|
|
244
483
|
}
|
|
245
|
-
|
|
484
|
+
var METHOD_LABEL = {
|
|
485
|
+
email: "Continue with email",
|
|
486
|
+
phone: "Continue with phone",
|
|
487
|
+
google: "Continue with Google",
|
|
488
|
+
passkey: "Continue with a passkey"
|
|
489
|
+
};
|
|
490
|
+
function ConnectModal({ open, onClose, onConnect, title = "Sign in", methods = ["email"], googleIdToken, theme }) {
|
|
246
491
|
const base = useTheme();
|
|
247
492
|
const t = { ...base, ...theme };
|
|
248
|
-
const
|
|
249
|
-
const
|
|
493
|
+
const login = useLogin();
|
|
494
|
+
const { step, method, email, phone, session, loading, error, start, startPhone, verify, loginWithGoogle, loginWithPasskey, reset } = login;
|
|
495
|
+
const [identifier, setIdentifier] = useState("");
|
|
250
496
|
const [codeInput, setCodeInput] = useState("");
|
|
497
|
+
const [active, setActive] = useState(methods.includes("email") || !methods.includes("phone") ? "email" : "phone");
|
|
498
|
+
const [localError, setLocalError] = useState();
|
|
251
499
|
const firstFieldRef = useRef(null);
|
|
500
|
+
const announced = useRef(void 0);
|
|
501
|
+
const onConnectRef = useRef(onConnect);
|
|
502
|
+
onConnectRef.current = onConnect;
|
|
252
503
|
useEffect(() => {
|
|
253
|
-
if (step
|
|
254
|
-
|
|
504
|
+
if (step !== "done" || !session || announced.current === session.token) return;
|
|
505
|
+
announced.current = session.token;
|
|
506
|
+
onConnectRef.current?.(session);
|
|
507
|
+
}, [step, session]);
|
|
508
|
+
const onCloseRef = useRef(onClose);
|
|
509
|
+
onCloseRef.current = onClose;
|
|
255
510
|
useEffect(() => {
|
|
256
511
|
if (!open) return;
|
|
257
512
|
const onKey = (e) => {
|
|
258
|
-
if (e.key === "Escape")
|
|
513
|
+
if (e.key === "Escape") onCloseRef.current();
|
|
259
514
|
};
|
|
260
515
|
document.addEventListener("keydown", onKey);
|
|
261
|
-
firstFieldRef.current?.focus();
|
|
262
516
|
return () => document.removeEventListener("keydown", onKey);
|
|
263
|
-
}, [open
|
|
517
|
+
}, [open]);
|
|
518
|
+
useEffect(() => {
|
|
519
|
+
if (open) firstFieldRef.current?.focus();
|
|
520
|
+
}, [open, step, active]);
|
|
521
|
+
const useGoogle = useCallback(async () => {
|
|
522
|
+
if (!googleIdToken) return;
|
|
523
|
+
setLocalError(void 0);
|
|
524
|
+
try {
|
|
525
|
+
await loginWithGoogle(await googleIdToken());
|
|
526
|
+
} catch (e) {
|
|
527
|
+
setLocalError(e);
|
|
528
|
+
}
|
|
529
|
+
}, [googleIdToken, loginWithGoogle]);
|
|
264
530
|
if (!open) return null;
|
|
531
|
+
const shown = error ?? localError;
|
|
265
532
|
const overlay = { position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1e3 };
|
|
266
533
|
const card = {
|
|
267
534
|
background: t.background,
|
|
@@ -270,6 +537,7 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
|
|
|
270
537
|
padding: 24,
|
|
271
538
|
width: 360,
|
|
272
539
|
maxWidth: "90vw",
|
|
540
|
+
fontFamily: t.fontFamily,
|
|
273
541
|
boxShadow: "0 10px 40px rgba(0,0,0,0.2)"
|
|
274
542
|
};
|
|
275
543
|
const input = { width: "100%", boxSizing: "border-box", padding: "10px 12px", border: `1px solid ${t.border}`, borderRadius: "8px", fontSize: 14, marginTop: 8 };
|
|
@@ -286,43 +554,70 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
|
|
|
286
554
|
cursor: loading ? "default" : "pointer",
|
|
287
555
|
opacity: loading ? 0.6 : 1
|
|
288
556
|
};
|
|
557
|
+
const alt = { ...button, background: "none", color: t.accent, border: `1px solid ${t.border}`, fontWeight: 500, marginTop: 8 };
|
|
289
558
|
const link = { background: "none", border: "none", color: t.muted, fontSize: 13, marginTop: 12, cursor: "pointer" };
|
|
290
559
|
const errText = { color: t.danger, fontSize: 13, marginTop: 8 };
|
|
291
|
-
const
|
|
560
|
+
const onSubmitIdentifier = (e) => {
|
|
292
561
|
e.preventDefault();
|
|
293
|
-
|
|
562
|
+
const value = identifier.trim();
|
|
563
|
+
if (!value) return;
|
|
564
|
+
void (active === "phone" ? startPhone(value) : start(value));
|
|
294
565
|
};
|
|
295
566
|
const onSubmitCode = (e) => {
|
|
296
567
|
e.preventDefault();
|
|
297
568
|
if (codeInput.trim()) void verify(codeInput.trim());
|
|
298
569
|
};
|
|
299
|
-
const close = () =>
|
|
300
|
-
|
|
301
|
-
|
|
570
|
+
const close = () => onClose();
|
|
571
|
+
const alternatives = () => methods.filter((m) => m !== active && (m !== "google" || googleIdToken)).map(
|
|
572
|
+
(m) => createElement(
|
|
573
|
+
"button",
|
|
574
|
+
{
|
|
575
|
+
key: m,
|
|
576
|
+
type: "button",
|
|
577
|
+
style: alt,
|
|
578
|
+
disabled: loading,
|
|
579
|
+
onClick: () => {
|
|
580
|
+
setLocalError(void 0);
|
|
581
|
+
if (m === "email" || m === "phone") {
|
|
582
|
+
setActive(m);
|
|
583
|
+
setIdentifier("");
|
|
584
|
+
} else if (m === "passkey") void loginWithPasskey();
|
|
585
|
+
else void useGoogle();
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
METHOD_LABEL[m]
|
|
589
|
+
)
|
|
590
|
+
);
|
|
302
591
|
let body;
|
|
303
592
|
if (step === "email") {
|
|
593
|
+
const isPhone = active === "phone";
|
|
304
594
|
body = createElement(
|
|
305
|
-
"
|
|
306
|
-
{
|
|
307
|
-
createElement(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
595
|
+
"div",
|
|
596
|
+
{ key: "identifier" },
|
|
597
|
+
createElement(
|
|
598
|
+
"form",
|
|
599
|
+
{ onSubmit: onSubmitIdentifier },
|
|
600
|
+
createElement("label", { style: { fontSize: 13, color: t.muted }, htmlFor: "waaskey-identifier" }, isPhone ? "Phone" : "Email"),
|
|
601
|
+
createElement("input", {
|
|
602
|
+
id: "waaskey-identifier",
|
|
603
|
+
ref: firstFieldRef,
|
|
604
|
+
style: input,
|
|
605
|
+
type: isPhone ? "tel" : "email",
|
|
606
|
+
placeholder: isPhone ? "+15551234567" : "you@example.com",
|
|
607
|
+
value: identifier,
|
|
608
|
+
onChange: (e) => setIdentifier(e.target.value),
|
|
609
|
+
disabled: loading
|
|
610
|
+
}),
|
|
611
|
+
shown ? createElement("div", { style: errText, role: "alert" }, shown.message) : null,
|
|
612
|
+
createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Sending\u2026" : "Send code")
|
|
613
|
+
),
|
|
614
|
+
...alternatives()
|
|
320
615
|
);
|
|
321
616
|
} else if (step === "code") {
|
|
322
617
|
body = createElement(
|
|
323
618
|
"form",
|
|
324
619
|
{ onSubmit: onSubmitCode, key: "code" },
|
|
325
|
-
createElement("div", { style: { fontSize: 13, color: t.muted } }, `Enter the code sent to ${email}`),
|
|
620
|
+
createElement("div", { style: { fontSize: 13, color: t.muted } }, `Enter the code sent to ${method === "phone" ? phone : email}`),
|
|
326
621
|
createElement("input", {
|
|
327
622
|
id: "waaskey-code",
|
|
328
623
|
ref: firstFieldRef,
|
|
@@ -333,7 +628,7 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
|
|
|
333
628
|
onChange: (e) => setCodeInput(e.target.value),
|
|
334
629
|
disabled: loading
|
|
335
630
|
}),
|
|
336
|
-
|
|
631
|
+
shown ? createElement("div", { style: errText, role: "alert" }, shown.message) : null,
|
|
337
632
|
createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Verifying\u2026" : "Verify"),
|
|
338
633
|
createElement(
|
|
339
634
|
"button",
|
|
@@ -342,10 +637,11 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
|
|
|
342
637
|
type: "button",
|
|
343
638
|
onClick: () => {
|
|
344
639
|
setCodeInput("");
|
|
640
|
+
setIdentifier("");
|
|
345
641
|
reset();
|
|
346
642
|
}
|
|
347
643
|
},
|
|
348
|
-
"\u2190 Use a different email"
|
|
644
|
+
method === "phone" ? "\u2190 Use a different number" : "\u2190 Use a different email"
|
|
349
645
|
)
|
|
350
646
|
);
|
|
351
647
|
} else {
|
|
@@ -398,8 +694,10 @@ function WalletWidget({ walletId, chains, sendChainId = "evm:1", sendDecimals =
|
|
|
398
694
|
{ style: s.body },
|
|
399
695
|
tab === "assets" && createElement(AssetsTab, { chains, address, loading: wallet.loading, s }),
|
|
400
696
|
tab === "receive" && createElement(ReceiveTab, { address, s }),
|
|
401
|
-
|
|
402
|
-
|
|
697
|
+
// Pass the loaded wallet (not the id) so the Send/Activity tabs reuse it instead of
|
|
698
|
+
// each issuing its own `wallets.get` for the wallet this widget already holds.
|
|
699
|
+
tab === "send" && createElement(SendTab, { wallet: wallet.data ?? walletId, sendChainId, sendDecimals, onSent, s }),
|
|
700
|
+
tab === "activity" && createElement(ActivityTab, { wallet: wallet.data ?? walletId, s })
|
|
403
701
|
)
|
|
404
702
|
);
|
|
405
703
|
}
|
|
@@ -428,13 +726,13 @@ function ReceiveTab({ address, s }) {
|
|
|
428
726
|
);
|
|
429
727
|
}
|
|
430
728
|
function SendTab({
|
|
431
|
-
|
|
729
|
+
wallet,
|
|
432
730
|
sendChainId,
|
|
433
731
|
sendDecimals,
|
|
434
732
|
onSent,
|
|
435
733
|
s
|
|
436
734
|
}) {
|
|
437
|
-
const { send, status, result, error, reset } = useSend(
|
|
735
|
+
const { send, status, result, error, reset } = useSend(wallet);
|
|
438
736
|
const [to, setTo] = useState("");
|
|
439
737
|
const [amount, setAmount] = useState("");
|
|
440
738
|
const onSubmit = (e) => {
|
|
@@ -446,8 +744,10 @@ function SendTab({
|
|
|
446
744
|
return createElement(
|
|
447
745
|
"div",
|
|
448
746
|
{ style: { textAlign: "center" } },
|
|
449
|
-
createElement("div", { style: { fontWeight: 600, marginBottom:
|
|
747
|
+
createElement("div", { style: { fontWeight: 600, marginBottom: 4 } }, "Signed \u2713"),
|
|
748
|
+
createElement("div", { style: { ...s.muted, fontSize: 13, marginBottom: 8 } }, "Not broadcast yet \u2014 submit the signed transaction to a node."),
|
|
450
749
|
createElement("div", { style: { ...s.muted, wordBreak: "break-all", fontFamily: "monospace" } }, result.txHash),
|
|
750
|
+
createElement("button", { type: "button", style: s.secondaryButton, onClick: () => void navigator.clipboard?.writeText(result.signedTx) }, "Copy signed tx"),
|
|
451
751
|
createElement("button", { type: "button", style: s.secondaryButton, onClick: reset }, "Send another")
|
|
452
752
|
);
|
|
453
753
|
}
|
|
@@ -471,8 +771,8 @@ function SendTab({
|
|
|
471
771
|
createElement("button", { type: "submit", style: s.button, disabled: pending }, pending ? "Sending\u2026" : "Send")
|
|
472
772
|
);
|
|
473
773
|
}
|
|
474
|
-
function ActivityTab({
|
|
475
|
-
const { data, error, loading } = useSignatures(
|
|
774
|
+
function ActivityTab({ wallet, s }) {
|
|
775
|
+
const { data, error, loading } = useSignatures(wallet);
|
|
476
776
|
if (loading) return createElement("div", { style: s.muted }, "Loading activity\u2026");
|
|
477
777
|
if (error) return createElement("div", { style: s.error, role: "alert" }, error.message);
|
|
478
778
|
if (!data || data.length === 0) return createElement("div", { style: s.empty }, "No signing activity yet.");
|
|
@@ -698,6 +998,6 @@ function FundWidget({ walletAddress, chainId, cryptoCurrency = "ETH", fiatCurren
|
|
|
698
998
|
);
|
|
699
999
|
}
|
|
700
1000
|
|
|
701
|
-
export { ConnectModal, FundWidget, SignPrompt, ThemeContext, WaasProvider, WaaskeyContext, WaaskeyProvider, WalletWidget, darkTheme, defaultTheme, lightTheme, resolveTheme, useBalance, useBalances, useCreateWallet, useLogin, useQrCode, useSend, useSignPrompt, useSignatures, useTheme, useWaas, useWaaskey, useWallet };
|
|
1001
|
+
export { AuthContext, ConnectModal, FundWidget, SignPrompt, ThemeContext, WaasProvider, WaaskeyContext, WaaskeyProvider, WalletWidget, darkTheme, defaultTheme, lightTheme, resolveTheme, useAuth, useBalance, useBalances, useCreateWallet, useLogin, useQrCode, useSend, useSignPrompt, useSignatures, useTheme, useUser, useWaas, useWaaskey, useWallet, useWallets };
|
|
702
1002
|
//# sourceMappingURL=index.js.map
|
|
703
1003
|
//# sourceMappingURL=index.js.map
|