From 3a8469a8e311f58f773827cc842b01701fc5dd3d Mon Sep 17 00:00:00 2001 From: Gj Date: Fri, 11 Aug 2023 16:47:06 +0800 Subject: [PATCH] feat(#40): Functional Redirection Supplement --- package.json | 2 +- packages/code-editor/examples/App.vue | 1 + packages/code-editor/packages/index.vue | 8 +- packages/code-editor/packages/useEditor.js | 83 +++-- packages/proxy-lib/src/redirectFetch.ts | 33 +- packages/proxy-lib/src/redirectUrlFunc.ts | 51 ++++ packages/proxy-lib/src/redirectXHR.ts | 24 +- packages/proxy-lib/src/types.ts | 6 + packages/proxy-lib/types/redirectUrlFunc.d.ts | 13 + packages/proxy-lib/types/types.d.ts | 6 + packages/shell-chrome/manifest.json | 2 +- packages/vue-panels/src/views/index.vue | 2 +- .../vue-panels/src/views/redirector/modal.vue | 289 ++++++++++-------- 13 files changed, 357 insertions(+), 163 deletions(-) create mode 100644 packages/proxy-lib/src/redirectUrlFunc.ts create mode 100644 packages/proxy-lib/types/redirectUrlFunc.d.ts diff --git a/package.json b/package.json index 6daa5df..82e28f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ajax-proxy", "private": true, "description": "Modify your Ajax response to test", - "version": "2.2.6", + "version": "2.2.7", "scripts": { "dev": "pnpm -C ./packages/vue-panels serve", "watch": "run-p watch:lib watch:chrome", diff --git a/packages/code-editor/examples/App.vue b/packages/code-editor/examples/App.vue index 10a8fe2..0171234 100644 --- a/packages/code-editor/examples/App.vue +++ b/packages/code-editor/examples/App.vue @@ -5,6 +5,7 @@ ref="codeEditor" v-model="codeText" @change="handleChange" + type="redirector" /> diff --git a/packages/code-editor/packages/index.vue b/packages/code-editor/packages/index.vue index 3193e6c..3764f3e 100644 --- a/packages/code-editor/packages/index.vue +++ b/packages/code-editor/packages/index.vue @@ -14,6 +14,10 @@ export default { type: String, default: "", }, + type: { + type: String, + default: "interceptor", + }, }, watch: { value: { @@ -35,7 +39,7 @@ export default { methods: { initEditor() { - this.aceEditor = useInit(this.$refs.ace); + this.aceEditor = useInit(this.$refs.ace, this.type); this.aceEditor.getSession().on("change", () => { this.$emit("change", this.aceEditor.getSession().getValue()); @@ -49,7 +53,7 @@ export default { }, setValue(value) { if (this.aceEditor) { - if (!value) this.aceEditor.setValue(getDefaultContent()); + if (!value) this.aceEditor.setValue(getDefaultContent(this.type)); else this.aceEditor.setValue(value); } }, diff --git a/packages/code-editor/packages/useEditor.js b/packages/code-editor/packages/useEditor.js index 77eee1c..b931c4b 100644 --- a/packages/code-editor/packages/useEditor.js +++ b/packages/code-editor/packages/useEditor.js @@ -8,7 +8,7 @@ import "ace-builds/src-noconflict/snippets/javascript"; //代码提示 // https://ace.c9.io/#nav=howto // 初始化 -export function useInit(container) { +export function useInit(container, type) { // 初始化 const target = ace.edit(container, { maxLines: 20, // 最大行数,超过会自动出现滚动条 @@ -25,46 +25,75 @@ export function useInit(container) { }); // 自定义提示 - customCompletions(target) + customCompletions(target, type) return target; } // 自定义提示 -function customCompletions(target) { - target.completers.push({ - getCompletions: function (state, session, pos, prefix, callback) { - if (prefix.length === 0) { - callback(null, []); - return; - } - callback(null, [ - { meta: 'AjaxProxy::Ctx.req', caption: 'req.url: string', value: 'req.url', score: 100 }, - { meta: 'AjaxProxy::Ctx.req', caption: 'req.method: string', value: 'req.method', score: 100 }, - { meta: 'AjaxProxy::Ctx.req', caption: 'req.body?: any', value: 'req.body', score: 100 }, - { meta: 'AjaxProxy::Ctx.res', caption: 'res.status: string', value: 'res.status', score: 100 }, - { meta: 'AjaxProxy::Ctx.res', caption: 'res.customStatus: string', value: 'res.customStatus', score: 100 }, - { meta: 'AjaxProxy::Ctx.res', caption: 'res.response: any', value: 'res.response', score: 100 }, - { - meta: 'AjaxProxy::Next', - caption: 'next({ override?: string, status?: string | number })', - value: 'next({ override: "", status: "" });', - score: 100 - }, - ]); - }, - }); +function customCompletions(target, type = 'interceptor') { + if (type === 'interceptor') + target.completers.push({ + getCompletions: function (state, session, pos, prefix, callback) { + if (prefix.length === 0) { + callback(null, []); + return; + } + callback(null, [ + { meta: 'AjaxProxy::Ctx.req', caption: 'req.url: string', value: 'req.url', score: 100 }, + { meta: 'AjaxProxy::Ctx.req', caption: 'req.method: string', value: 'req.method', score: 100 }, + { meta: 'AjaxProxy::Ctx.req', caption: 'req.body?: any', value: 'req.body', score: 100 }, + { meta: 'AjaxProxy::Ctx.res', caption: 'res.status: string', value: 'res.status', score: 100 }, + { meta: 'AjaxProxy::Ctx.res', caption: 'res.customStatus: string', value: 'res.customStatus', score: 100 }, + { meta: 'AjaxProxy::Ctx.res', caption: 'res.response: any', value: 'res.response', score: 100 }, + { + meta: 'AjaxProxy::Next', + caption: 'next({ override?: string, status?: string | number })', + value: 'next({ override: "", status: "" });', + score: 100 + }, + ]); + }, + }); + else + target.completers.push({ + getCompletions: function (state, session, pos, prefix, callback) { + if (prefix.length === 0) { + callback(null, []); + return; + } + callback(null, [ + { meta: 'AjaxProxy::Ctx.req', caption: 'req.url: string', value: 'req.url', score: 100 }, + { meta: 'AjaxProxy::Ctx.req', caption: 'req.method: string', value: 'req.method', score: 100 }, + { + meta: 'AjaxProxy::Next', + caption: 'next({ url: string, headers?: { [key: string]: string } })', + value: 'next({ url: req.url });', + score: 100 + }, + ]); + }, + }); } // 默认内容 -export function getDefaultContent() { - const defaultContent = +export function getDefaultContent(type = "interceptor") { + let defaultContent = type === 'interceptor' ? ` function setup(req, res, next) { // TODO... // type Next = { override?: string, status?: string | number } next({ override: "", status: "" }); } +` : + ` +function setup( + req, /** req: { url: string, method: string }*/ + next /**{ url: string, headers?: { [key: string]: string } }*/ +) { + // TODO... + next({ url: "" }); +} ` return defaultContent } diff --git a/packages/proxy-lib/src/redirectFetch.ts b/packages/proxy-lib/src/redirectFetch.ts index ffde62f..746747b 100644 --- a/packages/proxy-lib/src/redirectFetch.ts +++ b/packages/proxy-lib/src/redirectFetch.ts @@ -1,4 +1,5 @@ import { finalRedirectUrl, matchIgnoresAndRule } from "./common"; +import { execSetup } from "./redirectUrlFunc"; import { RefGlobalState } from "./types"; // 共享状态 @@ -7,7 +8,7 @@ const OriginFetch = window.fetch.bind(window) // 初始化共享状态 export const initRedirectFetchState = (state: RefGlobalState) => globalState = state -export default function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise { +export default async function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise { let fetchMethod: string | undefined | "ANY" = "ANY" let customInit: RequestInit = init || {} if (init) { @@ -23,11 +24,39 @@ export default function CustomFetch(input: RequestInfo | URL, init?: RequestInit redirect_url = "", headers = [], ignores = [], + redirect_type = "text", + redirect_func = "" } = globalState.value.redirector_matching_content[i]; if (switch_on) { // 判断是否存在协议匹配 if (method && ![fetchMethod, "ANY"].includes(method.toUpperCase())) break - if (matchIgnoresAndRule(input.toString(), domain, filter_type, ignores)) { + if (redirect_type === "function") { + const payload = await execSetup({ url: input.toString(), method: fetchMethod }, redirect_func) + input = payload.url + + if (init?.headers) { + // 初始化 RequestInit + customInit = init + const initHeaders = new Headers(init.headers) + if (payload.headers) { + for (const key in payload.headers) { + if (Object.prototype.hasOwnProperty.call(payload.headers, key)) { + const value = payload.headers[key]; + if (key && value) initHeaders.set(key, value) + } + } + } + customInit.headers = initHeaders + } else { + const newHeaders: HeadersInit = payload.headers ? Object.keys(payload.headers).map(key => [key, payload.headers![key]]) : [] + customInit = { + headers: newHeaders + } + } + + // 值取当前命中的第一个,后续再命中的忽略 + break; + } else if (matchIgnoresAndRule(input.toString(), domain, filter_type, ignores)) { input = finalRedirectUrl(input.toString(), domain, redirect_url, filter_type) if (init?.headers) { diff --git a/packages/proxy-lib/src/redirectUrlFunc.ts b/packages/proxy-lib/src/redirectUrlFunc.ts new file mode 100644 index 0000000..62b926b --- /dev/null +++ b/packages/proxy-lib/src/redirectUrlFunc.ts @@ -0,0 +1,51 @@ +import { IRedirectHeader } from './types'; + + +const errLog = function (...args: any[]) { + console.log("%c[AjaxProxy][error]: ", "color: #ff4d4f", ...args) +} + +type Req = { + url: string + method: string +} + +type Next = { + url: string, + headers?: { [key: IRedirectHeader['key']]: IRedirectHeader['value'] } +} + +function isNext(x: any): x is Next { + if (!!x) return true + if (x.url && !x.headers) return true + return x.headers && Object.prototype.toString.call(x.headers) == '[object Object]' +} + +export function execSetup(req: Req, funcText: string): Promise { + return new Promise(resolve => { + try { + const source = ';(' + funcText + ')' + const execFunc = window.eval(source) + const type = typeof execFunc + if (type === "function") { + if (!funcText.includes('next(')) { + errLog("The structure of 'next' is incorrect [code 1]") + // original + return resolve({ url: req.url }) + } + execFunc(req, (next: Next) => { + // custom + if (isNext(next)) { + return resolve({ url: next.url, headers: next.headers }) + } + }) + return resolve({ url: req.url }) + } + errLog("Please enter a correct 'function' [code 2]") + return resolve({ url: req.url }) + } catch (error) { + errLog(error) + return resolve({ url: req.url }) + } + }) +} \ No newline at end of file diff --git a/packages/proxy-lib/src/redirectXHR.ts b/packages/proxy-lib/src/redirectXHR.ts index e5e0cb3..13d7762 100644 --- a/packages/proxy-lib/src/redirectXHR.ts +++ b/packages/proxy-lib/src/redirectXHR.ts @@ -1,4 +1,5 @@ import { finalRedirectUrl, fmtURLToString, matchIgnoresAndRule } from "./common"; +import { execSetup } from "./redirectUrlFunc"; import { RefGlobalState } from "./types"; // 状态 @@ -21,7 +22,7 @@ export default class CustomRedirectXHR extends XMLHttpRequest { const origin_XHR_open = open const origin_XHR_setRequestHeader = this.setRequestHeader const origin_XHR_send = this.send - this.open = (method: string, + this.open = async (method: string, url: string | URL, async?: boolean, username?: string | null, @@ -37,13 +38,31 @@ export default class CustomRedirectXHR extends XMLHttpRequest { redirect_url = "", headers = [], ignores = [], + redirect_type = "text", + redirect_func = "" } = globalState.value.redirector_matching_content[i]; if (switch_on) { // 判断是否存在协议匹配 if (method && ![this.method, "ANY"].includes(method.toUpperCase())) return // 规则判断 const currentUrl = fmtURLToString(url) - if (matchIgnoresAndRule(currentUrl, domain, filter_type, ignores)) { + if (redirect_type === "function") { + const payload = await execSetup({ url: currentUrl, method: this.method }, redirect_func) + url = payload.url + this.send = (body?: Document | XMLHttpRequestBodyInit | null) => { + if (payload.headers) { + for (const key in payload.headers) { + if (Object.prototype.hasOwnProperty.call(payload.headers, key)) { + const value = payload.headers[key]; + if (key && value) + this.setRequestHeader(key, value) + } + } + } + origin_XHR_send.call(this, body) + } + break; + } else if (matchIgnoresAndRule(currentUrl, domain, filter_type, ignores)) { url = finalRedirectUrl(currentUrl, domain, redirect_url, filter_type) // 获取 自定义 header 映射关系 @@ -59,6 +78,7 @@ export default class CustomRedirectXHR extends XMLHttpRequest { for (let k = 0; k < headers.length; k++) { const header = headers[k]; // 移除掉 映射关系 + // Reflect.deleteProperty(cacheHeaderMap, header.key) delete cacheHeaderMap[header.key] this.setRequestHeader(header.key, header.value) } diff --git a/packages/proxy-lib/src/types.ts b/packages/proxy-lib/src/types.ts index 8992a35..77ff4d8 100644 --- a/packages/proxy-lib/src/types.ts +++ b/packages/proxy-lib/src/types.ts @@ -8,6 +8,8 @@ export type IMode = "interceptor" | "redirector" export type RefGlobalState = { value: T } /**响应式类型 */ export type OverrideType = 'json' | 'function' +/**重定向类型 */ +export type RedirectType = 'text' | 'function' type CommonContent = { /**是否需要匹配 */ @@ -53,6 +55,10 @@ export type IMatchRedirectContent = { headers?: IRedirectHeader[] /**忽略名单 */ ignores?: string[] + /**重定向类型 */ + redirect_type?: RedirectType + /**函数响应 */ + redirect_func?: string } & CommonContent export type IGlobalState = { diff --git a/packages/proxy-lib/types/redirectUrlFunc.d.ts b/packages/proxy-lib/types/redirectUrlFunc.d.ts new file mode 100644 index 0000000..c029e08 --- /dev/null +++ b/packages/proxy-lib/types/redirectUrlFunc.d.ts @@ -0,0 +1,13 @@ +import { IRedirectHeader } from './types'; +declare type Req = { + url: string; + method: string; +}; +declare type Next = { + url: string; + headers?: { + [key: IRedirectHeader['key']]: IRedirectHeader['value']; + }; +}; +export declare function execSetup(req: Req, funcText: string): Promise; +export {}; diff --git a/packages/proxy-lib/types/types.d.ts b/packages/proxy-lib/types/types.d.ts index 6518c0b..8d20dca 100644 --- a/packages/proxy-lib/types/types.d.ts +++ b/packages/proxy-lib/types/types.d.ts @@ -10,6 +10,8 @@ export declare type RefGlobalState = { }; /**响应式类型 */ export declare type OverrideType = 'json' | 'function'; +/**重定向类型 */ +export declare type RedirectType = 'text' | 'function'; declare type CommonContent = { /**是否需要匹配 */ switch_on: boolean; @@ -51,6 +53,10 @@ export declare type IMatchRedirectContent = { headers?: IRedirectHeader[]; /**忽略名单 */ ignores?: string[]; + /**重定向类型 */ + redirect_type?: RedirectType; + /**函数响应 */ + redirect_func?: string; } & CommonContent; export declare type IGlobalState = { /**全局开关 */ diff --git a/packages/shell-chrome/manifest.json b/packages/shell-chrome/manifest.json index 4411a52..f049a3c 100644 --- a/packages/shell-chrome/manifest.json +++ b/packages/shell-chrome/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Ajax Proxy", - "version": "2.2.6", + "version": "2.2.7", "description": "Modify your Ajax response to test", "author": "Gj", "icons": { diff --git a/packages/vue-panels/src/views/index.vue b/packages/vue-panels/src/views/index.vue index 17a170d..b183a33 100644 --- a/packages/vue-panels/src/views/index.vue +++ b/packages/vue-panels/src/views/index.vue @@ -244,7 +244,7 @@ export default { this.currentMode = useMode.get(); // 初始化title const manifest = chrome.runtime?.getManifest(); - document.title = `Ajax Proxy ${manifest?.version}`; + document.title = `Ajax Proxy ${manifest?.version || 'DEV'}`; }, }, mounted() { diff --git a/packages/vue-panels/src/views/redirector/modal.vue b/packages/vue-panels/src/views/redirector/modal.vue index 0d3e1de..d152df6 100644 --- a/packages/vue-panels/src/views/redirector/modal.vue +++ b/packages/vue-panels/src/views/redirector/modal.vue @@ -46,141 +46,171 @@ - - - - - - -
- - +{{ $t("append") }} + + + - - Key - Value - {{ $t("describe") }} - {{ $t("option") }} - - + + + - - - + + +{{ $t("append") }} - - - - - - + Key + Value + {{ $t("describe") }} + {{ $t("option") }} + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + {{ $t("del") }} + + +
+
+ + +
+ {{ $t("del") }}+{{ $t("append") }} - - -
-
- - -
- - +{{ $t("append") }} - - - - - - - -
-
+ + + + + + + + + + + + + + + + import { uniqueId } from "@alrale/common-lib"; import { useTags } from "@/common/store"; +import { VueCodeEditor } from "@proxy/code-editor"; export default { + components: { CodeEditor: VueCodeEditor }, data() { return { isShow: false, @@ -245,6 +277,8 @@ export default { this.isEdit = true; // 编辑 this.title = this.$t("edit"); + // 重定向类型 + if (!row.redirect_type) row.redirect_type = "text"; // 兼容版本迭代,旧数据未存在白名单 if (!row.ignores) row.ignores = []; } else { @@ -258,6 +292,7 @@ export default { headers: [], ignores: [], filter_type: "normal", + redirect_type: "text", }; this.$nextTick(() => this.$refs.form.clearValidate()); },