实现gmxhr
Some checks failed
build / Build (push) Failing after 6s
test / Run tests (push) Failing after 8s

This commit is contained in:
2025-04-07 00:37:58 +08:00
parent a8054451ac
commit 1a55bb348f
13 changed files with 1255 additions and 517 deletions

View File

@ -1,9 +1,17 @@
import LoggerCore from "@App/app/logger/core";
import Logger from "@App/app/logger/logger";
import { GetSender, Group, MessageConnect } from "@Packages/message/server";
export default class GMApi {
constructor(private group: Group) {}
dealXhrResponse(con: MessageConnect, details: GMSend.XHRDetails, event: string, xhr: XMLHttpRequest, data?: any) {
async dealXhrResponse(
con: MessageConnect,
details: GMSend.XHRDetails,
event: string,
xhr: XMLHttpRequest,
data?: any
) {
const finalUrl = xhr.responseURL || details.url;
// 判断是否有headerFlag-final-url,有则替换finalUrl
let response: GMTypes.XHRResponse = {
@ -11,9 +19,56 @@ export default class GMApi {
readyState: <any>xhr.readyState,
status: xhr.status,
statusText: xhr.statusText,
// header由service_worker处理
// responseHeaders: xhr.getAllResponseHeaders().replace(removeXCat, ""),
responseType: details.responseType,
};
if (xhr.readyState === 4) {
const responseType = details.responseType?.toLowerCase();
if (responseType === "arraybuffer" || responseType === "blob") {
let blob: Blob;
if (xhr.response instanceof ArrayBuffer) {
blob = new Blob([xhr.response]);
response.response = URL.createObjectURL(blob);
} else {
blob = <Blob>xhr.response;
response.response = URL.createObjectURL(blob);
}
try {
if (xhr.getResponseHeader("Content-Type")?.indexOf("text") !== -1) {
// 如果是文本类型,则尝试转换为文本
response.responseText = await blob.text();
}
} catch (e) {
LoggerCore.logger(Logger.E(e)).error("GM XHR getResponseHeader error");
}
setTimeout(() => {
URL.revokeObjectURL(<string>response.response);
}, 60 * 1000);
} else if (response.responseType === "json") {
try {
response.response = JSON.parse(xhr.responseText);
} catch (e) {
LoggerCore.logger(Logger.E(e)).error("GM XHR JSON parse error");
}
try {
response.responseText = xhr.responseText;
} catch (e) {
LoggerCore.logger(Logger.E(e)).error("GM XHR getResponseText error");
}
} else {
try {
response.response = xhr.response;
} catch (e) {
LoggerCore.logger(Logger.E(e)).error("GM XHR response error");
}
try {
response.responseText = xhr.responseText || undefined;
} catch (e) {
LoggerCore.logger(Logger.E(e)).error("GM XHR getResponseText error");
}
}
}
if (data) {
response = Object.assign(response, data);
}
@ -24,16 +79,36 @@ export default class GMApi {
return response;
}
xmlHttpRequest(details: GMSend.XHRDetails, sender: GetSender) {
CAT_fetch(details: GMSend.XHRDetails, sender: GetSender) {
throw new Error("Method not implemented.");
}
async xmlHttpRequest(details: GMSend.XHRDetails, sender: GetSender) {
if (details.responseType === "stream") {
// 只有fetch支持ReadableStream
return this.CAT_fetch(details, sender);
}
const xhr = new XMLHttpRequest();
const con = sender.getConnect();
xhr.open(details.method || "GET", details.url);
xhr.open(details.method || "GET", details.url, true, details.user || "", details.password || "");
// 添加header
if (details.headers) {
for (const key in details.headers) {
xhr.setRequestHeader(key, details.headers[key]);
}
}
//超时时间
if (details.timeout) {
xhr.timeout = details.timeout;
}
if (details.overrideMimeType) {
xhr.overrideMimeType(details.overrideMimeType);
}
//设置响应类型
if (details.responseType !== "json") {
xhr.responseType = details.responseType || "";
}
xhr.onload = () => {
this.dealXhrResponse(con, details, "onload", xhr);
};
@ -65,14 +140,32 @@ export default class GMApi {
xhr.ontimeout = () => {
con?.sendMessage({ action: "ontimeout", data: {} });
};
//处理数据
if (details.dataType === "FormData") {
const data = new FormData();
if (details.data && details.data instanceof Array) {
await Promise.all(
details.data.map(async (val: GMSend.XHRFormData) => {
if (val.type === "file") {
const file = new File([await (await fetch(val.val)).blob()], val.filename!);
data.append(val.key, file, val.filename);
} else {
data.append(val.key, val.val);
}
})
);
xhr.send(data);
}
} else if (details.dataType === "Blob") {
if (!details.data) {
throw new Error("Blob data is empty");
}
const resp = await (await fetch(<string>details.data)).blob();
xhr.send(resp);
} else {
xhr.send(<string>details.data);
}
if (details.timeout) {
xhr.timeout = details.timeout;
}
if (details.overrideMimeType) {
xhr.overrideMimeType(details.overrideMimeType);
}
xhr.send();
con?.onDisconnect(() => {
xhr.abort();
});

View File

@ -52,20 +52,5 @@ export class OffscreenManager {
const gmApi = new GMApi(this.windowApi.group("gmApi"));
gmApi.init();
// // 处理gm xhr请求
// this.api.on("gmXhr", (data) => {
// console.log("123");
// });
// // 测试xhr
// const ret = await sendMessage(this.extensionMessage, "serviceWorker/testGmApi");
// console.log("test xhr", ret);
// const xhr = new XMLHttpRequest();
// xhr.onload = () => {
// console.log(xhr);
// };
// xhr.open("GET", "https://scriptcat.org/zh-CN");
// xhr.send();
}
}

View File

@ -159,7 +159,7 @@ export class Runtime {
crontabScript(script: ScriptRunResouce) {
// 执行定时脚本 运行表达式
if (!script.metadata.crontab) {
throw new Error("错误的crontab表达式");
throw new Error(script.name + " - 错误的crontab表达式");
}
// 如果有nextruntime,则加入重试队列
this.joinRetryList(script);

View File

@ -95,11 +95,21 @@ export default class GMApi {
] as chrome.declarativeNetRequest.ModifyHeaderInfo[];
Object.keys(headers).forEach((key) => {
const lowKey = key.toLowerCase();
if (unsafeHeaders[lowKey] || lowKey.startsWith("sec-") || lowKey.startsWith("proxy-")) {
if (headers[key]) {
if (unsafeHeaders[lowKey] || lowKey.startsWith("sec-") || lowKey.startsWith("proxy-")) {
if (headers[key]) {
requestHeaders.push({
header: key,
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: headers[key],
});
}
delete headers[key];
}
} else {
requestHeaders.push({
header: key,
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
value: headers[key],
operation: chrome.declarativeNetRequest.HeaderOperation.REMOVE,
});
delete headers[key];
}
@ -125,7 +135,6 @@ export default class GMApi {
requestMethods: [(params.method || "GET").toLowerCase() as chrome.declarativeNetRequest.RequestMethod],
excludedTabIds: excludedTabIds,
};
console.log(rule);
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [ruleId],
addRules: [rule],
@ -135,9 +144,9 @@ export default class GMApi {
gmXhrHeadersReceived: EventEmitter = new EventEmitter();
// TODO: maxRedirects实现
@PermissionVerify.API()
async GM_xmlhttpRequest(request: Request, con: GetSender) {
console.log("GM XHR", request);
if (request.params.length === 0) {
return Promise.reject(new Error("param is failed"));
}
@ -151,7 +160,6 @@ export default class GMApi {
}
params.headers["X-Scriptcat-GM-XHR-Request-Id"] = requestId.toString();
params.headers = await this.buildDNRRule(requestId, request.params[0]);
console.log(" params.headers", params.headers);
let responseHeader = "";
// 等待response
this.gmXhrHeadersReceived.addListener(
@ -160,6 +168,7 @@ export default class GMApi {
details.responseHeaders?.forEach((header) => {
responseHeader += header.name + ": " + header.value + "\n";
});
this.gmXhrHeadersReceived.removeAllListeners("headersReceived:" + requestId);
}
);
// 再发送到offscreen, 处理请求