diff --git a/extra/sidecar/sysproxy.win32-x64-msvc.node b/extra/sidecar/sysproxy.win32-x64-msvc.node new file mode 100644 index 0000000..b0744ba Binary files /dev/null and b/extra/sidecar/sysproxy.win32-x64-msvc.node differ diff --git a/package.json b/package.json index 70779ff..9f07440 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@electron-toolkit/utils": "^4.0.0", - "@mihomo-party/sysproxy": "^2.0.8", + "sysproxy-rs": "file:src/native/sysproxy", "adm-zip": "^0.5.16", "axios": "^1.13.2", "chokidar": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eeb77c3..412269b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@electron-toolkit/utils': specifier: ^4.0.0 version: 4.0.0(electron@37.10.0) - '@mihomo-party/sysproxy': - specifier: ^2.0.8 - version: 2.0.8 adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -38,6 +35,9 @@ importers: iconv-lite: specifier: ^0.7.1 version: 0.7.1 + sysproxy-rs: + specifier: file:src/native/sysproxy + version: file:src/native/sysproxy webdav: specifier: ^5.8.0 version: 5.8.0 @@ -1405,54 +1405,6 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} - '@mihomo-party/sysproxy-darwin-arm64@2.0.8': - resolution: {integrity: sha512-4bSqsjEkmtXzgr8zrSUiNmOdlfRDnkFoXICfJqH7ZlM+4L6n74zrm6perFP0NHPpn/oZO97QXGxIJUQSNhFDrw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@mihomo-party/sysproxy-darwin-x64@2.0.8': - resolution: {integrity: sha512-sIDzG7yyQZu+DKQ8X1MeYubdEqXSDjzYjVi+5rVZG/jfLlucS9QZNNiXyoTTDUD5cGRcqv1gYNRynd2Csewesg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@mihomo-party/sysproxy-linux-arm64-gnu@2.0.8': - resolution: {integrity: sha512-weKk+KcB4lghEj3z15x9FSyla3PT3uLIEU4l4LE4RqhzxgkbJmOt7Wu+ofx4/1k8g8OwwGXIucNgYsV0qpnZQg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@mihomo-party/sysproxy-linux-x64-gnu@2.0.8': - resolution: {integrity: sha512-wLt63mztsnZoGFUKxzizRRRd5qAtINg+tB2zdhOnr+0E9TaKLGxZnhYm+Nk8tAB1EBvqjmTWsJG9MDHikh2agg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@mihomo-party/sysproxy-win32-arm64-msvc@2.0.8': - resolution: {integrity: sha512-+Mxkw8d3rD6sbFZjjZ18kfx1/WrWXOVlpKd8k3Gdf4LUg7nW8vr64Eaxvjxwcw9AQ1Bu61SHEtvNZfu7woCc1w==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@mihomo-party/sysproxy-win32-ia32-msvc@2.0.8': - resolution: {integrity: sha512-xOVagbGu21MGzEMidpgMoQGRHY0V1EFdDKb+ZhPPnoIbFhECBRR9fQK7lENV6e7S41ppNbg1Rja4J94VpXdvZA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - - '@mihomo-party/sysproxy-win32-x64-msvc@2.0.8': - resolution: {integrity: sha512-AIgCFoExX36BgXN8sQyf0G99wrObFO0LGBzEFs9OsS2cg8bPkpt63XkAkNcGVxlqJD4WLzsS1GhbsL3qzo64DQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@mihomo-party/sysproxy@2.0.8': - resolution: {integrity: sha512-tCnkDL4UjbUPvFvFubswmWxz56f+gTsYDpv1ULke1YDEZN7aTSREgC3K+Ge7JjZj2jUZYU1lEYuhFgeFz6+W6w==} - engines: {node: '>= 10'} - '@npmcli/fs@2.1.2': resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -4746,6 +4698,9 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + sysproxy-rs@file:src/native/sysproxy: + resolution: {directory: src/native/sysproxy, type: directory} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -6769,37 +6724,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@mihomo-party/sysproxy-darwin-arm64@2.0.8': - optional: true - - '@mihomo-party/sysproxy-darwin-x64@2.0.8': - optional: true - - '@mihomo-party/sysproxy-linux-arm64-gnu@2.0.8': - optional: true - - '@mihomo-party/sysproxy-linux-x64-gnu@2.0.8': - optional: true - - '@mihomo-party/sysproxy-win32-arm64-msvc@2.0.8': - optional: true - - '@mihomo-party/sysproxy-win32-ia32-msvc@2.0.8': - optional: true - - '@mihomo-party/sysproxy-win32-x64-msvc@2.0.8': - optional: true - - '@mihomo-party/sysproxy@2.0.8': - optionalDependencies: - '@mihomo-party/sysproxy-darwin-arm64': 2.0.8 - '@mihomo-party/sysproxy-darwin-x64': 2.0.8 - '@mihomo-party/sysproxy-linux-arm64-gnu': 2.0.8 - '@mihomo-party/sysproxy-linux-x64-gnu': 2.0.8 - '@mihomo-party/sysproxy-win32-arm64-msvc': 2.0.8 - '@mihomo-party/sysproxy-win32-ia32-msvc': 2.0.8 - '@mihomo-party/sysproxy-win32-x64-msvc': 2.0.8 - '@npmcli/fs@2.1.2': dependencies: '@gar/promisify': 1.1.3 @@ -10935,6 +10859,8 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + sysproxy-rs@file:src/native/sysproxy: {} + tailwind-merge@3.4.0: {} tailwind-variants@3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.18): diff --git a/src/main/sys/sysproxy.ts b/src/main/sys/sysproxy.ts index 3dec8d2..c27ddce 100644 --- a/src/main/sys/sysproxy.ts +++ b/src/main/sys/sysproxy.ts @@ -1,10 +1,8 @@ -import { triggerAutoProxy, triggerManualProxy } from '@mihomo-party/sysproxy' +import { triggerAutoProxy, triggerManualProxy } from 'sysproxy-rs' import { getAppConfig, getControledMihomoConfig } from '../config' import { pacPort, startPacServer, stopPacServer } from '../resolve/server' import { promisify } from 'util' -import { exec, execFile } from 'child_process' -import path from 'path' -import { resourcesFilesDir } from '../utils/dirs' +import { exec } from 'child_process' import { net } from 'electron' import axios from 'axios' import fs from 'fs' @@ -76,87 +74,51 @@ async function enableSysProxy(): Promise { const { sysProxy } = await getAppConfig() const { mode, host, bypass = defaultBypass } = sysProxy const { 'mixed-port': port = 7890 } = await getControledMihomoConfig() - const execFilePromise = promisify(execFile) - switch (mode || 'manual') { - case 'auto': { - if (process.platform === 'win32') { - try { - await execFilePromise(path.join(resourcesFilesDir(), 'sysproxy.exe'), [ - 'pac', - `http://${host || '127.0.0.1'}:${pacPort}/pac` - ]) - } catch { - triggerAutoProxy(true, `http://${host || '127.0.0.1'}:${pacPort}/pac`) - } - } else if (process.platform === 'darwin') { - await helperRequest(() => - axios.post( - 'http://localhost/pac', - { url: `http://${host || '127.0.0.1'}:${pacPort}/pac` }, - { - socketPath: helperSocketPath - } - ) - ) - } else { - triggerAutoProxy(true, `http://${host || '127.0.0.1'}:${pacPort}/pac`) - } + const proxyHost = host || '127.0.0.1' - break + if (process.platform === 'darwin') { + // macOS 需要 helper 提权 + if (mode === 'auto') { + await helperRequest(() => + axios.post( + 'http://localhost/pac', + { url: `http://${proxyHost}:${pacPort}/pac` }, + { socketPath: helperSocketPath } + ) + ) + } else { + await helperRequest(() => + axios.post( + 'http://localhost/global', + { host: proxyHost, port: port.toString(), bypass: bypass.join(',') }, + { socketPath: helperSocketPath } + ) + ) } - - case 'manual': { - if (process.platform === 'win32') { - try { - await execFilePromise(path.join(resourcesFilesDir(), 'sysproxy.exe'), [ - 'global', - `${host || '127.0.0.1'}:${port}`, - bypass.join(';') - ]) - } catch { - triggerManualProxy(true, host || '127.0.0.1', port, bypass.join(',')) - } - } else if (process.platform === 'darwin') { - await helperRequest(() => - axios.post( - 'http://localhost/global', - { host: host || '127.0.0.1', port: port.toString(), bypass: bypass.join(',') }, - { - socketPath: helperSocketPath - } - ) - ) - } else { - triggerManualProxy(true, host || '127.0.0.1', port, bypass.join(',')) - } - break + } else { + // Windows / Linux 直接使用 sysproxy-rs + if (mode === 'auto') { + triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`) + } else { + triggerManualProxy(true, proxyHost, port, bypass.join(',')) } } } async function disableSysProxy(): Promise { await stopPacServer() - const execFilePromise = promisify(execFile) - if (process.platform === 'win32') { - try { - await execFilePromise(path.join(resourcesFilesDir(), 'sysproxy.exe'), ['set', '1']) - } catch { - triggerAutoProxy(false, '') - triggerManualProxy(false, '', 0, '') - } - } else if (process.platform === 'darwin') { + + if (process.platform === 'darwin') { await helperRequest(() => - axios.get('http://localhost/off', { - socketPath: helperSocketPath - }) + axios.get('http://localhost/off', { socketPath: helperSocketPath }) ) } else { + // Windows / Linux 直接使用 sysproxy-rs triggerAutoProxy(false, '') triggerManualProxy(false, '', 0, '') } } -// Helper function to check if socket file exists function isSocketFileExists(): boolean { try { return fs.existsSync(helperSocketPath) @@ -165,7 +127,6 @@ function isSocketFileExists(): boolean { } } -// Check if helper process is running (no admin privileges needed) async function isHelperRunning(): Promise { try { const execPromise = promisify(exec) @@ -176,7 +137,6 @@ async function isHelperRunning(): Promise { } } -// Start or restart helper service via launchctl async function startHelperService(): Promise { const execPromise = promisify(exec) const shell = `launchctl kickstart -k system/party.mihomo.helper` @@ -185,7 +145,6 @@ async function startHelperService(): Promise { await new Promise((resolve) => setTimeout(resolve, 1500)) } -// Send signal to recreate socket (only if process is running) async function requestSocketRecreation(): Promise { try { const execPromise = promisify(exec) @@ -199,7 +158,6 @@ async function requestSocketRecreation(): Promise { } } -// Wrapper function for helper requests with auto-retry on socket issues async function helperRequest(requestFn: () => Promise, maxRetries = 2): Promise { let lastError: Error | null = null diff --git a/src/native/sysproxy/index.d.ts b/src/native/sysproxy/index.d.ts new file mode 100644 index 0000000..f66990e --- /dev/null +++ b/src/native/sysproxy/index.d.ts @@ -0,0 +1,24 @@ +export interface SysproxyInfo { + enable: boolean + host: string + port: number + bypass: string +} + +export interface AutoproxyInfo { + enable: boolean + url: string +} + +export function triggerManualProxy( + enable: boolean, + host: string, + port: number, + bypass: string +): void + +export function triggerAutoProxy(enable: boolean, url: string): void + +export function getSystemProxy(): SysproxyInfo + +export function getAutoProxy(): AutoproxyInfo diff --git a/src/native/sysproxy/index.js b/src/native/sysproxy/index.js new file mode 100644 index 0000000..2efb1fa --- /dev/null +++ b/src/native/sysproxy/index.js @@ -0,0 +1,75 @@ +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') + +const { platform, arch } = process + +let nativeBinding = null +let loadError = null + +function isMusl() { + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') + } catch { + return true + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime + } +} + +function getBindingName() { + switch (platform) { + case 'win32': + if (arch === 'x64') return 'sysproxy.win32-x64-msvc.node' + if (arch === 'arm64') return 'sysproxy.win32-arm64-msvc.node' + break + case 'darwin': + if (arch === 'x64') return 'sysproxy.darwin-x64.node' + if (arch === 'arm64') return 'sysproxy.darwin-arm64.node' + break + case 'linux': + if (isMusl()) { + if (arch === 'x64') return 'sysproxy.linux-x64-musl.node' + if (arch === 'arm64') return 'sysproxy.linux-arm64-musl.node' + } else { + if (arch === 'x64') return 'sysproxy.linux-x64-gnu.node' + if (arch === 'arm64') return 'sysproxy.linux-arm64-gnu.node' + } + break + } + throw new Error(`Unsupported platform: ${platform}-${arch}`) +} + +function loadBinding() { + const bindingName = getBindingName() + + // 查找项目根目录的 extra/sidecar + let currentDir = __dirname + while (currentDir !== require('path').dirname(currentDir)) { + const sidecarPath = join(currentDir, 'extra', 'sidecar', bindingName) + if (existsSync(sidecarPath)) { + try { + nativeBinding = require(sidecarPath) + return nativeBinding + } catch (e) { + loadError = e + } + } + currentDir = require('path').dirname(currentDir) + } + + if (loadError) { + throw loadError + } + throw new Error(`Native binding not found: ${bindingName}`) +} + +const binding = loadBinding() + +module.exports.triggerManualProxy = binding.triggerManualProxy +module.exports.triggerAutoProxy = binding.triggerAutoProxy +module.exports.getSystemProxy = binding.getSystemProxy +module.exports.getAutoProxy = binding.getAutoProxy diff --git a/src/native/sysproxy/package.json b/src/native/sysproxy/package.json new file mode 100644 index 0000000..8996b51 --- /dev/null +++ b/src/native/sysproxy/package.json @@ -0,0 +1,8 @@ +{ + "name": "sysproxy-rs", + "version": "0.4.0", + "description": "System proxy library for Node.js", + "main": "index.js", + "types": "index.d.ts", + "license": "MIT" +}