// Session-wide preload that runs every page-inject add-on's bridge script in // the isolated world of tabs whose URL matches the add-on's declared origin // patterns. Registered via session.defaultSession.setPreloads in main.js // alongside bcnr-preload.js. // // The decision of WHICH scripts apply is made in main against the sender's // committed URL, not against anything the page can influence. Each script // gets a `theseus` object scoped to its add-on id: // theseus.contextBridge — expose an API into the page's main world // theseus.invoke(msg, payload) — call the add-on's onMessage(msg) handler // theseus.origin — the page origin main will show the user // theseus.evalInPage(code) — run code in the page's main world, resolve // its value (cookie-consent rules need it) // plus a `require` that only resolves "electron" so scripts written in the // ordinary preload idiom keep working. const { contextBridge, ipcRenderer, webFrame } = require("electron"); // Web tabs run preloads in iframes too (nodeIntegrationInSubFrames, for the // password hooks). Add-on page scripts (wallet providers, consent rules) // stay top-frame only, as they always were: main decides them from the // tab's URL, not the frame's, and must never put them into third-party frames. if (window.top !== window) return; let injections = []; try { injections = ipcRenderer.sendSync("addon-inject-scripts", location.href) || []; } catch (e) { console.warn("[theseus] add-on inject query failed:", e?.message || e); } for (const inj of injections) { const id = String(inj.id); const theseus = Object.freeze({ id, origin: inj.origin, contextBridge, invoke: (msg, payload) => ipcRenderer.invoke("addon-page-msg", id, String(msg), payload), evalInPage: (code) => webFrame.executeJavaScript(String(code)), }); const scopedRequire = (name) => { if (name === "electron") return { contextBridge }; throw new Error(`addon inject scripts may only require("electron") — got ${name}`); }; try { new Function("theseus", "require", inj.source)(theseus, scopedRequire); } catch (e) { console.warn(`[theseus] add-on "${id}" page-inject failed:`, e?.message || e); } } // wiz:// links (WizardConnect pairing, "Open in wallet"). A same-tab click is // caught by main on will-navigate; one that opens a new window // (target="_blank") reaches main's window-open handler, which can tell the // page's own link from a frame's only by the referrer, and rel="noreferrer", // common on dapps, strips it. So those clicks were dropped and the wallet // never saw them. A click the user really made on such a link in this, the // top document, goes straight to main instead; main checks it came from the // tab's main frame and routes it like any other pairing link. document.addEventListener("click", (e) => { if (!e.isTrusted || e.button !== 0) return; // composedPath reaches into shadow roots, where web-component buttons live. const a = (typeof e.composedPath === "function" ? e.composedPath() : [e.target]) .find((n) => n && n.tagName === "A" && n.hasAttribute && n.hasAttribute("href")); if (!a) return; const href = String(a.getAttribute("href") || "").trim(); if (!/^wiz:/i.test(href) || href.length > 4096) return; e.preventDefault(); ipcRenderer.send("wiz-link", href); }, true);