haixunMaster/extension/haixun-threads-sync/service-worker.js

119 lines
3.3 KiB
JavaScript
Raw Permalink Normal View History

2026-06-21 12:50:31 +00:00
import { buildStorageState } from "./storage-state.js";
const CONTENT_SCRIPT_ID = "haixun-bridge";
async function getHaixunSessionToken(serverOrigin) {
const cookie = await chrome.cookies.get({
url: serverOrigin,
name: "haixun_session",
});
return cookie?.value ?? null;
}
async function ensureContentScript(serverOrigin) {
const pattern = `${serverOrigin}/*`;
const existing = await chrome.scripting.getRegisteredContentScripts();
if (existing.some((script) => script.id === CONTENT_SCRIPT_ID)) {
return;
}
await chrome.scripting.registerContentScripts([
{
id: CONTENT_SCRIPT_ID,
matches: [pattern],
js: ["content-haixun.js"],
runAt: "document_idle",
},
]);
}
async function requestHostPermission(serverOrigin) {
const granted = await chrome.permissions.contains({
origins: [`${serverOrigin}/*`],
});
if (granted) return true;
return chrome.permissions.request({ origins: [`${serverOrigin}/*`] });
}
export async function syncThreadsSession(serverUrl, accountId) {
const origin = new URL(serverUrl).origin;
const sessionToken = await getHaixunSessionToken(origin);
if (!sessionToken) {
throw new Error(`請先在 Chrome 開啟並登入 ${origin}`);
}
const storageState = await buildStorageState();
const res = await fetch(`${origin}/api/session/import`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `haixun_session=${sessionToken}`,
},
body: JSON.stringify({
storageState,
...(accountId ? { accountId } : {}),
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(data.error ?? data.message ?? `匯入失敗HTTP ${res.status}`);
}
return data;
}
async function resolveServerUrl(partial) {
if (partial) return new URL(partial).origin;
const stored = await chrome.storage.sync.get(["serverUrl"]);
if (stored.serverUrl) return new URL(stored.serverUrl).origin;
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
const activeUrl = tabs[0]?.url;
if (activeUrl && !activeUrl.includes("threads.com") && !activeUrl.includes("threads.net")) {
try {
return new URL(activeUrl).origin;
} catch {
// ignore
}
}
throw new Error("請在擴充功能選項設定巡樓網址,或先開啟巡樓分頁");
}
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.action !== "sync") return undefined;
(async () => {
try {
const serverUrl = await resolveServerUrl(message.serverUrl);
const allowed = await requestHostPermission(serverUrl);
if (!allowed) {
throw new Error("需要授權存取巡樓網站才能同步");
}
await ensureContentScript(serverUrl);
const result = await syncThreadsSession(serverUrl, message.accountId);
sendResponse({ success: true, ...result });
} catch (error) {
sendResponse({
success: false,
valid: false,
message: error instanceof Error ? error.message : "同步失敗",
});
}
})();
return true;
});
chrome.runtime.onInstalled.addListener(async () => {
const { serverUrl } = await chrome.storage.sync.get(["serverUrl"]);
if (!serverUrl) {
await chrome.storage.sync.set({ serverUrl: "http://localhost:3000" });
}
});