Skip to content

Commit

Permalink
feat(#40): Functional Redirection Supplement
Browse files Browse the repository at this point in the history
  • Loading branch information
g0ngjie committed Aug 11, 2023
1 parent d51d207 commit 3a8469a
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 163 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/code-editor/examples/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ref="codeEditor"
v-model="codeText"
@change="handleChange"
type="redirector"
/>
<button type="button" @click="reset">reset</button>
</div>
Expand Down
8 changes: 6 additions & 2 deletions packages/code-editor/packages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export default {
type: String,
default: "",
},
type: {
type: String,
default: "interceptor",
},
},
watch: {
value: {
Expand All @@ -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());
Expand All @@ -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);
}
},
Expand Down
83 changes: 56 additions & 27 deletions packages/code-editor/packages/useEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, // 最大行数,超过会自动出现滚动条
Expand All @@ -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
}
33 changes: 31 additions & 2 deletions packages/proxy-lib/src/redirectFetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { finalRedirectUrl, matchIgnoresAndRule } from "./common";
import { execSetup } from "./redirectUrlFunc";
import { RefGlobalState } from "./types";

// 共享状态
Expand All @@ -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<Response> {
export default async function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
let fetchMethod: string | undefined | "ANY" = "ANY"
let customInit: RequestInit = init || {}
if (init) {
Expand All @@ -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) {
Expand Down
51 changes: 51 additions & 0 deletions packages/proxy-lib/src/redirectUrlFunc.ts
Original file line number Diff line number Diff line change
@@ -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<Next> {
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 })
}
})
}
24 changes: 22 additions & 2 deletions packages/proxy-lib/src/redirectXHR.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { finalRedirectUrl, fmtURLToString, matchIgnoresAndRule } from "./common";
import { execSetup } from "./redirectUrlFunc";
import { RefGlobalState } from "./types";

// 状态
Expand All @@ -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,
Expand All @@ -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 映射关系
Expand All @@ -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)
}
Expand Down
6 changes: 6 additions & 0 deletions packages/proxy-lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export type IMode = "interceptor" | "redirector"
export type RefGlobalState<T = IGlobalState> = { value: T }
/**响应式类型 */
export type OverrideType = 'json' | 'function'
/**重定向类型 */
export type RedirectType = 'text' | 'function'

type CommonContent = {
/**是否需要匹配 */
Expand Down Expand Up @@ -53,6 +55,10 @@ export type IMatchRedirectContent = {
headers?: IRedirectHeader[]
/**忽略名单 */
ignores?: string[]
/**重定向类型 */
redirect_type?: RedirectType
/**函数响应 */
redirect_func?: string
} & CommonContent

export type IGlobalState = {
Expand Down
13 changes: 13 additions & 0 deletions packages/proxy-lib/types/redirectUrlFunc.d.ts
Original file line number Diff line number Diff line change
@@ -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<Next>;
export {};
6 changes: 6 additions & 0 deletions packages/proxy-lib/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export declare type RefGlobalState<T = IGlobalState> = {
};
/**响应式类型 */
export declare type OverrideType = 'json' | 'function';
/**重定向类型 */
export declare type RedirectType = 'text' | 'function';
declare type CommonContent = {
/**是否需要匹配 */
switch_on: boolean;
Expand Down Expand Up @@ -51,6 +53,10 @@ export declare type IMatchRedirectContent = {
headers?: IRedirectHeader[];
/**忽略名单 */
ignores?: string[];
/**重定向类型 */
redirect_type?: RedirectType;
/**函数响应 */
redirect_func?: string;
} & CommonContent;
export declare type IGlobalState = {
/**全局开关 */
Expand Down
2 changes: 1 addition & 1 deletion packages/shell-chrome/manifest.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/vue-panels/src/views/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit 3a8469a

Please sign in to comment.