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" }); } });