diff --git a/src/containers/App.tsx b/src/containers/App.tsx index dd54a9c..99779a8 100644 --- a/src/containers/App.tsx +++ b/src/containers/App.tsx @@ -12,6 +12,7 @@ import Logs from '@containers/Logs' import Rules from '@containers/Rules' import Settings from '@containers/Settings' import SlideBar from '@containers/Sidebar' +import ExternalControllerModal from '@containers/ExternalControllerDrawer' import { getLogsStreamReader } from '@lib/request' export interface AppProps extends I18nProps { @@ -42,6 +43,7 @@ export default class App extends React.Component { ) } + ) } diff --git a/src/containers/Settings/components/ExternalControllerDrawer/index.tsx b/src/containers/ExternalControllerDrawer/index.tsx similarity index 66% rename from src/containers/Settings/components/ExternalControllerDrawer/index.tsx rename to src/containers/ExternalControllerDrawer/index.tsx index 1c89df7..e416f24 100644 --- a/src/containers/Settings/components/ExternalControllerDrawer/index.tsx +++ b/src/containers/ExternalControllerDrawer/index.tsx @@ -1,48 +1,55 @@ import * as React from 'react' import { translate } from 'react-i18next' +import { inject, observer } from 'mobx-react' +import { storeKeys } from '@lib/createStore' import { Modal, Input, Row, Col, Alert } from '@components' -import { I18nProps } from '@models' +import { BaseProps, I18nProps } from '@models' import './style.scss' -interface ExternalControllerModalProps extends I18nProps { - show: boolean - host: string - port: string - secret?: string - onConfirm: (host: string, port: string, secret: string) => void - onCancel: () => void -} +interface ExternalControllerModalProps extends I18nProps, BaseProps {} interface ExternalControllerModalState { - host: string + hostname: string port: string secret: string } +@inject(...storeKeys) +@observer class ExternalController extends React.Component { state = { - host: this.props.host, - port: this.props.port, - secret: this.props.secret || '' + hostname: '', + port: '', + secret: '' } private handleOk = () => { - const { onConfirm } = this.props - const { host, port, secret } = this.state - onConfirm(host, port, secret) + const { hostname, port, secret } = this.state + this.props.store.updateAPIInfo({ hostname, port, secret }) + } + + private handleCancel = () => { + this.props.store.setShowAPIModal(false) + } + + async componentWillMount () { + await this.props.store.fetchAPIInfo() + const info = this.props.store.apiInfo + this.setState({ hostname: info.hostname, port: info.port, secret: info.secret }) } render () { - const { show, onCancel, t } = this.props - const { host, port, secret } = this.state + const { t } = this.props + const { hostname, port, secret } = this.state + const show = this.props.store.showAPIModal return ( @@ -54,8 +61,8 @@ class ExternalController extends React.Component this.setState({ host })} + value={hostname} + onChange={hostname => this.setState({ hostname })} /> @@ -86,4 +93,4 @@ class ExternalController extends React.Component { state = { socks5ProxyPort: 7891, httpProxyPort: 7890, - externalControllerHost: '127.0.0.1', - externalControllerPort: '8080', - externalControllerSecret: '', - showEditDrawer: false, isClashX: false } @@ -32,18 +27,6 @@ class Settings extends React.Component { changeLanguage(language) } - handleExternalControllerChange = (host: string, port: string, secret: string) => { - setLocalStorageItem('externalControllerAddr', host) - setLocalStorageItem('externalControllerPort', port) - setLocalStorageItem('secret', secret) - this.setState({ - showEditDrawer: false, - externalControllerHost: host, - externalControllerPort: port, - externalControllerSecret: secret - }) - } - handleProxyModeChange = async (mode: string) => { const [, err] = await to(updateConfig({ mode })) if (!err) { @@ -84,16 +67,12 @@ class Settings extends React.Component { this.setState({ setAsSystemProxy: state }) } - async componentWillMount () { + async componentDidMount () { await rootStores.store.fetchData() if (isClashX()) { await rootStores.store.fetchClashXData() - const apiInfo = await jsBridge.getAPIInfo() this.setState({ - isClashX: true, - externalControllerHost: apiInfo.host, - externalControllerPort: apiInfo.port, - externalControllerSecret: apiInfo.secret + isClashX: true }) } @@ -108,14 +87,15 @@ class Settings extends React.Component { const { t, lng, store } = this.props const { isClashX, - externalControllerHost, - externalControllerPort, - externalControllerSecret, - showEditDrawer, socks5ProxyPort, httpProxyPort } = this.state + const { + hostname: externalControllerHost, + port: externalControllerPort + } = store.apiInfo + const { allowLan, mode } = store.data.general const { startAtLogin, @@ -208,7 +188,7 @@ class Settings extends React.Component { {`${externalControllerHost}:${externalControllerPort}`} - this.setState({ showEditDrawer: true })}> + this.props.store.setShowAPIModal(true)}> 修改 @@ -222,15 +202,6 @@ class Settings extends React.Component {

{t('versionString', { version: 'unknown' })}

{t('checkUpdate')} - - this.setState({ showEditDrawer: false })} - /> ) } diff --git a/src/lib/request.ts b/src/lib/request.ts index 6ee9a55..e0bd014 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance } from 'axios' import { Partial, getLocalStorageItem } from '@lib/helper' -import { isClashX } from '@lib/jsBridge' +import { isClashX, jsBridge } from '@lib/jsBridge' import { rootStores } from '@lib/createStore' import { StreamReader } from './streamer' @@ -105,18 +105,27 @@ export async function getInstance () { headers: secret ? { Authorization: `Bearer ${secret}` } : {} }) + instance.interceptors.response.use( + resp => resp, + err => { + if (!err.response || err.response.status === 401) { + rootStores.store.setShowAPIModal(true) + } + throw err + } + ) + return instance } export async function getExternalControllerConfig () { if (isClashX()) { - await rootStores.store.fetchAndParseConfig() - const general = rootStores.store.config.general + const info = await jsBridge.getAPIInfo() return { - hostname: general.externalControllerAddr, - port: general.externalControllerPort, - secret: general.secret + hostname: info.host, + port: info.port, + secret: info.secret } } @@ -138,6 +147,7 @@ export async function getLogsStreamReader () { const externalController = await getExternalControllerConfig() const { data: config } = await getConfig() const logUrl = `http://${externalController.hostname}:${externalController.port}/logs?level=${config['log-level']}` - logsStreamReader = new StreamReader({ url: logUrl, bufferLength: 200 }) + const auth = externalController.secret ? { Authorization: `Bearer ${externalController.secret}` } : {} + logsStreamReader = new StreamReader({ url: logUrl, bufferLength: 200, headers: auth }) return logsStreamReader } diff --git a/src/models/Config.ts b/src/models/Config.ts index d2ecdd7..64b191a 100644 --- a/src/models/Config.ts +++ b/src/models/Config.ts @@ -69,6 +69,12 @@ export interface ClashXData { systemProxy: boolean } +export interface APIInfo { + hostname: string + port: string + secret?: string +} + export interface Data { general?: { diff --git a/src/stores/ConfigStore.ts b/src/stores/ConfigStore.ts index 4fb9f46..8f452cd 100644 --- a/src/stores/ConfigStore.ts +++ b/src/stores/ConfigStore.ts @@ -1,9 +1,9 @@ import { observable, action, runInAction } from 'mobx' import * as yaml from 'yaml' import * as Models from '@models' -import { jsBridge } from '@lib/jsBridge' +import { jsBridge, isClashX } from '@lib/jsBridge' import * as API from '@lib/request' -import { getLocalStorageItem, partition } from '@lib/helper' +import { getLocalStorageItem, setLocalStorageItem, partition } from '@lib/helper' export class ConfigStore { @@ -22,12 +22,38 @@ export class ConfigStore { rules: [] } + @observable + apiInfo: Models.APIInfo = { + hostname: '127.0.0.1', + port: '8080', + secret: '' + } + + @observable + showAPIModal = false + @observable clashxData: Models.ClashXData = { startAtLogin: false, systemProxy: false } + @action + async fetchAPIInfo () { + if (isClashX()) { + const apiInfo = await jsBridge.getAPIInfo() + runInAction(() => { + this.apiInfo = { hostname: apiInfo.host, port: apiInfo.port, secret: apiInfo.secret } + }) + return + } + const info = await API.getExternalControllerConfig() + + runInAction(() => { + this.apiInfo = { ...info } + }) + } + @action async fetchData () { const [{ data: general }, rawProxies, rules] = await Promise.all([API.getConfig(), API.getProxies(), API.getRules()]) @@ -156,6 +182,22 @@ export class ConfigStore { jsBridge.writeConfigWithString(data) } + @action + async updateAPIInfo (info: Models.APIInfo) { + const { hostname, port, secret } = info + setLocalStorageItem('externalControllerAddr', hostname) + setLocalStorageItem('externalControllerPort', port) + setLocalStorageItem('secret', secret) + window.location.reload() + } + + @action + setShowAPIModal (visible: boolean) { + runInAction(() => { + this.showAPIModal = visible + }) + } + @action async modifyProxyByIndexAndSave (index: number, config: Models.Proxy) { const { proxy } = this.config