├── images └── grey_128.png ├── assets └── xswitch.sketch ├── src ├── window.d.ts ├── bootstrap.ts ├── pages │ ├── options │ │ ├── options.less │ │ ├── options.vx │ │ └── options.ts │ └── xswitch │ │ ├── xswitch.vx │ │ ├── xswitch.less │ │ └── xswitch.ts ├── router.ts ├── enums.ts ├── utils.ts ├── editor-config.ts ├── strip-json-comments.ts ├── constants.ts ├── background.ts ├── forward.ts └── chrome-storage.ts ├── recore.config.js ├── pub.sh ├── .npmignore ├── .vscode ├── settings.json └── tasks.json ├── .editorconfig ├── .travis.yml ├── options.html ├── .gitignore ├── XSwitch.html ├── index.html ├── tsconfig.json ├── manifest.json ├── tslint.json ├── LICENCE.md ├── lib └── monaco-editor │ └── min │ └── vs │ ├── language │ └── json │ │ ├── monaco.contribution.js │ │ └── jsonMode.js │ ├── loader.js │ └── editor │ └── editor.main.nls.js ├── package.json ├── __tests__ ├── parse.spec.ts └── index.spec.ts ├── readme.en_US.md └── readme.md /images/grey_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tankpt/xswitch/master/images/grey_128.png -------------------------------------------------------------------------------- /assets/xswitch.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tankpt/xswitch/master/assets/xswitch.sketch -------------------------------------------------------------------------------- /src/window.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | require: any; 3 | editor: any; 4 | monaco: any; 5 | _forward: any; 6 | } 7 | -------------------------------------------------------------------------------- /recore.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extraEntry: { 3 | 'background': './src/background.ts' 4 | }, 5 | vendors: false, 6 | } 7 | -------------------------------------------------------------------------------- /src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { runApp } from '@ali/recore'; 2 | import { PREFIX } from './constants'; 3 | 4 | runApp({ 5 | globalHelpers: { prefix: PREFIX }, 6 | }); 7 | -------------------------------------------------------------------------------- /src/pages/options/options.less: -------------------------------------------------------------------------------- 1 | .options-container { 2 | width: 500px; 3 | margin: 0 auto; 4 | padding: 50px; 5 | } 6 | 7 | ul, 8 | li { 9 | list-style: none; 10 | } 11 | -------------------------------------------------------------------------------- /pub.sh: -------------------------------------------------------------------------------- 1 | 2 | rm build/xswitch.css build/xswitch.js build/xswitch.js.map build/background.js.map build/forward.js.map 3 | rm xswitch.zip 4 | zip -r xswitch.zip build 5 | open https://chrome.google.com/webstore/developer/dashboard 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.iml 3 | .idea/ 4 | .vscode/ 5 | .xdev/ 6 | package-lock.json 7 | *~ 8 | ~* 9 | *.diff 10 | *.log 11 | *.patch 12 | *.bak 13 | .DS_Store 14 | Thumbs.db 15 | .project 16 | .*proj 17 | .svn/ 18 | *.swp 19 | -------------------------------------------------------------------------------- /src/pages/options/options.vx: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "./node_modules/typescript/lib", 3 | "json.schemas": [ 4 | { 5 | "fileMatch": [ 6 | "/manifest.json" 7 | ], 8 | "url": "http://json.schemastore.org/chrome-manifest" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | baseDir: './pages', 3 | exact: true, 4 | routes: [ 5 | { 6 | path: '/options.html', 7 | main: './options', 8 | }, 9 | { 10 | path: '/XSwitch.html', 11 | main: './xswitch', 12 | }, 13 | { 14 | path: '/', 15 | main: './xswitch', 16 | }, 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Tab indentation 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_size = 2 11 | indent_style = space 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | install: 5 | - npm i jest 6 | - npm i ts-jest 7 | - npm i typescript 8 | - npm i @types/jest 9 | - npm i @types/chrome 10 | - npm i @types/node 11 | - npm i @types/react 12 | - npm i coveralls 13 | script: 14 | - NODE_ENV=production npm test 15 | after_script: 16 | - NODE_ENV=production npm run ci 17 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | XSwitch 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum UrlType { 2 | REG = 'reg', 3 | STRING = 'string', 4 | } 5 | 6 | export enum Enabled { 7 | YES = 'enabled', 8 | NO = 'disabled', 9 | } 10 | 11 | export enum IconBackgroundColor { 12 | ON = '#1890ff', 13 | OFF = '#bfbfbf', 14 | ERROR = '#f5222d', 15 | } 16 | 17 | export enum BadgeText { 18 | ERROR = 'Error', 19 | OFF = 'OFF', 20 | ON = 'ON', 21 | } 22 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { stripJsonComments } from './strip-json-comments'; 2 | import { REG, EMPTY_STRING } from './constants'; 3 | 4 | export function JSONC2JSON(jsonc: string): string { 5 | return stripJsonComments(jsonc) 6 | .replace(REG.WHITESPACE, EMPTY_STRING) 7 | .replace(REG.TRIM_JSON, ($0, $1, $2) => $2); 8 | } 9 | 10 | export function JSON_Parse(json: string, cb: (error: object | boolean, json?: object) => void): void { 11 | try { 12 | cb(false, JSON.parse(json)); 13 | } catch (e) { 14 | cb(e); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | .idea/ 4 | .vscode/ 5 | .xdev/ 6 | ~* 7 | package-lock.json 8 | 9 | # Packages # 10 | ############ 11 | # it's better to unpack these files and commit the raw source 12 | # git has its own built in compression methods 13 | *.7z 14 | *.dmg 15 | *.gz 16 | *.iso 17 | *.jar 18 | *.rar 19 | *.tar 20 | *.zip 21 | 22 | # Logs and databases # 23 | ###################### 24 | *.log 25 | *.sql 26 | *.sqlite 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | *.swp 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | -------------------------------------------------------------------------------- /XSwitch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | xswitch 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | xswitch 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/editor-config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DEFAULT_DATA, 3 | LANGUAGE_JSON, 4 | DEFAULT_FONT_FAMILY, 5 | SHOW_FOLDING_CONTROLS, 6 | } from './constants'; 7 | 8 | export function getEditorConfig(value: string): object { 9 | return { 10 | value: value || DEFAULT_DATA, 11 | language: LANGUAGE_JSON, 12 | 13 | minimap: { 14 | enabled: false, 15 | }, 16 | fontFamily: DEFAULT_FONT_FAMILY, 17 | fontSize: 13, 18 | 19 | contextmenu: false, 20 | scrollBeyondLastLine: false, 21 | folding: true, 22 | showFoldingControls: SHOW_FOLDING_CONTROLS, 23 | 24 | useTabStops: true, 25 | wordBasedSuggestions: true, 26 | quickSuggestions: true, 27 | suggestOnTriggerCharacters: true, 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "npm", 6 | "isShellCommand": true, 7 | "showOutput": "always", 8 | "suppressTaskName": true, 9 | "tasks": [ 10 | { 11 | "taskName": "install", 12 | "args": ["install"] 13 | }, 14 | { 15 | "taskName": "update", 16 | "args": ["update"] 17 | }, 18 | { 19 | "taskName": "test", 20 | "args": ["run", "test"] 21 | }, 22 | { 23 | "taskName": "build", 24 | "args": ["run", "watch"] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": [ 6 | "dom", 7 | "es5", 8 | "es2015", 9 | "es2016", 10 | "es2017" 11 | ], 12 | "sourceMap": true, 13 | "jsx": "react", 14 | "moduleResolution": "node", 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noImplicitAny": true, 18 | "strictNullChecks": true, 19 | "experimentalDecorators": true, 20 | "forceConsistentCasingInFileNames": true, 21 | "esModuleInterop": true 22 | }, 23 | "include": [ 24 | "./src/", 25 | "./typings/", 26 | "./test" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "build", 31 | "dist" 32 | ], 33 | "types": [ 34 | "jest", 35 | "node", 36 | "chrome", 37 | "react" 38 | ], 39 | "awesomeTypescriptLoaderOptions": { 40 | "transpileOnly" : true 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "XSwitch", 3 | "description": "XSwitch tools for proxy web request url, support reg", 4 | "short_name": "xs", 5 | "version": "1.15.9", 6 | "manifest_version": 2, 7 | "browser_action": { 8 | "default_icon": "images/grey_128.png", 9 | "default_title": "XSwitch", 10 | "default_popup": "XSwitch.html" 11 | }, 12 | "permissions": [ 13 | "webRequest", 14 | "storage", 15 | "webRequestBlocking", 16 | "browsingData", 17 | "" 18 | ], 19 | "icons": { 20 | "48": "images/grey_128.png", 21 | "128": "images/grey_128.png" 22 | }, 23 | "commands": { 24 | "_execute_browser_action": { 25 | "suggested_key": { 26 | "windows": "Ctrl+Shift+X", 27 | "mac": "Command+Shift+X", 28 | "default": "Ctrl+Shift+X" 29 | } 30 | } 31 | }, 32 | "options_page": "options.html", 33 | "background": { 34 | "scripts": ["background.min.js"] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:latest"], 4 | "jsRules": {}, 5 | "rules": { 6 | "quotemark": [true, "single"], 7 | "interface-name": false, 8 | "variable-name": false, 9 | "object-literal-sort-keys": false, 10 | "member-access": [true, "no-public"], 11 | "ordered-imports": [true, { 12 | "grouped-imports": true, 13 | "import-sources-order": "any", 14 | "named-imports-order": "any" 15 | }], 16 | "trailing-comma": [ 17 | true, 18 | { 19 | "multiline": { 20 | "objects": "always", 21 | "arrays": "always", 22 | "imports": "always", 23 | "exports": "always", 24 | "functions": "never", 25 | "typeLiterals": "ignore" 26 | }, 27 | "esSpecCompliant": true 28 | } 29 | ], 30 | "prefer-for-of": false, 31 | "no-namespace": false 32 | }, 33 | "rulesDirectory": [] 34 | } 35 | -------------------------------------------------------------------------------- /src/pages/options/options.ts: -------------------------------------------------------------------------------- 1 | import { ViewController, observable, inject } from '@ali/recore'; 2 | import { Checkbox } from 'antd'; 3 | import { getOptions, setOptions } from '../../chrome-storage'; 4 | import { Enabled } from '../../enums'; 5 | import './options.less'; 6 | @inject({ 7 | components: { Checkbox }, 8 | }) 9 | export default class Options extends ViewController { 10 | @observable 11 | clearCacheEnabled = false; 12 | 13 | @observable 14 | corsEnabled = false; 15 | 16 | setOptionStorage() { 17 | setOptions({ 18 | clearCacheEnabled: this.clearCacheEnabled, 19 | corsEnabled: this.corsEnabled, 20 | }); 21 | } 22 | 23 | async $init() { 24 | this.clearCacheEnabled = (await getOptions()).clearCacheEnabled !== Enabled.NO; 25 | this.corsEnabled = (await getOptions()).corsEnabled !== Enabled.NO; 26 | } 27 | 28 | setClearCacheEnabled() { 29 | this.clearCacheEnabled = !this.clearCacheEnabled; 30 | this.setOptionStorage(); 31 | } 32 | 33 | setCorsEnabled() { 34 | this.corsEnabled = !this.corsEnabled; 35 | this.setOptionStorage(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENCE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 yize 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 | -------------------------------------------------------------------------------- /src/pages/xswitch/xswitch.vx: -------------------------------------------------------------------------------- 1 |
2 |
3 |
    4 |
  • 5 |  {item.name} 6 | 7 |
  • 8 |
