const API_URL = "https://www.jaredlog.com/readitlater/api/v1/capture"; async function getToken() { return new Promise((resolve) => { chrome.storage.sync.get(["READITLATER_TOKEN"], (res) => resolve(res.READITLATER_TOKEN || "")); }); } async function injectBanner(tabId, text, ok = true) { try { await chrome.scripting.executeScript({ target: { tabId }, world: "MAIN", // we want to touch the page DOM func: (text, ok) => { // Create a host and shadow root so site CSS can't break us const host = document.createElement("div"); host.setAttribute("id", "readitlater-toast-host"); host.style.all = "initial"; // reduce leakage in some edge cases const shadow = host.attachShadow({ mode: "closed" }); // Styles const style = document.createElement("style"); style.textContent = ` @keyframes slideDown { from { transform: translateY(-110%); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes fadeOut { to { opacity: 0; transform: translateY(-110%); } } .toast { position: fixed; top: 0; left: 50%; transform: translateX(-50%); z-index: 2147483647; margin: 12px auto 0; padding: 10px 14px; border-radius: 8px; font: 14px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: #0b1f0b; background: ${ok ? "#c7f9cc" : "#ffd6d6"}; border: 1px solid ${ok ? "#94d7a2" : "#ffabab"}; box-shadow: 0 10px 20px rgba(0,0,0,.12), 0 2px 6px rgba(0,0,0,.08); animation: slideDown 180ms ease-out; pointer-events: none; /* don't block page */ max-width: 90vw; text-align: center; } .toast .text { white-space: pre-wrap; } @media (prefers-reduced-motion: reduce) { .toast { animation: none; } } `; // Container const toast = document.createElement("div"); toast.className = "toast"; toast.setAttribute("role", "status"); toast.setAttribute("aria-live", "polite"); toast.innerHTML = `${text}`; shadow.append(style, toast); document.documentElement.appendChild(host); // Auto-remove after 2 seconds (fade for 200ms) const remove = () => host.remove(); setTimeout(() => { toast.style.animation = "fadeOut 200ms ease-in forwards"; setTimeout(remove, 220); }, 2000); }, args: [text, ok], }); } catch (e) { // Non-fatal if injection fails (e.g., restricted pages) console.warn("readitlater: banner inject failed", e); } } chrome.action.onClicked.addListener(async (tab) => { if (!tab?.id) return; const [result] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => ({ url: window.location.href, title: document.title, html: "" + document.documentElement.outerHTML }), }); const payload = result?.result || null; if (!payload) return; const token = await getToken(); try { const resp = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " + token }, body: JSON.stringify(payload), }); if (resp.ok) { // Success banner await injectBanner(tab.id, "Link saved.", true); } else { console.error("Capture failed:", resp.status, await resp.text()); await injectBanner(tab.id, "Save failed.", false); } } catch (e) { console.error("readitlater: network error", e); await injectBanner(tab.id, "Network error.", false); } }); // One-time: set your token in DevTools console on any page: // chrome.storage.sync.set({ READITLATER_TOKEN: "YOUR_HEX_TOKEN" });