From 702169941e6956aa8f3b727ffe59a7bd6676c346 Mon Sep 17 00:00:00 2001 From: Haishan Date: Sat, 5 Sep 2020 00:02:53 +0800 Subject: [PATCH] refactor: use api base url instead of hostname and port --- Dockerfile | 6 +- package.json | 19 +- src/api/configs.js | 30 --- src/api/configs.ts | 31 +++ src/api/{connections.js => connections.ts} | 76 +++--- src/api/{logs.js => logs.ts} | 58 +++-- src/api/traffic.js | 13 +- src/app.js | 1 + src/components/APIConfig.js | 129 ----------- src/components/APIConfig.module.css | 10 +- src/components/APIConfig.tsx | 116 ++++++++++ src/components/CollapsibleSectionHeader.tsx | 3 +- src/components/Field.module.css | 6 +- src/components/Logs.js | 6 +- src/misc/request-helper.js | 24 -- src/misc/request-helper.ts | 31 +++ src/misc/utils.js | 23 -- src/misc/utils.ts | 33 +++ src/store/app.js | 42 ++-- src/store/proxies.tsx | 21 +- src/types.ts | 3 +- webpack.config.js | 11 +- yarn.lock | 241 ++++++++------------ 23 files changed, 444 insertions(+), 489 deletions(-) delete mode 100644 src/api/configs.js create mode 100644 src/api/configs.ts rename src/api/{connections.js => connections.ts} (53%) rename src/api/{logs.js => logs.ts} (69%) delete mode 100644 src/components/APIConfig.js create mode 100644 src/components/APIConfig.tsx delete mode 100644 src/misc/request-helper.js create mode 100644 src/misc/request-helper.ts delete mode 100644 src/misc/utils.js create mode 100644 src/misc/utils.ts diff --git a/Dockerfile b/Dockerfile index c1479d7..89046c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,11 @@ WORKDIR /app COPY . . # Using yarn to install dependencies in CI will cause network timeout # Refer to https://github.com/date-fns/date-fns/issues/1004 -RUN yarn config set network-timeout 300000 && yarn && yarn run build +RUN yarn config set network-timeout 300000 \ + && yarn \ + && yarn build \ + # remove source maps - people like small image + && rm public/*.map FROM nginx:alpine RUN rm -rf /usr/share/nginx/html/* diff --git a/package.json b/package.json index b48ef7a..16aeb52 100644 --- a/package.json +++ b/package.json @@ -39,20 +39,20 @@ "clsx": "^1.1.0", "core-js": "^3.6.2", "date-fns": "^2.16.0", - "framer-motion": "^2.6.5", + "framer-motion": "^2.6.7", "history": "^5.0.0", "immer": "^7.0.8", "invariant": "^2.2.4", "lodash-es": "^4.17.14", "memoize-one": "^5.1.1", - "modern-normalize": "^0.7.0", + "modern-normalize": "^1.0.0", "prop-types": "^15.5.10", "react": "0.0.0-experimental-241c4467e", "react-dom": "0.0.0-experimental-241c4467e", "react-feather": "^2.0.3", "react-icons": "^3.10.0", "react-modal": "^3.11.1", - "react-query": "^2.12.1", + "react-query": "^2.15.4", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-switch": "^5.0.1", @@ -83,14 +83,14 @@ "@types/lodash-es": "^4.17.3", "@types/react": "^16.9.48", "@types/react-dom": "^16.9.8", - "@typescript-eslint/eslint-plugin": "^4.0.1", - "@typescript-eslint/parser": "^4.0.1", + "@typescript-eslint/eslint-plugin": "^4.1.0", + "@typescript-eslint/parser": "^4.1.0", "autoprefixer": "^9.8.6", "babel-eslint": "10.x", "babel-loader": "^8.0.5", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.0.4", - "css-loader": "^4.2.2", + "css-loader": "^4.3.0", "cssnano": "^4.1.7", "eslint": "^7.6.0", "eslint-config-airbnb-base": "^14.1.0", @@ -99,7 +99,7 @@ "eslint-import-resolver-webpack": "^0.12.2", "eslint-plugin-flowtype": "^5.1.0", "eslint-plugin-import": "^2.22.0", - "eslint-plugin-jest": "^23.20.0", + "eslint-plugin-jest": "^24.0.0", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-react": "^7.20.6", "eslint-plugin-react-hooks": "^4.0.8", @@ -108,13 +108,14 @@ "fork-ts-checker-notifier-webpack-plugin": "^3.0.0", "fork-ts-checker-webpack-plugin": "^5.0.14", "html-webpack-plugin": "^4.3.0", - "husky": "^4.0.0", + "husky": "^4.3.0", "lint-staged": "^10.2.13", "mini-css-extract-plugin": "^0.11.0", + "postcss": "^7.0.32", "postcss-custom-media": "^7.0.8", "postcss-extend-rule": "^3.0.0", "postcss-import": "^12.0.1", - "postcss-loader": "^3.0.0", + "postcss-loader": "^4.0.0", "postcss-nested": "^4.2.3", "postcss-simple-vars": "^5.0.2", "prettier": "^2.1.1", diff --git a/src/api/configs.js b/src/api/configs.js deleted file mode 100644 index 42f8ff0..0000000 --- a/src/api/configs.js +++ /dev/null @@ -1,30 +0,0 @@ -import { getURLAndInit } from '../misc/request-helper'; - -const endpoint = '/configs'; - -export async function fetchConfigs(apiConfig) { - const { url, init } = getURLAndInit(apiConfig); - return await fetch(url + endpoint, init); -} - -// TODO support PUT /configs -// req body -// { Path: string } - -function configsPatchWorkaround(o) { - // backward compatibility for older clash using `socket-port` - if ('socks-port' in o) { - o['socket-port'] = o['socks-port']; - } - return o; -} - -export async function updateConfigs(apiConfig, o) { - const { url, init } = getURLAndInit(apiConfig); - return await fetch(url + endpoint, { - ...init, - method: 'PATCH', - // mode: 'cors', - body: JSON.stringify(configsPatchWorkaround(o)) - }); -} diff --git a/src/api/configs.ts b/src/api/configs.ts new file mode 100644 index 0000000..4957e85 --- /dev/null +++ b/src/api/configs.ts @@ -0,0 +1,31 @@ +import { getURLAndInit } from 'src/misc/request-helper'; +import { ClashAPIConfig } from 'src/types'; + +const endpoint = '/configs'; + +export async function fetchConfigs(apiConfig: ClashAPIConfig) { + const { url, init } = getURLAndInit(apiConfig); + return await fetch(url + endpoint, init); +} + +// TODO support PUT /configs +// req body +// { Path: string } + +type ClashConfigPartial = { 'socks-port'?: unknown }; +function configsPatchWorkaround(o: ClashConfigPartial) { + // backward compatibility for older clash using `socket-port` + if ('socks-port' in o) { + o['socket-port'] = o['socks-port']; + } + return o; +} + +export async function updateConfigs( + apiConfig: ClashAPIConfig, + o: ClashConfigPartial +) { + const { url, init } = getURLAndInit(apiConfig); + const body = JSON.stringify(configsPatchWorkaround(o)); + return await fetch(url + endpoint, { ...init, body, method: 'PATCH' }); +} diff --git a/src/api/connections.js b/src/api/connections.ts similarity index 53% rename from src/api/connections.js rename to src/api/connections.ts index f540c74..7608a41 100644 --- a/src/api/connections.js +++ b/src/api/connections.ts @@ -1,4 +1,6 @@ -import { getURLAndInit } from '../misc/request-helper'; +import { ClashAPIConfig } from 'src/types'; + +import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper'; const endpoint = '/connections'; @@ -8,31 +10,31 @@ const subscribers = []; // see also https://github.com/Dreamacro/clash/blob/dev/constant/metadata.go#L41 type UUID = string; type ConnectionItem = { - id: UUID, + id: UUID; metadata: { - network: 'tcp' | 'udp', - type: 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown', - sourceIP: string, - destinationIP: string, - sourcePort: string, - destinationPort: string, - host: string, - }, - upload: number, - download: number, + network: 'tcp' | 'udp'; + type: 'HTTP' | 'HTTP Connect' | 'Socks5' | 'Redir' | 'Unknown'; + sourceIP: string; + destinationIP: string; + sourcePort: string; + destinationPort: string; + host: string; + }; + upload: number; + download: number; // e.g. "2019-11-30T22:48:13.416668+08:00", - start: string, - chains: Array, + start: string; + chains: Array; // e.g. 'Match', 'DomainKeyword' - rule: string, + rule: string; }; type ConnectionsData = { - downloadTotal: number, - uploadTotal: number, - connections: Array, + downloadTotal: number; + uploadTotal: number; + connections: Array; }; -function appendData(s) { +function appendData(s: string) { let o: ConnectionsData; try { o = JSON.parse(s); @@ -43,33 +45,25 @@ function appendData(s) { subscribers.forEach((f) => f(o)); } -function getWsUrl(apiConfig) { - const { hostname, port, secret } = apiConfig; - let qs = ''; - if (typeof secret === 'string' && secret !== '') { - qs += '?token=' + encodeURIComponent(secret); - } - return `ws://${hostname}:${port}${endpoint}${qs}`; -} +type UnsubscribeFn = () => void; -let wsState; -function fetchData(apiConfig, listener) { +let wsState: number; +export function fetchData( + apiConfig: ClashAPIConfig, + listener: unknown +): UnsubscribeFn | void { if (fetched || wsState === 1) { if (listener) return subscribe(listener); } wsState = 1; - const url = getWsUrl(apiConfig); + const url = buildWebSocketURL(apiConfig, endpoint); const ws = new WebSocket(url); - ws.addEventListener('error', function (_ev) { - wsState = 3; - }); - ws.addEventListener('message', function (event) { - appendData(event.data); - }); + ws.addEventListener('error', () => (wsState = 3)); + ws.addEventListener('message', (event) => appendData(event.data)); if (listener) return subscribe(listener); } -function subscribe(listener) { +function subscribe(listener: unknown): UnsubscribeFn { subscribers.push(listener); return function unsubscribe() { const idx = subscribers.indexOf(listener); @@ -77,20 +71,18 @@ function subscribe(listener) { }; } -async function closeAllConnections(apiConfig) { +export async function closeAllConnections(apiConfig: ClashAPIConfig) { const { url, init } = getURLAndInit(apiConfig); return await fetch(url + endpoint, { ...init, method: 'DELETE' }); } -export async function fetchConns(apiConfig) { +export async function fetchConns(apiConfig: ClashAPIConfig) { const { url, init } = getURLAndInit(apiConfig); return await fetch(url + endpoint, { ...init }); } -export async function closeConnById(apiConfig, id) { +export async function closeConnById(apiConfig: ClashAPIConfig, id: string) { const { url: baseURL, init } = getURLAndInit(apiConfig); const url = `${baseURL}${endpoint}/${id}`; return await fetch(url, { ...init, method: 'DELETE' }); } - -export { fetchData, closeAllConnections }; diff --git a/src/api/logs.js b/src/api/logs.ts similarity index 69% rename from src/api/logs.js rename to src/api/logs.ts index caabd04..1958638 100644 --- a/src/api/logs.js +++ b/src/api/logs.ts @@ -1,4 +1,16 @@ -import { getURLAndInit } from '../misc/request-helper'; +import { ClashAPIConfig } from 'src/types'; + +import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper'; + +type LogsAPIConfig = ClashAPIConfig & { logLevel: string }; +type LogEntry = { + time?: string; + id?: string; + even?: boolean; + // and some other props +}; +type AppendLogFn = (x: LogEntry) => void; + const endpoint = '/logs'; const textDecoder = new TextDecoder('utf-8'); @@ -10,8 +22,8 @@ let even = false; let fetched = false; let decoded = ''; -function appendData(s, callback) { - let o; +function appendData(s: string, callback: AppendLogFn) { + let o: LogEntry; try { o = JSON.parse(s); } catch (err) { @@ -23,12 +35,12 @@ function appendData(s, callback) { const time = now.toLocaleString('zh-Hans'); // mutate input param in place intentionally o.time = time; - o.id = now - 0 + getRandomStr(); + o.id = +now - 0 + getRandomStr(); o.even = even = !even; callback(o); } -function pump(reader, appendLog) { +function pump(reader: ReadableStreamDefaultReader, appendLog: AppendLogFn) { return reader.read().then(({ done, value }) => { const str = textDecoder.decode(value, { stream: !done }); decoded += str; @@ -56,31 +68,20 @@ function pump(reader, appendLog) { }); } -const apiConfigSnapshot = {}; -let controller; - -function getWsUrl(apiConfig) { - const { hostname, port, secret, logLevel } = apiConfig; - let qs = '?level=' + logLevel; - if (typeof secret === 'string' && secret !== '') { - qs += '&token=' + encodeURIComponent(secret); - } - return `ws://${hostname}:${port}${endpoint}${qs}`; -} +let apiConfigSnapshot: LogsAPIConfig; +let controller: AbortController; // 1 OPEN // other value CLOSED // similar to ws readyState but not the same // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState -let wsState; -function fetchLogs(apiConfig, appendLog) { +let wsState: number; +export function fetchLogs(apiConfig: LogsAPIConfig, appendLog: AppendLogFn) { if (fetched || wsState === 1) return; wsState = 1; - const url = getWsUrl(apiConfig); + const url = buildWebSocketURL(apiConfig, endpoint); const ws = new WebSocket(url); - ws.addEventListener('error', function (_ev) { - wsState = 3; - }); + ws.addEventListener('error', () => (wsState = 3)); ws.addEventListener('close', function (_ev) { wsState = 3; fetchLogsWithFetch(apiConfig, appendLog); @@ -90,11 +91,10 @@ function fetchLogs(apiConfig, appendLog) { }); } -function fetchLogsWithFetch(apiConfig, appendLog) { +function fetchLogsWithFetch(apiConfig: LogsAPIConfig, appendLog: AppendLogFn) { if ( controller && - (apiConfigSnapshot.hostname !== apiConfig.hostname || - apiConfigSnapshot.port !== apiConfig.port || + (apiConfigSnapshot.baseURL !== apiConfig.baseURL || apiConfigSnapshot.secret !== apiConfig.secret || apiConfigSnapshot.logLevel !== apiConfig.logLevel) ) { @@ -104,11 +104,7 @@ function fetchLogsWithFetch(apiConfig, appendLog) { } fetched = true; - - apiConfigSnapshot.hostname = apiConfig.hostname; - apiConfigSnapshot.port = apiConfig.port; - apiConfigSnapshot.secret = apiConfig.secret; - apiConfigSnapshot.logLevel = apiConfig.logLevel; + apiConfigSnapshot = { ...apiConfig }; controller = new AbortController(); const signal = controller.signal; @@ -131,5 +127,3 @@ function fetchLogsWithFetch(apiConfig, appendLog) { } ); } - -export { fetchLogs }; diff --git a/src/api/traffic.js b/src/api/traffic.js index f3f33ca..e50ec5e 100644 --- a/src/api/traffic.js +++ b/src/api/traffic.js @@ -1,4 +1,4 @@ -import { getURLAndInit } from '../misc/request-helper'; +import { buildWebSocketURL, getURLAndInit } from '../misc/request-helper'; const endpoint = '/traffic'; const textDecoder = new TextDecoder('utf-8'); @@ -69,15 +69,6 @@ function pump(reader) { }); } -function getWsUrl(apiConfig) { - const { hostname, port, secret } = apiConfig; - let qs = ''; - if (typeof secret === 'string' && secret !== '') { - qs += '?token=' + encodeURIComponent(secret); - } - return `ws://${hostname}:${port}${endpoint}${qs}`; -} - // 1 OPEN // other value CLOSED // similar to ws readyState but not the same @@ -86,7 +77,7 @@ let wsState; function fetchData(apiConfig) { if (fetched || wsState === 1) return traffic; wsState = 1; - const url = getWsUrl(apiConfig); + const url = buildWebSocketURL(apiConfig, endpoint); const ws = new WebSocket(url); ws.addEventListener('error', function (_ev) { wsState = 3; diff --git a/src/app.js b/src/app.js index f483269..78b75f9 100644 --- a/src/app.js +++ b/src/app.js @@ -10,6 +10,7 @@ const rootEl = document.getElementById('app'); Modal.setAppElement(rootEl); +// ReactDOM.render(, rootEl); const { createRoot } = ReactDOM; const root = createRoot(rootEl); root.render(); diff --git a/src/components/APIConfig.js b/src/components/APIConfig.js deleted file mode 100644 index bcda06e..0000000 --- a/src/components/APIConfig.js +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; - -import { getClashAPIConfig, updateClashAPIConfig } from '../store/app'; -import s0 from './APIConfig.module.css'; -import Button from './Button'; -import Field from './Field'; -import { connect } from './StateProvider'; -import SvgYacd from './SvgYacd'; - -const { useState, useEffect, useRef, useCallback } = React; - -const mapState = (s) => ({ - apiConfig: getClashAPIConfig(s), -}); - -function APIConfig({ apiConfig, dispatch }) { - const [hostname, setHostname] = useState(apiConfig.hostname); - const [port, setPort] = useState(apiConfig.port); - const [secret, setSecret] = useState(apiConfig.secret); - - const userTouchedFlagRef = useRef(false); - const contentEl = useRef(null); - - useEffect(() => { - contentEl.current.focus(); - - async function detectApiServer() { - // API server probing - // likely the current page url share the same base url with the API - // server - try { - const res = await fetch('/'); - const data = await res.json(); - if (data.hello === 'clash' && userTouchedFlagRef.current === false) { - const { hostname, port } = window.location; - setHostname(hostname); - setPort(port); - } - } catch (err) { - // ignore - } - } - - detectApiServer(); - }, []); - - const handleInputOnChange = useCallback((e) => { - userTouchedFlagRef.current = true; - const target = e.target; - const { name } = target; - const value = target.value; - switch (name) { - case 'port': - setPort(value); - break; - case 'hostname': - setHostname(value); - break; - case 'secret': - setSecret(value); - break; - default: - throw new Error(`unknown input name ${name}`); - } - }, []); - - const updateConfig = useCallback(() => { - dispatch(updateClashAPIConfig({ hostname, port, secret })); - }, [hostname, port, secret, dispatch]); - - const handleContentOnKeyDown = useCallback( - (e) => { - // enter keyCode is 13 - if (e.keyCode !== 13) return; - updateConfig(); - }, - [updateConfig] - ); - - return ( - // eslint-disable-next-line jsx-a11y/no-static-element-interactions -
-
-
- -
-
-
-
-
- -
-
- -
-
-
- -
-
-
-
-
- ); -} - -export default connect(mapState)(APIConfig); diff --git a/src/components/APIConfig.module.css b/src/components/APIConfig.module.css index f6f41f8..1092aed 100644 --- a/src/components/APIConfig.module.css +++ b/src/components/APIConfig.module.css @@ -20,7 +20,7 @@ } .body { - padding: 30px 0 10px; + padding: 30px 0 0; } .hostnamePort { @@ -37,8 +37,14 @@ } } +.error { + height: 20px; + font-size: 0.8em; + color: #ff8b8b; +} + .footer { - padding: 30px 0 10px; + padding: 5px 0 10px; display: flex; justify-content: flex-end; align-items: center; diff --git a/src/components/APIConfig.tsx b/src/components/APIConfig.tsx new file mode 100644 index 0000000..88bec8b --- /dev/null +++ b/src/components/APIConfig.tsx @@ -0,0 +1,116 @@ +import * as React from 'react'; +import { fetchConfigs } from 'src/api/configs'; +import { ClashAPIConfig } from 'src/types'; + +import { getClashAPIConfig, updateClashAPIConfig } from '../store/app'; +import s0 from './APIConfig.module.css'; +import Button from './Button'; +import Field from './Field'; +import { connect } from './StateProvider'; +import SvgYacd from './SvgYacd'; + +const { useState, useRef, useCallback } = React; +const Ok = 0; + +const mapState = (s) => ({ + apiConfig: getClashAPIConfig(s), +}); + +function APIConfig({ apiConfig, dispatch }) { + const [baseURL, setBaseURL] = useState(apiConfig.baseURL); + const [secret, setSecret] = useState(apiConfig.secret); + const [errMsg, setErrMsg] = useState(''); + + const userTouchedFlagRef = useRef(false); + const contentEl = useRef(null); + + const handleInputOnChange = useCallback((e) => { + userTouchedFlagRef.current = true; + setErrMsg(''); + const target = e.target; + const { name } = target; + const value = target.value; + switch (name) { + case 'baseURL': + setBaseURL(value); + break; + case 'secret': + setSecret(value); + break; + default: + throw new Error(`unknown input name ${name}`); + } + }, []); + + const onConfirm = useCallback(() => { + verify({ baseURL, secret }).then((ret) => { + if (ret[0] !== Ok) { + setErrMsg(ret[1]); + } else { + dispatch(updateClashAPIConfig({ baseURL, secret })); + } + }); + }, [baseURL, secret, dispatch]); + + const handleContentOnKeyDown = useCallback( + (e) => { + if (e.key !== 'Enter') return; + onConfirm(); + }, + [onConfirm] + ); + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
+
+
+ +
+
+
+
+ + +
+
+
{errMsg ? errMsg : null}
+
+
+
+ ); +} + +export default connect(mapState)(APIConfig); + +async function verify(apiConfig: ClashAPIConfig): Promise<[number, string?]> { + try { + new URL(apiConfig.baseURL); + } catch (e) { + return [1, 'Invalid URL']; + } + try { + const res = await fetchConfigs(apiConfig); + if (res.status > 399) { + return [1, res.statusText]; + } + return [Ok]; + } catch (e) { + return [1, 'Failed to connect']; + } +} diff --git a/src/components/CollapsibleSectionHeader.tsx b/src/components/CollapsibleSectionHeader.tsx index 77e7596..8614621 100644 --- a/src/components/CollapsibleSectionHeader.tsx +++ b/src/components/CollapsibleSectionHeader.tsx @@ -2,7 +2,6 @@ import cx from 'clsx'; import * as React from 'react'; import { ChevronDown } from 'react-feather'; -import { keyCodes } from '../misc/keycode'; import Button from './Button'; import s from './CollapsibleSectionHeader.module.css'; import { SectionNameType } from './shared/Basic'; @@ -19,7 +18,7 @@ export default function Header({ name, type, toggle, isOpen, qty }: Props) { const handleKeyDown = React.useCallback( (e: React.KeyboardEvent) => { e.preventDefault(); - if (e.keyCode === keyCodes.Enter || e.keyCode === keyCodes.Space) { + if (e.key === 'Enter' || e.key === ' ') { toggle(); } }, diff --git a/src/components/Field.module.css b/src/components/Field.module.css index ec0b306..9a5f1e4 100644 --- a/src/components/Field.module.css +++ b/src/components/Field.module.css @@ -4,7 +4,6 @@ input { -webkit-appearance: none; background-color: transparent; - /* background-color: var(--color-input-bg); */ background-image: none; border: none; border-radius: 0; @@ -15,7 +14,7 @@ font-size: inherit; height: 40px; outline: none; - padding: 0 8px; + padding: 0 4px; transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); width: 100%; &:focus { @@ -25,10 +24,11 @@ label { position: absolute; - left: 8px; + left: 5px; bottom: 22px; transition: transform 150ms ease-in-out; transform-origin: 0 0; + font-size: 0.9em; &.floatAbove { transform: scale(0.75) translateY(-25px); } diff --git a/src/components/Logs.js b/src/components/Logs.js index d43737e..4ba59b9 100644 --- a/src/components/Logs.js +++ b/src/components/Logs.js @@ -12,7 +12,6 @@ import ContentHeader from './ContentHeader'; import s0 from './Logs.module.css'; import LogSearch from './LogSearch'; import { connect } from './StateProvider'; -// import { useStoreState, useActions } from '../misc/store'; import SvgYacd from './SvgYacd'; const { useCallback, memo, useEffect } = React; @@ -64,7 +63,6 @@ const Row = memo(({ index, style, data }) => { }, areEqual); function Logs({ dispatch, logLevel, apiConfig, logs }) { - const { hostname, port, secret } = apiConfig; const appendLogInternal = useCallback( (log) => { dispatch(appendLog(log)); @@ -72,8 +70,8 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) { [dispatch] ); useEffect(() => { - fetchLogs({ hostname, port, secret, logLevel }, appendLogInternal); - }, [hostname, port, secret, logLevel, appendLogInternal]); + fetchLogs({ ...apiConfig, logLevel }, appendLogInternal); + }, [apiConfig, logLevel, appendLogInternal]); const [refLogsContainer, containerHeight] = useRemainingViewPortHeight(); return ( diff --git a/src/misc/request-helper.js b/src/misc/request-helper.js deleted file mode 100644 index 317dada..0000000 --- a/src/misc/request-helper.js +++ /dev/null @@ -1,24 +0,0 @@ -const headersCommon = { - 'Content-Type': 'application/json' -}; - -export function genCommonHeaders({ secret }) { - const h = { ...headersCommon }; - if (secret) { - h['Authorization'] = `Bearer ${secret}`; - } - return h; -} - -export function getAPIBaseURL({ hostname, port }) { - return `http://${hostname}:${port}`; -} - -export function getURLAndInit({ hostname, port, secret }) { - const baseURL = getAPIBaseURL({ hostname, port }); - const headers = genCommonHeaders({ secret }); - return { - url: baseURL, - init: { headers } - }; -} diff --git a/src/misc/request-helper.ts b/src/misc/request-helper.ts new file mode 100644 index 0000000..1096a67 --- /dev/null +++ b/src/misc/request-helper.ts @@ -0,0 +1,31 @@ +import { trimTrailingSlash } from 'src/misc/utils'; +import { ClashAPIConfig } from 'src/types'; + +const headersCommon = { 'Content-Type': 'application/json' }; + +function genCommonHeaders({ secret }: { secret?: string }) { + const h = { ...headersCommon }; + if (secret) { + h['Authorization'] = `Bearer ${secret}`; + } + return h; +} + +export function getURLAndInit({ baseURL, secret }: ClashAPIConfig) { + const headers = genCommonHeaders({ secret }); + return { + url: baseURL, + init: { headers }, + }; +} + +export function buildWebSocketURL(apiConfig: ClashAPIConfig, endpoint: string) { + const { baseURL, secret } = apiConfig; + let qs = ''; + if (typeof secret === 'string' && secret !== '') { + qs += '?token=' + encodeURIComponent(secret); + } + const url = new URL(baseURL); + url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:'); + return `${trimTrailingSlash(url.href)}${endpoint}${qs}`; +} diff --git a/src/misc/utils.js b/src/misc/utils.js deleted file mode 100644 index 66146c0..0000000 --- a/src/misc/utils.js +++ /dev/null @@ -1,23 +0,0 @@ -export function throttle(fn, timeout) { - let pending = false; - - return (...args) => { - if (!pending) { - pending = true; - fn(...args); - setTimeout(() => { - pending = false; - }, timeout); - } - }; -} - -export function debounce(fn, timeout) { - let timeoutId; - return (...args) => { - if (timeoutId) clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - fn(...args); - }, timeout); - }; -} diff --git a/src/misc/utils.ts b/src/misc/utils.ts new file mode 100644 index 0000000..d07b5f5 --- /dev/null +++ b/src/misc/utils.ts @@ -0,0 +1,33 @@ +export function throttle( + fn: (...args: T) => unknown, + timeout: number +) { + let pending = false; + + return (...args: T) => { + if (!pending) { + pending = true; + fn(...args); + setTimeout(() => { + pending = false; + }, timeout); + } + }; +} + +export function debounce( + fn: (...args: T) => unknown, + timeout: number +) { + let timeoutId: number; + return (...args: T) => { + if (timeoutId) clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + fn(...args); + }, timeout); + }; +} + +export function trimTrailingSlash(s: string) { + return s.replace(/\/$/, ''); +} diff --git a/src/store/app.js b/src/store/app.js index f01f52f..dc0e269 100644 --- a/src/store/app.js +++ b/src/store/app.js @@ -1,9 +1,12 @@ import { clearState, loadState, saveState } from '../misc/storage'; -import { debounce } from '../misc/utils'; +import { debounce, trimTrailingSlash } from '../misc/utils'; import { fetchConfigs } from './configs'; import { closeModal } from './modals'; -export const getClashAPIConfig = (s) => s.app.clashAPIConfig; +export const getClashAPIConfig = (s) => { + const idx = s.app.selectedClashAPIConfigIndex; + return s.app.clashAPIConfigs[idx]; +}; export const getTheme = (s) => s.app.theme; export const getSelectedChartStyleIndex = (s) => s.app.selectedChartStyleIndex; export const getLatencyTestUrl = (s) => s.app.latencyTestUrl; @@ -14,12 +17,11 @@ export const getAutoCloseOldConns = (s) => s.app.autoCloseOldConns; const saveStateDebounced = debounce(saveState, 600); -export function updateClashAPIConfig({ hostname: iHostname, port, secret }) { +export function updateClashAPIConfig({ baseURL, secret }) { return async (dispatch, getState) => { - const hostname = iHostname.trim().replace(/^http(s):\/\//, ''); - const clashAPIConfig = { hostname, port, secret }; + const clashAPIConfig = { baseURL, secret }; dispatch('appUpdateClashAPIConfig', (s) => { - s.app.clashAPIConfig = clashAPIConfig; + s.app.clashAPIConfigs[0] = clashAPIConfig; }); // side effect saveState(getState().app); @@ -92,13 +94,15 @@ export function updateCollapsibleIsOpen(prefix, name, v) { }; } +const defaultClashAPIConfig = { + baseURL: 'http://127.0.0.1:7892', + secret: '', +}; // type Theme = 'light' | 'dark'; const defaultState = { - clashAPIConfig: { - hostname: '127.0.0.1', - port: '7892', - secret: '', - }, + selectedClashAPIConfigIndex: 0, + clashAPIConfigs: [defaultClashAPIConfig], + latencyTestUrl: 'http://www.gstatic.com/generate_204', selectedChartStyleIndex: 0, theme: 'dark', @@ -126,18 +130,24 @@ function parseConfigQueryString() { export function initialState() { let s = loadState(); s = { ...defaultState, ...s }; - // TODO flat clashAPIConfig? - const query = parseConfigQueryString(); + + const conf = s.clashAPIConfigs[s.selectedClashAPIConfigIndex]; + const url = new URL(conf.baseURL); if (query.hostname) { - s.clashAPIConfig.hostname = query.hostname; + url.hostname = query.hostname; } if (query.port) { - s.clashAPIConfig.port = query.port; + url.port = query.port; } + // url.href is a stringifier and it appends a trailing slash + // that is not we want + conf.baseURL = trimTrailingSlash(url.href); + if (query.secret) { - s.clashAPIConfig.secret = query.secret; + conf.secret = query.secret; } + if (query.theme) { if (query.theme === 'dark' || query.theme === 'light') { s.theme = query.theme; diff --git a/src/store/proxies.tsx b/src/store/proxies.tsx index 20caed7..ccc5767 100644 --- a/src/store/proxies.tsx +++ b/src/store/proxies.tsx @@ -1,8 +1,9 @@ import { atom } from 'recoil'; +import { ClashAPIConfig } from 'src/types'; import * as connAPI from '../api/connections'; import * as proxiesAPI from '../api/proxies'; -import { getAutoCloseOldConns,getLatencyTestUrl } from './app'; +import { getAutoCloseOldConns, getLatencyTestUrl } from './app'; type PrimitiveProxyType = 'Shadowsocks' | 'Snell' | 'Socks5' | 'Http' | 'Vmess'; @@ -87,13 +88,7 @@ export const getDangleProxyNames = (s: GlobalState) => export const getShowModalClosePrevConns = (s: GlobalState) => s.proxies.showModalClosePrevConns; -type APIConfig = { - hostname: string; - port: string; - secret?: string; -}; - -export function fetchProxies(apiConfig: APIConfig) { +export function fetchProxies(apiConfig: ClashAPIConfig) { return async (dispatch: any, getState: any) => { const [proxiesData, providersData] = await Promise.all([ proxiesAPI.fetchProxies(apiConfig), @@ -135,7 +130,7 @@ export function fetchProxies(apiConfig: APIConfig) { }; } -export function updateProviderByName(apiConfig: APIConfig, name: string) { +export function updateProviderByName(apiConfig: ClashAPIConfig, name: string) { return async (dispatch) => { try { await proxiesAPI.updateProviderByName(apiConfig, name); @@ -166,7 +161,7 @@ export function healthcheckProviderByName(apiConfig, name) { } async function closeGroupConns( - apiConfig: APIConfig, + apiConfig: ClashAPIConfig, groupName: string, exceptionItemName: string ) { @@ -213,7 +208,7 @@ function resolveChain( async function switchProxyImpl( dispatch: any, getState: () => GlobalState, - apiConfig: APIConfig, + apiConfig: ClashAPIConfig, groupName: string, itemName: string ) { @@ -256,7 +251,7 @@ function closeModalClosePrevConns() { } function closePrevConns( - apiConfig: APIConfig, + apiConfig: ClashAPIConfig, proxies: ProxiesMapping, switchTo: SwitchProxyCtxItem ) { @@ -267,7 +262,7 @@ function closePrevConns( closeGroupConns(apiConfig, switchTo.groupName, chain[0]); } -function closePrevConnsAndTheModal(apiConfig: APIConfig) { +function closePrevConnsAndTheModal(apiConfig: ClashAPIConfig) { return async (dispatch, getState) => { const s = getState(); const switchTo = s.proxies.switchProxyCtx?.to; diff --git a/src/types.ts b/src/types.ts index 047f3fb..8988c5b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,4 @@ export type ClashAPIConfig = { - hostname: string; - port: number; + baseURL: string; secret?: string; }; diff --git a/webpack.config.js b/webpack.config.js index a73960d..25b2f52 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,6 +50,8 @@ const postcssPlugins = () => isDev ? false : require('cssnano')(), ].filter(Boolean); +const postcssOptions = { plugins: postcssPlugins() }; + const cssExtractPlugin = new MiniCssExtractPlugin({ filename: isDev ? '[name].css' : '[name].[contenthash].css', }); @@ -89,7 +91,7 @@ module.exports = { children: false, }, // https://webpack.js.org/configuration/devtool/ - devtool: isDev ? 'eval-source-map' : false, + devtool: isDev ? 'eval-source-map' : 'source-map', entry: { // app: ['react-hot-loader/patch', './src/app.js'] app: ['./src/app.js'], @@ -124,7 +126,7 @@ module.exports = { use: [ isDev ? { loader: 'style-loader' } : MiniCssExtractPlugin.loader, { loader: 'css-loader' }, - { loader: 'postcss-loader', options: { plugins: postcssPlugins } }, + { loader: 'postcss-loader', options: { postcssOptions } }, ], }, { @@ -141,10 +143,7 @@ module.exports = { }, }, }, - { - loader: 'postcss-loader', - options: { plugins: postcssPlugins }, - }, + { loader: 'postcss-loader', options: { postcssOptions } }, ], }, ], diff --git a/yarn.lock b/yarn.lock index 9bfbfdc..f747e9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26,12 +26,12 @@ semver "^5.5.0" "@babel/core@^7.11.1": - version "7.11.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.5.tgz#6ad96e2f71899ea3f9b651f0a911e85205d1ff6d" - integrity sha512-fsEANVOcZHzrsV6dMVWqpSeXClq3lNbYrfFGme6DE25FQWe7pyeYpXyx9guqUnpy466JLzZ8z4uwSr2iv60V5Q== + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651" + integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.5" + "@babel/generator" "^7.11.6" "@babel/helper-module-transforms" "^7.11.0" "@babel/helpers" "^7.10.4" "@babel/parser" "^7.11.5" @@ -45,16 +45,16 @@ lodash "^4.17.19" resolve "^1.3.2" semver "^5.4.1" - source-map "^0.6.1" + source-map "^0.5.0" -"@babel/generator@^7.11.5": - version "7.11.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.5.tgz#a5582773425a468e4ba269d9a1f701fbca6a7a82" - integrity sha512-9UqHWJ4IwRTy4l0o8gq2ef8ws8UPzvtMkVKjTLAiRmza9p9V6Z+OfuNd9fB1j5Q67F+dVJtPC2sZXI8NM9br4g== +"@babel/generator@^7.11.5", "@babel/generator@^7.11.6": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" + integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== dependencies: "@babel/types" "^7.11.5" jsesc "^2.5.1" - source-map "^0.6.1" + source-map "^0.5.0" "@babel/generator@^7.8.6": version "7.9.0" @@ -1469,84 +1469,61 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.0.1.tgz#88bde9239e29d688315718552cf80a3490491017" - integrity sha512-pQZtXupCn11O4AwpYVUX4PDFfmIJl90ZgrEBg0CEcqlwvPiG0uY81fimr1oMFblZnpKAq6prrT9a59pj1x58rw== +"@typescript-eslint/eslint-plugin@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.0.tgz#7d309f60815ff35e9627ad85e41928d7b7fd443f" + integrity sha512-U+nRJx8XDUqJxYF0FCXbpmD9nWt/xHDDG0zsw1vrVYAmEAuD/r49iowfurjSL2uTA2JsgtpsyG7mjO7PHf2dYw== dependencies: - "@typescript-eslint/experimental-utils" "4.0.1" - "@typescript-eslint/scope-manager" "4.0.1" + "@typescript-eslint/experimental-utils" "4.1.0" + "@typescript-eslint/scope-manager" "4.1.0" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.0.1.tgz#7d9a3ab6821ad5274dad2186c1aa0d93afd696eb" - integrity sha512-gAqOjLiHoED79iYTt3F4uSHrYmg/GPz/zGezdB0jAdr6S6gwNiR/j7cTZ8nREKVzMVKLd9G3xbg1sV9GClW3sw== +"@typescript-eslint/experimental-utils@4.1.0", "@typescript-eslint/experimental-utils@^4.0.1": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.1.0.tgz#263d7225645c09a411c8735eeffd417f50f49026" + integrity sha512-paEYLA37iqRIDPeQwAmoYSiZ3PiHsaAc3igFeBTeqRHgPnHjHLJ9OGdmP6nwAkF65p2QzEsEBtpjNUBWByNWzA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.0.1" - "@typescript-eslint/types" "4.0.1" - "@typescript-eslint/typescript-estree" "4.0.1" + "@typescript-eslint/scope-manager" "4.1.0" + "@typescript-eslint/types" "4.1.0" + "@typescript-eslint/typescript-estree" "4.1.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@^2.5.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== +"@typescript-eslint/parser@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.1.0.tgz#9b0409411725f14cd7faa81a664e5051225961db" + integrity sha512-hM/WNCQTzDHgS0Ke3cR9zPndL3OTKr9OoN9CL3UqulsAjYDrglSwIIgswSmHBcSbOzLmgaMARwrQEbIumIglvQ== dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-scope "^5.0.0" - eslint-utils "^2.0.0" - -"@typescript-eslint/parser@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.0.1.tgz#73772080db7a7a4534a35d719e006f503e664dc3" - integrity sha512-1+qLmXHNAWSQ7RB6fdSQszAiA7JTwzakj5cNYjBTUmpH2cqilxMZEIV+DRKjVZs8NzP3ALmKexB0w/ExjcK9Iw== - dependencies: - "@typescript-eslint/scope-manager" "4.0.1" - "@typescript-eslint/types" "4.0.1" - "@typescript-eslint/typescript-estree" "4.0.1" + "@typescript-eslint/scope-manager" "4.1.0" + "@typescript-eslint/types" "4.1.0" + "@typescript-eslint/typescript-estree" "4.1.0" debug "^4.1.1" -"@typescript-eslint/scope-manager@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.0.1.tgz#24d93c3000bdfcc5a157dc4d32b742405a8631b5" - integrity sha512-u3YEXVJ8jsj7QCJk3om0Y457fy2euEOkkzxIB/LKU3MdyI+FJ2gI0M4aKEaXzwCSfNDiZ13a3lDo5DVozc+XLQ== +"@typescript-eslint/scope-manager@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.1.0.tgz#9e389745ee9cfe12252ed1e9958808abd6b3a683" + integrity sha512-HD1/u8vFNnxwiHqlWKC/Pigdn0Mvxi84Y6GzbZ5f5sbLrFKu0al02573Er+D63Sw67IffVUXR0uR8rpdfdk+vA== dependencies: - "@typescript-eslint/types" "4.0.1" - "@typescript-eslint/visitor-keys" "4.0.1" + "@typescript-eslint/types" "4.1.0" + "@typescript-eslint/visitor-keys" "4.1.0" -"@typescript-eslint/types@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.0.1.tgz#1cf72582f764931f085cb8230ff215980fe467b2" - integrity sha512-S+gD3fgbkZYW2rnbjugNMqibm9HpEjqZBZkTiI3PwbbNGWmAcxolWIUwZ0SKeG4Dy2ktpKKaI/6+HGYVH8Qrlg== +"@typescript-eslint/types@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.1.0.tgz#edbd3fec346f34e13ce7aa176b03b497a32c496a" + integrity sha512-rkBqWsO7m01XckP9R2YHVN8mySOKKY2cophGM8K5uDK89ArCgahItQYdbg/3n8xMxzu2elss+an1TphlUpDuJw== -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/typescript-estree@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.1.0.tgz#394046ead25164494218c0e3d6b960695ea967f6" + integrity sha512-r6et57qqKAWU173nWyw31x7OfgmKfMEcjJl9vlJEzS+kf9uKNRr4AVTRXfTCwebr7bdiVEkfRY5xGnpPaNPe4Q== dependencies: - debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" - -"@typescript-eslint/typescript-estree@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.0.1.tgz#29a43c7060641ec51c902d9f50ac7c5866ec479f" - integrity sha512-zGzleORFXrRWRJAMLTB2iJD1IZbCPkg4hsI8mGdpYlKaqzvKYSEWVAYh14eauaR+qIoZVWrXgYSXqLtTlxotiw== - dependencies: - "@typescript-eslint/types" "4.0.1" - "@typescript-eslint/visitor-keys" "4.0.1" + "@typescript-eslint/types" "4.1.0" + "@typescript-eslint/visitor-keys" "4.1.0" debug "^4.1.1" globby "^11.0.1" is-glob "^4.0.1" @@ -1554,12 +1531,12 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/visitor-keys@4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.0.1.tgz#d4e8de62775f2a6db71c7e8539633680039fdd6c" - integrity sha512-yBSqd6FjnTzbg5RUy9J+9kJEyQjTI34JdGMJz+9ttlJzLCnGkBikxw+N5n2VDcc3CesbIEJ0MnZc5uRYnrEnCw== +"@typescript-eslint/visitor-keys@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.1.0.tgz#b2d528c9484e7eda1aa4f86ccf0432fb16e4d545" + integrity sha512-+taO0IZGCtCEsuNTTF2Q/5o8+fHrlml8i9YsZt2AiDCdYEJzYlsmRY991l/6f3jNXFyAWepdQj7n8Na6URiDRQ== dependencies: - "@typescript-eslint/types" "4.0.1" + "@typescript-eslint/types" "4.1.0" eslint-visitor-keys "^2.0.0" "@webassemblyjs/ast@1.9.0": @@ -3012,10 +2989,10 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" -css-loader@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.2.2.tgz#b668b3488d566dc22ebcf9425c5f254a05808c89" - integrity sha512-omVGsTkZPVwVRpckeUnLshPp12KsmMSLqYxs12+RzM9jRR5Y+Idn/tBffjXRvOE+qW7if24cuceFJqYR5FmGBg== +css-loader@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e" + integrity sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg== dependencies: camelcase "^6.0.0" cssesc "^3.0.0" @@ -3027,7 +3004,7 @@ css-loader@^4.2.2: postcss-modules-scope "^2.2.0" postcss-modules-values "^3.0.0" postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" + schema-utils "^2.7.1" semver "^7.3.2" css-select-base-adapter@^0.1.1: @@ -3715,12 +3692,12 @@ eslint-plugin-import@^2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^23.20.0: - version "23.20.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz#e1d69c75f639e99d836642453c4e75ed22da4099" - integrity sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw== +eslint-plugin-jest@^24.0.0: + version "24.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.0.tgz#6b1c460c529104c7d16d889e76fe708b281c4d14" + integrity sha512-a0G7hSDbuBCW4PNT6MVpAyfnGbUDOqxzOyhR6wT2BIBnR7MhvfAqd6KKfsTjX+Z3gxzIHiEsihzdClU4cSc6qQ== dependencies: - "@typescript-eslint/experimental-utils" "^2.5.0" + "@typescript-eslint/experimental-utils" "^4.0.1" eslint-plugin-jsx-a11y@^6.3.1: version "6.3.1" @@ -4295,20 +4272,20 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -framer-motion@^2.6.5: - version "2.6.6" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.6.6.tgz#49b52c58cfab708f9e743393c714806980b0030f" - integrity sha512-uSglqXNmJJ2YD0lq3x3ZLM7mwgwVNwoo6B3CAjeil4GJFDcENEwKNdGVMB53TbsFZwPiBLlcm9gJPQV1f9144w== +framer-motion@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.6.7.tgz#f01685750573ab24fec8b53ac48a05e8fec6deef" + integrity sha512-szwqKCH/yvMtaFmtphP3np/1/5HMDbPWfmAbVMXDvofC3qDi4+XsxVnI60egCnmYGxPeE5t4JFbZjAqOZy4HPg== dependencies: - framesync "^4.0.4" + framesync "^4.1.0" hey-listen "^1.0.8" - popmotion "9.0.0-rc.12" + popmotion "9.0.0-rc.13" style-value-types "^3.1.9" tslib "^1.10.0" optionalDependencies: "@emotion/is-prop-valid" "^0.8.2" -framesync@^4.0.4, framesync@^4.1.0: +framesync@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/framesync/-/framesync-4.1.0.tgz#69a8db3ca432dc70d6a76ba882684a1497ef068a" integrity sha512-MmgZ4wCoeVxNbx2xp5hN/zPDCbLSKiDt4BbbslK7j/pM2lg5S0vhTNv1v8BCVb99JPIo6hXBFdwzU7Q4qcAaoQ== @@ -4452,7 +4429,7 @@ glob@^7.0.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -4792,15 +4769,15 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -husky@^4.0.0: - version "4.2.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" - integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== +husky@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de" + integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" - cosmiconfig "^6.0.0" + cosmiconfig "^7.0.0" find-versions "^3.2.0" opencollective-postinstall "^2.0.2" pkg-dir "^4.2.0" @@ -4857,13 +4834,6 @@ immer@^7.0.8: resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.8.tgz#41dcbc5669a76500d017bef3ad0d03ce0a1d7c1e" integrity sha512-XnpIN8PXBBaOD43U8Z17qg6RQiKQYGDGGCIbz1ixmLGwBkSWwmrmx5X7d+hTtXDM8ur7m5OdLE0PiO+y5RB3pw== -import-cwd@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" - integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= - dependencies: - import-from "^2.1.0" - import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -4880,13 +4850,6 @@ import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" - integrity sha1-M1238qev/VOqpHHUuAId7ja387E= - dependencies: - resolve-from "^3.0.0" - import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" @@ -5404,6 +5367,11 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klona@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.3.tgz#98274552c513583ad7a01456a789a2a0b4a2a538" + integrity sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg== + language-subtag-registry@~0.3.2: version "0.3.20" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755" @@ -5908,10 +5876,10 @@ mkdirp@~0.5.1: dependencies: minimist "0.0.8" -modern-normalize@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-0.7.0.tgz#023a110fb00eafc65c7e01681a6e00db64c152aa" - integrity sha512-EiotlP2YuHgasvDZcgM5OAHAo9TV5d+GG6MNPO0wSaBRqVEc0ROPq7MuRRO7lfTGtAB6B72hTCfwA8mS6FeNeg== +modern-normalize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.0.0.tgz#539d84a1e141338b01b346f3e27396d0ed17601e" + integrity sha512-1lM+BMLGuDfsdwf3rsgBSrxJwAZHFIrQ8YR61xIqdHo0uNKI9M52wNpHSrliZATJp51On6JD0AfRxd4YGSU0lw== moment@^2.10.2: version "2.24.0" @@ -6586,10 +6554,10 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -popmotion@9.0.0-rc.12: - version "9.0.0-rc.12" - resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.12.tgz#c5db0d9bf7f314b7cc8789257f2902965ee9f0bf" - integrity sha512-wfiXZHxG9GGwgsFuw1Ssw6ZYzwxaBcm/O+7qKmeGIokUEFgpIys3YyK7BQv9O58P6gK1Ys+psTfSt9lycrItaQ== +popmotion@9.0.0-rc.13: + version "9.0.0-rc.13" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.13.tgz#0280968fb0dc3bd273cc3dbd4da24b01b6cfbcce" + integrity sha512-M8Ksx2THYrAUrptE5Ydd3jazQAMoXxqpClR/K8xK87v4UrJUTCb39kNar/ucJ4Rs7qPqPdVu+iqPKOJhpMylXQ== dependencies: framesync "^4.1.0" hey-listen "^1.0.8" @@ -6684,23 +6652,16 @@ postcss-import@^12.0.1: read-cache "^1.0.0" resolve "^1.1.7" -postcss-load-config@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" - integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== +postcss-loader@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.0.0.tgz#bfd04521d2a2db69cb9e8a7cee0a5ae704e751dc" + integrity sha512-LdpfM9yCVFeJzofnaFvLf3g9oMuH2mIIqOcu81n6JHxzRNBl78GHiYWUJ5gf4c7A7VZiBCeWwfVAMw/mQCAM3Q== dependencies: - cosmiconfig "^5.0.0" - import-cwd "^2.0.0" - -postcss-loader@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" - integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== - dependencies: - loader-utils "^1.1.0" - postcss "^7.0.0" - postcss-load-config "^2.0.0" - schema-utils "^1.0.0" + cosmiconfig "^7.0.0" + klona "^2.0.3" + loader-utils "^2.0.0" + schema-utils "^2.7.1" + semver "^7.3.2" postcss-merge-longhand@^4.0.11: version "4.0.11" @@ -7278,10 +7239,10 @@ react-modal@^3.11.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" -react-query@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.14.1.tgz#d4a0a721c6155fc7dc682e036c284243ff48f74e" - integrity sha512-3NONArLC0SrYSOmdJMHBGkQBNFo76YjLgN38bpWXz5NIeK0qaVxjwqobfipSX3N7dsHbLGFV9jImkky0DHLfoA== +react-query@^2.15.4: + version "2.15.4" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.15.4.tgz#0447510fc30aebba5a4b99879ec4b7155d9b3f20" + integrity sha512-gEF2Ebe1MtiyLKEaVEIlRMl6rwvP/RVwP+Xf68vhXDfD91IeZkkW7plRjoaHa0/NeUPM1AgTHb1Z7NtqaYDXjA== react-refresh@^0.8.2: version "0.8.3" @@ -7754,7 +7715,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1: +schema-utils@^2.6.5, schema-utils@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==