diff --git a/src/lib/ip.ts b/src/lib/ip.ts new file mode 100644 index 0000000..a6d8ff2 --- /dev/null +++ b/src/lib/ip.ts @@ -0,0 +1,24 @@ +// copy from https://github.com/sindresorhus/ip-regex + +const v4 = '(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}' + +const v6segment = '[a-fA-F\\d]{1,4}' + +const v6 = ` +(?: +(?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8 +(?:${v6segment}:){6}(?:${v4}|:${v6segment}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4 +(?:${v6segment}:){5}(?::${v4}|(?::${v6segment}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4 +(?:${v6segment}:){4}(?:(?::${v6segment}){0,1}:${v4}|(?::${v6segment}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4 +(?:${v6segment}:){3}(?:(?::${v6segment}){0,2}:${v4}|(?::${v6segment}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4 +(?:${v6segment}:){2}(?:(?::${v6segment}){0,3}:${v4}|(?::${v6segment}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4 +(?:${v6segment}:){1}(?:(?::${v6segment}){0,4}:${v4}|(?::${v6segment}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4 +(?::(?:(?::${v6segment}){0,5}:${v4}|(?::${v6segment}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4 +)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1 +`.replace(/\s*\/\/.*$/gm, '').replace(/\n/g, '').trim() + +const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`) + +export function isIP (input: string): boolean { + return v46Exact.test(input) +} diff --git a/src/stores/request.ts b/src/stores/request.ts index 9f8a106..aeca1c7 100644 --- a/src/stores/request.ts +++ b/src/stores/request.ts @@ -2,6 +2,7 @@ import { atom, useAtom, useAtomValue } from 'jotai' import { atomWithStorage } from 'jotai/utils' import { useLocation } from 'react-router-dom' +import { isIP } from '@lib/ip' import { isClashX, jsBridge } from '@lib/jsBridge' import { Client } from '@lib/request' @@ -19,8 +20,35 @@ const clashxConfigAtom = atom(async () => { } }) +function getControllerFromLocation (): string | null { + if (!isIP(location.hostname)) { + return null + } + + if (location.port !== '') { + return JSON.stringify([ + { + hostname: location.hostname, + port: +location.port, + secret: '', + }, + ]) + } + + const port = location.protocol === 'https:' ? 443 : 80 + return JSON.stringify([ + { + hostname: location.hostname, + port, + secret: '', + }, + ]) +} + // jotai v2 use initialValue first avoid hydration warning, but we don't want that -const hostsStorageOrigin = localStorage.getItem('externalControllers') ?? '[]' +const hostsStorageOrigin = localStorage.getItem('externalControllers') ?? + getControllerFromLocation() ?? + '[{ "hostname": "127.0.0.1", "port": 7890, "secret": "" }]' const hostSelectIdxStorageOrigin = localStorage.getItem('externalControllerIndex') ?? '0' export const hostsStorageAtom = atomWithStorage