├── .gitignore
├── Interceptor.test.json
├── LICENSE
├── README.func.md
├── README.md
├── README.zh.md
├── extension-zips.js
├── media
├── diagram.png
└── operation.gif
├── package.json
├── packages
├── code-editor
│ ├── .babelrc
│ ├── babel.config.js
│ ├── examples
│ │ ├── App.vue
│ │ └── main.js
│ ├── package.json
│ ├── packages
│ │ ├── index.js
│ │ ├── index.vue
│ │ └── useEditor.js
│ ├── public
│ │ └── index.html
│ └── vue.config.js
├── compatibility
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── types.ts
│ ├── tsconfig.json
│ └── types
│ │ ├── index.d.ts
│ │ └── types.d.ts
├── json-editor
│ ├── .babelrc
│ ├── babel.config.js
│ ├── examples
│ │ ├── App.vue
│ │ └── main.js
│ ├── package.json
│ ├── packages
│ │ ├── index.js
│ │ └── index.vue
│ ├── public
│ │ └── index.html
│ └── vue.config.js
├── proxy-lib
│ ├── package.json
│ ├── src
│ │ ├── common.ts
│ │ ├── createFetch.ts
│ │ ├── createXHR.ts
│ │ ├── index.ts
│ │ ├── overrideFunc.ts
│ │ ├── redirectFetch.ts
│ │ ├── redirectUrlFunc.ts
│ │ ├── redirectXHR.ts
│ │ └── types.ts
│ ├── test
│ │ ├── base.test.html
│ │ ├── base.test.interval.html
│ │ ├── redirect.html
│ │ └── test.xhr.post.get.html
│ ├── tsconfig.json
│ ├── tsconfig.node.json
│ ├── types
│ │ ├── common.d.ts
│ │ ├── createFetch.d.ts
│ │ ├── createXHR.d.ts
│ │ ├── index.d.ts
│ │ ├── overrideFunc.d.ts
│ │ ├── redirectFetch.d.ts
│ │ ├── redirectUrlFunc.d.ts
│ │ ├── redirectXHR.d.ts
│ │ └── types.d.ts
│ └── vite.config.ts
├── shared-utils
│ ├── package.json
│ ├── src
│ │ ├── action.ts
│ │ ├── consts.ts
│ │ ├── env.ts
│ │ ├── index.ts
│ │ ├── notice.ts
│ │ └── storage.ts
│ ├── tsconfig.json
│ └── types
│ │ ├── action.d.ts
│ │ ├── consts.d.ts
│ │ ├── env.d.ts
│ │ ├── index.d.ts
│ │ ├── notice.d.ts
│ │ └── storage.d.ts
├── shell-chrome
│ ├── icons
│ │ ├── 128.png
│ │ ├── 128g.png
│ │ ├── 48.png
│ │ └── 48g.png
│ ├── manifest.json
│ ├── package.json
│ ├── src
│ │ ├── consts.ts
│ │ ├── content.ts
│ │ ├── document.ts
│ │ └── service-worker
│ │ │ ├── badge.ts
│ │ │ ├── event.ts
│ │ │ ├── index.ts
│ │ │ ├── init.ts
│ │ │ ├── notice.ts
│ │ │ └── panel.ts
│ ├── tsconfig.json
│ └── webpack.config.js
└── vue-panels
│ ├── .vscode
│ └── settings.json
│ ├── LICENSE
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ ├── src
│ ├── App.vue
│ ├── common
│ │ ├── element-plugin.js
│ │ ├── index.js
│ │ ├── notice.js
│ │ └── store.js
│ ├── index.scss
│ ├── lang
│ │ ├── en.js
│ │ ├── fr.js
│ │ ├── index.js
│ │ ├── ireland.js
│ │ ├── ja.js
│ │ ├── ko.js
│ │ ├── ru.js
│ │ ├── zh_CN.js
│ │ └── zh_TW.js
│ ├── main.js
│ └── views
│ │ ├── index.vue
│ │ ├── interceptor
│ │ ├── jsonEdit.vue
│ │ ├── modal.vue
│ │ ├── table.vue
│ │ └── tag.vue
│ │ └── redirector
│ │ ├── modal.vue
│ │ └── table.vue
│ └── vue.config.js
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── release.js
└── zip
└── .gitkeep
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | # .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
24 | lib
25 | build
26 | *.zip
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Gj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.func.md:
--------------------------------------------------------------------------------
1 | ## 函数式响应
2 |
3 | **函数式响应**面向特殊需求。本质上也是通过代码片段方式注入到浏览器中,相比固定格式它可以更灵活,支持 Promise。你甚至可以直接在函数体内使用**XHR/Fetch**去发起独立请求。但需要注意的是,所有逻辑只能写在**setup**函数中。
4 |
5 | 
6 |
7 | ### 参数定义
8 |
9 | #### req
10 |
11 | 请求参数
12 |
13 | ```ts
14 | type Req = {
15 | url: string;
16 | method: string;
17 | body?: any;
18 | };
19 | ```
20 |
21 | #### res
22 |
23 | 响应参数
24 |
25 | ```ts
26 | type Res = {
27 | status: string; // 系统响应状态码
28 | customStatus: string; // 自定义状态码
29 | response: any;
30 | };
31 | ```
32 |
33 | #### next
34 |
35 | ```ts
36 | type Next = {
37 | override?: string;
38 | status?: string | number;
39 | };
40 | ```
41 |
42 | ### 示例一
43 |
44 | ```js
45 | function setup(req, res, next) {
46 | const RoleType = {
47 | ADMIN: 1,
48 | NORMAL: 2,
49 | };
50 |
51 | let override;
52 | // 需要区分XHR于Fetch请求对应不同数据格式的response
53 | const json = JSON.parse(res.response);
54 | if (json.user.roleType === RoleType.NORMAL) {
55 | json.user.roleType = RoleType.ADMIN;
56 | override = json;
57 | }
58 |
59 | next({ override });
60 | }
61 | ```
62 |
63 | ### 示例二
64 |
65 | ```js
66 | async function setup(req, res, next) {
67 | let override;
68 | if (res.status !== 200) {
69 | // 独立接口请求
70 | const res = await fetch("https://v1.hitokoto.cn/");
71 | const json = await res.json();
72 | override = {
73 | code: 100,
74 | data: json,
75 | };
76 | // 日志输出
77 | console.log("override:", override);
78 | } else override = res.response;
79 |
80 | next({
81 | // 如果不传override,则返回""
82 | override,
83 | // 如果不传status,则返回customStatus
84 | status: 200,
85 | });
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Ajax Proxy
4 |
5 |
6 |
7 | A browser plugin based on Chromium kernel · Tools for Developers · For the modification of web-side response
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | English | [中文](README.zh.md)
32 |
33 |
34 |
35 |
36 | ## When to use
37 |
38 | - When actual data fails to meet expected results, mocking data is needed.
39 | - In development or production stages, verification of exceptional scenarios or edge cases is necessary.
40 | - The frequent changes in interface data hinder the development process.
41 | - When a certain interface returns a 404 error.
42 |
43 | ## Installation
44 |
45 | [Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/ajax-proxy/iladajdkobpmadjfpeginhngnneaoefi)
46 |
47 | [Google Chrome](https://chrome.google.com/webstore/detail/ajax-proxy/jbikjaejnjfbloojafllmdiknfndgljo)
48 |
49 | ## Examples
50 |
51 | Video: [https://www.youtube.com/watch?v=F\_\_7LXBqnvQ&list=PLniy0-3-8-V1ZhsmG6\_\_HdOJBAschGWSt](https://www.youtube.com/watch?v=F__7LXBqnvQ&list=PLniy0-3-8-V1ZhsmG6__HdOJBAschGWSt)
52 |
53 |
54 |
55 | 
56 |
57 | 
58 |
59 | ## FAQ
60 |
61 | 1. Data interception does not work
62 | - You can switch between `interceptor` and `redirector` to solve the Ajax referencing problem
63 | 
64 | - You can select the `Network` section in Developer Tools and disable caching by checking ☑️
65 | 
66 | 2. [Function-based response explanation](README.func.md)
67 |
68 | ## Monorepo
69 |
70 | | Package | Description |
71 | | ------------------------------------------------- | ---------------------------------------- |
72 | | [@proxy/compatibility](./packages/compatibility/) | Old Data Compatibility Library |
73 | | [@proxy/lib](./packages/proxy-lib/) | Manipulating the Ajax Core Logic Library |
74 | | [@proxy/shared-utils](./packages/shared-utils/) | Public Class Libraries |
75 | | [@proxy/shell-chrome](./packages/shell-chrome/) | Browser Extension Library |
76 | | [@proxy/vue-panels](./packages/vue-panels/) | Application Operator Panel |
77 |
78 | ## Use of source code
79 |
80 | 1. Download the corresponding version of [Source code](https://github.com/g0ngjie/ajax-proxy/releases) and unzip it
81 | 2. Open `Developer mode` in your browser
82 | 3. Then just load the unpacked folder
83 |
84 | ## Testing
85 |
86 | You can test it directly in [Swagger](https://petstore.swagger.io/)
87 |
88 | ## ⭐ Stargazers
89 |
90 | Thanks for your support!
91 |
92 | [](https://github.com/g0ngjie/ajax-proxy/stargazers)
93 |
94 | ## License
95 |
96 | Ajax Proxy is [MIT licensed](LICENSE).
97 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Ajax Proxy
4 |
5 |
6 |
7 | 一款基于Chromium内核的浏览器插件 · 面向开发者的工具 · 用于Web端接口数据的修改
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | [English](README.md) | 中文
32 |
33 |
34 |
35 |
36 | ## 适用场景
37 |
38 | - 当实际数据无法达到预期结果时,需要进行 Mock 数据处理。
39 | - 在开发或生产阶段,需要验证异常场景或临界值。
40 | - 开发阶段数据频繁变更,导致页面无法正常联调时。
41 | - 某个接口返回 404 错误时。
42 |
43 |
44 |
45 | ## 安装
46 |
47 | [Edge 版本](https://microsoftedge.microsoft.com/addons/detail/ajax-proxy/iladajdkobpmadjfpeginhngnneaoefi)
48 |
49 | [Chrome 版本](https://chrome.google.com/webstore/detail/ajax-proxy/jbikjaejnjfbloojafllmdiknfndgljo)
50 |
51 | ## 效果展示
52 |
53 | 视频: [https://www.bilibili.com/video/BV1KB4y1j7Gm](https://www.bilibili.com/video/BV1KB4y1j7Gm)
54 |
55 |
56 |
57 | 
58 |
59 | 
60 |
61 | ## 视频介绍
62 |
63 | - [请求方式拦截介绍](https://www.bilibili.com/video/BV1eW4y1H76x/?vd_source=47f2c439d1dcdfef3c5f144bf04b0c01)
64 | - [状态码规则与正则的使用介绍](https://www.bilibili.com/video/BV1LV4y1V7e2/?vd_source=47f2c439d1dcdfef3c5f144bf04b0c01)
65 |
66 | ## 常见问题
67 |
68 | 1. 数据拦截不起作用
69 | - 方法 1: 可以通过切换 `interceptor` 和 `redirector` 来刷新 Ajax 引用问题
70 | 
71 | - 方法 2: 可以在开发者工具中的`网络(network)`里面,通过 ☑️ 禁用缓存
72 | 
73 | 2. [函数方式响应说明](README.func.md)
74 |
75 | ## Monorepo
76 |
77 | | Package | Description |
78 | | ------------------------------------------------- | -------------------- |
79 | | [@proxy/compatibility](./packages/compatibility/) | 老数据兼容库 |
80 | | [@proxy/lib](./packages/proxy-lib/) | 操作 Ajax 核心逻辑库 |
81 | | [@proxy/shared-utils](./packages/shared-utils/) | 通用类库 |
82 | | [@proxy/shell-chrome](./packages/shell-chrome/) | 浏览器扩展库 |
83 | | [@proxy/vue-panels](./packages/vue-panels/) | 应用操作面板 |
84 |
85 | ## 源码使用方式
86 |
87 | 1. 下载对应版本的 [Source code](https://github.com/g0ngjie/ajax-proxy/releases) 解压
88 | 2. 浏览器打开 `开发者模式`
89 | 3. 加载解压后的文件夹
90 |
91 | ## 测试用例
92 |
93 | 下载 [Interceptor.test.json](https://github.com/g0ngjie/ajax-proxy/blob/master/Interceptor.test.json)
94 |
95 | 分别使用在 [掘金](https://juejin.cn/) 首页、[百度翻译](https://fanyi.baidu.com/) 两个网站测试
96 |
97 | 1. 掘金: 直接在首页查看效果;
98 | 2. 百度翻译: 随便翻译点内容即可;
99 | 3. 也可以直接在[Swagger](https://petstore.swagger.io/)中测试
100 |
101 | ## ⭐ Stargazers
102 |
103 | 感谢支持!
104 |
105 | [](https://github.com/g0ngjie/ajax-proxy/stargazers)
106 |
107 | ## License
108 |
109 | Ajax Proxy is [MIT licensed](LICENSE).
110 |
--------------------------------------------------------------------------------
/extension-zips.js:
--------------------------------------------------------------------------------
1 | // require modules
2 | const fs = require('fs')
3 | const path = require('path')
4 | const archiver = require('archiver')
5 | const IS_CI = !!(process.env.CIRCLECI || process.env.GITHUB_ACTIONS)
6 | const ProgressBar = require('progress')
7 | const readDirGlob = require('readdir-glob')
8 | const PKG = require("./package.json")
9 |
10 | const INCLUDE_GLOBS = [
11 | 'icons/**',
12 | 'panels/**',
13 | 'content.js',
14 | 'document.js',
15 | 'manifest.json',
16 | 'service_worker.js',
17 | ]
18 | // SKIP_GLOBS makes glob searches more efficient
19 | const SKIP_DIR_GLOBS = ['node_modules', 'src']
20 |
21 | function bytesToSize(bytes) {
22 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
23 | if (bytes === 0) return '0 Byte'
24 | const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
25 | return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]
26 | }
27 |
28 | (async () => {
29 | await writeZip(`ajax-proxy-${PKG.version}.zip`, 'shell-chrome/build')
30 |
31 | async function writeZip(fileName, packageDir) {
32 | // create a file to stream archive data to.
33 | const output = fs.createWriteStream(path.join(__dirname, 'zip', fileName))
34 | const archive = archiver('zip', {
35 | zlib: { level: 9 }, // Sets the compression level.
36 | })
37 |
38 | if (!IS_CI) {
39 | const status = {
40 | total: 0,
41 | cFile: '...',
42 | cSize: '0 Bytes',
43 | tBytes: 0,
44 | tSize: '0 Bytes',
45 | }
46 |
47 | async function parseFileStats() {
48 | return new Promise((resolve, reject) => {
49 | const globber = readDirGlob(path.join('packages', packageDir),
50 | { pattern: INCLUDE_GLOBS, skip: SKIP_DIR_GLOBS, mark: true, stat: true })
51 | globber.on('match', match => {
52 | if (!match.stat.isDirectory()) status.total++
53 | })
54 | globber.on('error', err => {
55 | reject(err)
56 | })
57 | globber.on('end', () => {
58 | resolve()
59 | })
60 | })
61 | }
62 | await parseFileStats().catch(err => {
63 | console.error(err)
64 | process.exit(1)
65 | })
66 |
67 | const bar = new ProgressBar(`${fileName} @ :tSize [:bar] :current/:total :percent +:cFile@:cSize`, {
68 | width: 18,
69 | incomplete: ' ',
70 | total: status.total,
71 | })
72 | bar.tick(0, status)
73 |
74 | archive.on('entry', (entry) => {
75 | if (!entry.stats.isDirectory()) {
76 | const n = entry.name
77 | status.written++
78 | status.cFile = n.length > 14
79 | ? '...' + n.slice(n.length - 11)
80 | : n
81 | status.cSize = bytesToSize(entry.stats.size)
82 | status.tBytes += entry.stats.size
83 | status.tSize = bytesToSize(status.tBytes)
84 | bar.tick(1, status)
85 | }
86 | })
87 | }
88 |
89 | const end = new Promise((resolve) => {
90 | // listen for all archive data to be written
91 | // 'close' event is fired only when a file descriptor is involved
92 | output.on('close', () => {
93 | if (archive.pointer() < 1000) {
94 | console.warn(`Zip file (${fileName}) is only ${archive.pointer()} bytes`)
95 | }
96 | resolve()
97 | })
98 | })
99 |
100 | // This event is fired when the data source is drained no matter what was the data source.
101 | // It is not part of this library but rather from the NodeJS Stream API.
102 | // @see: https://nodejs.org/api/stream.html#stream_event_end
103 | output.on('end', function () {
104 | 'nothing'
105 | })
106 |
107 | // good practice to catch warnings (ie stat failures and other non-blocking errors)
108 | archive.on('warning', function (err) {
109 | if (err.code !== 'ENOENT') {
110 | // throw error
111 | console.error(err)
112 | process.exit(1)
113 | }
114 | })
115 |
116 | // good practice to catch this error explicitly
117 | archive.on('error', function (err) {
118 | console.error(err)
119 | process.exit(1)
120 | })
121 |
122 | // pipe archive data to the file
123 | archive.pipe(output)
124 |
125 | INCLUDE_GLOBS.forEach(glob => {
126 | // append files from a glob pattern
127 | archive.glob(glob, { cwd: path.join('packages', packageDir), skip: SKIP_DIR_GLOBS })
128 | })
129 |
130 | // finalize the archive (ie we are done appending files but streams have to finish yet)
131 | // 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
132 | archive.finalize()
133 |
134 | await end
135 | }
136 | })()
137 |
--------------------------------------------------------------------------------
/media/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/media/diagram.png
--------------------------------------------------------------------------------
/media/operation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/media/operation.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ajax-proxy",
3 | "private": true,
4 | "description": "Modify your Ajax response to test",
5 | "version": "2.2.10",
6 | "scripts": {
7 | "dev": "pnpm -C ./packages/vue-panels serve",
8 | "watch": "run-p watch:lib watch:chrome",
9 | "watch:lib": "pnpm -C ./packages/proxy-lib watch",
10 | "watch:chrome": "pnpm -C ./packages/shell-chrome watch",
11 | "build": "run-s build:all pkg",
12 | "build:all": "pnpm -F \"./packages/**\" build",
13 | "build:lib": "pnpm -C ./packages/proxy-lib build",
14 | "build:utils": "pnpm -C ./packages/shared-utils build",
15 | "build:chrome": "pnpm -C ./packages/shell-chrome build",
16 | "build:comp": "pnpm -C ./packages/compatibility build",
17 | "build:panels": "pnpm -C ./packages/vue-panels build",
18 | "clean:build": "rm -rf ./packages/proxy-lib/lib ./packages/shell-chrome/build ./packages/shared-utils/lib",
19 | "release": "node release.js",
20 | "pkg": "cp -r ./packages/vue-panels/dist ./packages/shell-chrome/build/panels",
21 | "zip": "node ./extension-zips.js",
22 | "preinstall": "npx only-allow pnpm"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/g0ngjie/ajax-proxy.git"
27 | },
28 | "engines": {
29 | "node": ">=16.16.0"
30 | },
31 | "keywords": [],
32 | "author": {
33 | "name": "Gj",
34 | "email": "gongjie0422@163.com"
35 | },
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/g0ngjie/ajax-proxy/issues"
39 | },
40 | "homepage": "https://github.com/g0ngjie/ajax-proxy#readme",
41 | "devDependencies": {
42 | "archiver": "^5.3.1",
43 | "inquirer": "6.2.0",
44 | "npm-run-all": "^4.1.5",
45 | "progress": "^2.0.3",
46 | "readdir-glob": "^1.1.2",
47 | "semver": "^7.3.7",
48 | "typescript": "^4.6.4"
49 | }
50 | }
--------------------------------------------------------------------------------
/packages/code-editor/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "component",
5 | {
6 | // 组件库的名字,需要和 package.json 里的 name 相同;
7 | "libraryName": "@proxy/code-editor",
8 | // 存放组件的文件夹,如果不想配置此项,默认文件夹的名字为 lib;
9 | "libDir": "lib"
10 | }
11 | ]
12 | ]
13 | }
--------------------------------------------------------------------------------
/packages/code-editor/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset']
3 | }
4 |
--------------------------------------------------------------------------------
/packages/code-editor/examples/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
code-editor demo
4 |
10 |
11 |
12 |
13 |
14 |
37 |
--------------------------------------------------------------------------------
/packages/code-editor/examples/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | new Vue({
5 | render: h => h(App),
6 | }).$mount('#app')
7 |
--------------------------------------------------------------------------------
/packages/code-editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/code-editor",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "A code editor of vue",
6 | "main": "lib/index.common.js",
7 | "files": [
8 | "lib"
9 | ],
10 | "scripts": {
11 | "build": "vue-cli-service build --target lib --name index --dest lib packages/index.js",
12 | "serve": "vue-cli-service serve"
13 | },
14 | "dependencies": {
15 | "ace-builds": "^1.13.1",
16 | "core-js": "^3.6.5",
17 | "vue": "2.6.10"
18 | },
19 | "eslintConfig": {
20 | "root": true,
21 | "env": {
22 | "node": true
23 | },
24 | "extends": [
25 | "plugin:vue/essential",
26 | "eslint:recommended"
27 | ],
28 | "parserOptions": {
29 | "parser": "babel-eslint"
30 | },
31 | "rules": {}
32 | },
33 | "browserslist": [
34 | "> 1%",
35 | "last 2 versions",
36 | "not dead"
37 | ],
38 | "devDependencies": {
39 | "@vue/cli-plugin-babel": "~4.5.0",
40 | "@vue/cli-service": "~4.5.0",
41 | "babel-eslint": "^10.1.0",
42 | "eslint": "^6.7.2",
43 | "eslint-plugin-vue": "^6.2.2",
44 | "vue-template-compiler": "2.6.10"
45 | }
46 | }
--------------------------------------------------------------------------------
/packages/code-editor/packages/index.js:
--------------------------------------------------------------------------------
1 | import VueCodeEditor from "./index.vue";
2 |
3 | // 动态引入src目录下的所有index.js
4 | function install(Vue) {
5 | Vue.component(VueCodeEditor.name, VueCodeEditor);
6 | }
7 |
8 | if (typeof window !== "undefined" && window.Vue) {
9 | install(window.Vue);
10 | }
11 |
12 | export default { install };
13 | export { VueCodeEditor }
--------------------------------------------------------------------------------
/packages/code-editor/packages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/packages/code-editor/packages/useEditor.js:
--------------------------------------------------------------------------------
1 | import ace from "ace-builds";
2 | import "ace-builds/webpack-resolver"; // 在 webpack 环境中使用必须要导入
3 | // 根据自己的需求按需引入
4 | import "ace-builds/src-noconflict/ext-language_tools";
5 | import "ace-builds/src-noconflict/theme-monokai"; // 主题
6 | import "ace-builds/src-noconflict/mode-javascript"; // 语言模式
7 | import "ace-builds/src-noconflict/snippets/javascript"; //代码提示
8 |
9 | // https://ace.c9.io/#nav=howto
10 | // 初始化
11 | export function useInit(container, type) {
12 | // 初始化
13 | const target = ace.edit(container, {
14 | maxLines: 20, // 最大行数,超过会自动出现滚动条
15 | minLines: 20, // 最小行数,还未到最大行数时,编辑器会自动伸缩大小
16 | fontSize: 14, // 编辑器内字体大小
17 | theme: "ace/theme/monokai", // 主题
18 | mode: "ace/mode/javascript", // 默认设置的语言模式
19 | tabSize: 4, // 制表符设置为 4 个空格大小
20 | });
21 | target.setOptions({
22 | enableSnippets: true,
23 | enableLiveAutocompletion: true,
24 | enableBasicAutocompletion: true,
25 | });
26 |
27 | // 自定义提示
28 | customCompletions(target, type)
29 |
30 | return target;
31 | }
32 |
33 | // 自定义提示
34 | function customCompletions(target, type = 'interceptor') {
35 | if (type === 'interceptor')
36 | target.completers.push({
37 | getCompletions: function (state, session, pos, prefix, callback) {
38 | if (prefix.length === 0) {
39 | callback(null, []);
40 | return;
41 | }
42 | callback(null, [
43 | { meta: 'AjaxProxy::Ctx.req', caption: 'req.url: string', value: 'req.url', score: 100 },
44 | { meta: 'AjaxProxy::Ctx.req', caption: 'req.method: string', value: 'req.method', score: 100 },
45 | { meta: 'AjaxProxy::Ctx.req', caption: 'req.body?: any', value: 'req.body', score: 100 },
46 | { meta: 'AjaxProxy::Ctx.res', caption: 'res.status: string', value: 'res.status', score: 100 },
47 | { meta: 'AjaxProxy::Ctx.res', caption: 'res.customStatus: string', value: 'res.customStatus', score: 100 },
48 | { meta: 'AjaxProxy::Ctx.res', caption: 'res.response: any', value: 'res.response', score: 100 },
49 | {
50 | meta: 'AjaxProxy::Next',
51 | caption: 'next({ override?: string, status?: string | number })',
52 | value: 'next({ override: "", status: "" });',
53 | score: 100
54 | },
55 | ]);
56 | },
57 | });
58 | else
59 | target.completers.push({
60 | getCompletions: function (state, session, pos, prefix, callback) {
61 | if (prefix.length === 0) {
62 | callback(null, []);
63 | return;
64 | }
65 | callback(null, [
66 | { meta: 'AjaxProxy::Ctx.req', caption: 'req.url: string', value: 'req.url', score: 100 },
67 | { meta: 'AjaxProxy::Ctx.req', caption: 'req.method: string', value: 'req.method', score: 100 },
68 | {
69 | meta: 'AjaxProxy::Next',
70 | caption: 'next({ url: string, headers?: { [key: string]: string } })',
71 | value: 'next({ url: req.url });',
72 | score: 100
73 | },
74 | ]);
75 | },
76 | });
77 | }
78 |
79 | // 默认内容
80 | export function getDefaultContent(type = "interceptor") {
81 | let defaultContent = type === 'interceptor' ?
82 | `
83 | /** @see https://github.com/g0ngjie/ajax-proxy/blob/master/README.func.md */
84 | function setup(req, res, next) {
85 | // TODO...
86 | // type Next = { override?: string, status?: string | number }
87 | next({ override: "", status: "" });
88 | }
89 | ` :
90 | `
91 | /** @see https://github.com/g0ngjie/ajax-proxy/blob/master/README.func.md */
92 | function setup(
93 | req, /** req: { url: string, method: string }*/
94 | next /**{ url: string, headers?: { [key: string]: string } }*/
95 | ) {
96 | // TODO...
97 | next({ url: "" });
98 | }
99 | `
100 | return defaultContent
101 | }
102 |
--------------------------------------------------------------------------------
/packages/code-editor/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= htmlWebpackPlugin.options.title %>
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/code-editor/vue.config.js:
--------------------------------------------------------------------------------
1 |
2 | const isProduction = process.env.NODE_ENV === "production";
3 |
4 | module.exports = {
5 | publicPath: './',
6 | runtimeCompiler: true,
7 | // 修改 src 为 examples
8 | pages: {
9 | index: {
10 | entry: "examples/main.js",
11 | template: "public/index.html",
12 | filename: "index.html",
13 | },
14 | },
15 | productionSourceMap: false,
16 | configureWebpack: (config) => {
17 | if (isProduction) {
18 | config.module.rules.push({
19 | test: /\.mjs$/,
20 | include: /node_modules/,
21 | type: "javascript/auto"
22 | });
23 | // 取消webpack警告的性能提示
24 | config.performance = {
25 | hints: "warning",
26 | //入口起点的最大体积
27 | maxEntrypointSize: 50000000,
28 | //生成文件的最大体积
29 | maxAssetSize: 30000000,
30 | };
31 | }
32 | },
33 | }
--------------------------------------------------------------------------------
/packages/compatibility/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/compatibility",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Ajax Proxy 新老数据兼容库",
6 | "main": "lib/index.js",
7 | "types": "types/index.d.ts",
8 | "scripts": {
9 | "watch": "tsc -w",
10 | "build": "rm -rf lib types && tsc"
11 | },
12 | "devDependencies": {
13 | "@types/node": "^18.0.1"
14 | },
15 | "keywords": [],
16 | "author": {
17 | "name": "Gj",
18 | "email": "gongjie0422@163.com"
19 | },
20 | "license": "MIT",
21 | "dependencies": {
22 | "@proxy/lib": "workspace:^1.0.0"
23 | }
24 | }
--------------------------------------------------------------------------------
/packages/compatibility/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MappingOldKeys,
3 | NewGLobalStateStruct,
4 | NewUploadStruct,
5 | OldGLobalStateStruct,
6 | OldInterceptorStruct,
7 | OldRedirectorStruct,
8 | OldUploadStruct
9 | } from "./types";
10 |
11 | // 判断是否为老GlobalState 数据
12 | function isOldGlobalState(x: any): x is OldGLobalStateStruct {
13 | return x && x.hasOwnProperty("globalSwitchOn")
14 | }
15 |
16 | // 判断是否为老上传数据结构
17 | function isOldUploadState(x: any): x is OldUploadStruct {
18 | return x &&
19 | (
20 | x.hasOwnProperty("proxy_routes") ||
21 | x.hasOwnProperty("redirect") ||
22 | x.hasOwnProperty("lang")
23 | )
24 | }
25 |
26 | // 判断是否为老 拦截列表
27 | function isOldInterceptor(x: any): x is OldInterceptorStruct[] {
28 | if (!x) return false
29 | if (!Array.isArray(x)) return false
30 | for (let i = 0; i < x.length; i++) {
31 | const target = x[i];
32 | if (!target.hasOwnProperty("match")) return false
33 | }
34 | return true
35 | }
36 |
37 | // 判断是否为老 重定向列表
38 | function isOldRedirector(x: any): x is OldRedirectorStruct[] {
39 | if (!x) return false
40 | if (!Array.isArray(x)) return false
41 | for (let i = 0; i < x.length; i++) {
42 | const target = x[i];
43 | if (!target.hasOwnProperty("redirect")) return false
44 | }
45 | return true
46 | }
47 |
48 | // 转义拦截列表
49 | function interceptorConversion(target: any) {
50 | if (isOldInterceptor(target)) {
51 | const newInterceptor: NewGLobalStateStruct['interceptor_matching_content'] = target.map(interceptor => {
52 | const {
53 | id = "", switchOn = false, filterType = "normal", method = "ANY", remark = "",
54 | match = "", tagId = "", statusCode = "200", override = "", hit = 0
55 | } = interceptor
56 | return {
57 | id, switch_on: switchOn, filter_type: filterType, method, remark,
58 | match_url: match, tagId, status_code: statusCode, override, hit
59 | }
60 | })
61 | return newInterceptor
62 | }
63 | return target
64 | }
65 |
66 | // 转义重定向列表
67 | function redirectorConversion(target: any) {
68 | if (isOldRedirector(target)) {
69 | const newInterceptor: NewGLobalStateStruct['redirector_matching_content'] = target.map(interceptor => {
70 | const {
71 | id = "", switchOn = false, filterType = "normal", method = "ANY", remark = "",
72 | domain = "", redirect = "", headers = [], whitelist = []
73 | } = interceptor
74 | return {
75 | id, switch_on: switchOn, filter_type: filterType, method, remark,
76 | domain, redirect_url: redirect, headers, ignores: whitelist
77 | }
78 | })
79 | return newInterceptor
80 | }
81 | return target
82 | }
83 |
84 | /**
85 | * 数据转换
86 | * content-script 使用, 页面加载时
87 | * 由于 v2.1.0 版本 项目重构,数据属性有变动。
88 | * 需要将老数据预处理为新数据结构体
89 | */
90 | export function onLoadForDataConversion(target: T) {
91 | if (isOldGlobalState(target)) {
92 | // 需要转换数据格式
93 | const {
94 | globalSwitchOn,
95 | mode,
96 | proxy_routes,
97 | redirect
98 | } = target
99 |
100 | // 转义 拦截列表
101 | const interceptors = interceptorConversion(proxy_routes)
102 | // 转义 重定向列表
103 | const redirectors = redirectorConversion(redirect)
104 | const newData: NewGLobalStateStruct = {
105 | global_on: globalSwitchOn,
106 | mode,
107 | interceptor_matching_content: interceptors,
108 | redirector_matching_content: redirectors
109 | }
110 | // 旧数据对应key,用于上游做旧数据清理使用
111 | const changeKeywords: MappingOldKeys = ["globalSwitchOn", "mode", "proxy_routes", "redirect"]
112 | return {
113 | changed: true,
114 | data: newData,
115 | changeKeywords
116 | }
117 | }
118 |
119 | // 新数据结构,无需转换
120 | return {
121 | changed: false,
122 | data: target,
123 | changeKeywords: []
124 | }
125 | }
126 |
127 | /**
128 | * 数据转换
129 | * panels 使用, 数据上传时
130 | * 由于 v2.1.0 版本 项目重构,数据属性有变动。
131 | * 需要将老数据预处理为新数据结构体
132 | * 原数据格式: { lang, proxy_routes, tags, mode, redirect }
133 | * 转义为: { language, mode, tags, interceptors, redirectors }
134 | */
135 | export function onUploadForDataConversion(target: OldUploadStruct | NewUploadStruct) {
136 | if (isOldUploadState(target)) {
137 | const {
138 | lang = 'en', proxy_routes = [], redirect = [], mode = 'interceptor', tags = []
139 | } = target
140 |
141 | // 转义 拦截列表
142 | const interceptors = interceptorConversion(proxy_routes)
143 | // 转义 重定向列表
144 | const redirectors = redirectorConversion(redirect)
145 | const newData: NewUploadStruct = {
146 | language: lang,
147 | mode,
148 | tags,
149 | interceptors,
150 | redirectors
151 | }
152 | return newData
153 | }
154 | // 新数据结构,无需转换
155 | return target
156 | }
157 |
--------------------------------------------------------------------------------
/packages/compatibility/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IFilterType,
3 | IGlobalState,
4 | IMatchInterceptorContent,
5 | IMatchRedirectContent,
6 | IMode,
7 | IRedirectHeader,
8 | IRequestMethod
9 | } from "@proxy/lib/types/types";
10 |
11 | /**
12 | * 标签结构体
13 | * 新老数据没变化
14 | * {id: "633f895c49960dd6iuyz3s3t", name: "asdf", used: false}
15 | * */
16 | type TagStruct = {
17 | id: string
18 | name: string
19 | used: boolean
20 | }
21 |
22 | type OldCommonStruct = {
23 | id: string
24 | /**是否需要匹配 */
25 | switchOn: boolean
26 | /**匹配规则 */
27 | filterType: IFilterType
28 | /**请求协议 */
29 | method?: IRequestMethod
30 | /**备注 */
31 | remark?: string;
32 | }
33 |
34 | /**老数据- 拦截列表 */
35 | export type OldInterceptorStruct = {
36 | /**匹配目标URL */
37 | match: string
38 | /**标签id */
39 | tagId?: TagStruct['id']
40 | /**状态码 */
41 | statusCode?: string;
42 | /**命中率 */
43 | hit?: number;
44 | /**需要覆盖的内容 */
45 | override?: string;
46 | } & OldCommonStruct
47 |
48 | /**老数据- 重定向列表 */
49 | export type OldRedirectorStruct = {
50 | /**域名 */
51 | domain: string
52 | /**重定向地址 */
53 | redirect: string
54 | /**请求头 */
55 | headers?: IRedirectHeader[];
56 | /**忽略名单 */
57 | whitelist?: string[];
58 | } & OldCommonStruct
59 |
60 | /**
61 | * 老数据 GlobalState 结构体
62 | */
63 | export type OldGLobalStateStruct = {
64 | /**全局开关 */
65 | globalSwitchOn: boolean
66 | /**模式 */
67 | mode: IMode
68 | /**拦截规则列表 */
69 | proxy_routes?: OldInterceptorStruct[]
70 | /**重定向规则列表 */
71 | redirect?: OldRedirectorStruct[]
72 | }
73 |
74 | /**
75 | * 旧数据 key 值映射
76 | */
77 | export type MappingOldKeys = (keyof OldGLobalStateStruct)[]
78 |
79 | /**
80 | * 新GlobalState数据结构体
81 | */
82 | export type NewGLobalStateStruct = IGlobalState & {
83 | /**拦截规则列表 */
84 | interceptor_matching_content?: (
85 | IMatchInterceptorContent & {
86 | id: string
87 | tagId?: TagStruct['id']
88 | }
89 | )[]
90 | redirector_matching_content?: (
91 | IMatchRedirectContent & {
92 | id: string
93 | }
94 | )[]
95 | }
96 |
97 | /**
98 | * 上传 - 老数据格式
99 | */
100 | export type OldUploadStruct = {
101 | /**语言 */
102 | lang: string
103 | /**拦截规则列表 */
104 | proxy_routes?: OldInterceptorStruct[]
105 | /**重定向规则列表 */
106 | redirect?: OldRedirectorStruct[]
107 | /**模式 */
108 | mode: IMode
109 | /**标签 */
110 | tags?: TagStruct[]
111 | }
112 |
113 | /**
114 | * 上传 - 新数据格式
115 | */
116 | export type NewUploadStruct = {
117 | language: string
118 | mode: IMode
119 | tags: TagStruct[]
120 | interceptors?: (
121 | IMatchInterceptorContent & {
122 | id: string
123 | tagId?: TagStruct['id']
124 | }
125 | )[]
126 | redirectors?: (
127 | IMatchRedirectContent & {
128 | id: string
129 | }
130 | )[]
131 | }
--------------------------------------------------------------------------------
/packages/compatibility/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "types": [
11 | "node"
12 | ],
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "sourceMap": true,
16 | "preserveWatchOutput": true,
17 | // Strict
18 | "noImplicitAny": false,
19 | "noImplicitThis": true,
20 | "alwaysStrict": true,
21 | "strictBindCallApply": true,
22 | "strictFunctionTypes": true,
23 | "declaration": true,
24 | "declarationDir": "types",
25 | "outDir": "lib"
26 | },
27 | "include": [
28 | "src/**/*",
29 | ],
30 | "exclude": [
31 | "node_modules"
32 | ]
33 | }
--------------------------------------------------------------------------------
/packages/compatibility/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { MappingOldKeys, NewGLobalStateStruct, NewUploadStruct, OldGLobalStateStruct, OldUploadStruct } from "./types";
2 | /**
3 | * 数据转换
4 | * content-script 使用, 页面加载时
5 | * 由于 v2.1.0 版本 项目重构,数据属性有变动。
6 | * 需要将老数据预处理为新数据结构体
7 | */
8 | export declare function onLoadForDataConversion(target: T): {
11 | changed: boolean;
12 | data: NewGLobalStateStruct;
13 | changeKeywords: MappingOldKeys;
14 | } | {
15 | changed: boolean;
16 | data: T;
17 | changeKeywords: never[];
18 | };
19 | /**
20 | * 数据转换
21 | * panels 使用, 数据上传时
22 | * 由于 v2.1.0 版本 项目重构,数据属性有变动。
23 | * 需要将老数据预处理为新数据结构体
24 | * 原数据格式: { lang, proxy_routes, tags, mode, redirect }
25 | * 转义为: { language, mode, tags, interceptors, redirectors }
26 | */
27 | export declare function onUploadForDataConversion(target: OldUploadStruct | NewUploadStruct): NewUploadStruct;
28 |
--------------------------------------------------------------------------------
/packages/compatibility/types/types.d.ts:
--------------------------------------------------------------------------------
1 | import { IFilterType, IGlobalState, IMatchInterceptorContent, IMatchRedirectContent, IMode, IRedirectHeader, IRequestMethod } from "@proxy/lib/types/types";
2 | /**
3 | * 标签结构体
4 | * 新老数据没变化
5 | * {id: "633f895c49960dd6iuyz3s3t", name: "asdf", used: false}
6 | * */
7 | declare type TagStruct = {
8 | id: string;
9 | name: string;
10 | used: boolean;
11 | };
12 | declare type OldCommonStruct = {
13 | id: string;
14 | /**是否需要匹配 */
15 | switchOn: boolean;
16 | /**匹配规则 */
17 | filterType: IFilterType;
18 | /**请求协议 */
19 | method?: IRequestMethod;
20 | /**备注 */
21 | remark?: string;
22 | };
23 | /**老数据- 拦截列表 */
24 | export declare type OldInterceptorStruct = {
25 | /**匹配目标URL */
26 | match: string;
27 | /**标签id */
28 | tagId?: TagStruct['id'];
29 | /**状态码 */
30 | statusCode?: string;
31 | /**命中率 */
32 | hit?: number;
33 | /**需要覆盖的内容 */
34 | override?: string;
35 | } & OldCommonStruct;
36 | /**老数据- 重定向列表 */
37 | export declare type OldRedirectorStruct = {
38 | /**域名 */
39 | domain: string;
40 | /**重定向地址 */
41 | redirect: string;
42 | /**请求头 */
43 | headers?: IRedirectHeader[];
44 | /**忽略名单 */
45 | whitelist?: string[];
46 | } & OldCommonStruct;
47 | /**
48 | * 老数据 GlobalState 结构体
49 | */
50 | export declare type OldGLobalStateStruct = {
51 | /**全局开关 */
52 | globalSwitchOn: boolean;
53 | /**模式 */
54 | mode: IMode;
55 | /**拦截规则列表 */
56 | proxy_routes?: OldInterceptorStruct[];
57 | /**重定向规则列表 */
58 | redirect?: OldRedirectorStruct[];
59 | };
60 | /**
61 | * 旧数据 key 值映射
62 | */
63 | export declare type MappingOldKeys = (keyof OldGLobalStateStruct)[];
64 | /**
65 | * 新GlobalState数据结构体
66 | */
67 | export declare type NewGLobalStateStruct = IGlobalState & {
68 | /**拦截规则列表 */
69 | interceptor_matching_content?: (IMatchInterceptorContent & {
70 | id: string;
71 | tagId?: TagStruct['id'];
72 | })[];
73 | redirector_matching_content?: (IMatchRedirectContent & {
74 | id: string;
75 | })[];
76 | };
77 | /**
78 | * 上传 - 老数据格式
79 | */
80 | export declare type OldUploadStruct = {
81 | /**语言 */
82 | lang: string;
83 | /**拦截规则列表 */
84 | proxy_routes?: OldInterceptorStruct[];
85 | /**重定向规则列表 */
86 | redirect?: OldRedirectorStruct[];
87 | /**模式 */
88 | mode: IMode;
89 | /**标签 */
90 | tags?: TagStruct[];
91 | };
92 | /**
93 | * 上传 - 新数据格式
94 | */
95 | export declare type NewUploadStruct = {
96 | language: string;
97 | mode: IMode;
98 | tags: TagStruct[];
99 | interceptors?: (IMatchInterceptorContent & {
100 | id: string;
101 | tagId?: TagStruct['id'];
102 | })[];
103 | redirectors?: (IMatchRedirectContent & {
104 | id: string;
105 | })[];
106 | };
107 | export {};
108 |
--------------------------------------------------------------------------------
/packages/json-editor/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "component",
5 | {
6 | // 组件库的名字,需要和 package.json 里的 name 相同;
7 | "libraryName": "@proxy/json-editor",
8 | // 存放组件的文件夹,如果不想配置此项,默认文件夹的名字为 lib;
9 | "libDir": "lib"
10 | }
11 | ]
12 | ]
13 | }
--------------------------------------------------------------------------------
/packages/json-editor/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset']
3 | }
4 |
--------------------------------------------------------------------------------
/packages/json-editor/examples/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
json-editor demo
4 |
12 |
13 |
14 |
15 |
16 |
17 |
49 |
--------------------------------------------------------------------------------
/packages/json-editor/examples/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | new Vue({
5 | render: h => h(App),
6 | }).$mount('#app')
7 |
--------------------------------------------------------------------------------
/packages/json-editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/json-editor",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "A json editor of vue",
6 | "main": "lib/index.common.js",
7 | "files": [
8 | "lib"
9 | ],
10 | "scripts": {
11 | "build": "vue-cli-service build --target lib --name index --dest lib packages/index.js",
12 | "serve": "vue-cli-service serve"
13 | },
14 | "dependencies": {
15 | "core-js": "^3.6.5",
16 | "jsoneditor": "^9.9.2",
17 | "vue": "^2.6.11"
18 | },
19 | "eslintConfig": {
20 | "root": true,
21 | "env": {
22 | "node": true
23 | },
24 | "extends": [
25 | "plugin:vue/essential",
26 | "eslint:recommended"
27 | ],
28 | "parserOptions": {
29 | "parser": "babel-eslint"
30 | },
31 | "rules": {}
32 | },
33 | "browserslist": [
34 | "> 1%",
35 | "last 2 versions",
36 | "not dead"
37 | ],
38 | "devDependencies": {
39 | "@vue/cli-plugin-babel": "~4.5.0",
40 | "@vue/cli-service": "~4.5.0",
41 | "babel-eslint": "^10.1.0",
42 | "eslint": "^6.7.2",
43 | "eslint-plugin-vue": "^6.2.2",
44 | "vue-template-compiler": "2.6.11"
45 | }
46 | }
--------------------------------------------------------------------------------
/packages/json-editor/packages/index.js:
--------------------------------------------------------------------------------
1 | import VueJsonEditor from "./index.vue";
2 |
3 | // 动态引入src目录下的所有index.js
4 | function install(Vue) {
5 | Vue.component(VueJsonEditor.name, VueJsonEditor);
6 | }
7 |
8 | if (typeof window !== "undefined" && window.Vue) {
9 | install(window.Vue);
10 | }
11 |
12 | export default { install };
13 | export { VueJsonEditor }
--------------------------------------------------------------------------------
/packages/json-editor/packages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
107 |
108 |
147 |
--------------------------------------------------------------------------------
/packages/json-editor/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= htmlWebpackPlugin.options.title %>
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/packages/json-editor/vue.config.js:
--------------------------------------------------------------------------------
1 |
2 | const isProduction = process.env.NODE_ENV === "production";
3 |
4 | module.exports = {
5 | publicPath: './',
6 | runtimeCompiler: true,
7 | // 修改 src 为 examples
8 | pages: {
9 | index: {
10 | entry: "examples/main.js",
11 | template: "public/index.html",
12 | filename: "index.html",
13 | },
14 | },
15 | productionSourceMap: false,
16 | configureWebpack: (config) => {
17 | if (isProduction) {
18 | config.module.rules.push({
19 | test: /\.mjs$/,
20 | include: /node_modules/,
21 | type: "javascript/auto"
22 | });
23 | // 取消webpack警告的性能提示
24 | config.performance = {
25 | hints: "warning",
26 | //入口起点的最大体积
27 | maxEntrypointSize: 50000000,
28 | //生成文件的最大体积
29 | maxAssetSize: 30000000,
30 | };
31 | }
32 | },
33 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/lib",
3 | "version": "1.0.0",
4 | "description": "ajax interceptor lib",
5 | "main": "lib/index.umd.js",
6 | "module": "lib/index.esm.js",
7 | "typings": "types/index.d.ts",
8 | "scripts": {
9 | "watch": "run-p watch:ts watch:vite",
10 | "build": "rm -rf types lib && tsc && vite build",
11 | "watch:ts": "tsc --watch",
12 | "watch:vite": "vite build --watch"
13 | },
14 | "files": [
15 | "lib",
16 | "types"
17 | ],
18 | "keywords": [],
19 | "author": {
20 | "name": "Gj",
21 | "email": "gongjie0422@163.com"
22 | },
23 | "license": "MIT",
24 | "devDependencies": {
25 | "vite": "^2.9.13"
26 | },
27 | "dependencies": {
28 | "@proxy/shared-utils": "workspace:^1.0.0"
29 | }
30 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/src/common.ts:
--------------------------------------------------------------------------------
1 | import { IMatchInterceptorContent, IMatchRedirectContent } from "./types";
2 | import { NoticeTo } from "@proxy/shared-utils";
3 |
4 | // match_url规则匹配
5 | export function maybeMatching(
6 | url: string,
7 | match: string,
8 | type: IMatchInterceptorContent['filter_type'] = "normal"
9 | ) {
10 | let matched = false;
11 | switch (type) {
12 | // 普通匹配规则
13 | case "normal":
14 | matched = url.includes(match);
15 | break;
16 | // 正则匹配规则
17 | case "regex":
18 | try {
19 | url.match(new RegExp(match, "i")) && (matched = true);
20 | } catch (error) { }
21 | break;
22 | }
23 | return matched;
24 | }
25 |
26 | /**
27 | * 规则过滤
28 | * 域名判断
29 | * 规则类型判断
30 | * 忽略列表判断
31 | */
32 | // 再根据规则匹配m
33 | export function matchIgnoresAndRule(
34 | url: string,
35 | match: string,
36 | type: IMatchRedirectContent['filter_type'] = "normal",
37 | ignores: IMatchRedirectContent['ignores']
38 | ) {
39 | let matched = false;
40 | // 先判断是否白名单过滤
41 | const findOne = ignores?.find((target) => url.includes(target));
42 | // 白名单过滤掉
43 | if (findOne) return matched;
44 | matched = maybeMatching(url, match, type);
45 | return matched;
46 | }
47 |
48 | function isURLObject(x: unknown): x is URL {
49 | return x &&
50 | (x as any).hasOwnProperty('host') &&
51 | (x as any).hasOwnProperty('hash')
52 | }
53 |
54 | /**格式化URL 返回 string */
55 | export function fmtURLToString(url: string | URL) {
56 | if (isURLObject(url)) return url.toString()
57 | else return url
58 | }
59 |
60 | /**获取重定向URL */
61 | export function finalRedirectUrl(
62 | url: string,
63 | match: string,
64 | redirect_url: string,
65 | type: IMatchRedirectContent['filter_type'] = "normal"
66 | ) {
67 | let finalUrl = url;
68 | switch (type) {
69 | // 普通替换
70 | case "normal":
71 | finalUrl = url.replace(match, redirect_url);
72 | break;
73 | // 正则替换
74 | case "regex":
75 | finalUrl = url.replace(new RegExp(match, "i"), redirect_url);
76 | break;
77 | }
78 | return finalUrl;
79 | }
80 |
81 | // 通知到 content 命中统计
82 | export function notice(url: string, match_url: string, method: string) {
83 | window.dispatchEvent(
84 | new CustomEvent(NoticeTo.CONTENT, {
85 | // 注意: 这里一般只用到 match_url 和 method
86 | // url为真实请求地址,使用的话 会导致 hit无法正确匹配,
87 | // 例如 fanyi.baidu.com 接口参数在url上面,match_url 为原始 url
88 | detail: { url, match_url, method },
89 | })
90 | );
91 | }
92 |
93 | export const warn = (...args: any[]) => console.warn(...args)
--------------------------------------------------------------------------------
/packages/proxy-lib/src/createFetch.ts:
--------------------------------------------------------------------------------
1 | import { maybeMatching, notice } from "./common";
2 | import { execSetup, getCtx } from "./overrideFunc";
3 | import { OverrideType, RefGlobalState } from "./types";
4 |
5 | // 共享状态
6 | let globalState: RefGlobalState
7 | // fetch 副本
8 | export const OriginFetch = window.fetch.bind(window)
9 | // 初始化共享状态
10 | export const initInterceptorFetchState = (state: RefGlobalState) => globalState = state
11 |
12 | function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise {
13 | let fetchMethod: string | undefined | "ANY" = "ANY"
14 | if (init) {
15 | fetchMethod = init.method?.toUpperCase() || "ANY"
16 | }
17 | return OriginFetch(input, init).then(async (response: Response) => {
18 | let txt: string | undefined;
19 | let status = response.status
20 | let statusText = response.statusText
21 | let _overrideType: OverrideType = "json"
22 | for (let i = 0; i < globalState.value.interceptor_matching_content.length; i++) {
23 | const target = globalState.value.interceptor_matching_content[i];
24 | const {
25 | switch_on = true,
26 | match_url,
27 | override = "",
28 | filter_type,
29 | method,
30 | status_code = "200",
31 | override_type = "json",
32 | override_func = ""
33 | } = target
34 | // 是否需要匹配
35 | if (switch_on && match_url) {
36 | // 判断是否存在协议匹配
37 | if (method && ![fetchMethod, "ANY"].includes(method.toUpperCase())) continue
38 | // 规则匹配
39 | const matched = maybeMatching(response.url, match_url, filter_type);
40 | if (!matched) continue // 退出当前循环
41 | _overrideType = override_type
42 | if (override_type === "function") {
43 | const ctx = getCtx(
44 | response.url,
45 | init?.method?.toUpperCase() || "GET",
46 | response.status,
47 | status_code,
48 | init?.body,
49 | response
50 | )
51 | const payload = await execSetup(ctx, override_func)
52 | if (payload.override)
53 | txt = typeof payload.override === "string" ? payload.override : JSON.stringify(payload.override);
54 | status = +payload.status!
55 | statusText = payload.status + ""
56 | } else {
57 | // 修改响应
58 | txt = typeof override === "string" ? override : JSON.stringify(override);
59 | // 修改状态码
60 | status = +status_code
61 | statusText = status_code
62 | }
63 | // 通知
64 | notice(response.url, match_url, fetchMethod || "")
65 | }
66 | }
67 |
68 | // 返回原始响应
69 | if (!globalState.value.global_on || (!txt && _overrideType !== 'function')) return response
70 |
71 | const stream = new ReadableStream({
72 | start(controller) {
73 | controller.enqueue(new TextEncoder().encode(txt));
74 | controller.close();
75 | },
76 | });
77 | const newResponse = new Response(stream, {
78 | headers: response.headers,
79 | status: status,
80 | statusText: statusText,
81 | });
82 | const proxy = new Proxy(newResponse, {
83 | get: function (target, prop) {
84 | const checkKeys = ['ok', 'redirected', 'type', 'url', 'useFinalURL', 'body', 'bodyUsed'];
85 | if (checkKeys.includes(prop as string)) {
86 | return Reflect.get(target, prop);
87 | }
88 | return Reflect.get(target, prop);
89 | },
90 | });
91 |
92 | for (let key in proxy) {
93 | // 获取proxy key 对应实例
94 | const target = Reflect.get(proxy, key);
95 | // 判断实例是否为 Response 实例
96 | if (typeof target === "function") {
97 | // 将新的 Response 实例绑定到 proxy 实例上
98 | Reflect.set(proxy, key, target.bind(newResponse));
99 | }
100 | }
101 | return proxy;
102 | });
103 | }
104 |
105 | export default CustomFetch
--------------------------------------------------------------------------------
/packages/proxy-lib/src/createXHR.ts:
--------------------------------------------------------------------------------
1 | import { maybeMatching, notice } from "./common";
2 | import { execSetup, getCtx } from "./overrideFunc";
3 | import { RefGlobalState } from "./types";
4 |
5 | // 共享状态
6 | let globalState: RefGlobalState
7 | // XMLHttpRequest 副本
8 | export const OriginXHR = window.XMLHttpRequest;
9 | // 初始化共享状态
10 | export const initInterceptorXHRState = (state: RefGlobalState) => globalState = state
11 |
12 | class CustomXHR extends XMLHttpRequest {
13 | // 响应内容
14 | responseText!: string;
15 | // XHR 响应
16 | response: any;
17 | status!: number;
18 | statusText: string = "";
19 | // 请求协议
20 | method = 'GET'
21 | // 请求Body
22 | body?: Document | XMLHttpRequestBodyInit | null
23 | // 消息锁
24 | private message_once_lock: boolean = false;
25 |
26 | constructor() {
27 | super();
28 | // 初始化原始XHR实例
29 | // 将XHR属性赋值给Custom
30 | // 重写 response & responseText
31 | this.watchAndOverride()
32 | // 拦截open,获取请求协议
33 | this.getMethod()
34 | }
35 |
36 | // 获取请求协议
37 | private getMethod() {
38 | const { open, send } = this
39 | this.open = (
40 | method: string,
41 | url: string | URL,
42 | async?: boolean,
43 | username?: string | null,
44 | password?: string | null,
45 | ) => {
46 | // 获取当前请求协议
47 | this.method = (method || 'ANY').toUpperCase()
48 | open.apply(this, [method, url, async !== undefined ? async : true, username, password])
49 | }
50 | this.send = (body?: Document | XMLHttpRequestBodyInit | null) => {
51 | this.body = body
52 | send.call(this, body)
53 | }
54 | }
55 |
56 | // 规则匹配,修改响应内容
57 | private async maybeNeedModifyRes(origin_xhr_response: any) {
58 | for (let i = 0; i < globalState.value.interceptor_matching_content.length; i++) {
59 | const target = globalState.value.interceptor_matching_content[i];
60 | const {
61 | switch_on = true,
62 | match_url,
63 | override = "",
64 | filter_type,
65 | method,
66 | status_code = "200",
67 | override_type = "json",
68 | override_func = ""
69 | } = target
70 | // 是否需要匹配
71 | if (switch_on && match_url) {
72 | // 判断是否存在协议匹配
73 | if (method && ![this.method, "ANY"].includes(method.toUpperCase())) continue
74 | // 规则匹配
75 | const matched = maybeMatching(this.responseURL, match_url, filter_type);
76 | if (!matched) continue // 退出当前循环
77 | if (override_type === "function") {
78 | const ctx = getCtx(this.responseURL, this.method, this.status, status_code, this.body, origin_xhr_response)
79 | const payload = await execSetup(ctx, override_func)
80 | if (payload.override) {
81 | const _override = typeof payload.override === "string" ? payload.override : JSON.stringify(payload.override);
82 | this.responseText = _override;
83 | this.response = _override;
84 | }
85 | this.status = +payload.status!
86 | this.statusText = payload.status + ""
87 | } else {
88 | // 修改响应
89 | this.responseText = override;
90 | this.response = override;
91 | // 修改状态码
92 | this.status = +status_code
93 | this.statusText = status_code
94 | }
95 | // 通知
96 | if (!this.message_once_lock) {
97 | notice(this.responseURL, match_url, this.method);
98 | this.message_once_lock = true;
99 | }
100 | }
101 | }
102 | }
103 |
104 | // 属性重写
105 | private overrideAttr(attr: keyof XMLHttpRequest, xhr: XMLHttpRequest) {
106 | // 重写属性
107 | // @ts-ignore
108 | if (typeof xhr[attr] === "function") this[attr] = xhr[attr].bind(xhr);
109 | else if (['responseText', 'response', 'status', 'statusText'].includes(attr))
110 | // responseText和response 属性只读
111 | // 缓存在对应 自定义 _[attr] 上
112 | Object.defineProperty(this, attr, {
113 | get: () =>
114 | // @ts-ignore
115 | this[`_${attr}`] == undefined ? xhr[attr] : this[`_${attr}`],
116 | // @ts-ignore
117 | set: (val) => (this[`_${attr}`] = val),
118 | enumerable: true,
119 | });
120 | else
121 | Object.defineProperty(this, attr, {
122 | get: () => xhr[attr],
123 | // @ts-ignore
124 | set: (val) => (xhr[attr] = val),
125 | enumerable: true,
126 | });
127 | }
128 |
129 | // 拦截监听
130 | private watchAndOverride() {
131 | // 获取原始XHR
132 | const xhr = new OriginXHR();
133 | for (let attr in xhr) {
134 | if (attr === "onreadystatechange") {
135 | xhr.onreadystatechange = async (...args) => {
136 | // 开启拦截
137 | if (this.readyState == 4) await this.maybeNeedModifyRes(xhr.response);
138 | this.onreadystatechange && this.onreadystatechange.apply(this, args);
139 | };
140 | continue;
141 | }
142 | // else if (attr === "onload") {
143 | // xhr.onload = async (...args) => {
144 | // // 开启拦截
145 | // await this.maybeNeedModifyRes(xhr.response);
146 | // this.onload && this.onload.apply(this, args);
147 | // };
148 | // this.onload = null;
149 | // continue;
150 | // }
151 | // 其他属性重写
152 | this.overrideAttr(attr as keyof XMLHttpRequest, xhr)
153 | }
154 | }
155 | }
156 |
157 | export default CustomXHR
--------------------------------------------------------------------------------
/packages/proxy-lib/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IGlobalState, IMatchInterceptorContent, IMatchRedirectContent, IMode, IRequestMethod, RefGlobalState } from "./types";
2 | import CreateXHR, { initInterceptorXHRState, OriginXHR } from "./createXHR";
3 | import CreateFetch, { initInterceptorFetchState, OriginFetch } from "./createFetch";
4 | import RedirectXHR, { initRedirectXHRState } from "./redirectXHR";
5 | import RedirectFetch, { initRedirectFetchState } from "./redirectFetch";
6 | import { warn } from "./common";
7 |
8 | // 初始化共享状态
9 | const globalState: RefGlobalState = {
10 | value: {
11 | // 全局状态开关
12 | global_on: false,
13 | // 模式
14 | mode: 'interceptor',
15 | // 拦截匹配内容
16 | interceptor_matching_content: [],
17 | // 重定向匹配
18 | redirector_matching_content: []
19 | }
20 | }
21 |
22 |
23 | function isIGlobalState(x: any): x is IGlobalState {
24 | return x &&
25 | x.hasOwnProperty("global_on") &&
26 | x.hasOwnProperty("mode")
27 | }
28 |
29 | function isMode(x: any): x is IMode {
30 | return typeof x === 'string' && ["interceptor", "redirector"].includes(x)
31 | }
32 |
33 | function isArray(x: any): x is any[] {
34 | return x && Array.isArray(x)
35 | }
36 |
37 | function isInterceptors(x: any): x is IMatchInterceptorContent[] {
38 | for (let i = 0; i < x.length; i++) {
39 | const target = x[i];
40 | const bool = target.hasOwnProperty("match_url") && target.hasOwnProperty("switch_on")
41 | if (!bool) {
42 | return false
43 | }
44 | }
45 | return true
46 | }
47 |
48 | function isRedirectors(x: any): x is IMatchRedirectContent[] {
49 | for (let i = 0; i < x.length; i++) {
50 | const target = x[i];
51 | const bool = target.hasOwnProperty("domain") &&
52 | target.hasOwnProperty("switch_on") &&
53 | target.hasOwnProperty("redirect_url")
54 | if (!bool) {
55 | return false
56 | }
57 | }
58 | return true
59 | }
60 |
61 | // 初始化状态
62 | function initState() {
63 | // 初始化共享状态
64 | initInterceptorXHRState(globalState);
65 | initInterceptorFetchState(globalState);
66 | initRedirectXHRState(globalState);
67 | initRedirectFetchState(globalState);
68 | }
69 |
70 | // 实例挂载
71 | function mountInstance() {
72 | const { global_on = true, mode } = globalState.value
73 | // 每次挂载时,需要预先重置一下引用
74 | window.XMLHttpRequest = OriginXHR
75 | window.fetch = OriginFetch
76 | if (global_on) {
77 | if (mode === 'interceptor') {
78 | // 挂载拦截器
79 | window.XMLHttpRequest = CreateXHR
80 | window.fetch = CreateFetch
81 | } else if (mode === "redirector") {
82 | // 挂载重定向
83 | window.XMLHttpRequest = RedirectXHR
84 | window.fetch = RedirectFetch
85 | }
86 | }
87 | }
88 |
89 | /**全局开关 */
90 | function update(global_switch_on: T): void
91 | /**修改模式 */
92 | function update(mode: T): void
93 | /**修改拦截器 */
94 | function update(interceptors: T): void
95 | /**修改重定向 */
96 | function update(redirectors: T): void
97 | /**修改全部属性 */
98 | function update(state: T): void
99 | function update(target: unknow) {
100 | // 全局开关
101 | if (typeof target === "boolean") {
102 | globalState.value.global_on = target
103 | // 更新一波实例
104 | mountInstance()
105 | }
106 | // 修改模式
107 | else if (isMode(target)) {
108 | globalState.value.mode = target
109 | // 需要更新一下实例
110 | mountInstance()
111 | }
112 | // 数组类型: 拦截列表、重定向列表
113 | else if (isArray(target)) {
114 | if (target.length > 0) {
115 | // 修改拦截器
116 | if (isInterceptors(target)) globalState.value.interceptor_matching_content = target
117 | // 修改重定向
118 | else if (isRedirectors(target)) globalState.value.redirector_matching_content = target
119 | } else {
120 | // 判断当前模式
121 | // 清空拦截列表
122 | if (globalState.value.mode === 'interceptor') globalState.value.interceptor_matching_content = []
123 | // 清空重定向列表
124 | else globalState.value.redirector_matching_content = []
125 | }
126 | }
127 | // 设置全部属性
128 | // 默认初始化时使用
129 | else if (isIGlobalState(target)) {
130 | // 替换全部
131 | globalState.value = target
132 | // 重新挂载实例
133 | mountInstance()
134 | } else warn("unknow type")
135 | }
136 |
137 | initState()
138 |
139 | export default {
140 | update,
141 | }
142 |
143 | export type {
144 | IRequestMethod,
145 | IMatchInterceptorContent,
146 | IMatchRedirectContent,
147 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/src/overrideFunc.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | const errLog = function (...args: any[]) {
4 | console.log("%c[AjaxProxy][error]: ", "color: #ff4d4f", ...args)
5 | }
6 |
7 | type Ctx = {
8 | req: {
9 | url: string
10 | method: string
11 | body?: any
12 | }
13 | res: {
14 | status: string
15 | customStatus: string
16 | response: any
17 | }
18 | }
19 |
20 | type Next = {
21 | override?: string
22 | status?: string | number
23 | }
24 |
25 | function isNext(x: any): x is Next {
26 | return !!x && (x.override || x.status)
27 | }
28 |
29 | export function getCtx(
30 | url: string,
31 | method: string,
32 | status: string | number,
33 | customStatus: string,
34 | body?: any,
35 | response?: any
36 | ): Ctx {
37 | return {
38 | req: { url, method, body },
39 | res: { status: status.toString(), customStatus, response },
40 | }
41 | }
42 |
43 | function overrideNext(next: T, customStatus: string): T {
44 | if (isNext(next)) {
45 | // 默认status
46 | if (!next.status) next.status = customStatus
47 | // 默认响应值
48 | if (!next.override) next.override = ""
49 | return next
50 | }
51 | return { override: "", status: customStatus } as T
52 | }
53 |
54 | export function execSetup(ctx: Ctx, funcText: string): Promise {
55 | return new Promise(resolve => {
56 | try {
57 | const source = ';(' + funcText + ')'
58 | const execFunc = window.eval(source)
59 | const type = typeof execFunc
60 | if (type === "function") {
61 | if (!funcText.includes('next(')) {
62 | errLog("The structure of 'next' is incorrect [code 1]")
63 | return resolve({ override: "", status: ctx.res.customStatus })
64 | }
65 | execFunc(ctx.req, ctx.res, (next: Next) => {
66 | const overrideData = overrideNext(next, ctx.res.customStatus)
67 | return resolve(overrideData)
68 | })
69 | } else {
70 | errLog("Please enter a correct 'function' [code 2]")
71 | return resolve({ override: "", status: ctx.res.customStatus })
72 | }
73 | } catch (error) {
74 | errLog(error)
75 | return resolve({ override: "", status: ctx.res.customStatus })
76 | }
77 | })
78 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/src/redirectFetch.ts:
--------------------------------------------------------------------------------
1 | import { finalRedirectUrl, matchIgnoresAndRule } from "./common";
2 | import { execSetup } from "./redirectUrlFunc";
3 | import { RefGlobalState } from "./types";
4 |
5 | // 共享状态
6 | let globalState: RefGlobalState
7 | const OriginFetch = window.fetch.bind(window)
8 | // 初始化共享状态
9 | export const initRedirectFetchState = (state: RefGlobalState) => globalState = state
10 |
11 | export default async function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise {
12 | let fetchMethod: string | undefined | "ANY" = "ANY"
13 | let customInit: RequestInit = init || {}
14 | if (init) {
15 | fetchMethod = init.method?.toUpperCase() || "ANY"
16 | }
17 |
18 | for (let i = 0; i < globalState.value.redirector_matching_content.length; i++) {
19 | const {
20 | switch_on = true,
21 | domain = "",
22 | method = "ANY",
23 | filter_type,
24 | redirect_url = "",
25 | headers = [],
26 | ignores = [],
27 | redirect_type = "text",
28 | redirect_func = ""
29 | } = globalState.value.redirector_matching_content[i];
30 | if (switch_on) {
31 | // 判断是否存在协议匹配
32 | if (method && ![fetchMethod, "ANY"].includes(method.toUpperCase())) break
33 | if (redirect_type === "function") {
34 | const payload = await execSetup({ url: input.toString(), method: fetchMethod }, redirect_func)
35 | input = payload.url
36 |
37 | if (init?.headers) {
38 | // 初始化 RequestInit
39 | customInit = init
40 | const initHeaders = new Headers(init.headers)
41 | if (payload.headers) {
42 | for (const key in payload.headers) {
43 | if (Object.prototype.hasOwnProperty.call(payload.headers, key)) {
44 | const value = payload.headers[key];
45 | if (key && value) initHeaders.set(key, value)
46 | }
47 | }
48 | }
49 | customInit.headers = initHeaders
50 | } else {
51 | const newHeaders: HeadersInit = payload.headers ? Object.keys(payload.headers).map(key => [key, payload.headers![key]]) : []
52 | customInit = {
53 | headers: newHeaders
54 | }
55 | }
56 |
57 | // 值取当前命中的第一个,后续再命中的忽略
58 | break;
59 | } else if (matchIgnoresAndRule(input.toString(), domain, filter_type, ignores)) {
60 | input = finalRedirectUrl(input.toString(), domain, redirect_url, filter_type)
61 |
62 | if (init?.headers) {
63 | // 初始化 RequestInit
64 | customInit = init
65 | const initHeaders = new Headers(init.headers)
66 | headers.forEach(header => {
67 | initHeaders.set(header.key, header.value)
68 | })
69 | customInit.headers = initHeaders
70 | } else {
71 | const newHeaders: HeadersInit = headers.map(header => [header.key, header.value])
72 | customInit = {
73 | headers: newHeaders
74 | }
75 | }
76 |
77 | // 值取当前命中的第一个,后续再命中的忽略
78 | break;
79 | }
80 | }
81 | }
82 | return OriginFetch.call(CustomFetch, input, customInit)
83 | }
84 |
--------------------------------------------------------------------------------
/packages/proxy-lib/src/redirectUrlFunc.ts:
--------------------------------------------------------------------------------
1 | import { IRedirectHeader } from './types';
2 |
3 |
4 | const errLog = function (...args: any[]) {
5 | console.log("%c[AjaxProxy][error]: ", "color: #ff4d4f", ...args)
6 | }
7 |
8 | type Req = {
9 | url: string
10 | method: string
11 | }
12 |
13 | type Next = {
14 | url: string,
15 | headers?: { [key: IRedirectHeader['key']]: IRedirectHeader['value'] }
16 | }
17 |
18 | function isNext(x: any): x is Next {
19 | if (!!x) return true
20 | if (x.url && !x.headers) return true
21 | return x.headers && Object.prototype.toString.call(x.headers) == '[object Object]'
22 | }
23 |
24 | export function execSetup(req: Req, funcText: string): Promise {
25 | return new Promise(resolve => {
26 | try {
27 | const source = ';(' + funcText + ')'
28 | const execFunc = window.eval(source)
29 | const type = typeof execFunc
30 | if (type === "function") {
31 | if (!funcText.includes('next(')) {
32 | errLog("The structure of 'next' is incorrect [code 1]")
33 | // original
34 | return resolve({ url: req.url })
35 | }
36 | execFunc(req, (next: Next) => {
37 | // custom
38 | if (isNext(next)) {
39 | return resolve({ url: next.url, headers: next.headers })
40 | }
41 | })
42 | return resolve({ url: req.url })
43 | }
44 | errLog("Please enter a correct 'function' [code 2]")
45 | return resolve({ url: req.url })
46 | } catch (error) {
47 | errLog(error)
48 | return resolve({ url: req.url })
49 | }
50 | })
51 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/src/redirectXHR.ts:
--------------------------------------------------------------------------------
1 | import { finalRedirectUrl, fmtURLToString, matchIgnoresAndRule } from "./common";
2 | import { execSetup } from "./redirectUrlFunc";
3 | import { RefGlobalState } from "./types";
4 |
5 | // 状态
6 | let globalState: RefGlobalState
7 | // 初始化共享状态
8 | export const initRedirectXHRState = (state: RefGlobalState) => globalState = state
9 |
10 | export default class CustomRedirectXHR extends XMLHttpRequest {
11 |
12 | // 请求协议
13 | method = 'ANY'
14 |
15 | constructor() {
16 | super();
17 | this.watchAndRedirect()
18 | }
19 |
20 | private watchAndRedirect() {
21 | const { open } = this
22 | const origin_XHR_open = open
23 | const origin_XHR_setRequestHeader = this.setRequestHeader
24 | const origin_XHR_send = this.send
25 | this.open = async (method: string,
26 | url: string | URL,
27 | async?: boolean,
28 | username?: string | null,
29 | password?: string | null,
30 | ) => {
31 | this.method = (method || "ANY").toUpperCase()
32 | for (let i = 0; i < globalState.value.redirector_matching_content.length; i++) {
33 | const {
34 | switch_on = true,
35 | domain = "",
36 | method = "ANY",
37 | filter_type,
38 | redirect_url = "",
39 | headers = [],
40 | ignores = [],
41 | redirect_type = "text",
42 | redirect_func = ""
43 | } = globalState.value.redirector_matching_content[i];
44 | if (switch_on) {
45 | // 判断是否存在协议匹配
46 | if (method && ![this.method, "ANY"].includes(method.toUpperCase())) return
47 | // 规则判断
48 | const currentUrl = fmtURLToString(url)
49 | if (redirect_type === "function") {
50 | const payload = await execSetup({ url: currentUrl, method: this.method }, redirect_func)
51 | url = payload.url
52 | this.send = (body?: Document | XMLHttpRequestBodyInit | null) => {
53 | if (payload.headers) {
54 | for (const key in payload.headers) {
55 | if (Object.prototype.hasOwnProperty.call(payload.headers, key)) {
56 | const value = payload.headers[key];
57 | if (key && value)
58 | this.setRequestHeader(key, value)
59 | }
60 | }
61 | }
62 | origin_XHR_send.call(this, body)
63 | }
64 | break;
65 | } else if (matchIgnoresAndRule(currentUrl, domain, filter_type, ignores)) {
66 | url = finalRedirectUrl(currentUrl, domain, redirect_url, filter_type)
67 |
68 | // 获取 自定义 header 映射关系
69 | const cacheHeaderMap: { [key: string]: string } = {}
70 | headers.forEach(header => (cacheHeaderMap[header.key] = header.value))
71 |
72 | this.setRequestHeader = (name: string, value: string) => {
73 | // 判断是否存在需要修改的header
74 | // 如果存在,则不修改,让send里面重新设置
75 | if (!cacheHeaderMap[name]) origin_XHR_setRequestHeader.apply(this, [name, value])
76 | }
77 | this.send = (body?: Document | XMLHttpRequestBodyInit | null) => {
78 | for (let k = 0; k < headers.length; k++) {
79 | const header = headers[k];
80 | // 移除掉 映射关系
81 | // Reflect.deleteProperty(cacheHeaderMap, header.key)
82 | delete cacheHeaderMap[header.key]
83 | this.setRequestHeader(header.key, header.value)
84 | }
85 | origin_XHR_send.call(this, body)
86 | }
87 | // 值取当前命中的第一个,后续再命中的忽略
88 | break;
89 | }
90 |
91 | }
92 | }
93 | origin_XHR_open.apply(this, [method, url, async !== undefined ? async : true, username, password])
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/packages/proxy-lib/src/types.ts:
--------------------------------------------------------------------------------
1 | /**请求协议 */
2 | export type IRequestMethod = "ANY" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
3 | /**规则 */
4 | export type IFilterType = "normal" | "regex";
5 | /**模式 */
6 | export type IMode = "interceptor" | "redirector"
7 | /**全局状态结构体 */
8 | export type RefGlobalState = { value: T }
9 | /**响应式类型 */
10 | export type OverrideType = 'json' | 'function'
11 | /**重定向类型 */
12 | export type RedirectType = 'text' | 'function'
13 |
14 | type CommonContent = {
15 | /**是否需要匹配 */
16 | switch_on: boolean;
17 | /**匹配规则 */
18 | filter_type?: IFilterType
19 | /**请求协议 */
20 | method?: IRequestMethod
21 | /**备注 */
22 | remark?: string;
23 | }
24 |
25 | /**拦截器对象 */
26 | export type IMatchInterceptorContent = {
27 | /**匹配目标URL */
28 | match_url: string;
29 | /**需要覆盖的内容 */
30 | override?: string;
31 | /**命中率 */
32 | hit?: number;
33 | /**状态码 */
34 | status_code?: string
35 | /**响应式类型 */
36 | override_type?: OverrideType
37 | /**函数响应 */
38 | override_func?: string
39 | } & CommonContent
40 |
41 | /**重定向头部结构体 */
42 | export type IRedirectHeader = {
43 | key: string
44 | value: string
45 | description?: string
46 | }
47 |
48 | /**重定向对象结构体 */
49 | export type IMatchRedirectContent = {
50 | /**域名 */
51 | domain: string
52 | /**重定向地址 */
53 | redirect_url: string
54 | /**请求头 */
55 | headers?: IRedirectHeader[]
56 | /**忽略名单 */
57 | ignores?: string[]
58 | /**重定向类型 */
59 | redirect_type?: RedirectType
60 | /**函数响应 */
61 | redirect_func?: string
62 | } & CommonContent
63 |
64 | export type IGlobalState = {
65 | /**全局开关 */
66 | global_on: boolean;
67 | /**模式 */
68 | mode: IMode;
69 | /**拦截规则列表 */
70 | interceptor_matching_content: IMatchInterceptorContent[];
71 | /**重定向规则列表 */
72 | redirector_matching_content: IMatchRedirectContent[];
73 | }
74 |
--------------------------------------------------------------------------------
/packages/proxy-lib/test/base.test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/packages/proxy-lib/test/base.test.interval.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/packages/proxy-lib/test/redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/packages/proxy-lib/test/test.xhr.post.get.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Document
9 |
10 |
11 |
12 |
13 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/packages/proxy-lib/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "forceConsistentCasingInFileNames": true,
7 | "moduleResolution": "node",
8 | "strict": true,
9 | "jsx": "preserve",
10 | "sourceMap": false,
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "esModuleInterop": true,
14 | "lib": ["esnext", "dom"],
15 | "skipLibCheck": true,
16 | "declaration": true,
17 | "declarationDir": "types",
18 | "outDir": "types",
19 | "emitDeclarationOnly": true,
20 | },
21 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
22 | "references": [{ "path": "./tsconfig.node.json" }]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/proxy-lib/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "strict": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "moduleResolution": "node"
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/common.d.ts:
--------------------------------------------------------------------------------
1 | import { IMatchInterceptorContent, IMatchRedirectContent } from "./types";
2 | export declare function maybeMatching(url: string, match: string, type?: IMatchInterceptorContent['filter_type']): boolean;
3 | /**
4 | * 规则过滤
5 | * 域名判断
6 | * 规则类型判断
7 | * 忽略列表判断
8 | */
9 | export declare function matchIgnoresAndRule(url: string, match: string, type: import("./types").IFilterType | undefined, ignores: IMatchRedirectContent['ignores']): boolean;
10 | /**格式化URL 返回 string */
11 | export declare function fmtURLToString(url: string | URL): string;
12 | /**获取重定向URL */
13 | export declare function finalRedirectUrl(url: string, match: string, redirect_url: string, type?: IMatchRedirectContent['filter_type']): string;
14 | export declare function notice(url: string, match_url: string, method: string): void;
15 | export declare const warn: (...args: any[]) => void;
16 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/createFetch.d.ts:
--------------------------------------------------------------------------------
1 | import { RefGlobalState } from "./types";
2 | export declare const OriginFetch: ((input: URL | RequestInfo, init?: RequestInit | undefined) => Promise) & typeof fetch;
3 | export declare const initInterceptorFetchState: (state: RefGlobalState) => RefGlobalState;
4 | declare function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise;
5 | export default CustomFetch;
6 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/createXHR.d.ts:
--------------------------------------------------------------------------------
1 | import { RefGlobalState } from "./types";
2 | export declare const OriginXHR: {
3 | new (): XMLHttpRequest;
4 | prototype: XMLHttpRequest;
5 | readonly DONE: number;
6 | readonly HEADERS_RECEIVED: number;
7 | readonly LOADING: number;
8 | readonly OPENED: number;
9 | readonly UNSENT: number;
10 | };
11 | export declare const initInterceptorXHRState: (state: RefGlobalState) => RefGlobalState;
12 | declare class CustomXHR extends XMLHttpRequest {
13 | responseText: string;
14 | response: any;
15 | status: number;
16 | statusText: string;
17 | method: string;
18 | body?: Document | XMLHttpRequestBodyInit | null;
19 | private message_once_lock;
20 | constructor();
21 | private getMethod;
22 | private maybeNeedModifyRes;
23 | private overrideAttr;
24 | private watchAndOverride;
25 | }
26 | export default CustomXHR;
27 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IGlobalState, IMatchInterceptorContent, IMatchRedirectContent, IMode, IRequestMethod } from "./types";
2 | /**全局开关 */
3 | declare function update(global_switch_on: T): void;
4 | /**修改模式 */
5 | declare function update(mode: T): void;
6 | /**修改拦截器 */
7 | declare function update(interceptors: T): void;
8 | /**修改重定向 */
9 | declare function update(redirectors: T): void;
10 | /**修改全部属性 */
11 | declare function update(state: T): void;
12 | declare const _default: {
13 | update: typeof update;
14 | };
15 | export default _default;
16 | export type { IRequestMethod, IMatchInterceptorContent, IMatchRedirectContent, };
17 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/overrideFunc.d.ts:
--------------------------------------------------------------------------------
1 | declare type Ctx = {
2 | req: {
3 | url: string;
4 | method: string;
5 | body?: any;
6 | };
7 | res: {
8 | status: string;
9 | customStatus: string;
10 | response: any;
11 | };
12 | };
13 | declare type Next = {
14 | override?: string;
15 | status?: string | number;
16 | };
17 | export declare function getCtx(url: string, method: string, status: string | number, customStatus: string, body?: any, response?: any): Ctx;
18 | export declare function execSetup(ctx: Ctx, funcText: string): Promise;
19 | export {};
20 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/redirectFetch.d.ts:
--------------------------------------------------------------------------------
1 | import { RefGlobalState } from "./types";
2 | export declare const initRedirectFetchState: (state: RefGlobalState) => RefGlobalState;
3 | export default function CustomFetch(input: RequestInfo | URL, init?: RequestInit): Promise;
4 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/redirectUrlFunc.d.ts:
--------------------------------------------------------------------------------
1 | import { IRedirectHeader } from './types';
2 | declare type Req = {
3 | url: string;
4 | method: string;
5 | };
6 | declare type Next = {
7 | url: string;
8 | headers?: {
9 | [key: IRedirectHeader['key']]: IRedirectHeader['value'];
10 | };
11 | };
12 | export declare function execSetup(req: Req, funcText: string): Promise;
13 | export {};
14 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/redirectXHR.d.ts:
--------------------------------------------------------------------------------
1 | import { RefGlobalState } from "./types";
2 | export declare const initRedirectXHRState: (state: RefGlobalState) => RefGlobalState;
3 | export default class CustomRedirectXHR extends XMLHttpRequest {
4 | method: string;
5 | constructor();
6 | private watchAndRedirect;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/proxy-lib/types/types.d.ts:
--------------------------------------------------------------------------------
1 | /**请求协议 */
2 | export declare type IRequestMethod = "ANY" | "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
3 | /**规则 */
4 | export declare type IFilterType = "normal" | "regex";
5 | /**模式 */
6 | export declare type IMode = "interceptor" | "redirector";
7 | /**全局状态结构体 */
8 | export declare type RefGlobalState = {
9 | value: T;
10 | };
11 | /**响应式类型 */
12 | export declare type OverrideType = 'json' | 'function';
13 | /**重定向类型 */
14 | export declare type RedirectType = 'text' | 'function';
15 | declare type CommonContent = {
16 | /**是否需要匹配 */
17 | switch_on: boolean;
18 | /**匹配规则 */
19 | filter_type?: IFilterType;
20 | /**请求协议 */
21 | method?: IRequestMethod;
22 | /**备注 */
23 | remark?: string;
24 | };
25 | /**拦截器对象 */
26 | export declare type IMatchInterceptorContent = {
27 | /**匹配目标URL */
28 | match_url: string;
29 | /**需要覆盖的内容 */
30 | override?: string;
31 | /**命中率 */
32 | hit?: number;
33 | /**状态码 */
34 | status_code?: string;
35 | /**响应式类型 */
36 | override_type?: OverrideType;
37 | /**函数响应 */
38 | override_func?: string;
39 | } & CommonContent;
40 | /**重定向头部结构体 */
41 | export declare type IRedirectHeader = {
42 | key: string;
43 | value: string;
44 | description?: string;
45 | };
46 | /**重定向对象结构体 */
47 | export declare type IMatchRedirectContent = {
48 | /**域名 */
49 | domain: string;
50 | /**重定向地址 */
51 | redirect_url: string;
52 | /**请求头 */
53 | headers?: IRedirectHeader[];
54 | /**忽略名单 */
55 | ignores?: string[];
56 | /**重定向类型 */
57 | redirect_type?: RedirectType;
58 | /**函数响应 */
59 | redirect_func?: string;
60 | } & CommonContent;
61 | export declare type IGlobalState = {
62 | /**全局开关 */
63 | global_on: boolean;
64 | /**模式 */
65 | mode: IMode;
66 | /**拦截规则列表 */
67 | interceptor_matching_content: IMatchInterceptorContent[];
68 | /**重定向规则列表 */
69 | redirector_matching_content: IMatchRedirectContent[];
70 | };
71 | export {};
72 |
--------------------------------------------------------------------------------
/packages/proxy-lib/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { resolve } from 'path'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | build: {
7 | lib: {
8 | entry: resolve(__dirname, 'src/index.ts'),
9 | name: 'lib',
10 | },
11 | rollupOptions: {
12 | output: [
13 | {
14 | format: 'umd',
15 | entryFileNames: '[name].umd.js',
16 | sourcemap: true,
17 | dir: resolve(__dirname, 'lib'),
18 | },
19 | {
20 | format: 'esm',
21 | entryFileNames: '[name].esm.js',
22 | sourcemap: true,
23 | dir: resolve(__dirname, 'lib'),
24 | }
25 | ]
26 | }
27 | },
28 | plugins: []
29 | })
30 |
--------------------------------------------------------------------------------
/packages/shared-utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/shared-utils",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "",
6 | "main": "lib/index.js",
7 | "types": "types/index.d.ts",
8 | "devDependencies": {
9 | "@types/chrome": "^0.0.197",
10 | "@types/node": "^18.0.1"
11 | },
12 | "scripts": {
13 | "watch": "tsc -w",
14 | "build": "rm -rf lib types && tsc"
15 | },
16 | "keywords": [],
17 | "author": {
18 | "name": "Gj",
19 | "email": "gongjie0422@163.com"
20 | },
21 | "license": "MIT"
22 | }
--------------------------------------------------------------------------------
/packages/shared-utils/src/action.ts:
--------------------------------------------------------------------------------
1 | import { useAction } from "./env";
2 |
3 | /**设置徽章 */
4 | export function setBadge(count: number | undefined) {
5 | if (useAction) {
6 | chrome.action.setBadgeBackgroundColor({ color: "#006d75" });
7 | if (count) {
8 | chrome.action.setBadgeText({ text: `+${count}` });
9 | } else {
10 | chrome.action.setBadgeText({ text: "" });
11 | }
12 | }
13 | }
14 |
15 | /**Tab栏图片启用禁用状态 */
16 | export function setTabIcon(enable: boolean) {
17 | if (useAction) {
18 | chrome.action.setIcon({
19 | path: enable ? "/icons/128.png" : "/icons/128g.png",
20 | });
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/shared-utils/src/consts.ts:
--------------------------------------------------------------------------------
1 | /**storage enums */
2 | export enum StorageKey {
3 | LANGUAGE = 'ajax-proxy:storage:language',
4 | /**全局开关 */
5 | GLOBAL_SWITCH = 'ajax-proxy:storage:global-switch',
6 | /**拦截列表 */
7 | INTERCEPT_LIST = 'ajax-proxy:storage:intercept-list',
8 | /**重定向列表 */
9 | REDIRECT_LIST = 'ajax-proxy:storage:redirect-list',
10 | /**模式 */
11 | MODE = 'ajax-proxy:storage:mode',
12 | /**标签 */
13 | TAGS = 'ajax-proxy:storage:tags',
14 | }
15 |
16 | /**通知-去向 */
17 | export enum NoticeTo {
18 | /**通知 content */
19 | CONTENT = 'ajax-proxy:notice:to:content',
20 | /**通知 panels */
21 | PANELS = 'ajax-proxy:notice:to:panels',
22 | /**通知 document */
23 | DOCUMENT = 'ajax-proxy:notice:to:document',
24 | /**通知 service-worker */
25 | SERVICE_WORKER = 'ajax-proxy:notice:to:service-worker'
26 | }
27 |
28 | /**通知-来自 */
29 | export enum NoticeFrom {
30 | /**来自 content */
31 | CONTENT = 'ajax-proxy:notice:from:content',
32 | /**来自 panels */
33 | PANELS = 'ajax-proxy:notice:from:panels',
34 | /**来自 service-worker */
35 | SERVICE_WORKER = 'ajax-proxy:notice:from:service-worker',
36 | }
37 |
38 | /**通知Key */
39 | export enum NoticeKey {
40 | /**全局开关 */
41 | GLOBAL_SWITCH = 'ajax-proxy:notice:global-switch',
42 | /**拦截数据列表 */
43 | INTERCEPT_LIST = 'ajax-proxy:notice:intercept-list',
44 | /**重定向列表 */
45 | REDIRECT_LIST = 'ajax-proxy:notice:redirect-list',
46 | /**获取当前title */
47 | GET_CURRENT_TITLE = "ajax-proxy:notice:get-current-title",
48 | /**徽章状态 */
49 | BADGE_STATUS = 'ajax-proxy:notice:badge-status',
50 | /**命中率 */
51 | HIT_RATE = 'ajax-proxy:notice:hit-rate',
52 | /**模式 */
53 | MODE = 'ajax-proxy:notice:mode',
54 | }
55 |
--------------------------------------------------------------------------------
/packages/shared-utils/src/env.ts:
--------------------------------------------------------------------------------
1 |
2 | export const useAction = typeof chrome !== "undefined" && typeof chrome.action !== "undefined";
3 |
4 | export const useRuntime = typeof chrome !== "undefined" && typeof chrome.runtime !== "undefined";
5 |
6 | export const useStorage = typeof chrome !== 'undefined' && typeof chrome.storage !== 'undefined'
7 |
--------------------------------------------------------------------------------
/packages/shared-utils/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './consts'
2 | export * from './env'
3 | export * from './storage'
4 | export * from './notice'
5 | export * from './action'
--------------------------------------------------------------------------------
/packages/shared-utils/src/notice.ts:
--------------------------------------------------------------------------------
1 | import { useRuntime } from "./env";
2 | import { NoticeFrom, NoticeTo, NoticeKey } from "./consts";
3 |
4 | /**
5 | * 通知 content -> document
6 | */
7 | export function noticeDocumentByContent(key: NoticeKey | string, value) {
8 | window.postMessage({
9 | from: NoticeFrom.CONTENT,
10 | to: NoticeTo.DOCUMENT,
11 | key,
12 | value,
13 | });
14 | }
15 |
16 | /**
17 | * 通知 content -> service-worker
18 | * @param key
19 | * @param value
20 | */
21 | export function noticeServiceWorkerByContent(key: NoticeKey | string, value) {
22 | if (useRuntime) {
23 | chrome.runtime.sendMessage({
24 | from: NoticeFrom.CONTENT,
25 | to: NoticeTo.SERVICE_WORKER,
26 | key,
27 | value,
28 | }).catch(err => {
29 | // 离线状态 或 网页未加载成功情况下,content_script 未加载,导致没有接收方
30 | // 会导致:Error: Could not establish connection. Receiving end does not exist.
31 | });
32 | }
33 | }
34 |
35 | /**
36 | * 通知 panels -> service-worker
37 | */
38 | export function noticeServiceWorkerByPanels(key, value) {
39 | if (useRuntime) {
40 | chrome.runtime.sendMessage(chrome.runtime.id, {
41 | from: NoticeFrom.PANELS,
42 | to: NoticeTo.SERVICE_WORKER,
43 | key, value
44 | }).catch(err => {
45 | // 离线状态 或 网页未加载成功情况下,content_script 未加载,导致没有接收方
46 | // 会导致:Error: Could not establish connection. Receiving end does not exist.
47 | });
48 | }
49 | }
50 |
51 | /**
52 | * 通知 service-worker -> panels
53 | */
54 | export function noticePanelsByServiceWorker(key: NoticeKey, value?: any) {
55 | if (useRuntime) {
56 | chrome.runtime.sendMessage({
57 | from: NoticeFrom.SERVICE_WORKER,
58 | to: NoticeTo.PANELS,
59 | key, value
60 | }).catch(err => { })
61 | }
62 | }
63 |
64 | /** service-worker 长链接监听 */
65 | export function onConnectByServiceWorker(
66 | onConnectFn: (port: chrome.runtime.Port) => void,
67 | onDisconnectFn: () => void,
68 | ) {
69 | // 长链接
70 | // 好处是可以实现无刷新更新拦截器代理
71 | // 弊端是每一个新的tab页都会更新current_port,旧的长链会注销
72 | chrome.runtime.onConnect.addListener((port) => {
73 | onConnectFn(port)
74 | // 监听长链接 被断开
75 | port.onDisconnect.addListener(() => onDisconnectFn())
76 | })
77 | }
78 |
79 |
80 | /**
81 | * 通知 service-worker -> content
82 | */
83 | export function noticeContentByServiceWorker(port: chrome.runtime.Port | undefined, key: NoticeKey, value) {
84 | if (port) {
85 | try {
86 | port.postMessage({
87 | from: NoticeFrom.SERVICE_WORKER,
88 | to: NoticeTo.CONTENT,
89 | key, value
90 | })
91 | } catch (error) {
92 | // catch err
93 | // error: Error: Attempting to use a disconnected port object
94 | // 当被操作页被关闭掉,而在操作面板上继续操作时,此时port通信断开,报异常
95 | // 一般不会走到这里,上有已经做 onDisconnect 监听
96 | }
97 | }
98 | }
99 |
100 | /**
101 | * 当前活动页签发生改变
102 | */
103 | export function onCurrentTabChanged(callback: (tab: chrome.tabs.Tab) => void) {
104 | chrome.tabs.onActivated.addListener(activeInfo => {
105 | chrome.tabs.get(activeInfo.tabId)
106 | .then(getTab => {
107 | // 判断是否为一个正常的页签
108 | if (getTab.url?.startsWith("http") || getTab.url?.startsWith("https")) callback(getTab)
109 | // title 置空
110 | else callback({ ...getTab, title: "" })
111 | })
112 | .catch(err => { })
113 | })
114 | }
115 |
--------------------------------------------------------------------------------
/packages/shared-utils/src/storage.ts:
--------------------------------------------------------------------------------
1 | import { StorageKey } from './consts'
2 | import { useStorage } from './env'
3 |
4 | // chrome.storage.sync.set 大于8,192字节的数据时,会报错 -> QUOTA_BYTES_PER_ITEM quota exceeded
5 | // chrome.storage.local.set可以包含5242880
6 |
7 | let storageData
8 |
9 | export function initStorage(): Promise {
10 | return new Promise((resolve) => {
11 | if (useStorage) {
12 | chrome.storage.local.get(null, result => {
13 | storageData = result
14 | resolve()
15 | })
16 | } else {
17 | storageData = {}
18 | resolve()
19 | }
20 | })
21 | }
22 |
23 | export function getStorage(key: string, defaultValue: any = null) {
24 | checkStorage()
25 | if (useStorage) {
26 | return getDefaultValue(storageData[key], defaultValue)
27 | } else {
28 | try {
29 | return getDefaultValue(JSON.parse(localStorage.getItem(key) as any), defaultValue)
30 | } catch (e) { }
31 | }
32 | }
33 |
34 | /**不走缓存获取数据 */
35 | export function getRealStorage(key: StorageKey, defaultValue: any = null) {
36 | if (useStorage) {
37 | return new Promise(resolve => {
38 | chrome.storage.local.get(key, result => {
39 | if (result.hasOwnProperty(key)) resolve(getDefaultValue(result[key], defaultValue))
40 | else resolve(defaultValue);
41 | })
42 | })
43 | } else {
44 | try {
45 | const result = getDefaultValue(JSON.parse(localStorage.getItem(key) as any), defaultValue)
46 | return Promise.resolve(result)
47 | } catch (e) {
48 | return Promise.resolve(null)
49 | }
50 | }
51 | }
52 |
53 | export function setStorage(key: string, val: any) {
54 | checkStorage()
55 | if (useStorage) {
56 | storageData[key] = val
57 | chrome.storage.local.set({ [key]: val })
58 | } else {
59 | try {
60 | localStorage.setItem(key, JSON.stringify(val))
61 | } catch (e) { }
62 | }
63 | }
64 |
65 | export function removeStorage(keys: string | string[]) {
66 | checkStorage()
67 | if (useStorage) {
68 | if (Array.isArray(keys)) keys.forEach(target => delete storageData[target])
69 | else delete storageData[keys]
70 | chrome.storage.local.remove(keys)
71 | } else {
72 | try {
73 | if (Array.isArray(keys)) keys.forEach(target => localStorage.removeItem(target))
74 | else localStorage.removeItem(keys)
75 | } catch (e) { }
76 | }
77 | }
78 |
79 | export function clearStorage() {
80 | checkStorage()
81 | if (useStorage) {
82 | storageData = {}
83 | chrome.storage.local.clear()
84 | } else {
85 | try {
86 | localStorage.clear()
87 | } catch (e) { }
88 | }
89 | }
90 |
91 | function checkStorage() {
92 | if (!storageData) {
93 | throw new Error('Storage wasn\'t initialized with \'init()\'')
94 | }
95 | }
96 |
97 | function getDefaultValue(value, defaultValue) {
98 | if (value == null) {
99 | return defaultValue
100 | }
101 | return value
102 | }
103 |
104 | /**获取全部数据 */
105 | export function getStorageAll(): Promise<{ [key: string]: any }> {
106 | if (useStorage) {
107 | return new Promise(resolve => {
108 | chrome.storage.local.get(null, result => {
109 | resolve(result)
110 | })
111 | })
112 | } else {
113 | if (JSON.stringify(localStorage) === '{}') return Promise.resolve({})
114 | const data = Object.keys(localStorage).reduce(function (obj, str) {
115 | try {
116 | obj[str] = JSON.parse(localStorage.getItem(str) as any)
117 | } catch (e) {
118 | obj[str] = localStorage.getItem(str)
119 | }
120 | return obj
121 | }, {});
122 | return Promise.resolve(data)
123 | }
124 | }
--------------------------------------------------------------------------------
/packages/shared-utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "types": [
11 | "node",
12 | "chrome"
13 | ],
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "sourceMap": true,
17 | "preserveWatchOutput": true,
18 | // Strict
19 | "noImplicitAny": false,
20 | "noImplicitThis": true,
21 | "alwaysStrict": true,
22 | "strictBindCallApply": true,
23 | "strictFunctionTypes": true,
24 | "declaration": true,
25 | "declarationDir": "types",
26 | "outDir": "lib"
27 | },
28 | "include": [
29 | "src/**/*",
30 | ],
31 | "exclude": [
32 | "node_modules"
33 | ]
34 | }
--------------------------------------------------------------------------------
/packages/shared-utils/types/action.d.ts:
--------------------------------------------------------------------------------
1 | /**设置徽章 */
2 | export declare function setBadge(count: number | undefined): void;
3 | /**Tab栏图片启用禁用状态 */
4 | export declare function setTabIcon(enable: boolean): void;
5 |
--------------------------------------------------------------------------------
/packages/shared-utils/types/consts.d.ts:
--------------------------------------------------------------------------------
1 | /**storage enums */
2 | export declare enum StorageKey {
3 | LANGUAGE = "ajax-proxy:storage:language",
4 | /**全局开关 */
5 | GLOBAL_SWITCH = "ajax-proxy:storage:global-switch",
6 | /**拦截列表 */
7 | INTERCEPT_LIST = "ajax-proxy:storage:intercept-list",
8 | /**重定向列表 */
9 | REDIRECT_LIST = "ajax-proxy:storage:redirect-list",
10 | /**模式 */
11 | MODE = "ajax-proxy:storage:mode",
12 | /**标签 */
13 | TAGS = "ajax-proxy:storage:tags"
14 | }
15 | /**通知-去向 */
16 | export declare enum NoticeTo {
17 | /**通知 content */
18 | CONTENT = "ajax-proxy:notice:to:content",
19 | /**通知 panels */
20 | PANELS = "ajax-proxy:notice:to:panels",
21 | /**通知 document */
22 | DOCUMENT = "ajax-proxy:notice:to:document",
23 | /**通知 service-worker */
24 | SERVICE_WORKER = "ajax-proxy:notice:to:service-worker"
25 | }
26 | /**通知-来自 */
27 | export declare enum NoticeFrom {
28 | /**来自 content */
29 | CONTENT = "ajax-proxy:notice:from:content",
30 | /**来自 panels */
31 | PANELS = "ajax-proxy:notice:from:panels",
32 | /**来自 service-worker */
33 | SERVICE_WORKER = "ajax-proxy:notice:from:service-worker"
34 | }
35 | /**通知Key */
36 | export declare enum NoticeKey {
37 | /**全局开关 */
38 | GLOBAL_SWITCH = "ajax-proxy:notice:global-switch",
39 | /**拦截数据列表 */
40 | INTERCEPT_LIST = "ajax-proxy:notice:intercept-list",
41 | /**重定向列表 */
42 | REDIRECT_LIST = "ajax-proxy:notice:redirect-list",
43 | /**获取当前title */
44 | GET_CURRENT_TITLE = "ajax-proxy:notice:get-current-title",
45 | /**徽章状态 */
46 | BADGE_STATUS = "ajax-proxy:notice:badge-status",
47 | /**命中率 */
48 | HIT_RATE = "ajax-proxy:notice:hit-rate",
49 | /**模式 */
50 | MODE = "ajax-proxy:notice:mode"
51 | }
52 |
--------------------------------------------------------------------------------
/packages/shared-utils/types/env.d.ts:
--------------------------------------------------------------------------------
1 | export declare const useAction: boolean;
2 | export declare const useRuntime: boolean;
3 | export declare const useStorage: boolean;
4 |
--------------------------------------------------------------------------------
/packages/shared-utils/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from './consts';
2 | export * from './env';
3 | export * from './storage';
4 | export * from './notice';
5 | export * from './action';
6 |
--------------------------------------------------------------------------------
/packages/shared-utils/types/notice.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import { NoticeKey } from "./consts";
3 | /**
4 | * 通知 content -> document
5 | */
6 | export declare function noticeDocumentByContent(key: NoticeKey | string, value: any): void;
7 | /**
8 | * 通知 content -> service-worker
9 | * @param key
10 | * @param value
11 | */
12 | export declare function noticeServiceWorkerByContent(key: NoticeKey | string, value: any): void;
13 | /**
14 | * 通知 panels -> service-worker
15 | */
16 | export declare function noticeServiceWorkerByPanels(key: any, value: any): void;
17 | /**
18 | * 通知 service-worker -> panels
19 | */
20 | export declare function noticePanelsByServiceWorker(key: NoticeKey, value?: any): void;
21 | /** service-worker 长链接监听 */
22 | export declare function onConnectByServiceWorker(onConnectFn: (port: chrome.runtime.Port) => void, onDisconnectFn: () => void): void;
23 | /**
24 | * 通知 service-worker -> content
25 | */
26 | export declare function noticeContentByServiceWorker(port: chrome.runtime.Port | undefined, key: NoticeKey, value: any): void;
27 | /**
28 | * 当前活动页签发生改变
29 | */
30 | export declare function onCurrentTabChanged(callback: (tab: chrome.tabs.Tab) => void): void;
31 |
--------------------------------------------------------------------------------
/packages/shared-utils/types/storage.d.ts:
--------------------------------------------------------------------------------
1 | import { StorageKey } from './consts';
2 | export declare function initStorage(): Promise;
3 | export declare function getStorage(key: string, defaultValue?: any): any;
4 | /**不走缓存获取数据 */
5 | export declare function getRealStorage(key: StorageKey, defaultValue?: any): Promise;
6 | export declare function setStorage(key: string, val: any): void;
7 | export declare function removeStorage(keys: string | string[]): void;
8 | export declare function clearStorage(): void;
9 | /**获取全部数据 */
10 | export declare function getStorageAll(): Promise<{
11 | [key: string]: any;
12 | }>;
13 |
--------------------------------------------------------------------------------
/packages/shell-chrome/icons/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/packages/shell-chrome/icons/128.png
--------------------------------------------------------------------------------
/packages/shell-chrome/icons/128g.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/packages/shell-chrome/icons/128g.png
--------------------------------------------------------------------------------
/packages/shell-chrome/icons/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/packages/shell-chrome/icons/48.png
--------------------------------------------------------------------------------
/packages/shell-chrome/icons/48g.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/packages/shell-chrome/icons/48g.png
--------------------------------------------------------------------------------
/packages/shell-chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "Ajax Proxy",
4 | "version": "2.2.10",
5 | "description": "Modify your Ajax response to test",
6 | "author": "Gj",
7 | "icons": {
8 | "128": "icons/128.png"
9 | },
10 | "permissions": [
11 | "storage",
12 | "notifications",
13 | "tabs"
14 | ],
15 | "action": {
16 | "default_icon": "icons/48g.png"
17 | },
18 | "background": {
19 | "service_worker": "service_worker.js"
20 | },
21 | "content_scripts": [
22 | {
23 | "matches": [
24 | ""
25 | ],
26 | "js": [
27 | "content.js"
28 | ],
29 | "run_at": "document_start",
30 | "all_frames": true
31 | }
32 | ],
33 | "commands": {
34 | "open_panel": {
35 | "suggested_key": {
36 | "default": "Shift+Alt+Q",
37 | "windows": "Shift+Alt+Q",
38 | "mac": "Shift+Alt+Q",
39 | "linux": "Shift+Alt+Q"
40 | },
41 | "description": "Open panel"
42 | },
43 | "close_panel": {
44 | "suggested_key": {
45 | "default": "Shift+Alt+C",
46 | "windows": "Shift+Alt+C",
47 | "mac": "Shift+Alt+C",
48 | "linux": "Shift+Alt+C"
49 | },
50 | "description": "Close panel"
51 | },
52 | "full_screen": {
53 | "suggested_key": {
54 | "default": "Shift+Alt+W",
55 | "windows": "Shift+Alt+W",
56 | "mac": "Shift+Alt+W",
57 | "linux": "Shift+Alt+W"
58 | },
59 | "description": "Full screen"
60 | },
61 | "resize_window": {
62 | "suggested_key": {
63 | "default": "Shift+Alt+S",
64 | "windows": "Shift+Alt+S",
65 | "mac": "Shift+Alt+S",
66 | "linux": "Shift+Alt+S"
67 | },
68 | "description": "Resize screen"
69 | }
70 | },
71 | "web_accessible_resources": [
72 | {
73 | "resources": [
74 | "document.js"
75 | ],
76 | "matches": [
77 | ""
78 | ]
79 | }
80 | ]
81 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/shell-chrome",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "rm -rf build && webpack --config webpack.config.js"
8 | },
9 | "keywords": [],
10 | "author": {
11 | "name": "Gj",
12 | "email": "gongjie0422@163.com"
13 | },
14 | "license": "MIT",
15 | "dependencies": {
16 | "@proxy/compatibility": "workspace:^1.0.0",
17 | "@proxy/lib": "workspace:^1.0.0",
18 | "@proxy/shared-utils": "workspace:^1.0.0"
19 | },
20 | "devDependencies": {
21 | "@types/chrome": "^0.0.197",
22 | "copy-webpack-plugin": "^11.0.0",
23 | "ts-loader": "^9.4.1",
24 | "webpack": "^5.74.0",
25 | "webpack-cli": "^4.10.0"
26 | }
27 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/src/consts.ts:
--------------------------------------------------------------------------------
1 |
2 | /**长链接通信名称 */
3 | export const CONNECT_NAME = 'ajax-proxy:connect:custom:name'
4 |
5 | /**
6 | * 首次设置document.title
7 | * 该枚举只在 content 和 service-worker 中使用
8 | */
9 | export const INIT_CURRENT_TITLE = "ajax-proxy:notice:init-current-title"
10 |
11 | /**
12 | * 通知更新 IGlobalState 结构数据
13 | * content 首次加载 @proxy/lib
14 | */
15 | export const NOTICE_KEY_REFRESH_GLOBAL_STATE = "ajax-proxy:notice:refresh:global-state"
--------------------------------------------------------------------------------
/packages/shell-chrome/src/content.ts:
--------------------------------------------------------------------------------
1 | // console.log("Ajax proxy content.js")
2 |
3 | import {
4 | initStorage,
5 | NoticeTo,
6 | NoticeFrom,
7 | NoticeKey,
8 | StorageKey,
9 | noticeDocumentByContent,
10 | noticeServiceWorkerByContent,
11 | getStorageAll,
12 | setStorage,
13 | getRealStorage,
14 | removeStorage,
15 | } from "@proxy/shared-utils";
16 | import { CONNECT_NAME, INIT_CURRENT_TITLE, NOTICE_KEY_REFRESH_GLOBAL_STATE } from "./consts";
17 | import { onLoadForDataConversion } from "@proxy/compatibility";
18 |
19 | // 在页面上插入代码
20 | const script = document.createElement("script");
21 | script.setAttribute("type", "text/javascript");
22 | script.setAttribute("src", chrome.runtime.getURL("document.js"));
23 | document.documentElement.appendChild(script);
24 |
25 | initStorage().then(() => {
26 | // 发送当前tab页 title
27 | noticeServiceWorkerByContent(INIT_CURRENT_TITLE, window.document.title)
28 |
29 | // document.js 资源加载
30 | script.addEventListener("load", async () => {
31 | // 获取 全局开关、模式、拦截列表、重定向列表
32 | const data = await getStorageAll();
33 | // 新老数据转换
34 | const { changed, data: getData, changeKeywords } = onLoadForDataConversion(data)
35 | // 如果有老数据变更新数据,则需要在这里 setStorage
36 | if (changed) {
37 | setStorage(StorageKey.GLOBAL_SWITCH, getData.global_on)
38 | setStorage(StorageKey.MODE, getData.mode)
39 | setStorage(StorageKey.INTERCEPT_LIST, getData.interceptor_matching_content)
40 | setStorage(StorageKey.REDIRECT_LIST, getData.redirector_matching_content)
41 | // 需要清理对应旧数据,不然始终会进到当前判断条件中
42 | removeStorage(changeKeywords)
43 | }
44 | const getGlobalSwtich = getData[StorageKey.GLOBAL_SWITCH] || false
45 | if (getGlobalSwtich) noticeDocumentByContent(NOTICE_KEY_REFRESH_GLOBAL_STATE, getData)
46 | });
47 |
48 | // 长链接通信接收 service-worker -> document
49 | const port = chrome.runtime.connect({ name: CONNECT_NAME });
50 | // 接收service-worker 传来的信息,转发给 document.js
51 | port.onMessage.addListener(function (msg) {
52 | if (msg.from === NoticeFrom.SERVICE_WORKER && msg.to === NoticeTo.CONTENT) {
53 | const { GLOBAL_SWITCH, INTERCEPT_LIST, REDIRECT_LIST, MODE } = NoticeKey
54 | if ([GLOBAL_SWITCH, INTERCEPT_LIST, REDIRECT_LIST, MODE].includes(msg.key)) {
55 | // 注意:如果是全局开关开启的话,需要预先通知更新 mode模式
56 | if (
57 | // 全局开关
58 | msg.key === GLOBAL_SWITCH &&
59 | // 开启状态下
60 | msg.value
61 | ) {
62 | // content-script 所有 getStorage 都必须访问真实实例,不能走缓存
63 | getRealStorage(StorageKey.MODE, 'interceptor').then(getMode => {
64 | // 通知 @proxy/lib 先更新 mode
65 | // 如果不更新,lib里始终都是 拦截模式
66 | noticeDocumentByContent(MODE, getMode)
67 | })
68 | }
69 | noticeDocumentByContent(msg.key, msg.value)
70 | }
71 | }
72 | });
73 |
74 | // 接收lib 传来的信息 转发给 service-worker
75 | // 没有from 属性
76 | window.addEventListener(
77 | NoticeTo.CONTENT,
78 | function (event) {
79 | const customEvent = event as CustomEvent
80 | // 通知徽章上命中率需要变更
81 | noticeServiceWorkerByContent(NoticeKey.BADGE_STATUS, customEvent.detail)
82 | },
83 | false
84 | );
85 |
86 | })
87 |
88 |
89 |
--------------------------------------------------------------------------------
/packages/shell-chrome/src/document.ts:
--------------------------------------------------------------------------------
1 | // console.log("Ajax proxy document.js");
2 |
3 | import lib from "@proxy/lib";
4 | import { NoticeFrom, NoticeTo, NoticeKey, StorageKey } from "@proxy/shared-utils";
5 | import { NOTICE_KEY_REFRESH_GLOBAL_STATE } from "./consts";
6 |
7 | window.addEventListener(
8 | "message",
9 | function (event) {
10 | const data = event.data;
11 | let globalSwitch = false
12 | if (data.from === NoticeFrom.CONTENT && data.to === NoticeTo.DOCUMENT) {
13 | switch (data.key) {
14 | // 更新全部数据
15 | // content 首次加载时
16 | case NOTICE_KEY_REFRESH_GLOBAL_STATE:
17 | const {
18 | GLOBAL_SWITCH,
19 | MODE,
20 | INTERCEPT_LIST,
21 | REDIRECT_LIST,
22 | } = StorageKey
23 | // 获取 映射
24 | // 设置默认值
25 | const {
26 | [GLOBAL_SWITCH]: global_on = false,
27 | [MODE]: mode = 'interceptor',
28 | [INTERCEPT_LIST]: interceptor_matching_content = [],
29 | [REDIRECT_LIST]: redirector_matching_content = [],
30 | } = data.value || {}
31 | lib.update({ global_on, mode, interceptor_matching_content, redirector_matching_content })
32 | break;
33 | // 全局开关
34 | case NoticeKey.GLOBAL_SWITCH:
35 | globalSwitch = data.value;
36 | lib.update(globalSwitch)
37 | break;
38 | // 模式切换
39 | case NoticeKey.MODE:
40 | lib.update(data.value)
41 | break;
42 | // 拦截器列表
43 | case NoticeKey.INTERCEPT_LIST:
44 | lib.update(data.value)
45 | break;
46 | // 重定向列表
47 | case NoticeKey.REDIRECT_LIST:
48 | lib.update(data.value)
49 | break;
50 | }
51 | }
52 | },
53 | false
54 | );
55 |
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/badge.ts:
--------------------------------------------------------------------------------
1 |
2 | // 和徽章相关的函数
3 |
4 | import { IRequestMethod } from "@proxy/lib";
5 | import { NoticeKey, StorageKey, setStorage, getRealStorage, noticePanelsByServiceWorker } from "@proxy/shared-utils";
6 | import { chromeNativeNotice } from "./notice";
7 |
8 | // 同步 命中率
9 | async function syncRoutesAsHit(routes, match_url, method) {
10 | const list = routes || [];
11 | // 总命中率
12 | let counter = 0;
13 | for (let i = 0; i < list.length; i++) {
14 | const target = list[i];
15 | if (target.switch_on) {
16 | // target 可能没有 method 属性时,设置默认值
17 | const targetMethod = target.method || "ANY"
18 | // 如果match_url 和method 匹配 则叠加当前命中率
19 | if (
20 | target.match_url === match_url &&
21 | (targetMethod === "ANY" || targetMethod === method)
22 | ) {
23 | const totalHit = target.hit ? target.hit + 1 : 1;
24 | target.hit = totalHit;
25 | counter += totalHit;
26 |
27 | // 这里开始统计某个接口命中次数是否存在过多
28 | const LIMIT = 100;
29 | // 命中次数 太多,通知一下
30 | let tooHigh = totalHit === LIMIT;
31 | // 每隔20次提醒一下
32 | if (!tooHigh && totalHit > LIMIT) {
33 | tooHigh = (totalHit - LIMIT) % 20 === 0;
34 | }
35 | if (tooHigh) {
36 | const message = [target.match_url, target.remark || ""].join("\n");
37 | const lang = await getRealStorage(StorageKey.LANGUAGE, "en");
38 | const i18n = {
39 | en: "Too many interceptions",
40 | zh: "拦截次数过多",
41 | };
42 | chromeNativeNotice({
43 | title: i18n[lang] || i18n.en,
44 | message,
45 | });
46 | }
47 | } else if (target.hit) {
48 | // 汇总其他match_url上 hit
49 | counter += target.hit;
50 | }
51 | }
52 | }
53 | // 更新本地拦截列表
54 | setStorage(StorageKey.INTERCEPT_LIST, list);
55 | return counter;
56 | }
57 |
58 | /**当前命中数据的结构体 */
59 | type BadgeHit = {
60 | match_url: string
61 | method: IRequestMethod
62 | }
63 | // badge 右下角小徽章设置
64 | export async function chromeBadge(data?: BadgeHit) {
65 | const { match_url, method } = data || {}
66 | const globalSwitchOn = await getRealStorage(StorageKey.GLOBAL_SWITCH, false);
67 | if (!globalSwitchOn) {
68 | chrome.action.setBadgeText({ text: "" });
69 | return;
70 | }
71 | // 判断模式
72 | const mode = await getRealStorage(StorageKey.MODE, 'interceptor');
73 | // 如果是重定向
74 | if (mode === "redirector") {
75 | chrome.action.setBadgeBackgroundColor({ color: "#006d75" });
76 | chrome.action.setBadgeText({ text: "R" });
77 | return;
78 | }
79 | // 拦截器模式颜色
80 | chrome.action.setBadgeBackgroundColor({ color: "#F56C6C" });
81 | const interceptList = await getRealStorage(StorageKey.INTERCEPT_LIST, []);
82 | // 如果没有需要拦截的数据时,设置默认值
83 | if (interceptList.length === 0) {
84 | chrome.action.setBadgeText({ text: "" });
85 | return;
86 | }
87 |
88 | const counter = await syncRoutesAsHit(interceptList, match_url, method)
89 | // 当计算完成,且 参数存在时证明 hit 属性已经做过叠加,需要通知到 panels变更列表 hit 数据
90 | if (match_url && method) {
91 | // 通知 panels 当前 match_url & method 的条件下已经命中,hit 属性已经变更 需要更新table 列表
92 | noticePanelsByServiceWorker(NoticeKey.HIT_RATE)
93 | }
94 | if (counter) chrome.action.setBadgeText({ text: `+${counter}` });
95 | else chrome.action.setBadgeText({ text: "" });
96 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/event.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createPanel,
3 | closePanel,
4 | fullScreenPanel,
5 | resizeWindow,
6 | } from "./panel";
7 |
8 | // 监听图标点击事件
9 | function onIconClickListener() {
10 | chrome.action.onClicked.addListener(function (tab) {
11 | createPanel();
12 | });
13 | }
14 |
15 | /**
16 | * 监听快捷键
17 | * 在manifest.json中定义 commands
18 | */
19 | function onCommandListener() {
20 | // commands
21 | chrome.commands.onCommand.addListener(function (command) {
22 | switch (command) {
23 | case "open_panel":
24 | createPanel();
25 | break;
26 | case "close_panel":
27 | closePanel();
28 | break;
29 | case "full_screen":
30 | fullScreenPanel();
31 | break;
32 | case "resize_window":
33 | resizeWindow();
34 | break;
35 | default:
36 | break;
37 | }
38 | });
39 | }
40 |
41 | /**注册全部监听列表 */
42 | export function injectEventListener() {
43 | // 监听图标点击事件
44 | onIconClickListener()
45 | // 监听快捷键
46 | onCommandListener()
47 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/index.ts:
--------------------------------------------------------------------------------
1 |
2 | // console.log("Ajax proxy service_worker.js")
3 |
4 | import {
5 | NoticeFrom,
6 | NoticeTo,
7 | NoticeKey,
8 | initStorage,
9 | noticePanelsByServiceWorker,
10 | } from "@proxy/shared-utils";
11 | import { injectEventListener } from "./event";
12 | import { noticeContent, useCurrentTitle } from "./notice";
13 | import { initDefaultSth } from "./init";
14 | import { chromeBadge } from "./badge";
15 | import { INIT_CURRENT_TITLE } from "../consts";
16 |
17 | initStorage().then(() => {
18 | // 接收content 和 panels 传来的信息
19 | chrome.runtime.onMessage.addListener((msg) => {
20 |
21 | if (msg.to === NoticeTo.SERVICE_WORKER) {
22 | const { key, value } = msg
23 | // 徽章
24 | if ([NoticeFrom.CONTENT, NoticeFrom.PANELS].includes(msg.from)) {
25 | // 当 panels 清空某个命中统计时
26 | // 重新统计一下全局命中率
27 | if (key === NoticeKey.BADGE_STATUS) chromeBadge(value)
28 | }
29 |
30 | if (msg.from === NoticeFrom.PANELS) {
31 | // 判断启用状态
32 | if (key === NoticeKey.GLOBAL_SWITCH) {
33 | chrome.action.setIcon({
34 | path: value ? "icons/128.png" : "icons/128g.png",
35 | });
36 | // 更新一下图标状态
37 | chromeBadge()
38 | noticeContent(key, value)
39 | }
40 | // 模式切换
41 | if (key === NoticeKey.MODE) {
42 | chromeBadge();
43 | noticeContent(key, value)
44 | }
45 | // 拦截器列表
46 | if (key === NoticeKey.INTERCEPT_LIST) {
47 | chromeBadge();
48 | noticeContent(key, value)
49 | }
50 | // 重定向列表
51 | if (key === NoticeKey.REDIRECT_LIST) {
52 | noticeContent(key, value)
53 | }
54 | // 获取当前document.title [被动]
55 | // panels -> service-worker -> connect.port.sender.tab.title -> panels
56 | if (key === NoticeKey.GET_CURRENT_TITLE) {
57 | const title = useCurrentTitle()
58 | noticePanelsByServiceWorker(NoticeKey.GET_CURRENT_TITLE, title)
59 | }
60 | } else if (msg.from === NoticeFrom.CONTENT) {
61 | // content -> service-worker -> panels
62 | // 页面加载初始化当前title [主动]
63 | if (key === INIT_CURRENT_TITLE) {
64 | // 接收content转发给panels
65 | noticePanelsByServiceWorker(NoticeKey.GET_CURRENT_TITLE, value)
66 | }
67 | }
68 | }
69 | });
70 |
71 | // 设置默认项
72 | initDefaultSth()
73 |
74 | // 注册其他监听列表
75 | injectEventListener()
76 | })
77 |
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/init.ts:
--------------------------------------------------------------------------------
1 | import { getRealStorage, StorageKey } from "@proxy/shared-utils";
2 | import { chromeBadge } from "./badge";
3 |
4 | /**设置默认项 */
5 | export async function initDefaultSth() {
6 | // 设置默认icon
7 | const bool = await getRealStorage(StorageKey.GLOBAL_SWITCH, false)
8 | if (bool) chrome.action.setIcon({ path: "icons/128.png" });
9 | else chrome.action.setIcon({ path: "icons/128g.png" });
10 |
11 | // 初始化徽章状态
12 | chromeBadge()
13 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/notice.ts:
--------------------------------------------------------------------------------
1 | import {
2 | NoticeKey,
3 | onConnectByServiceWorker,
4 | noticePanelsByServiceWorker,
5 | noticeContentByServiceWorker,
6 | onCurrentTabChanged
7 | } from "@proxy/shared-utils";
8 | import { CONNECT_NAME } from "../consts";
9 | import { createPanel } from "./panel";
10 |
11 | /**
12 | * 可以定义多对多,但考虑此工具无需多个链接。
13 | * 多对多方式:
14 | * current_port = {}
15 | * ...
16 | * current_port[tabId].postMessage()
17 | */
18 | let current_port: chrome.runtime.Port | undefined = undefined;
19 |
20 | // 长链接
21 | // 好处是可以实现无刷新更新拦截器代理
22 | // 弊端是每一个新的tab页都会更新current_port,旧的长链会注销
23 | onConnectByServiceWorker((port) => {
24 | if (port.name === CONNECT_NAME) {
25 | current_port = port
26 | }
27 | }, () => {
28 | // 长链接断开
29 | current_port = undefined
30 | // 通知 panels 清空 title
31 | noticePanelsByServiceWorker(NoticeKey.GET_CURRENT_TITLE, "")
32 | })
33 |
34 | // 当页签发生改变时,通知panels 变更title
35 | onCurrentTabChanged((tab) => {
36 | noticePanelsByServiceWorker(NoticeKey.GET_CURRENT_TITLE, tab.title)
37 | })
38 |
39 | /**
40 | * 通知 content
41 | */
42 | export const noticeContent = (key, value) => noticeContentByServiceWorker(current_port, key, value)
43 |
44 | /**获取 port里 title */
45 | export function useCurrentTitle() {
46 | return current_port?.sender?.tab?.title
47 | }
48 |
49 | /**
50 | * 系统通知
51 | * 当命中率过高时
52 | */
53 | export function chromeNativeNotice({ title, message }) {
54 | const notification = new Notification(title, {
55 | icon: "icons/128.png",
56 | body: message,
57 | });
58 | notification.onclick = function () {
59 | createPanel();
60 | notification.close();
61 | };
62 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/src/service-worker/panel.ts:
--------------------------------------------------------------------------------
1 |
2 | let current_window_id: number | undefined
3 |
4 | /**获取所有windowId */
5 | async function getAllWindowIds(): Promise<(number | undefined)[]> {
6 | return new Promise((resolve) => {
7 | chrome.windows.getAll(function (targets) {
8 | const ids = targets.map((item) => item.id);
9 | resolve(ids);
10 | });
11 | });
12 | }
13 |
14 | /**创建视图 */
15 | export async function createPanel() {
16 | const _createFunc = function () {
17 | // https://developer.chrome.com/docs/extensions/reference/windows/
18 | chrome.windows.create(
19 | {
20 | url: "panels/index.html",
21 | type: "popup",
22 | width: 1300,
23 | height: 750,
24 | top: 30,
25 | left: 150,
26 | },
27 | function (target) {
28 | // bugfix: id undefined 问题
29 | if (target) current_window_id = target.id
30 | }
31 | );
32 | };
33 | if (!current_window_id) {
34 | _createFunc();
35 | } else {
36 | // 获取所有窗口id,判断cacheId是否存在
37 | // 如果已经存在则置前
38 | const ids = await getAllWindowIds();
39 | const exist = ids.some((item) => item === current_window_id);
40 | if (exist) {
41 | chrome.windows.update(current_window_id, { focused: true });
42 | return;
43 | }
44 | // 不存在,则重新创建,刷新cacheId
45 | _createFunc();
46 | }
47 | }
48 |
49 | /**关闭视图 */
50 | export async function closePanel() {
51 | if (current_window_id) {
52 | chrome.windows.remove(current_window_id);
53 | current_window_id = undefined
54 | }
55 | }
56 |
57 | /**全屏 */
58 | export async function fullScreenPanel() {
59 | if (current_window_id) {
60 | chrome.windows.getCurrent(function (current) {
61 | if (current.id && current.id === current_window_id) {
62 | switch (current.state) {
63 | case "fullscreen":
64 | chrome.windows.update(current.id, { state: "normal" });
65 | break;
66 | default:
67 | chrome.windows.update(current.id, { state: "fullscreen" });
68 | break;
69 | }
70 | }
71 | });
72 | }
73 | }
74 |
75 | /**修改 panels 窗口大小 */
76 | export async function resizeWindow() {
77 | if (current_window_id) {
78 | chrome.windows.getCurrent(function (current) {
79 | // normal", "minimized", "maximized", or "fullscreen"
80 | const conf = {
81 | normal: "maximized",
82 | maximized: "fullscreen",
83 | fullscreen: "normal",
84 | };
85 | if (current.id && current.state && current.id === current_window_id)
86 | chrome.windows.update(current.id, { state: conf[current.state] });
87 | });
88 | }
89 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "allowSyntheticDefaultImports": true,
7 | "esModuleInterop": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "types": [
11 | "chrome"
12 | ],
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "sourceMap": true,
16 | "preserveWatchOutput": true,
17 | // Strict
18 | "noImplicitAny": false,
19 | "noImplicitThis": true,
20 | "alwaysStrict": true,
21 | "strictBindCallApply": true,
22 | "strictFunctionTypes": true
23 | },
24 | "include": [
25 | "src/**/*",
26 | ],
27 | "exclude": [
28 | "node_modules"
29 | ]
30 | }
--------------------------------------------------------------------------------
/packages/shell-chrome/webpack.config.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 | const CopyPlugin = require("copy-webpack-plugin");
3 |
4 | const staticFiles = ['manifest.json', 'icons/*'].map(file => {
5 | return {
6 | from: file,
7 | to: '.'
8 | }
9 | });
10 |
11 | module.exports = {
12 | mode: 'production',
13 | entry: {
14 | content: resolve(__dirname, "src/content.ts"),
15 | document: resolve(__dirname, "src/document.ts"),
16 | service_worker: resolve(__dirname, "src/service-worker/index.ts"),
17 | },
18 | output: {
19 | path: resolve(__dirname, 'build'),
20 | filename: '[name].js',
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.ts$/,
26 | use: 'ts-loader',
27 | exclude: /node_modules/
28 | }
29 | ]
30 | },
31 | resolve: {
32 | // 需要打包的文件后缀
33 | extensions: [".ts", ".js"]
34 | },
35 | plugins: [
36 | new CopyPlugin(
37 | {
38 | patterns: staticFiles,
39 | }
40 | ),
41 | ],
42 | // devtool: process.env.NODE_ENV !== 'production'
43 | // ? 'inline-source-map'
44 | // : false,
45 | }
46 |
--------------------------------------------------------------------------------
/packages/vue-panels/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "i18n-ally.displayLanguage": "zh",
3 | "i18n-ally.dirStructure": "auto",
4 | "i18n-ally.disablePathParsing": false,
5 | "i18n-ally.localesPaths": [
6 | "src/lang"
7 | ],
8 | "i18n-ally.enabledParsers": [
9 | "js"
10 | ],
11 | "i18n-ally.annotationInPlace": false,
12 | "i18n-ally.autoDetection": true,
13 | "i18n-ally.keystyle": "nested"
14 | }
--------------------------------------------------------------------------------
/packages/vue-panels/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 g0ngjie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/vue-panels/README.md:
--------------------------------------------------------------------------------
1 | # ajax-intercepter-views
2 |
3 | 
4 |
5 | ### 插件地址
6 | [Github](https://github.com/g0ngjie/ajax-proxy)
7 | ### 使用示例
8 |
9 | > 视频使用示例
10 |
11 | [BiliBili](https://www.bilibili.com/video/BV1rf4y1p749/)
--------------------------------------------------------------------------------
/packages/vue-panels/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ],
5 | plugins: [
6 | [
7 | "component",
8 | {
9 | "libraryName": "element-ui",
10 | "styleLibraryName": "theme-chalk"
11 | }
12 | ]
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/vue-panels/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "dist"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/vue-panels/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@proxy/vue-panels",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "watch": "vue-cli-service build --watch --no-clean --mode production"
9 | },
10 | "dependencies": {
11 | "@alrale/common-lib": "^1.0.49",
12 | "@proxy/code-editor": "workspace:^1.0.0",
13 | "@proxy/compatibility": "workspace:^1.0.0",
14 | "@proxy/json-editor": "workspace:^1.0.0",
15 | "@proxy/shared-utils": "workspace:^1.0.0",
16 | "core-js": "^3.6.5",
17 | "element-ui": "~2.15.1",
18 | "export-from-json": "^1.7.0",
19 | "vue": "^2.6.11",
20 | "vue-i18n": "^8.24.3"
21 | },
22 | "devDependencies": {
23 | "@vue/cli-plugin-babel": "~4.5.0",
24 | "@vue/cli-plugin-router": "~4.5.0",
25 | "@vue/cli-plugin-vuex": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "babel-plugin-component": "^1.1.1",
28 | "sass": "~1.26.5",
29 | "sass-loader": "~8.0.2",
30 | "vue-template-compiler": "^2.6.11"
31 | },
32 | "browserslist": [
33 | "> 1%",
34 | "last 2 versions",
35 | "not dead"
36 | ]
37 | }
--------------------------------------------------------------------------------
/packages/vue-panels/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/packages/vue-panels/public/favicon.ico
--------------------------------------------------------------------------------
/packages/vue-panels/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Ajax Proxy
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
23 |
29 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/common/element-plugin.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import {
3 | Dialog,
4 | Input,
5 | Switch,
6 | Select,
7 | Option,
8 | Button,
9 | RadioGroup,
10 | RadioButton,
11 | Table,
12 | TableColumn,
13 | Tooltip,
14 | Form,
15 | FormItem,
16 | Upload,
17 | MessageBox,
18 | Message,
19 | Alert,
20 | Drawer,
21 | Tag,
22 | Row,
23 | Col,
24 | Tabs,
25 | TabPane,
26 | } from "element-ui";
27 |
28 | Vue.use(Dialog);
29 | Vue.use(Input);
30 | Vue.use(Switch);
31 | Vue.use(Select);
32 | Vue.use(Option);
33 | Vue.use(Button);
34 | Vue.use(RadioGroup);
35 | Vue.use(RadioButton);
36 | Vue.use(Table);
37 | Vue.use(TableColumn);
38 | Vue.use(Tooltip);
39 | Vue.use(Form);
40 | Vue.use(FormItem);
41 | Vue.use(Upload);
42 | Vue.use(Alert);
43 | Vue.use(Drawer);
44 | Vue.use(Tag);
45 | Vue.use(Row);
46 | Vue.use(Col);
47 | Vue.use(Tabs);
48 | Vue.use(TabPane);
49 |
50 | Vue.prototype.$msgbox = MessageBox;
51 | Vue.prototype.$alert = MessageBox.alert;
52 | Vue.prototype.$confirm = MessageBox.confirm;
53 | Vue.prototype.$prompt = MessageBox.prompt;
54 | Vue.prototype.$message = Message;
55 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/common/index.js:
--------------------------------------------------------------------------------
1 | import { MessageBox } from "element-ui";
2 |
3 | /**
4 | * 确认弹窗
5 | * @returns
6 | */
7 | export async function confirmFunc({
8 | message = "",
9 | title = "",
10 | confirmText,
11 | cancelText,
12 | type,
13 | }) {
14 | const res = await MessageBox.confirm(message, title, {
15 | confirmButtonText: confirmText,
16 | cancelButtonText: cancelText,
17 | type,
18 | }).catch(() => {
19 | return {
20 | ok: false,
21 | };
22 | });
23 | return {
24 | ok: res === "confirm",
25 | };
26 | }
27 |
28 | /**提交内容 */
29 | export async function promptFunc({
30 | message = "",
31 | title = "",
32 | inputValue = "",
33 | }) {
34 | const { action, value } = await MessageBox.prompt(message, title, {
35 | inputValue,
36 | }).catch(() => {
37 | return {
38 | ok: false,
39 | };
40 | });
41 | return {
42 | ok: action === "confirm",
43 | data: value,
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/common/notice.js:
--------------------------------------------------------------------------------
1 |
2 | import { noticeServiceWorkerByPanels, NoticeKey } from "@proxy/shared-utils";
3 |
4 | /**通知 */
5 | export const useNotice = {
6 | /**全局开关 */
7 | globalSwitchOn: (value) => noticeServiceWorkerByPanels(NoticeKey.GLOBAL_SWITCH, value),
8 | /**同步拦截路由列表 */
9 | changeIntercepts: (routes) => noticeServiceWorkerByPanels(NoticeKey.INTERCEPT_LIST, routes),
10 | /**通知徽章上命中率需要变更 */
11 | changeBadge: () => noticeServiceWorkerByPanels(NoticeKey.BADGE_STATUS, null),
12 | /**同步给 @proxy/lib 模式 */
13 | changeMode: (value) => noticeServiceWorkerByPanels(NoticeKey.MODE, value),
14 | /**同步给 @proxy/lib 重定向列表 */
15 | changeRedirects: (value) => noticeServiceWorkerByPanels(NoticeKey.REDIRECT_LIST, value),
16 | /**获取当前title */
17 | getCurrentTitle: () => noticeServiceWorkerByPanels(NoticeKey.GET_CURRENT_TITLE),
18 | }
--------------------------------------------------------------------------------
/packages/vue-panels/src/common/store.js:
--------------------------------------------------------------------------------
1 |
2 | import { setStorage, getStorage, getRealStorage, StorageKey, getStorageAll } from "@proxy/shared-utils";
3 |
4 | /**获取所有 */
5 | export async function getStoreAll() {
6 | const getData = await getStorageAll()
7 | const { LANGUAGE, GLOBAL_SWITCH, MODE, TAGS, INTERCEPT_LIST, REDIRECT_LIST } = StorageKey
8 | const {
9 | [LANGUAGE]: language = 'en',
10 | [GLOBAL_SWITCH]: globalSwitch = false,
11 | [MODE]: mode = 'interceptor',
12 | [TAGS]: tags = [],
13 | [INTERCEPT_LIST]: interceptors = [],
14 | [REDIRECT_LIST]: redirectors = [],
15 | } = getData || {}
16 |
17 | return {
18 | ok: true,
19 | data: { language, globalSwitch, mode, tags, interceptors, redirectors }
20 | }
21 | }
22 |
23 | /**多语言设置 */
24 | export const useLang = {
25 | get() {
26 | return getStorage(StorageKey.LANGUAGE, 'en')
27 | },
28 | set(value) {
29 | setStorage(StorageKey.LANGUAGE, value)
30 | }
31 | }
32 |
33 | /**同步全局开关 */
34 | export const useGLobalSwitch = {
35 | get() {
36 | return getStorage(StorageKey.GLOBAL_SWITCH, false)
37 | },
38 | set(value) {
39 | setStorage(StorageKey.GLOBAL_SWITCH, value)
40 | }
41 | }
42 |
43 | /**拦截路由列表 */
44 | export const useInterceptorRoutes = {
45 | // 注意,panels 与 service-worker 使用的storage 是不同的实例
46 | // 命中率更新 是在 service-worker 里面直接进行数据存储的
47 | // 所以这里面不能用 getStorage,因为这里面会走缓存,导致无法拿到最新的数据
48 | getReal() {
49 | return getRealStorage(StorageKey.INTERCEPT_LIST, [])
50 | },
51 | set(value) {
52 | setStorage(StorageKey.INTERCEPT_LIST, value)
53 | }
54 | }
55 |
56 | /**Tag列表 */
57 | export const useTags = {
58 | get() {
59 | return getStorage(StorageKey.TAGS, [])
60 | },
61 | set(value) {
62 | setStorage(StorageKey.TAGS, value)
63 | }
64 | }
65 |
66 | /**模式状态 */
67 | export const useMode = {
68 | get() {
69 | return getStorage(StorageKey.MODE, "interceptor")
70 | },
71 | set(value) {
72 | setStorage(StorageKey.MODE, value)
73 | }
74 | }
75 |
76 | /**重定向列表 */
77 | export const useRedirects = {
78 | get() {
79 | return getStorage(StorageKey.REDIRECT_LIST, [])
80 | },
81 | set(value) {
82 | setStorage(StorageKey.REDIRECT_LIST, value)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/index.scss:
--------------------------------------------------------------------------------
1 | /* 改变主题色变量 */
2 | $--color-primary: teal;
3 |
4 | /* 改变 icon 字体路径变量,必需 */
5 | $--font-path: '~element-ui/lib/theme-chalk/fonts';
6 |
7 | @import "~element-ui/packages/theme-chalk/src/index";
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/en.js:
--------------------------------------------------------------------------------
1 | export default {
2 | interceptor: "Interceptor",
3 | redirector: "Redirector",
4 | redirect: "Redirect",
5 | create: "Create",
6 | edit: "Edit",
7 | del: "Delete",
8 | copy: "Copy",
9 | path: 'Match',
10 | matchPath: 'Match',
11 | normal: "Normal",
12 | regex: "Regex",
13 | responseData: 'ResponseText',
14 | responseFunc: 'Function',
15 | remark: 'Remark',
16 | tag: 'Tag',
17 | statusCode: 'StatusCode',
18 | domain: "Domain",
19 | updateRequestHeaders: "Request header modificatio",
20 | option: "Option",
21 | append: "Append",
22 | describe: "Description",
23 | exclusionList: "Exclusion lis",
24 | noEmpty: "Cannot be empty",
25 | confirm: "OK",
26 | cancel: "Cancel",
27 | searchTxt: "Search",
28 | reset: "Reset",
29 | status: 'Status',
30 | matchType: "Match type",
31 | hit: "Hit",
32 | handle: 'Options',
33 | restore: "Restore",
34 | backup: "Backup",
35 | msg: {
36 | pathNotEmpty: 'match is required',
37 | responseDataNotEmpty: 'response text is required',
38 | domainNotEmpty: 'Domain name information cannot be empty',
39 | redirectNotEmpty: 'The redirection address cannot be empty',
40 | keyNotEmpty: 'key cannot be empty',
41 | valueNotEmpty: 'value cannot be empty',
42 | jsonFormatError: 'JSON format error',
43 | confirmDeletion: 'Are you sure you want to delete?',
44 | noDataToDownload: 'There is no data to download',
45 | overrideData: 'If the upload is successful, the original file will be overwritten',
46 | readJsonErr: 'Read exception. The file may not be a JSON',
47 | dataErr: 'Data exception',
48 | formatErr: 'Format error',
49 | importEmpty: 'You imported an empty list',
50 | existsEmptyString: 'Empty string exists',
51 | },
52 | placeholder: {
53 | select: 'Please select',
54 | input: 'Please input',
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/fr.js:
--------------------------------------------------------------------------------
1 | // 法文
2 | export default {
3 | interceptor: "Intercepteur",
4 | redirector: "Redirection",
5 | redirect: "Redirection",
6 | create: "Nouveau",
7 | edit: "Édition",
8 | del: "Supprimer",
9 | copy: "Copier",
10 | path: 'Le chemin',
11 | matchPath: 'Correspondance',
12 | normal: "Fréquent",
13 | regex: "Regular",
14 | responseData: 'Données de réponse',
15 | responseFunc: 'Sur mesure',
16 | remark: 'Remarques',
17 | tag: 'Étiquettes',
18 | statusCode: "Code d'état",
19 | domain: "Nom de domaine",
20 | updateRequestHeaders: "Modification de l'en - tête de la demande",
21 | option: "Options",
22 | append: "Ajouter",
23 | describe: "Description",
24 | exclusionList: "Liste d'exclusion",
25 | noEmpty: "Ne peut pas être vide",
26 | confirm: "C'est sûr.",
27 | cancel: "Annulation",
28 | searchTxt: "Recherche",
29 | reset: "Réinitialiser",
30 | status: 'Statut',
31 | matchType: "Type de correspondance",
32 | hit: "Hit",
33 | handle: 'Fonctionnement',
34 | restore: "Reprise",
35 | backup: "Sauvegarde",
36 | msg: {
37 | pathNotEmpty: 'Le chemin de requête ne peut pas être vide',
38 | responseDataNotEmpty: 'Les données de réponse ne peuvent pas être vides',
39 | domainNotEmpty: 'Les informations sur le nom de domaine ne peuvent pas être vides',
40 | redirectNotEmpty: "L'adresse de redirection ne peut pas être vide",
41 | keyNotEmpty: "La valeur de la clé ne peut pas être vide",
42 | valueNotEmpty: 'La valeur ne peut pas être vide',
43 | jsonFormatError: 'Erreur de format json',
44 | confirmDeletion: 'Confirmer la suppression?',
45 | noDataToDownload: 'Aucune donnée à télécharger',
46 | overrideData: 'Le fichier original sera écrasé si le téléchargement réussit',
47 | readJsonErr: 'Exception de lecture, le fichier peut ne pas être un json',
48 | dataErr: 'Données anormales',
49 | formatErr: 'Mauvais format',
50 | importEmpty: 'Vous avez importé une liste vide',
51 | existsEmptyString: 'Présence d\'une chaîne vide',
52 | },
53 | placeholder: {
54 | select: 'Veuillez sélectionner',
55 | input: 'Veuillez entrer',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueI18n from 'vue-i18n'
3 |
4 | import ElementLocale from 'element-ui/lib/locale'
5 | import enLocale from 'element-ui/lib/locale/lang/en'
6 | import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
7 | import twLocale from 'element-ui/lib/locale/lang/zh-TW'
8 | // 日本语
9 | import jaLocale from 'element-ui/lib/locale/lang/ja'
10 | // 法语
11 | import frLocale from 'element-ui/lib/locale/lang/fr'
12 | // 韩语
13 | import koLocale from 'element-ui/lib/locale/lang/ko'
14 | // 俄语
15 | import ruLocale from 'element-ui/lib/locale/lang/ru-RU'
16 |
17 | import langZh from "./zh_CN.js"
18 | import langEN from "./en.js"
19 | import langTW from "./zh_TW.js"
20 | import langJA from "./ja.js"
21 | import langFR from "./fr.js"
22 | import langKO from "./ko.js"
23 | import langRU from "./ru.js"
24 | // 爱尔兰
25 | import langIE from "./ireland.js"
26 |
27 | Vue.use(VueI18n)
28 |
29 | const i18n = new VueI18n({
30 | locale: 'en',
31 | messages: {
32 | 'zh': { ...langZh, ...zhLocale },
33 | 'en': { ...langEN, ...enLocale },
34 | 'tw': { ...langTW, ...twLocale },
35 | 'ja': { ...langJA, ...jaLocale },
36 | 'fr': { ...langFR, ...frLocale },
37 | 'ko': { ...langKO, ...koLocale },
38 | 'ru': { ...langRU, ...ruLocale },
39 | 'ie': { ...langIE, ...enLocale }
40 | }
41 | })
42 | ElementLocale.i18n((key, value) => i18n.t(key, value))
43 |
44 | export default i18n
45 |
46 | export const Langs = [
47 | { value: 'en', label: 'English' },
48 | { value: 'zh', label: '简体中文' },
49 | { value: 'tw', label: '繁體中文' },
50 | { value: 'ja', label: '日本語' },
51 | { value: 'fr', label: 'Français' },
52 | { value: 'ko', label: '한국어' },
53 | { value: 'ru', label: 'Русский' },
54 | { value: 'ie', label: 'Gaeilge' }
55 | ]
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/ireland.js:
--------------------------------------------------------------------------------
1 | // 爱尔兰
2 | export default {
3 | interceptor: "Interceptor",
4 | redirector: "athdhírigh",
5 | redirect: "athdhírigh",
6 | create: "tógáil nua",
7 | edit: "eagar",
8 | del: "Scrios",
9 | copy: "cóipeáil",
10 | path: 'ruta',
11 | matchPath: 'Path matching',
12 | normal: "gnáth",
13 | regex: "rialta",
14 | responseData: 'Sonraí freagra',
15 | responseFunc: 'feidhm',
16 | remark: 'ráitis',
17 | tag: 'label',
18 | statusCode: 'Cód stádais',
19 | domain: "domain name",
20 | updateRequestHeaders: "Request header modification",
21 | option: "rogha",
22 | append: "Cuir leis",
23 | describe: "tuairisc",
24 | exclusionList: "Liosta eisiamh",
25 | noEmpty: "Ní féidir a bheith folamh",
26 | confirm: "cinneadh",
27 | cancel: "Cealaigh",
28 | searchTxt: "cuardaigh",
29 | reset: "Athshocraigh",
30 | status: 'Stádas',
31 | matchType: "Cineál comhoiriúnach",
32 | hit: "Cruthaigh",
33 | handle: 'oibríocht',
34 | restore: "athshlánú",
35 | backup: "cúltaca",
36 | msg: {
37 | pathNotEmpty: 'Ní féidir conair an iarratais a bheith folamh',
38 | responseDataNotEmpty: 'Ní féidir sonraí freagra a bheith folamh',
39 | domainNotEmpty: 'Ní féidir eolas faoi ainm an fhearainn a bheith folamh',
40 | redirectNotEmpty: 'Ní féidir seoladh athdhírithe a bheith folamh',
41 | keyNotEmpty: 'Ní féidir an luach eochrach a bheith folamh',
42 | valueNotEmpty: 'Ní féidir luach a bheith folamh',
43 | jsonFormatError: 'Earráid fhormáide JSON',
44 | confirmDeletion: 'deimhnigh scriosadh?',
45 | noDataToDownload: 'Gan sonraí le híosluchtú',
46 | overrideData: 'Má tá an t- uasluchtú rathúil, sárófar an bunchomhad',
47 | readJsonErr: 'Reading exception. The file may not be a JSON',
48 | dataErr: 'Eisceachta sonraí',
49 | formatErr: 'Earráid fhormáide',
50 | importEmpty: 'Iompórtáil tú liosta folamh',
51 | existsEmptyString: 'Tá teaghrán folamh ann',
52 | },
53 | placeholder: {
54 | select: 'Roghnaigh le do thoil',
55 | input: 'Iontráil le do thoil',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/ja.js:
--------------------------------------------------------------------------------
1 | // 日语
2 | export default {
3 | interceptor: "ブロッキング",
4 | redirector: "リダイレクト",
5 | redirect: "リダイレクト",
6 | create: "新規作成",
7 | edit: "編集",
8 | del: "削除",
9 | copy: "コピー",
10 | path: 'パス',
11 | matchPath: 'パスマッチング',
12 | normal: "通常",
13 | regex: "正則",
14 | responseData: 'レスポンスデータ',
15 | responseFunc: 'ビスポーク',
16 | remark: 'コメント',
17 | tag: 'タブ',
18 | statusCode: 'じょうたいコード',
19 | domain: "ドメイン名",
20 | updateRequestHeaders: "要求ヘッダの変更",
21 | option: "オプション",
22 | append: "追加",
23 | describe: "説明",
24 | exclusionList: "除外リスト",
25 | noEmpty: "空にできません",
26 | confirm: "を選択します。",
27 | cancel: "キャンセル",
28 | searchTxt: "検索",
29 | reset: "リセット",
30 | status: '状態',
31 | matchType: "照合タイプ",
32 | hit: "ヒット",
33 | handle: '操作',
34 | restore: "リカバリ",
35 | backup: "バックアップ",
36 | msg: {
37 | pathNotEmpty: '要求パスは空にできません',
38 | responseDataNotEmpty: '応答データを空にすることはできません',
39 | domainNotEmpty: 'ドメイン名情報は空にできません',
40 | redirectNotEmpty: 'リダイレクトアドレスは空にできません',
41 | keyNotEmpty: 'key値は空にできません',
42 | valueNotEmpty: 'value値は空にできません',
43 | jsonFormatError: 'JSON形式エラー',
44 | confirmDeletion: '削除の確認?',
45 | noDataToDownload: 'ダウンロードできるデータがありません',
46 | overrideData: 'アップロードに成功すると、元のファイルが上書きされます。',
47 | readJsonErr: '読み込み異常、ファイルがJSONでない可能性があります',
48 | dataErr: 'データ異常',
49 | formatErr: 'フォーマットエラー',
50 | importEmpty: '空のリストをインポートしました',
51 | existsEmptyString: '空の文字列が存在します',
52 | },
53 | placeholder: {
54 | select: '選択してください',
55 | input: '入力してください',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/ko.js:
--------------------------------------------------------------------------------
1 | // 韩语
2 | export default {
3 | interceptor: "차단기",
4 | redirector: "방향을 바꾸다",
5 | redirect: "방향을 바꾸다",
6 | create: "신설",
7 | edit: "편집자",
8 | del: "삭제",
9 | copy: "복제하다",
10 | path: '경로',
11 | matchPath: '경로 일치',
12 | normal: "여간하다",
13 | regex: "정칙",
14 | responseData: '응답 데이터',
15 | responseFunc: '사용자 정의',
16 | remark: '비고',
17 | tag: '라벨',
18 | statusCode: '상태 코드',
19 | domain: "도메인 이름",
20 | updateRequestHeaders: "헤더 수정 요청",
21 | option: "옵션",
22 | append: "덧붙이다",
23 | describe: "묘사",
24 | exclusionList: "명단을 제외하다",
25 | noEmpty: "공백일 수 없습니다.",
26 | confirm: "확정하다",
27 | cancel: "취소",
28 | searchTxt: "수색하다",
29 | reset: "재설정",
30 | status: '컨디션',
31 | matchType: "일치 유형",
32 | hit: "명중하다",
33 | handle: '조작하다',
34 | restore: "되살리다",
35 | backup: "백업",
36 | msg: {
37 | pathNotEmpty: '요청 경로가 비어 있을 수 없습니다',
38 | responseDataNotEmpty: '응답 데이터는 비워둘 수 없습니다.',
39 | domainNotEmpty: '도메인 이름은 비워 둘 수 없습니다.',
40 | redirectNotEmpty: '주소 리디렉션은 비워 둘 수 없습니다.',
41 | keyNotEmpty: '키 값은 비워둘 수 없습니다.',
42 | valueNotEmpty: 'value 값은 비워 둘 수 없습니다.',
43 | jsonFormatError: 'JSON 형식 오류',
44 | confirmDeletion: '삭제 확인?',
45 | noDataToDownload: '다운로드할 데이터가 없습니다.',
46 | overrideData: '업로드에 성공한 원본 파일을 덮어씁니다',
47 | readJsonErr: '읽기 예외, 파일이 JSON이 아닐 수 있음',
48 | dataErr: '데이터 예외',
49 | formatErr: '형식 오류',
50 | importEmpty: '빈 목록을 가져왔습니다',
51 | existsEmptyString: '빈 문자열 있음',
52 | },
53 | placeholder: {
54 | select: '선택하십시오.',
55 | input: '입력하십시오.',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/ru.js:
--------------------------------------------------------------------------------
1 | // 俄语
2 | export default {
3 | interceptor: "перехватчик",
4 | redirector: "Перенаправление",
5 | redirect: "Перенаправление",
6 | create: "Добавить",
7 | edit: "редактор",
8 | del: "Удалить",
9 | copy: "копировать",
10 | path: 'путь',
11 | matchPath: 'согласование путей',
12 | normal: "обычный",
13 | regex: "регулярный",
14 | responseData: 'данные ответа',
15 | responseFunc: 'функция',
16 | remark: 'Примечание',
17 | tag: 'метка',
18 | statusCode: 'код состояния',
19 | domain: "доменное имя",
20 | updateRequestHeaders: "заголовок запроса",
21 | option: "Параметры",
22 | append: "Добавить",
23 | describe: "описание",
24 | exclusionList: "исключить список",
25 | noEmpty: "не может быть пустым",
26 | confirm: "определение",
27 | cancel: "отмена",
28 | searchTxt: "поиск",
29 | reset: "сбросить",
30 | status: 'состояние',
31 | matchType: "Тип согласования",
32 | hit: "попадание",
33 | handle: 'операция',
34 | restore: "восстановить",
35 | backup: "резервная копия",
36 | msg: {
37 | pathNotEmpty: 'путь запроса не может быть пустым',
38 | responseDataNotEmpty: 'данные ответа не могут быть пустыми',
39 | domainNotEmpty: 'Информация о домене не может быть пустой',
40 | redirectNotEmpty: 'адрес перенаправления не может быть пустым',
41 | keyNotEmpty: 'значение key не может быть пустым',
42 | valueNotEmpty: 'значение value не может быть пустым',
43 | jsonFormatError: 'ошибка формата JSON',
44 | confirmDeletion: 'Подтверждение удаления?',
45 | noDataToDownload: 'Нет данных, которые можно загрузить',
46 | overrideData: 'Файл будет перезаписан',
47 | readJsonErr: 'Ошибка чтения файла может быть не JSON',
48 | dataErr: 'аномалия данных',
49 | formatErr: 'ошибка формата',
50 | importEmpty: 'вы импортировали пустой список',
51 | existsEmptyString: 'Наличие пустых строк',
52 | },
53 | placeholder: {
54 | select: 'Выберите',
55 | input: 'Введите пожалуйста',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/zh_CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | interceptor: "拦截器",
3 | redirector: "重定向",
4 | redirect: "重定向",
5 | create: "新增",
6 | edit: "编辑",
7 | del: "删除",
8 | copy: "复制",
9 | path: '路径',
10 | matchPath: '路径匹配',
11 | normal: "普通",
12 | regex: "正则",
13 | responseData: '响应数据',
14 | responseFunc: '函数式',
15 | remark: '备注',
16 | tag: '标签',
17 | statusCode: '状态码',
18 | domain: "域名",
19 | updateRequestHeaders: "请求头修改",
20 | option: "选项",
21 | append: "添加",
22 | describe: "描述",
23 | exclusionList: "排除名单",
24 | noEmpty: "不能为空",
25 | confirm: "确 定",
26 | cancel: "取 消",
27 | searchTxt: "搜索",
28 | reset: "重置",
29 | status: '状态',
30 | matchType: "匹配类型",
31 | hit: "命中",
32 | handle: '操作',
33 | restore: "恢复",
34 | backup: "备份",
35 | msg: {
36 | pathNotEmpty: '请求路径不能为空',
37 | responseDataNotEmpty: '响应数据不能为空',
38 | domainNotEmpty: '域名信息不能为空',
39 | redirectNotEmpty: '重定向地址不能为空',
40 | keyNotEmpty: 'key值不能为空',
41 | valueNotEmpty: 'value值不能为空',
42 | jsonFormatError: 'JSON格式错误',
43 | confirmDeletion: '确认删除?',
44 | noDataToDownload: '没有数据可以下载',
45 | overrideData: '上传成功原文件会被覆盖',
46 | readJsonErr: '读取异常,文件可能不是一个JSON',
47 | dataErr: '数据异常',
48 | formatErr: '格式错误',
49 | importEmpty: '你导入了一个空列表',
50 | existsEmptyString: '存在空字符串',
51 | },
52 | placeholder: {
53 | select: '请选择',
54 | input: '请输入',
55 | },
56 | };
57 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/lang/zh_TW.js:
--------------------------------------------------------------------------------
1 | // 台湾
2 | export default {
3 | interceptor: "攔截器",
4 | redirector: "重定向",
5 | redirect: "重定向",
6 | create: "新增",
7 | edit: "編輯",
8 | del: "删除",
9 | copy: "複製",
10 | path: '路徑',
11 | matchPath: '路徑匹配',
12 | normal: "普通",
13 | regex: "正則",
14 | responseData: '響應數據',
15 | responseFunc: '函數式',
16 | remark: '備註',
17 | tag: '標籤',
18 | statusCode: '狀態碼',
19 | domain: "域名",
20 | updateRequestHeaders: "請求頭修改",
21 | option: "選項",
22 | append: "添加",
23 | describe: "描述",
24 | exclusionList: "排除名單",
25 | noEmpty: "不能為空",
26 | confirm: "確 定",
27 | cancel: "取 消",
28 | searchTxt: "蒐索",
29 | reset: "重置",
30 | status: '狀態',
31 | matchType: "匹配類型",
32 | hit: "命中",
33 | handle: '操作',
34 | restore: "恢復",
35 | backup: "備份",
36 | msg: {
37 | pathNotEmpty: '請求路徑不能為空',
38 | responseDataNotEmpty: '響應數據不能為空',
39 | domainNotEmpty: '域名資訊不能為空',
40 | redirectNotEmpty: '重定向地址不能為空',
41 | keyNotEmpty: 'key值不能為空',
42 | valueNotEmpty: 'value值不能為空',
43 | jsonFormatError: 'JSON格式錯誤',
44 | confirmDeletion: '確認删除?',
45 | noDataToDownload: '沒有數據可以下載',
46 | overrideData: '上傳成功原檔案會被覆蓋',
47 | readJsonErr: '讀取异常,檔案可能不是一個JSON',
48 | dataErr: '數據异常',
49 | formatErr: '格式錯誤',
50 | importEmpty: '你導入了一個空清單',
51 | existsEmptyString: '存在空字符串',
52 | },
53 | placeholder: {
54 | select: '請選擇',
55 | input: '請輸入',
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | import '@/common/element-plugin'
5 | import i18n from "@/lang/index";
6 |
7 | import { initStorage } from "@proxy/shared-utils";
8 |
9 | Vue.prototype.$ELEMENT = { size: 'mini' };
10 | import "./index.scss"
11 |
12 | Vue.config.productionTip = false
13 |
14 | initStorage().then(() => {
15 | new Vue({
16 | i18n,
17 | render: h => h(App)
18 | }).$mount('#app')
19 | })
20 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 | {{ $t("backup") }}
17 |
18 |
25 | {{ $t("restore") }}
26 |
27 |
28 |
33 | {{
34 | $t("interceptor")
35 | }}
36 | {{
37 | $t("redirector")
38 | }}
39 |
40 |
41 |
47 |
53 |
54 |
55 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
267 |
324 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/interceptor/jsonEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
17 |
18 |
19 |
20 |
21 |
65 |
98 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/interceptor/modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
29 |
33 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
61 |
62 |
63 |
64 |
70 |
76 |
77 |
78 |
79 |
91 |
92 |
93 |
94 |
95 |
96 |
107 | JSON Editor
114 |
120 |
121 |
122 |
123 |
124 |
125 |
136 |
137 |
138 |
139 |
140 |
141 |
147 |
148 |
149 |
150 |
151 |
152 |
238 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/interceptor/table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{
4 | $t("create")
5 | }}
6 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
26 |
27 |
28 | {{
29 | $t("searchTxt")
30 | }}
31 | {{ $t("reset") }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {{
45 | { normal: $t("normal"), regex: $t("regex") }[
46 | row.filter_type || "normal"
47 | ]
48 | }}
49 |
50 |
51 |
52 |
53 | {{ row.method || "ANY" }}
54 |
55 |
56 |
62 |
63 | {{ row.status_code || 200 }}
64 |
65 |
66 |
71 |
76 |
82 |
83 |
84 |
90 | {{ row.hit }}
91 |
92 |
93 |
94 |
95 |
96 |
97 | {{ $t("edit") }}
98 |
99 | {{
100 | $t("del")
101 | }}
102 |
103 | {{
104 | $t("copy")
105 | }}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
295 |
296 |
304 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/interceptor/tag.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ tag.name }}
12 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
103 |
104 |
131 |
--------------------------------------------------------------------------------
/packages/vue-panels/src/views/redirector/table.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{
4 | $t("create")
5 | }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{
19 | { normal: $t("normal"), regex: $t("regex") }[
20 | row.filter_type || "normal"
21 | ]
22 | }}
23 |
24 |
25 |
26 |
27 | {{ row.method || "ANY" }}
28 |
29 |
30 |
35 |
40 |
45 |
46 |
47 |
48 | {{ $t("edit") }}
49 |
50 | {{
51 | $t("del")
52 | }}
53 |
54 | {{
55 | $t("copy")
56 | }}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
146 |
147 |
152 |
--------------------------------------------------------------------------------
/packages/vue-panels/vue.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // npm install compression-webpack-plugin@5.0.0 -S -D
3 | // const CompressionWebpackPlugin = require("compression-webpack-plugin");
4 | const isProduction = process.env.NODE_ENV === "production";
5 |
6 | module.exports = {
7 | devServer: {
8 | host: "0.0.0.0",
9 | port: 8082,
10 | https: false,
11 | hot: true, // 开启热模块加载
12 | disableHostCheck: true, // 启用 host 访问
13 | // proxy: {},
14 | },
15 | publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
16 | productionSourceMap: false,
17 | configureWebpack: (config) => {
18 | // 开启gzip压缩
19 | if (isProduction) {
20 | // config.plugins.push(
21 | // new CompressionWebpackPlugin({
22 | // algorithm: "gzip",
23 | // test: /\.js$|\.html$|\.json$|\.css/,
24 | // threshold: 10240,
25 | // minRatio: 0.8,
26 | // })
27 | // );
28 | // 开启分离js
29 | config.optimization = {
30 | runtimeChunk: "single",
31 | splitChunks: {
32 | chunks: "all",
33 | maxInitialRequests: Infinity,
34 | minSize: 20000,
35 | cacheGroups: {
36 | vendor: {
37 | test: /[\\/]node_modules[\\/]/,
38 | name(module) {
39 | // get the name. E.g. node_modules/packageName/not/this/part.js
40 | // or node_modules/packageName
41 | const packageName = module.context.match(
42 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/
43 | )[1];
44 | // npm package names are URL-safe, but some servers don't like @ symbols
45 | return `npm.${packageName.replace("@", "")}`;
46 | },
47 | },
48 | },
49 | },
50 | };
51 | // 取消webpack警告的性能提示
52 | config.performance = {
53 | hints: "warning",
54 | //入口起点的最大体积
55 | maxEntrypointSize: 50000000,
56 | //生成文件的最大体积
57 | maxAssetSize: 30000000,
58 | //只给出 js 文件的性能提示
59 | assetFilter: function(assetFilename) {
60 | return assetFilename.endsWith(".js");
61 | },
62 | };
63 | }
64 | },
65 | };
66 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | # all packages in subdirs of packages/ and components/
3 | - "packages/**"
4 | # exclude packages that are inside test directories
5 | - "!**/test/**"
6 |
--------------------------------------------------------------------------------
/release.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const inquirer = require('inquirer')
3 | const semver = require('semver')
4 | const pkg = require('./package.json')
5 | const manifest = require('./packages/shell-chrome/manifest.json')
6 |
7 | const curVersion = pkg.version
8 |
9 | ;(async () => {
10 | const { newVersion } = await inquirer.prompt([{
11 | type: 'input',
12 | name: 'newVersion',
13 | message: `Please provide a version (current: ${curVersion}):`,
14 | }])
15 |
16 | if (!semver.valid(newVersion)) {
17 | console.error(`Invalid version: ${newVersion}`)
18 | process.exit(1)
19 | }
20 |
21 | if (semver.lt(newVersion, curVersion)) {
22 | console.error(`New version (${newVersion}) cannot be lower than current version (${curVersion}).`)
23 | process.exit(1)
24 | }
25 |
26 | const { yes } = await inquirer.prompt([{
27 | name: 'yes',
28 | message: `Release ${newVersion}?`,
29 | type: 'confirm',
30 | }])
31 |
32 | if (yes) {
33 | pkg.version = newVersion
34 | manifest.version = newVersion
35 | fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2))
36 | fs.writeFileSync('./packages/shell-chrome/manifest.json', JSON.stringify(manifest, null, 2))
37 | } else {
38 | process.exit(1)
39 | }
40 | })()
41 |
--------------------------------------------------------------------------------
/zip/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/g0ngjie/ajax-proxy/d4c4edb6382dab15a0db0eb9cf6454666cc21684/zip/.gitkeep
--------------------------------------------------------------------------------