fix(pdd-extension): 增强 MMS Cookie 多维嗅探与 PASS_ID 校验,新增插件运行日志与升级 v1.1.0
This commit is contained in:
@@ -97,7 +97,27 @@ router.post('/sync-cookie', async (req, res, next) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 实测 Cookie 有效性(轻量探测)
|
||||
// 3. 诊断与实测 Cookie 有效性(轻量探测)
|
||||
const cookieKeys = decrypted.cookie
|
||||
.split(';')
|
||||
.map((part) => part.trim().split('=')[0]?.trim())
|
||||
.filter((k): k is string => Boolean(k))
|
||||
const hasPassId = cookieKeys.includes('PASS_ID')
|
||||
const hasRckk = cookieKeys.includes('rckk')
|
||||
|
||||
logInfo(
|
||||
'[pdd-extension]',
|
||||
`插件上报店铺 [${store.shopName}(${store.shopId})] Cookie,开始验证`,
|
||||
{
|
||||
shopId: store.shopId,
|
||||
cookieLen: decrypted.cookie.length,
|
||||
cookieKeyCount: cookieKeys.length,
|
||||
hasPassId,
|
||||
hasRckk,
|
||||
cookieKeys: cookieKeys.slice(0, 15),
|
||||
},
|
||||
)
|
||||
|
||||
const testResult = await PddClient.testCookie({
|
||||
cookie: decrypted.cookie,
|
||||
userAgent: decrypted.userAgent || store.userAgent,
|
||||
@@ -106,14 +126,31 @@ router.post('/sync-cookie', async (req, res, next) => {
|
||||
if (!testResult.valid) {
|
||||
logWarn('[pdd-extension]', `插件上报的 Cookie 实测未通过: ${testResult.message}`, {
|
||||
shopId: store.shopId,
|
||||
hasPassId,
|
||||
hasRckk,
|
||||
cookieLen: decrypted.cookie.length,
|
||||
})
|
||||
|
||||
const isSessionExpired =
|
||||
testResult.message.includes('43001') ||
|
||||
testResult.message.includes('会话已过期') ||
|
||||
!hasPassId
|
||||
|
||||
const userGuidance = !hasPassId
|
||||
? 'Cookie 中未包含拼多多核心会话标识 (PASS_ID)。请在当前浏览器打开并登录 mms.pinduoduo.com 商家后台,再点击插件上传。'
|
||||
: isSessionExpired
|
||||
? '拼多多商家会话已过期或失效 (43001)。请在浏览器刷新 mms.pinduoduo.com 页面重新登录,然后点击插件重新同步。'
|
||||
: `Cookie 校验失败: ${testResult.message}`
|
||||
|
||||
res.status(400).json({
|
||||
code: 400,
|
||||
message: `Cookie 校验失败: ${testResult.message}`,
|
||||
message: userGuidance,
|
||||
data: {
|
||||
valid: false,
|
||||
shopId: store.shopId,
|
||||
shopName: store.shopName,
|
||||
hasPassId,
|
||||
detail: testResult.message,
|
||||
},
|
||||
})
|
||||
return
|
||||
|
||||
@@ -40,12 +40,12 @@ test('decryptPddExtensionPayload successfully decrypts valid envelope', () => {
|
||||
assert.equal(decrypted.nonce, payload.nonce)
|
||||
})
|
||||
|
||||
test('decryptPddExtensionPayload rejects expired timestamp (> 60s)', () => {
|
||||
test('decryptPddExtensionPayload rejects expired timestamp (> 300s)', () => {
|
||||
const kp = getOrCreatePddRsaKeyPair()
|
||||
const payload = {
|
||||
shopId: 'store_test_01',
|
||||
cookie: 'PASS_ID=test_pass;',
|
||||
timestamp: Date.now() - 120_000, // 2 minutes ago
|
||||
timestamp: Date.now() - 600_000, // 10 minutes ago (> 300s)
|
||||
nonce: 'nonce-expired-' + Math.random().toString(36).slice(2),
|
||||
}
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ export interface PddDecryptedCookiePayload {
|
||||
nonce: string
|
||||
}
|
||||
|
||||
// In-memory Nonce cache for anti-replay attacks (TTL: 3 minutes)
|
||||
// In-memory Nonce cache for anti-replay attacks (TTL: 6 minutes)
|
||||
const seenNonces = new Map<string, number>()
|
||||
const NONCE_TTL_MS = 180_000
|
||||
const MAX_TIMESTAMP_SKEW_MS = 60_000
|
||||
const NONCE_TTL_MS = 360_000
|
||||
const MAX_TIMESTAMP_SKEW_MS = 300_000
|
||||
|
||||
function cleanupExpiredNonces() {
|
||||
const now = Date.now()
|
||||
|
||||
@@ -57,6 +57,19 @@
|
||||
- 客服日常只需像往常一样在浏览器中登录拼多多商家后台(`mms.pinduoduo.com`);
|
||||
- 只要商家后台处于登录状态,插件会在后台自动完成 Cookie 刷新与加密上报,服务端同步生效,全程无感。
|
||||
- **角标显示红色 ERR**:
|
||||
- 代表配对密钥错误或接口网络受阻,点击插件图标可查看具体错误原因。
|
||||
- **角标显示黄色 OFF / NO_CK**:
|
||||
- 点击插件图标查看具体错误原因。
|
||||
- 若提示「会话已过期 (43001)」或「未检测到 PASS_ID」:请在当前浏览器中打开 `mms.pinduoduo.com` 重新登录商家后台,然后点击插件【立即上传同步 Cookie】。
|
||||
- **角标显示黄色 OFF / NO_CK / 未登录**:
|
||||
- 代表当前浏览器未登录拼多多商家后台,只需在网页中正常登录即可自动恢复。
|
||||
|
||||
---
|
||||
|
||||
## 诊断中心与运行日志(v1.1.0 新增)
|
||||
|
||||
1. **实时日志控制台**:
|
||||
- 插件界面下方自带【运行日志】面板,记录包括 Cookie 嗅探、公钥获取、加密打包、服务端响应状态在内的全链路日志;
|
||||
- 关键令牌安全脱敏显示,敏感信息不落地、不泄露。
|
||||
2. **一键复制日志**:
|
||||
- 遇到任何异常时,点击【复制日志】即可将诊断上下文完整复制至剪贴板,方便快速排查;
|
||||
3. **手动指定 Cookie (选填)**:
|
||||
- 针对多浏览器环境或特定隔离场景,插件配置区支持手动粘贴 Cookie 覆盖,默认留空即采用全自动多维嗅探。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Pinduoduo Cookie Sync Background Service Worker (Manifest V3)
|
||||
* 拼多多 Cookie 自动嗅探与加密保活后台服务
|
||||
* 拼多多 Cookie 自动嗅探与加密保活后台服务 - 带结构化日志系统
|
||||
*/
|
||||
|
||||
try {
|
||||
@@ -10,6 +10,7 @@ try {
|
||||
}
|
||||
|
||||
const ALARM_NAME = 'pdd_cookie_sync_alarm'
|
||||
const MAX_LOGS_LIMIT = 100
|
||||
let debounceTimer = null
|
||||
|
||||
// 设置扩展图标角标状态
|
||||
@@ -20,18 +21,161 @@ function setExtensionBadge(text, color) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取拼多多全量 Cookie 字符串
|
||||
async function getPddCookieString() {
|
||||
// 格式化时间戳
|
||||
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 cookies = await chrome.cookies.getAll({ domain: 'pinduoduo.com' })
|
||||
if (!cookies || cookies.length === 0) {
|
||||
return ''
|
||||
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,
|
||||
}
|
||||
// 拼接成标准 Cookie 请求头格式
|
||||
return cookies.map((c) => `${c.name}=${c.value}`).join('; ')
|
||||
} catch (err) {
|
||||
console.error('读取拼多多 Cookie 异常:', err)
|
||||
return ''
|
||||
return {
|
||||
cookieStr: '',
|
||||
cookieCount: 0,
|
||||
keys: [],
|
||||
hasPassId: false,
|
||||
hasRckk: false,
|
||||
hasNano: false,
|
||||
isManual: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +185,7 @@ async function syncCookieToServer(isManual = false) {
|
||||
'serverUrl',
|
||||
'shopId',
|
||||
'syncToken',
|
||||
'manualCookie',
|
||||
'lastCookieHash',
|
||||
'lastSyncAt',
|
||||
])
|
||||
@@ -48,29 +193,66 @@ async function syncCookieToServer(isManual = false) {
|
||||
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: '请先打开插件弹窗完成服务器地址与店铺配对密钥配置',
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 读取当前 Cookie
|
||||
const cookieStr = await getPddCookieString()
|
||||
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',
|
||||
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()
|
||||
@@ -78,6 +260,7 @@ async function syncCookieToServer(isManual = false) {
|
||||
|
||||
// 若非手动强制触发,且 Cookie 未变动,且距离上次同步不足 1 小时,跳过重复上报
|
||||
if (!isManual && config.lastCookieHash === currentHash && now - lastSyncTime < 3600_000) {
|
||||
await addExtensionLog('info', '指纹比对', 'Cookie 无变动且未到周期,跳过重复网络上报')
|
||||
return {
|
||||
success: true,
|
||||
skipped: true,
|
||||
@@ -88,22 +271,25 @@ async function syncCookieToServer(isManual = false) {
|
||||
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) {
|
||||
throw new Error(`获取公钥失败 HTTP ${pkResp.status}`)
|
||||
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 公钥格式异常')
|
||||
throw new Error('服务端返回的 RSA 公钥格式异常 (缺少 publicKeySpki 字段)')
|
||||
}
|
||||
|
||||
// 4. 构建明文载荷(syncToken 在加密包内外双重保护)
|
||||
// 4. 构建明文载荷
|
||||
const payload = {
|
||||
shopId,
|
||||
syncToken,
|
||||
@@ -114,10 +300,13 @@ async function syncCookieToServer(isManual = false) {
|
||||
}
|
||||
|
||||
// 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: {
|
||||
@@ -127,10 +316,15 @@ async function syncCookieToServer(isManual = false) {
|
||||
body: JSON.stringify({ envelope }),
|
||||
})
|
||||
|
||||
const syncJson = await syncResp.json()
|
||||
const syncJson = await syncResp.json().catch(() => null)
|
||||
|
||||
if (!syncResp.ok || syncJson.code !== 0) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -141,13 +335,20 @@ async function syncCookieToServer(isManual = false) {
|
||||
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: 'Cookie 同步成功!服务端已完成在线校验并持久化生效',
|
||||
message: syncJson.message || 'Cookie 同步成功!服务端已完成在线校验并持久化生效',
|
||||
totalOrders: syncJson.data?.totalOrders,
|
||||
shopName: syncJson.data?.shopName,
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err)
|
||||
@@ -191,6 +392,7 @@ chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.alarms.create(ALARM_NAME, {
|
||||
periodInMinutes: 30,
|
||||
})
|
||||
addExtensionLog('info', '扩展安装/更新', '拼多多 Cookie 保活扩展 v1.1.0 后台服务启动')
|
||||
// 首次运行尝试同步一次
|
||||
syncCookieToServer(false).catch(() => null)
|
||||
})
|
||||
@@ -205,15 +407,33 @@ chrome.runtime.onMessage.addListener((req, _sender, sendResponse) => {
|
||||
}
|
||||
|
||||
if (req.action === 'get_status') {
|
||||
getPddCookieString().then((ck) => {
|
||||
chrome.storage.local.get(null).then((data) => {
|
||||
sendResponse({
|
||||
hasCookie: Boolean(ck && ck.length > 20),
|
||||
cookieLength: ck ? ck.length : 0,
|
||||
storage: data,
|
||||
})
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "拼多多 Cookie 自动保活同步",
|
||||
"version": "1.0.0",
|
||||
"description": "自动提取拼多多商家后台新鲜 Cookie 并通过端到端 RSA+AES-GCM 信封加密安全同步至业务总平台",
|
||||
"version": "1.1.0",
|
||||
"description": "自动嗅探拼多多商家后台新鲜 Cookie 并通过端到端 RSA+AES-GCM 信封加密安全同步至业务总平台",
|
||||
"permissions": [
|
||||
"cookies",
|
||||
"storage",
|
||||
"alarms"
|
||||
],
|
||||
"host_permissions": [
|
||||
"*://*.pinduoduo.com/*",
|
||||
"*://pinduoduo.com/*",
|
||||
"https://mms.pinduoduo.com/*",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
body {
|
||||
width: 360px;
|
||||
padding: 16px;
|
||||
width: 380px;
|
||||
padding: 14px;
|
||||
background: #f8fafc;
|
||||
color: #1e293b;
|
||||
font-size: 13px;
|
||||
@@ -22,9 +22,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.title {
|
||||
font-size: 15px;
|
||||
@@ -34,6 +34,14 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.version-tag {
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
background: #e0e7ff;
|
||||
color: #3730a3;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
@@ -43,43 +51,50 @@
|
||||
}
|
||||
.badge-ok { background: #dcfce7; color: #15803d; }
|
||||
.badge-err { background: #fee2e2; color: #b91c1c; }
|
||||
.badge-warn { background: #fef3c7; color: #b45309; }
|
||||
.badge-gray { background: #f1f5f9; color: #64748b; }
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 11px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.03);
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.label {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
.val {
|
||||
font-weight: 500;
|
||||
color: #0f172a;
|
||||
font-size: 12px;
|
||||
}
|
||||
.val-highlight {
|
||||
color: #2563eb;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 3px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #334155;
|
||||
}
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 7px 10px;
|
||||
padding: 6px 9px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
@@ -93,7 +108,7 @@
|
||||
.btn {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 8px 14px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
@@ -113,19 +128,100 @@
|
||||
background: #ffffff;
|
||||
border: 1px solid #cbd5e1;
|
||||
color: #334155;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #f8fafc;
|
||||
border-color: #94a3b8;
|
||||
}
|
||||
.btn-xs {
|
||||
width: auto;
|
||||
padding: 2px 7px;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
background: #f1f5f9;
|
||||
border: 1px solid #cbd5e1;
|
||||
color: #475569;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-xs:hover {
|
||||
background: #e2e8f0;
|
||||
color: #1e293b;
|
||||
}
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.msg-box {
|
||||
font-size: 12px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
margin-top: 8px;
|
||||
display: none;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.msg-success { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
|
||||
.msg-error { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
|
||||
.msg-warn { background: #fffbeb; color: #92400e; border: 1px solid #fde68a; }
|
||||
|
||||
/* 日志控制台样式 */
|
||||
.log-panel {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.log-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.log-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.log-box {
|
||||
background: #0f172a;
|
||||
color: #e2e8f0;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 11px;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.log-item {
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px dashed #1e293b;
|
||||
word-break: break-all;
|
||||
}
|
||||
.log-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.log-time {
|
||||
color: #64748b;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.log-tag-info { color: #38bdf8; font-weight: 600; margin-right: 4px; }
|
||||
.log-tag-success { color: #4ade80; font-weight: 600; margin-right: 4px; }
|
||||
.log-tag-warn { color: #facc15; font-weight: 600; margin-right: 4px; }
|
||||
.log-tag-error { color: #f87171; font-weight: 600; margin-right: 4px; }
|
||||
.log-action { color: #a78bfa; margin-right: 4px; }
|
||||
.log-empty {
|
||||
color: #64748b;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.security-tag {
|
||||
font-size: 11px;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
@@ -133,21 +229,13 @@
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.msg-box {
|
||||
font-size: 11px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 8px;
|
||||
display: none;
|
||||
}
|
||||
.msg-success { background: #f0fdf4; color: #166534; border: 1px solid #bbf7d0; }
|
||||
.msg-error { background: #fef2f2; color: #991b1b; border: 1px solid #fecaca; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span>拼多多 Cookie 保活</span>
|
||||
<span class="version-tag">v1.1.0</span>
|
||||
</div>
|
||||
<div id="statusBadge" class="badge badge-gray">未检测</div>
|
||||
</div>
|
||||
@@ -168,7 +256,7 @@
|
||||
</div>
|
||||
<div class="row" id="orderCountRow" style="display: none;">
|
||||
<span class="label">在售/历史单:</span>
|
||||
<span id="orderCountDisplay" class="val" style="color: #2563eb;">0 笔</span>
|
||||
<span id="orderCountDisplay" class="val val-highlight">0 笔</span>
|
||||
</div>
|
||||
<div id="statusMsg" class="msg-box"></div>
|
||||
</div>
|
||||
@@ -176,17 +264,34 @@
|
||||
<!-- 手动立即同步按钮 -->
|
||||
<button id="btnSyncNow" class="btn btn-primary">立即上传同步 Cookie</button>
|
||||
|
||||
<!-- 运行与诊断日志面板 -->
|
||||
<div class="log-panel">
|
||||
<div class="log-header">
|
||||
<div class="log-title">
|
||||
<span>📋 运行日志</span>
|
||||
<span id="logCountBadge" style="font-size: 10px; color: #64748b;">(0条)</span>
|
||||
</div>
|
||||
<div class="log-actions">
|
||||
<button id="btnCopyLogs" class="btn-xs" title="复制全部日志到剪贴板">复制日志</button>
|
||||
<button id="btnClearLogs" class="btn-xs" title="清空历史日志">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="logBox" class="log-box">
|
||||
<div class="log-empty">暂无运行日志</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 配置折叠区 -->
|
||||
<div style="margin-top: 12px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
||||
<span style="font-weight: 600; font-size: 12px; color: #475569;">连接配置</span>
|
||||
<a href="#" id="btnToggleImport" style="font-size: 11px; color: #2563eb; text-decoration: none;">一键导入配置 JSON</a>
|
||||
<span style="font-weight: 600; font-size: 12px; color: #475569;">⚙️ 连接配置</span>
|
||||
<a href="#" id="btnToggleImport" style="font-size: 11px; color: #2563eb; text-decoration: none;">导入配置 JSON</a>
|
||||
</div>
|
||||
|
||||
<!-- 导入 JSON 框(默认隐藏) -->
|
||||
<div id="importArea" style="display: none; margin-bottom: 10px;">
|
||||
<textarea id="importJsonInput" rows="3" placeholder="在此粘贴后台复制的完整配置 JSON..."></textarea>
|
||||
<button id="btnApplyImport" class="btn btn-secondary" style="margin-top: 4px; padding: 5px;">解析并填充</button>
|
||||
<button id="btnApplyImport" class="btn btn-secondary" style="margin-top: 4px; padding: 5px; font-size: 11px;">解析并填充输入框</button>
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-bottom: 8px;">
|
||||
@@ -198,10 +303,17 @@
|
||||
<label for="inputShopId">店铺标识 (Shop ID)</label>
|
||||
<input type="text" id="inputShopId" placeholder="例如:store_pdd_01">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<div class="form-group">
|
||||
<label for="inputSyncToken">专属配对密钥 (Sync Token)</label>
|
||||
<input type="password" id="inputSyncToken" placeholder="例如:pdd_tok_xxx">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label for="inputManualCookie" style="display: flex; justify-content: space-between;">
|
||||
<span>手动指定 Cookie (选填)</span>
|
||||
<span style="color: #94a3b8; font-weight: normal; font-size: 10px;">留空则自动提取</span>
|
||||
</label>
|
||||
<textarea id="inputManualCookie" rows="2" placeholder="默认自动嗅探浏览器 Cookie;若需要强制覆盖可在此手动粘贴完整 Cookie"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="btnSaveConfig" class="btn btn-secondary">保存配置并重连</button>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Pinduoduo Cookie Sync Extension Popup Script
|
||||
* 拼多多 Cookie 自动保活弹窗界面交互与实时日志引擎
|
||||
*/
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
@@ -21,6 +22,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
const inputServerUrl = document.getElementById('inputServerUrl')
|
||||
const inputShopId = document.getElementById('inputShopId')
|
||||
const inputSyncToken = document.getElementById('inputSyncToken')
|
||||
const inputManualCookie = document.getElementById('inputManualCookie')
|
||||
|
||||
const logBox = document.getElementById('logBox')
|
||||
const logCountBadge = document.getElementById('logCountBadge')
|
||||
const btnCopyLogs = document.getElementById('btnCopyLogs')
|
||||
const btnClearLogs = document.getElementById('btnClearLogs')
|
||||
|
||||
let cachedLogs = []
|
||||
|
||||
function formatTime(timestamp) {
|
||||
if (!timestamp) return '未同步'
|
||||
@@ -34,27 +43,94 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
return `${m}-${day} ${h}:${min}:${s}`
|
||||
}
|
||||
|
||||
function showMsg(text, isError = false) {
|
||||
function showMsg(text, type = 'success') {
|
||||
statusMsg.style.display = 'block'
|
||||
statusMsg.className = isError ? 'msg-box msg-error' : 'msg-box msg-success'
|
||||
if (type === 'error') {
|
||||
statusMsg.className = 'msg-box msg-error'
|
||||
} else if (type === 'warn') {
|
||||
statusMsg.className = 'msg-box msg-warn'
|
||||
} else {
|
||||
statusMsg.className = 'msg-box msg-success'
|
||||
}
|
||||
statusMsg.textContent = text
|
||||
}
|
||||
|
||||
// 加载并渲染运行日志
|
||||
function refreshLogs() {
|
||||
chrome.runtime.sendMessage({ action: 'get_logs' }, (res) => {
|
||||
const logs = Array.isArray(res?.logs) ? res.logs : []
|
||||
cachedLogs = logs
|
||||
logCountBadge.textContent = `(${logs.length}条)`
|
||||
|
||||
if (logs.length === 0) {
|
||||
logBox.innerHTML = '<div class="log-empty">暂无运行日志</div>'
|
||||
return
|
||||
}
|
||||
|
||||
let html = ''
|
||||
for (const item of logs) {
|
||||
let tagClass = 'log-tag-info'
|
||||
let tagText = '[INFO]'
|
||||
if (item.level === 'success') {
|
||||
tagClass = 'log-tag-success'
|
||||
tagText = '[OK]'
|
||||
} else if (item.level === 'warn') {
|
||||
tagClass = 'log-tag-warn'
|
||||
tagText = '[WARN]'
|
||||
} else if (item.level === 'error') {
|
||||
tagClass = 'log-tag-error'
|
||||
tagText = '[ERR]'
|
||||
}
|
||||
|
||||
const detailsHtml = item.details
|
||||
? `<div style="color: #94a3b8; font-size: 10px; margin-left: 12px;">↳ ${escapeHtml(item.details)}</div>`
|
||||
: ''
|
||||
|
||||
html += `
|
||||
<div class="log-item">
|
||||
<span class="log-time">${item.time || ''}</span>
|
||||
<span class="${tagClass}">${tagText}</span>
|
||||
<span class="log-action">[${escapeHtml(item.action || '系统')}]</span>
|
||||
<span>${escapeHtml(item.message || '')}</span>
|
||||
${detailsHtml}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
logBox.innerHTML = html
|
||||
})
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return ''
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function refreshStatusView(statusData) {
|
||||
const storage = statusData?.storage || {}
|
||||
const hasCookie = Boolean(statusData?.hasCookie)
|
||||
const hasPassId = Boolean(statusData?.hasPassId)
|
||||
const cookieCount = statusData?.cookieCount || 0
|
||||
|
||||
// 1. MMS 登录状态
|
||||
if (hasCookie) {
|
||||
mmsStatus.textContent = `已在线 (长度: ${statusData.cookieLength || 0})`
|
||||
if (hasPassId) {
|
||||
mmsStatus.textContent = `已就绪 (包含 PASS_ID,共 ${cookieCount} 项)`
|
||||
mmsStatus.style.color = '#15803d'
|
||||
} else if (hasCookie) {
|
||||
mmsStatus.textContent = `未含 PASS_ID (共 ${cookieCount} 项)`
|
||||
mmsStatus.style.color = '#d97706'
|
||||
} else {
|
||||
mmsStatus.textContent = '未登录 / 无有效Cookie'
|
||||
mmsStatus.textContent = '未检测到拼多多登录 Cookie'
|
||||
mmsStatus.style.color = '#b91c1c'
|
||||
}
|
||||
|
||||
// 2. 店铺标识
|
||||
if (storage.shopId) {
|
||||
if (storage.shopName) {
|
||||
shopDisplay.textContent = `${storage.shopName} (${storage.shopId || ''})`
|
||||
} else if (storage.shopId) {
|
||||
shopDisplay.textContent = storage.shopId
|
||||
} else {
|
||||
shopDisplay.textContent = '未配置'
|
||||
@@ -75,21 +151,25 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
orderCountRow.style.display = 'none'
|
||||
}
|
||||
|
||||
// 5. 顶部角标与错误消息
|
||||
// 5. 顶部角标与状态消息
|
||||
if (!storage.serverUrl || !storage.shopId || !storage.syncToken) {
|
||||
statusBadge.className = 'badge badge-gray'
|
||||
statusBadge.textContent = '待配对'
|
||||
} else if (!hasPassId && !storage.manualCookie) {
|
||||
statusBadge.className = 'badge badge-warn'
|
||||
statusBadge.textContent = '未登录MMS'
|
||||
showMsg('未检测到拼多多后台核心会话 (PASS_ID)。请在当前浏览器登录 mms.pinduoduo.com 并刷新后台页面', 'warn')
|
||||
} else if (storage.lastStatus === 'ok') {
|
||||
statusBadge.className = 'badge badge-ok'
|
||||
statusBadge.textContent = '运行正常'
|
||||
if (storage.lastMessage) {
|
||||
showMsg(storage.lastMessage, false)
|
||||
showMsg(storage.lastMessage, 'success')
|
||||
}
|
||||
} else if (storage.lastStatus === 'error') {
|
||||
statusBadge.className = 'badge badge-err'
|
||||
statusBadge.textContent = '同步异常'
|
||||
if (storage.lastMessage) {
|
||||
showMsg(storage.lastMessage, true)
|
||||
showMsg(storage.lastMessage, 'error')
|
||||
}
|
||||
} else {
|
||||
statusBadge.className = 'badge badge-gray'
|
||||
@@ -100,12 +180,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
inputServerUrl.value = storage.serverUrl || ''
|
||||
inputShopId.value = storage.shopId || ''
|
||||
inputSyncToken.value = storage.syncToken || ''
|
||||
inputManualCookie.value = storage.manualCookie || ''
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
// 初始化加载状态与日志
|
||||
chrome.runtime.sendMessage({ action: 'get_status' }, (res) => {
|
||||
refreshStatusView(res)
|
||||
})
|
||||
refreshLogs()
|
||||
|
||||
// 切换一键导入框
|
||||
btnToggleImport.addEventListener('click', (e) => {
|
||||
@@ -127,10 +209,11 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (parsed.serverUrl) inputServerUrl.value = parsed.serverUrl
|
||||
if (parsed.shopId) inputShopId.value = parsed.shopId
|
||||
if (parsed.syncToken) inputSyncToken.value = parsed.syncToken
|
||||
if (parsed.cookie) inputManualCookie.value = parsed.cookie
|
||||
importArea.style.display = 'none'
|
||||
showMsg('配置解析成功,请点击下方【保存配置】生效', false)
|
||||
showMsg('配置解析成功,请点击下方【保存配置并重连】生效', 'success')
|
||||
} catch {
|
||||
showMsg('JSON 格式不正确,请确保完整复制后台内容', true)
|
||||
showMsg('JSON 格式不正确,请确保完整复制管理系统后台弹窗中的配置', 'error')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -139,9 +222,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
const serverUrl = inputServerUrl.value.trim()
|
||||
const shopId = inputShopId.value.trim()
|
||||
const syncToken = inputSyncToken.value.trim()
|
||||
const manualCookie = inputManualCookie.value.trim()
|
||||
|
||||
if (!serverUrl || !shopId || !syncToken) {
|
||||
showMsg('请完整填写服务器地址、店铺标识和配对密钥', true)
|
||||
showMsg('请完整填写服务器地址、店铺标识和配对密钥', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -149,9 +233,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
serverUrl,
|
||||
shopId,
|
||||
syncToken,
|
||||
manualCookie,
|
||||
})
|
||||
|
||||
showMsg('配置已保存!正在尝试初次加密同步...', false)
|
||||
showMsg('配置已保存!正在发起加密上传与网关校验...', 'success')
|
||||
triggerSync()
|
||||
})
|
||||
|
||||
@@ -165,17 +250,53 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
btnSyncNow.textContent = '立即上传同步 Cookie'
|
||||
|
||||
if (res && res.success) {
|
||||
showMsg(res.message || '同步成功!', false)
|
||||
showMsg(res.message || '同步成功!', 'success')
|
||||
} else {
|
||||
showMsg(res?.message || '同步失败,请检查网络或配置', true)
|
||||
showMsg(res?.message || '同步失败,请检查网络或配置', 'error')
|
||||
}
|
||||
|
||||
// 刷新界面
|
||||
// 刷新界面与日志
|
||||
chrome.runtime.sendMessage({ action: 'get_status' }, (s) => {
|
||||
refreshStatusView(s)
|
||||
})
|
||||
refreshLogs()
|
||||
})
|
||||
}
|
||||
|
||||
btnSyncNow.addEventListener('click', triggerSync)
|
||||
|
||||
// 复制日志功能
|
||||
btnCopyLogs.addEventListener('click', async () => {
|
||||
if (!cachedLogs || cachedLogs.length === 0) {
|
||||
alert('当前没有日志可复制')
|
||||
return
|
||||
}
|
||||
|
||||
const textLines = cachedLogs.map((item) => {
|
||||
const detailStr = item.details ? ` | 详情: ${item.details}` : ''
|
||||
return `[${item.time}] [${item.level.toUpperCase()}] [${item.action}] ${item.message}${detailStr}`
|
||||
})
|
||||
const fullText = `=== 拼多多保活插件运行日志 (共 ${cachedLogs.length} 条) ===\n` + textLines.join('\n')
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(fullText)
|
||||
const origText = btnCopyLogs.textContent
|
||||
btnCopyLogs.textContent = '已复制!'
|
||||
setTimeout(() => {
|
||||
btnCopyLogs.textContent = origText
|
||||
}, 1500)
|
||||
} catch (err) {
|
||||
console.error('复制日志到剪贴板失败:', err)
|
||||
alert('复制失败,请手动选择复制')
|
||||
}
|
||||
})
|
||||
|
||||
// 清空日志功能
|
||||
btnClearLogs.addEventListener('click', () => {
|
||||
if (confirm('确定要清空扩展的历史运行日志吗?')) {
|
||||
chrome.runtime.sendMessage({ action: 'clear_logs' }, () => {
|
||||
refreshLogs()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1311,8 +1311,15 @@ export function PddPlatformPanel() {
|
||||
<li>
|
||||
在 Chrome / Edge 浏览器打开:<Text code>chrome://extensions</Text>
|
||||
</li>
|
||||
<li>开启右上角【开发者模式】,点击左上角【加载已解压的扩展程序】并选择该目录;</li>
|
||||
<li>点击浏览器工具栏扩展图标,点击【导入配置】并粘贴上述配置,保存即完成绑定!</li>
|
||||
<li>
|
||||
开启右上角【开发者模式】,点击左上角【加载已解压的扩展程序】并选择该目录(版本
|
||||
v1.1.0);
|
||||
</li>
|
||||
<li>点击浏览器工具栏扩展图标,点击【导入配置 JSON】并粘贴上述配置,点击保存;</li>
|
||||
<li>
|
||||
在当前浏览器中保持登录拼多多商家后台(<Text code>mms.pinduoduo.com</Text>
|
||||
),插件将全自动保活;若遇异常可随时在插件面板查看【运行日志】。
|
||||
</li>
|
||||
</ol>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user