152 lines
4.9 KiB
Plaintext
152 lines
4.9 KiB
Plaintext
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 extension’s 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" });
|
||
|