You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

169 lines
5.3 KiB

const express = require("express");
const router = express.Router();
const {jsdomFromText, browser} = require("sdenv");
const {Script} = require("node:vm");
const fs = require("node:fs");
const crypto = require("node:crypto")
const AreaNameEnum = require('../enums/AreaNameEnum');
const Store = require("../utils/Store");
let store = new Store();
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 jsStr = req.body['jsBase64'];
let cookie = req.body['cookieBase64'];
let userAgent = req.body['userAgentBase64'];
console.log(`${uuid};接收到 ${areaName} 请求:${url}`)
fs.writeFileSync(`./back/${uuid}.url`, `${url}\n ${userAgent}\n ${cookie} \n`);
if (url == null || url === '') {
return res.status(500).send('error url')
}
if (htmlStr == null || htmlStr === '') {
return res.status(500).send('error html')
}
let jsText;
let loadHtmlJs;
if (jsStr == null || jsStr === "") {
let jsPath = AreaNameEnum.getByAreaName(areaName).JS_FILE
if (jsPath == null) {
console.error('未找到js文件')
return res.send('未找到js文件')
}
jsText = fs.readFileSync(jsPath).toString('utf8');
loadHtmlJs = true;
} else {
jsText = Buffer.from(jsStr, 'base64').toString('utf-8')
loadHtmlJs = false
}
let cookies = await handle(url,
Buffer.from(htmlStr, 'base64').toString('utf-8'),
jsText,
cookie != null && cookie !== "" ? Buffer.from(cookie, 'base64').toString('utf-8') : null,
userAgent != null && userAgent !== "" ? Buffer.from(userAgent, 'base64').toString('utf-8') : null,
uuid, loadHtmlJs)
console.log(`${uuid};返回cookie ---->`, cookies.split('; '))
res.status(200).send(cookies);
} catch (e) {
console.error(e.message)
return res.status(500).send(e.message)
} finally {
console.log(`${uuid};rsCookie ${new Date() - start} ms`)
}
})
function loadJs(window, jsText) {
// 加载js
let js = '';
// 加载 页面上的js
const allScript = window.document.querySelectorAll('script[r="m"]');
for (let i = 0; i < allScript.length; i++) {
const script = allScript[i];
let attr = script.textContent;
if (attr) {
js += attr
} else {
js += jsText
}
js += ";\n"
}
return js;
}
function CookieStr2List(cookies) {
let list = []
for (let cookie of cookies.trim().split("; ")) {
list.push(cookie);
}
return list
}
async function handle(url, htmlStr, jsText, cookie, userAgent, uuid, loadHtmlJs) {
// 获取 origin
let baseUrl = new URL(url).origin;
// 初始化 jsDom 和 cookieJar
const [jsDom, cookieJar] = await jsdomFromText({
url: url,
referrer: url,
userAgent: userAgent,
contentType: "text/html",
runScripts: "outside-only", // runScripts: 'dangerously'/'outside-only'
})
// 设置 cookie
if (cookie != null) {
let cookieList = CookieStr2List(cookie);
console.log(`${uuid};cookie 加载长度--->`, cookieList, baseUrl)
fs.writeFileSync(`./back/${uuid}.cookie`, cookie)
for (let i = 0; i < cookieList.length; i++) {
cookieJar.setCookieSync(cookieList[i], baseUrl);
}
}
// 加载dom
let dom = await jsDom(htmlStr);
// console.log('html 加载长度--->', dom.serialize())
console.log(`${uuid};html 加载长度--->`, dom.serialize().length)
fs.writeFileSync(`./back/${uuid}.html`, dom.serialize())
window = dom.window
// js执行成功后的瑞树会跳转页面 会触发onbeforeunload钩子
window.onbeforeunload = async (url) => {
const cookies = cookieJar.getCookieStringSync(baseUrl);
// console.debug(`${url} 生成cookie:`, cookies);
store.set(uuid, cookies)
// window.close();
}
// 初始化浏览器
browser(window, 'chrome');
// 加载js
let js;
if (loadHtmlJs) {
js = loadJs(window, jsText);
} else {
js = jsText;
}
console.log(`${uuid};js 加载长度--->`, js.length)
fs.writeFileSync(`./back/${uuid}.js`, js)
// 执行 js
let script = new Script(js);
let internalVMContext = dom.getInternalVMContext();
script.runInContext(internalVMContext);
// 等待 onbeforeunload 钩子触发后的回掉
let timeout = -1;
let reTry = -1;
if (process.env.NODE_ENV === 'prod') {
timeout = 100
reTry = 10
} else {
timeout = 100
reTry = 10
}
let val = await store.waitGetAndDelete(uuid, timeout, reTry)
// bug 关闭后 部分参数是需要永久保存在内存中的 比如 cookie 实例 下次调用会报错
// internalVMContext.close()
// window.close()
// dom = null
if (val != null) {
return val;
}
console.log(`${uuid} 执行超时`)
return cookieJar.getCookieStringSync(baseUrl);
// throw new Error(`${uuid}执行超时`)
}
module.exports = router