@abstraxn/signer-react 3.3.7 → 3.3.8

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/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.3.8] - 2026-09-16
9
+
10
+ ### Fixed
11
+ - **Create Proposal never opened MetaMask (loader stuck)** – 3.3.7 force-restarted the WalletConnect relay after every return from the wallet, including after *connect*. That restart raced the next `session_request`, so Create Proposal never deeplinked to MetaMask on iOS/Android. Relay restart now happens only for an in-flight approval. New transactions send first, then explicitly open the connected wallet app so the confirmation popup appears.
12
+
8
13
  ## [3.3.7] - 2026-09-15
9
14
 
10
15
  ### Fixed
@@ -34,6 +34,12 @@ export declare function getDappReturnUrl(): string;
34
34
  * navigating Safari to a new URL (which reloads and drops in-memory state).
35
35
  */
36
36
  export declare function getSourceBrowserNativeRedirect(): string | undefined;
37
+ /**
38
+ * Open the already-connected wallet app so the user sees the confirmation UI.
39
+ * WalletConnect often sends the session_request but does not deeplink on a
40
+ * later tx if the QR modal is hidden — Create Proposal then sits on a loader.
41
+ */
42
+ export declare function openConnectedWalletApp(provider: any): void;
37
43
  /**
38
44
  * Lazy metadata so url/redirect are read at connect/request time, not at
39
45
  * provider mount (which is often the homepage in Next.js layouts).
@@ -123,6 +123,79 @@ export function getSourceBrowserNativeRedirect() {
123
123
  return "googlechrome://";
124
124
  return undefined;
125
125
  }
126
+ function isAndroid() {
127
+ if (typeof navigator === "undefined")
128
+ return false;
129
+ return /Android/i.test(navigator.userAgent || "");
130
+ }
131
+ function isMobileDevice() {
132
+ return isIOS() || isAndroid();
133
+ }
134
+ function isWalletHttpDeepLink(url) {
135
+ try {
136
+ const host = new URL(url).hostname.toLowerCase();
137
+ return (host.includes("metamask.app.link") ||
138
+ host.includes("link.metamask.io") ||
139
+ host.includes("rainbow.me") ||
140
+ host.includes("link.trustwallet.com") ||
141
+ host.includes("walletconnect.com"));
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
147
+ function canOpenAsWalletApp(url) {
148
+ if (!url)
149
+ return false;
150
+ if (/^https?:\/\//i.test(url))
151
+ return isWalletHttpDeepLink(url);
152
+ return true;
153
+ }
154
+ /**
155
+ * Open the already-connected wallet app so the user sees the confirmation UI.
156
+ * WalletConnect often sends the session_request but does not deeplink on a
157
+ * later tx if the QR modal is hidden — Create Proposal then sits on a loader.
158
+ */
159
+ export function openConnectedWalletApp(provider) {
160
+ if (typeof window === "undefined" || !isMobileDevice())
161
+ return;
162
+ restoreWalletConnectModal();
163
+ try {
164
+ if (typeof provider?.modal?.open === "function") {
165
+ void provider.modal.open();
166
+ }
167
+ }
168
+ catch {
169
+ // Modal may not exist after we force-hid it post-connect.
170
+ }
171
+ const peer = provider?.session?.peer?.metadata ??
172
+ provider?.signer?.session?.peer?.metadata;
173
+ const native = String(peer?.redirect?.native || "");
174
+ const universal = String(peer?.redirect?.universal || "");
175
+ const name = String(peer?.name || "").toLowerCase();
176
+ let target = "";
177
+ if (canOpenAsWalletApp(native))
178
+ target = native;
179
+ else if (canOpenAsWalletApp(universal))
180
+ target = universal;
181
+ else if (name.includes("metamask") || !name) {
182
+ target = isIOS() ? "metamask://" : "https://metamask.app.link/wc";
183
+ }
184
+ else if (name.includes("rainbow")) {
185
+ target = "rainbow://";
186
+ }
187
+ else if (name.includes("trust")) {
188
+ target = "trust://";
189
+ }
190
+ if (!target)
191
+ return;
192
+ try {
193
+ window.location.href = target;
194
+ }
195
+ catch {
196
+ // Ignore
197
+ }
198
+ }
126
199
  function getWalletConnectRedirect() {
127
200
  const native = getSourceBrowserNativeRedirect();
128
201
  return native ? { native } : undefined;
@@ -292,16 +365,13 @@ export async function resumeWalletConnectRelayer(provider, options) {
292
365
  }
293
366
  const run = (async () => {
294
367
  try {
295
- if (!options?.force && isRelayerConnected(relayer)) {
296
- const ping = relayer.provider?.connection?.ping;
297
- if (typeof ping === "function") {
298
- await Promise.race([
299
- ping.call(relayer.provider.connection),
300
- new Promise((_, reject) => setTimeout(() => reject(new Error("ping timeout")), 1500)),
301
- ]);
368
+ // New requests must not restart a live session — that races the
369
+ // session_request and MetaMask never shows a confirmation popup.
370
+ if (!options?.force) {
371
+ if (isRelayerConnected(relayer))
302
372
  return;
303
- }
304
- // No ping API: do not assume a zombie "connected" socket is healthy.
373
+ await restartWalletConnectTransport(relayer);
374
+ return;
305
375
  }
306
376
  await restartWalletConnectTransport(relayer);
307
377
  const session = provider?.session ?? provider?.signer?.session;
@@ -396,16 +466,32 @@ export function patchWalletConnectProviderForMobile(provider) {
396
466
  if (!isApproval && !wasBackgrounded) {
397
467
  return originalRequest(...args);
398
468
  }
469
+ if (!isApproval && wasBackgrounded) {
470
+ try {
471
+ await resumeWalletConnectRelayer(provider, { force: true });
472
+ }
473
+ finally {
474
+ provider[NEEDS_RESUME_FLAG] = false;
475
+ }
476
+ return originalRequest(...args);
477
+ }
399
478
  if (isApproval) {
400
479
  provider[PENDING_FLAG] = (provider[PENDING_FLAG] || 0) + 1;
401
480
  restoreWalletConnectModal();
402
481
  syncWalletConnectDappRedirect(provider);
403
482
  }
404
483
  try {
405
- await resumeWalletConnectRelayer(provider, { force: wasBackgrounded });
406
- if (wasBackgrounded)
407
- provider[NEEDS_RESUME_FLAG] = false;
408
- return await originalRequest(...args);
484
+ // Never force-restart right before sending a new tx — that is what
485
+ // blocked the MetaMask popup on Create Proposal after 3.3.7.
486
+ await Promise.race([
487
+ resumeWalletConnectRelayer(provider, { force: false }),
488
+ new Promise((resolve) => setTimeout(resolve, 1000)),
489
+ ]);
490
+ const resultPromise = originalRequest(...args);
491
+ if (typeof window !== "undefined") {
492
+ window.setTimeout(() => openConnectedWalletApp(provider), 400);
493
+ }
494
+ return await resultPromise;
409
495
  }
410
496
  finally {
411
497
  if (isApproval) {
@@ -422,23 +508,25 @@ export function subscribeWalletConnectForegroundResume(provider) {
422
508
  if (typeof window === "undefined" || !provider)
423
509
  return () => { };
424
510
  let timer = null;
425
- let hiddenAt = 0;
426
511
  const resume = () => {
427
512
  if (timer)
428
513
  clearTimeout(timer);
429
- const hiddenMs = hiddenAt ? Date.now() - hiddenAt : 0;
430
514
  const pending = hasPendingWalletConnectApproval(provider);
431
- const force = pending || hiddenMs > 400 || !!provider[NEEDS_RESUME_FLAG];
515
+ const needsResume = pending || !!provider[NEEDS_RESUME_FLAG];
516
+ // Do not restart after connect-only backgrounding — that broke the next
517
+ // session_request so MetaMask never opened for Create Proposal.
518
+ if (!needsResume) {
519
+ clearWalletReturnIfSameDocument();
520
+ return;
521
+ }
432
522
  timer = setTimeout(() => {
433
523
  clearWalletReturnIfSameDocument();
434
- void resumeWalletConnectRelayer(provider, { force });
435
- if (pending)
436
- restoreWalletConnectModal();
437
- }, pending || force ? 50 : 250);
524
+ void resumeWalletConnectRelayer(provider, { force: true });
525
+ restoreWalletConnectModal();
526
+ }, 50);
438
527
  };
439
528
  const onVisibility = () => {
440
529
  if (document.visibilityState === "hidden") {
441
- hiddenAt = Date.now();
442
530
  if (hasPendingWalletConnectApproval(provider)) {
443
531
  provider[NEEDS_RESUME_FLAG] = true;
444
532
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abstraxn/signer-react",
3
- "version": "3.3.7",
3
+ "version": "3.3.8",
4
4
  "description": "React SDK for Abstraxn Wallet - React components, hooks, and providers for seamless Web3 wallet integration",
5
5
  "main": "./dist/src/index.js",
6
6
  "module": "./dist/src/index.js",