import qrcode from "../vendor/qrcode.js"; // Schemes that either can't be QR-encoded usefully or are browser-internal. const PRIVILEGED_PROTOCOLS = new Set([ "about:", "moz-extension:", "chrome:", "view-source:", "resource:", "javascript:", "data:", ]); qrcode.stringToBytes = qrcode.stringToBytesFuncs["UTF-8"]; const qrEl = document.getElementById("qr"); const messageEl = document.getElementById("message"); const urlEl = document.getElementById("url-text"); const copyBtn = document.getElementById("copy"); let currentUrl = ""; let copyResetTimer = null; function showMessage(text) { qrEl.replaceChildren(); qrEl.dataset.empty = "true"; messageEl.textContent = text; messageEl.hidden = false; copyBtn.disabled = true; } function isPrivileged(url) { try { return PRIVILEGED_PROTOCOLS.has(new URL(url).protocol); } catch { return true; } } function renderQr(url) { // EC level 'M' (~15%) balances scan robustness against QR density for screen display. const qr = qrcode(0, "M"); qr.addData(url, "Byte"); qr.make(); const svgString = qr.createSvgTag({ cellSize: 4, margin: 2, scalable: true }); // Parse to a real DOM node rather than assigning innerHTML, to keep the // string→DOM boundary explicit even though the bytes come from a trusted library. const doc = new DOMParser().parseFromString(svgString, "image/svg+xml"); const svg = doc.documentElement; if (svg.nodeName.toLowerCase() !== "svg") { throw new Error("QR library did not return SVG"); } qrEl.replaceChildren(svg); qrEl.dataset.empty = "false"; messageEl.hidden = true; } async function init() { let tabs; try { tabs = await browser.tabs.query({ active: true, currentWindow: true }); } catch (err) { showMessage("Could not read the active tab."); return; } const url = tabs?.[0]?.url; if (!url) { showMessage("No URL available for this tab."); return; } currentUrl = url; urlEl.textContent = url; if (isPrivileged(url)) { showMessage("This page can't be shared (browser-internal URL)."); return; } try { renderQr(url); } catch (err) { // qrcode-generator throws when the data exceeds the largest QR version (~2,953 bytes for L). showMessage("URL is too long to encode as a QR code."); } } copyBtn.addEventListener("click", async () => { if (!currentUrl) return; try { await navigator.clipboard.writeText(currentUrl); copyBtn.textContent = "Copied!"; } catch (err) { copyBtn.textContent = "Copy failed"; } clearTimeout(copyResetTimer); copyResetTimer = setTimeout(() => { copyBtn.textContent = "Copy URL"; }, 1200); }); init();