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