UNPKG

@frak-labs/core-sdk

Version:

Core SDK of the Frak wallet, low level library to interact directly with the frak ecosystem.

180 lines (162 loc) 5.46 kB
import { getBackendUrl } from "../../config/backendUrl"; import { getClientId } from "../../config/clientId"; import type { FrakWalletSdkConfig } from "../../types"; /** * Base props for the iframe * @ignore */ export const baseIframeProps = { id: "frak-wallet", name: "frak-wallet", title: "Frak Wallet", allow: "publickey-credentials-get *; clipboard-write; web-share *", style: { width: "0", height: "0", border: "0", position: "absolute", zIndex: 2000001, top: "-1000px", left: "-1000px", colorScheme: "auto", }, }; /** * Create the Frak iframe * @param args * @param args.walletBaseUrl - Use `config.walletUrl` instead. Will be removed in future versions. * @param args.config - The configuration object containing iframe options, including the replacement for `walletBaseUrl`. */ export function createIframe({ walletBaseUrl, config, }: { walletBaseUrl?: string; config?: FrakWalletSdkConfig; }): Promise<HTMLIFrameElement | undefined> { // Check if the iframe is already created const alreadyCreatedIFrame = document.querySelector("#frak-wallet"); // If the iframe is already created, remove it if (alreadyCreatedIFrame) { alreadyCreatedIFrame.remove(); } const iframe = document.createElement("iframe"); // Set the base iframe props iframe.id = baseIframeProps.id; iframe.name = baseIframeProps.name; iframe.allow = baseIframeProps.allow; iframe.style.zIndex = baseIframeProps.style.zIndex.toString(); changeIframeVisibility({ iframe, isVisible: false }); // Set src BEFORE appending to DOM to avoid about:blank load event const walletUrl = config?.walletUrl ?? walletBaseUrl ?? "https://wallet.frak.id"; const clientId = getClientId(); // Preconnect to the wallet + backend origins so the handshake doesn't pay // for a cold DNS/TLS round-trip on partner sites that didn't warm them. preconnect(walletUrl); preconnect(getBackendUrl(walletUrl)); iframe.src = `${walletUrl}/listener?clientId=${encodeURIComponent(clientId)}`; return new Promise((resolve) => { iframe.addEventListener("load", () => resolve(iframe)); document.body.appendChild(iframe); }); } /** * Change the visibility of the given iframe * @ignore */ export function changeIframeVisibility({ iframe, isVisible, }: { iframe: HTMLIFrameElement; isVisible: boolean; }) { if (!isVisible) { iframe.style.width = "0"; iframe.style.height = "0"; iframe.style.border = "0"; iframe.style.position = "fixed"; iframe.style.top = "-1000px"; iframe.style.left = "-1000px"; return; } iframe.style.position = "fixed"; iframe.style.top = "0"; iframe.style.left = "0"; iframe.style.width = "100%"; iframe.style.height = "100%"; iframe.style.pointerEvents = "auto"; } /** * Find an iframe within window.opener by pathname * * When a popup is opened via window.open from an iframe, window.opener points to * the parent window, not the iframe itself. This utility searches through all frames * in window.opener to find an iframe matching the specified pathname. * * @param pathname - The pathname to search for (default: "/listener") * @returns The matching iframe window, or null if not found * * @example * ```typescript * // Find the default /listener iframe * const listenerIframe = findIframeInOpener(); * * // Find a custom iframe * const customIframe = findIframeInOpener("/my-custom-iframe"); * ``` */ export function findIframeInOpener(pathname = "/listener"): Window | null { if (!window.opener) return null; const frameCheck = (frame: Window) => { try { return ( frame.location.origin === window.location.origin && frame.location.pathname === pathname ); } catch { // Cross-origin frame, skip return false; } }; // Check if the openner window is the right one if (frameCheck(window.opener)) return window.opener; // Search through frames in window.opener try { const frames = window.opener.frames; for (let i = 0; i < frames.length; i++) { if (frameCheck(frames[i])) { return frames[i]; } } return null; } catch (error) { console.error( `[findIframeInOpener] Error finding iframe with pathname ${pathname}:`, error ); return null; } } /** * Inject a `<link rel="preconnect">` for the given origin. Idempotent — browsers * de-duplicate preconnects per origin automatically, but we also guard on a data * attribute to avoid spamming the <head> in SPA re-inits. */ function preconnect(url: string): void { if (typeof document === "undefined") return; try { const origin = new URL(url).origin; const selector = `link[rel="preconnect"][data-frak-preconnect="${origin}"]`; if (document.head.querySelector(selector)) return; const link = document.createElement("link"); link.rel = "preconnect"; link.href = origin; link.crossOrigin = ""; link.dataset.frakPreconnect = origin; document.head.appendChild(link); } catch { // Invalid URL — nothing to preconnect. } }