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. 日志链路打通:将页面防休眠、弹窗拦截与周期刷新事件接入扩展日志控制台。
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* Pinduoduo Merchant Page Active Keepalive & Anti-Inactivity Content Script
|
||||
* 拼多多商家后台页面主动保活、防休眠心跳与超时弹窗秒级拦截注入脚本
|
||||
*/
|
||||
|
||||
;(function () {
|
||||
'use strict'
|
||||
|
||||
const TAG = '[PDD-KEEPALIVE]'
|
||||
const HEARTBEAT_INTERVAL_MS = 2 * 60 * 1000 // 每 2 分钟执行一次主动心跳与会话续期
|
||||
const AUTO_RELOAD_INTERVAL_MS = 25 * 60 * 1000 // 每 25 分钟自动平滑刷新一次页面以保证全状态新鲜
|
||||
let lastUserActivityTime = Date.now()
|
||||
let isReloading = false
|
||||
|
||||
console.log(`${TAG} 拼多多页面主动保活与防休眠助手已加载:`, window.location.href)
|
||||
|
||||
// 辅助:向 background 记录运行日志
|
||||
function reportLog(level, action, message, details = null) {
|
||||
try {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'content_log',
|
||||
level,
|
||||
module: '页面保活',
|
||||
actionName: action,
|
||||
message,
|
||||
details,
|
||||
})
|
||||
} catch {
|
||||
// 忽略通信异常
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 监听真实用户操作,记录活跃时间戳
|
||||
const userEvents = ['mousemove', 'mousedown', 'keydown', 'scroll', 'touchstart']
|
||||
userEvents.forEach((evt) => {
|
||||
window.addEventListener(
|
||||
evt,
|
||||
() => {
|
||||
lastUserActivityTime = Date.now()
|
||||
},
|
||||
{ passive: true },
|
||||
)
|
||||
})
|
||||
|
||||
// 2. 模拟轻量用户活动(重置拼多多前端页面的空闲计时器)
|
||||
function dispatchFakeUserActivity() {
|
||||
try {
|
||||
const evt = new MouseEvent('mousemove', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: Math.floor(Math.random() * 200) + 100,
|
||||
clientY: Math.floor(Math.random() * 200) + 100,
|
||||
})
|
||||
document.dispatchEvent(evt)
|
||||
window.dispatchEvent(new Event('focus'))
|
||||
} catch (e) {
|
||||
console.warn(`${TAG} 模拟微动作异常:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 发送轻量级静默 API 心跳(主动向拼多多服务端续期 PASS_ID 与会话 TTL)
|
||||
async function sendSilentSessionHeartbeat() {
|
||||
try {
|
||||
dispatchFakeUserActivity()
|
||||
|
||||
// 选择拼多多商家后台最轻量的只读接口进行探测与续期
|
||||
const endpoints = [
|
||||
'/mangkhut/mms/recentOrderList?page=1&pageSize=1',
|
||||
'/home/',
|
||||
'/orders/list',
|
||||
]
|
||||
const targetEndpoint = endpoints[Math.floor(Math.random() * endpoints.length)]
|
||||
|
||||
const resp = await fetch(targetEndpoint, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
},
|
||||
})
|
||||
|
||||
if (resp.ok) {
|
||||
console.log(`${TAG} 服务端会话静默续期成功 (${targetEndpoint})`)
|
||||
} else if (resp.status === 401 || resp.status === 403) {
|
||||
reportLog('warn', '会话预警', `页面心跳返回 HTTP ${resp.status},可能需要重新登录`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`${TAG} 发送静默续期心跳异常:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 实时拦截超时提示遮罩弹窗(秒级自动点击刷新/恢复)
|
||||
function scanAndBustInactivityModal() {
|
||||
if (isReloading) return
|
||||
|
||||
// 拼多多超时弹窗典型文案特征
|
||||
const triggerKeywords = [
|
||||
'当前页面停留时间过长',
|
||||
'为了避免数据延迟',
|
||||
'建议刷新一次页面',
|
||||
'停留时间过长',
|
||||
]
|
||||
|
||||
// 查找所有可能的弹窗/Modal 容器
|
||||
const modalContainers = document.querySelectorAll(
|
||||
'.ant-modal, .pdd-modal, [class*="modal"], [class*="dialog"], [role="dialog"]',
|
||||
)
|
||||
|
||||
for (const container of modalContainers) {
|
||||
const text = container.textContent || ''
|
||||
const hasKeyword = triggerKeywords.some((kw) => text.includes(kw))
|
||||
if (!hasKeyword) continue
|
||||
|
||||
console.warn(`${TAG} 🚨 捕获到拼多多超时休眠弹窗!立即执行秒级自动恢复...`)
|
||||
reportLog('warn', '弹窗拦截', '捕获到「页面停留时间过长」弹窗,立即自动点击刷新续期会话')
|
||||
|
||||
// 寻找弹窗内的「刷新」或确定按钮
|
||||
const buttons = container.querySelectorAll('button, a, [role="button"]')
|
||||
let clicked = false
|
||||
|
||||
for (const btn of buttons) {
|
||||
const btnText = (btn.textContent || '').trim()
|
||||
if (btnText.includes('刷新') || btnText.includes('确定') || btnText.includes('知道了')) {
|
||||
btn.click()
|
||||
clicked = true
|
||||
console.log(`${TAG} ✅ 已自动触发弹窗内【${btnText}】按钮`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!clicked) {
|
||||
// 若未找到合适按钮,直接调用页面无感平滑重载
|
||||
isReloading = true
|
||||
console.log(`${TAG} 未找到确定按钮,执行 location.reload()`)
|
||||
setTimeout(() => {
|
||||
window.location.reload()
|
||||
}, 500)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 全局文本扫描(兜底非标准 modal 结构)
|
||||
const allDivs = document.querySelectorAll('div')
|
||||
for (const div of allDivs) {
|
||||
if (div.children.length === 0 && div.textContent) {
|
||||
if (div.textContent.includes('当前页面停留时间过长')) {
|
||||
const parent = div.closest('div[style*="z-index"]') || div.parentElement
|
||||
const btn = parent ? parent.querySelector('button') : null
|
||||
if (btn) {
|
||||
console.warn(`${TAG} 兜底捕获超时弹窗,触发点击`)
|
||||
btn.click()
|
||||
} else {
|
||||
isReloading = true
|
||||
window.location.reload()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. DOM 变化观察器(毫秒级感知弹窗挂载)
|
||||
const observer = new MutationObserver(() => {
|
||||
scanAndBustInactivityModal()
|
||||
})
|
||||
|
||||
if (document.body) {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 6. 定时执行静默会话心跳
|
||||
setInterval(() => {
|
||||
sendSilentSessionHeartbeat()
|
||||
scanAndBustInactivityModal()
|
||||
}, HEARTBEAT_INTERVAL_MS)
|
||||
|
||||
// 7. 定时平滑刷新兜底(每 25 分钟:若用户 10 分钟未操作,自动安全刷新当前页面刷新凭证)
|
||||
setInterval(() => {
|
||||
const idleTime = Date.now() - lastUserActivityTime
|
||||
// 如果页面处于静置状态(超过 5 分钟未打字或点击),执行一次平滑刷新保持全局新鲜
|
||||
if (idleTime > 5 * 60 * 1000 && !isReloading) {
|
||||
console.log(`${TAG} 页面静置已超周期,执行安全平滑重载以更新全量会话凭证`)
|
||||
reportLog('info', '周期刷新', '后台页面静置超 25 分钟,自动平滑重载以保持 Token 绝对新鲜')
|
||||
isReloading = true
|
||||
window.location.reload()
|
||||
}
|
||||
}, AUTO_RELOAD_INTERVAL_MS)
|
||||
|
||||
// 8. 响应来自 Background 的保活 Ping
|
||||
chrome.runtime.onMessage.addListener((req, _sender, sendResponse) => {
|
||||
if (req.action === 'ping_keepalive') {
|
||||
sendSilentSessionHeartbeat()
|
||||
scanAndBustInactivityModal()
|
||||
sendResponse({ status: 'alive', timestamp: Date.now() })
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化首次扫描与心跳
|
||||
setTimeout(() => {
|
||||
sendSilentSessionHeartbeat()
|
||||
scanAndBustInactivityModal()
|
||||
reportLog('info', '页面接入', '拼多多商家后台已成功注入主动保活与防退出引擎')
|
||||
}, 3000)
|
||||
})()
|
||||
Reference in New Issue
Block a user