151
chrome_extension_readitlater.txt
Normal file
151
chrome_extension_readitlater.txt
Normal file
@@ -0,0 +1,151 @@
|
||||
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" });
|
||||
|
||||
Reference in New Issue
Block a user