@nexus-cross/connect-kit-react 1.1.4 → 1.3.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.
Files changed (45) hide show
  1. package/README.md +15 -29
  2. package/dist/chunk-KM3ZTTHL.js +143 -0
  3. package/dist/client.d.ts +46 -0
  4. package/dist/client.js +1 -0
  5. package/dist/index.d.ts +378 -25
  6. package/dist/index.js +1 -12
  7. package/package.json +15 -6
  8. package/dist/components/ConnectButton.d.ts +0 -87
  9. package/dist/components/ConnectButton.d.ts.map +0 -1
  10. package/dist/components/ConnectButton.js +0 -417
  11. package/dist/components/ConnectButton.js.map +0 -1
  12. package/dist/components/CrossConnectKitProvider.d.ts +0 -38
  13. package/dist/components/CrossConnectKitProvider.d.ts.map +0 -1
  14. package/dist/components/CrossConnectKitProvider.js +0 -737
  15. package/dist/components/CrossConnectKitProvider.js.map +0 -1
  16. package/dist/components/CrossConnectModal.d.ts +0 -20
  17. package/dist/components/CrossConnectModal.d.ts.map +0 -1
  18. package/dist/components/CrossConnectModal.js +0 -80
  19. package/dist/components/CrossConnectModal.js.map +0 -1
  20. package/dist/components/OtherWalletsModal.d.ts +0 -20
  21. package/dist/components/OtherWalletsModal.d.ts.map +0 -1
  22. package/dist/components/OtherWalletsModal.js +0 -300
  23. package/dist/components/OtherWalletsModal.js.map +0 -1
  24. package/dist/components/WalletInfoPopover.d.ts +0 -7
  25. package/dist/components/WalletInfoPopover.d.ts.map +0 -1
  26. package/dist/components/WalletInfoPopover.js +0 -166
  27. package/dist/components/WalletInfoPopover.js.map +0 -1
  28. package/dist/components/types.d.ts +0 -23
  29. package/dist/components/types.d.ts.map +0 -1
  30. package/dist/components/types.js +0 -2
  31. package/dist/components/types.js.map +0 -1
  32. package/dist/context/CrossConnectKitContext.d.ts +0 -93
  33. package/dist/context/CrossConnectKitContext.d.ts.map +0 -1
  34. package/dist/context/CrossConnectKitContext.js +0 -10
  35. package/dist/context/CrossConnectKitContext.js.map +0 -1
  36. package/dist/context/CrossConnectKitThemeContext.d.ts +0 -41
  37. package/dist/context/CrossConnectKitThemeContext.d.ts.map +0 -1
  38. package/dist/context/CrossConnectKitThemeContext.js +0 -22
  39. package/dist/context/CrossConnectKitThemeContext.js.map +0 -1
  40. package/dist/hooks/useCrossxEmbeddedInfo.d.ts +0 -41
  41. package/dist/hooks/useCrossxEmbeddedInfo.d.ts.map +0 -1
  42. package/dist/hooks/useCrossxEmbeddedInfo.js +0 -241
  43. package/dist/hooks/useCrossxEmbeddedInfo.js.map +0 -1
  44. package/dist/index.d.ts.map +0 -1
  45. package/dist/index.js.map +0 -1
