From 82743b88e5b275140caf291855b88644e2bc1899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?coffee=20=E2=98=95=EF=B8=8F?= Date: Sat, 9 May 2026 11:19:08 -0400 Subject: [PATCH] Allow wallet iframe storage access --- packages/wallet-sdk/src/core/Dialog.ts | 8 +++- packages/wallet-sdk/test/src/Dialog.test.ts | 47 ++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/packages/wallet-sdk/src/core/Dialog.ts b/packages/wallet-sdk/src/core/Dialog.ts index d228d87..81fcea9 100644 --- a/packages/wallet-sdk/src/core/Dialog.ts +++ b/packages/wallet-sdk/src/core/Dialog.ts @@ -9,9 +9,11 @@ * * Iframe attributes are pinned to the same set Porto ships: * sandbox="allow-forms allow-scripts allow-same-origin allow-popups - * allow-popups-to-escape-sandbox" + * allow-popups-to-escape-sandbox + * allow-storage-access-by-user-activation" * allow="payment; publickey-credentials-get ; - * publickey-credentials-create ; clipboard-write" + * publickey-credentials-create ; storage-access ; + * clipboard-write" * * The clickjacking defence (IntersectionObserver v2 isVisible check) lives * INSIDE the iframe (in apps/web/wallet-host) — not here. From the parent @@ -176,12 +178,14 @@ export function iframe(options: IframeOptions = {}): DialogFactory { "allow-same-origin", "allow-popups", "allow-popups-to-escape-sandbox", + "allow-storage-access-by-user-activation", ].join(" "), ); const allow = [ "payment", `publickey-credentials-get ${hostOrigin}`, `publickey-credentials-create ${hostOrigin}`, + `storage-access ${hostOrigin}`, ]; if (!UserAgent.isFirefox()) allow.push("clipboard-write"); frame.setAttribute("allow", allow.join("; ")); diff --git a/packages/wallet-sdk/test/src/Dialog.test.ts b/packages/wallet-sdk/test/src/Dialog.test.ts index dcb41c3..ffc1709 100644 --- a/packages/wallet-sdk/test/src/Dialog.test.ts +++ b/packages/wallet-sdk/test/src/Dialog.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import * as Dialog from "../../src/core/Dialog.js"; function noopHandlers(): Dialog.DialogHandlers { @@ -152,3 +152,48 @@ describe("popup factory", () => { } }); }); + +describe("iframe factory", () => { + const realMatchMedia = window.matchMedia; + + beforeEach(() => { + Object.defineProperty(window, "matchMedia", { + configurable: true, + value: vi.fn().mockReturnValue({ + addEventListener: vi.fn(), + matches: false, + removeEventListener: vi.fn(), + }), + }); + }); + + afterEach(() => { + Object.defineProperty(window, "matchMedia", { + configurable: true, + value: realMatchMedia, + }); + document.querySelectorAll("dialog[data-abs-wallet]").forEach((node) => { + node.remove(); + }); + }); + + it("allows the wallet host iframe to request storage access", () => { + const handle = Dialog.iframe()({ + host: "https://wallet.test", + handlers: noopHandlers(), + }); + + const frame = document.querySelector( + 'iframe[data-testid="abstract-wallet"]', + ); + + expect(frame?.getAttribute("sandbox")?.split(" ")).toContain( + "allow-storage-access-by-user-activation", + ); + expect(frame?.getAttribute("allow")).toContain( + "storage-access https://wallet.test", + ); + + handle.destroy(); + }); +});