const express = require("express"); const router = express.Router(); const {jsdomFromText, browser} = require("sdenv"); const {Script} = require("node:vm"); const crypto = require("node:crypto") const AreaNameEnum = require('../enums/AreaNameEnum'); const Store = require("../utils/Store"); const JsUtil = require('../utils/JsUtil'); let store = new Store(); let sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); router.post('/rsCookie', async (req, res) => { let uuid = crypto.randomUUID().replace(/-/g, ""); let start = new Date(); try { let url = req.body['url']; let areaName = req.body['areaName']; let htmlStr = req.body['htmlBase64']; let cookie = req.body['cookieBase64']; let userAgent = req.body['userAgentBase64']; console.log(`${uuid};接收到 ${areaName} 请求:${url}`) if (url == null || url === '') { return res.status(500).send('error url') } if (htmlStr == null || htmlStr === '') { return res.status(500).send('error html') } let cookies = await handle(url, uuid, areaName, Buffer.from(htmlStr, 'base64').toString('utf-8'), cookie != null && cookie !== "" ? Buffer.from(cookie, 'base64').toString('utf-8') : null, userAgent != null && userAgent !== "" ? Buffer.from(userAgent, 'base64').toString('utf-8') : null,) console.log(`${uuid};返回cookie ---->`, cookies) res.status(200).send(cookies); } catch (e) { console.error(e.stack) return res.status(500).send(e.message) } finally { console.log(`${uuid};rsCookie ${new Date() - start} ms`) } }) function CookieStr2List(cookies) { let list = [] for (let cookie of cookies.trim().split("; ")) { list.push(cookie); } return list } async function handle(url, uuid, areaName, htmlStr, cookie, userAgent) { // 获取 origin let baseUrl = new URL(url).origin; // 初始化 jsDom 和 cookieJar const [jsDom, cookieJar] = jsdomFromText({ url: url, referrer: url, userAgent: userAgent, contentType: "text/html", runScripts: "outside-only", // runScripts: 'dangerously'/'outside-only' }) // 加载dom let dom = await jsDom(htmlStr); console.log(`${uuid};html 加载长度--->`, dom.serialize().length) window = dom.window // ------------------------------------------------ param ---------------------------------------------------------- // 标志判断cookie是否生成 window[uuid] = false // ------------------------------------------------ function ------------------------------------------------------- // js执行成功后会跳转页面 会触发onbeforeunload钩子 window.onbeforeunload = async (url) => { console.debug(`${url} 页面回调完成`); window[uuid] = true } // 设置 cookie if (cookie != null) { let cookieList = CookieStr2List(cookie); console.log(`${uuid};cookie 加载长度--->`, cookieList.length, baseUrl) for (let i = 0; i < cookieList.length; i++) { cookieJar.setCookieSync(cookieList[i], baseUrl); } } // 方案1 通过监听cookie 判断cookie是否生成 const superSetCookie = cookieJar.setCookie; // 设置 setCookie 代理 cookieJar.setCookie = function (cookie, currentUrl, options, callback) { console.debug(`${uuid};正在设置 Cookie:`, cookie, currentUrl); let call = superSetCookie.call(this, cookie, currentUrl, options, callback); // 设置标志可取标志 if (cookie.includes('YqQ7a3SgknV8P')) { window[uuid] = true } return call; }; // ------------------------------------------------ 实例化浏览器 ----------------------------------------------------- browser(window, 'chrome'); // 加载js let js = await JsUtil.loadJs(window.document, areaName, cookie); console.log(`${uuid};js 加载长度--->`, js.length) // 执行 js let script = new Script(js); let internalVMContext = dom.getInternalVMContext(); script.runInContext(internalVMContext, {timeout: 1000}); // 等待cookie 被设置 for (let i = 0; i < 10; i++) { if (window[uuid]) { break; } await sleep(100) } // 获取cookie let resCookie = cookieJar.getCookieStringSync(baseUrl); // 关闭 dom.window.close() return resCookie; } /** * 方案1 * 利用rs特性 js加载完成后会刷新页面 * 利用 window.onbeforeunload 判断页面是否执行完毕 * @param window * @param cookieJar * @param uuid * @returns {Promise<*|null>} */ function scheme1_before(window, cookieJar, uuid) { window.onbeforeunload = async (url) => { let baseUrl = new URL(url).origin; const cookies = cookieJar.getCookieStringSync(baseUrl); console.debug(`${url} 页面回调生成cookie:`, cookies); store.set(uuid, cookies) // window.close(); } } /** * 方案1 * @param window * @param cookieJar * @param uuid * @returns {Promise<*|null>} */ async function scheme1_after(window, cookieJar, uuid) { // 等待 onbeforeunload 钩子触发后的回掉 let val = await store.waitGetAndDelete(uuid, 100, 10) if (val != null) { return val; } return null; } /** * 监听cookie出现指定cookie * @param window * @param cookieJar * @param uuid * @returns {Promise} */ function scheme2_before(window, cookieJar, uuid) { // window[uuid] = false; const superSetCookie = cookieJar.setCookie; // 设置 setCookie 代理 cookieJar.setCookie = function (cookie, currentUrl, options, callback) { console.log(`${uuid};正在设置 Cookie:`, cookie, currentUrl); return superSetCookie.call(this, cookie, currentUrl, options, callback); // let cookieStringSync = super.getCookieStringSync(); // console.log(cookieStringSync) // if (cookie.includes(key)) { // // 设置标志可取标志 // window[uuid] = true // } // return call; }; } /** * 方案2 * @param window * @param cookieJar * @param key * @param uuid * @returns {Promise} */ async function scheme2_after(window, cookieJar, key, uuid) { let sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); for (let i = 0; i < 10; i++) { let cookieStringSync = cookieJar.getCookieStringSync(); if (cookieStringSync.includes(key)) { return cookieStringSync; } await sleep(100) } return null; } /** * 方案3 根据cookie现有的数量判断 * @param window * @param cookieJar * @param baseUrl * @param uuid */ function scheme3_before(window, cookieJar, baseUrl, uuid) { const initCookie = cookieJar.getCookieStringSync(baseUrl); window[uuid + 'CookieSize'] = initCookie != null ? initCookie.trim().split("; ").length : 0; } /** * 方案3 * @param window * @param cookieJar * @param baseUrl * @param uuid * @returns {Promise} */ async function scheme3_after(window, cookieJar, baseUrl, uuid) { let initCookieLength = window[uuid + 'CookieSize'] let sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); for (let i = 0; i < 10; i++) { let cookieStringSync = cookieJar.getCookieStringSync(baseUrl); let cookies = cookieStringSync != null ? cookieStringSync.trim().split("; ").length : 0; if (cookies > initCookieLength) { return cookieStringSync; } await sleep(100) } return null; } // 测试 handle handle('https://app.yunnan.chinatax.gov.cn/xxmh/html/dfts/index_frame.html', crypto.randomUUID().replace(/-/g, ""), "yunnan", '\n' + '\n' + '\n' + '\n' + '\n' + '\n' + '\n' + '\n' + '', '94UkHqjsnpGlO=60fhuKFCJ238ngXbeFvuNpiZyZvK2HCOsbkRBi7WAk8COuYc2Jv1.twWrOJ3p.Pcu_rE0FYzg9PJ4CjTocIPJHPG; tpass_tc8td8ea8edn4be483f5a9ktd52599n5=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjY2N2ZlYmU5YTM3MDQ3ZTViN2NlYmQ2MDYxYmU5NDNlIn0.zgV3bl3lf54MfLiNQ4enrhpjn0amydMPChZ2Z1fGry9oYEvE5_PbJWTczoFDdBLFyTrPEEKhsMwrkK4DF_p1Tg; tpass_m7d4a8meca944da79ppdb4bef7d7795a=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImZlMDZhZjljOWZmMTQ2MmM5MmMwZjBiZjIzZWIxMGZiIn0.tlg4c2FMwvQJ8x8f6G7lZEhVQ1x6qJEs2GkykWlmbRZD7JZOJ7NO88dQToMifIxDvnqZ7jZbbLOv9w8Mt4eVDg; dzswjCookie=3829a119fd1852b650f8f854393032dd; SF_cookie_17=31743519; YqQ7a3SgknV8O=60FAYNp0FZHRnRctdl7QyufG1rNQ_oSO8eM3AZGHqUUiLrNOzIHJ5oR7XIz58K5Li3pPYsoQGEX1OVCEWhEx_poA; SYS_CHANNEL_ID=J4; JSESSIONID=7ED72361D29F104CFF195ECD33D6D23E; channelId=J4; DZSWJ_TGC=7ED72361D29F104CFF195ECD33D6D23E', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' ) module.exports = router