9 |
10 | 18 | 19 |
20 |
21 |
22 |
23 |
24 | } unCheckedChildren={} checked={checked} @change="toggleButton"/> 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /lib/monaco-editor/min/vs/language/json/monaco.contribution.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * monaco-json version: 2.2.0(370169f666a52e1b91623841799be4eab9204094) 4 | * Released under the MIT license 5 | * https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md 6 | *-----------------------------------------------------------------------------*/ 7 | define("vs/language/json/monaco.contribution",["require","exports"],function(t,e){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var o=monaco.Emitter,n=function(){function e(e,n){this._onDidChange=new o,this._languageId=e,this.setDiagnosticsOptions(n)}return Object.defineProperty(e.prototype,"onDidChange",{get:function(){return this._onDidChange.event},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"languageId",{get:function(){return this._languageId},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"diagnosticsOptions",{get:function(){return this._diagnosticsOptions},enumerable:!0,configurable:!0}),e.prototype.setDiagnosticsOptions=function(e){this._diagnosticsOptions=e||Object.create(null),this._onDidChange.fire(this)},e}(),i=new(e.LanguageServiceDefaultsImpl=n)("json",{validate:!0,allowComments:!0,schemas:[]});monaco.languages.json={jsonDefaults:i},monaco.languages.register({id:"json",extensions:[".json",".bowerrc",".jshintrc",".jscsrc",".eslintrc",".babelrc"],aliases:["JSON","json"],mimetypes:["application/json"]}),monaco.languages.onLanguage("json",function(){monaco.Promise.wrap(new Promise(function(e,n){t(["./jsonMode"],e,n)})).then(function(e){return e.setupMode(i)})})}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xswitch", 3 | "description": "A proxy tool based on Chrome.extensions", 4 | "author": "yize", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:yize/xswitch.git" 8 | }, 9 | "scripts": { 10 | "start": "nowa2 start", 11 | "build": "nowa2 build && cp -rf images lib XSwitch.html options.html manifest.json build", 12 | "pub": "npm run build && npm t && sh pub.sh", 13 | "test": "jest", 14 | "ci": "jest --coverage && cat ./coverage/lcov.info | coveralls" 15 | }, 16 | "keywords": [ 17 | "xswitch", 18 | "chrome", 19 | "urlProxy", 20 | "proxy", 21 | "xproxy" 22 | ], 23 | "license": "MIT", 24 | "dependencies": { 25 | "@ali/recore": "^1.0.10", 26 | "antd": "^3.9.2" 27 | }, 28 | "devDependencies": { 29 | "@ali/nowa-recore-solution": "^1.0.0", 30 | "@ali/recore-loader": "^1.0.0", 31 | "@nowa/cli": "^0.6.0", 32 | "@types/chrome": "^0.0.73", 33 | "@types/jest": "^23.3.2", 34 | "@types/node": "^10.9.4", 35 | "@types/react": "^16", 36 | "coveralls": "^3.0.2", 37 | "css-loader": "^1.0.0", 38 | "html-webpack-plugin": "^3.2.0", 39 | "jest": "^23.6.0", 40 | "mini-css-extract-plugin": "^0.4.2", 41 | "monaco": "^1.201704190613.0+9ac64297a3b2ace5240299ba54b03f5029378397", 42 | "ts-jest": "^23.1.4", 43 | "ts-loader": "~5.0.0", 44 | "typescript": "~3.0.3", 45 | "webpack": "~4.17.2", 46 | "webpack-cli": "~3.1.0", 47 | "webpack-merge": "~4.1.4" 48 | }, 49 | "jest": { 50 | "moduleFileExtensions": [ 51 | "ts", 52 | "tsx", 53 | "js" 54 | ], 55 | "transform": { 56 | "^.+\\.(ts|tsx)$": "ts-jest" 57 | }, 58 | "globals": { 59 | "ts-jest": { 60 | "tsConfig": "tsconfig.json" 61 | } 62 | }, 63 | "testMatch": [ 64 | "**/__tests__/*.+(ts|tsx|js)" 65 | ] 66 | }, 67 | "nowa": { 68 | "solution": "@ali/nowa-recore-solution" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/pages/xswitch/xswitch.less: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | display: none; 3 | } 4 | 5 | ::-webkit-scrollbar-thumb { 6 | display: none; 7 | } 8 | 9 | .xswitch-wrapper { 10 | display: flex; 11 | overflow: hidden; 12 | .xswitch-tabs { 13 | padding: 0; 14 | width: 200px; 15 | white-space: nowrap; 16 | overflow: auto; 17 | margin-right: -1px; 18 | z-index: 2; 19 | margin-bottom: 0; 20 | padding-bottom: 44px; 21 | flex: 1; 22 | li { 23 | padding: 5px 10px; 24 | line-height: 40px; 25 | border: 1px solid transparent; 26 | cursor: pointer; 27 | display: flex; 28 | align-items: center; 29 | .delete-icon { 30 | display: none; 31 | opacity: 0; 32 | } 33 | .label { 34 | flex: 1; 35 | overflow: hidden; 36 | } 37 | &:hover { 38 | background: #efefef; 39 | .delete-icon { 40 | display: inline-block; 41 | opacity: 0.5; 42 | &:hover { 43 | opacity: 1; 44 | } 45 | } 46 | } 47 | &:first-of-type { 48 | border-top: 0; 49 | .delete-icon { 50 | display: none !important; 51 | } 52 | } 53 | &.editing { 54 | border-color: #bfbfbf #fff #bfbfbf transparent; 55 | } 56 | } 57 | } 58 | .xswitch-new-item-container { 59 | padding: 10px; 60 | z-index: 3; 61 | background: #fff; 62 | width: 100%; 63 | position: relative; 64 | .confirm-button{ 65 | position: absolute; 66 | right: 15px; 67 | top: 15px; 68 | cursor: pointer; 69 | } 70 | } 71 | } 72 | 73 | .xswitch-container { 74 | width: 100%; 75 | min-width: 600px; 76 | min-height: 600px; 77 | height: 100vh; 78 | } 79 | 80 | .xswitch-left-area{ 81 | display: flex; 82 | align-items: baseline; 83 | flex-direction: column; 84 | height: 100vh; 85 | border-right: 1px solid #bfbfbf; 86 | } 87 | 88 | .toolbar-area { 89 | width: 120px; 90 | position: fixed; 91 | right: 20px; 92 | top: 13px; 93 | z-index: 1000; 94 | cursor: pointer; 95 | display: flex; 96 | flex-direction: row; 97 | justify-content: space-around; 98 | } 99 | 100 | .switch-control { 101 | width: 50px; 102 | } 103 | 104 | .xswitch-icon { 105 | width: 22px; 106 | opacity: 0.5; 107 | &:hover { 108 | opacity: 1; 109 | } 110 | } -------------------------------------------------------------------------------- /src/strip-json-comments.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY_STRING } from './constants'; 2 | 3 | // https://github.com/sindresorhus/strip-json-comments 4 | const singleComment = 1; 5 | const multiComment = 2; 6 | const stripWithoutWhitespace = (): string => EMPTY_STRING; 7 | const stripWithWhitespace = (str: string, start: number, end: number): string => 8 | str.slice(start, end).replace(/\S/g, ' '); 9 | 10 | interface IStripOptions { 11 | whitespace?: boolean; 12 | } 13 | 14 | export function stripJsonComments(str: string, opts?: IStripOptions): string { 15 | opts = opts || {}; 16 | 17 | const strip = 18 | opts.whitespace === false ? stripWithoutWhitespace : stripWithWhitespace; 19 | 20 | let insideString: boolean = false; 21 | let insideComment: number | boolean = false; 22 | let offset: number = 0; 23 | let ret: string = EMPTY_STRING; 24 | 25 | for (let i: number = 0; i < str.length; i++) { 26 | const currentChar = str[i]; 27 | const nextChar = str[i + 1]; 28 | 29 | if (!insideComment && currentChar === '"') { 30 | const escaped = str[i - 1] === '\\' && str[i - 2] !== '\\'; 31 | if (!escaped) { 32 | insideString = !insideString; 33 | } 34 | } 35 | 36 | if (insideString) { 37 | continue; 38 | } 39 | 40 | if (!insideComment && currentChar + nextChar === '//') { 41 | ret += str.slice(offset, i); 42 | offset = i; 43 | insideComment = singleComment; 44 | i++; 45 | } else if ( 46 | insideComment === singleComment && 47 | currentChar + nextChar === '\r\n' 48 | ) { 49 | i++; 50 | insideComment = false; 51 | ret += strip(str, offset, i); 52 | offset = i; 53 | continue; 54 | } else if (insideComment === singleComment && currentChar === '\n') { 55 | insideComment = false; 56 | ret += strip(str, offset, i); 57 | offset = i; 58 | } else if (!insideComment && currentChar + nextChar === '/*') { 59 | ret += str.slice(offset, i); 60 | offset = i; 61 | insideComment = multiComment; 62 | i++; 63 | continue; 64 | } else if ( 65 | insideComment === multiComment && 66 | currentChar + nextChar === '*/' 67 | ) { 68 | i++; 69 | insideComment = false; 70 | ret += strip(str, offset, i + 1); 71 | offset = i + 1; 72 | continue; 73 | } 74 | } 75 | 76 | return ( 77 | ret + (insideComment ? strip(str.substr(offset), 0, 0) : str.substr(offset)) 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /__tests__/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { REG, EMPTY_STRING } from '../src/constants'; 2 | import { stripJsonComments } from '../src/strip-json-comments'; 3 | 4 | const replace = (jsonc: string): string => { 5 | try { 6 | return JSON.parse( 7 | stripJsonComments(jsonc) 8 | .replace(REG.WHITESPACE, EMPTY_STRING) 9 | .replace(REG.TRIM_JSON, ($0, $1, $2) => $2) 10 | ); 11 | } catch (e) { 12 | console.log( 13 | stripJsonComments(jsonc) 14 | .replace(REG.WHITESPACE, EMPTY_STRING) 15 | .replace(REG.TRIM_JSON, ($0, $1, $2) => $2) 16 | ); 17 | return 'parsed error'; 18 | } 19 | }; 20 | 21 | describe('parse', () => { 22 | test('parse pure JSON', () => { 23 | const jsonString = `{ 24 | "proxy": [ 25 | [ 26 | "a.com", 27 | "b.com" 28 | ] 29 | ] 30 | }`; 31 | 32 | expect(replace(jsonString)).not.toEqual('parsed error'); 33 | }); 34 | 35 | test('parse pure JSON with comma', () => { 36 | const jsonString = `{ 37 | "proxy": [ 38 | [ 39 | "a.com", 40 | "b.com", 41 | ],,,,, 42 | ], 43 | }`; 44 | 45 | expect(replace(jsonString)).toEqual({ proxy: [['a.com', 'b.com']] }); 46 | }); 47 | test('parse urls with ?? ,', () => { 48 | const jsonString = `{ 49 | "proxy": [ 50 | [ 51 | "a.com??a.js,b.js", 52 | "b.com??a.js,b.js", 53 | ], 54 | ] 55 | }`; 56 | 57 | expect(replace(jsonString)).toEqual({ 58 | proxy: [['a.com??a.js,b.js', 'b.com??a.js,b.js']] 59 | }); 60 | }); 61 | 62 | test('parse urls with ?? with comments,', () => { 63 | const jsonString = `{ 64 | "proxy": [ 65 | [ 66 | "a.com??a.js,b.js", 67 | // 68 | "b.com??a.js,b.js", 69 | ], 70 | ] 71 | }`; 72 | 73 | expect(replace(jsonString)).toEqual({ 74 | proxy: [['a.com??a.js,b.js', 'b.com??a.js,b.js']] 75 | }); 76 | }); 77 | 78 | test('parse urls with ?? with comments,', () => { 79 | const jsonString = `{ 80 | "proxy": [ 81 | // jQuery 82 | [ 83 | // jQuery 84 | "a.com??a.js,b.js", 85 | // 86 | "b.com??a.js,b.js", 87 | // jQuery 88 | ], 89 | // jQuery 90 | [ 91 | "jQuery", 92 | // jQuery 93 | ,"jQuery.min.js" 94 | // jQuery 95 | ] 96 | ] 97 | }`; 98 | 99 | expect(replace(jsonString)).toEqual({ 100 | proxy: [ 101 | ['a.com??a.js,b.js', 'b.com??a.js,b.js'], 102 | ['jQuery', 'jQuery.min.js'] 103 | ] 104 | }); 105 | }); 106 | 107 | test('parse reg rules', () => { 108 | const jsonString = `{ 109 | "proxy": [ 110 | [ 111 | "(.*)a.com??a.js,b.js", 112 | "$1b.com??a.js,b.js", 113 | ], 114 | ] 115 | }`; 116 | 117 | expect(replace(jsonString)).toEqual({ 118 | proxy: [['(.*)a.com??a.js,b.js', '$1b.com??a.js,b.js']] 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /readme.en_US.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | [中文版](./readme.md) 8 | 9 | ## XSwitch 10 | 11 | [![Chrome version][badge-cws]][link-cws] [![Chrome version][badge-cws-count]][link-cws] [![Build Status][badge-travis]][link-travis] [![Coverage Status][badge-coverage]][link-coverage] [![license][badge-license]][link-xswitch] 12 | 13 | A [Chrome Extension][link-cws] for redirecting/forwarding request urls. 14 | 15 | 16 | 17 | 18 | 19 | ## Features 20 | 21 | - [x] Redirect `request.url` 22 | - [x] Global switch control 23 | - [x] Disable browser cache 24 | - [x] JSON comments 25 | - [x] Rule suggestions 26 | - [x] CORS 27 | - [x] CORS & browser cache control 28 | - [x] Rules Grouping 29 | 30 | ## Usage 31 | 32 | 更多说明:[https://yuque.com/jiushen/blog/xswitch-readme](https://yuque.com/jiushen/blog/xswitch-readme) 33 | 34 | Rules will be executed in order before all requests are initiated. 35 | 36 | ```js 37 | { 38 | // proxyRules 39 | "proxy": [ 40 | [ 41 | "//alinw.alicdn.com/platform/daily-test/isDaily.js", 42 | "//alinw.alicdn.com/platform/daily-test/isDaily.json" 43 | ], 44 | // string replace, global mode 45 | [ 46 | "alinw", 47 | "g" 48 | ] 49 | // replace all x.min to x 50 | [ 51 | ".min", 52 | "" 53 | ], 54 | // use reg 55 | [ 56 | "(.*)/platform/daily-test/(.*).js$", 57 | "http://127.0.0.1:3000/daily-test/$1.js" 58 | ], 59 | // replace to inline JavaScript 60 | [ 61 | "https://alinw.alicdn.com/platform/daily-test/isDaily.js", 62 | "data:text/javascript,window.__isDaily = true;" 63 | ] 64 | ], 65 | // urls that want CORS 66 | "cors": [ 67 | "cors.a.com", 68 | "(.*).b.com" 69 | ] 70 | } 71 | ``` 72 | 73 | ## License 74 | 75 | [MIT](https://opensource.org/licenses/MIT) © [yize.shc](https://nsole.co) 76 | 77 | [link-xswitch]: https://github.com/yize/xswitch 78 | [link-cws]: https://chrome.google.com/webstore/detail/xswitch/idkjhjggpffolpidfkikidcokdkdaogg 79 | [link-me]: https://github.com/Microsoft/monaco-editor 80 | [link-travis]: https://travis-ci.org/yize/xswitch 81 | [link-coverage]: https://coveralls.io/github/yize/xswitch?branch=master 82 | [badge-travis]: https://travis-ci.org/yize/xswitch.svg?branch=master 83 | [badge-coverage]: https://coveralls.io/repos/github/yize/xswitch/badge.svg?branch=master 84 | [badge-license]: https://img.shields.io/github/license/yize/xswitch.svg 85 | [badge-cws]: https://img.shields.io/chrome-web-store/v/idkjhjggpffolpidfkikidcokdkdaogg.svg?label=chrome 86 | [badge-cws-count]: https://img.shields.io/chrome-web-store/users/idkjhjggpffolpidfkikidcokdkdaogg.svg 87 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | [English](./readme.en_US.md) 8 | 9 | ## XSwitch 10 | 11 | [![Chrome version][badge-cws]][link-cws] [![Chrome version][badge-cws-count]][link-cws] [![Build Status][badge-travis]][link-travis] [![Coverage Status][badge-coverage]][link-coverage] [![license][badge-license]][link-xswitch] 12 | 13 | 一个用来做请求链接转发的 [Chrome 浏览器插件][link-cws],因为采用的是浏览器原生 `API`,安全性和性能能得到保障。 14 | 15 | [![XSwitch-intro](https://cdn.nlark.com/yuque/0/2018/png/137701/1536999137086-9377abf2-ac97-4ccf-ae71-de178bf7238a.png)](https://www.youtube.com/watch?v=--gQM3ysCzc) 16 | 17 | [优酷视频介绍](https://v.youku.com/v_show/id_XMzgyNDgwODAwNA==.html) 18 | 19 | ## 功能 20 | 21 | - [x] 请求地址转发 22 | - [x] 全局插件启用开关 23 | - [x] 可禁用浏览器缓存 24 | - [x] 可在 JSON 中写注释 25 | - [x] 自动补全 26 | - [x] 支持 CORS,支持 withCredentials 27 | - [x] 跨域和缓存禁用键 28 | - [x] 分组规则 29 | 30 | ## 用法 31 | 32 | 所有的规则,会按照定义的顺序从前往后执行,即使匹配到了规则,也会继续往下匹配。 33 | 34 | 小提示:把 `HTTPS` 的链接转发到 `http://127.0.0.1` 下,浏览器不会出安全提示。如果之前习惯用 `localhost` 的同学,可以尝试下。 35 | 36 | ```js 37 | { 38 | // 转发规则 39 | "proxy": [ 40 | [ 41 | "//alinw.alicdn.com/platform/daily-test/isDaily.js", // 匹配 URL 42 | "//alinw.alicdn.com/platform/daily-test/isDaily.json" // 替换成这个 URL 43 | ], 44 | // 字符串替换,会全局匹配 45 | [ 46 | "alinw", 47 | "g" 48 | ] 49 | // 把链接里所有的 .min 替换掉 50 | // [ 51 | // ".min", 52 | // "" 53 | // ], 54 | // 正则 55 | // [ 56 | // "(.*)/platform/daily-test/(.*).js$", 57 | // "http://127.0.0.1:3000/daily-test/$1.js" 58 | // ], 59 | // 直接转换成 inline 模式的 JavaScript 60 | // [ 61 | // "https://alinw.alicdn.com/platform/daily-test/isDaily.js", 62 | // "data:text/javascript,window.__isDaily = true;" 63 | // ] 64 | ], 65 | // 希望开启 CORS 跨域的链接 66 | "cors": [ 67 | "cors.a.com", 68 | "(.*).b.com" 69 | ] 70 | } 71 | ``` 72 | 73 | 更多说明:[https://yuque.com/jiushen/blog/xswitch-readme](https://yuque.com/jiushen/blog/xswitch-readme) 74 | 75 | - 访问 [https://alinw.alicdn.com/platform/daily-test/isDaily.js](https://alinw.alicdn.com/platform/daily-test/isDaily.js) 76 | - 最终, 你的 URL 会被改写成 [https://g.alicdn.com/platform/daily-test/isDaily.json](https://g.alicdn.com/platform/daily-test/isDaily.json) 77 | 78 | ## License 79 | 80 | [MIT](https://opensource.org/licenses/MIT) © [yize.shc](https://nsole.co) 81 | 82 | [link-xswitch]: https://github.com/yize/xswitch 83 | [link-cws]: https://chrome.google.com/webstore/detail/xswitch/idkjhjggpffolpidfkikidcokdkdaogg 84 | [link-me]: https://github.com/Microsoft/monaco-editor 85 | [link-travis]: https://travis-ci.org/yize/xswitch 86 | [link-coverage]: https://coveralls.io/github/yize/xswitch?branch=master 87 | [badge-travis]: https://travis-ci.org/yize/xswitch.svg?branch=master 88 | [badge-coverage]: https://coveralls.io/repos/github/yize/xswitch/badge.svg?branch=master 89 | [badge-license]: https://img.shields.io/github/license/yize/xswitch.svg 90 | [badge-cws]: https://img.shields.io/chrome-web-store/v/idkjhjggpffolpidfkikidcokdkdaogg.svg?label=chrome 91 | [badge-cws-count]: https://img.shields.io/chrome-web-store/users/idkjhjggpffolpidfkikidcokdkdaogg.svg 92 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const REG = { 2 | TRIM_JSON: /(,+)([^a-z0-9["])/gi, 3 | CHROME_EXTENSION: /^chrome-extension:\/\//i, 4 | // support [ ] ( ) \ * ^ $ 5 | FORWARD: /\\|\[|]|\(|\)|\*|\$|\^/i, 6 | WHITESPACE: /\s+/g, 7 | X_HEADER: /^x-/, 8 | }; 9 | 10 | export const ALL_URLS = ''; 11 | export const BLOCKING = 'blocking'; 12 | export const REQUEST_HEADERS = 'requestHeaders'; 13 | export const RESPONSE_HEADERS = 'responseHeaders'; 14 | export const DEFAULT_CREDENTIALS_RESPONSE_HEADERS = 15 | 'Content-Type, access-control-allow-headers, Authorization, X-Requested-With, X-Referer'; 16 | export const CORS = { 17 | METHODS: 'access-control-allow-methods', 18 | CREDENTIALS: 'access-control-allow-credentials', 19 | ORIGIN: 'access-control-allow-origin', 20 | HEADERS: 'access-control-allow-headers', 21 | }; 22 | export const ACCESS_CONTROL_REQUEST_HEADERS = 'access-control-request-headers'; 23 | export const DEFAULT_CORS_ORIGIN = '*'; 24 | export const DEFAULT_CORS_METHODS = '*'; 25 | export const DEFAULT_CORS_CREDENTIALS = 'true'; 26 | export const ORIGIN = 'origin'; 27 | /** 28 | * Disabled storage key 29 | */ 30 | export const DISABLED = 'disabled'; 31 | /** 32 | * pure JSON storage key 33 | */ 34 | export const JSON_CONFIG = 'config'; 35 | /** 36 | * JSON with comments storage key 37 | */ 38 | export const JSONC_CONFIG = 'config_for_shown'; 39 | 40 | export const EDITING_CONFIG_KEY = 'config_editing_key'; 41 | export const TAB_LIST = 'tab_list'; 42 | export const ACTIVE_KEYS = 'active_keys'; 43 | export const CLEAR_CACHE_ENABLED = 'clearCacheEnabled'; 44 | export const CORS_STORAGE = 'cors'; 45 | export const CORS_ENABLED_STORAGE_KEY = 'corsEnabled'; 46 | export const PROXY_STORAGE_KEY = 'proxy'; 47 | export const MILLISECONDS_PER_WEEK = 1000 * 60 * 60 * 24 * 7; 48 | export const RULE = 'rule'; 49 | export const LANGUAGE_JSON = 'json'; 50 | export const CHANGE = 'change'; 51 | export const DOM_CONTENT_LOADED = 'DOMContentLoaded'; 52 | export const SWITCH_DOM_ID = 'J_Switch'; 53 | export const SWITCH_INNER_DOM_ID = 'J_SwitchInner'; 54 | export const SWITCH_AREA_DOM_ID = 'J_SwitchArea'; 55 | export const NEW_TAB_DOM_ID = 'J_OpenInNewTab'; 56 | export const OPEN_README_DOM_ID = 'J_OpenReadme'; 57 | export const CONTAINER_DOM_ID = 'J_Container'; 58 | export const STATUS_DOM_ID = 'J_Status'; 59 | export const CLEAR_CACHE_ENABLED_DOM_ID = 'J_ClearCacheEnabled'; 60 | export const CORS_ENABLED_DOM_ID = 'J_CorsEnabled'; 61 | export const SWITCH_CHECKED_CLASSNAME = 'ant-switch-checked'; 62 | export const POPUP_HTML_PATH = 'XSwitch.html'; 63 | export const PREFIX = process.env.NODE_ENV !== 'production' ? '/build/' : './'; 64 | export const MONACO_VS_PATH = process.env.NODE_ENV !== 'production' 65 | ? '/build/lib/monaco-editor/min/vs' 66 | : './lib/monaco-editor/min/vs'; 67 | export const MONACO_CONTRIBUTION_PATH = 'vs/language/json/monaco.contribution'; 68 | export const HELP_URL = 'https://yuque.com/jiushen/blog/xswitch-readme'; 69 | export const DEFAULT_FONT_FAMILY = 'Menlo, Monaco, "Courier New", monospace'; 70 | export const PLATFORM_MAC = 'Mac'; 71 | export const OPTIONS_SAVED = 'Options saved.'; 72 | export const EMPTY_STRING = ''; 73 | export const KEY_DOWN = 'keydown'; 74 | export const CLICK = 'click'; 75 | export const ANYTHING = 'anyString'; 76 | export const FORMAT_DOCUMENT_CMD = 'editor.action.formatDocument'; 77 | export const KEY_CODE_S = 83; 78 | export const SHOW_FOLDING_CONTROLS = 'always'; 79 | export const OPACITY_VISIBLE = '1'; 80 | export const NULL_STRING = 'null'; 81 | export const RULE_COMPLETION = `[ 82 | "\${1:from}", 83 | "\${1:to}", 84 | ],`; 85 | 86 | export const DEFAULT_DATA = `{ 87 | // Use IntelliSense to learn about possible links. 88 | // Type \`rule\` to quick insert rule. 89 | // 输入 rule 来快速插入规则 90 | // For more information, visit: https://github.com/yize/xswitch 91 | "proxy": [ 92 | [ 93 | "https://unpkg.com/react@16.4.1/umd/react.production.min.js", 94 | "https://unpkg.com/react@16.4.1/umd/react.development.js" 95 | ], 96 | // \`Command/Ctrl + click\` to visit: 97 | // https://unpkg.com/react@16.4.1/umd/react.production.min.js 98 | // [ 99 | // "(.*)/path1/path2/(.*)", // https://www.sample.com/path1/path2/index.js 100 | // "http://127.0.0.1:3000/$2", // http://127.0.0.1:3000/index.js 101 | // ], 102 | ], 103 | // urls that want CORS 104 | // "cors": [ 105 | // "mocks.a.com", 106 | // "mocks.b.com" 107 | // ] 108 | } 109 | `; 110 | 111 | export const DEFAULT_DUP_DATA = `{ 112 | "proxy": [ 113 | [ 114 | "(.*)/path1/path2/(.*)", // https://www.sample.com/path1/path2/index.js 115 | "http://127.0.0.1:3000/$2", // http://127.0.0.1:3000/index.js 116 | ], 117 | ], 118 | } 119 | `; -------------------------------------------------------------------------------- /src/pages/xswitch/xswitch.ts: -------------------------------------------------------------------------------- 1 | import { ViewController, observable, inject } from '@ali/recore'; 2 | import { Switch, Icon, Checkbox, Input, Popconfirm, Button } from 'antd'; 3 | 4 | import './xswitch.less'; 5 | 6 | import { 7 | ANYTHING, 8 | FORMAT_DOCUMENT_CMD, 9 | KEY_CODE_S, 10 | KEY_DOWN, 11 | LANGUAGE_JSON, 12 | MONACO_CONTRIBUTION_PATH, 13 | MONACO_VS_PATH, 14 | PLATFORM_MAC, 15 | RULE, 16 | RULE_COMPLETION, 17 | POPUP_HTML_PATH, 18 | HELP_URL, 19 | DEFAULT_DUP_DATA, 20 | } from '../../constants'; 21 | import { Enabled } from '../../enums'; 22 | import { 23 | getConfig, 24 | saveConfig, 25 | setChecked, 26 | getChecked, 27 | openLink, 28 | getEditingConfigKey, 29 | setEditingConfigKey, 30 | setConfigItems, 31 | getConfigItems, 32 | removeUnusedItems, 33 | } from '../../chrome-storage'; 34 | import { getEditorConfig } from '../../editor-config'; 35 | 36 | let editor: any; 37 | @inject({ 38 | components: { Switch, Icon, Checkbox, Input, Popconfirm, Button }, 39 | }) 40 | export default class XSwitch extends ViewController { 41 | @observable 42 | checked = true; 43 | 44 | @observable 45 | editingKey = '0'; 46 | 47 | @observable 48 | deletingKey = '0'; 49 | 50 | @observable 51 | newItem = ''; 52 | 53 | @observable 54 | items: any = []; 55 | 56 | async $init() { 57 | this.checked = (await getChecked()) !== Enabled.NO; 58 | } 59 | 60 | async $didMount() { 61 | window.require.config({ paths: { vs: MONACO_VS_PATH } }); 62 | const editingConfigKey: string = await getEditingConfigKey(); 63 | this.editingKey = editingConfigKey; 64 | const config: any = await getConfig(editingConfigKey); 65 | this.items = Array.from(await getConfigItems()); 66 | await removeUnusedItems() 67 | 68 | let monacoReady: boolean = true; 69 | 70 | window.require([MONACO_CONTRIBUTION_PATH], () => { 71 | editor = window.monaco.editor.create( 72 | this.$refs.shell, 73 | getEditorConfig(config) 74 | ); 75 | 76 | saveConfig(editor.getValue(), this.editingKey); 77 | 78 | window.monaco.languages.registerCompletionItemProvider(LANGUAGE_JSON, { 79 | provideCompletionItems: () => { 80 | const textArr: any[] = []; 81 | chrome.extension 82 | .getBackgroundPage()! 83 | ._forward.urls.forEach((item: any) => { 84 | if (item) { 85 | textArr.push({ 86 | label: item, 87 | kind: window.monaco.languages.CompletionItemKind.Text, 88 | }); 89 | } 90 | }); 91 | 92 | const extraItems = [ 93 | { 94 | label: RULE, 95 | kind: window.monaco.languages.CompletionItemKind.Method, 96 | insertText: { 97 | value: RULE_COMPLETION, 98 | }, 99 | }, 100 | ]; 101 | return [...textArr, ...extraItems]; 102 | }, 103 | }); 104 | 105 | editor.onDidChangeModelContent(() => { 106 | saveConfig(editor.getValue(), this.editingKey); 107 | }); 108 | 109 | editor.onDidScrollChange(() => { 110 | if (monacoReady) { 111 | editor.trigger(ANYTHING, FORMAT_DOCUMENT_CMD); 112 | monacoReady = false; 113 | } 114 | }); 115 | }); 116 | 117 | function preventSave() { 118 | document.addEventListener( 119 | KEY_DOWN, 120 | (e) => { 121 | const controlKeyDown = navigator.platform.match(PLATFORM_MAC) 122 | ? e.metaKey 123 | : e.ctrlKey; 124 | if (e.keyCode === KEY_CODE_S && controlKeyDown) { 125 | e.preventDefault(); 126 | } 127 | }, 128 | false 129 | ); 130 | } 131 | preventSave(); 132 | } 133 | 134 | setEditorValue(value: string) { 135 | editor.setValue(value); 136 | } 137 | 138 | toggleButton() { 139 | this.checked = !this.checked; 140 | setChecked(this.checked); 141 | } 142 | 143 | openNewTab() { 144 | openLink(POPUP_HTML_PATH, true); 145 | } 146 | openReadme() { 147 | openLink(HELP_URL); 148 | } 149 | 150 | async setEditingKeyHandler(id: string) { 151 | this.editingKey = id; 152 | const config: any = await getConfig(this.editingKey); 153 | this.setEditorValue(config || DEFAULT_DUP_DATA); 154 | setEditingConfigKey(this.editingKey); 155 | // reset 156 | this.deletingKey = '0'; 157 | } 158 | 159 | async setEditingKey(event: EventTarget, ctx: any) { 160 | await this.setEditingKeyHandler(ctx.item.id); 161 | } 162 | 163 | setActive(event: EventTarget, ctx: any) { 164 | ctx.item.active = !ctx.item.active; 165 | setConfigItems(this.items); 166 | } 167 | 168 | async add() { 169 | const id = '' + new Date().getTime(); 170 | const self = this; 171 | if (this.newItem) { 172 | this.items.push({ 173 | id, 174 | name: this.newItem, 175 | active: true, 176 | }); 177 | } 178 | setConfigItems(this.items); 179 | this.editingKey = id; 180 | setEditingConfigKey(this.editingKey); 181 | await this.setEditingKeyHandler(id); 182 | setTimeout(function () { 183 | self.$refs.tabs.scrollTop = self.$refs.tabs.scrollHeight; 184 | }, 0) 185 | this.newItem = ''; 186 | } 187 | 188 | async remove(ev: EventTarget, ctx: any) { 189 | ev.stopPropagation(); 190 | if(this.deletingKey === ctx.item.id){ 191 | const i = this.items.indexOf(ctx.item); 192 | if (i > -1) { 193 | this.items.splice(i, 1); 194 | } 195 | // i will not be 0 196 | if(this.items[i-1].hasOwnProperty('id')){ 197 | this.editingKey = this.items[i-1].id; 198 | await this.setEditingKeyHandler(this.editingKey); 199 | } 200 | setConfigItems(this.items); 201 | }else{ 202 | this.deletingKey = ctx.item.id; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/background.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ALL_URLS, 3 | BLOCKING, 4 | EMPTY_STRING, 5 | MILLISECONDS_PER_WEEK, 6 | REQUEST_HEADERS, 7 | RESPONSE_HEADERS, 8 | JSON_CONFIG, 9 | DISABLED, 10 | CLEAR_CACHE_ENABLED, 11 | CORS_ENABLED_STORAGE_KEY, 12 | PROXY_STORAGE_KEY, 13 | CORS_STORAGE, 14 | ACTIVE_KEYS, 15 | TAB_LIST, 16 | } from './constants'; 17 | import { 18 | BadgeText, 19 | Enabled, 20 | IconBackgroundColor, 21 | } from './enums'; 22 | import forward from './forward'; 23 | 24 | let clearRunning: boolean = false; 25 | let clearCacheEnabled: boolean = true; 26 | let corsEnabled: boolean = true; 27 | let parseError: boolean = false; 28 | let jsonActiveKeys = ['0']; 29 | let conf: StorageJSON = { 30 | 0: { 31 | [PROXY_STORAGE_KEY]: [], 32 | [CORS_STORAGE]: [], 33 | }, 34 | }; 35 | 36 | interface SingleConfig { 37 | [PROXY_STORAGE_KEY]: Array<[]>; 38 | [CORS_STORAGE]: string[]; 39 | } 40 | 41 | interface StorageJSON { 42 | 0: SingleConfig; 43 | [key: string]: any; 44 | } 45 | 46 | chrome.storage.sync.get({ 47 | [JSON_CONFIG]: { 48 | 0: { 49 | [PROXY_STORAGE_KEY]: [], 50 | [CORS_STORAGE]: [], 51 | }, 52 | }, 53 | [ACTIVE_KEYS]: ['0'], 54 | }, (result) => { 55 | jsonActiveKeys = result[ACTIVE_KEYS]; 56 | if (result && result[JSON_CONFIG]) { 57 | conf = result[JSON_CONFIG]; 58 | const config = getActiveConfig(conf); 59 | forward[JSON_CONFIG] = { ...config }; 60 | } else { 61 | forward[JSON_CONFIG] = { 62 | [PROXY_STORAGE_KEY]: [], 63 | [CORS_STORAGE]: [], 64 | }; 65 | parseError = false; 66 | } 67 | }); 68 | 69 | function getActiveConfig(config: StorageJSON): object { 70 | const activeKeys = [...jsonActiveKeys]; 71 | const json = config['0']; 72 | activeKeys.forEach((key: string) => { 73 | if (config[key] && key !== '0') { 74 | if (config[key][PROXY_STORAGE_KEY]) { 75 | if (!json[PROXY_STORAGE_KEY]) { 76 | json[PROXY_STORAGE_KEY] = []; 77 | } 78 | json[PROXY_STORAGE_KEY] = [...json[PROXY_STORAGE_KEY], ...config[key][PROXY_STORAGE_KEY]]; 79 | } 80 | 81 | if (config[key][CORS_STORAGE]) { 82 | if (!json[CORS_STORAGE]) { 83 | json[CORS_STORAGE] = []; 84 | } 85 | json[CORS_STORAGE] = [...json[CORS_STORAGE], ...config[key][CORS_STORAGE]]; 86 | } 87 | } 88 | }); 89 | return json; 90 | } 91 | 92 | chrome.storage.sync.get( 93 | { 94 | [DISABLED]: Enabled.YES, 95 | [CLEAR_CACHE_ENABLED]: Enabled.YES, 96 | [CORS_ENABLED_STORAGE_KEY]: Enabled.YES, 97 | }, 98 | (result) => { 99 | forward[DISABLED] = result[DISABLED]; 100 | clearCacheEnabled = result[CLEAR_CACHE_ENABLED] === Enabled.YES; 101 | corsEnabled = result[CORS_ENABLED_STORAGE_KEY] === Enabled.YES; 102 | setIcon(); 103 | } 104 | ); 105 | 106 | chrome.storage.onChanged.addListener((changes) => { 107 | if (changes[ACTIVE_KEYS]) { 108 | jsonActiveKeys = changes[ACTIVE_KEYS].newValue; 109 | } 110 | 111 | if (changes[JSON_CONFIG]) { 112 | const config = getActiveConfig(changes[JSON_CONFIG].newValue); 113 | forward[JSON_CONFIG] = { ...config }; 114 | } 115 | 116 | if (changes[DISABLED]) { 117 | forward[DISABLED] = changes[DISABLED].newValue; 118 | } 119 | 120 | if (changes[CLEAR_CACHE_ENABLED]) { 121 | clearCacheEnabled = changes[CLEAR_CACHE_ENABLED].newValue === Enabled.YES; 122 | } 123 | 124 | if (changes[CORS_ENABLED_STORAGE_KEY]) { 125 | corsEnabled = changes[CORS_ENABLED_STORAGE_KEY].newValue === Enabled.YES; 126 | } 127 | 128 | chrome.storage.sync.get({ 129 | [JSON_CONFIG]: { 130 | 0: { 131 | [PROXY_STORAGE_KEY]: [], 132 | [CORS_STORAGE]: [], 133 | }, 134 | }, 135 | }, (result) => { 136 | if (result && result[JSON_CONFIG]) { 137 | conf = result[JSON_CONFIG]; 138 | const config = getActiveConfig(conf); 139 | forward[JSON_CONFIG] = { ...config }; 140 | } 141 | setIcon(); 142 | }); 143 | }); 144 | 145 | chrome.webRequest.onBeforeRequest.addListener( 146 | (details) => { 147 | if (forward[DISABLED] !== Enabled.NO) { 148 | if (clearCacheEnabled) { 149 | clearCache(); 150 | } 151 | 152 | return forward.onBeforeRequestCallback(details); 153 | } 154 | return {}; 155 | }, 156 | { 157 | urls: [ALL_URLS], 158 | }, 159 | [BLOCKING] 160 | ); 161 | 162 | // Breaking the CORS Limitation 163 | chrome.webRequest.onHeadersReceived.addListener( 164 | headersReceivedListener, 165 | { 166 | urls: [ALL_URLS], 167 | }, 168 | [BLOCKING, RESPONSE_HEADERS] 169 | ); 170 | 171 | chrome.webRequest.onBeforeSendHeaders.addListener( 172 | (details) => forward.onBeforeSendHeadersCallback(details), 173 | { urls: [ALL_URLS] }, 174 | [BLOCKING, REQUEST_HEADERS] 175 | ); 176 | 177 | function setBadgeAndBackgroundColor( 178 | text: string | number, 179 | color: string 180 | ): void { 181 | const { browserAction } = chrome; 182 | browserAction.setBadgeText({ 183 | text: EMPTY_STRING + text, 184 | }); 185 | browserAction.setBadgeBackgroundColor({ 186 | color, 187 | }); 188 | } 189 | 190 | function setIcon(): void { 191 | if (parseError) { 192 | setBadgeAndBackgroundColor(BadgeText.ERROR, IconBackgroundColor.ERROR); 193 | return; 194 | } 195 | 196 | if (forward[DISABLED] !== Enabled.NO) { 197 | setBadgeAndBackgroundColor( 198 | forward[JSON_CONFIG][PROXY_STORAGE_KEY].length, 199 | IconBackgroundColor.ON 200 | ); 201 | } else { 202 | setBadgeAndBackgroundColor(BadgeText.OFF, IconBackgroundColor.OFF); 203 | return; 204 | } 205 | } 206 | 207 | function headersReceivedListener( 208 | details: chrome.webRequest.WebResponseHeadersDetails): chrome.webRequest.BlockingResponse { 209 | return forward.onHeadersReceivedCallback(details, corsEnabled); 210 | } 211 | 212 | function clearCache(): void { 213 | if (!clearRunning) { 214 | clearRunning = true; 215 | const millisecondsPerWeek = MILLISECONDS_PER_WEEK; 216 | const oneWeekAgo = new Date().getTime() - millisecondsPerWeek; 217 | chrome.browsingData.removeCache( 218 | { 219 | since: oneWeekAgo, 220 | }, 221 | () => { 222 | clearRunning = false; 223 | } 224 | ); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/forward.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CORS, 3 | DEFAULT_CORS_CREDENTIALS, 4 | DEFAULT_CORS_METHODS, 5 | DEFAULT_CORS_ORIGIN, 6 | ORIGIN, 7 | REG, 8 | EMPTY_STRING, 9 | DEFAULT_CREDENTIALS_RESPONSE_HEADERS, 10 | NULL_STRING, 11 | ACCESS_CONTROL_REQUEST_HEADERS, 12 | } from './constants'; 13 | import { Enabled, UrlType } from './enums'; 14 | 15 | interface IFowardConfig { 16 | proxy?: string[][]; 17 | cors?: string[]; 18 | } 19 | 20 | /** 21 | * get url type 22 | * @param url urls 23 | * @param reg rule 24 | */ 25 | const matchUrl = (url: string, reg: string): string | boolean => { 26 | if (REG.FORWARD.test(reg)) { 27 | // support ?? 28 | const r = new RegExp(reg.replace('??', '\\?\\?'), 'i'); 29 | const matched = r.test(url); 30 | if (matched) { 31 | return UrlType.REG; 32 | } 33 | } else { 34 | const matched = url.indexOf(reg) > -1; 35 | if (matched) { 36 | return UrlType.STRING; 37 | } 38 | } 39 | return false; 40 | }; 41 | 42 | class Forward { 43 | private _lastRequestId: string | null = null; 44 | private _disabled: Enabled = Enabled.YES; 45 | private _config: IFowardConfig = {}; 46 | private _originRequest: Map = new Map(); 47 | private _originRequestHeaders: Map = new Map(); 48 | private _urls: string[] = new Array(200); // for cache 49 | 50 | get urls(): string[] { 51 | return this._urls; 52 | } 53 | 54 | get disabled(): Enabled { 55 | return this._disabled; 56 | } 57 | 58 | set disabled(newValue: Enabled) { 59 | this._disabled = newValue; 60 | } 61 | get config(): IFowardConfig { 62 | return this._config; 63 | } 64 | set config(newValue: IFowardConfig) { 65 | this._config = { ...newValue }; 66 | } 67 | 68 | // Breaking the CORS Limitation 69 | onHeadersReceivedCallback( 70 | details: chrome.webRequest.WebResponseHeadersDetails, 71 | cors: boolean = true 72 | ): chrome.webRequest.BlockingResponse { 73 | // has cors rules 74 | const corsMap: string[] = this.config.cors!; 75 | let corsMatched: boolean = false; 76 | 77 | if (corsMap && corsMap.length) { 78 | corsMap.forEach((rule) => { 79 | if (matchUrl(details.url, rule)) { 80 | corsMatched = true; 81 | } 82 | }); 83 | } 84 | 85 | const disabled: boolean = 86 | this.disabled === Enabled.NO || !cors || !corsMatched; 87 | 88 | if (disabled) { 89 | return {}; 90 | } 91 | 92 | const originUrl: string = details.url; 93 | let resHeaders: chrome.webRequest.HttpHeader[] = []; 94 | let CORSOrigin: string = 95 | (this._originRequest.get(details.requestId) 96 | ? this._originRequest.get(details.requestId) 97 | : details.initiator) || DEFAULT_CORS_ORIGIN; 98 | 99 | if (details.responseHeaders && details.responseHeaders.filter) { 100 | let hasCredentials: boolean | string = false; 101 | let tempOrigin: string = EMPTY_STRING; 102 | resHeaders = details.responseHeaders.filter((responseHeader) => { 103 | // Already has access-control-allow-origin headers 104 | if (CORS.ORIGIN === responseHeader.name.toLowerCase()) { 105 | tempOrigin = responseHeader.value!; 106 | } 107 | 108 | if (CORS.CREDENTIALS === responseHeader.name.toLowerCase()) { 109 | hasCredentials = responseHeader.value!; 110 | } 111 | 112 | if ( 113 | [CORS.ORIGIN, CORS.CREDENTIALS, CORS.METHODS, CORS.HEADERS].indexOf( 114 | responseHeader.name.toLowerCase() 115 | ) < 0 116 | ) { 117 | return true; 118 | } 119 | return false; 120 | }); 121 | 122 | // only when hasCredentials 123 | if (hasCredentials) { 124 | CORSOrigin = tempOrigin; 125 | } 126 | } 127 | 128 | // suck point 129 | if ( 130 | CORSOrigin === DEFAULT_CORS_ORIGIN && 131 | this._originRequest.get(details.requestId) === NULL_STRING 132 | ) { 133 | CORSOrigin = DEFAULT_CORS_ORIGIN; 134 | } 135 | 136 | resHeaders.push({ 137 | name: CORS.ORIGIN, 138 | value: CORSOrigin, 139 | }); 140 | resHeaders.push({ 141 | name: CORS.CREDENTIALS, 142 | value: DEFAULT_CORS_CREDENTIALS, 143 | }); 144 | resHeaders.push({ 145 | name: CORS.METHODS, 146 | value: DEFAULT_CORS_METHODS, 147 | }); 148 | 149 | let CORSHeader: string = EMPTY_STRING; 150 | 151 | if (this._originRequestHeaders.get(details.requestId)) { 152 | CORSHeader = ',' + this._originRequestHeaders.get(details.requestId); 153 | } 154 | 155 | resHeaders.push({ 156 | name: CORS.HEADERS, 157 | value: DEFAULT_CREDENTIALS_RESPONSE_HEADERS + CORSHeader, 158 | }); 159 | 160 | return { 161 | responseHeaders: resHeaders, 162 | }; 163 | } 164 | 165 | redirectToMatchingRule( 166 | details: chrome.webRequest.WebRequestHeadersDetails 167 | ): chrome.webRequest.BlockingResponse { 168 | const rules = this.config.proxy; 169 | let redirectUrl: string = details.url; 170 | 171 | // in case of chrome-extension downtime 172 | if (!rules || !rules.length || REG.CHROME_EXTENSION.test(redirectUrl)) { 173 | return {}; 174 | } 175 | 176 | if ( 177 | /http(s?):\/\/.*\.(js|css|json|jsonp)/.test(redirectUrl) && 178 | this._urls.indexOf(redirectUrl) < 0 179 | ) { 180 | this._urls.shift(); 181 | this._urls.push(redirectUrl); 182 | } 183 | 184 | try { 185 | for (let i: number = 0; i < rules.length; i++) { 186 | const rule = rules[i]; 187 | if (rule && rule[0] && typeof rule[1] === 'string') { 188 | const reg = rule[0]; 189 | const matched = matchUrl(redirectUrl, reg); 190 | 191 | if (details.requestId !== this._lastRequestId) { 192 | if (matched === UrlType.REG) { 193 | const r = new RegExp(reg.replace('??', '\\?\\?'), 'i'); 194 | redirectUrl = redirectUrl.replace(r, rule[1]); 195 | } else if (matched === UrlType.STRING) { 196 | redirectUrl = redirectUrl.split(rule[0]).join(rule[1]); 197 | } 198 | } 199 | } 200 | } 201 | } catch (e) { 202 | console.error('rule match error', e); 203 | } 204 | 205 | this._lastRequestId = details.requestId; 206 | return redirectUrl === details.url ? {} : { redirectUrl }; 207 | } 208 | 209 | onBeforeSendHeadersCallback( 210 | details: chrome.webRequest.WebRequestHeadersDetails 211 | ): chrome.webRequest.BlockingResponse { 212 | const headers: string[] = []; 213 | for (let i: number = 0; i < details.requestHeaders!.length; ++i) { 214 | const requestName = details.requestHeaders![i].name.toLowerCase(); 215 | if (requestName === ORIGIN) { 216 | this._originRequest.set( 217 | details.requestId, 218 | details.requestHeaders![i].value! 219 | ); 220 | } else if (requestName === ACCESS_CONTROL_REQUEST_HEADERS || REG.X_HEADER.test(requestName)) { 221 | headers.push(requestName); 222 | } 223 | } 224 | if (headers.length) { 225 | this._originRequestHeaders.set(details.requestId, headers.join(',')); 226 | } 227 | return { requestHeaders: details.requestHeaders }; 228 | } 229 | 230 | onBeforeRequestCallback( 231 | details: chrome.webRequest.WebRequestHeadersDetails 232 | ): chrome.webRequest.BlockingResponse { 233 | return this.redirectToMatchingRule(details); 234 | } 235 | } 236 | 237 | if (!window._forward) { 238 | window._forward = new Forward(); 239 | } 240 | 241 | export default window._forward; 242 | -------------------------------------------------------------------------------- /src/chrome-storage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | JSONC_CONFIG, 3 | JSON_CONFIG, 4 | DISABLED, 5 | CLEAR_CACHE_ENABLED, 6 | CORS_ENABLED_STORAGE_KEY, 7 | TAB_LIST, 8 | EDITING_CONFIG_KEY, 9 | ACTIVE_KEYS, 10 | } from './constants'; 11 | import { JSONC2JSON, JSON_Parse } from './utils'; 12 | import { Enabled } from './enums'; 13 | 14 | interface ConfigStorage { 15 | [JSONC_CONFIG]: object; 16 | } 17 | interface OptionsStorage { 18 | [CLEAR_CACHE_ENABLED]: string; 19 | [CORS_ENABLED_STORAGE_KEY]: string; 20 | } 21 | 22 | export function getConfig(editingConfigKey: string): Promise { 23 | return new Promise((resolve) => { 24 | if (process.env.NODE_ENV !== 'production') { 25 | return resolve({ 26 | [JSONC_CONFIG]: { 27 | 0: '', 28 | }, 29 | }); 30 | } 31 | window.chrome.storage.sync.get({ 32 | [JSONC_CONFIG]: { 33 | 0: '', 34 | }, 35 | }, (result: any) => { 36 | if (typeof result[JSONC_CONFIG] === 'string') { 37 | return resolve(result[JSONC_CONFIG]); 38 | } 39 | resolve(result[JSONC_CONFIG][editingConfigKey]); 40 | }); 41 | }); 42 | } 43 | 44 | export function getActiveKeys(): Promise { 45 | return new Promise((resolve) => { 46 | if (process.env.NODE_ENV !== 'production') { 47 | return resolve({ 48 | [ACTIVE_KEYS]: ['0'], 49 | }); 50 | } 51 | window.chrome.storage.sync.get( 52 | { 53 | [ACTIVE_KEYS]: ['0'], 54 | }, (result: any) => { 55 | resolve(result[ACTIVE_KEYS]); 56 | }); 57 | }); 58 | } 59 | 60 | export function setActiveKeys(keys?: string[]): Promise | void { 61 | if (process.env.NODE_ENV === 'production') { 62 | return new Promise((resolve) => { 63 | window.chrome.storage.sync.set( 64 | { 65 | [ACTIVE_KEYS]: keys, 66 | }, 67 | resolve 68 | ); 69 | }); 70 | } 71 | } 72 | 73 | export function getConfigItems(): Promise { 74 | return new Promise((resolve) => { 75 | if (process.env.NODE_ENV !== 'production') { 76 | return resolve({ 77 | [TAB_LIST]: [{ 78 | id: '0', 79 | name: 'Current', 80 | active: true, 81 | }], 82 | }); 83 | } 84 | window.chrome.storage.sync.get( 85 | { 86 | [TAB_LIST]: [{ 87 | id: '0', 88 | name: 'Current', 89 | active: true, 90 | }], 91 | }, (result: any) => { 92 | resolve(result[TAB_LIST]); 93 | }); 94 | }); 95 | } 96 | 97 | export function setConfigItems(items?: any): Promise | void { 98 | if (process.env.NODE_ENV === 'production') { 99 | return new Promise((resolve) => { 100 | window.chrome.storage.sync.set( 101 | { 102 | [TAB_LIST]: items.slice(), 103 | [ACTIVE_KEYS]: items.map((item: any) => { 104 | if (item.active) { 105 | return item.id; 106 | } 107 | }), 108 | }, 109 | resolve 110 | ); 111 | }); 112 | } 113 | } 114 | 115 | export function getEditingConfigKey(): Promise { 116 | return new Promise((resolve) => { 117 | if (process.env.NODE_ENV !== 'production') { 118 | return resolve('0'); 119 | } 120 | window.chrome.storage.sync.get( 121 | { 122 | [EDITING_CONFIG_KEY]: '0', 123 | }, (result) => { 124 | resolve(result[EDITING_CONFIG_KEY]); 125 | }); 126 | }); 127 | } 128 | 129 | export function setEditingConfigKey(key: string): Promise | void { 130 | if (process.env.NODE_ENV === 'production') { 131 | return new Promise((resolve) => { 132 | window.chrome.storage.sync.set( 133 | { 134 | [EDITING_CONFIG_KEY]: key, 135 | }, 136 | resolve 137 | ); 138 | }); 139 | } 140 | } 141 | 142 | export function saveConfig(jsonc: string, editingConfigKey: string): Promise | void { 143 | const json = JSONC2JSON(jsonc); 144 | 145 | if (process.env.NODE_ENV === 'production') { 146 | return new Promise((resolve) => { 147 | window.chrome.storage.sync.get({ 148 | [JSONC_CONFIG]: {}, 149 | [JSON_CONFIG]: {}, 150 | }, (result) => { 151 | // migrate 152 | if (typeof result[JSONC_CONFIG] === 'string') { 153 | result[JSONC_CONFIG] = {}; 154 | result[JSON_CONFIG] = {}; 155 | } 156 | 157 | result[JSONC_CONFIG][editingConfigKey] = jsonc; 158 | 159 | JSON_Parse(json, (error, parsedJSON) => { 160 | if (!error) { 161 | result[JSON_CONFIG][editingConfigKey] = parsedJSON; 162 | return; 163 | } 164 | result[JSON_CONFIG][editingConfigKey] = ''; 165 | }); 166 | 167 | window.chrome.storage.sync.set( 168 | result, 169 | resolve 170 | ); 171 | }); 172 | }); 173 | } 174 | } 175 | 176 | export function getChecked(): Promise { 177 | return new Promise((resolve) => { 178 | if (process.env.NODE_ENV !== 'production') { 179 | return resolve(Enabled.YES); 180 | } 181 | window.chrome.storage.sync.get(DISABLED, (result: any) => { 182 | resolve(result[DISABLED]); 183 | }); 184 | }); 185 | } 186 | 187 | export function setChecked(checked?: boolean): Promise | void { 188 | if (process.env.NODE_ENV === 'production') { 189 | return new Promise((resolve) => { 190 | window.chrome.storage.sync.set( 191 | { 192 | [DISABLED]: checked ? Enabled.YES : Enabled.NO, 193 | }, 194 | resolve 195 | ); 196 | }); 197 | } 198 | } 199 | 200 | export function getOptions(): Promise { 201 | return new Promise((resolve) => { 202 | if (process.env.NODE_ENV !== 'production') { 203 | return resolve({ 204 | [CLEAR_CACHE_ENABLED]: Enabled.YES, 205 | [CORS_ENABLED_STORAGE_KEY]: Enabled.YES, 206 | }); 207 | } 208 | window.chrome.storage.sync.get( 209 | { 210 | [CLEAR_CACHE_ENABLED]: Enabled.YES, 211 | [CORS_ENABLED_STORAGE_KEY]: Enabled.YES, 212 | }, 213 | (result) => { 214 | resolve({ 215 | [CLEAR_CACHE_ENABLED]: result.clearCacheEnabled, 216 | [CORS_ENABLED_STORAGE_KEY]: result.corsEnabled, 217 | }); 218 | } 219 | ); 220 | }); 221 | } 222 | 223 | export function setOptions(options: any): Promise | void { 224 | if (process.env.NODE_ENV === 'production') { 225 | return new Promise((resolve) => { 226 | window.chrome.storage.sync.set( 227 | { 228 | clearCacheEnabled: options.clearCacheEnabled 229 | ? Enabled.YES 230 | : Enabled.NO, 231 | corsEnabled: options.corsEnabled ? Enabled.YES : Enabled.NO, 232 | }, 233 | resolve 234 | ); 235 | }); 236 | } 237 | } 238 | 239 | export function openLink(url: string, isInner: boolean = false): void { 240 | chrome.tabs.create( 241 | { url: isInner ? chrome.extension.getURL(url) : url }, 242 | (tab) => { 243 | // Tab opened. 244 | } 245 | ); 246 | } 247 | 248 | 249 | export function removeUnusedItems(){ 250 | window.chrome.storage.sync.get({ 251 | [JSONC_CONFIG]: {}, 252 | [JSON_CONFIG]: {}, 253 | [TAB_LIST]: [{ 254 | id: '0', 255 | name: 'Current', 256 | active: true, 257 | }], 258 | }, (result) => { 259 | let stash: any = { 260 | [JSONC_CONFIG]: {}, 261 | [JSON_CONFIG]: {}, 262 | }; 263 | result[TAB_LIST].forEach((tab: any)=>{ 264 | stash[JSONC_CONFIG][tab.id] = result[JSONC_CONFIG][tab.id]; 265 | stash[JSON_CONFIG][tab.id] = result[JSON_CONFIG][tab.id]; 266 | }) 267 | window.chrome.storage.sync.set( 268 | stash, 269 | ()=>{} 270 | ); 271 | }); 272 | } 273 | -------------------------------------------------------------------------------- /__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Enabled } from '../src/enums'; 2 | import forward from '../src/forward'; 3 | 4 | beforeEach(() => { 5 | forward.config = {}; 6 | }); 7 | 8 | describe('no rules', () => { 9 | test('no forward when no rules', () => { 10 | expect( 11 | // @ts-ignore 12 | forward.redirectToMatchingRule({ url: 'g.alicdn.com', requestId: 1 }) 13 | ).toEqual({}); 14 | expect( 15 | // @ts-ignore 16 | forward.redirectToMatchingRule({ 17 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 18 | requestId: 2 19 | }) 20 | ).toEqual({}); 21 | expect( 22 | // @ts-ignore 23 | forward.redirectToMatchingRule({ 24 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 25 | requestId: 2 26 | }) 27 | ).toEqual({}); 28 | }); 29 | }); 30 | 31 | describe('rule[1] is not string', () => { 32 | test('no forward when no rule[1]', () => { 33 | forward.config.proxy = [['g.alicdn.com']]; 34 | expect( 35 | // @ts-ignore 36 | forward.redirectToMatchingRule({ url: 'g.alicdn.com', requestId: 1 }) 37 | ).toEqual({}); 38 | expect( 39 | // @ts-ignore 40 | forward.redirectToMatchingRule({ 41 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 42 | requestId: 2 43 | }) 44 | ).toEqual({}); 45 | }); 46 | 47 | test('no forward when rule[1] is not string', () => { 48 | // @ts-ignore 49 | forward.config.proxy = [['g.alicdn.com', ['a', 'b']]]; 50 | expect( 51 | // @ts-ignore 52 | forward.redirectToMatchingRule({ url: 'g.alicdn.com', requestId: 1 }) 53 | ).toEqual({}); 54 | expect( 55 | // @ts-ignore 56 | forward.redirectToMatchingRule({ 57 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 58 | requestId: 2 59 | }) 60 | ).toEqual({}); 61 | }); 62 | 63 | test('no forward when rule[1] is not string', () => { 64 | // @ts-ignore 65 | forward.config.proxy = [['g.alicdn.com', {}]]; 66 | expect( 67 | // @ts-ignore 68 | forward.redirectToMatchingRule({ url: 'g.alicdn.com', requestId: 1 }) 69 | ).toEqual({}); 70 | expect( 71 | // @ts-ignore 72 | forward.redirectToMatchingRule({ 73 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 74 | requestId: 2 75 | }) 76 | ).toEqual({}); 77 | }); 78 | }); 79 | 80 | describe('chrome-extension://', () => { 81 | test('should not forward', () => { 82 | forward.config.proxy = [['(.*).js', '$1..js']]; 83 | expect( 84 | // @ts-ignore 85 | forward.redirectToMatchingRule({ 86 | url: 'chrome-extension://xxxxx/a.js', 87 | requestId: 1 88 | }) 89 | ).toEqual({}); 90 | }); 91 | test('should not forward', () => { 92 | forward.config.proxy = [['xxxxx', 'xxxx']]; 93 | expect( 94 | // @ts-ignore 95 | forward.redirectToMatchingRule({ 96 | url: 'chrome-extension://xxxxx/a.js', 97 | requestId: 2 98 | }) 99 | ).toEqual({}); 100 | }); 101 | }); 102 | 103 | describe('same request id should not forward', () => { 104 | test('no forward', () => { 105 | expect( 106 | // @ts-ignore 107 | forward.redirectToMatchingRule({ url: 'g.alicdn.com', requestId: 1 }) 108 | ).toEqual({}); 109 | expect( 110 | // @ts-ignore 111 | forward.redirectToMatchingRule({ 112 | url: 'https://g.alicdn.com??a.js,b.js,c.js', 113 | requestId: 1 114 | }) 115 | ).toEqual({}); 116 | }); 117 | }); 118 | 119 | describe('string urls', () => { 120 | test('should forward normal url without query', () => { 121 | forward.config.proxy = [['g.alicdn.com', 'g.alicdn.com?t=2']]; 122 | expect( 123 | // @ts-ignore 124 | forward.redirectToMatchingRule({ 125 | url: 'https://g.alicdn.com', 126 | requestId: 1 127 | }).redirectUrl 128 | ).toEqual('https://g.alicdn.com?t=2'); 129 | 130 | expect( 131 | // @ts-ignore 132 | forward.redirectToMatchingRule({ 133 | url: 'https://g.alicdn.com#aaaa', 134 | requestId: 2 135 | }) 136 | ).toEqual({ 137 | redirectUrl: 'https://g.alicdn.com?t=2#aaaa' 138 | }); 139 | expect( 140 | // @ts-ignore 141 | forward.redirectToMatchingRule({ 142 | url: 'https://g.alicdn.com/??a.js,b.js,c.js', 143 | requestId: 3 144 | }).redirectUrl 145 | ).toEqual('https://g.alicdn.com?t=2/??a.js,b.js,c.js'); 146 | }); 147 | 148 | test('should forward normal url with query', () => { 149 | forward.config.proxy = [ 150 | ['https://g.alicdn.com?t=1', 'https://g.alicdn.com?t=2'] 151 | ]; 152 | expect( 153 | // @ts-ignore 154 | forward.redirectToMatchingRule({ 155 | url: 'https://g.alicdn.com?t=1&k=2', 156 | requestId: 1 157 | }).redirectUrl 158 | ).toEqual('https://g.alicdn.com?t=2&k=2'); 159 | 160 | expect( 161 | // @ts-ignore 162 | forward.redirectToMatchingRule({ 163 | url: 'https://g.alicdn.com#aaaa', 164 | requestId: 2 165 | }).redirectUrl 166 | ).toBeFalsy(); 167 | 168 | expect( 169 | // @ts-ignore 170 | forward.redirectToMatchingRule({ 171 | url: 'https://g.alicdn.com?t=1#aaaa', 172 | requestId: 3 173 | }).redirectUrl 174 | ).toEqual('https://g.alicdn.com?t=2#aaaa'); 175 | }); 176 | test('should forward url with ?? ', () => { 177 | forward.config.proxy = [ 178 | ['https://a.com/??a.js,b.js', 'https://b.com/??a.js,b.js'] 179 | ]; 180 | expect( 181 | // @ts-ignore 182 | forward.redirectToMatchingRule({ 183 | url: 'https://a.com/??a.js,b.js', 184 | requestId: 1 185 | }).redirectUrl 186 | ).toEqual('https://b.com/??a.js,b.js'); 187 | 188 | expect( 189 | // @ts-ignore 190 | forward.redirectToMatchingRule({ 191 | url: 'https://g.alicdn.com/#aaaa', 192 | requestId: 2 193 | }).redirectUrl 194 | ).toBeFalsy(); 195 | 196 | expect( 197 | // @ts-ignore 198 | forward.redirectToMatchingRule({ 199 | url: 'https://a.com/??a.js,b.js?t=1#aaa', 200 | requestId: 3 201 | }).redirectUrl 202 | ).toEqual('https://b.com/??a.js,b.js?t=1#aaa'); 203 | }); 204 | }); 205 | 206 | describe('reg urls', () => { 207 | test('should forward reg url without query', () => { 208 | forward.config.proxy = [['g.(\\w+).com', 'g.alicdn.com?t=2']]; 209 | 210 | expect( 211 | // @ts-ignore 212 | forward.redirectToMatchingRule({ 213 | url: 'https://g1.alicdn.com', 214 | requestId: 1 215 | }).redirectUrl 216 | ).toBeFalsy(); 217 | 218 | expect( 219 | // @ts-ignore 220 | forward.redirectToMatchingRule({ 221 | url: 'https://g.alicdn.com', 222 | requestId: 2 223 | }).redirectUrl 224 | ).toEqual('https://g.alicdn.com?t=2'); 225 | 226 | expect( 227 | // @ts-ignore 228 | forward.redirectToMatchingRule({ 229 | url: 'https://g.alicdn.com#aaaa', 230 | requestId: 3 231 | }).redirectUrl 232 | ).toEqual('https://g.alicdn.com?t=2#aaaa'); 233 | expect( 234 | // @ts-ignore 235 | forward.redirectToMatchingRule({ 236 | url: 'https://g.alicdn.com/??a.js,b.js,c.js', 237 | requestId: 4 238 | }).redirectUrl 239 | ).toEqual('https://g.alicdn.com?t=2/??a.js,b.js,c.js'); 240 | }); 241 | 242 | test('should forward reg url with query', () => { 243 | forward.config.proxy = [ 244 | ['(.*)g.(.*).com\\?t=1', 'https://g.alicdn.com?t=2'] 245 | ]; 246 | expect( 247 | // @ts-ignore 248 | forward.redirectToMatchingRule({ 249 | url: 'https://g.alicdn.com?t=1&k=2', 250 | requestId: 1 251 | }).redirectUrl 252 | ).toEqual('https://g.alicdn.com?t=2&k=2'); 253 | 254 | expect( 255 | // @ts-ignore 256 | forward.redirectToMatchingRule({ 257 | url: 'https://g.alicdn.com#aaaa', 258 | requestId: 2 259 | }).redirectUrl 260 | ).toBeFalsy(); 261 | 262 | expect( 263 | // @ts-ignore 264 | forward.redirectToMatchingRule({ 265 | url: 'https://g.alicdn.com?t=1#aaaa', 266 | requestId: 3 267 | }).redirectUrl 268 | ).toEqual('https://g.alicdn.com?t=2#aaaa'); 269 | }); 270 | 271 | test('should forward reg url with ??', () => { 272 | forward.config.proxy = [ 273 | ['(.*)g.alicdn.com/\\?\\?(.*)', '$1alinw.alicdn.com/??$2'] 274 | ]; 275 | expect( 276 | // @ts-ignore 277 | forward.redirectToMatchingRule({ 278 | url: 'https://g.alicdn.com/??a.js,b.js?t=1', 279 | requestId: 1 280 | }).redirectUrl 281 | ).toEqual('https://alinw.alicdn.com/??a.js,b.js?t=1'); 282 | 283 | expect( 284 | // @ts-ignore 285 | forward.redirectToMatchingRule({ 286 | url: 'https://g.alicdn.com/#aaaa', 287 | requestId: 2 288 | }).redirectUrl 289 | ).toBeFalsy(); 290 | 291 | expect( 292 | // @ts-ignore 293 | forward.redirectToMatchingRule({ 294 | url: 'https://g.alicdn.com/??a.js,b.js?t=1#aaa', 295 | requestId: 3 296 | }).redirectUrl 297 | ).toEqual('https://alinw.alicdn.com/??a.js,b.js?t=1#aaa'); 298 | }); 299 | }); 300 | 301 | describe('multiple rules', () => { 302 | test('should support multiple rules', () => { 303 | forward.config.proxy = [ 304 | [ 305 | '//g.alicdn.com/platform/daily-test/(.*).js$', 306 | '//g.alicdn.com/platform/daily-test/$1.json' 307 | ], 308 | ['g.alicdn.com', 'alinw.alicdn.com'] 309 | ]; 310 | expect( 311 | // @ts-ignore 312 | forward.redirectToMatchingRule({ 313 | url: 'https://g.alicdn.com/platform/daily-test/isDaily.js', 314 | requestId: 1 315 | }).redirectUrl 316 | ).toEqual('https://alinw.alicdn.com/platform/daily-test/isDaily.json'); 317 | }); 318 | }); 319 | 320 | describe('CORS without access-control-allow-origin', () => { 321 | test('should support cors', () => { 322 | forward.config.proxy = [ 323 | ['http://dev-a.b.com/(.*).json', 'http://dev-c.d.com/$1.json'] 324 | ]; 325 | forward.config.cors = ['dev-c.d.com']; 326 | 327 | const testheaderDetails = { 328 | frameId: 0, 329 | initiator: 'http://dev-a.b.com', 330 | method: 'GET', 331 | parentFrameId: -1, 332 | requestId: '265010', 333 | responseHeaders: [ 334 | { 335 | name: 'Date', 336 | value: 'Wed, 11 Jul 2018 12:38:45 GMT' 337 | }, 338 | { 339 | name: 'Content-Type', 340 | value: 'application/json;charset=UTF-8' 341 | }, 342 | { 343 | name: 'Transfer-Encoding', 344 | value: 'chunked' 345 | }, 346 | { 347 | name: 'Connection', 348 | value: 'keep-alive' 349 | }, 350 | { 351 | name: 'Vary', 352 | value: 'Accept-Encoding' 353 | }, 354 | { 355 | name: 'X-Application-Context', 356 | value: 'a-b-c:7001' 357 | }, 358 | { 359 | name: 'X-Content-Type-Options', 360 | value: 'nosniff' 361 | }, 362 | { 363 | name: 'X-XSS-Protection', 364 | value: '1; mode=block' 365 | }, 366 | { 367 | name: 'Cache-Control', 368 | value: 'no-cache, no-store, max-age=0, must-revalidate' 369 | }, 370 | { 371 | name: 'Pragma', 372 | value: 'no-cache' 373 | }, 374 | { 375 | name: 'Expires', 376 | value: '0' 377 | }, 378 | { 379 | name: 'X-Frame-Options', 380 | value: 'DENY' 381 | }, 382 | { 383 | name: 'Strict-Transport-Security', 384 | value: 'max-age=31536000 ; includeSubDomains' 385 | }, 386 | { 387 | name: 'Content-Encoding', 388 | value: 'gzip' 389 | }, 390 | { 391 | name: 'Server', 392 | value: 'Tengine/Aserver' 393 | }, 394 | { 395 | name: 'EagleEye-TraceId', 396 | value: '0a67793015313127251908120e23db' 397 | }, 398 | { 399 | name: 'Timing-Allow-Origin', 400 | value: '*' 401 | } 402 | ], 403 | statusCode: 200, 404 | statusLine: 'HTTP/1.1 200', 405 | tabId: 2988, 406 | timeStamp: 1531312725294.728, 407 | type: 'xmlhttprequest', 408 | url: 'http://dev-c.d.com/overview/type.json?' 409 | }; 410 | const expectHeaderDetails = [ 411 | { name: 'Date', value: 'Wed, 11 Jul 2018 12:38:45 GMT' }, 412 | { name: 'Content-Type', value: 'application/json;charset=UTF-8' }, 413 | { name: 'Transfer-Encoding', value: 'chunked' }, 414 | { name: 'Connection', value: 'keep-alive' }, 415 | { name: 'Vary', value: 'Accept-Encoding' }, 416 | { name: 'X-Application-Context', value: 'a-b-c:7001' }, 417 | { name: 'X-Content-Type-Options', value: 'nosniff' }, 418 | { name: 'X-XSS-Protection', value: '1; mode=block' }, 419 | { 420 | name: 'Cache-Control', 421 | value: 'no-cache, no-store, max-age=0, must-revalidate' 422 | }, 423 | { name: 'Pragma', value: 'no-cache' }, 424 | { name: 'Expires', value: '0' }, 425 | { name: 'X-Frame-Options', value: 'DENY' }, 426 | { 427 | name: 'Strict-Transport-Security', 428 | value: 'max-age=31536000 ; includeSubDomains' 429 | }, 430 | { name: 'Content-Encoding', value: 'gzip' }, 431 | { name: 'Server', value: 'Tengine/Aserver' }, 432 | { name: 'EagleEye-TraceId', value: '0a67793015313127251908120e23db' }, 433 | { name: 'Timing-Allow-Origin', value: '*' }, 434 | { name: 'access-control-allow-origin', value: 'http://dev-a.b.com' }, 435 | { name: 'access-control-allow-credentials', value: 'true' }, 436 | { name: 'access-control-allow-methods', value: '*' }, 437 | { 438 | name: 'access-control-allow-headers', 439 | value: 440 | 'Content-Type, access-control-allow-headers, Authorization, X-Requested-With, X-Referer' 441 | } 442 | ]; 443 | expect( 444 | // @ts-ignore 445 | forward.onHeadersReceivedCallback(testheaderDetails).responseHeaders 446 | ).toEqual(expectHeaderDetails); 447 | }); 448 | }); 449 | 450 | describe('CORS withCredentials', () => { 451 | test('should support cors', () => { 452 | forward.config.proxy = [ 453 | ['http://127.0.0.1/(.*).json', 'http://a.b.com/$1.json'] 454 | ]; 455 | forward.config.cors = ['http://a.b.com']; 456 | const testheaderDetails = { 457 | frameId: 0, 458 | initiator: 'http://127.0.0.1', 459 | method: 'GET', 460 | parentFrameId: -1, 461 | requestId: '271953', 462 | responseHeaders: [ 463 | { 464 | name: 'Date', 465 | value: 'Thu, 12 Jul 2018 02:32:09 GMT' 466 | }, 467 | { 468 | name: 'Content-Type', 469 | value: 'application/json;charset=UTF-8' 470 | }, 471 | { 472 | name: 'Transfer-Encoding', 473 | value: 'chunked' 474 | }, 475 | { 476 | name: 'Connection', 477 | value: 'keep-alive' 478 | }, 479 | { 480 | name: 'Vary', 481 | value: 'Accept-Encoding' 482 | }, 483 | { 484 | name: 'X-Content-Type-Options', 485 | value: 'nosniff' 486 | }, 487 | { 488 | name: 'X-XSS-Protection', 489 | value: '1; mode=block' 490 | }, 491 | { 492 | name: 'Cache-Control', 493 | value: 'no-cache, no-store, max-age=0, must-revalidate' 494 | }, 495 | { 496 | name: 'Pragma', 497 | value: 'no-cache' 498 | }, 499 | { 500 | name: 'Expires', 501 | value: '0' 502 | }, 503 | { 504 | name: 'X-Frame-Options', 505 | value: 'DENY' 506 | }, 507 | { 508 | name: 'Strict-Transport-Security', 509 | value: 'max-age=31536000 ; includeSubDomains' 510 | }, 511 | { 512 | name: 'access-control-allow-credentials', 513 | value: 'true' 514 | }, 515 | { 516 | name: 'access-control-allow-origin', 517 | value: 'http://127.0.0.1' 518 | }, 519 | { 520 | name: 'Vary', 521 | value: 'Origin' 522 | }, 523 | { 524 | name: 'Access-Control-Expose-Headers', 525 | value: 'Set-Cookie' 526 | }, 527 | { 528 | name: 'X-Application-Context', 529 | value: 'ottscgadmin:7001' 530 | }, 531 | { 532 | name: 'EagleEye-TraceId-daily', 533 | value: '1e37823915313627291994023e' 534 | }, 535 | { 536 | name: 'Content-Encoding', 537 | value: 'gzip' 538 | }, 539 | { 540 | name: 'Server', 541 | value: 'Tengine/Aserver' 542 | }, 543 | { 544 | name: 'EagleEye-TraceId', 545 | value: '0bef992c15313627291782432e3237' 546 | }, 547 | { 548 | name: 'Timing-Allow-Origin', 549 | value: '*' 550 | } 551 | ], 552 | statusCode: 200, 553 | statusLine: 'HTTP/1.1 200', 554 | tabId: 3055, 555 | timeStamp: 1531362729284.772, 556 | type: 'xmlhttprequest', 557 | url: 'http://a.b.com/scg/option.json?' 558 | }; 559 | const expectHeaderDetails = [ 560 | { name: 'Date', value: 'Thu, 12 Jul 2018 02:32:09 GMT' }, 561 | { name: 'Content-Type', value: 'application/json;charset=UTF-8' }, 562 | { name: 'Transfer-Encoding', value: 'chunked' }, 563 | { name: 'Connection', value: 'keep-alive' }, 564 | { name: 'Vary', value: 'Accept-Encoding' }, 565 | { name: 'X-Content-Type-Options', value: 'nosniff' }, 566 | { name: 'X-XSS-Protection', value: '1; mode=block' }, 567 | { 568 | name: 'Cache-Control', 569 | value: 'no-cache, no-store, max-age=0, must-revalidate' 570 | }, 571 | { name: 'Pragma', value: 'no-cache' }, 572 | { name: 'Expires', value: '0' }, 573 | { name: 'X-Frame-Options', value: 'DENY' }, 574 | { 575 | name: 'Strict-Transport-Security', 576 | value: 'max-age=31536000 ; includeSubDomains' 577 | }, 578 | { name: 'Vary', value: 'Origin' }, 579 | { name: 'Access-Control-Expose-Headers', value: 'Set-Cookie' }, 580 | { name: 'X-Application-Context', value: 'ottscgadmin:7001' }, 581 | { name: 'EagleEye-TraceId-daily', value: '1e37823915313627291994023e' }, 582 | { name: 'Content-Encoding', value: 'gzip' }, 583 | { name: 'Server', value: 'Tengine/Aserver' }, 584 | { name: 'EagleEye-TraceId', value: '0bef992c15313627291782432e3237' }, 585 | { name: 'Timing-Allow-Origin', value: '*' }, 586 | { name: 'access-control-allow-origin', value: 'http://127.0.0.1' }, 587 | { name: 'access-control-allow-credentials', value: 'true' }, 588 | { name: 'access-control-allow-methods', value: '*' }, 589 | { 590 | name: 'access-control-allow-headers', 591 | value: 592 | 'Content-Type, access-control-allow-headers, Authorization, X-Requested-With, X-Referer' 593 | } 594 | ]; 595 | expect( 596 | // @ts-ignore 597 | forward.onHeadersReceivedCallback(testheaderDetails).responseHeaders 598 | ).toEqual(expectHeaderDetails); 599 | }); 600 | }); 601 | 602 | describe('CORS withCredentials and no forwardConfig', () => { 603 | test('should return {}', () => { 604 | forward.config.proxy = []; 605 | // @ts-ignore 606 | expect(forward.onHeadersReceivedCallback({}).responseHeaders).toEqual( 607 | undefined 608 | ); 609 | }); 610 | }); 611 | 612 | describe('CORS withCredentials and forwardConfig is disabled', () => { 613 | test('should return {}', () => { 614 | forward.disabled = Enabled.NO; 615 | // @ts-ignore 616 | expect(forward.onHeadersReceivedCallback({}).responseHeaders).toEqual( 617 | undefined 618 | ); 619 | }); 620 | }); 621 | -------------------------------------------------------------------------------- /lib/monaco-editor/min/vs/loader.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.13.2(53a4043676e6259fb734c90fad14bf16f7425640) 4 | * Released under the MIT license 5 | * https://github.com/Microsoft/vscode/blob/master/LICENSE.txt 6 | *-----------------------------------------------------------*/ 7 | "use strict";var _amdLoaderGlobal=this;!function(e){e.global=_amdLoaderGlobal;var t=function(){function t(){this._detected=!1,this._isWindows=!1,this._isNode=!1,this._isElectronRenderer=!1,this._isWebWorker=!1}return Object.defineProperty(t.prototype,"isWindows",{get:function(){return this._detect(),this._isWindows},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isNode",{get:function(){return this._detect(),this._isNode},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isElectronRenderer",{get:function(){return this._detect(),this._isElectronRenderer},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"isWebWorker",{get:function(){return this._detect(),this._isWebWorker},enumerable:!0,configurable:!0}),t.prototype._detect=function(){this._detected||(this._detected=!0,this._isWindows=t._isWindows(),this._isNode="undefined"!=typeof module&&!!module.exports, 8 | this._isElectronRenderer="undefined"!=typeof process&&void 0!==process.versions&&void 0!==process.versions.electron&&"renderer"===process.type,this._isWebWorker="function"==typeof e.global.importScripts)},t._isWindows=function(){return!!("undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.indexOf("Windows")>=0)||"undefined"!=typeof process&&"win32"===process.platform},t}();e.Environment=t}(AMDLoader||(AMDLoader={}));!function(e){var t=function(){return function(e,t,r){this.type=e,this.detail=t,this.timestamp=r}}();e.LoaderEvent=t;var r=function(){function r(e){this._events=[new t(1,"",e)]}return r.prototype.record=function(r,n){this._events.push(new t(r,n,e.Utilities.getHighPerformanceTimestamp()))},r.prototype.getEvents=function(){return this._events},r}();e.LoaderEventRecorder=r;var n=function(){function e(){}return e.prototype.record=function(e,t){},e.prototype.getEvents=function(){return[]},e.INSTANCE=new e,e}();e.NullLoaderEventRecorder=n}(AMDLoader||(AMDLoader={}));!function(e){ 9 | var t=function(){function t(){}return t.fileUriToFilePath=function(e,t){if(t=decodeURI(t),e){if(/^file:\/\/\//.test(t))return t.substr(8);if(/^file:\/\//.test(t))return t.substr(5)}else if(/^file:\/\//.test(t))return t.substr(7);return t},t.startsWith=function(e,t){return e.length>=t.length&&e.substr(0,t.length)===t},t.endsWith=function(e,t){return e.length>=t.length&&e.substr(e.length-t.length)===t},t.containsQueryString=function(e){return/^[^\#]*\?/gi.test(e)},t.isAbsolutePath=function(e){return/^((http:\/\/)|(https:\/\/)|(file:\/\/)|(\/))/.test(e)},t.forEachProperty=function(e,t){if(e){var r=void 0;for(r in e)e.hasOwnProperty(r)&&t(r,e[r])}},t.isEmpty=function(e){var r=!0;return t.forEachProperty(e,function(){r=!1}),r},t.recursiveClone=function(e){if(!e||"object"!=typeof e)return e;var r=Array.isArray(e)?[]:{};return t.forEachProperty(e,function(e,n){r[e]=n&&"object"==typeof n?t.recursiveClone(n):n}),r},t.generateAnonymousModule=function(){return"===anonymous"+t.NEXT_ANONYMOUS_ID+++"==="}, 10 | t.isAnonymousModule=function(e){return/^===anonymous/.test(e)},t.getHighPerformanceTimestamp=function(){return this.PERFORMANCE_NOW_PROBED||(this.PERFORMANCE_NOW_PROBED=!0,this.HAS_PERFORMANCE_NOW=e.global.performance&&"function"==typeof e.global.performance.now),this.HAS_PERFORMANCE_NOW?e.global.performance.now():Date.now()},t.NEXT_ANONYMOUS_ID=1,t.PERFORMANCE_NOW_PROBED=!1,t.HAS_PERFORMANCE_NOW=!1,t}();e.Utilities=t}(AMDLoader||(AMDLoader={}));!function(e){var t=function(){function t(){}return t.validateConfigurationOptions=function(t){function r(e){return"load"===e.errorCode?(console.error('Loading "'+e.moduleId+'" failed'),console.error("Detail: ",e.detail),e.detail&&e.detail.stack&&console.error(e.detail.stack),console.error("Here are the modules that depend on it:"),void console.error(e.neededBy)):"factory"===e.errorCode?(console.error('The factory method of "'+e.moduleId+'" has thrown an exception'),console.error(e.detail),void(e.detail&&e.detail.stack&&console.error(e.detail.stack))):void 0} 11 | return"string"!=typeof(t=t||{}).baseUrl&&(t.baseUrl=""),"boolean"!=typeof t.isBuild&&(t.isBuild=!1),"object"!=typeof t.paths&&(t.paths={}),"object"!=typeof t.config&&(t.config={}),void 0===t.catchError&&(t.catchError=!1),"string"!=typeof t.urlArgs&&(t.urlArgs=""),"function"!=typeof t.onError&&(t.onError=r),"object"==typeof t.ignoreDuplicateModules&&Array.isArray(t.ignoreDuplicateModules)||(t.ignoreDuplicateModules=[]),t.baseUrl.length>0&&(e.Utilities.endsWith(t.baseUrl,"/")||(t.baseUrl+="/")),Array.isArray(t.nodeModules)||(t.nodeModules=[]),("number"!=typeof t.nodeCachedDataWriteDelay||t.nodeCachedDataWriteDelay<0)&&(t.nodeCachedDataWriteDelay=7e3),"function"!=typeof t.onNodeCachedData&&(t.onNodeCachedData=function(e,t){e&&("cachedDataRejected"===e.errorCode?console.warn("Rejected cached data from file: "+e.path):"unlink"===e.errorCode||"writeFile"===e.errorCode?(console.error("Problems writing cached data file: "+e.path),console.error(e.detail)):console.error(e))}),t}, 12 | t.mergeConfigurationOptions=function(r,n){void 0===r&&(r=null),void 0===n&&(n=null);var o=e.Utilities.recursiveClone(n||{});return e.Utilities.forEachProperty(r,function(t,r){"ignoreDuplicateModules"===t&&void 0!==o.ignoreDuplicateModules?o.ignoreDuplicateModules=o.ignoreDuplicateModules.concat(r):"paths"===t&&void 0!==o.paths?e.Utilities.forEachProperty(r,function(e,t){return o.paths[e]=t}):"config"===t&&void 0!==o.config?e.Utilities.forEachProperty(r,function(e,t){return o.config[e]=t}):o[t]=e.Utilities.recursiveClone(r)}),t.validateConfigurationOptions(o)},t}();e.ConfigurationOptionsUtil=t;var r=function(){function r(e,r){if(this._env=e,this.options=t.mergeConfigurationOptions(r),this._createIgnoreDuplicateModulesMap(),this._createNodeModulesMap(),this._createSortedPathsRules(),""===this.options.baseUrl){if(this.options.nodeRequire&&this.options.nodeRequire.main&&this.options.nodeRequire.main.filename&&this._env.isNode){ 13 | var n=this.options.nodeRequire.main.filename,o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\"));this.options.baseUrl=n.substring(0,o+1)}if(this.options.nodeMain&&this._env.isNode){var n=this.options.nodeMain,o=Math.max(n.lastIndexOf("/"),n.lastIndexOf("\\"));this.options.baseUrl=n.substring(0,o+1)}}}return r.prototype._createIgnoreDuplicateModulesMap=function(){this.ignoreDuplicateModulesMap={};for(var e=0;e=0){var n=t.resolveModule(e.substr(0,r)),s=t.resolveModule(e.substr(r+1)),d=this._moduleIdProvider.getModuleId(n+"!"+s),a=this._moduleIdProvider.getModuleId(n);return new i(d,a,s)}return new o(this._moduleIdProvider.getModuleId(t.resolveModule(e)))},s.prototype._normalizeDependencies=function(e,t){for(var r=[],n=0,o=0,i=e.length;o0;){var a=d.shift(),u=this._modules2[a];u&&(s=u.onDependencyError(r)||s);var l=this._inverseDependencies2[a];if(l)for(var o=0,i=l.length;o0;){var d=s.shift().dependencies;if(d)for(var o=0,i=d.length;o=n.length)t._onLoadError(e,r);else{var s=n[o],d=t.getRecorder();if(t._config.isBuild()&&"empty:"===s)return t._buildInfoPath[e]=s,t.defineModule(t._moduleIdProvider.getStrModuleId(e),[],null,null,null),void t._onLoad(e);d.record(10,s),t._scriptLoader.load(t,s,function(){t._config.isBuild()&&(t._buildInfoPath[e]=s),d.record(11,s),t._onLoad(e)},function(e){d.record(12,s),i(e)})}};i(null)}},s.prototype._loadPluginDependency=function(e,r){var n=this 31 | ;if(!this._modules2[r.id]&&!this._knownModules2[r.id]){this._knownModules2[r.id]=!0;var o=function(e){n.defineModule(n._moduleIdProvider.getStrModuleId(r.id),[],e,null,null)};o.error=function(e){n._config.onError(n._createLoadError(r.id,e))},e.load(r.pluginParam,this._createRequire(t.ROOT),o,this._config.getOptionsLiteral())}},s.prototype._resolve=function(e){for(var t=this,r=e.dependencies,n=0,s=r.length;n \n")),e.unresolvedDependenciesCount-- 32 | }else if(this._inverseDependencies2[d.id]=this._inverseDependencies2[d.id]||[],this._inverseDependencies2[d.id].push(e.id),d instanceof i){var l=this._modules2[d.pluginId];if(l&&l.isComplete()){this._loadPluginDependency(l.exports,d);continue}var c=this._inversePluginDependencies2.get(d.pluginId);c||(c=[],this._inversePluginDependencies2.set(d.pluginId,c)),c.push(d),this._loadModule(d.pluginId)}else this._loadModule(d.id)}else e.unresolvedDependenciesCount--;else e.unresolvedDependenciesCount--;else e.exportsPassedIn=!0,e.unresolvedDependenciesCount--}0===e.unresolvedDependenciesCount&&this._onModuleComplete(e)},s.prototype._onModuleComplete=function(e){var t=this,r=this.getRecorder();if(!e.isComplete()){for(var n=e.dependencies,i=[],s=0,d=n.length;s console.log` because `log` has been completed recently.","Select suggestions based on previous prefixes that have completed those suggestions, e.g. `co -> console` and `con -> const`.","Controls how suggestions are pre-selected when showing the suggest list.","Font size for the suggest widget.","Line height for the suggest widget.","Controls whether filtering and sorting suggestions accounts for small typos.","Control whether an active snippet prevents quick suggestions.","Controls whether the editor should highlight matches similar to the selection","Controls whether the editor should highlight semantic symbol occurrences.","Controls the number of decorations that can show up at the same position in the overview ruler.","Controls whether a border should be drawn around the overview ruler.","Control the cursor animation style.","Zoom the font of the editor when using mouse wheel and holding `Ctrl`.","Controls the cursor style.","Controls the width of the cursor when `#editor.cursorStyle#` is set to `line`.","Enables/Disables font ligatures.","Controls whether the cursor should be hidden in the overview ruler.","Render whitespace characters except for single spaces between words.","Controls how the editor should render whitespace characters.","Controls whether the editor should render control characters.","Controls whether the editor should render indent guides.","Controls whether the editor should highlight the active indent guide.","Highlights both the gutter and the current line.","Controls how the editor should render the current line highlight.","Controls whether the editor shows CodeLens","Controls whether the editor has code folding enabled","Controls the strategy for computing folding ranges. `auto` uses a language specific folding strategy, if available. `indentation` uses the indentation based folding strategy.","Controls whether the fold controls on the gutter are automatically hidden.","Highlight matching brackets when one of them is selected.","Controls whether the editor should render the vertical glyph margin. Glyph margin is mostly used for debugging.","Inserting and deleting whitespace follows tab stops.","Remove trailing auto inserted whitespace.","Keep peek editors open even when double clicking their content or when hitting `Escape`.","Controls whether the editor should allow moving selections via drag and drop.","The editor will use platform APIs to detect when a Screen Reader is attached.","The editor will be permanently optimized for usage with a Screen Reader.","The editor will never be optimized for usage with a Screen Reader.","Controls whether the editor should run in a mode where it is optimized for screen readers.","Controls fading out of unused code.","Controls whether the editor should detect links and make them clickable.","Controls whether the editor should render the inline color decorators and color picker.","Enables the code action lightbulb in the editor.","Controls whether organize imports action should be run on file save.","Code action kinds to be run on save.","Timeout in milliseconds after which the code actions that are run on save are cancelled.","Controls whether the Linux primary clipboard should be supported.","Controls whether the diff editor shows the diff side by side or inline.","Controls whether the diff editor shows changes in leading or trailing whitespace as diffs.","Special handling for large files to disable certain memory intensive features.","Controls whether the diff editor shows +/- indicators for added/removed changes."], 10 | "vs/editor/common/config/editorOptions":["The editor is not accessible at this time. Press Alt+F1 for options.","Editor content"],"vs/editor/common/controller/cursor":["Unexpected exception while executing command."],"vs/editor/common/modes/modesRegistry":["Plain Text"],"vs/editor/common/services/modelServiceImpl":["[{0}]\n{1}","[{0}] {1}"], 11 | "vs/editor/common/view/editorColorRegistry":["Background color for the highlight of line at the cursor position.","Background color for the border around the line at the cursor position.","Background color of highlighted ranges, like by quick open and find features. The color must not be opaque to not hide underlying decorations.","Background color of the border around highlighted ranges.","Color of the editor cursor.","The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.","Color of whitespace characters in the editor.","Color of the editor indentation guides.","Color of the active editor indentation guides.","Color of editor line numbers.","Color of editor active line number","Id is deprecated. Use 'editorLineNumber.activeForeground' instead.","Color of editor active line number","Color of the editor rulers.","Foreground color of editor code lenses","Background color behind matching brackets","Color for matching brackets boxes","Color of the overview ruler border.","Background color of the editor gutter. The gutter contains the glyph margins and the line numbers.","Foreground color of error squigglies in the editor.","Border color of error squigglies in the editor.","Foreground color of warning squigglies in the editor.","Border color of warning squigglies in the editor.","Foreground color of info squigglies in the editor.","Border color of info squigglies in the editor.","Foreground color of hint squigglies in the editor.","Border color of hint squigglies in the editor.","Border of unnecessary code in the editor.","Opacity of unnecessary code in the editor.","Overview ruler marker color for errors.","Overview ruler marker color for warnings.","Overview ruler marker color for infos."], 12 | "vs/editor/contrib/bracketMatching/bracketMatching":["Overview ruler marker color for matching brackets.","Go to Bracket","Select to Bracket"],"vs/editor/contrib/caretOperations/caretOperations":["Move Caret Left","Move Caret Right"],"vs/editor/contrib/caretOperations/transpose":["Transpose Letters"],"vs/editor/contrib/clipboard/clipboard":["Cut","Cu&&t","Copy","&&Copy","Paste","&&Paste","Copy With Syntax Highlighting"],"vs/editor/contrib/codeAction/codeActionCommands":["Show Fixes ({0})","Show Fixes","Quick Fix...","No code actions available","No code actions available","Refactor...","No refactorings available","Source Action...","No source actions available","Organize Imports","No organize imports action available"],"vs/editor/contrib/comment/comment":["Toggle Line Comment","&&Toggle Line Comment","Add Line Comment","Remove Line Comment","Toggle Block Comment","Toggle &&Block Comment"],"vs/editor/contrib/contextmenu/contextmenu":["Show Editor Context Menu"], 13 | "vs/editor/contrib/cursorUndo/cursorUndo":["Soft Undo"],"vs/editor/contrib/find/findController":["Find","&&Find","Find With Selection","Find Next","Find Previous","Find Next Selection","Find Previous Selection","Replace","&&Replace"],"vs/editor/contrib/find/findWidget":["Find","Find","Previous match","Next match","Find in selection","Close","Replace","Replace","Replace","Replace All","Toggle Replace mode","Only the first {0} results are highlighted, but all find operations work on the entire text.","{0} of {1}","No Results"],"vs/editor/contrib/folding/folding":["Unfold","Unfold Recursively","Fold","Fold Recursively","Fold All Block Comments","Fold All Regions","Unfold All Regions","Fold All","Unfold All","Fold Level {0}"],"vs/editor/contrib/fontZoom/fontZoom":["Editor Font Zoom In","Editor Font Zoom Out","Editor Font Zoom Reset"], 14 | "vs/editor/contrib/format/formatActions":["Made 1 formatting edit on line {0}","Made {0} formatting edits on line {1}","Made 1 formatting edit between lines {0} and {1}","Made {0} formatting edits between lines {1} and {2}","There is no formatter for '{0}'-files installed.","Format Document","There is no document formatter for '{0}'-files installed.","Format Selection","There is no selection formatter for '{0}'-files installed."],"vs/editor/contrib/goToDefinition/goToDefinitionCommands":["No definition found for '{0}'","No definition found"," – {0} definitions","Go to Definition","Open Definition to the Side","Peek Definition","No implementation found for '{0}'","No implementation found"," – {0} implementations","Go to Implementation","Peek Implementation","No type definition found for '{0}'","No type definition found"," – {0} type definitions","Go to Type Definition","Peek Type Definition"],"vs/editor/contrib/goToDefinition/goToDefinitionMouse":["Click to show {0} definitions."], 15 | "vs/editor/contrib/gotoError/gotoError":["Go to Next Problem (Error, Warning, Info)","Go to Previous Problem (Error, Warning, Info)","Go to Next Problem in Files (Error, Warning, Info)","Go to Previous Problem in Files (Error, Warning, Info)"],"vs/editor/contrib/gotoError/gotoErrorWidget":["({0}/{1})","Editor marker navigation widget error color.","Editor marker navigation widget warning color.","Editor marker navigation widget info color.","Editor marker navigation widget background."],"vs/editor/contrib/hover/hover":["Show Hover"],"vs/editor/contrib/hover/modesContentHover":["Loading..."],"vs/editor/contrib/inPlaceReplace/inPlaceReplace":["Replace with Previous Value","Replace with Next Value"], 16 | "vs/editor/contrib/linesOperations/linesOperations":["Copy Line Up","&&Copy Line Up","Copy Line Down","Co&&py Line Down","Move Line Up","Mo&&ve Line Up","Move Line Down","Move &&Line Down","Sort Lines Ascending","Sort Lines Descending","Trim Trailing Whitespace","Delete Line","Indent Line","Outdent Line","Insert Line Above","Insert Line Below","Delete All Left","Delete All Right","Join Lines","Transpose characters around the cursor","Transform to Uppercase","Transform to Lowercase"],"vs/editor/contrib/links/links":["Cmd + click to follow link","Ctrl + click to follow link","Cmd + click to execute command","Ctrl + click to execute command","Option + click to follow link","Alt + click to follow link","Option + click to execute command","Alt + click to execute command","Failed to open this link because it is not well-formed: {0}","Failed to open this link because its target is missing.","Open Link"],"vs/editor/contrib/message/messageController":["Cannot edit in read-only editor"], 17 | "vs/editor/contrib/multicursor/multicursor":["Add Cursor Above","&&Add Cursor Above","Add Cursor Below","A&&dd Cursor Below","Add Cursors to Line Ends","Add C&&ursors to Line Ends","Add Selection To Next Find Match","Add &&Next Occurrence","Add Selection To Previous Find Match","Add P&&revious Occurrence","Move Last Selection To Next Find Match","Move Last Selection To Previous Find Match","Select All Occurrences of Find Match","Select All &&Occurrences","Change All Occurrences"],"vs/editor/contrib/parameterHints/parameterHints":["Trigger Parameter Hints"],"vs/editor/contrib/parameterHints/parameterHintsWidget":["{0}, hint"],"vs/editor/contrib/referenceSearch/peekViewWidget":["Close"],"vs/editor/contrib/referenceSearch/referenceSearch":[" – {0} references","Find All References"],"vs/editor/contrib/referenceSearch/referencesController":["Loading..."], 18 | "vs/editor/contrib/referenceSearch/referencesModel":["symbol in {0} on line {1} at column {2}","1 symbol in {0}, full path {1}","{0} symbols in {1}, full path {2}","No results found","Found 1 symbol in {0}","Found {0} symbols in {1}","Found {0} symbols in {1} files"], 19 | "vs/editor/contrib/referenceSearch/referencesWidget":["Failed to resolve file.","{0} references","{0} reference","no preview available","References","No results","References","Background color of the peek view title area.","Color of the peek view title.","Color of the peek view title info.","Color of the peek view borders and arrow.","Background color of the peek view result list.","Foreground color for line nodes in the peek view result list.","Foreground color for file nodes in the peek view result list.","Background color of the selected entry in the peek view result list.","Foreground color of the selected entry in the peek view result list.","Background color of the peek view editor.","Background color of the gutter in the peek view editor.","Match highlight color in the peek view result list.","Match highlight color in the peek view editor.","Match highlight border in the peek view editor."], 20 | "vs/editor/contrib/rename/rename":["No result.","Successfully renamed '{0}' to '{1}'. Summary: {2}","Rename failed to execute.","Rename Symbol"],"vs/editor/contrib/rename/renameInputField":["Rename input. Type new name and press Enter to commit."],"vs/editor/contrib/smartSelect/smartSelect":["Expand Select","&&Expand Selection","Shrink Select","&&Shrink Selection"],"vs/editor/contrib/snippet/snippetVariables":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sun","Mon","Tue","Wed","Thu","Fri","Sat","January","February","March","April","May","June","July","August","September","October","November","December","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"vs/editor/contrib/suggest/suggestController":["Accepting '{0}' did insert the following text: {1}","Trigger Suggest"], 21 | "vs/editor/contrib/suggest/suggestWidget":["Background color of the suggest widget.","Border color of the suggest widget.","Foreground color of the suggest widget.","Background color of the selected entry in the suggest widget.","Color of the match highlights in the suggest widget.","Read More...{0}","{0}, suggestion, has details","{0}, suggestion","Read less...{0}","Loading...","No suggestions.","{0}, accepted","{0}, suggestion, has details","{0}, suggestion"],"vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode":["Toggle Tab Key Moves Focus"], 22 | "vs/editor/contrib/wordHighlighter/wordHighlighter":["Background color of a symbol during read-access, like reading a variable. The color must not be opaque to not hide underlying decorations.","Background color of a symbol during write-access, like writing to a variable. The color must not be opaque to not hide underlying decorations.","Border color of a symbol during read-access, like reading a variable.","Border color of a symbol during write-access, like writing to a variable.","Overview ruler marker color for symbol highlights. The color must not be opaque to not hide underlying decorations.","Overview ruler marker color for write-access symbol highlights. The color must not be opaque to not hide underlying decorations.","Go to Next Symbol Highlight","Go to Previous Symbol Highlight"], 23 | "vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp":["No selection","Line {0}, Column {1} ({2} selected)","Line {0}, Column {1}","{0} selections ({1} characters selected)","{0} selections","Now changing the setting `accessibilitySupport` to 'on'.","Now opening the Editor Accessibility documentation page."," in a read-only pane of a diff editor."," in a pane of a diff editor."," in a read-only code editor"," in a code editor","To configure the editor to be optimized for usage with a Screen Reader press Command+E now.","To configure the editor to be optimized for usage with a Screen Reader press Control+E now.","The editor is configured to be optimized for usage with a Screen Reader.","The editor is configured to never be optimized for usage with a Screen Reader, which is not the case at this time.","Pressing Tab in the current editor will move focus to the next focusable element. Toggle this behavior by pressing {0}.","Pressing Tab in the current editor will move focus to the next focusable element. The command {0} is currently not triggerable by a keybinding.","Pressing Tab in the current editor will insert the tab character. Toggle this behavior by pressing {0}.","Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding.","Press Command+H now to open a browser window with more information related to editor accessibility.","Press Control+H now to open a browser window with more information related to editor accessibility.","You can dismiss this tooltip and return to the editor by pressing Escape or Shift+Escape.","Show Accessibility Help"], 24 | "vs/editor/standalone/browser/inspectTokens/inspectTokens":["Developer: Inspect Tokens"],"vs/editor/standalone/browser/quickOpen/gotoLine":["Go to line {0} and character {1}","Go to line {0}","Type a line number between 1 and {0} to navigate to","Type a character between 1 and {0} to navigate to","Go to line {0}","Type a line number, followed by an optional colon and a character number to navigate to","Go to Line..."],"vs/editor/standalone/browser/quickOpen/quickCommand":["{0}, commands","Type the name of an action you want to execute","Command Palette"],"vs/editor/standalone/browser/quickOpen/quickOutline":["{0}, symbols","Type the name of an identifier you wish to navigate to","Go to Symbol...","symbols ({0})","modules ({0})","classes ({0})","interfaces ({0})","methods ({0})","functions ({0})","properties ({0})","variables ({0})","variables ({0})","constructors ({0})","calls ({0})"],"vs/editor/standalone/browser/simpleServices":["Made {0} edits in {1} files"], 25 | "vs/editor/standalone/browser/standaloneCodeEditor":["Editor content","Press Ctrl+F1 for Accessibility Options.","Press Alt+F1 for Accessibility Options."],"vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast":["Toggle High Contrast Theme"],"vs/platform/configuration/common/configurationRegistry":["Default Configuration Overrides","Configure editor settings to be overridden for {0} language.","Configure editor settings to be overridden for a language.","Cannot register '{0}'. This matches property pattern '\\\\[.*\\\\]$' for describing language specific editor settings. Use 'configurationDefaults' contribution.","Cannot register '{0}'. This property is already registered."],"vs/platform/keybinding/common/abstractKeybindingService":["({0}) was pressed. Waiting for second key of chord...","The key combination ({0}, {1}) is not a command."], 26 | "vs/platform/list/browser/listService":["Workbench","Maps to `Control` on Windows and Linux and to `Command` on macOS.","Maps to `Alt` on Windows and Linux and to `Option` on macOS.","The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.","Controls how to open items in trees and lists using the mouse (if supported). For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ","Controls whether trees support horizontal scrolling in the workbench."],"vs/platform/markers/common/markers":["Error","Warning","Info"], 27 | "vs/platform/theme/common/colorRegistry":["Colors used in the workbench.","Overall foreground color. This color is only used if not overridden by a component.","Overall foreground color for error messages. This color is only used if not overridden by a component.","Overall border color for focused elements. This color is only used if not overridden by a component.","An extra border around elements to separate them from others for greater contrast.","An extra border around active elements to separate them from others for greater contrast.","Foreground color for links in text.","Background color for code blocks in text.","Shadow color of widgets such as find/replace inside the editor.","Input box background.","Input box foreground.","Input box border.","Border color of activated options in input fields.","Input validation background color for information severity.","Input validation border color for information severity.","Input validation background color for warning severity.","Input validation border color for warning severity.","Input validation background color for error severity.","Input validation border color for error severity.","List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.","List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.","List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.","List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.","List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.","List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.","List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.","List/Tree background when hovering over items using the mouse.","List/Tree foreground when hovering over items using the mouse.","List/Tree drag and drop background when moving items around using the mouse.","List/Tree foreground color of the match highlights when searching inside the list/tree.","Quick picker color for grouping labels.","Quick picker color for grouping borders.","Badge background color. Badges are small information labels, e.g. for search results count.","Badge foreground color. Badges are small information labels, e.g. for search results count.","Scrollbar shadow to indicate that the view is scrolled.","Scrollbar slider background color.","Scrollbar slider background color when hovering.","Scrollbar slider background color when clicked on.","Background color of the progress bar that can show for long running operations.","Editor background color.","Editor default foreground color.","Background color of editor widgets, such as find/replace.","Border color of editor widgets. The color is only used if the widget chooses to have a border and if the color is not overridden by a widget.","Border color of the resize bar of editor widgets. The color is only used if the widget chooses to have a resize border and if the color is not overridden by a widget.","Color of the editor selection.","Color of the selected text for high contrast.","Color of the selection in an inactive editor. The color must not be opaque to not hide underlying decorations.","Color for regions with the same content as the selection. The color must not be opaque to not hide underlying decorations.","Border color for regions with the same content as the selection.","Color of the current search match.","Color of the other search matches. The color must not be opaque to not hide underlying decorations.","Color of the range limiting the search. The color must not be opaque to not hide underlying decorations.","Border color of the current search match.","Border color of the other search matches.","Border color of the range limiting the search. The color must not be opaque to not hide underlying decorations.","Highlight below the word for which a hover is shown. The color must not be opaque to not hide underlying decorations.","Background color of the editor hover.","Border color of the editor hover.","Color of active links.","Background color for text that got inserted. The color must not be opaque to not hide underlying decorations.","Background color for text that got removed. The color must not be opaque to not hide underlying decorations.","Outline color for the text that got inserted.","Outline color for text that got removed.","Border color between the two text editors.","Overview ruler marker color for find matches. The color must not be opaque to not hide underlying decorations.","Overview ruler marker color for selection highlights. The color must not be opaque to not hide underlying decorations."] 28 | }); -------------------------------------------------------------------------------- /lib/monaco-editor/min/vs/language/json/jsonMode.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * monaco-json version: 2.2.0(370169f666a52e1b91623841799be4eab9204094) 4 | * Released under the MIT license 5 | * https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md 6 | *-----------------------------------------------------------------------------*/ 7 | define("vs/language/json/workerManager",["require","exports"],function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var c=monaco.Promise,n=function(){function e(e){var t=this;this._defaults=e,this._worker=null,this._idleCheckInterval=setInterval(function(){return t._checkIfIdle()},3e4),this._lastUsedTime=0,this._configChangeListener=this._defaults.onDidChange(function(){return t._stopWorker()})}return e.prototype._stopWorker=function(){this._worker&&(this._worker.dispose(),this._worker=null),this._client=null},e.prototype.dispose=function(){clearInterval(this._idleCheckInterval),this._configChangeListener.dispose(),this._stopWorker()},e.prototype._checkIfIdle=function(){this._worker&&(12e4e?r=o:n=o+1}var i=n-1;return a.create(i,e-t[i])},e.prototype.offsetAt=function(e){var t=this.getLineOffsets();if(e.line>=t.length)return this._content.length;if(e.line<0)return 0;var n=t[e.line],r=e.line+1=e.offset&&t=n.children.length)return;n=n.children[f]}}return n}},t.getNodePath=function e(t){if(!t.parent||!t.parent.children)return[];var n=e(t.parent);if("property"===t.parent.type){var r=t.parent.children[0].value;n.push(r)}else if("array"===t.parent.type){var o=t.parent.children.indexOf(t);-1!==o&&n.push(o)}return n},t.getNodeValue=function e(t){switch(t.type){case"array":return t.children.map(e);case"object":for(var n=Object.create(null),r=0,o=t.children;r