├── .npmrc ├── packages ├── lifter-main │ ├── .npmignore │ ├── typings │ │ └── global.d.ts │ ├── src │ │ ├── domains │ │ │ ├── proxy │ │ │ │ ├── project │ │ │ │ │ ├── project-identity.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ │ ├── project-name.ts │ │ │ │ │ │ └── project-base-dir.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ ├── project-repositoty.ts │ │ │ │ │ │ └── project-factory.ts │ │ │ │ │ └── project-entity.ts │ │ │ │ ├── rewrite-rule │ │ │ │ │ ├── rewrite-rule-identity.ts │ │ │ │ │ ├── rewrite-rule-modifier │ │ │ │ │ │ ├── rewrite-rule-modifier-identity.ts │ │ │ │ │ │ ├── value-objects │ │ │ │ │ │ │ ├── rewrite-rule-modifier-value.ts │ │ │ │ │ │ │ └── rewrite-rule-modifier-header.ts │ │ │ │ │ │ ├── rewrite-rule-modifier-entity.ts │ │ │ │ │ │ ├── rewrite-rule-delete-modifier-entity.ts │ │ │ │ │ │ └── rewrite-rule-update-modifier-entity.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ │ ├── rewrite-rule-url-pattern.ts │ │ │ │ │ │ └── rewrite-rule-modifier-map.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ └── rewrite-rule-repository.ts │ │ │ │ │ └── rewrite-rule-entity.ts │ │ │ │ ├── auto-responder │ │ │ │ │ ├── auto-responder-identity.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ │ ├── auto-responder-file-path.ts │ │ │ │ │ │ ├── auto-responder-pattern.ts │ │ │ │ │ │ ├── auto-responder-any-path.ts │ │ │ │ │ │ ├── auto-responder-any-path.spec.ts │ │ │ │ │ │ └── auto-responder-pattern.spec.ts │ │ │ │ │ ├── lib │ │ │ │ │ │ └── get-match-path-code-string.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ ├── auto-responder-factory.spec.ts │ │ │ │ │ │ ├── auto-responder-repositoty.ts │ │ │ │ │ │ └── auto-responder-factory.ts │ │ │ │ │ ├── specs │ │ │ │ │ │ ├── find-match-entry.ts │ │ │ │ │ │ └── find-match-entry.spec.ts │ │ │ │ │ ├── auto-responder-entity.spec.ts │ │ │ │ │ ├── auto-responder-entity.ts │ │ │ │ │ └── auto-responder-service.spec.ts │ │ │ │ ├── client-request │ │ │ │ │ ├── client-request-identity.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ ├── client-request-repository.ts │ │ │ │ │ │ └── client-request-factory.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ │ └── client-request-url.ts │ │ │ │ │ ├── lib │ │ │ │ │ │ └── client-responder-context.ts │ │ │ │ │ ├── client-request-entity.ts │ │ │ │ │ └── client-request-service.ts │ │ │ │ └── local-file-response │ │ │ │ │ ├── local-file-response-identity.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ ├── local-file-response-path.ts │ │ │ │ │ ├── local-file-response-size.ts │ │ │ │ │ └── local-file-response-type.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ └── local-file-response-factory.ts │ │ │ │ │ └── local-file-response-entity.ts │ │ │ ├── settings │ │ │ │ ├── network-interface │ │ │ │ │ ├── network-interface-identity.ts │ │ │ │ │ ├── value-objects │ │ │ │ │ │ ├── network-interface-name.ts │ │ │ │ │ │ └── network-interface-service-name.ts │ │ │ │ │ ├── specs │ │ │ │ │ │ ├── parse-get-autoproxy-url.ts │ │ │ │ │ │ ├── parse-get-autoproxy-url.spec.ts │ │ │ │ │ │ ├── parse-getwebproxy-command.ts │ │ │ │ │ │ ├── parse-getwebproxy-command.spec.ts │ │ │ │ │ │ └── parse-network-devices.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ └── network-interface-factory.ts │ │ │ │ │ └── network-interface-service.ts │ │ │ │ ├── proxy-bypass-domain │ │ │ │ │ ├── proxy-bypass-domain-identity.ts │ │ │ │ │ ├── vaue-objects │ │ │ │ │ │ └── proxy-bypass-domain-name.ts │ │ │ │ │ ├── proxy-bypass-domain-entity.ts │ │ │ │ │ ├── lifecycle │ │ │ │ │ │ ├── proxy-bypass-domain-factory.ts │ │ │ │ │ │ └── proxy-bypass-domain-repository.ts │ │ │ │ │ ├── proxy-bypass-domain-service.spec.ts │ │ │ │ │ └── proxy-bypass-domain-service.ts │ │ │ │ ├── networksetup-proxy │ │ │ │ │ ├── networksetup-proxy-container.ts │ │ │ │ │ ├── networksetup-proxy-service.spec.ts │ │ │ │ │ └── networksetup-proxy-service.ts │ │ │ │ ├── proxy-command-grant │ │ │ │ │ └── vaue-objects │ │ │ │ │ │ └── proxy-command-grant-setting.ts │ │ │ │ ├── user-settings │ │ │ │ │ ├── user-settings-service.spec.ts │ │ │ │ │ └── user-settings-storage.ts │ │ │ │ ├── certificate │ │ │ │ │ └── certificate-service.spec.ts │ │ │ │ └── pac-file │ │ │ │ │ ├── pac-file-service.ts │ │ │ │ │ └── pac-file-service.spec.ts │ │ │ ├── base │ │ │ │ ├── value-objects │ │ │ │ │ ├── base-value-object.ts │ │ │ │ │ └── file-path.ts │ │ │ │ ├── base-entity.ts │ │ │ │ ├── async-nedb-id-generator.ts │ │ │ │ └── async-on-nedb-repository.ts │ │ │ └── libs │ │ │ │ └── resolve-all.ts │ │ ├── libs │ │ │ ├── user-keychains-path.ts │ │ │ ├── throwable-command.ts │ │ │ ├── ssl-certificate-path.ts │ │ │ └── proxy-command-path.ts │ │ ├── index.ts │ │ ├── application │ │ │ ├── libs │ │ │ │ └── fetch-ifconfig.ts │ │ │ ├── proxy │ │ │ │ └── proxy-service.ts │ │ │ ├── settings │ │ │ │ └── proxy-setting │ │ │ │ │ └── proxy-setting-service.spec.ts │ │ │ └── application.ts │ │ ├── settings.ts │ │ └── inversify.config.ts │ ├── test │ │ ├── settings.ts │ │ └── mocks │ │ │ ├── require-mocks │ │ │ ├── nedb.ts │ │ │ ├── http-mitm-proxy.ts │ │ │ ├── @lifter │ │ │ │ └── lifter-common.ts │ │ │ └── exec-commands │ │ │ │ ├── exec-commands.ts │ │ │ │ └── set-proxy-setting-state.ts │ │ │ ├── get-test-container.ts │ │ │ └── mock-state-event.ts │ ├── tsconfig.json │ ├── package.json │ └── .dependency-cruiser.json ├── lifter-cli │ ├── .npmignore │ ├── package.json │ └── index.js ├── electron-window-manager │ ├── index.d.ts │ ├── package.json │ ├── LICENSE │ ├── loadFailure.html │ └── yarn.lock ├── networksetup-proxy │ ├── .gitignore │ ├── example │ │ ├── run.sh │ │ ├── postexample.js │ │ ├── package.json │ │ └── index.js │ ├── global.d.ts │ ├── .npmignore │ ├── rust │ │ ├── proxy-setting │ │ └── proxy-setting.rs │ ├── tsconfig.json │ ├── test.js │ └── package.json ├── lifter-app │ ├── .npmignore │ ├── mocks │ │ ├── electron-context-menu.js │ │ ├── electron.js │ │ └── electron-ipc.js │ ├── typings │ │ ├── electron-unhandled │ │ │ └── electron-unhandled.d.ts │ │ ├── electron-context-menu │ │ │ └── electron-context-menu.d.ts │ │ ├── electron-load-devtool │ │ │ └── electron-load-devtool.d.ts │ │ ├── electron-window-manager │ │ │ └── electron-window-manager.d.ts │ │ ├── vue-shims │ │ │ └── vue-shims.d.ts │ │ ├── electron-ipc │ │ │ └── electron-ipc.d.ts │ │ └── global.d.ts │ ├── README.md │ ├── test │ │ ├── tsconfig.json │ │ ├── mocks │ │ │ ├── electron.ts │ │ │ └── electron-ipc.ts │ │ └── setup.js │ ├── src │ │ ├── renderer │ │ │ ├── store │ │ │ │ └── modules │ │ │ │ │ ├── get-file-drop-dialog-module.ts │ │ │ │ │ ├── get-setting-dialog-module.ts │ │ │ │ │ ├── mixins │ │ │ │ │ └── dialog-mixin.ts │ │ │ │ │ ├── get-client-request-module.ts │ │ │ │ │ ├── get-rewrite-rule-modifiers-dialog-module.ts │ │ │ │ │ ├── get-header-tab-module.ts │ │ │ │ │ ├── get-proxy-bypass-domain-module.ts │ │ │ │ │ └── get-auto-responder-module.ts │ │ │ ├── components │ │ │ │ ├── li-main │ │ │ │ │ ├── proxy-bypass-domain.vue │ │ │ │ │ ├── li-main.vue │ │ │ │ │ ├── connection.vue │ │ │ │ │ ├── auto-responder.vue │ │ │ │ │ └── rewrite-rule.vue │ │ │ │ ├── li-header │ │ │ │ │ ├── divider.vue │ │ │ │ │ ├── li-header.vue │ │ │ │ │ ├── right-toolbar.vue │ │ │ │ │ ├── tab-contents.vue │ │ │ │ │ └── left-toolbar.vue │ │ │ │ ├── li-dialog │ │ │ │ │ ├── li-dialog.vue │ │ │ │ │ ├── file-drop-dialog.vue │ │ │ │ │ └── rewrite-rule-modifiers-dialog │ │ │ │ │ │ ├── rewrite-rule-modifiers-dialog.vue │ │ │ │ │ │ ├── delete-modifiers-table.vue │ │ │ │ │ │ └── update-modifiers-table.vue │ │ │ │ ├── app.vue │ │ │ │ ├── app.mock.ts │ │ │ │ ├── index.ts │ │ │ │ └── mixins │ │ │ │ │ ├── rewrite-rule-modifiers-mixin.ts │ │ │ │ │ └── table-handler-mixin.ts │ │ │ ├── index.css │ │ │ ├── index.ts │ │ │ ├── messages.spec.ts │ │ │ ├── application │ │ │ │ ├── libs │ │ │ │ │ └── get-window-manager.ts │ │ │ │ ├── context-menu │ │ │ │ │ └── context-menu-service.ts │ │ │ │ └── application.mock.ts │ │ │ └── messages.ts │ │ ├── settings.ts │ │ └── main │ │ │ ├── index.ts │ │ │ ├── window-manager.ts │ │ │ └── application-main-state.ts │ ├── config │ │ ├── electron-builder.yml │ │ ├── webpack.renderer.dev.js │ │ └── webpack.renderer.build.js │ ├── tsconfig.json │ ├── storybook │ │ ├── config.js │ │ └── webpack.config.js │ └── stories │ │ └── index.stories.js ├── lifter-common │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts └── file-watcher │ ├── yarn.lock │ ├── package.json │ ├── index.js │ └── test.js ├── .prettierignore ├── resources └── screenshot-1.png ├── lerna.json ├── .travis.yml ├── .gitignore ├── tsconfig-base.json ├── README.md └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | progress=false 2 | -------------------------------------------------------------------------------- /packages/lifter-main/.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | -------------------------------------------------------------------------------- /packages/lifter-cli/.npmignore: -------------------------------------------------------------------------------- 1 | http-mitm-proxy/ 2 | repositories/ 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | dist/ 4 | package.json 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /packages/electron-window-manager/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@lifter/electron-window-manager"; 2 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/.gitignore: -------------------------------------------------------------------------------- 1 | /index.d.ts 2 | /index.js 3 | .idea 4 | node_modules/ 5 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/example/run.sh: -------------------------------------------------------------------------------- 1 | cd example 2 | yarn install 3 | yarn run example 4 | -------------------------------------------------------------------------------- /packages/lifter-app/.npmignore: -------------------------------------------------------------------------------- 1 | config/ 2 | dist/ 3 | mocks/ 4 | stories/ 5 | storybook/ 6 | test/ 7 | -------------------------------------------------------------------------------- /packages/lifter-app/mocks/electron-context-menu.js: -------------------------------------------------------------------------------- 1 | export default function electronContextMenu() {} 2 | -------------------------------------------------------------------------------- /packages/lifter-app/mocks/electron.js: -------------------------------------------------------------------------------- 1 | export const remote = { 2 | require: () => {}, 3 | }; 4 | -------------------------------------------------------------------------------- /resources/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyo-ago/lifter/HEAD/resources/screenshot-1.png -------------------------------------------------------------------------------- /packages/lifter-app/typings/electron-unhandled/electron-unhandled.d.ts: -------------------------------------------------------------------------------- 1 | declare module "electron-unhandled"; 2 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "sudo-prompt"; 2 | declare module "@lifter/file-watcher"; 3 | -------------------------------------------------------------------------------- /packages/lifter-app/typings/electron-context-menu/electron-context-menu.d.ts: -------------------------------------------------------------------------------- 1 | declare module "electron-context-menu"; 2 | -------------------------------------------------------------------------------- /packages/lifter-app/typings/electron-load-devtool/electron-load-devtool.d.ts: -------------------------------------------------------------------------------- 1 | declare module "electron-load-devtool"; 2 | -------------------------------------------------------------------------------- /packages/lifter-app/typings/electron-window-manager/electron-window-manager.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@lifter/electron-window-manager"; 2 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | rust/proxy-setting.rs 3 | index.ts 4 | tsconfig.json 5 | package-lock.json 6 | test.js 7 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/rust/proxy-setting: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyo-ago/lifter/HEAD/packages/networksetup-proxy/rust/proxy-setting -------------------------------------------------------------------------------- /packages/lifter-app/typings/vue-shims/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /packages/lifter-main/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Ifconfig { 2 | [name: string]: { 3 | status?: "inactive" | "active"; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /packages/lifter-app/README.md: -------------------------------------------------------------------------------- 1 | # lifter 2 | 3 | [![Build Status](https://travis-ci.org/kyo-ago/lifter.svg?branch=master)](https://travis-ci.org/kyo-ago/lifter) 4 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | }, 4 | "extends": "../../tsconfig-base.json", 5 | "include": [ 6 | "./index.ts" 7 | ] 8 | } -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/project-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class ProjectIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.5.1", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "version": "0.2.6", 7 | "gitVersionPrefix": "v", 8 | "npmClient": "yarn" 9 | } 10 | -------------------------------------------------------------------------------- /packages/lifter-app/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "../" 4 | }, 5 | "extends": "../tsconfig.json", 6 | "include": [ 7 | "../src/**/*" 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class RewriteRuleIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/auto-responder-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class AutoResponderIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/client-request-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class ClientRequestIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/test/settings.ts: -------------------------------------------------------------------------------- 1 | export const TEST_REPOSITORY_BASE_DIR_PATH = "testRepositories"; 2 | export const TEST_USER_DATA_PATH = "."; 3 | export const TEST_USER_HOME_PATH = process.env.HOME; 4 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-file-drop-dialog-module.ts: -------------------------------------------------------------------------------- 1 | import { DialogMixin } from "./mixins/dialog-mixin"; 2 | 3 | export function getFileDropDialogModule() { 4 | return DialogMixin(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-setting-dialog-module.ts: -------------------------------------------------------------------------------- 1 | import { DialogMixin } from "./mixins/dialog-mixin"; 2 | 3 | export function getSettingDialogModule() { 4 | return DialogMixin(); 5 | } 6 | -------------------------------------------------------------------------------- /packages/lifter-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/" 4 | }, 5 | "extends": "../../tsconfig-base.json", 6 | "include": [ 7 | "./src/index.ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/local-file-response-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class LocalFilResponseIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/network-interface-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class NetworkInterfaceIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/proxy-bypass-domain-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class ProxyBypassDomainIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/value-objects/auto-responder-file-path.ts: -------------------------------------------------------------------------------- 1 | import { FilePath } from "../../../base/value-objects/file-path"; 2 | 3 | export class AutoResponderFilePath extends FilePath {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/value-objects/project-name.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 2 | 3 | export class ProjectName extends BaseValueObject {} 4 | -------------------------------------------------------------------------------- /packages/lifter-app/mocks/electron-ipc.js: -------------------------------------------------------------------------------- 1 | export const subscribe = () => undefined; 2 | export const publish = () => Promise.resolve(""); 3 | export const addWindow = () => undefined; 4 | export const removeWindow = () => undefined; 5 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/base/value-objects/base-value-object.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseValueObject { 2 | constructor(protected _value: T) {} 3 | 4 | get value() { 5 | return this._value; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/value-objects/local-file-response-path.ts: -------------------------------------------------------------------------------- 1 | import { FilePath } from "../../../base/value-objects/file-path"; 2 | 3 | export class LocalFileResponsePath extends FilePath {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/value-objects/project-base-dir.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 2 | 3 | export class ProjectBaseDir extends BaseValueObject {} 4 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/example/postexample.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const p = fs.readFileSync("./package.json"); 3 | fs.writeFileSync( 4 | "./package.json", 5 | String(p).replace(/"file:.+?"/, '"file:.."'), 6 | ); 7 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/rewrite-rule-modifier-identity.ts: -------------------------------------------------------------------------------- 1 | import { NumberIdentity } from "typescript-dddbase"; 2 | 3 | export class RewriteRuleModifierIdentity extends NumberIdentity {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/value-objects/local-file-response-size.ts: -------------------------------------------------------------------------------- 1 | export class LocalFileResponseSize { 2 | constructor(private _value: number) {} 3 | 4 | get value() { 5 | return this._value; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/value-objects/local-file-response-type.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 2 | 3 | export class LocalFileResponseType extends BaseValueObject {} 4 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/vaue-objects/proxy-bypass-domain-name.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 2 | 3 | export class ProxyBypassDomainName extends BaseValueObject {} 4 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-main/proxy-bypass-domain.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/base/base-entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Identity } from "typescript-dddbase"; 2 | 3 | export abstract class BaseEntity> extends Entity { 4 | get id() { 5 | return this.getIdentity().getValue(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/value-objects/rewrite-rule-modifier-value.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../../base/value-objects/base-value-object"; 2 | 3 | export class RewriteRuleModifierValue extends BaseValueObject {} 4 | -------------------------------------------------------------------------------- /packages/file-watcher/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | throttle-debounce@^1.0.1: 6 | version "1.0.1" 7 | resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-1.0.1.tgz#dad0fe130f9daf3719fdea33dc36a8e6ba7f30b5" 8 | -------------------------------------------------------------------------------- /packages/lifter-app/test/mocks/electron.ts: -------------------------------------------------------------------------------- 1 | import * as mockRequire from "mock-require"; 2 | import * as sinon from "sinon"; 3 | 4 | let sandbox = sinon.createSandbox(); 5 | 6 | mockRequire("electron", sandbox.stub(require("../../mocks/electron"))); 7 | 8 | afterEach(() => { 9 | sandbox.resetHistory(); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | min-width: 0; 8 | min-height: 0; 9 | font-family: ".SFNSDisplay-Regular", "Helvetica Neue", "Lucida Grande", 10 | sans-serif; 11 | font-size: 12px; 12 | tab-size: 4; 13 | } 14 | -------------------------------------------------------------------------------- /packages/lifter-app/test/mocks/electron-ipc.ts: -------------------------------------------------------------------------------- 1 | import * as mockRequire from "mock-require"; 2 | import * as sinon from "sinon"; 3 | 4 | let sandbox = sinon.createSandbox(); 5 | 6 | mockRequire("electron-ipc", sandbox.stub(require("../../mocks/electron-ipc"))); 7 | 8 | afterEach(() => { 9 | sandbox.resetHistory(); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/lifter-app/config/electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: com.electron.lifter 2 | productName: Lifter Proxy 3 | 4 | directories: 5 | output: dist 6 | 7 | publish: 8 | provider: github 9 | owner: kyo-ago 10 | repo: lifter 11 | 12 | mac: 13 | target: 14 | - zip 15 | - dmg 16 | category: public.app-category.developer-tools 17 | -------------------------------------------------------------------------------- /packages/lifter-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // fail in rxjs 4 | "strict": false, 5 | "module": "esnext", 6 | "declaration": false, 7 | "rootDir": "./src/", 8 | "lib": ["dom", "es2017", "es2017.object"] 9 | }, 10 | "extends": "../../tsconfig-base.json", 11 | "include": [ 12 | "src/**/*" 13 | ] 14 | } -------------------------------------------------------------------------------- /packages/lifter-main/src/libs/user-keychains-path.ts: -------------------------------------------------------------------------------- 1 | import * as Path from "path"; 2 | import { BaseValueObject } from "../domains/base/value-objects/base-value-object"; 3 | 4 | export class UserKeychainsPath extends BaseValueObject { 5 | getPath() { 6 | return Path.join(this.value, "/Library/Keychains/login.keychain-db"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/value-objects/rewrite-rule-modifier-header.ts: -------------------------------------------------------------------------------- 1 | import { BaseValueObject } from "../../../../base/value-objects/base-value-object"; 2 | 3 | export class RewriteRuleModifierHeader extends BaseValueObject { 4 | constructor(value: string) { 5 | super(value.toLowerCase()); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/value-objects/network-interface-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the return value of the "ifconfig -l" command. 3 | * 4 | * ex. en0, en1, lpss-serial2 5 | */ 6 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 7 | 8 | export class NetworkInterfaceName extends BaseValueObject {} 9 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/libs/resolve-all.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Identity } from "typescript-dddbase"; 2 | 3 | export function ResolveAll>>(entities: { 4 | [key: string]: E; 5 | }): E[] { 6 | return Object.keys(entities) 7 | .map(key => Number(key)) 8 | .sort((a, b) => a - b) 9 | .map(key => entities[String(key)]); 10 | } 11 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/value-objects/network-interface-service-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the return value of the "networksetup - listNetworkserviceorder" command. 3 | * 4 | * ex. Wi-Fi 5 | */ 6 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 7 | 8 | export class NetworkInterfaceServiceName extends BaseValueObject {} 9 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/require-mocks/nedb.ts: -------------------------------------------------------------------------------- 1 | import * as mockRequire from "mock-require"; 2 | import * as Datastore from "nedb"; 3 | 4 | /** 5 | * This is constructor mock. 6 | * require Function object(not allow function) 7 | */ 8 | mockRequire("nedb", function(option: Datastore.DataStoreOptions) { 9 | option.inMemoryOnly = true; 10 | return new Datastore(option); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/file-watcher/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/file-watcher", 3 | "description": "file watcher", 4 | "version": "0.2.2", 5 | "author": "@kyo_ago", 6 | "dependencies": { 7 | "throttle-debounce": "^1.0.1" 8 | }, 9 | "license": "GPL-3.0", 10 | "main": "index.js", 11 | "scripts": { 12 | "build": "true", 13 | "clean": "true", 14 | "test": "mocha" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-header/divider.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /packages/lifter-common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/lifter-common", 3 | "description": "lifter proxy common modules", 4 | "version": "0.2.6", 5 | "author": "@kyo_ago", 6 | "license": "GPL-3.0", 7 | "main": "./build/index.js", 8 | "scripts": { 9 | "build": "tsc -p .", 10 | "clean": "rm -fr build", 11 | "test": "tsc -p ." 12 | }, 13 | "types": "./build/index.d.ts" 14 | } 15 | -------------------------------------------------------------------------------- /packages/lifter-main/src/libs/throwable-command.ts: -------------------------------------------------------------------------------- 1 | export interface IOResult { 2 | stdout: string; 3 | stderr: string; 4 | } 5 | 6 | export async function throwableCommand( 7 | promisedCommand: Promise, 8 | ): Promise { 9 | let { stdout, stderr } = await promisedCommand; 10 | if (stderr) { 11 | throw new Error(stderr); 12 | } 13 | return stdout; 14 | } 15 | -------------------------------------------------------------------------------- /packages/lifter-app/typings/electron-ipc/electron-ipc.d.ts: -------------------------------------------------------------------------------- 1 | declare module "electron-ipc" { 2 | const ipc: { 3 | subscribe( 4 | key: string, 5 | callback: (event: any, message: any) => Promise | void, 6 | ): void; 7 | publish: (key: string, message?: any) => Promise; 8 | addWindow(window: any): void; 9 | removeWindow(window: any): void; 10 | }; 11 | export = ipc; 12 | } 13 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/require-mocks/http-mitm-proxy.ts: -------------------------------------------------------------------------------- 1 | import * as mockRequire from "mock-require"; 2 | import * as sinon from "sinon"; 3 | 4 | let sandbox = sinon.createSandbox(); 5 | 6 | let stub = sandbox.stub({ 7 | onError: () => undefined, 8 | onRequest: () => undefined, 9 | listen: () => undefined, 10 | }); 11 | mockRequire("http-mitm-proxy", () => stub); 12 | 13 | afterEach(() => { 14 | sandbox.resetHistory(); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/lifter-app/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "example": "node index.js", 8 | "postexample": "node postexample.js" 9 | }, 10 | "author": "@kyo_ago", 11 | "license": "GPL-3.0", 12 | "dependencies": { 13 | "mock-require": "^2.0.2", 14 | "networksetup-proxy": "file:..", 15 | "sinon": "^2.1.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/value-objects/rewrite-rule-url-pattern.ts: -------------------------------------------------------------------------------- 1 | import * as micromatch from "micromatch"; 2 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 3 | 4 | export class RewriteRuleUrlPattern extends BaseValueObject { 5 | isMatchUrl(pathSearch: string): boolean { 6 | return micromatch.isMatch(pathSearch, this.value, { 7 | matchBase: true, 8 | }); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/example/index.js: -------------------------------------------------------------------------------- 1 | const sinon = require("sinon"); 2 | const mockRequire = require("mock-require"); 3 | mockRequire("sudo-prompt", { 4 | exec: sinon.spy(), 5 | }); 6 | const sudoPrompt = require("sudo-prompt"); 7 | const NetworksetupProxy = require("networksetup-proxy").NetworksetupProxy; 8 | const networksetupProxy = new NetworksetupProxy(); 9 | networksetupProxy.hasGrant(); 10 | networksetupProxy.grant(); 11 | // console.log(sudoPrompt.exec); 12 | -------------------------------------------------------------------------------- /packages/lifter-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/lifter-cli", 3 | "description": "lifter proxy cli client", 4 | "version": "0.2.6", 5 | "author": "@kyo_ago", 6 | "dependencies": { 7 | "@lifter/lifter-main": "^0.2.6", 8 | "inquirer": "^5.2.0" 9 | }, 10 | "license": "GPL-3.0", 11 | "main": "index.js", 12 | "scripts": { 13 | "build": "true", 14 | "clean": "rm -fr repositories http-mitm-proxy", 15 | "cli": "node index.js", 16 | "test": "true" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/lifter-main/src/libs/ssl-certificate-path.ts: -------------------------------------------------------------------------------- 1 | import * as Path from "path"; 2 | import { BaseValueObject } from "../domains/base/value-objects/base-value-object"; 3 | import { HTTP_SSL_CA_DIR_NAME } from "../settings"; 4 | 5 | export class SslCertificatePath extends BaseValueObject { 6 | getCaPath() { 7 | return Path.join(this.getCaDir(), "certs", "ca.pem"); 8 | } 9 | 10 | getCaDir() { 11 | return Path.join(this.value, HTTP_SSL_CA_DIR_NAME); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/lifter-app/test/setup.js: -------------------------------------------------------------------------------- 1 | require("jsdom-global")(); 2 | 3 | // for element-dialog 4 | let style = document.createElement("style"); 5 | style.type = "text/css"; 6 | style.appendChild( 7 | document.createTextNode(` 8 | .el-dialog__wrapper { 9 | transition-delay: 0; 10 | transition-duration: 0; 11 | animation-delay: 0; 12 | animation-duration: 0; 13 | } 14 | `), 15 | ); 16 | document.head.appendChild(style); 17 | 18 | global.process.env.NODE_ENV = "test"; 19 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/get-test-container.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | import { getContainer } from "../../src/inversify.config"; 3 | import { 4 | TEST_REPOSITORY_BASE_DIR_PATH, 5 | TEST_USER_DATA_PATH, 6 | TEST_USER_HOME_PATH, 7 | } from "../settings"; 8 | 9 | export function getTestContainer(): Promise { 10 | return getContainer( 11 | TEST_REPOSITORY_BASE_DIR_PATH, 12 | TEST_USER_DATA_PATH, 13 | TEST_USER_HOME_PATH, 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/mixins/dialog-mixin.ts: -------------------------------------------------------------------------------- 1 | export function DialogMixin() { 2 | return { 3 | namespaced: true, 4 | state() { 5 | return { 6 | isShowing: false, 7 | }; 8 | }, 9 | mutations: { 10 | show(state) { 11 | state.isShowing = true; 12 | }, 13 | hide(state) { 14 | state.isShowing = false; 15 | }, 16 | }, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/lifter-main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": false, 4 | "outDir": "./build/", 5 | "rootDir": "./", 6 | "types": ["reflect-metadata"], 7 | "lib": ["es6", "dom", "es2017", "es2017.object"], 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true 10 | }, 11 | "extends": "../../tsconfig-base.json", 12 | "include": [ 13 | "src/**/*.ts", 14 | "typings/global.d.ts" 15 | ], 16 | "exclude": [ 17 | "test", 18 | "src/**/*.spec.ts" 19 | ] 20 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | osx_image: xcode9.2 3 | 4 | notifications: 5 | email: false 6 | 7 | language: node_js 8 | node_js: 9 | - "8.9.0" 10 | 11 | branches: 12 | except: 13 | - "/^v\\d+\\.\\d+\\.\\d+$/" 14 | 15 | cache: 16 | yarn: true 17 | timeout: 600 18 | directories: 19 | - node_modules 20 | - $HOME/.electron 21 | - $HOME/Library/Caches/Yarn/v1 22 | 23 | script: 24 | - yarn run bootstrap 25 | - yarn run build 26 | - yarn run test 27 | - if [[ $TRAVIS_BRANCH =~ release/.+ ]]; then yarn run publish:app; fi 28 | -------------------------------------------------------------------------------- /packages/lifter-main/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as App from "./application/application"; 2 | import { getContainer } from "./inversify.config"; 3 | 4 | export type Application = App.Application; 5 | 6 | export async function createApplication( 7 | projectBaseDir: string, 8 | userDataPath: string, 9 | userHomePath: string, 10 | ): Promise { 11 | let container = await getContainer( 12 | projectBaseDir, 13 | userDataPath, 14 | userHomePath, 15 | ); 16 | return container.get(App.Application); 17 | } 18 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/base/value-objects/file-path.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { promisify } from "util"; 3 | import { BaseValueObject } from "./base-value-object"; 4 | 5 | const promisedFsStat = promisify(fs.stat); 6 | const promisedFsReadFile = promisify(fs.readFile); 7 | 8 | export abstract class FilePath extends BaseValueObject { 9 | getState(): Promise { 10 | return promisedFsStat(this._value); 11 | } 12 | 13 | getBody(): Promise { 14 | return promisedFsReadFile(this._value); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/require-mocks/@lifter/lifter-common.ts: -------------------------------------------------------------------------------- 1 | import "mocha"; 2 | import * as mockRequire from "mock-require"; 3 | import * as sinon from "sinon"; 4 | 5 | let sandbox = sinon.createSandbox(); 6 | 7 | let stub = { 8 | ipc: sandbox.stub({ 9 | subscribe: () => {}, 10 | publish: () => {}, 11 | addWindow: () => {}, 12 | removeWindow: () => {}, 13 | }), 14 | }; 15 | 16 | mockRequire("@lifter/lifter-common", stub); 17 | export const mockLifterCommon = stub; 18 | 19 | afterEach(() => { 20 | sandbox.resetHistory(); 21 | }); 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | coverage 7 | 8 | # Dependency directory 9 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 10 | node_modules 11 | 12 | # Debug log from npm 13 | npm-debug.log 14 | yarn-debug.log 15 | 16 | ## Directory-based project format: 17 | .idea/ 18 | 19 | /packages/*/build/ 20 | /packages/*/dist/ 21 | /packages/*/lib/ 22 | /packages/*/repositories/ 23 | /packages/*/.http-mitm-proxy/ 24 | /packages/*/http-mitm-proxy/ 25 | /packages/**/package-lock.json 26 | -------------------------------------------------------------------------------- /packages/lifter-app/config/webpack.renderer.dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.vue$/, 6 | use: [ 7 | { 8 | loader: "vue-loader", 9 | options: { 10 | loaders: { 11 | i18n: "@kazupon/vue-i18n-loader", 12 | }, 13 | }, 14 | }, 15 | ], 16 | }, 17 | ], 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/rewrite-rule-modifier-entity.ts: -------------------------------------------------------------------------------- 1 | import { RewriteRuleModifierEntityJSON } from "@lifter/lifter-common"; 2 | import { OutgoingHttpHeaders } from "http"; 3 | import { BaseEntity } from "../../../base/base-entity"; 4 | import { RewriteRuleModifierIdentity } from "./rewrite-rule-modifier-identity"; 5 | 6 | export abstract class RewriteRuleModifierEntity extends BaseEntity< 7 | RewriteRuleModifierIdentity 8 | > { 9 | abstract get json(): RewriteRuleModifierEntityJSON; 10 | abstract applyHeader(header: OutgoingHttpHeaders): OutgoingHttpHeaders; 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "newLine": "lf", 7 | "lib": ["es2017", "es2017.object"], 8 | 9 | "declaration": true, 10 | 11 | "noEmitOnError": true, 12 | 13 | "strict": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "allowSyntheticDefaultImports": true, 19 | "experimentalDecorators": true, 20 | 21 | "sourceMap": false, 22 | "inlineSources": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/electron-window-manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/electron-window-manager", 3 | "description": "A NodeJs module that handles window management for Electron (Atom Shell, previously)", 4 | "version": "0.2.2", 5 | "author": "Ahmed Zain ", 6 | "dependencies": { 7 | "electron-localshortcut": "^3.1.0", 8 | "electron-window-state": "^4.1.1", 9 | "melanke-watchjs": "^1.3.1" 10 | }, 11 | "license": "MIT", 12 | "main": "index.js", 13 | "scripts": { 14 | "build": "true", 15 | "clean": "true", 16 | "test": "node -c index.js" 17 | }, 18 | "types": "./index.d.ts" 19 | } 20 | -------------------------------------------------------------------------------- /packages/file-watcher/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const debounce = require("throttle-debounce/debounce"); 4 | 5 | module.exports = function watch(filePath, callback, debounceTime = 300) { 6 | let targetDir = path.dirname(filePath); 7 | let targetFile = path.basename(filePath); 8 | let debounceCallback = debounce(debounceTime, callback); 9 | let watcher = fs.watch(targetDir, (_, fileName) => { 10 | if (fileName !== targetFile) { 11 | return; 12 | } 13 | debounceCallback(); 14 | }); 15 | return () => { 16 | watcher.close(); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/lifecycle/client-request-repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { OnMemoryRepository } from "typescript-dddbase"; 3 | import { ResolveAll } from "../../../libs/resolve-all"; 4 | import { ClientRequestEntity } from "../client-request-entity"; 5 | import { ClientRequestIdentity } from "../client-request-identity"; 6 | 7 | @injectable() 8 | export class ClientRequestRepository extends OnMemoryRepository< 9 | ClientRequestIdentity, 10 | ClientRequestEntity 11 | > { 12 | resolveAll(): ClientRequestEntity[] { 13 | return ResolveAll(this.entities); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lifter proxy 2 | 3 | Local Proxy for frontend developers. 4 | 5 | Lifter proxy is built with [node-http-mitm-proxy](https://github.com/joeferner/node-http-mitm-proxy), [electron](https://github.com/electron/electron) and [vuejs](https://github.com/vuejs/vue) 6 | 7 | ![](resources/screenshot-1.png) 8 | 9 | ## Installing 10 | 11 | [Download the correct version](https://github.com/kyo-ago/lifter/releases/latest) 12 | 13 | ## Support Environment 14 | 15 | * macOS 10.13.* 16 | 17 | ## Author 18 | 19 | - [github/kyo-ago](https://github.com/kyo-ago) 20 | - [twitter/kyo_ago](https://twitter.com/kyo_ago) 21 | 22 | ## License 23 | 24 | GPL-3.0 © kyo-ago 25 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-client-request-module.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequestEntityJSON } from "@lifter/lifter-common"; 2 | import { Application } from "../../application/application"; 3 | 4 | export function getClientRequestModule( 5 | _: Application, 6 | clientRequests: ClientRequestEntityJSON[], 7 | ) { 8 | return { 9 | namespaced: true, 10 | state: { 11 | entries: clientRequests, 12 | }, 13 | mutations: { 14 | add(state, clientRequest: ClientRequestEntityJSON) { 15 | state.entries.unshift(clientRequest); 16 | }, 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /packages/lifter-app/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron"; 2 | 3 | // app === undefined is test env(node) 4 | let userDataPath = app ? app.getPath("userData") : "."; 5 | 6 | export const USER_DATA_PATH = userDataPath; 7 | export const USER_HOME_PATH = app 8 | ? app.getPath("home") 9 | : process.env.HOME || "."; 10 | export const REPOSITORY_BASE_DIR_PATH = `${userDataPath}/Repositories`; 11 | export const WINDOW_STATE_DIR = "WindowStates/"; 12 | export const WindowManagerInit = { 13 | defaultSetup: { 14 | defaultWidth: 1000, 15 | defaultHeight: 800, 16 | acceptFirstMouse: true, 17 | resizable: true, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/value-objects/client-request-url.ts: -------------------------------------------------------------------------------- 1 | import { Url } from "url"; 2 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 3 | 4 | export class ClientRequestUrl extends BaseValueObject { 5 | constructor(private reqestUrl: Url) { 6 | super(reqestUrl); 7 | } 8 | 9 | getPathname(): string { 10 | return this.reqestUrl.pathname || ""; 11 | } 12 | 13 | getPathSearch(): string { 14 | return `${this.getPathname()}${this.reqestUrl.search || ""}`; 15 | } 16 | 17 | getHref(): string { 18 | return this.reqestUrl.href || ""; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-rewrite-rule-modifiers-dialog-module.ts: -------------------------------------------------------------------------------- 1 | export function getRewriteRuleModifiersDialogModule() { 2 | return { 3 | namespaced: true, 4 | state() { 5 | return { 6 | isShowing: false, 7 | rewriteRoleId: undefined, 8 | }; 9 | }, 10 | mutations: { 11 | show(state, rewriteRoleId: number) { 12 | state.rewriteRoleId = rewriteRoleId; 13 | state.isShowing = true; 14 | }, 15 | hide(state) { 16 | state.isShowing = false; 17 | }, 18 | }, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-dialog/li-dialog.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 26 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/networksetup-proxy/networksetup-proxy-container.ts: -------------------------------------------------------------------------------- 1 | import { APPLICATION_NAME } from "@lifter/lifter-common"; 2 | import { NetworksetupProxy } from "@lifter/networksetup-proxy"; 3 | import { injectable } from "inversify"; 4 | import { ProxyCommandPath } from "../../../libs/proxy-command-path"; 5 | 6 | @injectable() 7 | export class NetworksetupProxyContainer { 8 | private readonly networksetupProxy: NetworksetupProxy; 9 | 10 | constructor(proxyCommandPath: ProxyCommandPath) { 11 | this.networksetupProxy = new NetworksetupProxy( 12 | `${APPLICATION_NAME} sudo prompt`, 13 | proxyCommandPath.value, 14 | ); 15 | } 16 | 17 | getCommand() { 18 | return this.networksetupProxy; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/lifter-app/config/webpack.renderer.build.js: -------------------------------------------------------------------------------- 1 | const webpackConf = require("electron-webpack/webpack.renderer.config.js"); 2 | const webpackMerge = require("webpack-merge"); 3 | const testConfig = require("../test/webpack.config"); 4 | 5 | module.exports = async env => { 6 | let conf = await webpackConf(env); 7 | delete testConfig.externals; 8 | conf.module.rules = conf.module.rules.filter( 9 | rule => !(rule.test.test(".vue") || rule.test.test(".ts")), 10 | ); 11 | let resultConfig = webpackMerge.smart(conf, testConfig); 12 | resultConfig.module.rules 13 | .filter(rule => rule.test.test(".ts")) 14 | .forEach(rule => { 15 | rule.exclude = /node_modules|\.(mock|spec)\.tsx?$/; 16 | }); 17 | return resultConfig; 18 | }; 19 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-header-tab-module.ts: -------------------------------------------------------------------------------- 1 | export const HeaderTabs = ["Connection", "Auto responder", "Rewrite rule"]; 2 | export const HeaderTabNameToIndex = HeaderTabs.reduce( 3 | (base, _, index, array) => { 4 | base[array[index]] = index; 5 | return base; 6 | }, 7 | {}, 8 | ); 9 | 10 | export function getHeaderTabModule() { 11 | return { 12 | namespaced: true, 13 | state: { 14 | index: 0, 15 | }, 16 | mutations: { 17 | changeIndex(state, index: number) { 18 | state.index = index; 19 | }, 20 | changeName(state, name: string) { 21 | state.index = HeaderTabNameToIndex[name]; 22 | }, 23 | }, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/lib/get-match-path-code-string.ts: -------------------------------------------------------------------------------- 1 | const escapePattern = _ => _.replace(/\\/g, "\\").replace(/"/g, '\\"'); 2 | 3 | export function GetFileMatchCodeString( 4 | proxyConnect: string, 5 | text: string, 6 | ): string { 7 | let pattern = escapePattern(text); 8 | return ` 9 | if (url.includes("${pattern}", url.length - "${pattern}".length)) { 10 | return "${proxyConnect}"; 11 | } 12 | `; 13 | } 14 | 15 | export function GetDirectoryMatchCodeString( 16 | proxyConnect: string, 17 | text: string, 18 | ): string { 19 | let pattern = escapePattern(text); 20 | return ` 21 | if (url.includes("${pattern}")) { 22 | return "${proxyConnect}"; 23 | } 24 | `; 25 | } 26 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-main/li-main.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 31 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-header/li-header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | 34 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/app.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/test.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const NetworksetupProxy = require("./").NetworksetupProxy; 3 | 4 | describe("networksetup-proxy", () => { 5 | let networksetupProxy; 6 | beforeEach(() => { 7 | networksetupProxy = new NetworksetupProxy(); 8 | }); 9 | 10 | it("should return escaped parameter when symbol", function() { 11 | assert.deepEqual(networksetupProxy.getSscapedParams(["hoge"]), [ 12 | "hoge", 13 | ]); 14 | assert.deepEqual(networksetupProxy.getSscapedParams([" "]), ['" "']); 15 | assert.deepEqual(networksetupProxy.getSscapedParams(["\\"]), [ 16 | '"\\\\"', 17 | ]); 18 | assert.deepEqual( 19 | networksetupProxy.getSscapedParams([undefined, undefined]), 20 | ['""', '""'], 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/lifter-main/src/application/libs/fetch-ifconfig.ts: -------------------------------------------------------------------------------- 1 | import { getIfconfig } from "../../libs/exec-commands"; 2 | 3 | export async function fetchIfconfig(): Promise { 4 | let ifconfig = await getIfconfig(); 5 | return ifconfig 6 | .trim() 7 | .split(/(?=^\w)/m) 8 | .reduce((base, cur) => { 9 | let [key, ...body] = cur.split(/(\s*:\s*)/); 10 | body.shift(); 11 | base[key] = body 12 | .join("") 13 | .split(/\r?\n/) 14 | .filter(_ => _) 15 | .reduce((base, cur) => { 16 | let [key, ...body] = cur.trim().split(/(\W)/); 17 | body.shift(); 18 | base[key] = body.join("").trim(); 19 | return base; 20 | }, {}); 21 | return base; 22 | }, {}); 23 | } 24 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-main/connection.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/proxy-bypass-domain-entity.ts: -------------------------------------------------------------------------------- 1 | import { ProxyBypassDomainEntityJSON } from "@lifter/lifter-common"; 2 | import { BaseEntity } from "../../base/base-entity"; 3 | import { ProxyBypassDomainIdentity } from "./proxy-bypass-domain-identity"; 4 | import { ProxyBypassDomainName } from "./vaue-objects/proxy-bypass-domain-name"; 5 | 6 | export class ProxyBypassDomainEntity extends BaseEntity< 7 | ProxyBypassDomainIdentity 8 | > { 9 | constructor( 10 | identity: ProxyBypassDomainIdentity, 11 | private _name: ProxyBypassDomainName, 12 | ) { 13 | super(identity); 14 | } 15 | 16 | get name(): string { 17 | return this._name.value; 18 | } 19 | 20 | get json(): ProxyBypassDomainEntityJSON { 21 | return { 22 | id: this.id, 23 | name: this.name, 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/specs/parse-get-autoproxy-url.ts: -------------------------------------------------------------------------------- 1 | import { LOCAL_PAC_FILE_URL } from "../../../../settings"; 2 | 3 | export interface CommandResult { 4 | URL: string; 5 | Enabled: "Yes" | "No"; 6 | } 7 | 8 | export function ParseGetAutoproxyUrl(stdout: string): boolean { 9 | let result = stdout 10 | .trim() 11 | .split(/\r?\n/) 12 | .reduce( 13 | (base: Partial, cur: string) => { 14 | let [key, ...val] = cur.split(/:/); 15 | (base)[key.trim()] = val.join(":").trim(); 16 | return base; 17 | }, 18 | >{}, 19 | ); 20 | if (result.Enabled !== "Yes") { 21 | return false; 22 | } 23 | if (!LOCAL_PAC_FILE_URL.test(result.URL)) { 24 | return false; 25 | } 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/app.mock.ts: -------------------------------------------------------------------------------- 1 | import { createLocalVue, mount } from "@vue/test-utils"; 2 | import Element from "element-ui"; 3 | import VueI18n from "vue-i18n"; 4 | import Vuex from "vuex"; 5 | import { ApplicationMock } from "../application/application.mock"; 6 | import { getLocale, messages } from "../messages"; 7 | import { getStore } from "../store"; 8 | import App from "./app.vue"; 9 | 10 | export function createAppMock() { 11 | const localVue = createLocalVue(); 12 | 13 | localVue.use(Vuex); 14 | localVue.use(VueI18n); 15 | 16 | let i18n = new VueI18n({ 17 | locale: getLocale(navigator), 18 | messages, 19 | }); 20 | 21 | localVue.use(Element, { 22 | i18n: i18n.t.bind(i18n), 23 | }); 24 | 25 | let store = getStore(ApplicationMock); 26 | 27 | return mount(App, { 28 | store, 29 | i18n, 30 | localVue, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/lifecycle/project-repositoty.ts: -------------------------------------------------------------------------------- 1 | import Datastore = require("nedb"); 2 | import { injectable } from "inversify"; 3 | import { AsyncOnNedbRepository } from "../../../base/async-on-nedb-repository"; 4 | import { ProjectEntity } from "../project-entity"; 5 | import { ProjectIdentity } from "../project-identity"; 6 | import { ProjectFactory } from "./project-factory"; 7 | 8 | @injectable() 9 | export class ProjectRepository extends AsyncOnNedbRepository< 10 | ProjectIdentity, 11 | ProjectEntity 12 | > { 13 | constructor(dataStoreOptions: Datastore.DataStoreOptions) { 14 | super(dataStoreOptions, { 15 | toEntity: (json: any): ProjectEntity => { 16 | return ProjectFactory.fromJSON(json); 17 | }, 18 | toJSON: (entity: ProjectEntity): any => { 19 | return entity.json; 20 | }, 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/lifter-main/src/libs/proxy-command-path.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { BaseValueObject } from "../domains/base/value-objects/base-value-object"; 3 | import { 4 | DEVELOP_PROXY_SETTING_COMMAND_PATH, 5 | PRODUCTION_PROXY_SETTING_COMMAND_PATH, 6 | } from "../settings"; 7 | 8 | export class ProxyCommandPath extends BaseValueObject { 9 | static async getPath(): Promise { 10 | let fsExists = (path: string) => 11 | new Promise(resolve => fs.exists(path, resolve)); 12 | if (await fsExists(DEVELOP_PROXY_SETTING_COMMAND_PATH)) { 13 | return new ProxyCommandPath(DEVELOP_PROXY_SETTING_COMMAND_PATH); 14 | } 15 | if (await fsExists(PRODUCTION_PROXY_SETTING_COMMAND_PATH)) { 16 | return new ProxyCommandPath(PRODUCTION_PROXY_SETTING_COMMAND_PATH); 17 | } 18 | throw new Error("Missing networksetup-proxy command"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-dialog/file-drop-dialog.vue: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "en-US": { 4 | "DropHere": "Drop here!" 5 | } 6 | } 7 | 8 | 9 | 23 | 24 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/lifecycle/auto-responder-factory.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../../test/mocks/get-test-container"; 4 | import { AutoResponderEntity } from "../auto-responder-entity"; 5 | import { AutoResponderFactory } from "./auto-responder-factory"; 6 | 7 | describe("AutoResponderFactory", () => { 8 | let autoResponderFactory: AutoResponderFactory; 9 | 10 | beforeEach(async () => { 11 | autoResponderFactory = (await getTestContainer()).get( 12 | AutoResponderFactory, 13 | ); 14 | }); 15 | 16 | it("create", () => { 17 | let autoResponderFileEntity = autoResponderFactory.create( 18 | "auto-responder-factory.spec.ts", 19 | "./auto-responder-factory.spec.ts", 20 | ); 21 | assert(autoResponderFileEntity instanceof AutoResponderEntity); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/specs/parse-get-autoproxy-url.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { ParseGetAutoproxyUrl } from "./parse-get-autoproxy-url"; 4 | 5 | describe("ParseGetAutoproxyUrl", () => { 6 | it("success", () => { 7 | let result = ParseGetAutoproxyUrl(` 8 | URL: http://127.0.0.1:8888/proxy.pac 9 | Enabled: Yes 10 | `); 11 | assert(result); 12 | }); 13 | it("Enabled: No", () => { 14 | let result = ParseGetAutoproxyUrl(` 15 | URL: http://127.0.0.1:8888/proxy.pac 16 | Enabled: No 17 | `); 18 | assert(!result); 19 | }); 20 | it("URL: (null)", () => { 21 | let result = ParseGetAutoproxyUrl(` 22 | URL: (null) 23 | Enabled: Yes 24 | `); 25 | assert(!result); 26 | }); 27 | it("failed", () => { 28 | let result = ParseGetAutoproxyUrl(` 29 | URL: (null) 30 | Enabled: No 31 | `); 32 | assert(!result); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/lifecycle/proxy-bypass-domain-factory.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { ProxyBypassDomainEntity } from "../proxy-bypass-domain-entity"; 3 | import { ProxyBypassDomainIdentity } from "../proxy-bypass-domain-identity"; 4 | import { ProxyBypassDomainName } from "../vaue-objects/proxy-bypass-domain-name"; 5 | 6 | @injectable() 7 | export class ProxyBypassDomainFactory { 8 | private identity = 0; 9 | 10 | static fromJSON(json: any): ProxyBypassDomainEntity { 11 | return new ProxyBypassDomainEntity( 12 | new ProxyBypassDomainIdentity(json.id), 13 | new ProxyBypassDomainName(json.pattern), 14 | ); 15 | } 16 | 17 | create(name: string): ProxyBypassDomainEntity { 18 | return new ProxyBypassDomainEntity( 19 | new ProxyBypassDomainIdentity(this.identity++), 20 | new ProxyBypassDomainName(name), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Element from "element-ui"; 4 | import "element-ui/lib/theme-chalk/index.css"; 5 | import Vue from "vue"; 6 | import VueI18n from "vue-i18n"; 7 | import Vuex from "vuex"; 8 | 9 | import { Application } from "./application/application"; 10 | import { render } from "./components"; 11 | import { getLocale, messages } from "./messages"; 12 | import { getStore } from "./store"; 13 | 14 | ((context: any) => context.keys().forEach(context))( 15 | require.context("./", true, /\.css$/), 16 | ); 17 | 18 | Vue.use(Vuex); 19 | Vue.use(VueI18n); 20 | 21 | let i18n = new VueI18n({ 22 | locale: getLocale(navigator), 23 | fallbackLocale: "en-US", 24 | messages, 25 | }); 26 | 27 | Vue.use(Element, { 28 | i18n: i18n.t.bind(i18n), 29 | }); 30 | 31 | let application = new Application(); 32 | application.load(); 33 | let store = getStore(application); 34 | 35 | render(store, i18n); 36 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/lifecycle/project-factory.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { ProjectEntity } from "../project-entity"; 3 | import { ProjectIdentity } from "../project-identity"; 4 | import { ProjectBaseDir } from "../value-objects/project-base-dir"; 5 | import { ProjectName } from "../value-objects/project-name"; 6 | 7 | @injectable() 8 | export class ProjectFactory { 9 | private identity = 0; 10 | 11 | static fromJSON(json: any): ProjectEntity { 12 | return new ProjectEntity( 13 | new ProjectIdentity(json.id), 14 | new ProjectName(json.name), 15 | new ProjectBaseDir(json.baseDir), 16 | ); 17 | } 18 | 19 | create(projectBaseDir: string): ProjectEntity { 20 | return new ProjectEntity( 21 | new ProjectIdentity(this.identity++), 22 | new ProjectName("new project"), 23 | new ProjectBaseDir(projectBaseDir), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-app/storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from "@storybook/vue"; 2 | 3 | import Vue from "vue"; 4 | import Vuex from "vuex"; 5 | import VueI18n from "vue-i18n"; 6 | import Element from "element-ui"; 7 | import "element-ui/lib/theme-chalk/index.css"; 8 | import { getLocale, messages } from "../src/renderer/messages"; 9 | 10 | global.process = global.process || { 11 | env: { 12 | NODE_ENV: "development", 13 | }, 14 | }; 15 | 16 | Vue.use(Vuex); 17 | Vue.use(VueI18n); 18 | let i18n = new VueI18n({ 19 | locale: getLocale(navigator), 20 | fallbackLocale: "en-US", 21 | messages, 22 | }); 23 | 24 | Vue.use(Element, { 25 | i18n: i18n.t.bind(i18n), 26 | }); 27 | 28 | // automatically import all files ending in *.stories.js 29 | let loadStories = context => { 30 | context.keys().forEach(filename => context(filename)); 31 | }; 32 | 33 | const stories = require.context("../stories", true, /.stories.js$/); 34 | configure(() => loadStories(stories), module); 35 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/project/project-entity.ts: -------------------------------------------------------------------------------- 1 | import { DataStoreOptions } from "nedb"; 2 | import { BaseEntity } from "../../base/base-entity"; 3 | import { ProjectIdentity } from "./project-identity"; 4 | import { ProjectBaseDir } from "./value-objects/project-base-dir"; 5 | import { ProjectName } from "./value-objects/project-name"; 6 | 7 | export class ProjectEntity extends BaseEntity { 8 | constructor( 9 | projectIdentity: ProjectIdentity, 10 | private name: ProjectName, 11 | public baseDir: ProjectBaseDir, 12 | ) { 13 | super(projectIdentity); 14 | } 15 | 16 | get json() { 17 | return { 18 | id: this.id, 19 | name: this.name.value, 20 | baseDir: this.baseDir.value, 21 | }; 22 | } 23 | 24 | getDataStoreOptions(name: string): DataStoreOptions { 25 | return { 26 | filename: `${this.baseDir.value}/${name}.nedb`, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/lib/client-responder-context.ts: -------------------------------------------------------------------------------- 1 | import { OutgoingHttpHeaders } from "http"; 2 | import * as HttpMitmProxy from "http-mitm-proxy"; 3 | import * as URL from "url"; 4 | 5 | export class ClientResponderContext { 6 | constructor( 7 | private ctx: HttpMitmProxy.IContext, 8 | private passCallback: (error: Error | undefined) => void, 9 | ) {} 10 | 11 | getUrl(): URL.Url { 12 | let encrypted = (this.ctx.clientToProxyRequest).client.encrypted; 13 | let host = this.ctx.clientToProxyRequest.headers.host; 14 | let path = this.ctx.clientToProxyRequest.url; 15 | return URL.parse(`http${encrypted ? `s` : ``}://${host}${path}`); 16 | } 17 | 18 | pass() { 19 | this.passCallback(undefined); 20 | } 21 | 22 | response(header: OutgoingHttpHeaders, body: Buffer | string): void { 23 | this.ctx.proxyToClientResponse.writeHead(200, header); 24 | this.ctx.proxyToClientResponse.end(body); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/client-request-entity.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequestEntityJSON } from "@lifter/lifter-common"; 2 | import { BaseEntity } from "../../base/base-entity"; 3 | import { ClientRequestIdentity } from "./client-request-identity"; 4 | import { ClientRequestUrl } from "./value-objects/client-request-url"; 5 | 6 | export class ClientRequestEntity extends BaseEntity { 7 | constructor( 8 | identity: ClientRequestIdentity, 9 | private _url: ClientRequestUrl, 10 | ) { 11 | super(identity); 12 | } 13 | 14 | get href(): string { 15 | return this._url.getHref(); 16 | } 17 | 18 | get pathname(): string { 19 | return this._url.getPathname(); 20 | } 21 | 22 | get pathSearch(): string { 23 | return this._url.getPathSearch(); 24 | } 25 | 26 | get json(): ClientRequestEntityJSON { 27 | return { 28 | id: this.id, 29 | href: this._url.getHref(), 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/lifecycle/rewrite-rule-repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { AsyncOnNedbRepository } from "../../../base/async-on-nedb-repository"; 3 | import { ProjectEntity } from "../../project/project-entity"; 4 | import { RewriteRuleEntity } from "../rewrite-rule-entity"; 5 | import { RewriteRuleIdentity } from "../rewrite-rule-identity"; 6 | import { RewriteRuleFactory } from "./rewrite-rule-factory"; 7 | 8 | @injectable() 9 | export class RewriteRuleRepository extends AsyncOnNedbRepository< 10 | RewriteRuleIdentity, 11 | RewriteRuleEntity 12 | > { 13 | constructor(projectEntity: ProjectEntity) { 14 | super(projectEntity.getDataStoreOptions(RewriteRuleRepository.name), { 15 | toEntity: (json: any): RewriteRuleEntity => { 16 | return RewriteRuleFactory.fromJSON(json); 17 | }, 18 | toJSON: (entity: RewriteRuleEntity): any => { 19 | return entity.json; 20 | }, 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/lifecycle/network-interface-factory.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { NetworkInterfaceEntity } from "../network-interface-entity"; 3 | import { NetworkInterfaceIdentity } from "../network-interface-identity"; 4 | import { NetworkInterfaceName } from "../value-objects/network-interface-name"; 5 | import { NetworkInterfaceServiceName } from "../value-objects/network-interface-service-name"; 6 | 7 | export interface NetworkDeviceParam { 8 | name: string; 9 | serviceName: string; 10 | enable: boolean; 11 | } 12 | 13 | @injectable() 14 | export class NetworkInterfaceFactory { 15 | private identity = 0; 16 | 17 | create(param: NetworkDeviceParam): NetworkInterfaceEntity { 18 | return new NetworkInterfaceEntity( 19 | new NetworkInterfaceIdentity(this.identity++), 20 | new NetworkInterfaceName(param.name), 21 | new NetworkInterfaceServiceName(param.serviceName), 22 | param.enable, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/index.ts: -------------------------------------------------------------------------------- 1 | import Vue, { ComponentOptions } from "vue"; 2 | import VueI18n from "vue-i18n"; 3 | import { Store } from "vuex"; 4 | import App from "./app.vue"; 5 | 6 | export interface VueComponent extends ComponentOptions { 7 | data?: object | ((this: any) => object); 8 | computed?: { [key: string]: (this: any, ...args: any[]) => any }; 9 | methods?: { [key: string]: (this: any, ...args: any[]) => any }; 10 | created?(this: any): void; 11 | beforeDestroy?(this: any): void; 12 | destroyed?(this: any): void; 13 | beforeMount?(this: any): void; 14 | mounted?(this: any): void; 15 | beforeUpdate?(this: any): void; 16 | updated?(this: any): void; 17 | activated?(this: any): void; 18 | deactivated?(this: any): void; 19 | errorCaptured?(this: any): boolean | void; 20 | } 21 | 22 | export function render(store: Store, i18n: VueI18n) { 23 | new Vue({ 24 | store, 25 | i18n, 26 | components: { App }, 27 | template: "", 28 | }).$mount("#app"); 29 | } 30 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/networksetup-proxy", 3 | "version": "0.2.3", 4 | "author": "@kyo_ago", 5 | "dependencies": { 6 | "@lifter/file-watcher": "^0.2.2", 7 | "execa": "^0.10.0", 8 | "stat-mode": "^0.2.2", 9 | "sudo-prompt": "^8.2.0" 10 | }, 11 | "devDependencies": { 12 | "@types/stat-mode": "^0.2.0" 13 | }, 14 | "license": "GPL-3.0", 15 | "main": "./index.js", 16 | "scripts": { 17 | "build": "tsc -p .", 18 | "clean": "rm -fr example/node_modules example/package-lock.json example/yarn.lock", 19 | "grant": "sudo chown 0:0 ./rust/proxy-setting && sudo chmod 4755 ./rust/proxy-setting", 20 | "prepublish": "npm run build", 21 | "pretest": "npm run build", 22 | "rust-build": "rm -f rust/proxy-setting && rustc -Copt-level=3 -o ./rust/proxy-setting ./rust/proxy-setting.rs && strip rust/proxy-setting", 23 | "rust-install": "curl -sSf https://static.rust-lang.org/rustup.sh | sh", 24 | "test": "mocha && npm run build && sh ./example/run.sh" 25 | }, 26 | "types": "./index.d.ts" 27 | } 28 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/lifecycle/auto-responder-repositoty.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { AsyncOnNedbRepository } from "../../../base/async-on-nedb-repository"; 3 | import { ProjectEntity } from "../../project/project-entity"; 4 | import { AutoResponderEntity } from "../auto-responder-entity"; 5 | import { AutoResponderIdentity } from "../auto-responder-identity"; 6 | import { AutoResponderFactory } from "./auto-responder-factory"; 7 | 8 | @injectable() 9 | export class AutoResponderRepository extends AsyncOnNedbRepository< 10 | AutoResponderIdentity, 11 | AutoResponderEntity 12 | > { 13 | constructor(projectEntity: ProjectEntity) { 14 | super(projectEntity.getDataStoreOptions(AutoResponderRepository.name), { 15 | toEntity: (json: any): AutoResponderEntity => { 16 | return AutoResponderFactory.fromJSON(json); 17 | }, 18 | toJSON: (entity: AutoResponderEntity): any => { 19 | return entity.json; 20 | }, 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/rewrite-rule-delete-modifier-entity.ts: -------------------------------------------------------------------------------- 1 | import { RewriteRuleDeleteModifierEntityJSON } from "@lifter/lifter-common"; 2 | import { OutgoingHttpHeaders } from "http"; 3 | import { RewriteRuleModifierEntity } from "./rewrite-rule-modifier-entity"; 4 | import { RewriteRuleModifierIdentity } from "./rewrite-rule-modifier-identity"; 5 | import { RewriteRuleModifierHeader } from "./value-objects/rewrite-rule-modifier-header"; 6 | 7 | export class RewriteRuleDeleteModifierEntity extends RewriteRuleModifierEntity { 8 | constructor( 9 | identity: RewriteRuleModifierIdentity, 10 | private header: RewriteRuleModifierHeader, 11 | ) { 12 | super(identity); 13 | } 14 | 15 | get json(): RewriteRuleDeleteModifierEntityJSON { 16 | return { 17 | id: this.id, 18 | header: this.header.value, 19 | }; 20 | } 21 | 22 | applyHeader(header: OutgoingHttpHeaders): OutgoingHttpHeaders { 23 | delete header[this.header.value]; 24 | return header; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/specs/parse-getwebproxy-command.ts: -------------------------------------------------------------------------------- 1 | import { NETWORK_HOST_NAME, PROXY_PORT } from "../../../../settings"; 2 | 3 | // export for tests 4 | export interface CommandResult { 5 | Enabled: "Yes" | "No"; 6 | Server: string; 7 | Port: string; 8 | "Authenticated Proxy Enabled": string; 9 | } 10 | 11 | export function ParseGetwebproxyCommand(stdout: string): boolean { 12 | let result = stdout 13 | .trim() 14 | .split(/\r?\n/) 15 | .reduce( 16 | (base: Partial, cur: string) => { 17 | let [key, ...val] = cur.split(/:/); 18 | (base)[key.trim()] = val.join(":").trim(); 19 | return base; 20 | }, 21 | >{}, 22 | ); 23 | 24 | if (result.Enabled !== "Yes") { 25 | return false; 26 | } 27 | if (result.Server !== NETWORK_HOST_NAME) { 28 | return false; 29 | } 30 | if (result.Port !== String(PROXY_PORT)) { 31 | return false; 32 | } 33 | return true; 34 | } 35 | -------------------------------------------------------------------------------- /packages/lifter-main/src/settings.ts: -------------------------------------------------------------------------------- 1 | export const PROXY_PORT = 8888; 2 | export const IFCONFIG_COMMAND = "/sbin/ifconfig"; 3 | export const NETWORK_SETUP_COMMAND = "/usr/sbin/networksetup"; 4 | export const NETWORK_HOST_NAME = "localhost"; 5 | export const BIND_HOST_NAME = "127.0.0.1"; 6 | export const SECURITY_COMMAND = "/usr/bin/security"; 7 | export const HTTP_SSL_CA_DIR_NAME = `http-mitm-proxy`; 8 | export const PROXY_SERVER_NAME = `${NETWORK_HOST_NAME}:${PROXY_PORT}`; 9 | export const LOCAL_PAC_FILE_URL = new RegExp( 10 | `^http://${NETWORK_HOST_NAME}|${BIND_HOST_NAME}:${PROXY_PORT}/proxy\.pac$`, 11 | ); 12 | export const PROXY_PREFERENCES_PLIST_PATH = 13 | "/Library/Preferences/SystemConfiguration/preferences.plist"; 14 | 15 | export const PRODUCTION_PROXY_SETTING_COMMAND_PATH = __dirname.replace( 16 | /app\.asar\/.+/, 17 | `app.asar.unpacked/node_modules/@lifter/networksetup-proxy/rust/proxy-setting`, 18 | ); 19 | export const DEVELOP_PROXY_SETTING_COMMAND_PATH = 20 | "./node_modules/@lifter/networksetup-proxy/rust/proxy-setting"; 21 | 22 | export const CERTIFICATE_NAME = `NodeMITMProxyCA`; 23 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/lifecycle/local-file-response-factory.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { LocalFileResponseEntity } from "../local-file-response-entity"; 3 | import { LocalFilResponseIdentity } from "../local-file-response-identity"; 4 | import { LocalFileResponsePath } from "../value-objects/local-file-response-path"; 5 | import { LocalFileResponseSize } from "../value-objects/local-file-response-size"; 6 | import { LocalFileResponseType } from "../value-objects/local-file-response-type"; 7 | 8 | export interface LocalFileResponseParam { 9 | path: string; 10 | type: string; 11 | size: number; 12 | } 13 | 14 | @injectable() 15 | export class LocalFileResponseFactory { 16 | private identity = 0; 17 | 18 | create(param: LocalFileResponseParam): LocalFileResponseEntity { 19 | return new LocalFileResponseEntity( 20 | new LocalFilResponseIdentity(this.identity++), 21 | new LocalFileResponsePath(param.path), 22 | new LocalFileResponseType(param.type), 23 | new LocalFileResponseSize(param.size), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/network-interface-service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { fetchIfconfig } from "../../../application/libs/fetch-ifconfig"; 3 | import { getListnetworkserviceorder } from "../../../libs/exec-commands"; 4 | import { 5 | NetworkDeviceParam, 6 | NetworkInterfaceFactory, 7 | } from "./lifecycle/network-interface-factory"; 8 | import { NetworkInterfaceEntity } from "./network-interface-entity"; 9 | import { ParseNetworkDevices } from "./specs/parse-network-devices"; 10 | 11 | @injectable() 12 | export class NetworkInterfaceService { 13 | constructor(private networkInterfaceFactory: NetworkInterfaceFactory) {} 14 | 15 | async fetchAllInterface(): Promise { 16 | let [serviceorder, ifconfigResult] = await Promise.all([ 17 | getListnetworkserviceorder(), 18 | fetchIfconfig(), 19 | ]); 20 | return ParseNetworkDevices(serviceorder, ifconfigResult).map( 21 | (param: NetworkDeviceParam) => 22 | this.networkInterfaceFactory.create(param), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-command-grant/vaue-objects/proxy-command-grant-setting.ts: -------------------------------------------------------------------------------- 1 | import { ProxyCommandGrantStatus } from "@lifter/lifter-common"; 2 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 3 | 4 | export class ProxyCommandGrantSetting extends BaseValueObject { 5 | static getGranted(): ProxyCommandGrantSetting { 6 | return new ProxyCommandGrantSetting(true); 7 | } 8 | static getNotGranted(): ProxyCommandGrantSetting { 9 | return new ProxyCommandGrantSetting(false); 10 | } 11 | 12 | isGranted(): boolean { 13 | return this.value; 14 | } 15 | 16 | callGranted(granted: () => Promise): Promise { 17 | return this.match({ 18 | Grant: () => granted(), 19 | notGrant: () => Promise.resolve(void 0), 20 | }); 21 | } 22 | 23 | match(matchCase: { Grant: () => T; notGrant: () => T }): T { 24 | return this.isGranted() ? matchCase.Grant() : matchCase.notGrant(); 25 | } 26 | 27 | getStatus(): ProxyCommandGrantStatus { 28 | return this.value ? "On" : "Off"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/messages.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getLocale, messages } from "./messages"; 4 | 5 | describe("messages", () => { 6 | it("messages", () => { 7 | assert(!messages.en); 8 | assert(messages["en-US"]); 9 | }); 10 | 11 | it("globalMessages", () => { 12 | assert(messages["en-US"]["delete"]); 13 | }); 14 | 15 | it("getLocale", () => { 16 | assert( 17 | getLocale({ 18 | languages: [], 19 | language: "en", 20 | }) === "en-US", 21 | ); 22 | 23 | assert( 24 | getLocale({ 25 | languages: [], 26 | language: "en-US", 27 | }) === "en-US", 28 | ); 29 | 30 | assert( 31 | getLocale({ 32 | languages: [], 33 | language: "ja", 34 | }) === "ja", 35 | ); 36 | 37 | assert( 38 | getLocale({ 39 | languages: [], 40 | language: "zh-CN", 41 | }) === "zh-CN", 42 | ); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/electron-window-manager/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ahmed Zain 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 | 23 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/mock-state-event.ts: -------------------------------------------------------------------------------- 1 | import * as EventEmitter from "events"; 2 | import { MockProxyCommandGrantStatus } from "./require-mocks/@lifter/networksetup-proxy"; 3 | import { MockCertificateStatus } from "./require-mocks/exec-commands/set-cetificate-state"; 4 | import { MockProxySettingStatus } from "./require-mocks/exec-commands/set-proxy-setting-state"; 5 | 6 | interface Events { 7 | updateCertificateState: MockCertificateStatus; 8 | updateProxySettingState: MockProxySettingStatus; 9 | updateProxyCommandGrantStatus: MockProxyCommandGrantStatus; 10 | } 11 | 12 | export const MockStateEvent = new class extends EventEmitter { 13 | on( 14 | event: K, 15 | listener: (newState: Events[K]) => void, 16 | ) { 17 | return super.on(event, listener); 18 | } 19 | emit(event: K, arg: Events[K]) { 20 | return super.emit(event, arg); 21 | } 22 | }(); 23 | 24 | beforeEach(() => { 25 | MockStateEvent.emit("updateCertificateState", "initialize"); 26 | MockStateEvent.emit("updateProxySettingState", "initialize"); 27 | MockStateEvent.emit("updateProxyCommandGrantStatus", "initialize"); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/lifecycle/proxy-bypass-domain-repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { AsyncOnNedbRepository } from "../../../base/async-on-nedb-repository"; 3 | import { ProjectEntity } from "../../../proxy/project/project-entity"; 4 | import { ProxyBypassDomainEntity } from "../proxy-bypass-domain-entity"; 5 | import { ProxyBypassDomainIdentity } from "../proxy-bypass-domain-identity"; 6 | import { ProxyBypassDomainFactory } from "./proxy-bypass-domain-factory"; 7 | 8 | @injectable() 9 | export class ProxyBypassDomainRepository extends AsyncOnNedbRepository< 10 | ProxyBypassDomainIdentity, 11 | ProxyBypassDomainEntity 12 | > { 13 | constructor(projectEntity: ProjectEntity) { 14 | super( 15 | projectEntity.getDataStoreOptions(ProxyBypassDomainRepository.name), 16 | { 17 | toEntity: (json: any): ProxyBypassDomainEntity => { 18 | return ProxyBypassDomainFactory.fromJSON(json); 19 | }, 20 | toJSON: (entity: ProxyBypassDomainEntity): any => { 21 | return entity.json; 22 | }, 23 | }, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/proxy-bypass-domain-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { ProxyBypassDomainService } from "./proxy-bypass-domain-service"; 5 | 6 | describe("ProxyBypassDomainService", () => { 7 | let proxyBypassDomainService: ProxyBypassDomainService; 8 | beforeEach(async () => { 9 | let container = await getTestContainer(); 10 | proxyBypassDomainService = container.get(ProxyBypassDomainService); 11 | }); 12 | 13 | let getProxyBypassDomains = () => { 14 | return proxyBypassDomainService.getProxyBypassDomains(); 15 | }; 16 | 17 | it("fetchAll", async () => { 18 | let results = await getProxyBypassDomains().fetchAll(); 19 | assert(results); 20 | assert(results.length === 0); 21 | }); 22 | 23 | it("overwriteAll", async () => { 24 | let domain = "example.com"; 25 | await getProxyBypassDomains().applyAll([domain]); 26 | 27 | let results = await getProxyBypassDomains().fetchAll(); 28 | assert(results.length === 1); 29 | assert(results[0].name === domain); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/specs/parse-getwebproxy-command.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getMockWebproxyState } from "../../../../../test/mocks/require-mocks/exec-commands/set-proxy-setting-state"; 4 | import { 5 | CommandResult, 6 | ParseGetwebproxyCommand, 7 | } from "./parse-getwebproxy-command"; 8 | 9 | interface Command { 10 | param: Partial; 11 | result: boolean; 12 | } 13 | 14 | describe("ParseGetwebproxyCommand", () => { 15 | ([ 16 | { 17 | param: {}, 18 | result: true, 19 | }, 20 | { 21 | param: { Enabled: "No" }, 22 | result: false, 23 | }, 24 | { 25 | param: { Server: "example.com" }, 26 | result: false, 27 | }, 28 | { 29 | param: { Port: 0 }, 30 | result: false, 31 | }, 32 | ]).forEach((command: Command) => { 33 | it(JSON.stringify(command.param), () => { 34 | let result = ParseGetwebproxyCommand( 35 | getMockWebproxyState(command.param), 36 | ); 37 | assert(result === command.result); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/specs/find-match-entry.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { ClientRequestEntity } from "../../client-request/client-request-entity"; 3 | import { LocalFileResponseFactory } from "../../local-file-response/lifecycle/local-file-response-factory"; 4 | import { LocalFileResponseEntity } from "../../local-file-response/local-file-response-entity"; 5 | import { AutoResponderEntity } from "../auto-responder-entity"; 6 | 7 | @injectable() 8 | export class FindMatchEntry { 9 | constructor(private localFileResponseFactory: LocalFileResponseFactory) {} 10 | 11 | async getLocalFileResponse( 12 | promise: Promise, 13 | clientRequestEntity: ClientRequestEntity, 14 | autoResponderEntity: AutoResponderEntity, 15 | ): Promise { 16 | let result = await promise; 17 | if (result) return result; 18 | 19 | let localFileResponseParam = await autoResponderEntity.getMatchResponder( 20 | clientRequestEntity, 21 | ); 22 | if (!localFileResponseParam) { 23 | return null; 24 | } 25 | 26 | return this.localFileResponseFactory.create(localFileResponseParam); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/value-objects/auto-responder-pattern.ts: -------------------------------------------------------------------------------- 1 | import * as micromatch from "micromatch"; 2 | import * as Path from "path"; 3 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 4 | import { ClientRequestEntity } from "../../client-request/client-request-entity"; 5 | 6 | export class AutoResponderPattern extends BaseValueObject { 7 | private matchRegexp: RegExp; 8 | 9 | constructor(pattern: string) { 10 | super(pattern); 11 | 12 | this.matchRegexp = micromatch.makeRe(pattern); 13 | } 14 | 15 | getMatchCodeString(proxyConnect: string): string { 16 | // similar matchBase option 17 | let regexp = String(this.matchRegexp).replace(/^\/\^/, "/"); 18 | 19 | return ` 20 | if ((${regexp}).test(url)) { 21 | return "${proxyConnect}"; 22 | } 23 | `; 24 | } 25 | 26 | isMatchPath(clientRequestEntity: ClientRequestEntity): boolean { 27 | let pathSearch = clientRequestEntity.pathSearch; 28 | 29 | // similar matchBase option 30 | return ( 31 | this.matchRegexp.test(pathSearch) || 32 | this.matchRegexp.test(Path.basename(pathSearch)) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-modifier/rewrite-rule-update-modifier-entity.ts: -------------------------------------------------------------------------------- 1 | import { RewriteRuleUpdateModifierEntityJSON } from "@lifter/lifter-common"; 2 | import { OutgoingHttpHeaders } from "http"; 3 | import { RewriteRuleModifierEntity } from "./rewrite-rule-modifier-entity"; 4 | import { RewriteRuleModifierIdentity } from "./rewrite-rule-modifier-identity"; 5 | import { RewriteRuleModifierHeader } from "./value-objects/rewrite-rule-modifier-header"; 6 | import { RewriteRuleModifierValue } from "./value-objects/rewrite-rule-modifier-value"; 7 | 8 | export class RewriteRuleUpdateModifierEntity extends RewriteRuleModifierEntity { 9 | constructor( 10 | identity: RewriteRuleModifierIdentity, 11 | private header: RewriteRuleModifierHeader, 12 | private value: RewriteRuleModifierValue, 13 | ) { 14 | super(identity); 15 | } 16 | 17 | get json(): RewriteRuleUpdateModifierEntityJSON { 18 | return { 19 | id: this.id, 20 | header: this.header.value, 21 | value: this.value.value, 22 | }; 23 | } 24 | 25 | applyHeader(header: OutgoingHttpHeaders): OutgoingHttpHeaders { 26 | header[this.header.value] = this.value.value; 27 | return header; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/lifecycle/client-request-factory.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequestEntityJSON } from "@lifter/lifter-common"; 2 | import { injectable } from "inversify"; 3 | import * as URL from "url"; 4 | import { ClientRequestEntity } from "../client-request-entity"; 5 | import { ClientRequestIdentity } from "../client-request-identity"; 6 | import { ClientRequestUrl } from "../value-objects/client-request-url"; 7 | 8 | @injectable() 9 | export class ClientRequestFactory { 10 | private identity = 0; 11 | 12 | static fromJSON(clientRequestEntityJSON: ClientRequestEntityJSON) { 13 | return new ClientRequestEntity( 14 | new ClientRequestIdentity(clientRequestEntityJSON.id), 15 | new ClientRequestUrl(URL.parse(clientRequestEntityJSON.href)), 16 | ); 17 | } 18 | 19 | create(url: URL.Url): ClientRequestEntity { 20 | return new ClientRequestEntity( 21 | new ClientRequestIdentity(this.identity++), 22 | new ClientRequestUrl(url), 23 | ); 24 | } 25 | 26 | createFromString(href: string): ClientRequestEntity { 27 | return new ClientRequestEntity( 28 | new ClientRequestIdentity(this.identity++), 29 | new ClientRequestUrl(URL.parse(href)), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/lifter-main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/lifter-main", 3 | "description": "lifter proxy main process", 4 | "version": "0.2.6", 5 | "author": "@kyo_ago", 6 | "dependencies": { 7 | "@lifter/electron-window-manager": "^0.2.2", 8 | "@lifter/file-watcher": "^0.2.2", 9 | "@lifter/lifter-common": "^0.2.6", 10 | "@lifter/networksetup-proxy": "^0.2.3", 11 | "execa": "^0.10.0", 12 | "http-mitm-proxy": "^0.6.1", 13 | "inversify": "^4.13.0", 14 | "micromatch": "^3.1.10", 15 | "mime": "^2.2.0", 16 | "nedb": "^1.8.0", 17 | "reflect-metadata": "^0.1.12", 18 | "rxjs": "^5.5.7", 19 | "typescript-dddbase": "^0.0.5" 20 | }, 21 | "devDependencies": { 22 | "@types/mime": "^2.0.0", 23 | "@types/nedb": "^1.8.5", 24 | "dependency-cruiser": "^3.1.1", 25 | "ts-node": "^5.0.1" 26 | }, 27 | "directories": { 28 | "test": "src/" 29 | }, 30 | "license": "GPL-3.0", 31 | "main": "./build/index.js", 32 | "scripts": { 33 | "build": "tsc -p . --rootDir ./src/", 34 | "clean": "rm -fr build", 35 | "depcruise": "depcruise --validate .dependency-cruiser.json ./src", 36 | "test": "NODE_ENV='test' mocha --require ts-node/register test/mocks/require-mocks/*.ts test/mocks/require-mocks/**/*.ts src/**/*.spec.ts" 37 | }, 38 | "types": "./build/index.d.ts" 39 | } 40 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/user-settings/user-settings-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { UserSettingsService } from "./user-settings-service"; 5 | 6 | describe("UserSettingsService", () => { 7 | let userSettingsService: UserSettingsService; 8 | beforeEach(async () => { 9 | let container = await getTestContainer(); 10 | userSettingsService = container.get(UserSettingsService); 11 | }); 12 | 13 | let getUserSetting = () => { 14 | return userSettingsService.getUserSetting(); 15 | }; 16 | 17 | it("getAutoEnableProxy", async () => { 18 | let result = getUserSetting().getAutoEnableProxy(); 19 | assert(result === "On"); 20 | }); 21 | 22 | it("changeAutoEnableProxy", async () => { 23 | let result = await getUserSetting().changeAutoEnableProxy(); 24 | assert(result === "Off"); 25 | }); 26 | 27 | it("getPacFileProxy", async () => { 28 | let result = getUserSetting().getPacFileProxy(); 29 | assert(result === "On"); 30 | }); 31 | 32 | it("changePacFileProxy", async () => { 33 | let result = await getUserSetting().changePacFileProxy(); 34 | assert(result === "Off"); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/lifter-app/src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { createApplication } from "@lifter/lifter-main"; 2 | import { app } from "electron"; 3 | import * as loadDevtool from "electron-load-devtool"; 4 | import * as unhandled from "electron-unhandled"; 5 | import { 6 | REPOSITORY_BASE_DIR_PATH, 7 | USER_DATA_PATH, 8 | USER_HOME_PATH, 9 | } from "../settings"; 10 | import { ApplicationSubscriber } from "./application-subscriber"; 11 | import { WindowManager } from "./window-manager"; 12 | 13 | unhandled(); 14 | 15 | (async () => { 16 | let [application] = await Promise.all([ 17 | createApplication( 18 | REPOSITORY_BASE_DIR_PATH, 19 | USER_DATA_PATH, 20 | USER_HOME_PATH, 21 | ), 22 | new Promise(resolve => app.on("ready", resolve)), 23 | ]); 24 | ApplicationSubscriber(application); 25 | let windowManager = new WindowManager(application); 26 | windowManager.load(); 27 | 28 | app.on("window-all-closed", async () => { 29 | await application.shutdown(); 30 | app.quit(); 31 | }); 32 | app.on("activate", () => windowManager.createMainWindow()); 33 | 34 | try { 35 | application.startup(); 36 | loadDevtool(loadDevtool.VUEJS_DEVTOOLS); 37 | await windowManager.createMainWindow(); 38 | } catch (err) { 39 | console.error(err); 40 | } 41 | })(); 42 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/network-interface/specs/parse-network-devices.ts: -------------------------------------------------------------------------------- 1 | import { NetworkDeviceParam } from "../lifecycle/network-interface-factory"; 2 | 3 | interface Device { 4 | ServiceName: string; 5 | Device: string; 6 | } 7 | 8 | export function ParseNetworkDevices( 9 | serviceorder: string, 10 | ifconfig: Ifconfig, 11 | ): NetworkDeviceParam[] { 12 | // drop first line 13 | let services = serviceorder 14 | .trim() 15 | .split("\n") 16 | .splice(1) 17 | .join("\n"); 18 | let deviceOrder: Device[] = services 19 | .trim() 20 | .split(/\n\n/) 21 | .map((line: string) => { 22 | let [serviceName, device] = line.split(/\n/); 23 | return { 24 | ServiceName: serviceName.replace(/^\(\d+\)/, "").trim(), 25 | Device: (device.match(/,\s*Device:\s*(.+?)\)/i) || [""]).pop(), 26 | }; 27 | }); 28 | 29 | return deviceOrder 30 | .filter((device: Device) => ifconfig[device["Device"]]) 31 | .map((device: Device) => { 32 | let conf = ifconfig[device["Device"]]; 33 | return { 34 | name: device["Device"], 35 | serviceName: device["ServiceName"], 36 | enable: conf.status === "active", 37 | }; 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/local-file-response/local-file-response-entity.ts: -------------------------------------------------------------------------------- 1 | import { OutgoingHttpHeaders } from "http"; 2 | import * as mime from "mime"; 3 | import { BaseEntity } from "../../base/base-entity"; 4 | import { LocalFilResponseIdentity } from "./local-file-response-identity"; 5 | import { LocalFileResponsePath } from "./value-objects/local-file-response-path"; 6 | import { LocalFileResponseSize } from "./value-objects/local-file-response-size"; 7 | import { LocalFileResponseType } from "./value-objects/local-file-response-type"; 8 | 9 | export class LocalFileResponseEntity extends BaseEntity< 10 | LocalFilResponseIdentity 11 | > { 12 | constructor( 13 | identity: LocalFilResponseIdentity, 14 | private path: LocalFileResponsePath, 15 | private type: LocalFileResponseType, 16 | private size: LocalFileResponseSize, 17 | ) { 18 | super(identity); 19 | } 20 | 21 | getBody() { 22 | return this.path.getBody(); 23 | } 24 | 25 | getHeader(): OutgoingHttpHeaders { 26 | return { 27 | "content-length": this.getContentLength(), 28 | "content-type": this.getContentType(), 29 | }; 30 | } 31 | 32 | private getContentType() { 33 | return this.type.value || mime.getType(this.path.value); 34 | } 35 | 36 | private getContentLength() { 37 | return this.size.value; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-proxy-bypass-domain-module.ts: -------------------------------------------------------------------------------- 1 | import { ProxyBypassDomainEntityJSON } from "@lifter/lifter-common"; 2 | import { Application } from "../../application/application"; 3 | 4 | export function getProxyBypassDomainModule( 5 | application: Application, 6 | proxyBypassDomains: ProxyBypassDomainEntityJSON[], 7 | ) { 8 | return { 9 | namespaced: true, 10 | state: { 11 | entries: proxyBypassDomains, 12 | }, 13 | mutations: { 14 | save(state, proxyBypassDomains: ProxyBypassDomainEntityJSON[]) { 15 | state.entries = proxyBypassDomains; 16 | }, 17 | }, 18 | actions: { 19 | async gets({ commit }): Promise { 20 | let proxyBypassDomains = await application.getProxyBypassDomains(); 21 | commit("save", proxyBypassDomains); 22 | return proxyBypassDomains; 23 | }, 24 | async save( 25 | { dispatch }, 26 | targetProxyBypassDomains: ProxyBypassDomainEntityJSON[], 27 | ): Promise { 28 | await application.saveProxyBypassDomains( 29 | targetProxyBypassDomains, 30 | ); 31 | return await dispatch("gets"); 32 | }, 33 | }, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-dialog/rewrite-rule-modifiers-dialog/rewrite-rule-modifiers-dialog.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | 43 | 45 | -------------------------------------------------------------------------------- /packages/lifter-app/stories/index.stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from "@storybook/vue"; 2 | import App from "../src/renderer/components/app"; 3 | import LiHeader from "../src/renderer/components/li-header/li-header"; 4 | import LiMain from "../src/renderer/components/li-main/li-main"; 5 | import LiDialog from "../src/renderer/components/li-dialog/li-dialog"; 6 | import { getStore } from "../src/renderer/store"; 7 | import { ApplicationMock } from "../src/renderer/application/application.mock"; 8 | import { getLocale, messages } from "../src/renderer/messages"; 9 | import VueI18n from "vue-i18n"; 10 | 11 | let i18n = new VueI18n({ 12 | locale: getLocale(navigator), 13 | messages, 14 | }); 15 | 16 | storiesOf("App", module) 17 | .add("app", () => ({ 18 | components: { App }, 19 | template: "", 20 | store: getStore(ApplicationMock), 21 | i18n, 22 | })) 23 | .add("li-header", () => ({ 24 | components: { LiHeader }, 25 | template: "", 26 | store: getStore(ApplicationMock), 27 | i18n, 28 | })) 29 | .add("li-main", () => ({ 30 | components: { LiMain }, 31 | template: "", 32 | store: getStore(ApplicationMock), 33 | i18n, 34 | })) 35 | .add("li-dialog", () => ({ 36 | components: { LiDialog }, 37 | template: "", 38 | store: getStore(ApplicationMock), 39 | i18n, 40 | })); 41 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-main/auto-responder.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 49 | 50 | 52 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/store/modules/get-auto-responder-module.ts: -------------------------------------------------------------------------------- 1 | import { AutoResponderEntityJSON } from "@lifter/lifter-common"; 2 | import { Application } from "../../application/application"; 3 | 4 | export function getAutoResponderModule( 5 | application: Application, 6 | autoResponders: AutoResponderEntityJSON[], 7 | ) { 8 | return { 9 | namespaced: true, 10 | state: { 11 | entries: autoResponders, 12 | }, 13 | mutations: { 14 | add(state, autoResponders: AutoResponderEntityJSON[]) { 15 | state.entries = autoResponders.concat(state.entries); 16 | }, 17 | save(state, autoResponders: AutoResponderEntityJSON[]) { 18 | state.entries = autoResponders; 19 | }, 20 | }, 21 | actions: { 22 | async add({ commit }, files: File[]) { 23 | let autoResponders = await application.addDropFiles(files); 24 | commit("add", autoResponders); 25 | }, 26 | async deletes( 27 | { commit }, 28 | targetAutoResponders: AutoResponderEntityJSON[], 29 | ) { 30 | await application.deleteAutoResponderEntities( 31 | targetAutoResponders, 32 | ); 33 | let autoResponders = await application.fetchAutoResponderEntities(); 34 | commit("save", autoResponders); 35 | }, 36 | }, 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/value-objects/auto-responder-any-path.ts: -------------------------------------------------------------------------------- 1 | import * as Path from "path"; 2 | import { FilePath } from "../../../base/value-objects/file-path"; 3 | import { ClientRequestEntity } from "../../client-request/client-request-entity"; 4 | import { AutoResponderFilePath } from "./auto-responder-file-path"; 5 | 6 | export class AutoResponderAnyPath extends FilePath { 7 | async getAutoResponderFilePath( 8 | clientRequestEntity: ClientRequestEntity, 9 | ): Promise { 10 | let stat = await this.getState(); 11 | if (stat.isFile()) { 12 | return new AutoResponderFilePath(this.value); 13 | } 14 | 15 | if (!stat.isDirectory()) { 16 | return null; 17 | } 18 | 19 | let pathSearch = clientRequestEntity.pathSearch; 20 | 21 | // / 22 | if (pathSearch === "/") { 23 | return null; 24 | } 25 | 26 | // /hoge 27 | if (pathSearch.match(/^\/[^\/]+$/)) { 28 | return new AutoResponderFilePath(Path.join(this.value, pathSearch)); 29 | } 30 | 31 | let splited = pathSearch.split(`/${Path.basename(this.value)}/`); 32 | 33 | // unmatch 34 | if (splited.length === 1) return null; 35 | 36 | let lastPath = splited.pop(); 37 | 38 | // directory match 39 | if (!lastPath) return null; 40 | 41 | return new AutoResponderFilePath(Path.join(this.value, lastPath)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-header/right-toolbar.vue: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "en-US": { 4 | "Settings": "Settings..." 5 | } 6 | } 7 | 8 | 9 | 26 | 27 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /packages/networksetup-proxy/rust/proxy-setting.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::Command; 3 | 4 | fn main() { 5 | use std::os::unix::process::CommandExt; 6 | let mut command = Command::new("/usr/sbin/networksetup"); 7 | 8 | let mut args = env::args(); 9 | args.next(); 10 | 11 | let param = args.next().unwrap(); 12 | 13 | match param.as_ref() { 14 | "-setwebproxystate" 15 | | "-setsecurewebproxystate" 16 | | "-setproxybypassdomains" 17 | | "-setautoproxystate" 18 | => { 19 | command.arg(param); 20 | for arg in args { 21 | command.arg(arg); 22 | } 23 | }, 24 | "-setwebproxy" 25 | | "-setsecurewebproxy" 26 | => { 27 | command.arg(param); 28 | command.arg(args.next().unwrap()); // networkservice 29 | command.arg("localhost"); // domain 30 | command.arg(args.next().unwrap()); // port 31 | for arg in args { 32 | command.arg(arg); 33 | } 34 | }, 35 | "-setautoproxyurl" 36 | => { 37 | command.arg(param); 38 | command.arg(args.next().unwrap()); 39 | command.arg(format!("http://127.0.0.1:{}/proxy.pac", args.next().unwrap())); 40 | }, 41 | _ => panic!("Unknown parameter: {}", param), 42 | }; 43 | 44 | command.uid(0); 45 | let output = match command.output() { 46 | Ok(p) => p, 47 | Err(e) => panic!("Failed to execute: {}", e), 48 | }; 49 | 50 | print!("{}", String::from_utf8_lossy(output.stdout.as_slice())); 51 | } 52 | -------------------------------------------------------------------------------- /packages/lifter-app/src/main/window-manager.ts: -------------------------------------------------------------------------------- 1 | import * as windowManager from "@lifter/electron-window-manager"; 2 | import { APPLICATION_NAME } from "@lifter/lifter-common"; 3 | import { Application } from "@lifter/lifter-main"; 4 | import { ipc } from "../lib/ipc"; 5 | import { WINDOW_STATE_DIR, WindowManagerInit } from "../settings"; 6 | import { getApplicationMainStateJSON } from "./application-main-state"; 7 | 8 | export class WindowManager { 9 | constructor(private application: Application) {} 10 | 11 | load() { 12 | windowManager.init(WindowManagerInit); 13 | } 14 | 15 | async createMainWindow() { 16 | let name = "mainWindow"; 17 | if (windowManager.get(name)) { 18 | return; 19 | } 20 | 21 | const isDevelopment = process.env.NODE_ENV !== "production"; 22 | const url = isDevelopment 23 | ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` 24 | : `file://${__dirname}/index.html`; 25 | 26 | let state = await getApplicationMainStateJSON(this.application); 27 | windowManager.sharedData.set("mainApps", state); 28 | windowManager.open(name, APPLICATION_NAME, url, "default", { 29 | file: `${WINDOW_STATE_DIR}main-window-state.json`, 30 | }); 31 | 32 | if (isDevelopment) { 33 | windowManager 34 | .get(name) 35 | .content() 36 | .openDevTools(); 37 | } 38 | 39 | let window = windowManager.get(name); 40 | ipc.addWindow(window.object); 41 | window.object.on("closed", () => ipc.removeWindow(window)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/lifter-main/.dependency-cruiser.json: -------------------------------------------------------------------------------- 1 | { 2 | "forbidden": [ 3 | { 4 | "name": "no-deprecated-core", 5 | "comment": "Warn about dependencies on deprecated core modules.", 6 | "severity": "warn", 7 | "from": {}, 8 | "to": { "dependencyTypes": ["core"], "path": "^(punycode|domain)$" } 9 | }, 10 | { 11 | "name": "not-to-unresolvable", 12 | "severity": "error", 13 | "from": { "pathNot": "^[\\./]*node_modules" }, 14 | "to": { "couldNotResolve": true } 15 | }, 16 | { 17 | "name": "no-circular", 18 | "severity": "warn", 19 | "from": { "pathNot": "^[\\./]*node_modules" }, 20 | "to": { "circular": true } 21 | }, 22 | { 23 | "severity": "error", 24 | "from": { "pathNot": "^[\\./]*node_modules" }, 25 | "to": { 26 | "pathNot": "/(:?sinon|mocha)/", 27 | "dependencyTypes": ["unknown", "undetermined", "npm-no-pkg", "npm-unknown", "npm-optional", "deprecated", "npm-peer"] 28 | } 29 | }, 30 | { 31 | "name": "no-duplicate-dep-types", 32 | "severity": "warn", 33 | "from": {}, 34 | "to": { "moreThanOneDependencyType": true } 35 | }, 36 | { 37 | "name": "not-to-test", 38 | "comment": "Don't allow dependencies from outside the test folder to test", 39 | "severity": "error", 40 | "from": { "pathNot": "^test|\\.spec\\.ts$" }, 41 | "to": { "path": "^test" } 42 | }, 43 | 44 | 45 | { 46 | "severity": "error", 47 | "from": { 48 | "path": "^src/domains/" 49 | }, 50 | "to": { 51 | "path": "^src/application/" 52 | } 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/require-mocks/exec-commands/exec-commands.ts: -------------------------------------------------------------------------------- 1 | import * as mockRequire from "mock-require"; 2 | import * as sinon from "sinon"; 3 | import * as ExecCommands from "../../../../src/libs/exec-commands"; 4 | import { MockStateEvent } from "../../mock-state-event"; 5 | import { 6 | MockCertificateStatus, 7 | setCetificateState, 8 | } from "./set-cetificate-state"; 9 | import { 10 | MockProxySettingStatus, 11 | setProxySettingState, 12 | } from "./set-proxy-setting-state"; 13 | 14 | export type ExecCommandsStub = sinon.SinonStubbedInstance; 15 | 16 | let sandbox = sinon.createSandbox(); 17 | let stub = sandbox.stub(ExecCommands); 18 | 19 | MockStateEvent.on( 20 | "updateCertificateState", 21 | (newState: MockCertificateStatus) => { 22 | let setStub = setCetificateState(newState); 23 | setStub(stub); 24 | }, 25 | ); 26 | 27 | MockStateEvent.on( 28 | "updateProxySettingState", 29 | (newState: MockProxySettingStatus) => { 30 | let setStub = setProxySettingState(newState); 31 | setStub(stub); 32 | }, 33 | ); 34 | 35 | stub.getIfconfig.resolves(` 36 | lo0: flags=8049 mtu 16384 37 | inet 127.0.0.1 netmask 0xff000000 38 | en0: flags=8863 mtu 1500 39 | inet 192.168.150.51 netmask 0xffffff00 broadcast 192.168.150.255 40 | status: active 41 | `); 42 | 43 | stub.getAutoproxyurl.resolves(` 44 | URL: (null) 45 | Enabled: No 46 | `); 47 | 48 | mockRequire("../../../../src/libs/exec-commands", stub); 49 | 50 | afterEach(() => { 51 | sandbox.resetHistory(); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/application/libs/get-window-manager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ProxyBypassDomainEntityJSON, 3 | RewriteRuleEntityJSON, 4 | } from "@lifter/lifter-common"; 5 | import { remote } from "electron"; 6 | import { ApplicationMainStateJSON } from "../../../main/application-main-state"; 7 | 8 | type WindowId = "mainWindow"; 9 | 10 | interface StoreDateKeyMap { 11 | mainApps: ApplicationMainStateJSON; 12 | mainRewriteRules: RewriteRuleEntityJSON[]; 13 | mainProxyBypassDomains: ProxyBypassDomainEntityJSON[]; 14 | } 15 | 16 | interface BridgeDateKeyMap {} 17 | 18 | interface WindowManager { 19 | sharedData: { 20 | fetch: (key: K) => StoreDateKeyMap[K]; 21 | set: ( 22 | key: K, 23 | jsons: StoreDateKeyMap[K], 24 | ) => void; 25 | watch: ( 26 | name: string, 27 | callback: ( 28 | props: any, 29 | action: string, 30 | newValue: any, 31 | oldValue: any, 32 | ) => void, 33 | ) => void; 34 | }; 35 | bridge: { 36 | on: ( 37 | key: K, 38 | callback: (jsons: BridgeDateKeyMap[K]) => void, 39 | ) => void; 40 | emit: ( 41 | key: K, 42 | jsons: BridgeDateKeyMap[K], 43 | ) => void; 44 | }; 45 | open: (windowId: WindowId, ...args: any[]) => any; 46 | get: (windowId: WindowId) => any; 47 | } 48 | 49 | export const windowManager: WindowManager = remote.require( 50 | "@lifter/electron-window-manager", 51 | ); 52 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/messages.ts: -------------------------------------------------------------------------------- 1 | import VueI18n from "vue-i18n"; 2 | 3 | const langMapper = { 4 | en: "en-US", 5 | }; 6 | 7 | const globalMessages = { 8 | "en-US": { 9 | delete: "Delete", 10 | }, 11 | ja: { 12 | delete: "Delete", 13 | }, 14 | }; 15 | 16 | export const messages: VueI18n.LocaleMessages = [ 17 | "zh-CN", 18 | "en", 19 | "de", 20 | "pt", 21 | "es", 22 | "da", 23 | "fr", 24 | "nb-NO", 25 | "zh-TW", 26 | "it", 27 | "ko", 28 | "ja", 29 | "nl", 30 | "vi", 31 | "ru-RU", 32 | "tr-TR", 33 | "pt-br", 34 | "fa", 35 | "th", 36 | "id", 37 | "bg", 38 | "pl", 39 | "fi", 40 | "sv-SE", 41 | "el", 42 | "sk", 43 | "ca", 44 | "cs-CZ", 45 | "ua", 46 | "tk", 47 | "ta", 48 | "lv", 49 | "af-ZA", 50 | "ee", 51 | "sl", 52 | "ar", 53 | "he", 54 | "lt", 55 | "mn", 56 | "kz", 57 | "hu", 58 | "ro", 59 | "ku", 60 | ].reduce((base: VueI18n.LocaleMessages, cur: string) => { 61 | let mappedLang = (langMapper)[cur] || cur; 62 | base[mappedLang] = { 63 | ...(globalMessages)[mappedLang], 64 | ...require(`element-ui/lib/locale/lang/${cur}`).default, 65 | }; 66 | return base; 67 | }, {}); 68 | 69 | export function getLocale(nav: any): VueI18n.Locale { 70 | const mainLangs = ["en-US", "ja"]; 71 | const locales = nav.languages 72 | .map(lang => (langMapper)[lang] || lang) 73 | .filter(lang => mainLangs.includes(lang)) 74 | .concat(nav.languages.filter(lang => !!messages[lang])); 75 | 76 | return locales[0] || (langMapper)[nav.language] || nav.language; 77 | } 78 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-header/tab-contents.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 33 | 34 | 69 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/mixins/rewrite-rule-modifiers-mixin.ts: -------------------------------------------------------------------------------- 1 | import { RewriteRuleActionType } from "@lifter/lifter-common"; 2 | 3 | export function makeRewriteRuleModifiersMixin( 4 | rewriteRuleActionType: RewriteRuleActionType, 5 | ) { 6 | let components = undefined; 7 | if (global.process.env.NODE_ENV === "test") { 8 | components = { 9 | "el-input": { 10 | name: "el-input", 11 | render: () => "", 12 | }, 13 | "el-table": { 14 | name: "el-table", 15 | render: () => "", 16 | }, 17 | "el-autocomplete": { 18 | name: "el-autocomplete", 19 | render: () => "", 20 | }, 21 | }; 22 | } 23 | return { 24 | components, 25 | props: { 26 | rewriteRoleId: { 27 | type: Number, 28 | required: true, 29 | }, 30 | }, 31 | computed: { 32 | modifiers() { 33 | return this.$store.getters["rewriteRule/modifiers"]( 34 | this.rewriteRoleId, 35 | rewriteRuleActionType, 36 | ); 37 | }, 38 | }, 39 | methods: { 40 | getHeaderExamples(_, cb) { 41 | cb([ 42 | { 43 | value: "content-type", 44 | }, 45 | { 46 | value: "content-length", 47 | }, 48 | { 49 | value: "cookie", 50 | }, 51 | { 52 | value: "origin", 53 | }, 54 | ]); 55 | }, 56 | }, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/certificate/certificate-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { MockStateEvent } from "../../../../test/mocks/mock-state-event"; 5 | import { CertificateService } from "./certificate-service"; 6 | 7 | describe("CertificateService", () => { 8 | let certificateService: CertificateService; 9 | beforeEach(async () => { 10 | let container = await getTestContainer(); 11 | certificateService = container.get(CertificateService); 12 | }); 13 | 14 | let getCertificateService = () => { 15 | return certificateService.getCertificateService(); 16 | }; 17 | 18 | it("fetch", async () => { 19 | let result = await getCertificateService().fetchCurrentStatus(); 20 | assert(result === "Missing"); 21 | }); 22 | 23 | it("change Missing to Installed", async () => { 24 | MockStateEvent.emit("updateCertificateState", "Missing"); 25 | let result = await getCertificateService().changeCertificateStatus(); 26 | assert(result === "Installed"); 27 | }); 28 | 29 | it("change Installed to Missing", async () => { 30 | MockStateEvent.emit("updateCertificateState", "Installed"); 31 | let result = await getCertificateService().changeCertificateStatus(); 32 | assert(result === "Missing"); 33 | }); 34 | 35 | it("change to fetch", async () => { 36 | MockStateEvent.emit("updateCertificateState", "Missing"); 37 | let result = await getCertificateService().changeCertificateStatus(); 38 | assert(result === "Installed"); 39 | 40 | let fetchResult = await getCertificateService().fetchCurrentStatus(); 41 | assert(fetchResult === "Installed"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/value-objects/rewrite-rule-modifier-map.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RewriteRuleActionType, 3 | RewriteRuleModifierMapJSON, 4 | } from "@lifter/lifter-common"; 5 | import { OutgoingHttpHeaders } from "http"; 6 | import { BaseValueObject } from "../../../base/value-objects/base-value-object"; 7 | import { RewriteRuleModifierEntity } from "../rewrite-rule-modifier/rewrite-rule-modifier-entity"; 8 | import { RewriteRuleModifierIdentity } from "../rewrite-rule-modifier/rewrite-rule-modifier-identity"; 9 | 10 | export class RewriteRuleModifierMap extends BaseValueObject< 11 | { [key in RewriteRuleActionType]: RewriteRuleModifierEntity[] } 12 | > { 13 | get json(): RewriteRuleModifierMapJSON { 14 | return Object.keys(this.value).reduce( 15 | (base, cur) => { 16 | base[cur] = this.value[cur].map(modifier => modifier.json); 17 | return base; 18 | }, 19 | {}, 20 | ); 21 | } 22 | 23 | addModifier( 24 | action: RewriteRuleActionType, 25 | modifier: RewriteRuleModifierEntity, 26 | ): void { 27 | this.value[action] = this.value[action] || []; 28 | this.value[action].push(modifier); 29 | } 30 | 31 | deleteModifier( 32 | action: RewriteRuleActionType, 33 | modifierId: RewriteRuleModifierIdentity, 34 | ): void { 35 | this.value[action] = (this.value[action] || []).filter( 36 | modifier => !modifier.getIdentity().equals(modifierId), 37 | ); 38 | } 39 | 40 | applyHeader(header: OutgoingHttpHeaders): OutgoingHttpHeaders { 41 | return this.value["UPDATE"] 42 | .concat(this.value["DELETE"]) 43 | .reduce((header, modifier) => modifier.applyHeader(header), header); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/lifter-common/src/index.ts: -------------------------------------------------------------------------------- 1 | export const APPLICATION_NAME = "Lifter Proxy"; 2 | 3 | export type CertificateStatus = "Missing" | "Installed"; 4 | 5 | export type ProxySettingStatus = "On" | "Off" | "NoTargetInterfaces"; 6 | 7 | export type ProxyCommandGrantStatus = "On" | "Off"; 8 | 9 | export type AutoEnableProxyStatus = "On" | "Off"; 10 | 11 | export type PacFileProxyStatus = "On" | "Off"; 12 | 13 | export type RewriteRuleActionType = "UPDATE" | "DELETE"; 14 | 15 | export interface AutoResponderEntityJSON { 16 | id: number; 17 | pattern: string; 18 | path: string; 19 | projectId: number; 20 | } 21 | 22 | export interface ClientRequestEntityJSON { 23 | id: number; 24 | href: string; 25 | } 26 | 27 | export interface ProxyBypassDomainEntityJSON { 28 | id: number; 29 | name: string; 30 | } 31 | 32 | export interface RewriteRuleModifierEntityJSON { 33 | id: number; 34 | header: string; 35 | } 36 | 37 | export interface RewriteRuleUpdateModifierEntityJSON 38 | extends RewriteRuleModifierEntityJSON { 39 | value: string; 40 | } 41 | 42 | export interface RewriteRuleDeleteModifierEntityJSON 43 | extends RewriteRuleModifierEntityJSON {} 44 | 45 | export interface CreateRewriteRuleModifierEntityJSON { 46 | header: string; 47 | } 48 | 49 | export interface CreateRewriteRuleUpdateModifierEntityJSON 50 | extends CreateRewriteRuleModifierEntityJSON { 51 | value: string; 52 | } 53 | 54 | export interface CreateRewriteRuleDeleteModifierEntityJSON 55 | extends CreateRewriteRuleModifierEntityJSON {} 56 | 57 | export type RewriteRuleModifierMapJSON = { 58 | UPDATE: RewriteRuleUpdateModifierEntityJSON[]; 59 | DELETE: RewriteRuleDeleteModifierEntityJSON[]; 60 | }; 61 | 62 | export interface RewriteRuleEntityJSON { 63 | id: number; 64 | url: string; 65 | modifier: RewriteRuleModifierMapJSON; 66 | } 67 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/pac-file/pac-file-service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { AutoResponderService } from "../../proxy/auto-responder/auto-responder-service"; 3 | import { ClientResponderContext } from "../../proxy/client-request/lib/client-responder-context"; 4 | import { NetworksetupProxyService } from "../networksetup-proxy/networksetup-proxy-service"; 5 | 6 | @injectable() 7 | export class PacFileService { 8 | constructor( 9 | private autoResponderService: AutoResponderService, 10 | private networksetupProxyService: NetworksetupProxyService, 11 | ) {} 12 | private unsubscribe: (() => void) | null = null; 13 | 14 | async start() { 15 | await this.networksetupProxyService.setAutoProxyUrl(); 16 | this.unsubscribe && this.unsubscribe(); 17 | this.unsubscribe = this.autoResponderService.bind(() => 18 | this.networksetupProxyService.reloadAutoProxyUrl(), 19 | ); 20 | } 21 | 22 | async stop() { 23 | await this.networksetupProxyService.clearAutoProxyUrl(); 24 | this.unsubscribe && this.unsubscribe(); 25 | this.unsubscribe = null; 26 | } 27 | 28 | async response(clientResponderContext: ClientResponderContext) { 29 | let content = await this.getContent(); 30 | clientResponderContext.response( 31 | { 32 | "content-length": content.length, 33 | "content-type": "application/x-ns-proxy-autoconfig", 34 | }, 35 | content, 36 | ); 37 | } 38 | 39 | private async getContent(): Promise { 40 | let codeString = await this.autoResponderService.fetchMatchCodes(); 41 | return ` 42 | function FindProxyForURL(url, host) { 43 | ${codeString} 44 | return "DIRECT"; 45 | } 46 | `; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/lifter-app/storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpackMerge = require("webpack-merge"); 3 | const testConfig = require("../test/webpack.config"); 4 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 5 | 6 | module.exports = storybookBaseConfig => { 7 | storybookBaseConfig.module.rules = storybookBaseConfig.module.rules.filter( 8 | rule => !rule.test.test(".vue"), 9 | ); 10 | delete testConfig.externals; 11 | let resultConfig = webpackMerge.smart(storybookBaseConfig, testConfig, { 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.css$/, 16 | use: ExtractTextPlugin.extract({ 17 | fallback: "style-loader", 18 | use: "css-loader", 19 | }), 20 | }, 21 | { 22 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 23 | use: { 24 | loader: "url-loader", 25 | options: { 26 | limit: 10240, 27 | name: "fonts/[name]--[folder].[ext]", 28 | }, 29 | }, 30 | }, 31 | ], 32 | }, 33 | plugins: [new ExtractTextPlugin("styles.css")], 34 | resolve: { 35 | alias: { 36 | electron: path.resolve(__dirname, "../mocks/electron"), 37 | "electron-ipc": path.resolve( 38 | __dirname, 39 | "../mocks/electron-ipc", 40 | ), 41 | "electron-context-menu": path.resolve( 42 | __dirname, 43 | "../mocks/electron-context-menu", 44 | ), 45 | }, 46 | }, 47 | }); 48 | return resultConfig; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/networksetup-proxy/networksetup-proxy-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { MockStateEvent } from "../../../../test/mocks/mock-state-event"; 5 | import { ProxyCommandGrantService } from "../proxy-command-grant/proxy-command-grant-service"; 6 | 7 | describe("ProxyCommandGrantService", () => { 8 | let proxyCommandGrantService: ProxyCommandGrantService; 9 | beforeEach(async () => { 10 | let container = await getTestContainer(); 11 | proxyCommandGrantService = container.get(ProxyCommandGrantService); 12 | }); 13 | 14 | let getProxyCommandGrantService = () => { 15 | return proxyCommandGrantService.getProxyCommandGrantService(); 16 | }; 17 | 18 | it("fetchStatus", async () => { 19 | let result = await getProxyCommandGrantService().fetchStatus(); 20 | assert(result === "Off"); 21 | }); 22 | 23 | [ 24 | ["initialize", "On"], 25 | ["CancelGrant", "Off"], 26 | ["Off", "On"], 27 | ["On", "Off"], 28 | ].forEach(([from, to]) => { 29 | it(`changeProxyCommandGrantStatus ${from} to ${to}`, async () => { 30 | MockStateEvent.emit("updateProxyCommandGrantStatus", from); 31 | // reload MockStateEvent state 32 | await proxyCommandGrantService.load(); 33 | 34 | let result = await getProxyCommandGrantService().changeStatus(); 35 | assert(result === to); 36 | }); 37 | }); 38 | 39 | it("changeStatus to fetch", async () => { 40 | let result = await getProxyCommandGrantService().changeStatus(); 41 | assert(result === "On"); 42 | 43 | let fetchResult = await getProxyCommandGrantService().fetchStatus(); 44 | assert(fetchResult === "On"); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/base/async-nedb-id-generator.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import * as Datastore from "nedb"; 3 | import "reflect-metadata"; 4 | import { promisify } from "util"; 5 | 6 | @injectable() 7 | export abstract class AsyncNedbIdGenerator { 8 | private datastore: Datastore; 9 | private identity = 0; 10 | private timeout = 0; 11 | private keyName = "currentCount"; 12 | 13 | private find: (query: any) => Promise; 14 | private update: (query: any, value: any, option: any) => Promise; 15 | private loadDatabase: () => Promise; 16 | 17 | constructor(dataStoreOptions: Datastore.DataStoreOptions) { 18 | this.datastore = new Datastore(dataStoreOptions); 19 | this.find = promisify(this.datastore.find.bind(this.datastore)); 20 | this.update = promisify(this.datastore.update.bind(this.datastore)); 21 | this.loadDatabase = promisify( 22 | this.datastore.loadDatabase.bind(this.datastore), 23 | ); 24 | } 25 | 26 | async load() { 27 | await this.loadDatabase(); 28 | let line = await this.find({ name: this.keyName }); 29 | if (line.length) { 30 | this.identity = line.shift()["count"]; 31 | return; 32 | } 33 | return this.upsertCurrentId(); 34 | } 35 | 36 | protected getNextIdNumber() { 37 | this.upsertCurrentId(); 38 | return this.identity++; 39 | } 40 | 41 | private upsertCurrentId() { 42 | if (this.timeout) return; 43 | this.timeout = setTimeout(() => { 44 | this.timeout = 0; 45 | // ignore promise 46 | return this.update( 47 | { name: this.keyName }, 48 | { 49 | name: this.keyName, 50 | count: this.identity, 51 | }, 52 | { upsert: true }, 53 | ); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/application/context-menu/context-menu-service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BrowserWindow, 3 | ContextMenuParams, 4 | MenuItemConstructorOptions, 5 | } from "electron"; 6 | import * as contextMenu from "electron-context-menu"; 7 | 8 | type ContextMenuEventType = "prepend" | "append"; 9 | 10 | interface ContextMenuHandler { 11 | (event: ContextMenuEvent): MenuItemConstructorOptions | null; 12 | } 13 | 14 | export interface ContextMenuEvent { 15 | type: ContextMenuEventType; 16 | params: ContextMenuParams; 17 | browserWindow: BrowserWindow; 18 | } 19 | 20 | export class ContextMenuService { 21 | private handlers: ContextMenuHandler[] = []; 22 | 23 | bind() { 24 | let next = ( 25 | type: ContextMenuEventType, 26 | params: ContextMenuParams, 27 | browserWindow: BrowserWindow, 28 | ): MenuItemConstructorOptions[] => { 29 | let event: ContextMenuEvent = { 30 | type, 31 | params, 32 | browserWindow, 33 | }; 34 | return this.handlers 35 | .map(handler => handler(event)) 36 | .filter(result => !!result); 37 | }; 38 | let handler = (type: ContextMenuEventType) => { 39 | return ( 40 | params: ContextMenuParams, 41 | browserWindow: BrowserWindow, 42 | ) => { 43 | let results = next(type, params, browserWindow); 44 | return results.length ? results : undefined; 45 | }; 46 | }; 47 | contextMenu({ 48 | prepend: handler("prepend"), 49 | append: handler("append"), 50 | }); 51 | } 52 | 53 | subscribe(handler: ContextMenuHandler): () => void { 54 | this.handlers.push(handler); 55 | return () => { 56 | this.handlers = this.handlers.filter(val => val !== handler); 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/rewrite-rule/rewrite-rule-entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RewriteRuleActionType, 3 | RewriteRuleEntityJSON, 4 | } from "@lifter/lifter-common"; 5 | import { OutgoingHttpHeaders } from "http"; 6 | import { BaseEntity } from "../../base/base-entity"; 7 | import { ClientRequestEntity } from "../client-request/client-request-entity"; 8 | import { RewriteRuleIdentity } from "./rewrite-rule-identity"; 9 | import { RewriteRuleModifierEntity } from "./rewrite-rule-modifier/rewrite-rule-modifier-entity"; 10 | import { RewriteRuleModifierIdentity } from "./rewrite-rule-modifier/rewrite-rule-modifier-identity"; 11 | import { RewriteRuleModifierMap } from "./value-objects/rewrite-rule-modifier-map"; 12 | import { RewriteRuleUrlPattern } from "./value-objects/rewrite-rule-url-pattern"; 13 | 14 | export class RewriteRuleEntity extends BaseEntity { 15 | constructor( 16 | identity: RewriteRuleIdentity, 17 | private url: RewriteRuleUrlPattern, 18 | private modifier: RewriteRuleModifierMap, 19 | ) { 20 | super(identity); 21 | } 22 | 23 | get json(): RewriteRuleEntityJSON { 24 | return { 25 | id: this.id, 26 | url: this.url.value, 27 | modifier: this.modifier.json, 28 | }; 29 | } 30 | 31 | addModifier( 32 | action: RewriteRuleActionType, 33 | modifier: RewriteRuleModifierEntity, 34 | ): void { 35 | return this.modifier.addModifier(action, modifier); 36 | } 37 | 38 | deleteModifier( 39 | action: RewriteRuleActionType, 40 | modifierId: RewriteRuleModifierIdentity, 41 | ): void { 42 | return this.modifier.deleteModifier(action, modifierId); 43 | } 44 | 45 | applyHeader( 46 | clientRequestEntity: ClientRequestEntity, 47 | header: OutgoingHttpHeaders, 48 | ): OutgoingHttpHeaders { 49 | if (!this.url.isMatchUrl(clientRequestEntity.pathSearch)) { 50 | return header; 51 | } 52 | return this.modifier.applyHeader(header); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/mixins/table-handler-mixin.ts: -------------------------------------------------------------------------------- 1 | export function makeTableHandlerMixin( 2 | deleteDispatcher: (store: any, entities: any[]) => Promise, 3 | ) { 4 | return { 5 | data() { 6 | return { 7 | contextMenuHandler: null, 8 | }; 9 | }, 10 | methods: { 11 | selectRow(row) { 12 | if (!this.$refs.table) return; 13 | this.$refs.table.toggleRowSelection(row); 14 | return; 15 | }, 16 | getTableStyle({ row, columnIndex }) { 17 | if (columnIndex) return ""; 18 | if (!this.$refs.table) return ""; 19 | if (!this.$refs.table.selection.includes(row)) return ""; 20 | return "current-row"; 21 | }, 22 | onClickDeleteButton(row) { 23 | return deleteDispatcher.call(this, this.$store, [row]); 24 | }, 25 | }, 26 | mounted() { 27 | this.$data.contextMenuHandler = this.$store.state.contextMenuService.subscribe( 28 | event => { 29 | if (event.type !== "append") { 30 | return null; 31 | } 32 | if (!this.$refs.table.selection.length) { 33 | return null; 34 | } 35 | return { 36 | click: () => 37 | deleteDispatcher.call( 38 | this, 39 | this.$store, 40 | this.$refs.table.selection, 41 | ), 42 | label: this.$t("delete"), 43 | }; 44 | }, 45 | ); 46 | }, 47 | async destroyed() { 48 | if (this.$data.contextMenuHandler) { 49 | await this.$data.contextMenuHandler(); 50 | this.$data.contextMenuHandler = null; 51 | } 52 | }, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifter/lifter", 3 | "description": "Local Proxy for frontend developers", 4 | "author": "kyo_ago", 5 | "bugs": { 6 | "url": "https://github.com/kyo-ago/lifter/issues" 7 | }, 8 | "devDependencies": { 9 | "@types/execa": "^0.9.0", 10 | "@types/micromatch": "^3.1.0", 11 | "@types/mocha": "^5.0.0", 12 | "@types/node": "^9.6.0", 13 | "@types/sinon": "^4.3.0", 14 | "fixpack": "^2.3.1", 15 | "lerna": "^2.9.0", 16 | "mocha": "^5.0.5", 17 | "mock-require": "^3.0.1", 18 | "prettier": "^1.11.1", 19 | "sinon": "^4.4.8", 20 | "typescript": "^2.7.2" 21 | }, 22 | "homepage": "https://github.com/kyo-ago/lifter", 23 | "keywords": [ 24 | "local proxy", 25 | "proxy" 26 | ], 27 | "license": "GPL-3.0", 28 | "lint-staged": { 29 | "*.{js,jsx,ts,tsx,css}": [ 30 | "prettier --write", 31 | "git add" 32 | ] 33 | }, 34 | "prettier": { 35 | "printWidth": 80, 36 | "tabWidth": 4 37 | }, 38 | "private": true, 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/kyo-ago/lifter.git" 42 | }, 43 | "scripts": { 44 | "app": "NODE_ENV=development lerna run dev --scope @lifter/lifter-app", 45 | "bootstrap": "lerna bootstrap", 46 | "build": "lerna run build", 47 | "clean": "yarn run clean:packages && yarn run clean:local", 48 | "clean:local": "rm -fr lerna-debug.log yarn-error.log package-lock.json yarn.lock node_modules app dist", 49 | "clean:packages": "rm -fr packages/*/{node_modules,npm-debug.log,package-lock.json,yarn.lock,yarn-error.log} && lerna run clean", 50 | "cli": "cd packages/lifter-cli && node ./", 51 | "fix": "lerna exec fixpack && yarn run fixpack", 52 | "postcommit": "git reset", 53 | "prepublish": "yarn run bootstrap", 54 | "prettier": "prettier --trailing-comma all --write '**/*.{js,jsx,ts,tsx,css,vue}'", 55 | "publish": "lerna publish", 56 | "publish:app": "lerna run release --stream --scope @lifter/lifter-app", 57 | "storybook": "lerna run storybook --scope @lifter/lifter-app", 58 | "test": "lerna run test --parallel" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/lifter-main/test/mocks/require-mocks/exec-commands/set-proxy-setting-state.ts: -------------------------------------------------------------------------------- 1 | import { ProxySettingStatus } from "@lifter/lifter-common"; 2 | import { NETWORK_HOST_NAME, PROXY_PORT } from "../../../../src/settings"; 3 | import { ExecCommandsStub } from "./exec-commands"; 4 | 5 | export const MockNetworksetupResult = `\nAn asterisk (*) denotes that a network service is disabled. 6 | (1) iPhone USB 7 | (Hardware Port: iPhone USB, Device: en2) 8 | 9 | (2) Wi Fi 10 | (Hardware Port: Wi-Fi, Device: en0) 11 | 12 | (3) Bluetooth PAN 13 | (Hardware Port: Bluetooth PAN, Device: en1)\n\n`; 14 | 15 | export const getMockWebproxyState = (param = {}) => { 16 | let command = { 17 | Enabled: "Yes", 18 | Server: NETWORK_HOST_NAME, 19 | Port: PROXY_PORT, 20 | ...param, 21 | }; 22 | return `\nEnabled: ${command.Enabled} 23 | Server: ${command.Server} 24 | Port: ${command.Port} 25 | Authenticated Proxy Enabled: 0\n`; 26 | }; 27 | 28 | export type MockProxySettingStatus = ProxySettingStatus | "initialize"; 29 | 30 | export function setProxySettingState(newState: MockProxySettingStatus) { 31 | if (newState === "On") { 32 | return setWebProxyingState; 33 | } 34 | if (newState === "Off") { 35 | return setNoWebProxyState; 36 | } 37 | if (newState === "initialize") { 38 | return setInitializedState; 39 | } 40 | console.error(`Invalid proxy setting state "${newState}".`); 41 | } 42 | 43 | let setNoWebProxyState = (stub: ExecCommandsStub) => { 44 | let command = { Enabled: "No" }; 45 | stub.getWebproxy.resolves(getMockWebproxyState(command)); 46 | stub.getSecureWebproxy.resolves(getMockWebproxyState(command)); 47 | }; 48 | 49 | let setWebProxyingState = (stub: ExecCommandsStub) => { 50 | stub.getWebproxy.resolves(getMockWebproxyState()); 51 | stub.getSecureWebproxy.resolves(getMockWebproxyState()); 52 | }; 53 | 54 | let setInitializedState = (stub: ExecCommandsStub) => { 55 | stub.getListnetworkserviceorder.resolves(MockNetworksetupResult); 56 | stub.getProxyByPassDomains.resolves(`\n*.local\n169.254/16\n`); 57 | setNoWebProxyState(stub); 58 | }; 59 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/auto-responder-entity.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import * as Path from "path"; 4 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 5 | import { ClientRequestFactory } from "../client-request/lifecycle/client-request-factory"; 6 | import { AutoResponderEntity } from "./auto-responder-entity"; 7 | import { AutoResponderFactory } from "./lifecycle/auto-responder-factory"; 8 | 9 | describe("AutoResponderEntity.getMatchResponder", () => { 10 | let autoResponderFactory: AutoResponderFactory; 11 | let clientRequestFactory: ClientRequestFactory; 12 | beforeEach(async () => { 13 | let container = await getTestContainer(); 14 | autoResponderFactory = container.get(AutoResponderFactory); 15 | clientRequestFactory = container.get(ClientRequestFactory); 16 | }); 17 | let createAutoResponderGlobEntity = ( 18 | pattern: string, 19 | path: string, 20 | ): AutoResponderEntity => { 21 | return autoResponderFactory.create(pattern, path); 22 | }; 23 | 24 | it("file path", async () => { 25 | let autoResponderGlobEntity = createAutoResponderGlobEntity( 26 | "/*", 27 | __filename, 28 | ); 29 | let clientRequestEntity = clientRequestFactory.createFromString( 30 | "/hoge", 31 | ); 32 | let result = await autoResponderGlobEntity.getMatchResponder( 33 | clientRequestEntity, 34 | ); 35 | assert(result.path === __filename); 36 | }); 37 | 38 | it("directory path", async () => { 39 | let filename = Path.basename(__filename); 40 | let autoResponderGlobEntity = createAutoResponderGlobEntity( 41 | "/*", 42 | __dirname, 43 | ); 44 | let clientRequestEntity = clientRequestFactory.createFromString( 45 | `/${filename}`, 46 | ); 47 | let result = await autoResponderGlobEntity.getMatchResponder( 48 | clientRequestEntity, 49 | ); 50 | assert(result.path === __filename); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/electron-window-manager/loadFailure.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 6 | 32 | 33 | 34 | 35 |
36 | 38 |
Unable to load the application interface, please try again later.
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/specs/find-match-entry.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../../test/mocks/get-test-container"; 4 | import { ClientRequestFactory } from "../../client-request/lifecycle/client-request-factory"; 5 | import { LocalFileResponseFactory } from "../../local-file-response/lifecycle/local-file-response-factory"; 6 | import { AutoResponderFactory } from "../lifecycle/auto-responder-factory"; 7 | import { FindMatchEntry } from "./find-match-entry"; 8 | 9 | describe("FindMatchEntry.getLocalFileResponse", () => { 10 | let autoResponderFactory: AutoResponderFactory; 11 | let localFileResponseFactory: LocalFileResponseFactory; 12 | let clientRequestFactory: ClientRequestFactory; 13 | beforeEach(async () => { 14 | let container = await getTestContainer(); 15 | autoResponderFactory = container.get(AutoResponderFactory); 16 | localFileResponseFactory = container.get(LocalFileResponseFactory); 17 | clientRequestFactory = container.get(ClientRequestFactory); 18 | }); 19 | 20 | it("success", async () => { 21 | let abstractAutoResponderEntity = await autoResponderFactory.create( 22 | "/hoge", 23 | __filename, 24 | ); 25 | let findMatchEntry = new FindMatchEntry(localFileResponseFactory); 26 | let localFileResponseEntity = await findMatchEntry.getLocalFileResponse( 27 | Promise.resolve(null), 28 | clientRequestFactory.createFromString("/hoge"), 29 | abstractAutoResponderEntity, 30 | ); 31 | assert(localFileResponseEntity); 32 | }); 33 | 34 | it("failed", async () => { 35 | let abstractAutoResponderEntity = await autoResponderFactory.create( 36 | "/hoge", 37 | __filename, 38 | ); 39 | let findMatchEntry = new FindMatchEntry(localFileResponseFactory); 40 | let localFileResponseEntity = await findMatchEntry.getLocalFileResponse( 41 | Promise.resolve(null), 42 | clientRequestFactory.createFromString("/huga"), 43 | abstractAutoResponderEntity, 44 | ); 45 | assert(!localFileResponseEntity); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/lifter-main/src/application/proxy/proxy-service.ts: -------------------------------------------------------------------------------- 1 | import * as HttpMitmProxy from "http-mitm-proxy"; 2 | import { injectable } from "inversify"; 3 | import { promisify } from "util"; 4 | import { ClientRequestService } from "../../domains/proxy/client-request/client-request-service"; 5 | import { ClientResponderContext } from "../../domains/proxy/client-request/lib/client-responder-context"; 6 | import { SslCertificatePath } from "../../libs/ssl-certificate-path"; 7 | import { BIND_HOST_NAME, PROXY_PORT } from "../../settings"; 8 | 9 | @injectable() 10 | export class ProxyService { 11 | private mitmProxy: HttpMitmProxy.IProxy = HttpMitmProxy(); 12 | 13 | constructor( 14 | private sslCertificatePath: SslCertificatePath, 15 | private clientRequestService: ClientRequestService, 16 | ) {} 17 | 18 | async listen() { 19 | this.mitmProxy.onError( 20 | ( 21 | context: HttpMitmProxy.IContext | null, 22 | err?: Error, 23 | errorKind?: string, 24 | ) => { 25 | // context may be null 26 | let url = 27 | context && context.clientToProxyRequest 28 | ? context.clientToProxyRequest.url 29 | : ""; 30 | console.error(`${errorKind} on ${url}:${err}`); 31 | }, 32 | ); 33 | 34 | this.mitmProxy.onRequest( 35 | async ( 36 | ctx: HttpMitmProxy.IContext, 37 | passCallback: (error: Error | undefined) => void, 38 | ) => { 39 | let clientResponderContext = new ClientResponderContext( 40 | ctx, 41 | passCallback, 42 | ); 43 | /// ignore promise 44 | void this.clientRequestService.onRequest( 45 | clientResponderContext, 46 | ); 47 | }, 48 | ); 49 | 50 | await promisify(this.mitmProxy.listen.bind(this.mitmProxy))({ 51 | port: PROXY_PORT, 52 | host: BIND_HOST_NAME, 53 | silent: true, 54 | sslCaDir: this.sslCertificatePath.getCaDir(), 55 | }); 56 | } 57 | 58 | shutdown() { 59 | this.mitmProxy.close(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/pac-file/pac-file-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import * as Path from "path"; 4 | import * as sinon from "sinon"; 5 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 6 | import { PROXY_SERVER_NAME } from "../../../settings"; 7 | import { AutoResponderService } from "../../proxy/auto-responder/auto-responder-service"; 8 | import { ClientResponderContext } from "../../proxy/client-request/lib/client-responder-context"; 9 | import { PacFileService } from "./pac-file-service"; 10 | 11 | describe("PacFileService", () => { 12 | let pacFileService: PacFileService; 13 | let autoResponderService: AutoResponderService; 14 | let sandbox = sinon.createSandbox(); 15 | let clientResponderContext = sandbox.createStubInstance( 16 | ClientResponderContext, 17 | ); 18 | 19 | beforeEach(async () => { 20 | let container = await getTestContainer(); 21 | pacFileService = container.get(PacFileService); 22 | autoResponderService = container.get(AutoResponderService); 23 | }); 24 | afterEach(() => { 25 | sandbox.resetHistory(); 26 | }); 27 | 28 | it("getContent.header", async () => { 29 | await pacFileService.response(clientResponderContext); 30 | let spyCall = clientResponderContext.response.lastCall; 31 | assert(spyCall.args[0]["content-length"] === spyCall.args[1].length); 32 | assert( 33 | spyCall.args[0]["content-type"] === 34 | "application/x-ns-proxy-autoconfig", 35 | ); 36 | }); 37 | it("getContent.body", async () => { 38 | await autoResponderService.store([__filename]); 39 | await pacFileService.response(clientResponderContext); 40 | let spyCall = clientResponderContext.response.lastCall; 41 | let func = new Function(`return (${spyCall.args[1]})`); 42 | assert(func()("") === "DIRECT"); 43 | let basename = Path.basename(__filename); 44 | assert(func()(`/${basename}`) === `PROXY ${PROXY_SERVER_NAME}`); 45 | assert(func()(`/hoge/${basename}`) === `PROXY ${PROXY_SERVER_NAME}`); 46 | assert(func()(`/${basename}.hoge`) === "DIRECT"); 47 | assert(func()(`/hoge/${basename}.hoge`) === "DIRECT"); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/proxy-bypass-domain/proxy-bypass-domain-service.ts: -------------------------------------------------------------------------------- 1 | import { ProxyBypassDomainEntityJSON } from "@lifter/lifter-common"; 2 | import { injectable } from "inversify"; 3 | import { NetworksetupProxyService } from "../networksetup-proxy/networksetup-proxy-service"; 4 | import { ProxyBypassDomainFactory } from "./lifecycle/proxy-bypass-domain-factory"; 5 | import { ProxyBypassDomainRepository } from "./lifecycle/proxy-bypass-domain-repository"; 6 | 7 | export interface getProxyBypassDomains { 8 | applyAll: (domains: string[]) => Promise; 9 | fetchAll: () => Promise; 10 | } 11 | 12 | @injectable() 13 | export class ProxyBypassDomainService { 14 | constructor( 15 | private proxyBypassDomainFactory: ProxyBypassDomainFactory, 16 | private proxyBypassDomainRepository: ProxyBypassDomainRepository, 17 | private networksetupProxyService: NetworksetupProxyService, 18 | ) {} 19 | 20 | load(): Promise { 21 | return this.setProxyBypassDomains(); 22 | } 23 | 24 | getProxyBypassDomains(): getProxyBypassDomains { 25 | return { 26 | applyAll: (domains: string[]): Promise => { 27 | return this.overwriteAll(domains); 28 | }, 29 | fetchAll: (): Promise => { 30 | return this.fetchAllJSONs(); 31 | }, 32 | }; 33 | } 34 | 35 | private async overwriteAll(domains: string[]): Promise { 36 | let entities = domains.map(domain => 37 | this.proxyBypassDomainFactory.create(domain), 38 | ); 39 | await this.proxyBypassDomainRepository.overwriteAll(entities); 40 | return await this.setProxyBypassDomains(); 41 | } 42 | 43 | private async fetchAllJSONs(): Promise { 44 | let allEntities = await this.proxyBypassDomainRepository.resolveAll(); 45 | return allEntities.map(entity => entity.json); 46 | } 47 | 48 | private async setProxyBypassDomains(): Promise { 49 | let proxyBypassDomainEntities = await this.proxyBypassDomainRepository.resolveAll(); 50 | return await this.networksetupProxyService.setProxyBypassDomains( 51 | proxyBypassDomainEntities, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/auto-responder-entity.ts: -------------------------------------------------------------------------------- 1 | import { AutoResponderEntityJSON } from "@lifter/lifter-common"; 2 | import * as mime from "mime"; 3 | import { BaseEntity } from "../../base/base-entity"; 4 | import { ClientRequestEntity } from "../client-request/client-request-entity"; 5 | import { LocalFileResponseParam } from "../local-file-response/lifecycle/local-file-response-factory"; 6 | import { ProjectIdentity } from "../project/project-identity"; 7 | import { AutoResponderIdentity } from "./auto-responder-identity"; 8 | import { AutoResponderAnyPath } from "./value-objects/auto-responder-any-path"; 9 | import { AutoResponderFilePath } from "./value-objects/auto-responder-file-path"; 10 | import { AutoResponderPattern } from "./value-objects/auto-responder-pattern"; 11 | 12 | export class AutoResponderEntity extends BaseEntity { 13 | constructor( 14 | identity: AutoResponderIdentity, 15 | public pattern: AutoResponderPattern, 16 | public path: AutoResponderAnyPath, 17 | public projectIdentity: ProjectIdentity, 18 | ) { 19 | super(identity); 20 | } 21 | 22 | async getMatchResponder( 23 | clientRequestEntity: ClientRequestEntity, 24 | ): Promise { 25 | if (!this.pattern.isMatchPath(clientRequestEntity)) return null; 26 | 27 | let filePath = await this.path.getAutoResponderFilePath( 28 | clientRequestEntity, 29 | ); 30 | 31 | return filePath 32 | ? this.filePathToLocalFileResponseParam(filePath) 33 | : null; 34 | } 35 | 36 | get json(): AutoResponderEntityJSON { 37 | return { 38 | id: this.id, 39 | pattern: this.pattern.value, 40 | path: this.path.value, 41 | projectId: this.projectIdentity.getValue(), 42 | }; 43 | } 44 | 45 | protected async filePathToLocalFileResponseParam( 46 | filePath: AutoResponderFilePath, 47 | ): Promise { 48 | let stats; 49 | try { 50 | stats = await filePath.getState(); 51 | } catch (e) { 52 | // missing file 53 | return null; 54 | } 55 | return { 56 | path: filePath.value, 57 | type: mime.getType(filePath.value), 58 | size: stats.size, 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/networksetup-proxy/networksetup-proxy-service.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { NetworkInterfaceEntity } from "../network-interface/network-interface-entity"; 3 | import { NetworkInterfaceService } from "../network-interface/network-interface-service"; 4 | import { ProxyBypassDomainEntity } from "../proxy-bypass-domain/proxy-bypass-domain-entity"; 5 | import { NetworksetupProxyContainer } from "./networksetup-proxy-container"; 6 | 7 | @injectable() 8 | export class NetworksetupProxyService { 9 | constructor( 10 | private networksetupProxyCommand: NetworksetupProxyContainer, 11 | private networkInterfaceService: NetworkInterfaceService, 12 | ) {} 13 | 14 | enableProxy(): Promise { 15 | return this.callAllInterface(ni => 16 | ni.enableProxy(this.networksetupProxyCommand.getCommand()), 17 | ); 18 | } 19 | 20 | disableProxy(): Promise { 21 | return this.callAllInterface(ni => 22 | ni.disableProxy(this.networksetupProxyCommand.getCommand()), 23 | ); 24 | } 25 | 26 | clearAutoProxyUrl() { 27 | return this.callAllInterface(ni => 28 | ni.clearAutoProxyUrl(this.networksetupProxyCommand.getCommand()), 29 | ); 30 | } 31 | 32 | setAutoProxyUrl() { 33 | return this.callAllInterface(ni => 34 | ni.setAutoProxyUrl(this.networksetupProxyCommand.getCommand()), 35 | ); 36 | } 37 | 38 | reloadAutoProxyUrl() { 39 | return this.callAllInterface(ni => 40 | ni.reloadAutoProxyUrl(this.networksetupProxyCommand.getCommand()), 41 | ); 42 | } 43 | 44 | setProxyBypassDomains( 45 | proxyBypassDomainEntities: ProxyBypassDomainEntity[], 46 | ) { 47 | return this.callAllInterface(async ni => { 48 | await ni.setProxyBypassDomains( 49 | this.networksetupProxyCommand.getCommand(), 50 | proxyBypassDomainEntities, 51 | ); 52 | }); 53 | } 54 | 55 | private async callAllInterface( 56 | callback: ( 57 | networkInterfaceEntity: NetworkInterfaceEntity, 58 | ) => Promise, 59 | ): Promise { 60 | let networkInterfaceEntities = await this.networkInterfaceService.fetchAllInterface(); 61 | await Promise.all(networkInterfaceEntities.map(ni => callback(ni))); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-header/left-toolbar.vue: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "en-US": { 4 | "NoProxyCommandGrant": "Require granted for the proxy command", 5 | "NoTargetInterfaces": "Network disabled", 6 | "Off": "Disable proxy", 7 | "On": "Enable proxy" 8 | }, 9 | "ja": { 10 | "NoProxyCommandGrant": "Proxy commandに権限が付与されていません", 11 | "NoTargetInterfaces": "有効なネットワークインターフェイスが見つかりません", 12 | "Off": "Proxyが無効です", 13 | "On": "Proxyが有効です" 14 | } 15 | } 16 | 17 | 18 | 34 | 35 | 74 | 75 | 86 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-dialog/rewrite-rule-modifiers-dialog/delete-modifiers-table.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 71 | 72 | 74 | -------------------------------------------------------------------------------- /packages/lifter-main/src/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "inversify"; 2 | import "reflect-metadata"; 3 | import { AutoResponderRepository } from "./domains/proxy/auto-responder/lifecycle/auto-responder-repositoty"; 4 | import { ProjectFactory } from "./domains/proxy/project/lifecycle/project-factory"; 5 | import { ProjectEntity } from "./domains/proxy/project/project-entity"; 6 | import { RewriteRuleFactory } from "./domains/proxy/rewrite-rule/lifecycle/rewrite-rule-factory"; 7 | import { RewriteRuleRepository } from "./domains/proxy/rewrite-rule/lifecycle/rewrite-rule-repository"; 8 | import { ProxyBypassDomainRepository } from "./domains/settings/proxy-bypass-domain/lifecycle/proxy-bypass-domain-repository"; 9 | import { ProxyBypassDomainService } from "./domains/settings/proxy-bypass-domain/proxy-bypass-domain-service"; 10 | import { ProxyCommandGrantService } from "./domains/settings/proxy-command-grant/proxy-command-grant-service"; 11 | import { UserSettingsStorage } from "./domains/settings/user-settings/user-settings-storage"; 12 | import { ProxyCommandPath } from "./libs/proxy-command-path"; 13 | import { SslCertificatePath } from "./libs/ssl-certificate-path"; 14 | import { UserKeychainsPath } from "./libs/user-keychains-path"; 15 | 16 | export async function getContainer( 17 | projectBaseDir: string, 18 | userDataPath: string, 19 | userHomePath: string, 20 | ): Promise { 21 | let container = new Container({ 22 | defaultScope: "Singleton", 23 | autoBindInjectable: true, 24 | skipBaseClassChecks: true, 25 | }); 26 | container 27 | .bind(SslCertificatePath) 28 | .toConstantValue(new SslCertificatePath(userDataPath)); 29 | container 30 | .bind(UserKeychainsPath) 31 | .toConstantValue(new UserKeychainsPath(userHomePath)); 32 | container 33 | .bind(ProjectEntity) 34 | .toConstantValue(new ProjectFactory().create(projectBaseDir)); 35 | container 36 | .bind(ProxyCommandPath) 37 | .toConstantValue(await ProxyCommandPath.getPath()); 38 | 39 | await Promise.all([ 40 | container.get(AutoResponderRepository).load(), 41 | container.get(RewriteRuleFactory).load(), 42 | container.get(RewriteRuleRepository).load(), 43 | container.get(ProxyBypassDomainRepository).load(), 44 | container.get(UserSettingsStorage).load(), 45 | ]); 46 | 47 | await Promise.all([container.get(ProxyCommandGrantService).load()]); 48 | await container.get(ProxyBypassDomainService).load(); 49 | 50 | return container; 51 | } 52 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/settings/user-settings/user-settings-storage.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import * as Datastore from "nedb"; 3 | import { promisify } from "util"; 4 | import { ProjectEntity } from "../../proxy/project/project-entity"; 5 | 6 | export interface UserSettings { 7 | autoEnableProxy: boolean; 8 | pacFileProxy: boolean; 9 | } 10 | 11 | const DefaultUserSettings: UserSettings = { 12 | autoEnableProxy: true, 13 | pacFileProxy: false, 14 | }; 15 | 16 | @injectable() 17 | export class UserSettingsStorage { 18 | private userSettings: UserSettings = DefaultUserSettings; 19 | 20 | private readonly datastore: Datastore; 21 | private readonly find: (query: any) => Promise; 22 | private readonly update: ( 23 | query: any, 24 | value: any, 25 | option: any, 26 | ) => Promise; 27 | private readonly loadDatabase: () => Promise; 28 | 29 | constructor(projectEntity: ProjectEntity) { 30 | let dataStoreOptions = projectEntity.getDataStoreOptions( 31 | UserSettingsStorage.name, 32 | ); 33 | this.datastore = new Datastore(dataStoreOptions); 34 | 35 | this.find = promisify(this.datastore.find.bind(this.datastore)); 36 | this.update = promisify(this.datastore.update.bind(this.datastore)); 37 | this.loadDatabase = promisify( 38 | this.datastore.loadDatabase.bind(this.datastore), 39 | ); 40 | } 41 | 42 | async load(): Promise { 43 | await this.loadDatabase(); 44 | let line = await this.find({}); 45 | this.userSettings = line.reduce((base, cur) => { 46 | base[cur.name] = cur.value; 47 | return base; 48 | }, this.userSettings); 49 | this.userSettings.pacFileProxy = false; 50 | } 51 | 52 | resolve(name: S): UserSettings[S] { 53 | return this.userSettings[name]; 54 | } 55 | 56 | toggle(name: S): Promise { 57 | return this.store(name, !this.userSettings[name]); 58 | } 59 | 60 | private async store( 61 | name: S, 62 | value: UserSettings[S], 63 | ): Promise { 64 | await this.update( 65 | { name: name }, 66 | { 67 | name: name, 68 | value: value, 69 | }, 70 | { upsert: true }, 71 | ); 72 | this.userSettings[name] = value; 73 | return value; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/application/application.mock.ts: -------------------------------------------------------------------------------- 1 | import * as sinon from "sinon"; 2 | import { ApplicationMainStateJSON } from "../../main/application-main-state"; 3 | import { Application } from "./application"; 4 | import { ContextMenuService } from "./context-menu/context-menu-service"; 5 | 6 | let sandbox = sinon.createSandbox(); 7 | 8 | let applicationMock = sandbox.createStubInstance(Application); 9 | 10 | setApplicationMockState(); 11 | 12 | let contextMenuService = sandbox.createStubInstance( 13 | ContextMenuService, 14 | ); 15 | applicationMock.contextMenuService = contextMenuService; 16 | applicationMock.addDropFiles.resolves([]); 17 | applicationMock.selectDialogEntry.resolves([]); 18 | applicationMock.fetchAutoResponderEntities.resolves([]); 19 | applicationMock.deleteAutoResponderEntities.resolves(); 20 | applicationMock.changeCertificateStatus.resolves("Installed"); 21 | applicationMock.changeProxySettingStatus.resolves("On"); 22 | applicationMock.changeProxyCommandGrantStatus.resolves("On"); 23 | applicationMock.changeAutoEnableProxySetting.resolves("Off"); 24 | applicationMock.changePacFileProxySetting.resolves("Off"); 25 | 26 | applicationMock.getProxyBypassDomains.resolves([]); 27 | applicationMock.saveProxyBypassDomains.resolves(undefined); 28 | 29 | applicationMock.getRewriteRules.resolves([]); 30 | applicationMock.addRewriteRule.callsFake((url: string) => { 31 | return { 32 | id: 1, 33 | url: url, 34 | modifier: [], 35 | }; 36 | }); 37 | applicationMock.deleteRewriteRules.resolves(undefined); 38 | applicationMock.addRewriteRuleModifier.resolves(undefined); 39 | applicationMock.deleteRewriteRuleModifiers.resolves(undefined); 40 | 41 | if ("undefined" !== typeof afterEach) { 42 | afterEach(() => { 43 | sandbox.resetHistory(); 44 | }); 45 | } 46 | 47 | export const ApplicationMock = applicationMock; 48 | 49 | export function setApplicationMockState( 50 | state: Partial = {}, 51 | ) { 52 | applicationMock.getCurrentState.callsFake((): ApplicationMainStateJSON => ({ 53 | autoResponderEntries: [], 54 | clientRequestEntries: [], 55 | proxyBypassDomainEntries: [], 56 | rewriteRuleEntries: [], 57 | certificateState: "Missing", 58 | certificateCommands: [], 59 | proxySettingStatus: "Off", 60 | proxyCommandGrantStatus: "Off", 61 | proxyCommandGrantCommands: [], 62 | autoEnableProxySetting: "Off", 63 | pacFileProxySetting: "Off", 64 | ...state, 65 | })); 66 | } 67 | -------------------------------------------------------------------------------- /packages/lifter-main/src/application/settings/proxy-setting/proxy-setting-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { MockStateEvent } from "../../../../test/mocks/mock-state-event"; 5 | import { ProxyCommandGrantService } from "../../../domains/settings/proxy-command-grant/proxy-command-grant-service"; 6 | import { UserSettingsService } from "../../../domains/settings/user-settings/user-settings-service"; 7 | import { ProxySettingService } from "./proxy-setting-service"; 8 | 9 | describe("ProxySettingService", () => { 10 | let proxySettingService: ProxySettingService; 11 | let proxyCommandGrantService: ProxyCommandGrantService; 12 | let userSettingsService: UserSettingsService; 13 | beforeEach(async () => { 14 | let container = await getTestContainer(); 15 | proxySettingService = container.get(ProxySettingService); 16 | proxyCommandGrantService = container.get(ProxyCommandGrantService); 17 | userSettingsService = container.get(UserSettingsService); 18 | }); 19 | 20 | let getNetworksetupProxyService = () => { 21 | return proxySettingService.getProxySettingService(); 22 | }; 23 | 24 | it("fetchProxyCommandGrantStatus", async () => { 25 | let result = await getNetworksetupProxyService().fetch(); 26 | assert(result === "Off"); 27 | }); 28 | 29 | [["initialize", "On"], ["Off", "On"], ["On", "Off"]].forEach( 30 | ([from, to]) => { 31 | it(`change ${from} to ${to}`, async () => { 32 | MockStateEvent.emit("updateProxySettingState", from); 33 | let result = await getNetworksetupProxyService().change(); 34 | assert(result === to); 35 | }); 36 | }, 37 | ); 38 | 39 | it("change to fetch", async () => { 40 | await userSettingsService.isAutoEnableProxy({ 41 | On: () => userSettingsService.changePacFileProxy(), 42 | }); 43 | 44 | MockStateEvent.emit("updateProxyCommandGrantStatus", "On"); 45 | MockStateEvent.emit("updateProxySettingState", "Off"); 46 | 47 | // reload updateProxyCommandGrantStatus 48 | await proxyCommandGrantService.load(); 49 | 50 | let result = await getNetworksetupProxyService().change(); 51 | assert(result === "On"); 52 | 53 | let fetchResult = await getNetworksetupProxyService().fetch(); 54 | assert(fetchResult === "On"); 55 | 56 | await userSettingsService.changePacFileProxy(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/lifecycle/auto-responder-factory.ts: -------------------------------------------------------------------------------- 1 | import { AutoResponderEntityJSON } from "@lifter/lifter-common"; 2 | import * as fs from "fs"; 3 | import { injectable } from "inversify"; 4 | import * as Path from "path"; 5 | import { AsyncNedbIdGenerator } from "../../../base/async-nedb-id-generator"; 6 | import { ProjectEntity } from "../../project/project-entity"; 7 | import { ProjectIdentity } from "../../project/project-identity"; 8 | import { AutoResponderEntity } from "../auto-responder-entity"; 9 | import { AutoResponderIdentity } from "../auto-responder-identity"; 10 | import { AutoResponderAnyPath } from "../value-objects/auto-responder-any-path"; 11 | import { AutoResponderPattern } from "../value-objects/auto-responder-pattern"; 12 | 13 | @injectable() 14 | export class AutoResponderFactory extends AsyncNedbIdGenerator { 15 | constructor(private projectEntity: ProjectEntity) { 16 | super(projectEntity.getDataStoreOptions(AutoResponderFactory.name)); 17 | } 18 | 19 | static fromJSON(autoResponderEntityJSON: AutoResponderEntityJSON) { 20 | return new AutoResponderEntity( 21 | new AutoResponderIdentity(autoResponderEntityJSON.id), 22 | new AutoResponderPattern(autoResponderEntityJSON.pattern), 23 | new AutoResponderAnyPath(autoResponderEntityJSON.path), 24 | new ProjectIdentity(autoResponderEntityJSON.projectId), 25 | ); 26 | } 27 | 28 | create(pattern: string, path: string): AutoResponderEntity { 29 | return new AutoResponderEntity( 30 | new AutoResponderIdentity(this.getNextIdNumber()), 31 | new AutoResponderPattern(pattern), 32 | new AutoResponderAnyPath(path), 33 | this.projectEntity.getIdentity(), 34 | ); 35 | } 36 | 37 | createFromFile(file: File): Promise { 38 | return this.createFrom(file.name, (file).path); 39 | } 40 | 41 | createFromPath(path: string): Promise { 42 | return this.createFrom(Path.basename(path), path); 43 | } 44 | 45 | private createFrom( 46 | pattern: string, 47 | path: string, 48 | ): Promise { 49 | return new Promise((resolve, reject) => { 50 | fs.stat(path, err => { 51 | if (err) { 52 | return reject(err); 53 | } 54 | let autoResponderEntity = this.create(pattern, path); 55 | resolve(autoResponderEntity); 56 | }); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/file-watcher/test.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const assert = require("assert"); 3 | const sinon = require("sinon"); 4 | const watch = require("./"); 5 | 6 | describe("file-watch", () => { 7 | let testFileName = `${__dirname}/test.tmp`; 8 | afterEach(() => { 9 | fs.existsSync(testFileName) && fs.unlinkSync(testFileName); 10 | }); 11 | let waitFor = check => { 12 | return new Promise(resolve => { 13 | let interva = setInterval(() => { 14 | if (check()) { 15 | return; 16 | } 17 | clearInterval(interva); 18 | resolve(); 19 | }, 5); 20 | }); 21 | }; 22 | 23 | it("watch", () => { 24 | let spy = sinon.spy(); 25 | let unwatch = watch(__filename, spy); 26 | unwatch(); 27 | assert(!spy.called); 28 | }); 29 | 30 | it("create", async () => { 31 | let spy = sinon.spy(); 32 | let unwatch = watch(testFileName, spy, 0); 33 | fs.writeFileSync(testFileName, "hoge"); 34 | await waitFor(() => !spy.called); 35 | unwatch(); 36 | assert(spy.called); 37 | }); 38 | 39 | it("write", async () => { 40 | let spy = sinon.spy(); 41 | fs.writeFileSync(testFileName, "hoge"); 42 | let unwatch = watch(testFileName, spy, 0); 43 | fs.writeFileSync(testFileName, "huga"); 44 | await waitFor(() => !spy.called); 45 | unwatch(); 46 | assert(spy.called); 47 | }); 48 | 49 | it("unlink", async () => { 50 | let spy = sinon.spy(); 51 | fs.writeFileSync(testFileName, "hoge"); 52 | let unwatch = watch(testFileName, spy, 0); 53 | fs.unlinkSync(testFileName); 54 | await waitFor(() => !spy.called); 55 | unwatch(); 56 | assert(spy.called); 57 | }); 58 | 59 | it("rename", async () => { 60 | let newPath = `${__dirname}/test1.tmp`; 61 | let spy = sinon.spy(); 62 | fs.writeFileSync(testFileName, "hoge"); 63 | let unwatch = watch(testFileName, spy, 0); 64 | fs.renameSync(testFileName, newPath); 65 | await waitFor(() => !spy.called); 66 | unwatch(); 67 | assert(spy.called); 68 | fs.unlinkSync(newPath); 69 | }); 70 | 71 | it("ignore other file", async () => { 72 | let newPath = `${__dirname}/test1.tmp`; 73 | let spy = sinon.spy(); 74 | let unwatch = watch(testFileName, spy, 0); 75 | fs.writeFileSync(newPath, "hoge"); 76 | await new Promise(resolve => setTimeout(resolve, 50)); 77 | unwatch(); 78 | assert(!spy.called); 79 | fs.unlinkSync(newPath); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/lifter-cli/index.js: -------------------------------------------------------------------------------- 1 | const { createApplication } = require("@lifter/lifter-main"); 2 | const inquirer = require("inquirer"); 3 | 4 | (async () => { 5 | let application = await createApplication( 6 | `${__dirname}/repositories`, 7 | __dirname, 8 | process.env.HOME, 9 | ); 10 | 11 | await application.startup(); 12 | 13 | let answer = await inquirer.prompt({ 14 | type: "list", 15 | name: "method", 16 | message: "call method", 17 | choices: [ 18 | { 19 | name: 20 | "application.getCertificateService().fetchCurrentStatus()", 21 | value: application.getCertificateService().fetchCurrentStatus, 22 | }, 23 | { 24 | name: 25 | "application.getCertificateService().changeCertificateStatus()", 26 | value: application.getCertificateService() 27 | .changeCertificateStatus, 28 | }, 29 | { 30 | name: "application.getProxyCommandGrantService().fetchStatus()", 31 | value: application.getProxyCommandGrantService().fetchStatus, 32 | }, 33 | { 34 | name: 35 | "application.getProxyCommandGrantService().changeStatus()", 36 | value: application.getProxyCommandGrantService().changeStatus, 37 | }, 38 | { 39 | name: "application.getUserSetting().getAutoEnableProxy()", 40 | value: application.getUserSetting().getAutoEnableProxy, 41 | }, 42 | { 43 | name: "application.getUserSetting().changePacFileProxy()", 44 | value: application.getUserSetting().changePacFileProxy, 45 | }, 46 | { 47 | name: "application.getUserSetting().getPacFileProxy()", 48 | value: application.getUserSetting().getPacFileProxy, 49 | }, 50 | { 51 | name: "application.getUserSetting().changeAutoEnableProxy()", 52 | value: application.getUserSetting().changeAutoEnableProxy, 53 | }, 54 | { 55 | name: "application.getProxySettingService().fetch()", 56 | value: application.getProxySettingService().fetch, 57 | }, 58 | { 59 | name: "application.getProxySettingService().change()", 60 | value: application.getProxySettingService().change, 61 | }, 62 | ], 63 | }); 64 | let result = await answer.method(); 65 | console.log(result); 66 | 67 | await application.shutdown(); 68 | 69 | process.exit(0); 70 | })(); 71 | -------------------------------------------------------------------------------- /packages/lifter-app/src/main/application-main-state.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AutoEnableProxyStatus, 3 | AutoResponderEntityJSON, 4 | CertificateStatus, 5 | ClientRequestEntityJSON, 6 | PacFileProxyStatus, 7 | ProxyBypassDomainEntityJSON, 8 | ProxyCommandGrantStatus, 9 | ProxySettingStatus, 10 | RewriteRuleEntityJSON, 11 | } from "@lifter/lifter-common"; 12 | import { Application } from "@lifter/lifter-main"; 13 | 14 | export interface ApplicationMainStateJSON { 15 | autoResponderEntries: AutoResponderEntityJSON[]; 16 | clientRequestEntries: ClientRequestEntityJSON[]; 17 | proxyBypassDomainEntries: ProxyBypassDomainEntityJSON[]; 18 | rewriteRuleEntries: RewriteRuleEntityJSON[]; 19 | certificateState: CertificateStatus; 20 | certificateCommands: string[]; 21 | proxySettingStatus: ProxySettingStatus; 22 | proxyCommandGrantStatus: ProxyCommandGrantStatus; 23 | proxyCommandGrantCommands: string[]; 24 | autoEnableProxySetting: AutoEnableProxyStatus; 25 | pacFileProxySetting: PacFileProxyStatus; 26 | } 27 | 28 | export async function getApplicationMainStateJSON( 29 | application: Application, 30 | ): Promise { 31 | // Promise.all is max 10 arguments (d.ts limit) 32 | let [ 33 | autoResponderEntries, 34 | clientRequestEntries, 35 | proxyBypassDomainEntries, 36 | rewriteRuleEntries, 37 | certificateState, 38 | ] = await Promise.all([ 39 | application.getAutoResponder().fetchAll(), 40 | application.getClientRequestService().fetchAll(), 41 | application.getProxyBypassDomains().fetchAll(), 42 | application.getRewriteRules().fetchAll(), 43 | application.getCertificateService().fetchCurrentStatus(), 44 | ]); 45 | let [ 46 | certificateCommands, 47 | proxySettingStatus, 48 | proxyCommandGrantStatus, 49 | proxyCommandGrantCommands, 50 | autoEnableProxySetting, 51 | pacFileProxySetting, 52 | ] = await Promise.all([ 53 | application.getCertificateService().fetchCurrentCommands(), 54 | application.getProxySettingService().fetch(), 55 | application.getProxyCommandGrantService().fetchStatus(), 56 | application.getProxyCommandGrantService().fetchCommands(), 57 | application.getUserSetting().getAutoEnableProxy(), 58 | application.getUserSetting().getPacFileProxy(), 59 | ]); 60 | 61 | return { 62 | autoResponderEntries, 63 | clientRequestEntries, 64 | proxyBypassDomainEntries, 65 | rewriteRuleEntries, 66 | certificateState, 67 | certificateCommands, 68 | proxySettingStatus, 69 | proxyCommandGrantStatus, 70 | proxyCommandGrantCommands, 71 | autoEnableProxySetting, 72 | pacFileProxySetting, 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/value-objects/auto-responder-any-path.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import * as Path from "path"; 4 | import { getTestContainer } from "../../../../../test/mocks/get-test-container"; 5 | import { ClientRequestFactory } from "../../client-request/lifecycle/client-request-factory"; 6 | import { AutoResponderAnyPath } from "./auto-responder-any-path"; 7 | 8 | describe("AutoResponderAnyPath", () => { 9 | describe("getAutoResponderFilePath", () => { 10 | let dirname = Path.basename(__dirname); 11 | let filename = Path.basename(__filename); 12 | 13 | let clientRequestFactory: ClientRequestFactory; 14 | beforeEach(async () => { 15 | clientRequestFactory = (await getTestContainer()).get( 16 | ClientRequestFactory, 17 | ); 18 | }); 19 | 20 | [ 21 | { 22 | name: "match file", 23 | path: __filename, 24 | request: `/${dirname}/${filename}`, 25 | result: __filename, 26 | }, 27 | { 28 | name: "match unknown file", 29 | path: __filename, 30 | request: `/${dirname}/hoge.txt`, 31 | result: __filename, 32 | }, 33 | { 34 | name: "match directory", 35 | path: __dirname, 36 | request: `/${dirname}/${filename}`, 37 | result: __filename, 38 | }, 39 | { 40 | name: "match root directory", 41 | path: __dirname, 42 | request: `/${filename}`, 43 | result: __filename, 44 | }, 45 | ].forEach(pattern => { 46 | it(pattern.name, async () => { 47 | let autoResponderAnyPath = new AutoResponderAnyPath( 48 | pattern.path, 49 | ); 50 | let clientRequestEntity = clientRequestFactory.createFromString( 51 | pattern.request, 52 | ); 53 | let result = await autoResponderAnyPath.getAutoResponderFilePath( 54 | clientRequestEntity, 55 | ); 56 | assert(result.value === pattern.result); 57 | }); 58 | }); 59 | 60 | it("unmatch directory", async () => { 61 | let autoResponderAnyPath = new AutoResponderAnyPath(__dirname); 62 | let clientRequestEntity = clientRequestFactory.createFromString( 63 | `/hoge/${filename}`, 64 | ); 65 | let result = await autoResponderAnyPath.getAutoResponderFilePath( 66 | clientRequestEntity, 67 | ); 68 | assert(result === null); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-dialog/rewrite-rule-modifiers-dialog/update-modifiers-table.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 79 | 80 | 82 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/value-objects/auto-responder-pattern.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../../test/mocks/get-test-container"; 4 | import { ClientRequestFactory } from "../../client-request/lifecycle/client-request-factory"; 5 | import { AutoResponderPattern } from "./auto-responder-pattern"; 6 | 7 | describe("AutoResponderPattern", () => { 8 | let clientRequestFactory: ClientRequestFactory; 9 | beforeEach(async () => { 10 | clientRequestFactory = (await getTestContainer()).get( 11 | ClientRequestFactory, 12 | ); 13 | }); 14 | let testPattern = [ 15 | { 16 | name: "match", 17 | pattern: "/*", 18 | path: "/hoge", 19 | result: true, 20 | }, 21 | { 22 | name: "directory match", 23 | pattern: "/*/", 24 | path: "/hoge/", 25 | result: true, 26 | }, 27 | { 28 | name: "multi directory match", 29 | pattern: "/**", 30 | path: "/hoge/huga.js", 31 | result: true, 32 | }, 33 | { 34 | name: "extension match", 35 | pattern: "*.js", 36 | path: "/hoge/huga.js", 37 | result: true, 38 | }, 39 | { 40 | name: "extension match", 41 | pattern: "*.js", 42 | path: "/hoge/huga.js", 43 | result: true, 44 | }, 45 | { 46 | name: "unmatch", 47 | pattern: "/hoge/*", 48 | path: "/huga", 49 | result: false, 50 | }, 51 | ]; 52 | 53 | describe("getMatchCodeString", () => { 54 | testPattern.forEach(pattern => { 55 | it(pattern.name, () => { 56 | let autoResponderPattern = new AutoResponderPattern( 57 | pattern.pattern, 58 | ); 59 | let result = autoResponderPattern.getMatchCodeString("match"); 60 | let code = `((url) => {${result}})("${pattern.path}")`; 61 | assert(eval(code) === (pattern.result ? "match" : undefined)); 62 | }); 63 | }); 64 | }); 65 | 66 | describe("isMatchPath", () => { 67 | testPattern.forEach(pattern => { 68 | it(pattern.name, () => { 69 | let autoResponderPattern = new AutoResponderPattern( 70 | pattern.pattern, 71 | ); 72 | let clientRequestEntity = clientRequestFactory.createFromString( 73 | pattern.path, 74 | ); 75 | let result = autoResponderPattern.isMatchPath( 76 | clientRequestEntity, 77 | ); 78 | assert(result === pattern.result); 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/electron-window-manager/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | debug@^2.6.8: 6 | version "2.6.9" 7 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 8 | dependencies: 9 | ms "2.0.0" 10 | 11 | deep-equal@^1.0.1: 12 | version "1.0.1" 13 | resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" 14 | 15 | electron-is-accelerator@^0.1.0: 16 | version "0.1.2" 17 | resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b" 18 | 19 | electron-localshortcut@^3.1.0: 20 | version "3.1.0" 21 | resolved "https://registry.yarnpkg.com/electron-localshortcut/-/electron-localshortcut-3.1.0.tgz#10c1ffd537b8d39170aaf6e1551341f7780dd2ce" 22 | dependencies: 23 | debug "^2.6.8" 24 | electron-is-accelerator "^0.1.0" 25 | keyboardevent-from-electron-accelerator "^1.1.0" 26 | keyboardevents-areequal "^0.2.1" 27 | 28 | electron-window-state@^4.1.1: 29 | version "4.1.1" 30 | resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-4.1.1.tgz#6b34fdc31b38514dfec8b7c8f7b5d4addb67632d" 31 | dependencies: 32 | deep-equal "^1.0.1" 33 | jsonfile "^2.2.3" 34 | mkdirp "^0.5.1" 35 | 36 | graceful-fs@^4.1.6: 37 | version "4.1.11" 38 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 39 | 40 | jsonfile@^2.2.3: 41 | version "2.4.0" 42 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" 43 | optionalDependencies: 44 | graceful-fs "^4.1.6" 45 | 46 | keyboardevent-from-electron-accelerator@^1.1.0: 47 | version "1.1.0" 48 | resolved "https://registry.yarnpkg.com/keyboardevent-from-electron-accelerator/-/keyboardevent-from-electron-accelerator-1.1.0.tgz#324614f6e33490c37ffc5be5876b3e85fe223c84" 49 | 50 | keyboardevents-areequal@^0.2.1: 51 | version "0.2.2" 52 | resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" 53 | 54 | melanke-watchjs@^1.3.1: 55 | version "1.4.3" 56 | resolved "https://registry.yarnpkg.com/melanke-watchjs/-/melanke-watchjs-1.4.3.tgz#fbf772259cb6c4829e6ef9dd47d74cfc17cac982" 57 | 58 | minimist@0.0.8: 59 | version "0.0.8" 60 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 61 | 62 | mkdirp@^0.5.1: 63 | version "0.5.1" 64 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 65 | dependencies: 66 | minimist "0.0.8" 67 | 68 | ms@2.0.0: 69 | version "2.0.0" 70 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 71 | -------------------------------------------------------------------------------- /packages/lifter-app/src/renderer/components/li-main/rewrite-rule.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 86 | 87 | 97 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/client-request/client-request-service.ts: -------------------------------------------------------------------------------- 1 | import { ClientRequestEntityJSON } from "@lifter/lifter-common"; 2 | import { injectable } from "inversify"; 3 | import * as Rx from "rxjs/Rx"; 4 | import * as URL from "url"; 5 | import { LOCAL_PAC_FILE_URL } from "../../../settings"; 6 | import { PacFileService } from "../../settings/pac-file/pac-file-service"; 7 | import { AutoResponderService } from "../auto-responder/auto-responder-service"; 8 | import { ClientRequestEntity } from "./client-request-entity"; 9 | import { ClientResponderContext } from "./lib/client-responder-context"; 10 | import { ClientRequestFactory } from "./lifecycle/client-request-factory"; 11 | import { ClientRequestRepository } from "./lifecycle/client-request-repository"; 12 | 13 | export interface getClientRequestService { 14 | subscribe: ( 15 | callback: (clientRequestEntity: ClientRequestEntityJSON) => void, 16 | ) => void; 17 | fetchAll: () => Promise; 18 | } 19 | 20 | @injectable() 21 | export class ClientRequestService { 22 | private observable: Rx.Subject = new Rx.Subject(); 23 | 24 | constructor( 25 | private autoResponderService: AutoResponderService, 26 | private pacFileService: PacFileService, 27 | private clientRequestFactory: ClientRequestFactory, 28 | private clientRequestRepository: ClientRequestRepository, 29 | ) {} 30 | 31 | store(url: URL.Url): ClientRequestEntity { 32 | let clientRequestEntity = this.clientRequestFactory.create(url); 33 | this.clientRequestRepository.store(clientRequestEntity); 34 | this.observable.next(clientRequestEntity.json); 35 | return clientRequestEntity; 36 | } 37 | 38 | getClientRequestService(): getClientRequestService { 39 | return { 40 | subscribe: ( 41 | callback: ( 42 | clientRequestEntity: ClientRequestEntityJSON, 43 | ) => void, 44 | ): void => { 45 | this.observable.subscribe(callback); 46 | }, 47 | fetchAll: (): Promise => { 48 | return this.resolveAll(); 49 | }, 50 | }; 51 | } 52 | 53 | async onRequest( 54 | clientResponderContext: ClientResponderContext, 55 | ): Promise { 56 | let url = clientResponderContext.getUrl(); 57 | 58 | if (LOCAL_PAC_FILE_URL.test(url.href)) { 59 | return await this.pacFileService.response(clientResponderContext); 60 | } 61 | 62 | let clientRequestEntity = this.store(url); 63 | 64 | return await this.autoResponderService.response( 65 | clientResponderContext, 66 | clientRequestEntity, 67 | ); 68 | } 69 | 70 | private async resolveAll(): Promise { 71 | let clientRequestEntries = await this.clientRequestRepository.resolveAll(); 72 | return clientRequestEntries.map( 73 | (entity): ClientRequestEntityJSON => entity.json, 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/base/async-on-nedb-repository.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import * as Datastore from "nedb"; 3 | import { Entity, Identity } from "typescript-dddbase"; 4 | import { promisify } from "util"; 5 | import { ResolveAll } from "../libs/resolve-all"; 6 | 7 | export interface NedbMapper, E extends Entity> { 8 | toEntity(json: any): E; 9 | toJSON(entity: E): any; 10 | } 11 | 12 | @injectable() 13 | export abstract class AsyncOnNedbRepository< 14 | ID extends Identity, 15 | E extends Entity 16 | > { 17 | private datastore: Datastore; 18 | private find: (query: any) => Promise; 19 | private update: (query: any, value: any, option: any) => Promise; 20 | private remove: (query: any, option: any) => Promise; 21 | private loadDatabase: () => Promise; 22 | 23 | private entities: { [key: string]: E } = {}; 24 | 25 | constructor( 26 | dataStoreOptions: Datastore.DataStoreOptions, 27 | private mapper: NedbMapper, 28 | ) { 29 | this.datastore = new Datastore(dataStoreOptions); 30 | this.find = promisify(this.datastore.find.bind(this.datastore)); 31 | this.update = promisify(this.datastore.update.bind(this.datastore)); 32 | this.remove = promisify(this.datastore.remove.bind(this.datastore)); 33 | this.loadDatabase = promisify( 34 | this.datastore.loadDatabase.bind(this.datastore), 35 | ); 36 | } 37 | 38 | async load(): Promise { 39 | await this.loadDatabase(); 40 | let data = await this.find({}); 41 | this.entities = data.map(d => this.mapper.toEntity(d)).reduce( 42 | (base, cur) => { 43 | base[String(cur.getIdentity().getValue())] = cur; 44 | return base; 45 | }, 46 | <{ [key: string]: E }>{}, 47 | ); 48 | } 49 | 50 | async resolveAll(): Promise { 51 | return ResolveAll(this.entities); 52 | } 53 | 54 | async resolve(identity: ID): Promise { 55 | return this.entities[identity.getValue()]; 56 | } 57 | 58 | async store(entity: E): Promise { 59 | let json = this.mapper.toJSON(entity); 60 | let id = entity.getIdentity().getValue(); 61 | await this.update({ id: id }, json, { upsert: true }); 62 | this.entities[id] = entity; 63 | return entity; 64 | } 65 | 66 | storeList(entityList: E[]): Promise { 67 | return Promise.all(entityList.map(i => this.store(i))); 68 | } 69 | 70 | deleteByEntity(entity: E): Promise> { 71 | return this.deleteByIdentity(entity.getIdentity()); 72 | } 73 | 74 | async deleteByIdentity( 75 | identity: ID, 76 | ): Promise> { 77 | let id = identity.getValue(); 78 | await this.remove({ id: id }, {}); 79 | delete this.entities[id]; 80 | return this; 81 | } 82 | 83 | async overwriteAll(entities: E[]): Promise { 84 | await this.deleteAll(); 85 | return this.storeList(entities); 86 | } 87 | 88 | protected deleteAll(): Promise { 89 | return this.remove({}, { multi: true }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/lifter-main/src/application/application.ts: -------------------------------------------------------------------------------- 1 | import { injectable } from "inversify"; 2 | import { 3 | AutoResponderService, 4 | getAutoResponder, 5 | } from "../domains/proxy/auto-responder/auto-responder-service"; 6 | import { 7 | ClientRequestService, 8 | getClientRequestService, 9 | } from "../domains/proxy/client-request/client-request-service"; 10 | import { 11 | getRewriteRules, 12 | RewriteRuleService, 13 | } from "../domains/proxy/rewrite-rule/rewrite-rule-service"; 14 | import { 15 | CertificateService, 16 | getCertificateService, 17 | } from "../domains/settings/certificate/certificate-service"; 18 | import { 19 | getProxyBypassDomains, 20 | ProxyBypassDomainService, 21 | } from "../domains/settings/proxy-bypass-domain/proxy-bypass-domain-service"; 22 | import { 23 | getProxyCommandGrantService, 24 | ProxyCommandGrantService, 25 | } from "../domains/settings/proxy-command-grant/proxy-command-grant-service"; 26 | import { 27 | getUserSetting, 28 | UserSettingsService, 29 | } from "../domains/settings/user-settings/user-settings-service"; 30 | import { ProxyService } from "./proxy/proxy-service"; 31 | import { 32 | getProxySettingService, 33 | ProxySettingService, 34 | } from "./settings/proxy-setting/proxy-setting-service"; 35 | 36 | @injectable() 37 | export class Application { 38 | constructor( 39 | private userSettingsService: UserSettingsService, 40 | private proxySettingService: ProxySettingService, 41 | private proxyService: ProxyService, 42 | private autoResponderService: AutoResponderService, 43 | private certificateService: CertificateService, 44 | private proxyCommandGrantService: ProxyCommandGrantService, 45 | private clientRequestService: ClientRequestService, 46 | private proxyBypassDomainService: ProxyBypassDomainService, 47 | private rewriteRuleService: RewriteRuleService, 48 | ) {} 49 | 50 | async startup() { 51 | await this.proxyService.listen(); 52 | await this.proxySettingService.startup(); 53 | } 54 | 55 | async shutdown(): Promise { 56 | await this.proxySettingService.shutdown(); 57 | await this.proxyService.shutdown(); 58 | } 59 | 60 | getAutoResponder(): getAutoResponder { 61 | return this.autoResponderService.getAutoResponder(); 62 | } 63 | 64 | getCertificateService(): getCertificateService { 65 | return this.certificateService.getCertificateService(); 66 | } 67 | 68 | getProxyCommandGrantService(): getProxyCommandGrantService { 69 | return this.proxyCommandGrantService.getProxyCommandGrantService(); 70 | } 71 | 72 | getClientRequestService(): getClientRequestService { 73 | return this.clientRequestService.getClientRequestService(); 74 | } 75 | 76 | getProxyBypassDomains(): getProxyBypassDomains { 77 | return this.proxyBypassDomainService.getProxyBypassDomains(); 78 | } 79 | 80 | getRewriteRules(): getRewriteRules { 81 | return this.rewriteRuleService.getRewriteRules(); 82 | } 83 | 84 | getUserSetting(): getUserSetting { 85 | return this.userSettingsService.getUserSetting(); 86 | } 87 | 88 | getProxySettingService(): getProxySettingService { 89 | return this.proxySettingService.getProxySettingService(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/lifter-main/src/domains/proxy/auto-responder/auto-responder-service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { getTestContainer } from "../../../../test/mocks/get-test-container"; 4 | import { ClientRequestFactory } from "../client-request/lifecycle/client-request-factory"; 5 | import { AutoResponderService } from "./auto-responder-service"; 6 | import { AutoResponderFactory } from "./lifecycle/auto-responder-factory"; 7 | import { AutoResponderRepository } from "./lifecycle/auto-responder-repositoty"; 8 | 9 | describe("AutoResponderService", () => { 10 | let clientRequestFactory: ClientRequestFactory; 11 | let autoResponderFactory: AutoResponderFactory; 12 | let autoResponderRepository: AutoResponderRepository; 13 | let autoResponderService: AutoResponderService; 14 | 15 | beforeEach(async () => { 16 | let container = await getTestContainer(); 17 | clientRequestFactory = container.get(ClientRequestFactory); 18 | autoResponderFactory = container.get(AutoResponderFactory); 19 | autoResponderRepository = container.get(AutoResponderRepository); 20 | autoResponderService = container.get(AutoResponderService); 21 | }); 22 | 23 | it("getAutoResponder.add", async () => { 24 | let autoResponder = autoResponderService.getAutoResponder(); 25 | let results = await autoResponder.add([__filename]); 26 | assert(results[0].path === __filename); 27 | 28 | let fetchResults = await autoResponder.fetchAll(); 29 | assert(fetchResults.length === 1); 30 | assert(fetchResults[0].path === __filename); 31 | }); 32 | 33 | it("getAutoResponder.fetchAll", async () => { 34 | let autoResponder = autoResponderService.getAutoResponder(); 35 | assert((await autoResponder.fetchAll()).length === 0); 36 | 37 | await autoResponder.add([__filename]); 38 | 39 | assert((await autoResponder.fetchAll()).length === 1); 40 | }); 41 | 42 | it("getAutoResponder.delete", async () => { 43 | let autoResponder = autoResponderService.getAutoResponder(); 44 | await autoResponder.add([__filename]); 45 | 46 | let fetchResults = await autoResponder.fetchAll(); 47 | 48 | await autoResponder.deletes(fetchResults.map(res => res.id)); 49 | 50 | assert((await autoResponder.fetchAll()).length === 0); 51 | }); 52 | 53 | it("find result is null", async () => { 54 | let clientRequestEntity = clientRequestFactory.createFromString(""); 55 | let result = await autoResponderService.find(clientRequestEntity); 56 | assert(!result); 57 | }); 58 | 59 | it("find result is not null", async () => { 60 | await autoResponderService.store([__filename]); 61 | let clientRequestEntity = clientRequestFactory.createFromString( 62 | __filename, 63 | ); 64 | let result = await autoResponderService.find(clientRequestEntity); 65 | assert(result); 66 | }); 67 | 68 | it("find choose from entities", async () => { 69 | let entities = Array.from(Array(10)).map((_, index) => 70 | autoResponderFactory.create(String(index), __filename), 71 | ); 72 | await autoResponderRepository.storeList(entities); 73 | 74 | let clientRequestEntity = clientRequestFactory.createFromString("/2"); 75 | let result = await autoResponderService.find(clientRequestEntity); 76 | assert(result); 77 | }); 78 | }); 79 | --------------------------------------------------------------------------------