mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-29 12:22:44 +08:00
新增网络请求拦截功能:在浏览器命令中添加请求和响应拦截选项,支持自定义拦截规则和内容替换
This commit is contained in:
parent
9257faf132
commit
e097678c93
@ -9,8 +9,13 @@ const initCDP = async (targetId) => {
|
|||||||
port,
|
port,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { Page, Runtime, Target, Network, Emulation, DOM } = client;
|
const { Page, Runtime, Target, Network, Emulation, DOM, Fetch } = client;
|
||||||
await Promise.all([Page.enable(), Runtime.enable(), DOM.enable()]);
|
await Promise.all([
|
||||||
|
Page.enable(),
|
||||||
|
Runtime.enable(),
|
||||||
|
DOM.enable(),
|
||||||
|
Fetch.enable(),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
client,
|
client,
|
||||||
@ -20,6 +25,7 @@ const initCDP = async (targetId) => {
|
|||||||
Network,
|
Network,
|
||||||
Emulation,
|
Emulation,
|
||||||
DOM,
|
DOM,
|
||||||
|
Fetch,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -6,6 +6,7 @@ const url = require("./url");
|
|||||||
const cookie = require("./cookie");
|
const cookie = require("./cookie");
|
||||||
const screenshot = require("./screenshot");
|
const screenshot = require("./screenshot");
|
||||||
const device = require("./device");
|
const device = require("./device");
|
||||||
|
const network = require("./network");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...url,
|
...url,
|
||||||
@ -16,4 +17,5 @@ module.exports = {
|
|||||||
...cookie,
|
...cookie,
|
||||||
...screenshot,
|
...screenshot,
|
||||||
...device,
|
...device,
|
||||||
|
...network,
|
||||||
};
|
};
|
||||||
|
240
plugin/lib/quickcomposer/browser/network.js
Normal file
240
plugin/lib/quickcomposer/browser/network.js
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
const { initCDP, cleanupCDP } = require("./cdp");
|
||||||
|
const { searchTarget } = require("./tabs");
|
||||||
|
|
||||||
|
// 使用正则替换内容
|
||||||
|
const replaceWithRegex = (content, pattern, replacement) => {
|
||||||
|
try {
|
||||||
|
const regex = new RegExp(pattern, "g");
|
||||||
|
return content.replace(regex, replacement);
|
||||||
|
} catch (error) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 存储活动的拦截连接
|
||||||
|
let activeInterceptions = new Map();
|
||||||
|
|
||||||
|
// 将对象格式的 headers 转换为数组格式
|
||||||
|
const convertHeaders = (headers) => {
|
||||||
|
return Object.entries(headers).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value: String(value),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 检查 URL 是否匹配规则
|
||||||
|
const isUrlMatch = (url, pattern) => {
|
||||||
|
try {
|
||||||
|
const regex = new RegExp(pattern);
|
||||||
|
return regex.test(url);
|
||||||
|
} catch (error) {
|
||||||
|
return url.includes(pattern);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改请求
|
||||||
|
const setRequestInterception = async (tab, rules) => {
|
||||||
|
// 先清除所有拦截规则
|
||||||
|
await clearInterception();
|
||||||
|
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const client = await initCDP(target.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.Fetch.enable({
|
||||||
|
patterns: [{ url: "*", requestStage: "Request" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
client.Fetch.requestPaused(async ({ requestId, request }) => {
|
||||||
|
try {
|
||||||
|
let modified = null;
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (isUrlMatch(request.url, rule.url)) {
|
||||||
|
modified = {
|
||||||
|
url: rule.redirectUrl || request.url,
|
||||||
|
method: request.method,
|
||||||
|
headers: { ...request.headers },
|
||||||
|
postData: request.postData,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rule.headerKey && rule.headerValue) {
|
||||||
|
modified.headers[rule.headerKey] = rule.headerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.pattern && rule.replacement) {
|
||||||
|
const url = new URL(modified.url);
|
||||||
|
for (const [key, value] of url.searchParams.entries()) {
|
||||||
|
const decodedValue = decodeURIComponent(value);
|
||||||
|
const newValue = replaceWithRegex(
|
||||||
|
decodedValue,
|
||||||
|
rule.pattern,
|
||||||
|
rule.replacement
|
||||||
|
);
|
||||||
|
if (decodedValue !== newValue) {
|
||||||
|
url.searchParams.set(key, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modified.url = url.toString();
|
||||||
|
|
||||||
|
if (modified.postData) {
|
||||||
|
modified.postData = replaceWithRegex(
|
||||||
|
modified.postData,
|
||||||
|
rule.pattern,
|
||||||
|
rule.replacement
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modified) {
|
||||||
|
await client.Fetch.continueRequest({
|
||||||
|
requestId,
|
||||||
|
url: modified.url,
|
||||||
|
method: modified.method,
|
||||||
|
headers: convertHeaders(modified.headers),
|
||||||
|
postData: modified.postData,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await client.Fetch.continueRequest({ requestId });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await client.Fetch.continueRequest({ requestId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
activeInterceptions.set("request", client);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `设置请求拦截规则成功`,
|
||||||
|
rules,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await cleanupCDP(client);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改响应
|
||||||
|
const setResponseInterception = async (tab, rules) => {
|
||||||
|
// 先清除所有拦截规则
|
||||||
|
await clearInterception();
|
||||||
|
|
||||||
|
const target = await searchTarget(tab);
|
||||||
|
const client = await initCDP(target.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await client.Fetch.enable({
|
||||||
|
patterns: [{ url: "*", requestStage: "Response" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
client.Fetch.requestPaused(
|
||||||
|
async ({ requestId, request, responseHeaders, responseStatusCode }) => {
|
||||||
|
try {
|
||||||
|
const contentType = responseHeaders.find(
|
||||||
|
(h) => h.name.toLowerCase() === "content-type"
|
||||||
|
)?.value;
|
||||||
|
const isTextContent = contentType && contentType.includes("text");
|
||||||
|
|
||||||
|
if (!isTextContent) {
|
||||||
|
await client.Fetch.continueRequest({ requestId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shouldIntercept = false;
|
||||||
|
let modifiedBody = null;
|
||||||
|
let modifiedStatus = responseStatusCode;
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (isUrlMatch(request.url, rule.url)) {
|
||||||
|
shouldIntercept = true;
|
||||||
|
|
||||||
|
if (rule.statusCode) {
|
||||||
|
modifiedStatus = rule.statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.pattern) {
|
||||||
|
try {
|
||||||
|
const response = await client.Fetch.getResponseBody({
|
||||||
|
requestId,
|
||||||
|
});
|
||||||
|
const originalBody = response.base64Encoded
|
||||||
|
? Buffer.from(response.body, "base64").toString()
|
||||||
|
: response.body;
|
||||||
|
|
||||||
|
if (originalBody) {
|
||||||
|
modifiedBody = replaceWithRegex(
|
||||||
|
originalBody,
|
||||||
|
rule.pattern,
|
||||||
|
rule.replacement || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
shouldIntercept = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldIntercept && modifiedBody !== null) {
|
||||||
|
await client.Fetch.fulfillRequest({
|
||||||
|
requestId,
|
||||||
|
responseCode: modifiedStatus,
|
||||||
|
responseHeaders,
|
||||||
|
body: Buffer.from(modifiedBody).toString("base64"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await client.Fetch.continueRequest({ requestId });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await client.Fetch.continueRequest({ requestId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
activeInterceptions.set("response", client);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `设置响应拦截规则成功`,
|
||||||
|
rules,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await cleanupCDP(client);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除所有拦截规则
|
||||||
|
const clearInterception = async () => {
|
||||||
|
if (activeInterceptions.size === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `还没有设置拦截规则`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (const [type, client] of activeInterceptions.entries()) {
|
||||||
|
try {
|
||||||
|
await client.Fetch.disable();
|
||||||
|
await cleanupCDP(client);
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
activeInterceptions.clear();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `清除拦截规则成功`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
setRequestInterception,
|
||||||
|
setResponseInterception,
|
||||||
|
clearInterception,
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { newVarInputVal } from "js/composer/varInputValManager";
|
import { newVarInputVal } from "js/composer/varInputValManager";
|
||||||
import { deviceName, userAgent } from "js/options/httpOptions";
|
import { deviceName, userAgent, commonHeaders } from "js/options/httpOptions";
|
||||||
|
|
||||||
const tabConfig = {
|
const tabConfig = {
|
||||||
component: "OptionEditor",
|
component: "OptionEditor",
|
||||||
@ -625,6 +625,128 @@ export const browserCommands = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.browser.setRequestInterception",
|
||||||
|
label: "修改请求/响应",
|
||||||
|
icon: "network",
|
||||||
|
isAsync: true,
|
||||||
|
isAsync: true,
|
||||||
|
subCommands: [
|
||||||
|
{
|
||||||
|
value: "quickcomposer.browser.setRequestInterception",
|
||||||
|
label: "修改请求",
|
||||||
|
icon: "upload",
|
||||||
|
config: [
|
||||||
|
tabConfig,
|
||||||
|
{
|
||||||
|
topLabel: "拦截规则",
|
||||||
|
isCollapse: false,
|
||||||
|
component: "ArrayEditor",
|
||||||
|
icon: "rule",
|
||||||
|
columns: {
|
||||||
|
url: {
|
||||||
|
label: "要拦截的URL",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
placeholder: "支持正则,如.*\\.baidu\\.com",
|
||||||
|
width: 12,
|
||||||
|
},
|
||||||
|
headerKey: {
|
||||||
|
label: "要修改的请求头",
|
||||||
|
component: "VariableInput",
|
||||||
|
options: {
|
||||||
|
items: commonHeaders,
|
||||||
|
},
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
headerValue: {
|
||||||
|
label: "要修改的请求头值",
|
||||||
|
component: "VariableInput",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
label: "要修改的请求内容(body及url参数)",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
width: 6,
|
||||||
|
placeholder: "支持正则,如(role: )[guest|user]",
|
||||||
|
},
|
||||||
|
replacement: {
|
||||||
|
label: "替换内容",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
width: 6,
|
||||||
|
placeholder: "支持替换符,如$1admin",
|
||||||
|
},
|
||||||
|
redirectUrl: {
|
||||||
|
label: "重定向到指定URL",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
width: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.browser.setResponseInterception",
|
||||||
|
label: "修改响应",
|
||||||
|
icon: "download",
|
||||||
|
config: [
|
||||||
|
tabConfig,
|
||||||
|
{
|
||||||
|
topLabel: "拦截规则",
|
||||||
|
isCollapse: false,
|
||||||
|
component: "ArrayEditor",
|
||||||
|
icon: "rule",
|
||||||
|
width: 12,
|
||||||
|
columns: {
|
||||||
|
url: {
|
||||||
|
label: "要拦截的URL",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
placeholder: "支持正则,如.*\\.baidu\\.com",
|
||||||
|
width: 9,
|
||||||
|
},
|
||||||
|
statusCode: {
|
||||||
|
label: "状态码",
|
||||||
|
component: "VariableInput",
|
||||||
|
defaultValue: 200,
|
||||||
|
options: {
|
||||||
|
items: [
|
||||||
|
{ label: "200", value: 200 },
|
||||||
|
{ label: "302", value: 302 },
|
||||||
|
{ label: "401", value: 401 },
|
||||||
|
{ label: "403", value: 403 },
|
||||||
|
{ label: "404", value: 404 },
|
||||||
|
{ label: "500", value: 500 },
|
||||||
|
{ label: "502", value: 502 },
|
||||||
|
{ label: "503", value: 503 },
|
||||||
|
{ label: "504", value: 504 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
defaultValue: newVarInputVal("var", ""),
|
||||||
|
width: 3,
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
label: "要修改的响应内容",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
placeholder: "支持正则,如(role: )[guest|user]",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
replacement: {
|
||||||
|
label: "替换内容",
|
||||||
|
defaultValue: newVarInputVal("str"),
|
||||||
|
placeholder: "支持替换符,如$1admin",
|
||||||
|
width: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "quickcomposer.browser.clearInterception",
|
||||||
|
label: "清除所有拦截规则",
|
||||||
|
icon: "clear",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: "quickcomposer.browser.setDevice",
|
value: "quickcomposer.browser.setDevice",
|
||||||
label: "设备模拟",
|
label: "设备模拟",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user