├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ ├── erb-logo.png │ └── palette-sponsor-banner.svg ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .yarnrc ├── README.md ├── README_CN.md ├── assets ├── assets.d.ts ├── entitlements.mac.plist ├── icon.icns ├── icon.ico ├── icon.png ├── icon.svg ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png └── iconx.png ├── mockttpx ├── .gitignore ├── index.js ├── package.json └── yarn.lock ├── package.json ├── readme └── 2022-11-26-13-10-19-image.png ├── release └── app │ ├── package-lock.json │ └── package.json ├── src ├── __tests__ │ └── App.test.tsx ├── main │ ├── main.ts │ ├── menu.ts │ ├── preload.ts │ └── util.ts └── renderer │ ├── App.less │ ├── App.tsx │ ├── index.ejs │ ├── index.tsx │ └── preload.d.ts ├── tsconfig.json ├── ui └── logo1024x1024.psd └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import webpackPaths from './webpack.paths'; 7 | import { dependencies as externals } from '../../release/app/package.json'; 8 | 9 | const configuration: webpack.Configuration = { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | stats: 'errors-only', 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.[jt]sx?$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader', 21 | options: { 22 | // Remove this line to enable type checking in webpack builds 23 | transpileOnly: true, 24 | compilerOptions: { 25 | module: 'esnext', 26 | }, 27 | }, 28 | }, 29 | }, 30 | ], 31 | }, 32 | 33 | output: { 34 | path: webpackPaths.srcPath, 35 | // https://github.com/webpack/webpack/issues/1114 36 | library: { 37 | type: 'commonjs2', 38 | }, 39 | }, 40 | 41 | /** 42 | * Determine the array of extensions that should be used to resolve modules. 43 | */ 44 | resolve: { 45 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 46 | modules: [webpackPaths.srcPath, 'node_modules'], 47 | }, 48 | 49 | plugins: [ 50 | new webpack.EnvironmentPlugin({ 51 | NODE_ENV: 'production', 52 | }), 53 | ], 54 | }; 55 | 56 | export default configuration; 57 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | }), 50 | 51 | /** 52 | * Create global constants which can be configured at compile time. 53 | * 54 | * Useful for allowing different behaviour between development builds and 55 | * release builds 56 | * 57 | * NODE_ENV should be production so that modules do not perform certain 58 | * development checks 59 | */ 60 | new webpack.EnvironmentPlugin({ 61 | NODE_ENV: 'production', 62 | DEBUG_PROD: false, 63 | START_MINIMIZED: false, 64 | }), 65 | 66 | new webpack.DefinePlugin({ 67 | 'process.type': '"main"', 68 | }), 69 | ], 70 | 71 | /** 72 | * Disables webpack processing of __dirname and __filename. 73 | * If you run the bundle in node.js it falls back to these values of node.js. 74 | * https://github.com/webpack/webpack/issues/2010 75 | */ 76 | node: { 77 | __dirname: false, 78 | __filename: false, 79 | }, 80 | }; 81 | 82 | export default merge(baseConfig, configuration); 83 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.ts: -------------------------------------------------------------------------------- 1 | import 'webpack-dev-server'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import webpack from 'webpack'; 5 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 6 | import chalk from 'chalk'; 7 | import { merge } from 'webpack-merge'; 8 | import { execSync, spawn } from 'child_process'; 9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | 14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 15 | // at the dev webpack config is not accidentally run in a production environment 16 | if (process.env.NODE_ENV === 'production') { 17 | checkNodeEnv('development'); 18 | } 19 | 20 | const port = process.env.PORT || 1212; 21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json'); 22 | const skipDLLs = 23 | module.parent?.filename.includes('webpack.config.renderer.dev.dll') || 24 | module.parent?.filename.includes('webpack.config.eslint'); 25 | 26 | /** 27 | * Warn if the DLL is not built 28 | */ 29 | if ( 30 | !skipDLLs && 31 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest)) 32 | ) { 33 | console.log( 34 | chalk.black.bgYellow.bold( 35 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 36 | ) 37 | ); 38 | execSync('npm run postinstall'); 39 | } 40 | 41 | const configuration: webpack.Configuration = { 42 | devtool: 'inline-source-map', 43 | 44 | mode: 'development', 45 | 46 | target: ['web', 'electron-renderer'], 47 | 48 | entry: [ 49 | `webpack-dev-server/client?http://localhost:${port}/dist`, 50 | 'webpack/hot/only-dev-server', 51 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 52 | ], 53 | 54 | output: { 55 | path: webpackPaths.distRendererPath, 56 | publicPath: '/', 57 | filename: 'renderer.dev.js', 58 | library: { 59 | type: 'umd', 60 | }, 61 | }, 62 | 63 | module: { 64 | rules: [ 65 | { 66 | test: /\.s?css$/, 67 | use: [ 68 | 'style-loader', 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | modules: true, 73 | sourceMap: true, 74 | importLoaders: 1, 75 | }, 76 | }, 77 | 'sass-loader', 78 | ], 79 | include: /\.module\.s?(c|a)ss$/, 80 | }, 81 | { 82 | test: /\.s?css$/, 83 | use: ['style-loader', 'css-loader', 'sass-loader'], 84 | exclude: /\.module\.s?(c|a)ss$/, 85 | }, 86 | { 87 | test: /\.less$/, 88 | use: ['style-loader', 'css-loader', { 89 | loader:'less-loader', 90 | options: { 91 | lessOptions: { 92 | javascriptEnabled: true, 93 | } 94 | } 95 | }], 96 | exclude: /\.module\.less$/, 97 | }, 98 | // Fonts 99 | { 100 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 101 | type: 'asset/resource', 102 | }, 103 | // Images 104 | { 105 | test: /\.(png|jpg|jpeg|gif)$/i, 106 | type: 'asset/resource', 107 | }, 108 | // SVG 109 | { 110 | test: /\.svg$/, 111 | use: [ 112 | { 113 | loader: '@svgr/webpack', 114 | options: { 115 | prettier: false, 116 | svgo: false, 117 | svgoConfig: { 118 | plugins: [{ removeViewBox: false }], 119 | }, 120 | titleProp: true, 121 | ref: true, 122 | }, 123 | }, 124 | 'file-loader', 125 | ], 126 | }, 127 | ], 128 | }, 129 | plugins: [ 130 | ...(skipDLLs 131 | ? [] 132 | : [ 133 | new webpack.DllReferencePlugin({ 134 | context: webpackPaths.dllPath, 135 | manifest: require(manifest), 136 | sourceType: 'var', 137 | }), 138 | ]), 139 | 140 | new webpack.NoEmitOnErrorsPlugin(), 141 | 142 | /** 143 | * Create global constants which can be configured at compile time. 144 | * 145 | * Useful for allowing different behaviour between development builds and 146 | * release builds 147 | * 148 | * NODE_ENV should be production so that modules do not perform certain 149 | * development checks 150 | * 151 | * By default, use 'development' as NODE_ENV. This can be overriden with 152 | * 'staging', for example, by changing the ENV variables in the npm scripts 153 | */ 154 | new webpack.EnvironmentPlugin({ 155 | NODE_ENV: 'development', 156 | }), 157 | 158 | new webpack.LoaderOptionsPlugin({ 159 | debug: true, 160 | }), 161 | 162 | new ReactRefreshWebpackPlugin(), 163 | 164 | new HtmlWebpackPlugin({ 165 | filename: path.join('index.html'), 166 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 167 | minify: { 168 | collapseWhitespace: true, 169 | removeAttributeQuotes: true, 170 | removeComments: true, 171 | }, 172 | isBrowser: false, 173 | env: process.env.NODE_ENV, 174 | isDevelopment: process.env.NODE_ENV !== 'production', 175 | nodeModules: webpackPaths.appNodeModulesPath, 176 | }), 177 | ], 178 | 179 | node: { 180 | __dirname: false, 181 | __filename: false, 182 | }, 183 | 184 | devServer: { 185 | port, 186 | compress: true, 187 | hot: true, 188 | headers: { 'Access-Control-Allow-Origin': '*' }, 189 | static: { 190 | publicPath: '/', 191 | }, 192 | historyApiFallback: { 193 | verbose: true, 194 | }, 195 | setupMiddlewares(middlewares) { 196 | console.log('Starting preload.js builder...'); 197 | const preloadProcess = spawn('npm', ['run', 'start:preload'], { 198 | shell: true, 199 | stdio: 'inherit', 200 | }) 201 | .on('close', (code: number) => process.exit(code!)) 202 | .on('error', (spawnError) => console.error(spawnError)); 203 | 204 | console.log('Starting Main Process...'); 205 | let args = ['run', 'start:main']; 206 | if (process.env.MAIN_ARGS) { 207 | args = args.concat( 208 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat() 209 | ); 210 | } 211 | spawn('npm', args, { 212 | shell: true, 213 | stdio: 'inherit', 214 | }) 215 | .on('close', (code: number) => { 216 | preloadProcess.kill(); 217 | process.exit(code!); 218 | }) 219 | .on('error', (spawnError) => console.error(spawnError)); 220 | return middlewares; 221 | }, 222 | }, 223 | }; 224 | 225 | export default merge(baseConfig, configuration); 226 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const configuration: webpack.Configuration = { 22 | devtool: 'source-map', 23 | 24 | mode: 'production', 25 | 26 | target: ['web', 'electron-renderer'], 27 | 28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], 29 | 30 | output: { 31 | path: webpackPaths.distRendererPath, 32 | publicPath: './', 33 | filename: 'renderer.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.s?(a|c)ss$/, 43 | use: [ 44 | MiniCssExtractPlugin.loader, 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | sourceMap: true, 50 | importLoaders: 1, 51 | }, 52 | }, 53 | 'sass-loader', 54 | ], 55 | include: /\.module\.s?(c|a)ss$/, 56 | }, 57 | { 58 | test: /\.s?(a|c)ss$/, 59 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], 60 | exclude: /\.module\.s?(c|a)ss$/, 61 | }, 62 | { 63 | test: /\.less$/, 64 | use: [MiniCssExtractPlugin.loader, 'css-loader', { 65 | loader:'less-loader', 66 | options: { 67 | lessOptions: { 68 | javascriptEnabled: true, 69 | } 70 | } 71 | }], 72 | exclude: /\.module\.less$/, 73 | }, 74 | // Fonts 75 | { 76 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 77 | type: 'asset/resource', 78 | }, 79 | // Images 80 | { 81 | test: /\.(png|jpg|jpeg|gif)$/i, 82 | type: 'asset/resource', 83 | }, 84 | // SVG 85 | { 86 | test: /\.svg$/, 87 | use: [ 88 | { 89 | loader: '@svgr/webpack', 90 | options: { 91 | prettier: false, 92 | svgo: false, 93 | svgoConfig: { 94 | plugins: [{ removeViewBox: false }], 95 | }, 96 | titleProp: true, 97 | ref: true, 98 | }, 99 | }, 100 | 'file-loader', 101 | ], 102 | }, 103 | ], 104 | }, 105 | 106 | optimization: { 107 | minimize: true, 108 | minimizer: [ 109 | new TerserPlugin({ 110 | parallel: true, 111 | }), 112 | new CssMinimizerPlugin(), 113 | ], 114 | }, 115 | 116 | plugins: [ 117 | /** 118 | * Create global constants which can be configured at compile time. 119 | * 120 | * Useful for allowing different behaviour between development builds and 121 | * release builds 122 | * 123 | * NODE_ENV should be production so that modules do not perform certain 124 | * development checks 125 | */ 126 | new webpack.EnvironmentPlugin({ 127 | NODE_ENV: 'production', 128 | DEBUG_PROD: false, 129 | }), 130 | 131 | new MiniCssExtractPlugin({ 132 | filename: 'style.css', 133 | }), 134 | 135 | new BundleAnalyzerPlugin({ 136 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 137 | }), 138 | 139 | new HtmlWebpackPlugin({ 140 | filename: 'index.html', 141 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 142 | minify: { 143 | collapseWhitespace: true, 144 | removeAttributeQuotes: true, 145 | removeComments: true, 146 | }, 147 | isBrowser: false, 148 | isDevelopment: process.env.NODE_ENV !== 'production', 149 | }), 150 | 151 | new webpack.DefinePlugin({ 152 | 'process.type': '"renderer"', 153 | }), 154 | ], 155 | }; 156 | 157 | export default merge(baseConfig, configuration); 158 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/img/erb-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/img/palette-sponsor-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://github.com/name-q/intermediator/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const foldersToRemove = [ 5 | webpackPaths.distPath, 6 | webpackPaths.buildPath, 7 | webpackPaths.dllPath, 8 | ]; 9 | 10 | foldersToRemove.forEach((folder) => { 11 | rimraf.sync(folder); 12 | }); 13 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('electron-notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 16 | console.warn( 17 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 18 | ); 19 | return; 20 | } 21 | 22 | const appName = context.packager.appInfo.productFilename; 23 | 24 | await notarize({ 25 | appBundleId: build.appId, 26 | appPath: `${appOutDir}/${appName}.app`, 27 | appleId: process.env.APPLE_ID, 28 | appleIdPassword: process.env.APPLE_ID_PASS, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'import/no-unresolved': 'error', 7 | // Since React 17 and typescript 4.1 you can safely disable the rule 8 | 'react/react-in-jsx-scope': 'off', 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 2020, 12 | sourceType: 'module', 13 | project: './tsconfig.json', 14 | tsconfigRootDir: __dirname, 15 | createDefaultProgram: true, 16 | }, 17 | settings: { 18 | 'import/resolver': { 19 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 20 | node: {}, 21 | webpack: { 22 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 23 | }, 24 | typescript: {}, 25 | }, 26 | 'import/parsers': { 27 | '@typescript-eslint/parser': ['.ts', '.tsx'], 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | Rule.qy 31 | mockttpx/dist -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npm.taobao.org" 2 | sass_binary_site "https://npm.taobao.org/mirrors/node-sass/" 3 | phantomjs_cdnurl "http://cnpmjs.org/downloads" 4 | electron_mirror "https://npm.taobao.org/mirrors/electron/" 5 | profiler_binary_host_mirror "https://npm.taobao.org/mirrors/node-inspector/" 6 | chromedriver_cdnurl "https://cdn.npm.taobao.org/dist/chromedriver" 7 | electron-builder-binaries_mirror "https://registry.npmmirror.com/-/binary/electron-builder-binaries/" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intermediator 2 | 3 | EN/[简体](README_CN.md) 4 | 5 | Intermediator is a lightweight open source tool for modifying interface return values on Windows (x64) and Mac systems. 6 | 7 | You can use it to modify HTTP (S) response values, redirect static file 8 | requests to the local file directory, and support batch modification of request 9 | return values in a regular manner. 10 | 11 | --- 12 | 13 | ## How to use 14 | 15 | 1. Enter the URL containing http/https 16 | 17 | 18 | 19 | 2. Click Add Rule 20 | 21 | 3. Edit your rules 22 | 23 | ```Easily 24 |      Currently, API, batch regular and path matching methods are 25 |      supported Enter the changed value/local path in the Changed value 26 | 27 |      Eg: Select the path mode 28 | 29 |      The input monitored api path supports fuzzy matching 30 |      Enter the local path in the Changed value It is worth noting that 31 |      the path pattern matches the path, not the file 32 | 33 |      Eg: Select the api mode 34 | 35 |      The full value of the input monitored api path does not match the 36 |      GET parameter in the api temporarily? x= &y= In Changed value, 37 |      enter the request response result, right click the copy value 38 |      and modify it to the desired value 39 | ``` 40 | 41 |     4. Click the browser icon to see the effect 42 | 43 | ## Intermediator Workflow 44 | 45 | | life cycle | Intermediator implement | Read Write Disk | New process | Listening port | 46 | | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------- | -------------- | 47 | | open | Read the rule file Rule.qy in the software directory | √ | x | x | 48 | | initialization | Apply the rules saved by the Save button and select the first rule by default | x | x | x | 49 | | Click Add Rule | Modify state value only | x | x | x | 50 | | Click Save Rule | Write the current rule to Rule.qy | √ | x | x | 51 | | Click to open the browser | Start the local node process and only apply the rules of the current URL to build the proxy service. Open the built-in browser to inject proxy rules and open devTools | √ | √ | √ | 52 | | Click again to open the browser after changing the rules | Start the local node process to listen to the new port, build a new rule proxy server for the current URL, and open a new window of the built-in browser to inject the proxy | √ | √ | √ | 53 | | Click again to open the browser after changing the URL and rules | ditto | √ | √ | √ | 54 | | Close a browser window | Do nothing | x | x | x | 55 | | Close all windows | End the process and shut down all agent services | x | x | x | 56 | 57 | ## 58 | 59 | ## Send your feedback 60 | 61 |     You can give feedback directly in ISSUES 62 | 63 |     [Issues · name-q/intermediator · GitHub](https://github.com/name-q/intermediator/issues) 64 | 65 | ## Download link 66 | 67 |     mac: 68 | 69 |         Intel chip Download Intermediator-1.0.0.dmg 70 | 71 |         M1 or M2 chip Download Intermediator-1.0.0-arm64.dmg 72 | 73 |     win: 74 | 75 |         Installation package for 64 bit systems only Download Intermediator.Setup.1.0.0.exe 76 | 77 |     [Release Intermediator · name-q/intermediator · GitHub](https://github.com/name-q/intermediator/releases/tag/PublicTest) 78 | 79 | ## common problem 80 | 81 | Q:What's the difference with PostMan 82 | 83 | A:PostMan is mainly used for back-end debugging interface, and Mediator is used for front-end debugging page 84 | 85 | Q:Common usage scenarios 86 | 87 | A: 1. Some environments have data but cannot be directly connected. You can copy the value to your environment 88 | 89 | 2. Modify a value to let the front-end engineer see the modified effect 90 | 91 | 3. Map multiple static file requests to the local hot update compiled directory 92 | 93 | Q:Why not support PC global request acquisition 94 | 95 | A:The global request must modify your network proxy&trust the CA certificate generated by us 96 | 97 | When using VPN or other proxy software, there will be competition for network proxy. Of course, we 98 | 99 | You can inject some software, such as Chrome, to support the injection of insecure CAs and proxies. 100 | 101 | There are many tools for packet capture and modification in the market, such as charles fiddler wireshark 102 | 103 | There are fees and learning costs. We only proxy our own browser without pollution. 104 | 105 | It can be achieved by packet capturing tools, but it does not conform to our clear and lightweight original intention and direction. 106 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Intermediator 2 | 3 | 简体/[EN](README.md) 4 | 5 | Intermediator是一个轻量开源工具,用于 Windows(x64)、 Mac 系统上修改接口返回值。 6 | 7 | 您可以使用它修改HTTP(S)响应值、重定向静态文件请求到本地文件目录,支持使用正则的方式批量修改请求返回值。 8 | 9 | --- 10 | 11 | ## 使用方式 12 | 13 |     1. 输入包含http/https的网址 14 | 15 | 16 | 17 |     2. 点击添加规则 18 | 19 |     3. 编辑你的规则 20 | 21 | ```便捷的编辑你的规则 22 |      当前支持指定API、批量正则和路径的方式匹配要改变的api 23 |      在Changed value中输入改变后的值/本地路径 24 |      eg:选择path模式 25 |         输入被监听的api路径 支持模糊匹配 26 |         Changed value中输入本地路径 27 |         值得注意的是path模式匹配到路径而不是文件 28 |      eg:选择api模式 29 |         输入被监听的api路径完整值 暂不匹配api中的GET参数 ?x= &y= 30 |         Changed value中输入请求响应结果的右击复制值并修改成你想要的值 31 | ``` 32 | 33 |     4. 点击浏览器图标即可看到效果 34 | 35 | ## Intermediator工作流程 36 | 37 | | 生命周期 | Intermediator执行 | 读写磁盘 | 新开进程 | 监听端口 | 38 | | ----------------- | --------------------------------------------------- | ---- | ---- | ---- | 39 | | 打开 | 读取软件目录中的规则文件Rule.qy | √ | x | x | 40 | | 初始化 | 应用保存按钮储存的规则并默认选择第一个规则 | x | x | x | 41 | | 点击新增规则 | 仅修改state值 | x | x | x | 42 | | 点击保存规则 | 写当前规则到Rule.qy | √ | x | x | 43 | | 点击打开浏览器 | 启动本地node进程并仅应用当前网址的规则构建代理服务,打开内置浏览器注入代理规则打开devTools | √ | √ | √ | 44 | | 改变规则后再次点击打开浏览器 | 启动本地node进程监听新的端口构建当前网址新规则代理服务器,打开内置浏览器新窗口注入代理 | √ | √ | √ | 45 | | 改变网址和规则后再次点击打开浏览器 | 同上 | √ | √ | √ | 46 | | 关闭某个浏览器窗口 | 不执行任何操作 | x | x | x | 47 | | 关闭所有窗口 | 结束进程并关闭所有代理服务 | x | x | x | 48 | 49 | ## 50 | 51 | ## 发送您的反馈 52 | 53 |     您可以直接在ISSUES中反馈 54 | 55 |     [Issues · name-q/intermediator · GitHub](https://github.com/name-q/intermediator/issues) 56 | 57 | ## 下载链接 58 | 59 |     mac: 60 | 61 |         Intel芯片下载Intermediator-1.0.0.dmg 62 | 63 |         M1或M2芯片下载Intermediator-1.0.0-arm64.dmg 64 | 65 |     win: 66 | 67 |         下载Intermediator.Setup.1.0.0.exe 仅适用64位系统 68 | 69 |     [Release Intermediator · name-q/intermediator · GitHub](https://github.com/name-q/intermediator/releases/tag/PublicTest) 70 | 71 | ## 常见问题 72 | 73 |     Q:和PostMan有什么区别 74 | 75 |     A:PostMan主要用于后端调试接口,Intermediator用于前端调试页面 76 | 77 |     Q:常见的使用场景 78 | 79 |     A: 1.部分环境有数据但不允许直接连调,可复制值到你的环境上 80 | 81 |             2.修改某个值让前端工程师看到修改后的效果 82 | 83 |             3.将多个静态文件请求映射到本地热更新编译后的目录 84 | 85 |     Q:为何不支持PC全局请求获取 86 | 87 |     A:    全局请求获取必须要修改您的网络代理&信任我们生成的CA证书 88 | 89 |               当使用VPN或其他代理软件时会出现互相争夺网络代理当然我们 90 | 91 |               可以注入某个软件例如Chrome支持不安全的CA和Proxy的注入。 92 | 93 |               市场上的抓包改包工具很丰富例如charles\fiddler\wireshark 94 | 95 |              有偿且有学习成本,我们仅代理了自带的浏览器不存在污染。 96 | 97 |               做抓包工具可以实现但并不符合我们明确且轻量的初衷和方向。 98 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | export const ReactComponent: React.FC>; 5 | 6 | const content: string; 7 | export default content; 8 | } 9 | 10 | declare module '*.png' { 11 | const content: string; 12 | export default content; 13 | } 14 | 15 | declare module '*.jpg' { 16 | const content: string; 17 | export default content; 18 | } 19 | 20 | declare module '*.scss' { 21 | const content: Styles; 22 | export default content; 23 | } 24 | 25 | declare module '*.sass' { 26 | const content: Styles; 27 | export default content; 28 | } 29 | 30 | declare module '*.css' { 31 | const content: Styles; 32 | export default content; 33 | } 34 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icon.png -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/iconx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/assets/iconx.png -------------------------------------------------------------------------------- /mockttpx/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | mockttpx 4 | mockttpx.exe -------------------------------------------------------------------------------- /mockttpx/index.js: -------------------------------------------------------------------------------- 1 | 2 | (async () => { 3 | const { promises: Fs } = require('fs') 4 | 5 | const mockttp = require('mockttp'); 6 | let rulex = JSON.parse(decodeURIComponent(process.argv[2]))[0] 7 | 8 | // create https proxy 9 | const https = await mockttp.generateCACertificate(); 10 | const server = mockttp.getLocal({ https }); 11 | 12 | if (rulex.onoff) { 13 | let { rule } = rulex 14 | if (rule.length) { 15 | // load rules 16 | rule.map(async item => { 17 | let { onoff, type, method, change, value } = item 18 | if (onoff && change) { 19 | change = change.split('?')[0] 20 | change = change.startsWith('/') ? change : `/${change}` 21 | if (['api'].includes(type)) { 22 | if (['POST', 'ALL'].includes(method)) { 23 | await server 24 | .forPost(change) 25 | .always() 26 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 27 | } 28 | if (['GET', 'ALL'].includes(method)) { 29 | await server 30 | .forGet(change) 31 | .always() 32 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 33 | } 34 | if (['PUT', 'ALL'].includes(method)) { 35 | await server 36 | .forPut(change) 37 | .always() 38 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 39 | } 40 | if (['DELETE', 'ALL'].includes(method)) { 41 | await server 42 | .forDelete(change) 43 | .always() 44 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 45 | } 46 | if (['HEAD', 'ALL'].includes(method)) { 47 | await server 48 | .forHead(change) 49 | .always() 50 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 51 | } 52 | if (['OPTIONS', 'ALL'].includes(method)) { 53 | await server 54 | .forOptions(change) 55 | .always() 56 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 57 | } 58 | // 这种方式不可取 会将页面丢失 ::根目录/也是GET:: 59 | // ALL定义为上述所有请求的规则载入与type === 'regular'一致 60 | // if (['ALL'].includes(method)) { 61 | // await server 62 | // .forAnyRequest(change) 63 | // .always() 64 | // .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 65 | // } 66 | } 67 | if (['regular'].includes(type)) { 68 | let reg = '/' 69 | try { 70 | reg = new RegExp(change) 71 | } catch { 72 | return 73 | } 74 | await server 75 | .forPost(reg) 76 | .always() 77 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 78 | await server 79 | .forGet(reg) 80 | .always() 81 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 82 | await server 83 | .forPut(reg) 84 | .always() 85 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 86 | await server 87 | .forDelete(reg) 88 | .always() 89 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 90 | await server 91 | .forHead(reg) 92 | .always() 93 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 94 | await server 95 | .forOptions(reg) 96 | .always() 97 | .thenReply(200, value, { "content-type": "application/json; charset=UTF-8" }) 98 | } 99 | if (['path'].includes(type)) { 100 | let valuex = false 101 | try { 102 | // value必须是目录 103 | valuex = await Fs.stat(value) 104 | valuex = valuex.isDirectory() 105 | } catch { 106 | return 107 | } 108 | if (change && valuex) { 109 | server.forGet(new RegExp(change)) 110 | .thenCallback(async (req) => { 111 | let { path } = req 112 | // 获取文件名(带后缀) 113 | const fileName = path.substring(path.lastIndexOf('/') + 1).split('?')[0].toLocaleLowerCase() 114 | // 获取后缀 115 | let suffix = fileName.split('.')[fileName.split('.').length - 1].toLocaleLowerCase() 116 | // 获取文件buffer 117 | let rawBody = 'cannot find file' 118 | try { 119 | value = value.endsWith('/') ? value : `${value}/` 120 | rawBody = await Fs.readFile(`${value}${fileName}`) 121 | } catch { 122 | return { 123 | statusCode: 404, 124 | rawBody 125 | } 126 | } 127 | // 根据后缀反推Content-Type 128 | // 浏览器根据Content-Type优化预览时的展示 129 | // 怎么说呢type类型太多了没必要加全 把常用的搞出来吧 130 | let headers 131 | let enumContentType = { 132 | js: 'application/javascript', 133 | css: 'text/css', 134 | 135 | woff2: 'font/woff2', 136 | html: 'text/html', 137 | xhtml: 'text/html', 138 | jsp: 'text/html', 139 | txt: 'text/plain', 140 | xml: 'text/xml', 141 | 142 | gif: 'image/gif', 143 | jpeg: 'image/jpeg', 144 | jpg: 'image/jpeg', 145 | png: 'image/png', 146 | pdf: 'application/pdf' 147 | } 148 | 149 | if (enumContentType[suffix]) headers = { 150 | "Content-Type": ['image', 'font'].includes(enumContentType[suffix]) 151 | ? enumContentType[suffix] : `${enumContentType[suffix]}; charset=UTF-8` 152 | } 153 | 154 | if (headers) return { 155 | statusCode: 200, 156 | headers, 157 | rawBody 158 | } 159 | 160 | return { 161 | statusCode: 200, 162 | rawBody 163 | } 164 | }) 165 | } 166 | } 167 | } 168 | }) 169 | } 170 | } 171 | // Thanks, Tim Perry 172 | await server.forUnmatchedRequest().always().thenPassThrough() 173 | 174 | // open proxy 175 | await server.start(); 176 | 177 | const caFingerprint = mockttp.generateSPKIFingerprint(https.cert); 178 | 179 | console.log(`${server.port}>>>${caFingerprint}`); 180 | })(); -------------------------------------------------------------------------------- /mockttpx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mockttpx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "pkg": "pkg package.json --out-path=dist/" 9 | }, 10 | "keywords": [], 11 | "license": "MIT", 12 | "author": { 13 | "name": "name-q", 14 | "email": "1620206666@qq.com", 15 | "url": "https://github.com/name-q/intermediator" 16 | }, 17 | "dependencies": { 18 | "mockttp": "^3.6.0" 19 | }, 20 | "pkg": { 21 | "scripts": [], 22 | "targets": [ 23 | "node14-macos-x64" 24 | ], 25 | "assets": [ 26 | "node_modules/zstd-codec/lib/zstd-codec-binding.js", 27 | "node_modules/vm2/lib/setup-sandbox.js" 28 | ] 29 | }, 30 | "bin": "./index.js", 31 | "devDependencies": { 32 | "pkg": "^5.8.0" 33 | } 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Small and easy to use tool to change the API RESponse value, which only changes itself without polluting the system agent", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "electron", 6 | "boilerplate", 7 | "react", 8 | "typescript", 9 | "ts", 10 | "sass", 11 | "webpack", 12 | "hot", 13 | "reload" 14 | ], 15 | "homepage": "https://github.com/name-q/intermediator#readme", 16 | "bugs": { 17 | "url": "https://github.com/name-q/intermediator/issues" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/name-q/intermediator.git" 22 | }, 23 | "license": "MIT", 24 | "author": { 25 | "name": "name-q", 26 | "email": "1620206666@qq.com", 27 | "url": "https://github.com/name-q/intermediator" 28 | }, 29 | "contributors": [ 30 | { 31 | "name": "name-q", 32 | "email": "1620206666@qq.com", 33 | "url": "https://github.com/name-q/intermediator" 34 | } 35 | ], 36 | "main": "./src/main/main.ts", 37 | "scripts": { 38 | "build": "concurrently \"yarn build:main\" \"yarn build:renderer\"", 39 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", 40 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", 41 | "postinstall": "npx ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", 42 | "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", 43 | "package:mac": "npx ts-node ./.erb/scripts/clean.js dist && yarn build && electron-builder build --publish never", 44 | "package:win": "npx ts-node ./.erb/scripts/clean.js dist && yarn build && electron-builder build --win --publish never", 45 | "prepare": "husky install", 46 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app", 47 | "start": "npx ts-node ./.erb/scripts/check-port-in-use.js && yarn start:renderer", 48 | "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .", 49 | "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", 50 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", 51 | "build:test": "npx ts-node ./.erb/scripts/clean.js dist && npx cross-env DEBUG_PROD=true npm run package", 52 | "test": "jest" 53 | }, 54 | "lint-staged": { 55 | "*.{js,jsx,ts,tsx}": [ 56 | "cross-env NODE_ENV=development eslint --cache" 57 | ], 58 | "*.json,.{eslintrc,prettierrc}": [ 59 | "prettier --ignore-path .eslintignore --parser json --write" 60 | ], 61 | "*.{css,scss}": [ 62 | "prettier --ignore-path .eslintignore --single-quote --write" 63 | ], 64 | "*.{html,md,yml}": [ 65 | "prettier --ignore-path .eslintignore --single-quote --write" 66 | ] 67 | }, 68 | "browserslist": [], 69 | "prettier": { 70 | "singleQuote": true, 71 | "overrides": [ 72 | { 73 | "files": [ 74 | ".prettierrc", 75 | ".eslintrc" 76 | ], 77 | "options": { 78 | "parser": "json" 79 | } 80 | } 81 | ] 82 | }, 83 | "jest": { 84 | "moduleDirectories": [ 85 | "node_modules", 86 | "release/app/node_modules", 87 | "src" 88 | ], 89 | "moduleFileExtensions": [ 90 | "js", 91 | "jsx", 92 | "ts", 93 | "tsx", 94 | "json" 95 | ], 96 | "moduleNameMapper": { 97 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", 98 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 99 | }, 100 | "setupFiles": [ 101 | "./.erb/scripts/check-build-exists.ts" 102 | ], 103 | "testEnvironment": "jsdom", 104 | "testEnvironmentOptions": { 105 | "url": "http://localhost/" 106 | }, 107 | "testPathIgnorePatterns": [ 108 | "release/app/dist" 109 | ], 110 | "transform": { 111 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 112 | } 113 | }, 114 | "dependencies": { 115 | "antd": "^4.24.2", 116 | "child_process": "^1.0.2", 117 | "crypto-js": "^4.1.1", 118 | "electron-debug": "^3.2.0", 119 | "electron-log": "^4.4.8", 120 | "electron-updater": "^5.2.3", 121 | "fix-path": "^4.0.0", 122 | "react": "^18.2.0", 123 | "react-dom": "^18.2.0", 124 | "react-json-view": "^1.21.3", 125 | "react-router-dom": "^6.3.0" 126 | }, 127 | "devDependencies": { 128 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", 129 | "@svgr/webpack": "^6.3.1", 130 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", 131 | "@testing-library/jest-dom": "^5.16.5", 132 | "@testing-library/react": "^13.3.0", 133 | "@types/jest": "^28.1.7", 134 | "@types/node": "18.7.6", 135 | "@types/react": "^18.0.17", 136 | "@types/react-dom": "^18.0.6", 137 | "@types/react-test-renderer": "^18.0.0", 138 | "@types/terser-webpack-plugin": "^5.0.4", 139 | "@types/webpack-bundle-analyzer": "^4.4.2", 140 | "@typescript-eslint/eslint-plugin": "^5.33.1", 141 | "@typescript-eslint/parser": "^5.33.1", 142 | "browserslist-config-erb": "^0.0.3", 143 | "chalk": "^4.1.2", 144 | "concurrently": "^7.3.0", 145 | "core-js": "^3.24.1", 146 | "cross-env": "^7.0.3", 147 | "css-loader": "^6.7.1", 148 | "css-minimizer-webpack-plugin": "^4.0.0", 149 | "detect-port": "^1.3.0", 150 | "electron": "^20.0.2", 151 | "electron-builder": "^23.3.3", 152 | "electron-devtools-installer": "^3.2.0", 153 | "electron-notarize": "^1.2.1", 154 | "electron-rebuild": "^3.2.9", 155 | "electronmon": "^2.0.2", 156 | "eslint": "^8.22.0", 157 | "eslint-config-airbnb-base": "^15.0.0", 158 | "eslint-config-erb": "^4.0.3", 159 | "eslint-import-resolver-typescript": "^3.4.1", 160 | "eslint-import-resolver-webpack": "^0.13.2", 161 | "eslint-plugin-compat": "^4.0.2", 162 | "eslint-plugin-import": "^2.26.0", 163 | "eslint-plugin-jest": "^26.8.3", 164 | "eslint-plugin-jsx-a11y": "^6.6.1", 165 | "eslint-plugin-promise": "^6.0.0", 166 | "eslint-plugin-react": "^7.30.1", 167 | "eslint-plugin-react-hooks": "^4.6.0", 168 | "file-loader": "^6.2.0", 169 | "html-webpack-plugin": "^5.5.0", 170 | "husky": "^8.0.1", 171 | "identity-obj-proxy": "^3.0.0", 172 | "jest": "^28.1.3", 173 | "jest-environment-jsdom": "^28.1.3", 174 | "less": "^4.1.3", 175 | "less-loader": "^11.1.0", 176 | "lint-staged": "^13.0.3", 177 | "mini-css-extract-plugin": "^2.6.1", 178 | "prettier": "^2.7.1", 179 | "react-refresh": "^0.14.0", 180 | "react-test-renderer": "^18.2.0", 181 | "rimraf": "^3.0.2", 182 | "sass": "^1.54.4", 183 | "sass-loader": "^13.0.2", 184 | "style-loader": "^3.3.1", 185 | "terser-webpack-plugin": "^5.3.5", 186 | "ts-jest": "^28.0.8", 187 | "ts-loader": "^9.3.1", 188 | "ts-node": "^10.9.1", 189 | "typescript": "^4.7.4", 190 | "url-loader": "^4.1.1", 191 | "webpack": "^5.74.0", 192 | "webpack-bundle-analyzer": "^4.5.0", 193 | "webpack-cli": "^4.10.0", 194 | "webpack-dev-server": "^4.10.0", 195 | "webpack-merge": "^5.8.0" 196 | }, 197 | "build": { 198 | "productName": "Intermediator", 199 | "appId": "org.erb.Intermediator", 200 | "asar": true, 201 | "asarUnpack": "**\\*.{node,dll}", 202 | "files": [ 203 | "dist", 204 | "node_modules", 205 | "package.json" 206 | ], 207 | "afterSign": ".erb/scripts/notarize.js", 208 | "mac": { 209 | "target": { 210 | "target": "default", 211 | "arch": [ 212 | "arm64", 213 | "x64" 214 | ] 215 | }, 216 | "type": "distribution", 217 | "hardenedRuntime": true, 218 | "entitlements": "assets/entitlements.mac.plist", 219 | "entitlementsInherit": "assets/entitlements.mac.plist", 220 | "gatekeeperAssess": false 221 | }, 222 | "dmg": { 223 | "contents": [ 224 | { 225 | "x": 130, 226 | "y": 220 227 | }, 228 | { 229 | "x": 410, 230 | "y": 220, 231 | "type": "link", 232 | "path": "/Applications" 233 | } 234 | ] 235 | }, 236 | "win": { 237 | "requestedExecutionLevel": "requireAdministrator", 238 | "target": [ 239 | {"target":"nsis","arch":"x64"} 240 | ] 241 | }, 242 | "nsis": { 243 | "oneClick": false, 244 | "allowElevation": true, 245 | "allowToChangeInstallationDirectory": true, 246 | "installerHeaderIcon": "./assets/icon.ico", 247 | "createDesktopShortcut": true, 248 | "createStartMenuShortcut": true, 249 | "shortcutName": "Intermediator" 250 | }, 251 | "linux": { 252 | "target": [ 253 | "AppImage" 254 | ], 255 | "category": "Development" 256 | }, 257 | "directories": { 258 | "app": "release/app", 259 | "buildResources": "assets", 260 | "output": "release/build" 261 | }, 262 | "extraResources": [ 263 | "./assets/**", 264 | "./mockttpx/dist/**" 265 | ], 266 | "publish": { 267 | "provider": "github", 268 | "owner": "intermediator", 269 | "repo": "intermediator" 270 | } 271 | }, 272 | "collective": { 273 | "url": "https://opencollective.com/intermediator-594" 274 | }, 275 | "devEngines": { 276 | "node": ">=14.x", 277 | "npm": ">=7.x" 278 | }, 279 | "electronmon": { 280 | "patterns": [ 281 | "!**/**", 282 | "src/main/**" 283 | ], 284 | "logLevel": "quiet" 285 | } 286 | } -------------------------------------------------------------------------------- /readme/2022-11-26-13-10-19-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/name-q/intermediator/f55837f669c92f4e4797b72dad1fd5ebf9ad2f71/readme/2022-11-26-13-10-19-image.png -------------------------------------------------------------------------------- /release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intermediator", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intermediator", 3 | "version": "1.0.0", 4 | "description": "", 5 | "license": "MIT", 6 | "author": { 7 | "name": "name-q", 8 | "email": "1620206666@qq.com", 9 | "url": "https://github.com/name-q" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, no-console: off, promise/always-return: off */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build:main`, this file is compiled to 9 | * `./src/main.js` using webpack. This gives us some performance wins. 10 | */ 11 | import path from 'path'; 12 | import { app, BrowserWindow, shell, ipcMain, dialog, screen } from 'electron'; 13 | // import { autoUpdater } from 'electron-updater'; 14 | // import log from 'electron-log'; 15 | import MenuBuilder from './menu'; 16 | import { resolveHtmlPath } from './util'; 17 | 18 | // yarn 19 | // install .base_profile && .zshrc environment variable -->node 20 | import fixPath from 'fix-path' 21 | fixPath() 22 | 23 | const cp = require('child_process') 24 | 25 | // 终端命令 26 | let cps: Array = [] 27 | 28 | // Proxy ignore 29 | app.commandLine.appendSwitch('ignore-certificate-errors'); 30 | app.commandLine.appendSwitch("disable-site-isolation-trials"); 31 | app.commandLine.appendSwitch('allow-insecure-localhost', 'true'); 32 | 33 | //引入node原生fs模块 34 | const fs = require("fs") 35 | //引入node原生读写配置 36 | const ini = require('ini'); 37 | // class AppUpdater { 38 | // constructor() { 39 | // log.transports.file.level = 'info'; 40 | // autoUpdater.logger = log; 41 | // autoUpdater.checkForUpdatesAndNotify(); 42 | // } 43 | // } 44 | 45 | let mainWindow: BrowserWindow | null = null; 46 | let ruleWindow: BrowserWindow; 47 | 48 | ipcMain.on('ipc-example', async (event, arg) => { 49 | const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; 50 | // console.log(msgTemplate(arg)); 51 | event.reply('ipc-example', msgTemplate('pong')); 52 | }); 53 | 54 | // 打开内部浏览器并应用规则 55 | ipcMain.on('intermediator', async (event, arg) => { 56 | let [rule, url] = arg 57 | // console.log(rule, '<<<<>>>', url) 58 | try { 59 | const RESOURCES_PATH = app.isPackaged 60 | ? path.join(process.resourcesPath, 'assets') 61 | : path.join(__dirname, '../../assets'); 62 | 63 | const getAssetPath = (...paths: string[]): string => { 64 | return path.join(RESOURCES_PATH, ...paths); 65 | }; 66 | 67 | let { width, height } = screen.getPrimaryDisplay().workAreaSize 68 | width = width * .8 69 | height = height * .8 70 | ruleWindow = new BrowserWindow({ 71 | width, 72 | height, 73 | icon: getAssetPath('iconx.png'), 74 | skipTaskbar: true, 75 | title: `intermediator-->${url}`, 76 | webPreferences: { 77 | nodeIntegration: false, 78 | contextIsolation: false, 79 | webSecurity: false, 80 | devTools: true, 81 | } 82 | }); 83 | 84 | const spawn = cp.spawn('./mockttpx', [encodeURIComponent(JSON.stringify(rule))], { 85 | maxBuffer: 1024 * 1024 * 999, 86 | cwd: app.isPackaged 87 | ? path.join(process.resourcesPath, 'mockttpx/dist') 88 | : path.join(__dirname, '../../mockttpx/dist') 89 | }) 90 | cps[cps.length] = spawn 91 | 92 | spawn.stdout.on('data', async (data: any) => { 93 | // get proxy prot & PEM 94 | let [prot, PEM] = (data + "").split('>>>') 95 | // console.log(JSON.stringify(rule), '<<< { 103 | if (!ruleWindow) { 104 | throw new Error('"ruleWindow" is not defined'); 105 | } else { 106 | setTimeout(() => { 107 | // hide 108 | ruleWindow.setSkipTaskbar(true) 109 | }, 200) 110 | } 111 | }); 112 | }) 113 | 114 | } catch (error: any) { 115 | dialog.showMessageBox(ruleWindow, { 116 | type: 'error', 117 | message: error, 118 | detail: 'ERROR' 119 | }) 120 | event.reply('intermediator', error) 121 | } 122 | event.reply('intermediator', 'wait ...') 123 | return 124 | }) 125 | 126 | // 获取规则缓存文件 127 | ipcMain.on('fs', async (event, arg) => { 128 | // const msgTemplate = (msg: string) => `fs: ${msg}`; 129 | // console.log(msgTemplate(arg)); 130 | 131 | let rulePath = app.isPackaged 132 | ? path.join(process.resourcesPath, 'Rule.qy') 133 | : path.join(__dirname, '../../Rule.qy'); 134 | 135 | if (!fs.existsSync(rulePath)) { 136 | // 写入初始缓存文件 137 | fs.writeFileSync(rulePath, ini.stringify([])) 138 | } 139 | 140 | // 获取缓存文件 141 | if (arg[0] === 'getCache') { 142 | let rule = ini.parse(fs.readFileSync(rulePath).toString()); 143 | event.reply('fs', rule); 144 | } 145 | 146 | // 写入缓存文件 147 | if (arg[0] === 'setCache') { 148 | fs.writeFileSync(rulePath, ini.stringify(arg[1])) 149 | event.reply('fs', 'rule save ok'); 150 | } 151 | 152 | }); 153 | 154 | if (process.env.NODE_ENV === 'production') { 155 | const sourceMapSupport = require('source-map-support'); 156 | sourceMapSupport.install(); 157 | } 158 | 159 | // const isDebug = 160 | // process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; 161 | 162 | // if (isDebug) { 163 | // require('electron-debug')(); 164 | // } 165 | 166 | // const installExtensions = async () => { 167 | // const installer = require('electron-devtools-installer'); 168 | // const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 169 | // const extensions = ['REACT_DEVELOPER_TOOLS']; 170 | 171 | // return installer 172 | // .default( 173 | // extensions.map((name) => installer[name]), 174 | // forceDownload 175 | // ) 176 | // .catch(console.log); 177 | // }; 178 | 179 | const createWindow = async () => { 180 | // if (isDebug) { 181 | // await installExtensions(); 182 | // } 183 | 184 | const RESOURCES_PATH = app.isPackaged 185 | ? path.join(process.resourcesPath, 'assets') 186 | : path.join(__dirname, '../../assets'); 187 | 188 | const getAssetPath = (...paths: string[]): string => { 189 | return path.join(RESOURCES_PATH, ...paths); 190 | }; 191 | 192 | mainWindow = new BrowserWindow({ 193 | show: false, 194 | width: 333, 195 | height: 666, 196 | icon: getAssetPath('icon.png'), 197 | alwaysOnTop: true, 198 | webPreferences: { 199 | devTools: false, 200 | preload: app.isPackaged 201 | ? path.join(__dirname, 'preload.js') 202 | : path.join(__dirname, '../../.erb/dll/preload.js'), 203 | }, 204 | }); 205 | 206 | mainWindow.loadURL(resolveHtmlPath('index.html')); 207 | 208 | mainWindow.on('ready-to-show', () => { 209 | if (!mainWindow) { 210 | throw new Error('"mainWindow" is not defined'); 211 | } 212 | if (process.env.START_MINIMIZED) { 213 | mainWindow.minimize(); 214 | } else { 215 | setTimeout(() => mainWindow?.show(), 200) 216 | } 217 | }); 218 | 219 | mainWindow.on('closed', () => { 220 | mainWindow = null; 221 | }); 222 | 223 | const menuBuilder = new MenuBuilder(mainWindow); 224 | menuBuilder.buildMenu(); 225 | 226 | // Open urls in the user's browser 227 | mainWindow.webContents.setWindowOpenHandler((edata) => { 228 | shell.openExternal(edata.url); 229 | return { action: 'deny' }; 230 | }); 231 | 232 | // Remove this if your app does not use auto updates 233 | // eslint-disable-next-line 234 | // new AppUpdater(); 235 | }; 236 | 237 | /** 238 | * Add event listeners... 239 | */ 240 | 241 | app.on('window-all-closed', () => { 242 | // close all proxy 243 | for (let i = 0; i < cps.length; i++) { 244 | cps[i].kill() 245 | } 246 | // Respect the OSX convention of having the application in memory even 247 | // after all windows have been closed 248 | if (process.platform !== 'darwin') { 249 | app.quit(); 250 | } 251 | }); 252 | 253 | // 阻止点击内部链接打开新窗口 254 | let contentTemp: any = null; 255 | const newWindowListener = (e: any) => { 256 | e.preventDefault(); 257 | dialog.showMessageBox(ruleWindow, { 258 | type: 'error', 259 | message: 'Rules in effect', 260 | detail: 'Prevent opening new windows through links' 261 | }) 262 | contentTemp?.removeListener("new-window", newWindowListener); 263 | }; 264 | app.on("web-contents-created", (e, webContents) => { 265 | webContents.addListener("new-window", newWindowListener); 266 | contentTemp = webContents; 267 | }); 268 | 269 | app 270 | .whenReady() 271 | .then(() => { 272 | createWindow(); 273 | app.on('activate', () => { 274 | // On macOS it's common to re-create a window in the app when the 275 | // dock icon is clicked and there are no other windows open. 276 | if (mainWindow === null) createWindow(); 277 | }); 278 | }) 279 | .catch(console.log); 280 | -------------------------------------------------------------------------------- /src/main/menu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | Menu, 4 | shell, 5 | BrowserWindow, 6 | MenuItemConstructorOptions, 7 | } from 'electron'; 8 | 9 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { 10 | selector?: string; 11 | submenu?: DarwinMenuItemConstructorOptions[] | Menu; 12 | } 13 | 14 | export default class MenuBuilder { 15 | mainWindow: BrowserWindow; 16 | 17 | constructor(mainWindow: BrowserWindow) { 18 | this.mainWindow = mainWindow; 19 | } 20 | 21 | buildMenu(): Menu { 22 | if ( 23 | process.env.NODE_ENV === 'development' || 24 | process.env.DEBUG_PROD === 'true' 25 | ) { 26 | this.setupDevelopmentEnvironment(); 27 | } 28 | 29 | const template = 30 | process.platform === 'darwin' 31 | ? this.buildDarwinTemplate() 32 | : this.buildDefaultTemplate(); 33 | 34 | const menu = Menu.buildFromTemplate(template); 35 | Menu.setApplicationMenu(menu); 36 | 37 | return menu; 38 | } 39 | 40 | setupDevelopmentEnvironment(): void { 41 | this.mainWindow.webContents.on('context-menu', (_, props) => { 42 | const { x, y } = props; 43 | 44 | Menu.buildFromTemplate([ 45 | { 46 | label: 'Inspect element', 47 | click: () => { 48 | this.mainWindow.webContents.inspectElement(x, y); 49 | }, 50 | }, 51 | ]).popup({ window: this.mainWindow }); 52 | }); 53 | } 54 | 55 | buildDarwinTemplate(): MenuItemConstructorOptions[] { 56 | const subMenuAbout: DarwinMenuItemConstructorOptions = { 57 | label: 'Electron', 58 | submenu: [ 59 | { 60 | label: 'Quit', 61 | accelerator: 'Command+Q', 62 | click: () => { 63 | app.quit(); 64 | }, 65 | }, 66 | ], 67 | }; 68 | const subMenuEdit: DarwinMenuItemConstructorOptions = { 69 | label: 'Edit', 70 | submenu: [ 71 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 72 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 73 | { type: 'separator' }, 74 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 75 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 76 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 77 | { 78 | label: 'Select All', 79 | accelerator: 'Command+A', 80 | selector: 'selectAll:', 81 | }, 82 | ], 83 | }; 84 | const subMenuViewDev: MenuItemConstructorOptions = { 85 | label: 'View', 86 | submenu: [ 87 | { 88 | label: 'Reload', 89 | accelerator: 'Command+R', 90 | click: () => { 91 | this.mainWindow.webContents.reload(); 92 | }, 93 | }, 94 | { 95 | label: 'Toggle Full Screen', 96 | accelerator: 'Ctrl+Command+F', 97 | click: () => { 98 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 99 | }, 100 | }, 101 | { 102 | label: 'Toggle Developer Tools', 103 | accelerator: 'Alt+Command+I', 104 | click: () => { 105 | this.mainWindow.webContents.toggleDevTools(); 106 | }, 107 | }, 108 | ], 109 | }; 110 | const subMenuViewProd: MenuItemConstructorOptions = { 111 | label: 'View', 112 | submenu: [ 113 | { 114 | label: 'Toggle Full Screen', 115 | accelerator: 'Ctrl+Command+F', 116 | click: () => { 117 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 118 | }, 119 | }, 120 | ], 121 | }; 122 | const subMenuWindow: DarwinMenuItemConstructorOptions = { 123 | label: 'Window', 124 | submenu: [ 125 | { 126 | label: 'Minimize', 127 | accelerator: 'Command+M', 128 | selector: 'performMiniaturize:', 129 | }, 130 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 131 | { type: 'separator' }, 132 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }, 133 | ], 134 | }; 135 | const subMenuHelp: MenuItemConstructorOptions = { 136 | label: 'Help', 137 | submenu: [ 138 | { 139 | label: 'Learn More', 140 | click() { 141 | shell.openExternal('https://github.com/name-q'); 142 | }, 143 | }, 144 | { 145 | label: 'Documentation', 146 | click() { 147 | shell.openExternal( 148 | 'https://github.com/name-q/intermediator' 149 | ); 150 | }, 151 | }, 152 | { 153 | label: 'Search Issues', 154 | click() { 155 | shell.openExternal('https://github.com/name-q/intermediator/issues'); 156 | }, 157 | }, 158 | ], 159 | }; 160 | 161 | const subMenuView = 162 | process.env.NODE_ENV === 'development' || 163 | process.env.DEBUG_PROD === 'true' 164 | ? subMenuViewDev 165 | : subMenuViewProd; 166 | 167 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; 168 | } 169 | 170 | buildDefaultTemplate() { 171 | const templateDefault = [ 172 | { 173 | label: '&File', 174 | submenu: [ 175 | { 176 | label: '&Open', 177 | accelerator: 'Ctrl+O', 178 | }, 179 | { 180 | label: '&Close', 181 | accelerator: 'Ctrl+W', 182 | click: () => { 183 | this.mainWindow.close(); 184 | }, 185 | }, 186 | ], 187 | }, 188 | { 189 | label: '&View', 190 | submenu: 191 | process.env.NODE_ENV === 'development' || 192 | process.env.DEBUG_PROD === 'true' 193 | ? [ 194 | { 195 | label: '&Reload', 196 | accelerator: 'Ctrl+R', 197 | click: () => { 198 | this.mainWindow.webContents.reload(); 199 | }, 200 | }, 201 | { 202 | label: 'Toggle &Full Screen', 203 | accelerator: 'F11', 204 | click: () => { 205 | this.mainWindow.setFullScreen( 206 | !this.mainWindow.isFullScreen() 207 | ); 208 | }, 209 | }, 210 | { 211 | label: 'Toggle &Developer Tools', 212 | accelerator: 'Alt+Ctrl+I', 213 | click: () => { 214 | this.mainWindow.webContents.toggleDevTools(); 215 | }, 216 | }, 217 | ] 218 | : [ 219 | { 220 | label: 'Toggle &Full Screen', 221 | accelerator: 'F11', 222 | click: () => { 223 | this.mainWindow.setFullScreen( 224 | !this.mainWindow.isFullScreen() 225 | ); 226 | }, 227 | }, 228 | ], 229 | }, 230 | { 231 | label: 'Help', 232 | submenu: [ 233 | { 234 | label: 'Learn More', 235 | click() { 236 | shell.openExternal('https://github.com/name-q'); 237 | }, 238 | }, 239 | { 240 | label: 'Documentation', 241 | click() { 242 | shell.openExternal( 243 | 'https://github.com/name-q/intermediator' 244 | ); 245 | }, 246 | }, 247 | { 248 | label: 'Search Issues', 249 | click() { 250 | shell.openExternal('https://github.com/name-q/intermediator/issues'); 251 | }, 252 | }, 253 | ], 254 | }, 255 | ]; 256 | 257 | return templateDefault; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 2 | 3 | export type Channels = 'ipc-example' | 'fs' | 'intermediator'; 4 | 5 | contextBridge.exposeInMainWorld('electron', { 6 | ipcRenderer: { 7 | sendMessage(channel: Channels, args: unknown[]) { 8 | ipcRenderer.send(channel, args); 9 | }, 10 | on(channel: Channels, func: (...args: unknown[]) => void) { 11 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 12 | func(...args); 13 | ipcRenderer.on(channel, subscription); 14 | 15 | return () => { 16 | ipcRenderer.removeListener(channel, subscription); 17 | }; 18 | }, 19 | once(channel: Channels, func: (...args: unknown[]) => void) { 20 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/App.less: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.less'; 2 | 3 | @bg: rgb(221, 249, 252); 4 | @border: #d9d9d9; 5 | 6 | * { 7 | // 文字不能被选中 8 | -webkit-user-select: none; 9 | -moz-user-select: none; 10 | -ms-user-select: none; 11 | user-select: none; 12 | } 13 | 14 | body { 15 | margin: 0; 16 | } 17 | 18 | body::-webkit-scrollbar { 19 | width: 0; 20 | } 21 | 22 | #main { 23 | background-color: @bg; 24 | height: 100vh; 25 | } 26 | 27 | .ant-input-group-addon { 28 | padding: 0 !important; 29 | } 30 | 31 | .toolsBox { 32 | display: flex; 33 | height: 35px; 34 | border-bottom: 2.33333px dashed @border; 35 | flex-direction: row; 36 | align-items: center; 37 | justify-content: space-between; 38 | padding: 0 10px; 39 | } 40 | 41 | .ant-input-group-addon { 42 | cursor: pointer; 43 | } 44 | 45 | [data-theme='compact'] .site-collapse-custom-collapse .site-collapse-custom-panel, 46 | .site-collapse-custom-collapse .site-collapse-custom-panel { 47 | margin-bottom: 10px; 48 | overflow: hidden; 49 | border-radius: 0 0 10px 10px; 50 | background-color: @bg; 51 | } 52 | 53 | // // 折叠标题换行 54 | // .ant-collapse > .ant-collapse-item > .ant-collapse-header .ant-collapse-header-text { 55 | // flex: auto; 56 | // display: inline-block; 57 | // width: 100%; 58 | // word-break: break-all; 59 | // white-space: normal; 60 | // } 61 | .ant-collapse>.ant-collapse-item>.ant-collapse-header { 62 | align-items: center; 63 | padding: 12px; 64 | padding-bottom: 3px; 65 | } 66 | 67 | .header { 68 | display: flex; 69 | flex-direction: row; 70 | flex-wrap: nowrap; 71 | justify-content: space-around; 72 | align-items: center; 73 | 74 | .url { 75 | width: 100%; 76 | display: inline-block; 77 | word-break: break-all; 78 | white-space: normal; 79 | } 80 | } 81 | 82 | .toolbar { 83 | display: flex; 84 | flex-direction: row; 85 | justify-content: space-between; 86 | align-items: center; 87 | margin-bottom: 30px; 88 | 89 | span { 90 | font-size: 18.333333px; 91 | color: #333; 92 | cursor: pointer; 93 | } 94 | } 95 | 96 | .ruleBox { 97 | display: flex; 98 | flex-direction: column; 99 | flex-wrap: wrap; 100 | align-items: center; 101 | border: 1px outset @border; 102 | margin-top: 20px; 103 | padding: 5px; 104 | 105 | .react-json-view { 106 | width: 100%; 107 | background-color: #fff !important; 108 | } 109 | 110 | .ruleItemTools { 111 | align-self: flex-end; 112 | margin-right: -12px; 113 | margin-top: -20px; 114 | background-color: @bg; 115 | padding: 5px; 116 | span { 117 | margin-right: 10px; 118 | font-size: 14px; 119 | } 120 | button { 121 | height: 20px; 122 | width: 40px; 123 | } 124 | .ant-switch-small.ant-switch-checked .ant-switch-handle { 125 | top: calc(100% - 12px - 2px); 126 | } 127 | } 128 | 129 | } 130 | 131 | .footer { 132 | text-align: center; 133 | padding-top: 100vh; 134 | padding-bottom: 10px; 135 | background-color: @bg; 136 | } -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState, memo, useCallback, useRef, useEffect } from 'react' 2 | import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; 3 | const crypto = require('crypto-js'); 4 | 5 | import { 6 | Input, 7 | Collapse, 8 | Button, 9 | Switch, 10 | Select, 11 | Popover, 12 | message, 13 | } from 'antd'; 14 | import ReactJson from 'react-json-view'; 15 | import { 16 | SaveOutlined, 17 | ChromeOutlined, 18 | CompassOutlined, 19 | PlusOutlined, 20 | PlusCircleOutlined, 21 | MinusCircleOutlined, 22 | DeleteOutlined, 23 | } from '@ant-design/icons'; 24 | 25 | import './App.less'; 26 | 27 | const { Panel } = Collapse; 28 | const { Option } = Select; 29 | 30 | const Main = memo(() => { 31 | const [url, setUrl] = useState('') 32 | const [activeKey, setActiveKey]: [string[], Function] = useState([]) 33 | const [rule, setRule] = useState([]) 34 | const [status, setStatus] = useState(true) 35 | const [easterEgg, setEasterEgg] = useState(0) 36 | 37 | const inputEl: any = useRef(null); 38 | useEffect(() => { 39 | inputEl.current.focus() 40 | // 获取规则缓存文件信息 41 | window.electron.ipcRenderer.sendMessage("fs", ['getCache']) 42 | window.electron.ipcRenderer.once('fs', (arg) => { 43 | let cacheRule = formatData(arg) 44 | // eslint-disable-next-line no-console 45 | console.log(cacheRule, '<<< 54 | { 64 | handleAddRule(url, setActiveKey, rule, setRule) 65 | handleIntermediator(rule, url) 66 | }, [url]) 67 | } 68 | rotate={33} 69 | /> 70 | 71 | ); 72 | 73 | return ( 74 |
75 | setUrl(e.target.value)} 81 | onPressEnter={useCallback(() => handleAddRule(url, setActiveKey, rule, setRule), [url])} 82 | placeholder="URL must contain 'http://' or 'https://' " 83 | /> 84 |
85 |
86 | 95 |
104 | setStatus(e)} 109 | /> 110 |
111 | 112 | {status && ( 113 | } 117 | className="site-collapse-custom-collapse list-group" 118 | onChange={(key: string | string[]) => setActiveKey(key)} 119 | > 120 | { 121 | rule.map((ruleBox: ruleBox) => ( 122 | 125 |
126 |
setUrl(ruleBox.url)}> 127 | {ruleBox.url} 128 |
129 | { 134 | e.stopPropagation() 135 | handleRuleOnoff(ruleBox.indexes, setRule, rule, checked) 136 | }} 137 | /> 138 |
139 | 140 | } 141 | key={ruleBox.indexes} 142 | className="site-collapse-custom-panel" 143 | > 144 | {/* toolbar */} 145 |
148 | 153 |

delete the all rule under this URL ?

154 | 162 | 163 | } 164 | trigger="hover" 165 | > 166 | 167 |
168 | handleAddRuleItem(ruleBox.indexes, setRule, rule)} /> 169 |
170 | {/* ruleItem */} 171 | {ruleBox.rule.map((item, index) => ( 172 |
173 |
174 | handleDeleteRuleItem(ruleBox.indexes, setRule, rule, index)} /> 175 | { 182 | e.stopPropagation() 183 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'onoff', checked) 184 | }} 185 | /> 186 |
187 | 188 | { 193 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'remarks', e.target.value) 194 | }} 195 | /> 196 | 207 | { 220 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'change', e.target.value) 221 | }} 222 | /> 223 | 224 | 225 | 226 | {item.type === 'api' && ( 227 | 242 | )} 243 | { 248 | handleRuleItemChange(ruleBox.indexes, setRule, rule, index, 'value', e.target.value) 249 | }} 250 | /> 251 | 252 | 253 | {!!ruleToJson(item.value) && ( 254 | { 262 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e) 263 | }} 264 | onAdd={e => { 265 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e) 266 | }} 267 | onDelete={e => { 268 | handleJSONEditorChange(ruleBox.indexes, setRule, rule, index, e) 269 | }} 270 | displayDataTypes={false} 271 | /> 272 | )} 273 | 274 |
275 | ))} 276 |
277 | )) 278 | } 279 |
280 | )} 281 | 282 |
setEasterEgg(easterEgg + 1)} 285 | > 286 | {easterEgg ? ': >' : ':)'} 287 |
288 | {easterEgg >= 3 && ( 289 |