Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/qr-overlay-pointer-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"thirdweb": patch
---

Keep the WalletConnect QR overlay usable when it is opened from inside another modal, and stop overlays stacking.

The vanilla `createQROverlay` did not declare `pointer-events` on its root. Because the overlay is appended to `document.body`, and modal libraries commonly set `pointer-events: none` there while a dialog is open, the overlay inherited that value and became unclickable — visible at `z-index: 9999`, but with clicks passing through to the dialog behind it. It now sets `pointer-events: auto` explicitly.

Separately, each connect attempt only tracked the overlay it created, so an abandoned attempt — or a pairing that expired and re-emitted its URI — left its overlay in the DOM and the next attempt stacked another on top. Overlays are now tagged and any stale one is removed before a new one is appended.
7 changes: 7 additions & 0 deletions .changeset/rabby-mobile-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"thirdweb": patch
---

Fix Rabby connections on mobile and in-modal QR on desktop by refreshing its stale wallet registry entry.

The generated entry for `io.rabby` recorded no iOS app, no Android app and no deep link, even though the WalletConnect registry has published all three for some time. As a result Rabby had no mobile hand-off available and was excluded from `WCSupportedWalletIds`, so mobile users could not reach the Rabby app and desktop users were routed onto the generic external-connect fallback instead of the in-modal WalletConnect QR screen.
3 changes: 2 additions & 1 deletion packages/thirdweb/src/wallets/__generated__/wallet-ids.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions packages/thirdweb/src/wallets/wallet-connect/qr-overlay.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @vitest-environment happy-dom
import { beforeEach, describe, expect, it } from "vitest";
import { createQROverlay } from "./qr-overlay.js";

const URI = "wc:1234@2?relay-protocol=irn&symKey=abcd";

/**
* The overlay is identified by its own styling rather than by any attribute it
* sets, so these tests describe observable behaviour rather than implementation.
*/
function overlayElements(): HTMLElement[] {
return Array.from(document.body.children).filter(
(el): el is HTMLElement =>
el instanceof HTMLElement && el.style.zIndex === "9999",
);
}

describe("createQROverlay", () => {
beforeEach(() => {
document.body.innerHTML = "";
});

it("keeps the overlay clickable when the host page disables pointer events on body", () => {
// Modal libraries commonly do this while a dialog is open.
document.body.style.pointerEvents = "none";

const overlay = createQROverlay(URI);

const [root] = overlayElements();
expect(root).toBeDefined();
// Without an explicit value the overlay inherits `none` from body and
// becomes unclickable despite being visible.
expect(root?.style.pointerEvents).toBe("auto");

overlay.destroy();
});

it("replaces an overlay left behind by a previous attempt instead of stacking", () => {
createQROverlay(URI);
createQROverlay(URI);

expect(overlayElements()).toHaveLength(1);
});
});
18 changes: 18 additions & 0 deletions packages/thirdweb/src/wallets/wallet-connect/qr-overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export interface QROverlay {
show: () => void;
}

/**
* Marks overlays created by this module so stale ones can be cleaned up.
* @internal
*/
const QR_OVERLAY_ATTRIBUTE = "data-tw-qr-overlay";

/**
* Creates a QR code overlay for the given WalletConnect URI
*/
Expand All @@ -69,14 +75,26 @@ export function createQROverlay(
onCancel,
} = options;

// Remove overlays left behind by earlier connect attempts. Each call only
// tracks the overlay it created, so an abandoned attempt - or a pairing that
// expired and re-emitted its URI - would otherwise stack another one on top.
document.querySelectorAll(`[${QR_OVERLAY_ATTRIBUTE}]`).forEach((stale) => {
stale.remove();
});

// Create overlay backdrop
const overlay = document.createElement("div");
overlay.setAttribute(QR_OVERLAY_ATTRIBUTE, "");
// pointer-events is set explicitly because the overlay is appended to
// document.body, and modal libraries commonly disable pointer events there
// while a dialog is open. Inheriting that would render the overlay unclickable.
overlay.style.cssText = `
position: fixed;
inset: 0;
background-color: ${theme === "dark" ? "rgba(0, 0, 0, 0.8)" : "rgba(0, 0, 0, 0.5)"};
backdrop-filter: blur(10px);
z-index: 9999;
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
Expand Down
Loading