diff --git a/spec/main/unit/proxy.spec.ts b/spec/main/unit/proxy.spec.ts new file mode 100644 index 00000000..16ad4c32 --- /dev/null +++ b/spec/main/unit/proxy.spec.ts @@ -0,0 +1,60 @@ +import path from 'path' +import ProxyConfiguration from '~/src/main/proxy' +import { ManualProxy, ProxyProtocol } from '~/src/types/proxy' + +const preferencesDBPath = path.resolve(__dirname, '../../preferences.json') +const proxyConfiguration = new ProxyConfiguration(preferencesDBPath) + +describe('Parser', () => { + it('do not use proxy', () => { + proxyConfiguration.setSystemProxy('DIRECT') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).toEqual(false) + }) + + it('HTTP and HTTPS proxy', () => { + proxyConfiguration.setSystemProxy('PROXY hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.http) + expect(manualProxy.host).toEqual('hoge.example.com') + expect(manualProxy.port).toEqual('8080') + }) + + it('SOCKS4 proxy', () => { + proxyConfiguration.setSystemProxy('SOCKS4 hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.socks4) + }) + it('SOCKS4A proxy', () => { + proxyConfiguration.setSystemProxy('SOCKS4A hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.socks4a) + }) + it('SOCKS5 proxy', () => { + proxyConfiguration.setSystemProxy('SOCKS5 hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.socks5) + }) + it('SOCKS5H proxy', () => { + proxyConfiguration.setSystemProxy('SOCKS5H hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.socks5h) + }) + it('SOCKS proxy', () => { + proxyConfiguration.setSystemProxy('SOCKS hoge.example.com:8080') + const proxy = proxyConfiguration.parseSystemProxy() + expect(proxy).not.toBe(false) + const manualProxy = proxy as ManualProxy + expect(manualProxy.protocol).toEqual(ProxyProtocol.socks5) + }) +}) diff --git a/spec/preferences.json b/spec/preferences.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/spec/preferences.json @@ -0,0 +1 @@ +{} diff --git a/src/main/index.ts b/src/main/index.ts index 23d90e7b..646c574f 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -4,6 +4,7 @@ import { app, ipcMain, shell, + session, Menu, Tray, BrowserWindow, @@ -45,6 +46,7 @@ import HashtagCache from './cache/hashtag' import AccountCache from './cache/account' import { InsertAccountCache } from '~/src/types/insertAccountCache' import { Proxy } from '~/src/types/proxy' +import ProxyConfiguration from './proxy' /** * Context menu @@ -144,6 +146,7 @@ const soundBasePath = process.env.NODE_ENV === 'development' ? path.join(__dirname, '../../build/sounds/') : path.join(process.resourcesPath!, 'build/sounds/') let launcher: AutoLaunch | null = null +const proxyConfiguration = new ProxyConfiguration(preferencesDBPath) // On MAS build, auto launch is not working. // We have to use Launch Agent: https://github.com/Teamwork/node-auto-launch/issues/43 @@ -270,6 +273,16 @@ async function createWindow() { mainWindow.webContents.on('will-navigate', event => event.preventDefault()) + /** + * Get system proxy configuration. + */ + if (session && session.defaultSession) { + session.defaultSession.resolveProxy('https://mastodon.social', proxyInfo => { + proxyConfiguration.setSystemProxy(proxyInfo) + log.info(`System proxy configuration: ${proxyInfo}`) + }) + } + mainWindow.on('closed', () => { mainWindow = null }) @@ -982,6 +995,11 @@ ipcMain.on('update-proxy-config', (event: IpcMainEvent, proxy: Proxy) => { }) }) +ipcMain.on('get-proxy-configuration', async (event: IpcMainEvent) => { + const proxy = await proxyConfiguration.getConfig() + event.sender.send('response-get-proxy-configuration', proxy) +}) + // language ipcMain.on('change-language', (event: IpcMainEvent, value: string) => { const preferences = new Preferences(preferencesDBPath) diff --git a/src/main/preferences.ts b/src/main/preferences.ts index 2fb8a62d..1a74df1c 100644 --- a/src/main/preferences.ts +++ b/src/main/preferences.ts @@ -1,4 +1,5 @@ import storage from 'electron-json-storage' +import log from 'electron-log' import objectAssignDeep from 'object-assign-deep' import DisplayStyle from '../constants/displayStyle' import Theme from '../constants/theme' @@ -97,6 +98,7 @@ export default class Preferences { const preferences = await this.get() return objectAssignDeep({}, Base, preferences) } catch (err) { + log.error(err) return Base } } diff --git a/src/main/proxy.ts b/src/main/proxy.ts new file mode 100644 index 00000000..2d5c4f7e --- /dev/null +++ b/src/main/proxy.ts @@ -0,0 +1,74 @@ +import { ProxySource, ManualProxy, ProxyProtocol } from '~/src/types/proxy' +import Preferences from './preferences' + +export default class ProxyConfiguration { + public preferences: Preferences + public systemProxy: string | null = null + + constructor(preferencesDBPath: string) { + this.preferences = new Preferences(preferencesDBPath) + } + + public setSystemProxy(proxy: string) { + this.systemProxy = proxy + } + + public async getConfig(): Promise { + const conf = await this.preferences.get() + const source = conf.proxy.source as ProxySource + switch (source) { + case ProxySource.no: + return false + case ProxySource.system: + if (this.systemProxy) { + return this.parseSystemProxy() + } else { + return false + } + case ProxySource.manual: + return conf.proxy.manualProxyConfig + } + } + + public parseSystemProxy(): ManualProxy | false { + if (!this.systemProxy) { + return false + } + if (this.systemProxy === 'DIRECT') { + return false + } + const result = this.systemProxy.match(/^([A-Z0-9]+)\s+([a-z0-9-_.]+):([0-9]+)$/) + if (!result || result.length !== 4) { + return false + } + let protocol = ProxyProtocol.http + switch (result[1]) { + case 'PROXY': + protocol = ProxyProtocol.http + break + case 'SOCKS4': + protocol = ProxyProtocol.socks4 + break + case 'SOCKS4A': + protocol = ProxyProtocol.socks4a + break + case 'SOCKS5': + protocol = ProxyProtocol.socks5 + break + case 'SOCKS5H': + protocol = ProxyProtocol.socks5h + break + case 'SOCKS': + protocol = ProxyProtocol.socks5 + break + } + const manual: ManualProxy = { + protocol: protocol, + host: result[2], + port: result[3], + username: '', + password: '' + } + return manual + } +}