@@ -1,737 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { WagmiProvider, useAccount, useConnect, useDisconnect } from 'wagmi';
4
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
- import { clearWalletSessionStorage, onOpenOtherWallets, pickInitialState, resolveCurrentWallet, } from '@nexus-cross/connect-kit-wagmi';
6
- import { buildThemeCssText, resolveThemeMode, } from '@nexus-cross/connect-kit-core';
7
- import { CrossConnectKitContext } from '../context/CrossConnectKitContext.js';
8
- import { CrossConnectKitThemeContext, } from '../context/CrossConnectKitThemeContext.js';
9
- import { CrossConnectModal } from './CrossConnectModal.js';
10
- const queryClient = new QueryClient();
11
- const INTENT_STORAGE_KEY = 'crossx-kit:last-wallet-intent';
12
- /**
13
- * True when the thrown error is the crossy-sdk embedded connector's
14
- * deliberate handoff to another wallet. Those surfaces look like
15
- * UserRejectedRequestError with a cause message "User requested external
16
- * wallet connection" — they are flow control, not real failures.
17
- */
18
- function isExternalWalletHandoff(err) {
19
- const messages = [];
20
- let cur = err;
21
- let depth = 0;
22
- while (cur && depth < 5) {
23
- const obj = cur;
24
- if (typeof obj.message === 'string')
25
- messages.push(obj.message);
26
- cur = obj.cause;
27
- depth += 1;
28
- }
29
- return messages.some((m) => /external wallet connection/i.test(m));
30
- }
31
- function readIntent() {
32
- if (typeof window === 'undefined')
33
- return null;
34
- try {
35
- return window.localStorage.getItem(INTENT_STORAGE_KEY) ?? null;
36
- }
37
- catch {
38
- return null;
39
- }
40
- }
41
- function writeIntent(walletId) {
42
- if (typeof window === 'undefined')
43
- return;
44
- try {
45
- if (walletId)
46
- window.localStorage.setItem(INTENT_STORAGE_KEY, walletId);
47
- else
48
- window.localStorage.removeItem(INTENT_STORAGE_KEY);
49
- }
50
- catch {
51
- /* storage unavailable (private mode, quota) — intent falls back to in-memory only */
52
- }
53
- }
54
- function readActiveProvider(storageKey, fallback) {
55
- if (typeof window === 'undefined')
56
- return fallback;
57
- try {
58
- const v = window.localStorage.getItem(storageKey);
59
- return v === 'cross' || v === 'reown' ? v : fallback;
60
- }
61
- catch {
62
- return fallback;
63
- }
64
- }
65
- function writeActiveProvider(storageKey, value) {
66
- if (typeof window === 'undefined')
67
- return;
68
- try {
69
- window.localStorage.setItem(storageKey, value);
70
- }
71
- catch {
72
- /* storage unavailable — falls back to in-memory only */
73
- }
74
- }
75
- function CrossConnectKitInner({ children, config, activeProvider, onRequestProviderSwap, pendingConnectWalletId, clearPending, themeMode, themeTokens, extraWallets, walletAllowlist, }) {
76
- const [otherWalletsOpen, setOtherWalletsOpen] = useState(false);
77
- const [lastIntent, setLastIntent] = useState(() => readIntent());
78
- // Unified, in-memory "connect attempt in flight" marker. Deliberately
79
- // NOT seeded from storage (unlike `lastIntent`) so a stale persisted
80
- // intent from a previous page load can never surface a phantom
81
- // "Connecting…" spinner. Set at the top of `runConnect` / wrapped
82
- // `oauth.signIn`; cleared on success, cancel, or error.
83
- const [connectingWalletId, setConnectingWalletId] = useState(null);
84
- const { isConnected, isConnecting, isReconnecting, connector: activeConnector, } = useAccount();
85
- const { connectors, connectAsync } = useConnect();
86
- const { disconnectAsync: wagmiDisconnectAsync } = useDisconnect();
87
- const { kitConfig, connectorRegistry, crossProvider, reownProvider } = config;
88
- const activeAdapter = activeProvider === 'cross' ? crossProvider : reownProvider;
89
- const currentWallet = useMemo(() => {
90
- if (!isConnected || !activeConnector)
91
- return null;
92
- return resolveCurrentWallet(connectorRegistry, activeConnector.id, lastIntent);
93
- }, [isConnected, activeConnector, connectorRegistry, lastIntent]);
94
- const availableWallets = useMemo(() => connectorRegistry.entries.filter((e) => e.type !== 'embedded'), [connectorRegistry]);
95
- useEffect(() => {
96
- return onOpenOtherWallets(() => setOtherWalletsOpen(true));
97
- }, []);
98
- // When OtherWalletsModal opens on top of the crossy-sdk-js login
99
- // modal, the SDK is supposed to dismiss its own overlay in response to
100
- // the `CROSSxError: User requested external wallet connection` thrown
101
- // from `signIn`. A race between our modal mount and the SDK's cleanup
102
- // occasionally leaves the login overlay lingering in the DOM — remove
103
- // it defensively so the user doesn't see two stacked modals.
104
- //
105
- // The SDK mounts every overlay under the shared id `__crossx-confirm-
106
- // overlay` (see BrowserConfirmationAdapter.ts). Removing it here also
107
- // aborts the SDK Promise, but the flow is already cancelling anyway.
108
- useEffect(() => {
109
- if (!otherWalletsOpen)
110
- return;
111
- if (typeof document === 'undefined')
112
- return;
113
- // Defer a tick so the SDK's own close path runs first when it's
114
- // going to. Only remove if the overlay is still there afterwards.
115
- const timer = setTimeout(() => {
116
- document
117
- .querySelectorAll('#__crossx-confirm-overlay')
118
- .forEach((el) => el.remove());
119
- }, 50);
120
- return () => clearTimeout(timer);
121
- }, [otherWalletsOpen]);
122
- const isConnectedRef = useRef(isConnected);
123
- const prevConnectedRef = useRef(isConnected);
124
- isConnectedRef.current = isConnected;
125
- // Tracks whether the active adapter's own in-page modal (AppKit QR view)
126
- // is currently open. Lets the window-focus cancel watcher below stand
127
- // down while a QR modal is up — that path self-manages cancellation via
128
- // `subscribeModalState`, and a desktop user blurring/refocusing while
129
- // scanning on their phone must NOT be read as a dismissal.
130
- const adapterModalOpenRef = useRef(false);
131
- // Diagnostic: surface the provider / account state inside CrossConnectKitInner
132
- // so the user can see in browser devtools which config is mounted and
133
- // what wagmi reports. Cheap to keep on — fires only on state change.
134
- useEffect(() => {
135
- console.debug('[crossx-kit] state', {
136
- activeProvider,
137
- isConnected,
138
- isConnecting,
139
- isReconnecting,
140
- activeConnectorId: activeConnector?.id,
141
- currentWallet,
142
- lastIntent,
143
- });
144
- }, [
145
- activeProvider,
146
- isConnected,
147
- isConnecting,
148
- isReconnecting,
149
- activeConnector?.id,
150
- currentWallet,
151
- lastIntent,
152
- ]);
153
- // Auto-close the adapter's native modal once wagmi reports a
154
- // connection. AppKit's QR-only views (e.g. ConnectingWalletConnectBasic)
155
- // don't always dismiss themselves when the session is established —
156
- // explicitly close on the rising edge of `isConnected` so the modal
157
- // doesn't linger over the connected UI.
158
- useEffect(() => {
159
- if (isConnected && !prevConnectedRef.current) {
160
- activeAdapter?.closeModal?.({ kitConfig });
161
- }
162
- prevConnectedRef.current = isConnected;
163
- }, [isConnected, activeAdapter, kitConfig]);
164
- // Release the unified connecting marker the moment wagmi reports a
165
- // connection — the terminal "success" for every path. Cancel/error
166
- // terminals are handled by `clearIntent()` (wallet paths) and the
167
- // wrapped `oauth.signIn` catch (social paths).
168
- useEffect(() => {
169
- if (isConnected)
170
- setConnectingWalletId(null);
171
- }, [isConnected]);
172
- const findConnector = useCallback((connectorId) => connectors.find((c) => c.id === connectorId), [connectors]);
173
- // Push `themeMode` / `themeTokens` changes into the embedded crossy-sdk
174
- // at runtime. The SDK owns a live confirmation modal adapter whose
175
- // palette is frozen at `crossxConnector(...)` construction, so without
176
- // this effect the DApp's `setThemeMode(...)` only affects our CSS
177
- // variables — the SDK's next signature / login modal would still open
178
- // in the stale palette. `provider.sdk.applyTheme` is crossy-sdk 2.0's
179
- // public runtime API (see `CROSSxSDK.applyTheme`).
180
- useEffect(() => {
181
- const embeddedEntry = connectorRegistry.getEntry('cross_embedded');
182
- if (!embeddedEntry)
183
- return;
184
- const crossx = findConnector(embeddedEntry.connectorId);
185
- if (!crossx)
186
- return;
187
- let cancelled = false;
188
- void (async () => {
189
- try {
190
- const provider = (await crossx.getProvider());
191
- if (cancelled)
192
- return;
193
- provider?.sdk?.applyTheme?.(themeMode, themeTokens ?? undefined);
194
- }
195
- catch (err) {
196
- console.debug('[crossx-kit] applyTheme skipped:', err);
197
- }
198
- })();
199
- return () => {
200
- cancelled = true;
201
- };
202
- }, [connectorRegistry, findConnector, themeMode, themeTokens]);
203
- const recordIntent = useCallback((walletId) => {
204
- setLastIntent(walletId);
205
- writeIntent(walletId);
206
- }, []);
207
- const clearIntent = useCallback(() => {
208
- setLastIntent(null);
209
- writeIntent(null);
210
- // Cancel/error/dismissal terminal — also drop the connecting marker so
211
- // the unified `isConnecting` releases in lockstep with the intent.
212
- setConnectingWalletId(null);
213
- }, []);
214
- // Watch the adapter's native modal. When the user dismisses it without
215
- // completing pairing, wagmi is still "not connected" and any in-flight
216
- // connectAsync (e.g. cross_wallet's UniversalConnector waiting for a
217
- // QR scan) is left dangling. Force a clean disconnect so the
218
- // "Connecting…" state doesn't stick and lastIntent doesn't poison the
219
- // next attempt.
220
- useEffect(() => {
221
- if (!activeAdapter?.subscribeModalState)
222
- return;
223
- let sawOpen = false;
224
- return activeAdapter.subscribeModalState({ kitConfig }, (open) => {
225
- adapterModalOpenRef.current = open;
226
- if (open) {
227
- sawOpen = true;
228
- return;
229
- }
230
- if (!sawOpen)
231
- return;
232
- sawOpen = false;
233
- // Defer so a pair completing at the moment of dismissal can flip
234
- // wagmi `isConnected` before we decide the user cancelled.
235
- setTimeout(() => {
236
- if (isConnectedRef.current)
237
- return;
238
- void (async () => {
239
- try {
240
- await wagmiDisconnectAsync();
241
- }
242
- catch {
243
- /* already-disconnected is fine */
244
- }
245
- clearIntent();
246
- })();
247
- }, 250);
248
- });
249
- }, [activeAdapter, kitConfig, wagmiDisconnectAsync, clearIntent]);
250
- // Belt-and-suspenders cancel detection for connect flows that open a
251
- // SEPARATE BROWSER WINDOW whose underlying promise does NOT reject when
252
- // the user simply closes that window — most notably the injected
253
- // MetaMask / Binance extension approval popup (`eth_requestAccounts`
254
- // stays pending on close), and external wallet/OAuth popups in general.
255
- // Those windows steal focus from the opener; closing them returns it.
256
- //
257
- // `subscribeModalState` only covers the adapters' own in-page AppKit
258
- // modal (QR view), which never blurs the opener — so it can't see an
259
- // extension popup dismissal. This focus watcher fills that gap WITHOUT
260
- // touching the QR path: an in-page modal triggers no window blur, so
261
- // `sawBlur` stays false and this is a no-op for it.
262
- //
263
- // On focus-return we wait one grace tick for a genuine approval to land
264
- // (`isConnected`) before releasing the unified connecting marker — so a
265
- // successful approve (popup closes → focus returns → connectAsync
266
- // resolves) is not mistaken for a cancel.
267
- useEffect(() => {
268
- if (!connectingWalletId)
269
- return;
270
- if (isConnected)
271
- return;
272
- if (typeof window === 'undefined')
273
- return;
274
- // Seed from current focus: the popup may already have stolen focus by
275
- // the time this effect attaches, in which case the `blur` event has
276
- // already fired and we'd otherwise miss it.
277
- let sawBlur = typeof document !== 'undefined' && !document.hasFocus();
278
- let graceTimer = null;
279
- const onBlur = () => {
280
- sawBlur = true;
281
- };
282
- const onFocus = () => {
283
- if (!sawBlur)
284
- return;
285
- if (graceTimer)
286
- clearTimeout(graceTimer);
287
- graceTimer = setTimeout(() => {
288
- if (isConnectedRef.current)
289
- return;
290
- // An adapter QR modal is up — leave cancellation to
291
- // subscribeModalState; the window blur was a scan-on-phone detour,
292
- // not a dismissal of an extension/OAuth popup.
293
- if (adapterModalOpenRef.current)
294
- return;
295
- void (async () => {
296
- try {
297
- await wagmiDisconnectAsync();
298
- }
299
- catch {
300
- /* already-disconnected is fine */
301
- }
302
- clearIntent();
303
- })();
304
- }, 1000);
305
- };
306
- window.addEventListener('blur', onBlur);
307
- window.addEventListener('focus', onFocus);
308
- return () => {
309
- window.removeEventListener('blur', onBlur);
310
- window.removeEventListener('focus', onFocus);
311
- if (graceTimer)
312
- clearTimeout(graceTimer);
313
- };
314
- }, [connectingWalletId, isConnected, wagmiDisconnectAsync, clearIntent]);
315
- const runConnect = useCallback(async (walletId) => {
316
- console.debug('[crossx-kit] runConnect enter', {
317
- walletId,
318
- activeProvider,
319
- isConnected,
320
- activeConnectorId: activeConnector?.id,
321
- });
322
- const entry = connectorRegistry.getEntry(walletId);
323
- if (!entry) {
324
- console.error(`[crossx-kit] Unknown walletId: "${walletId}"`);
325
- return;
326
- }
327
- // Light the unified connecting marker now — BEFORE any provider swap
328
- // or `beforeConnect` branch — so the spinner appears the instant the
329
- // user commits to a wallet, uniformly across the wagmi connectAsync
330
- // path and the reown direct-connect path (which never touches wagmi's
331
- // own `isConnecting`). A provider swap remounts this subtree, but the
332
- // post-swap `pendingConnectWalletId` effect re-enters `runConnect` and
333
- // re-sets it.
334
- setConnectingWalletId(walletId);
335
- if (entry.providerKind !== activeProvider) {
336
- console.debug('[crossx-kit] provider swap needed', {
337
- from: activeProvider,
338
- to: entry.providerKind,
339
- walletId,
340
- });
341
- recordIntent(walletId);
342
- try {
343
- await wagmiDisconnectAsync();
344
- }
345
- catch {
346
- /* noop */
347
- }
348
- await new Promise((resolve) => setTimeout(resolve, 0));
349
- onRequestProviderSwap(entry.providerKind, walletId);
350
- return;
351
- }
352
- recordIntent(walletId);
353
- if (activeAdapter?.beforeConnect) {
354
- try {
355
- // Forward the active theme so the adapter's library-native
356
- // modal (e.g. AppKit's CROSSx QR view) can sync with the
357
- // host's current palette — including post-bootstrap toggles
358
- // via `useCrossConnectKitTheme().setThemeMode(...)`.
359
- const result = await activeAdapter.beforeConnect({ kitConfig, themeMode }, walletId);
360
- console.debug('[crossx-kit] beforeConnect result', { walletId, result });
361
- if (result === 'handled')
362
- return;
363
- }
364
- catch (err) {
365
- console.warn(`[crossx-kit] adapter.beforeConnect threw for "${walletId}":`, err);
366
- }
367
- }
368
- const primary = findConnector(entry.connectorId);
369
- console.debug('[crossx-kit] findConnector', {
370
- lookupId: entry.connectorId,
371
- found: primary?.id,
372
- availableIds: connectors.map((c) => c.id),
373
- });
374
- if (!primary) {
375
- console.error(`[crossx-kit] Connector "${entry.connectorId}" not found on provider ` +
376
- `"${entry.providerKind}". Available: ${connectors.map((c) => c.id).join(', ')}`);
377
- clearIntent();
378
- return;
379
- }
380
- try {
381
- console.debug('[crossx-kit] connectAsync start', { connectorId: primary.id });
382
- const result = await connectAsync({ connector: primary });
383
- console.debug('[crossx-kit] connectAsync resolved', {
384
- accountsLen: result.accounts.length,
385
- chainId: result.chainId,
386
- });
387
- return;
388
- }
389
- catch (err) {
390
- const handoff = isExternalWalletHandoff(err);
391
- if (!handoff) {
392
- console.error(`[crossx-kit] connectAsync error for "${walletId}":`, err);
393
- try {
394
- await wagmiDisconnectAsync();
395
- }
396
- catch {
397
- /* ignore */
398
- }
399
- }
400
- if (!entry.fallbackConnectorId) {
401
- clearIntent();
402
- return;
403
- }
404
- }
405
- if (entry.fallbackConnectorId) {
406
- const fallback = findConnector(entry.fallbackConnectorId);
407
- if (fallback) {
408
- try {
409
- await connectAsync({ connector: fallback });
410
- }
411
- catch (err) {
412
- console.error(`[crossx-kit] Fallback connect failed for "${walletId}":`, err);
413
- try {
414
- await wagmiDisconnectAsync();
415
- }
416
- catch {
417
- /* ignore */
418
- }
419
- clearIntent();
420
- }
421
- }
422
- else {
423
- clearIntent();
424
- }
425
- }
426
- }, [
427
- connectorRegistry,
428
- connectors,
429
- findConnector,
430
- connectAsync,
431
- wagmiDisconnectAsync,
432
- recordIntent,
433
- clearIntent,
434
- activeAdapter,
435
- activeProvider,
436
- kitConfig,
437
- onRequestProviderSwap,
438
- themeMode,
439
- ]);
440
- // After a provider swap, pendingConnectWalletId is set and the
441
- // WagmiProvider has just remounted with the new config. Fire the
442
- // connect for that wallet now that the hooks point at the right
443
- // config.
444
- useEffect(() => {
445
- if (!pendingConnectWalletId)
446
- return;
447
- const wallet = pendingConnectWalletId;
448
- clearPending();
449
- void runConnect(wallet);
450
- // We intentionally only depend on the pending id and active provider —
451
- // runConnect has its own deps but refetching here would re-trigger
452
- // connects on unrelated state changes.
453
- // eslint-disable-next-line react-hooks/exhaustive-deps
454
- }, [pendingConnectWalletId, activeProvider]);
455
- const connectWallet = useCallback(async () => {
456
- // Open the kit's unified connect modal. The user picks Google /
457
- // Apple (delegated to `oauth.signIn`) or a wallet (delegated to
458
- // `runConnect`). The SDK's own modal is no longer invoked directly —
459
- // social entries route through the headless `signInWithProvider`.
460
- setOtherWalletsOpen(true);
461
- }, []);
462
- const disconnect = useCallback(async () => {
463
- try {
464
- await wagmiDisconnectAsync();
465
- }
466
- catch (err) {
467
- console.error('[crossx-kit] wagmi disconnect error:', err);
468
- }
469
- if (activeAdapter?.teardown) {
470
- try {
471
- await activeAdapter.teardown({ kitConfig });
472
- }
473
- catch (err) {
474
- console.error('[crossx-kit] adapter teardown error:', err);
475
- }
476
- }
477
- clearWalletSessionStorage();
478
- clearIntent();
479
- }, [wagmiDisconnectAsync, activeAdapter, kitConfig, clearIntent]);
480
- const selectWallet = useCallback(async () => {
481
- const embeddedEntry = connectorRegistry.getEntry('cross_embedded');
482
- if (!embeddedEntry)
483
- return null;
484
- const crossx = findConnector(embeddedEntry.connectorId);
485
- if (!crossx)
486
- return null;
487
- try {
488
- const provider = (await crossx.getProvider());
489
- const sdk = provider?.sdk;
490
- if (!sdk?.selectWallet)
491
- return null;
492
- const result = await sdk.selectWallet(sdk.currentAddress ?? undefined);
493
- if (result?.address && provider.notifyAccountsChanged) {
494
- provider.notifyAccountsChanged([result.address]);
495
- }
496
- return result;
497
- }
498
- catch {
499
- return null;
500
- }
501
- }, [connectorRegistry, findConnector]);
502
- // Wrap the OAuth port so the social (Google/Apple) path feeds the same
503
- // unified connecting marker as the wallet path. `oauth.signIn` opens the
504
- // SDK popup and "forgets" (see OAuthPort docs): success surfaces later as
505
- // wagmi `isConnected` (cleared by the effect above); a rejection here is
506
- // the user closing the popup, so we drop the marker on catch.
507
- const oauth = useMemo(() => {
508
- const base = config.oauth;
509
- if (!base)
510
- return null;
511
- return {
512
- signIn: async (provider) => {
513
- setConnectingWalletId('cross_embedded');
514
- try {
515
- await base.signIn(provider);
516
- }
517
- catch (err) {
518
- setConnectingWalletId(null);
519
- throw err;
520
- }
521
- },
522
- };
523
- }, [config.oauth]);
524
- // Unified connecting flag: true from the moment a connect attempt starts
525
- // until it succeeds. `connectingWalletId` is only ever set by an explicit
526
- // user action (modal pick → runConnect, social → wrapped signIn) and is
527
- // cleared on success/cancel/error, so this is free of the background
528
- // `isConnecting` flapping that wagmi exhibits during session restore.
529
- const kitIsConnecting = connectingWalletId != null && !isConnected;
530
- const contextValue = useMemo(() => ({
531
- kitConfig,
532
- connectorRegistry,
533
- availableWallets,
534
- connect: runConnect,
535
- connectWallet,
536
- disconnect,
537
- selectWallet,
538
- currentWallet,
539
- lastIntent,
540
- isConnecting: kitIsConnecting,
541
- connectingWalletId,
542
- openOtherWallets: () => setOtherWalletsOpen(true),
543
- closeOtherWallets: () => setOtherWalletsOpen(false),
544
- otherWalletsOpen,
545
- oauth,
546
- extraWallets,
547
- walletAllowlist,
548
- }), [
549
- kitConfig,
550
- connectorRegistry,
551
- availableWallets,
552
- runConnect,
553
- connectWallet,
554
- disconnect,
555
- selectWallet,
556
- currentWallet,
557
- lastIntent,
558
- kitIsConnecting,
559
- connectingWalletId,
560
- otherWalletsOpen,
561
- oauth,
562
- extraWallets,
563
- walletAllowlist,
564
- ]);
565
- return (_jsxs(CrossConnectKitContext.Provider, { value: contextValue, children: [children, _jsx(CrossConnectModal, { open: otherWalletsOpen, onOpenChange: setOtherWalletsOpen })] }));
566
- }
567
- /**
568
- * Patterns that are benign tear-down noise from the WalletConnect
569
- * universal provider: a WebSocket closes while a subscribe request is
570
- * mid-flight (most commonly when WagmiProvider remounts during a
571
- * provider swap). They surface as uncaught promise rejections because
572
- * WC's internal EventEmitter throws into the void — silence them so
573
- * consumers' error tooling stays clean. Actual connection failures
574
- * still surface through wagmi's connectAsync error path.
575
- */
576
- const WC_BENIGN_ERROR_PATTERNS = [
577
- /Connection interrupted while trying to subscribe/i,
578
- /No matching key\. subscription:/i,
579
- // Session proposal TTL expired without a wallet accepting (default
580
- // 5 min). The modal already surfaces its own "expired" UI — the
581
- // secondary rejection on the shared emitter is just noise.
582
- /Proposal expired/i,
583
- ];
584
- function isBenignWcError(reason) {
585
- const msg = reason?.message;
586
- if (typeof msg !== 'string')
587
- return false;
588
- return WC_BENIGN_ERROR_PATTERNS.some((p) => p.test(msg));
589
- }
590
- const EMPTY_EXTRA_WALLETS = {};
591
- /**
592
- * Strip the persisted `connections` from the SSR hydration state before
593
- * handing it to wagmi.
594
- *
595
- * wagmi serializes each persisted connection's connector down to
596
- * `{ id, name, type, uid }` (no methods — see `@wagmi/core` createConfig
597
- * `partialize`). On the client, `WagmiProvider` hydrates with
598
- * `status: 'reconnecting'` and only re-attaches the *real* connector
599
- * after the async `reconnect()` resolves — for the WalletConnect-based
600
- * cross side that's after the relay session is restored, which can take
601
- * seconds.
602
- *
603
- * The trap: wagmi v3's `getConnection` reports `isConnected: !!address`
604
- * while `status === 'reconnecting'`, so a consumer reading
605
- * `useConnection().isConnected` sees `true` and can fire a write against
606
- * the still-method-less connector → `connector.getChainId is not a
607
- * function` (and similar). Clearing `connections` keeps the hydrated
608
- * `chainId` for first paint but makes `isConnected` stay false until
609
- * `reconnect()` swaps in a real, usable connector — matching wagmi v2's
610
- * `useAccount()` semantics (not connected while reconnecting).
611
- */
612
- function sanitizeInitialState(state) {
613
- if (!state)
614
- return state;
615
- return { ...state, connections: new Map(), current: null };
616
- }
617
- export function CrossConnectKitProvider({ children, config, initialState, extraWallets = EMPTY_EXTRA_WALLETS, walletAllowlist, }) {
618
- const [activeProvider, setActiveProviderState] = useState(() => readActiveProvider(config.activeProviderStorageKey, config.defaultProvider));
619
- const [pendingConnectWalletId, setPendingConnectWalletId] = useState(null);
620
- const swapInFlight = useRef(false);
621
- // ── Theme state ───────────────────────────────────────────────────
622
- // Resolve the initial mode from `kitConfig.theme` (+ `autoDetectTheme`)
623
- // and expose runtime overrides via the ThemeContext. `modeOverride` /
624
- // `tokensOverride === null` means "use the value from the config".
625
- const { kitConfig } = config;
626
- const [modeOverride, setModeOverride] = useState(null);
627
- const [tokensOverride, setTokensOverride] = useState(null);
628
- // Bumped on `prefers-color-scheme` change so `configMode` re-resolves.
629
- const [osThemeTick, setOsThemeTick] = useState(0);
630
- const configMode = useMemo(() => resolveThemeMode(kitConfig.theme, kitConfig.autoDetectTheme),
631
- // `osThemeTick` is intentionally part of the dep list — it is the
632
- // signal we're allowed to re-consult `prefers-color-scheme`.
633
- // eslint-disable-next-line react-hooks/exhaustive-deps
634
- [kitConfig.theme, kitConfig.autoDetectTheme, osThemeTick]);
635
- const activeMode = modeOverride ?? configMode;
636
- const activeTokens = tokensOverride ?? kitConfig.themeTokens;
637
- // Follow OS `prefers-color-scheme` when `autoDetectTheme` is on and
638
- // the DApp hasn't called `setThemeMode` manually.
639
- useEffect(() => {
640
- if (!kitConfig.autoDetectTheme)
641
- return;
642
- if (modeOverride)
643
- return;
644
- if (typeof window === 'undefined' || !window.matchMedia)
645
- return;
646
- const mql = window.matchMedia('(prefers-color-scheme: dark)');
647
- const handler = () => setOsThemeTick((n) => n + 1);
648
- try {
649
- mql.addEventListener('change', handler);
650
- return () => mql.removeEventListener('change', handler);
651
- }
652
- catch {
653
- // Legacy Safari (< 14) fallback.
654
- mql.addListener(handler);
655
- return () => mql.removeListener(handler);
656
- }
657
- }, [kitConfig.autoDetectTheme, modeOverride]);
658
- // Tag the documentElement so the emitted CSS rules
659
- // (`[data-cck-theme="dark"] { --cck-... }`) are active, and mirror
660
- // onto `data-theme` which dapp-ui component stylesheets already key on.
661
- useEffect(() => {
662
- if (typeof document === 'undefined')
663
- return;
664
- const root = document.documentElement;
665
- const prevCkit = root.getAttribute('data-cck-theme');
666
- const prevTheme = root.getAttribute('data-theme');
667
- root.setAttribute('data-cck-theme', activeMode);
668
- // Only set `data-theme` if the host page hasn't already claimed it —
669
- // many apps manage their own global theme toggle via that attribute.
670
- if (!prevTheme) {
671
- root.setAttribute('data-theme', activeMode);
672
- }
673
- return () => {
674
- if (prevCkit == null)
675
- root.removeAttribute('data-cck-theme');
676
- else
677
- root.setAttribute('data-cck-theme', prevCkit);
678
- if (prevTheme == null)
679
- root.removeAttribute('data-theme');
680
- };
681
- }, [activeMode]);
682
- // Build the CSS once per token change. Both `light` and `dark` blocks
683
- // are emitted so toggling `data-cck-theme` is an instant, style-only
684
- // switch — no layout thrash, no style tag churn.
685
- const themeCss = useMemo(() => buildThemeCssText(activeTokens), [activeTokens]);
686
- const themeContextValue = useMemo(() => ({
687
- mode: activeMode,
688
- tokens: activeTokens,
689
- setThemeMode: (mode) => setModeOverride(mode),
690
- setThemeTokens: (tokens) => setTokensOverride(tokens),
691
- }), [activeMode, activeTokens]);
692
- useEffect(() => {
693
- if (typeof window === 'undefined')
694
- return;
695
- const handler = (event) => {
696
- if (isBenignWcError(event.reason)) {
697
- event.preventDefault();
698
- }
699
- };
700
- window.addEventListener('unhandledrejection', handler);
701
- return () => window.removeEventListener('unhandledrejection', handler);
702
- }, []);
703
- const requestProviderSwap = useCallback((kind, walletId) => {
704
- if (swapInFlight.current)
705
- return;
706
- swapInFlight.current = true;
707
- setPendingConnectWalletId(walletId);
708
- setActiveProviderState(kind);
709
- writeActiveProvider(config.activeProviderStorageKey, kind);
710
- // Clear the lock after this microtask so subsequent user actions
711
- // (post-remount) can swap again if needed.
712
- queueMicrotask(() => {
713
- swapInFlight.current = false;
714
- });
715
- }, [config.activeProviderStorageKey]);
716
- const clearPending = useCallback(() => setPendingConnectWalletId(null), []);
717
- // Pick the wagmi config + matching hydration slice for the active side.
718
- const wagmiConfig = activeProvider === 'cross' ? config.crossWagmiConfig : config.reownWagmiConfig;
719
- const initial = sanitizeInitialState(pickInitialState(initialState, activeProvider));
720
- if (!wagmiConfig) {
721
- // The DApp requested an active provider that isn't configured —
722
- // fall through to the other side rather than crash.
723
- const fallback = activeProvider === 'cross' ? config.reownWagmiConfig : config.crossWagmiConfig;
724
- if (!fallback) {
725
- throw new Error('[crossx-kit] createCrossxConfig returned no wagmi Config for either side');
726
- }
727
- const otherKind = activeProvider === 'cross' ? 'reown' : 'cross';
728
- // Schedule a state fix for next render.
729
- queueMicrotask(() => setActiveProviderState(otherKind));
730
- }
731
- const effectiveConfig = wagmiConfig ??
732
- (activeProvider === 'cross'
733
- ? config.reownWagmiConfig
734
- : config.crossWagmiConfig);
735
- return (_jsxs(CrossConnectKitThemeContext.Provider, { value: themeContextValue, children: [_jsx("style", { "data-cross-connect-kit-theme": "", children: themeCss }), _jsx(WagmiProvider, { config: effectiveConfig, initialState: initial, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(CrossConnectKitInner, { config: config, activeProvider: activeProvider, onRequestProviderSwap: requestProviderSwap, pendingConnectWalletId: pendingConnectWalletId, clearPending: clearPending, themeMode: activeMode, themeTokens: activeTokens, extraWallets: extraWallets, walletAllowlist: walletAllowlist ?? null, children: children }) }) }, activeProvider)] }));
736
- }
737
- //# sourceMappingURL=CrossConnectKitProvider.js.map