Files
order_site/apps/extensions/pdd-cookie-sync/background.js
T
yml2213 9a342e7ea6 feat(pdd-extension): 插件升级至 v1.2.0,增加防休眠心跳、会话主动续期与超时弹窗秒级拦截
1. 新增 content.js 页面级保活引擎,自动注入拼多多商家后台 (mms.pinduoduo.com);
2. 主动防休眠与静默续期:每 2 分钟向 MMS 接口发送轻量只读心跳并派发微动作,主动刷新服务端 PASS_ID 有效期,从根本上阻止空闲弹窗生成;
3. 超时弹窗秒级自动拦截:使用 MutationObserver 实时监听 DOM,毫秒级捕获「当前页面停留时间过长」弹窗并自动点击「刷新」,彻底解决页面挂死导致 Cookie 会话过期被踢出账号的问题;
4. 后台长效平滑刷新:后台标签页静置超 25 分钟自动执行无感安全重载,保障 CSRF Token 与会话 24 小时鲜活;
5. 日志链路打通:将页面防休眠、弹窗拦截与周期刷新事件接入扩展日志控制台。
2026-09-19 11:06:32 +08:00

463 lines
14 KiB
JavaScript

/**
* 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
}
})