From f8bd18aba6c1da40deae67a8eada6d854d8a10ac Mon Sep 17 00:00:00 2001 From: Felipe Mendes Date: Fri, 17 Jan 2025 14:50:00 -0300 Subject: [PATCH] fix: set iframe deeplink target to `_top` (#5602) * fix: set iframe deeplink target to _top * refactor: create openDeeplink util * test: add tests for isIframe util --- packages/utils/src/misc.ts | 25 ++++++++-- packages/utils/test/misc.spec.ts | 83 +++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index b1c40e355..857fce7f1 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -413,11 +413,7 @@ export async function handleDeeplinkRedirect({ return; } - if (link.startsWith("https://") || link.startsWith("http://")) { - window.open(link, "_blank", "noreferrer noopener"); - } else { - window.open(link, isTelegram() ? "_blank" : "_self", "noreferrer noopener"); - } + openDeeplink(link); } else if (env === ENV_MAP.reactNative) { // global.Linking is set by react-native-compat if (typeof (global as any)?.Linking !== "undefined") { @@ -444,6 +440,17 @@ export function formatDeeplinkUrl(deeplink: string, requestId: number, sessionTo return link; } +export function openDeeplink(url: string) { + let target = "_self"; + if (isIframe()) { + target = "_top"; + } else if (isTelegram() || url.startsWith("https://") || url.startsWith("http://")) { + target = "_blank"; + } + + window.open(url, target, "noreferrer noopener"); +} + export async function getDeepLink(storage: IKeyValueStorage, key: string) { let link: string | undefined = ""; try { @@ -501,6 +508,14 @@ export function isTelegram() { ); } +export function isIframe() { + try { + return window.self !== window.top; + } catch { + return false; + } +} + export function toBase64(input: string, removePadding = false): string { const encoded = Buffer.from(input).toString("base64"); return removePadding ? encoded.replace(/[=]/g, "") : encoded; diff --git a/packages/utils/test/misc.spec.ts b/packages/utils/test/misc.spec.ts index c78bb51db..6188f3e77 100644 --- a/packages/utils/test/misc.spec.ts +++ b/packages/utils/test/misc.spec.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { calcExpiry, formatDeeplinkUrl, @@ -8,6 +8,8 @@ import { hasOverlap, isExpired, toBase64, + openDeeplink, + isIframe, } from "../src"; const RELAY_URL = "wss://relay.walletconnect.org"; @@ -189,5 +191,84 @@ describe("Misc", () => { const decoded = atob(formatted.split("startapp=")[1]); expect(decoded).to.eql(partToEncode); }); + + describe("openDeeplink", () => { + const previousWindow = globalThis.window; + + beforeEach(() => { + Object.assign(globalThis, { + window: { + open: vi.fn(), + }, + }); + }); + + afterAll(() => { + Object.assign(globalThis, { + window: previousWindow, + }); + }); + + it("should target '_blank' if link starts with 'https://'", () => { + const url = "https://example.com"; + openDeeplink(url); + expect(window.open).toHaveBeenCalledWith(url, "_blank", "noreferrer noopener"); + }); + + it("should target '_blank' if link starts with 'http://'", () => { + const url = "http://example.com"; + openDeeplink(url); + expect(window.open).toHaveBeenCalledWith(url, "_blank", "noreferrer noopener"); + }); + + it("should target '_blank' for telegram deep link", () => { + Object.assign(window, { + Telegram: {}, + }); + + const url = "scheme://example.com"; + openDeeplink(url); + expect(window.open).toHaveBeenCalledWith(url, "_blank", "noreferrer noopener"); + + (window as any).Telegram = undefined; + }); + + it("should target '_top' if is an iframe", () => { + Object.assign(window, { + top: {}, + }); + + const url = "scheme://example.com"; + openDeeplink(url); + expect(window.open).toHaveBeenCalledWith(url, "_top", "noreferrer noopener"); + }); + + it("should target '_self' for other cases", () => { + const url = "scheme://example.com"; + openDeeplink(url); + expect(window.open).toHaveBeenCalledWith(url, "_self", "noreferrer noopener"); + }); + }); + }); + + describe("isIframe", () => { + const previousWindow = globalThis.window; + + afterEach(() => { + Object.assign(globalThis, { window: previousWindow }); + }); + + it("should return true if window.top is not equal to window", () => { + Object.assign(globalThis, { + window: { + top: {}, + }, + }); + expect(isIframe()).to.be.true; + }); + + it("should return false if window.top is equal to window", () => { + expect(isIframe()).to.be.false; + }); }); });