Files
readitlater/chrome_extension_readitlater.txt
root 1c4aaf18b2 Initial commit
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 03:53:49 +00:00

152 lines
4.9 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
jared.evans@JARED-EVANS-C02G14ZWQ05N webext_readitlater % cat manifest.json
{
"manifest_version": 3,
"name": "readitlater — Save Page",
"version": "0.1.0",
"action": { "default_title": "Save to readitlater" },
"permissions": ["activeTab", "scripting", "storage"],
"host_permissions": ["https://www.jaredlog.com/*"],
"background": { "service_worker": "background.js" },
"icons": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
}
jared.evans@JARED-EVANS-C02G14ZWQ05N webext_readitlater % cat background.js
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 = `<span class="text">${text}</span>`;
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: "<!doctype 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" });
How to add the hex token:
Use the extensions background page (preferred)
Go to chrome://extensions/.
Enable Developer mode (toggle top right).
Find your readitlater extension.
Click “service worker” link under “Inspect views”. This opens a DevTools console for the extension background.
Now paste:
chrome.storage.sync.set({ READITLATER_TOKEN: "2cb9f5b875af65b4de7ff7736e384ae9d33e1bf2176c45afac24a713804ef6d4" });