/** * Pinduoduo Cookie Sync Background Service Worker (Manifest V3) * 拼多多 Cookie 自动嗅探与加密保活后台服务 - 带结构化日志系统 */ try { importScripts('crypto.js') } catch (e) { console.error('导入加密工具失败:', e) } const ALARM_NAME = 'pdd_cookie_sync_alarm' const MAX_LOGS_LIMIT = 100 let debounceTimer = null // 设置扩展图标角标状态 function setExtensionBadge(text, color) { chrome.action.setBadgeText({ text }) if (color) { chrome.action.setBadgeBackgroundColor({ color }) } } // 格式化时间戳 function getNowFormattedTime() { const d = new Date() const m = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') const h = String(d.getHours()).padStart(2, '0') const min = String(d.getMinutes()).padStart(2, '0') const s = String(d.getSeconds()).padStart(2, '0') return `${m}-${day} ${h}:${min}:${s}` } /** * 记录扩展运行日志并持久化至 chrome.storage.local * @param {'info' | 'warn' | 'error' | 'success'} level 日志级别 * @param {string} action 动作模块 * @param {string} message 简要说明 * @param {any} [details] 附加诊断数据(脱敏安全显示) */ async function addExtensionLog(level, action, message, details = null) { try { const timeStr = getNowFormattedTime() const entry = { id: 'log_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6), time: timeStr, timestamp: Date.now(), level, action, message, details: details ? (typeof details === 'object' ? JSON.stringify(details) : String(details)) : null, } const data = await chrome.storage.local.get(['pdd_extension_logs']) const existing = Array.isArray(data.pdd_extension_logs) ? data.pdd_extension_logs : [] const updated = [entry, ...existing].slice(0, MAX_LOGS_LIMIT) await chrome.storage.local.set({ pdd_extension_logs: updated }) console.log(`[PDD-EXT] [${level.toUpperCase()}] [${action}] ${message}`, details || '') } catch (err) { console.error('保存扩展日志异常:', err) } } /** * 获取拼多多全量 Cookie 字符串 * 针对 mms.pinduoduo.com Host-only Cookie、根域名 Cookie、不同路径 Cookie 进行多维深度嗅探与合并去重 */ async function getPddCookieDetails(customCookie = '') { if (customCookie && customCookie.trim().length > 10) { const trimmed = customCookie.trim() const keys = trimmed .split(';') .map((p) => p.trim().split('=')[0]?.trim()) .filter(Boolean) return { cookieStr: trimmed, cookieCount: keys.length, keys, hasPassId: keys.includes('PASS_ID'), hasRckk: keys.includes('rckk'), hasNano: keys.some((k) => k.includes('nano')), isManual: true, } } try { const cookieMap = new Map() // 1. 深度嗅探 MMS 商家后台关键 URL 上下文 (包含 Host-only、Secure、HttpOnly 令牌) const targetUrls = [ 'https://mms.pinduoduo.com/', 'https://mms.pinduoduo.com/orders/list', 'https://mms.pinduoduo.com/home/', 'https://mms.pinduoduo.com/mangkhut/mms/recentOrderList', ] for (const url of targetUrls) { try { const cookies = await chrome.cookies.getAll({ url }) if (Array.isArray(cookies)) { for (const c of cookies) { if (c.name && c.value) { cookieMap.set(c.name, c.value) } } } } catch (err) { console.warn(`读取 URL [${url}] Cookie 异常:`, err) } } // 2. 补充查询主域名及各子域 Cookie const domains = [ 'mms.pinduoduo.com', '.mms.pinduoduo.com', 'pinduoduo.com', '.pinduoduo.com', ] for (const domain of domains) { try { const cookies = await chrome.cookies.getAll({ domain }) if (Array.isArray(cookies)) { for (const c of cookies) { if (c.name && c.value && !cookieMap.has(c.name)) { cookieMap.set(c.name, c.value) } } } } catch (err) { console.warn(`读取 Domain [${domain}] Cookie 异常:`, err) } } const keys = Array.from(cookieMap.keys()) const hasPassId = cookieMap.has('PASS_ID') const hasRckk = cookieMap.has('rckk') const hasNano = keys.some((k) => k.includes('nano')) if (cookieMap.size === 0) { return { cookieStr: '', cookieCount: 0, keys: [], hasPassId: false, hasRckk: false, hasNano: false, isManual: false, } } // 拼接成标准 Cookie 请求头字符串 const cookieStr = Array.from(cookieMap.entries()) .map(([name, val]) => `${name}=${val}`) .join('; ') return { cookieStr, cookieCount: cookieMap.size, keys, hasPassId, hasRckk, hasNano, isManual: false, } } catch (err) { console.error('读取拼多多 Cookie 异常:', err) return { cookieStr: '', cookieCount: 0, keys: [], hasPassId: false, hasRckk: false, hasNano: false, isManual: false, } } } // 核心同步逻辑 async function syncCookieToServer(isManual = false) { const config = await chrome.storage.local.get([ 'serverUrl', 'shopId', 'syncToken', 'manualCookie', 'lastCookieHash', 'lastSyncAt', ]) const serverUrl = String(config.serverUrl || '').trim().replace(/\/+$/, '') const shopId = String(config.shopId || '').trim() const syncToken = String(config.syncToken || '').trim() const manualCookie = String(config.manualCookie || '').trim() if (!serverUrl || !shopId || !syncToken) { setExtensionBadge('CFG', '#faad14') // 待配置 await addExtensionLog('warn', '配置检查', '未完成基础配置,缺少服务端地址、店铺标识或配对密钥') return { success: false, message: '请先打开插件弹窗完成服务器地址与店铺配对密钥配置', } } await addExtensionLog( 'info', '发起同步', `${isManual ? '手动点击立即同步' : '后台自动/保活同步'} [店铺: ${shopId}]`, { serverUrl, shopId, isManual }, ) // 1. 读取拼多多 Cookie const cookieDetails = await getPddCookieDetails(manualCookie) const cookieStr = cookieDetails.cookieStr if (!cookieStr) { setExtensionBadge('OFF', '#8c8c8c') // 未登录 await chrome.storage.local.set({ lastStatus: 'offline', lastMessage: '未在浏览器中检测到拼多多商家后台登录 Cookie', }) await addExtensionLog( 'error', 'Cookie嗅探', '未在浏览器检测到任何拼多多 Cookie,请先在当前浏览器打开并登录 mms.pinduoduo.com', ) return { success: false, message: '未检测到拼多多登录 Cookie,请先在当前浏览器中登录 mms.pinduoduo.com 商家后台', } } await addExtensionLog( cookieDetails.hasPassId ? 'info' : 'warn', 'Cookie嗅探', `成功提取 ${cookieDetails.cookieCount} 项 Cookie (含: ${cookieDetails.keys.slice(0, 8).join(', ')}${cookieDetails.keys.length > 8 ? '...' : ''})`, { totalKeys: cookieDetails.cookieCount, hasPassId: cookieDetails.hasPassId, hasRckk: cookieDetails.hasRckk, hasNano: cookieDetails.hasNano, isManual: cookieDetails.isManual, }, ) if (!cookieDetails.hasPassId) { await addExtensionLog( 'warn', '会话预警', '⚠️ 未检测到拼多多核心会话标识 PASS_ID,可能未登录或会话已过期。请在浏览器登录 mms.pinduoduo.com 并刷新后台页面', ) } // 2. 计算 Cookie 哈希并进行指纹去重 const currentHash = await computeSha256(cookieStr) const now = Date.now() const lastSyncTime = Number(config.lastSyncAt || 0) // 若非手动强制触发,且 Cookie 未变动,且距离上次同步不足 1 小时,跳过重复上报 if (!isManual && config.lastCookieHash === currentHash && now - lastSyncTime < 3600_000) { await addExtensionLog('info', '指纹比对', 'Cookie 无变动且未到周期,跳过重复网络上报') return { success: true, skipped: true, message: 'Cookie 无实质变动,无需重复上报', } } try { // 3. 请求服务端公钥 const pkUrl = `${serverUrl}/api/v1/pdd/extension/public-key` await addExtensionLog('info', '获取公钥', `请求服务端公钥: ${pkUrl}`) const pkResp = await fetch(pkUrl, { method: 'GET', headers: { Accept: 'application/json' }, }) if (!pkResp.ok) { const statusText = await pkResp.text().catch(() => '') throw new Error(`获取公钥失败 HTTP ${pkResp.status}: ${statusText || pkResp.statusText}`) } const pkJson = await pkResp.json() const serverPublicKeySpki = pkJson?.data?.publicKeySpki if (!serverPublicKeySpki) { throw new Error('服务端返回的 RSA 公钥格式异常 (缺少 publicKeySpki 字段)') } // 4. 构建明文载荷 const payload = { shopId, syncToken, cookie: cookieStr, userAgent: navigator.userAgent, timestamp: Date.now(), nonce: 'ext_' + Math.random().toString(36).slice(2) + Date.now().toString(36), } // 5. 执行端到端混合信封加密 (RSA-OAEP + AES-256-GCM) await addExtensionLog('info', '信封加密', '使用 RSA-OAEP + AES-256-GCM 封装高强度密文包') const envelope = await encryptPayloadWithEnvelope(payload, serverPublicKeySpki) // 6. 安全回传至服务端 const syncUrl = `${serverUrl}/api/v1/pdd/extension/sync-cookie` await addExtensionLog('info', '上传Cookie', `密文已发送至网关: ${syncUrl},等待服务端实测校验...`) const syncResp = await fetch(syncUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shop-Token': syncToken, }, body: JSON.stringify({ envelope }), }) const syncJson = await syncResp.json().catch(() => null) if (!syncResp.ok || !syncJson || syncJson.code !== 0) { const errMsg = syncJson?.message || `服务端返回错误 HTTP ${syncResp.status}` await addExtensionLog('error', '上传失败', `服务端返回校验未通过: ${errMsg}`, { httpStatus: syncResp.status, code: syncJson?.code, detail: syncJson?.data?.detail, }) throw new Error(errMsg) } // 同步成功,更新持久化状态 await chrome.storage.local.set({ lastCookieHash: currentHash, lastSyncAt: now, lastStatus: 'ok', lastMessage: syncJson.message || '同步成功并已生效', totalOrders: syncJson.data?.totalOrders, shopName: syncJson.data?.shopName, }) setExtensionBadge('OK', '#52c41a') // 绿色成功 await addExtensionLog('success', '同步成功', `Cookie 已通过拼多多网关实测校验并生效 (在售/历史订单数: ${syncJson.data?.totalOrders ?? 0})`, { shopName: syncJson.data?.shopName, totalOrders: syncJson.data?.totalOrders, }) return { success: true, message: syncJson.message || 'Cookie 同步成功!服务端已完成在线校验并持久化生效', totalOrders: syncJson.data?.totalOrders, shopName: syncJson.data?.shopName, } } catch (err) { const errMsg = err instanceof Error ? err.message : String(err) console.error('Cookie 自动同步失败:', errMsg) await chrome.storage.local.set({ lastStatus: 'error', lastMessage: errMsg, }) setExtensionBadge('ERR', '#ff4d4f') // 红色错误 return { success: false, message: errMsg, } } } // 监听拼多多 Cookie 实时变动事件(带防抖延时 3 秒) chrome.cookies.onChanged.addListener((changeInfo) => { const domain = changeInfo.cookie?.domain || '' if (domain.includes('pinduoduo.com')) { if (debounceTimer) { clearTimeout(debounceTimer) } debounceTimer = setTimeout(() => { syncCookieToServer(false).catch(() => null) }, 3000) } }) // 定时任务:每隔 15 分钟进行一次轻量巡检、保活上报与页面 Ping chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name === ALARM_NAME) { syncCookieToServer(false).catch(() => null) // 向打开的拼多多商家页面发送保活 Ping try { const tabs = await chrome.tabs.query({ url: '*://mms.pinduoduo.com/*' }) for (const tab of tabs) { if (tab.id) { chrome.tabs.sendMessage(tab.id, { action: 'ping_keepalive' }).catch(() => null) } } } catch { // 忽略查询异常 } } }) // 扩展安装或更新时注册定时闹钟 chrome.runtime.onInstalled.addListener(() => { chrome.alarms.create(ALARM_NAME, { periodInMinutes: 15, }) addExtensionLog('info', '扩展安装/更新', '拼多多 Cookie 保活扩展 v1.2.0 (含防退出/弹窗拦截引擎) 已启动') // 首次运行尝试同步一次 syncCookieToServer(false).catch(() => null) }) // 响应来自 popup.js 与 content.js 的交互消息 chrome.runtime.onMessage.addListener((req, _sender, sendResponse) => { if (req.action === 'content_log') { const level = req.level || 'info' const action = req.actionName || req.module || '页面保活' const message = req.message || '' const details = req.details || null addExtensionLog(level, action, message, details).then(() => { sendResponse({ success: true }) }) return true } if (req.action === 'trigger_sync') { syncCookieToServer(true).then(sendResponse).catch((err) => { sendResponse({ success: false, message: String(err) }) }) return true // 保持异步通信通道 } if (req.action === 'get_status') { chrome.storage.local.get(null).then(async (data) => { const details = await getPddCookieDetails(data.manualCookie) sendResponse({ hasCookie: Boolean(details.cookieStr && details.cookieStr.length > 20), cookieLength: details.cookieStr ? details.cookieStr.length : 0, cookieCount: details.cookieCount, hasPassId: details.hasPassId, hasRckk: details.hasRckk, hasNano: details.hasNano, storage: data, }) }) return true } if (req.action === 'get_logs') { chrome.storage.local.get(['pdd_extension_logs']).then((res) => { sendResponse({ logs: Array.isArray(res.pdd_extension_logs) ? res.pdd_extension_logs : [] }) }) return true } if (req.action === 'clear_logs') { chrome.storage.local.set({ pdd_extension_logs: [] }).then(async () => { await addExtensionLog('info', '清空日志', '用户已在插件控制台清空历史运行日志') sendResponse({ success: true }) }) return true } })