auto-detect proxy on Windows #812
Massimo Melina committed
Jan 13, 2025 at 00:03 UTC
a7416d357a5a765ddaf4699be124ba9fff19e79f
7 files changed
+42
-17
src/adminApis.ts
+1
-1
@@ -181,7 +181,7 @@ const frpDebounced = debounceAsync(async () => {
181
catch {
182
return false
183
}
184
-})
184
+}, { retain: 10_000 })
185
186
export function anyAccountCanLoginAdmin() {
187
return Boolean(_.find(accountsConfig.get(), accountCanLoginAdmin))
src/api.vfs.ts
+2
-8
@@ -14,10 +14,8 @@ import {
14
IS_WINDOWS, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR, HTTP_CONFLICT, HTTP_NOT_ACCEPTABLE,
15
IS_BINARY, APP_PATH
16
} from './const'
17
-import { getDiskSpace, getDiskSpaces, getDrives } from './util-os'
17
+import { getDiskSpace, getDiskSpaces, getDrives, reg } from './util-os'
18
import { getBaseUrlOrDefault, getServerStatus } from './listen'
19
-import { promisify } from 'util'
20
-import { execFile } from 'child_process'
19
import { SendListReadable } from './SendList'
20
21
// to manipulate the tree we need the original node
@@ -253,7 +251,7 @@ const apis: ApiHandlers = {
251
async windows_integrated() {
252
return {
253
is: await reg('query', WINDOWS_REG_KEY)
256
- .then(x => x.stdout.includes('REG_SZ'), () => false)
254
+ .then(x => x.includes('REG_SZ'), () => false)
255
}
256
},
257
@@ -284,7 +282,3 @@ function simplifyName(node: VfsNode) {
282
}
283
284
const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3'
287
-
288
-function reg(...pars: string[]) {
289
- return promisify(execFile)('reg', pars)
290
-}
src/config.ts
+4
-1
@@ -141,7 +141,7 @@ export function setConfig(newCfg: Record<string,unknown>, save?: boolean) {
141
if (!newCfg.hasOwnProperty(k))
142
apply(k, newCfg[k], true)
143
started = true
144
- events.emit('configReady')
144
+ events.emit('configReady', startedWithoutConfig)
145
if (version !== VERSION) // be sure to save version
146
saveConfigAsap()
147
@@ -186,12 +186,15 @@ function stringify(obj: any) {
186
return yaml.stringify(obj, { lineWidth:1000 })
187
}
188
189
+let startedWithoutConfig = false
190
console.log("config", filePath)
191
export const configFile = watchLoad(filePath, text => {
192
+ startedWithoutConfig = !text
193
try { setConfig(yaml.parse(text, { uniqueKeys: false }) || {}, false) }
194
catch(e: any) { console.error("Error in", filePath, ':', e.message || String(e)) }
195
}, {
196
failedOnFirstAttempt(){
197
+ startedWithoutConfig = true
198
console.log("No config file, using defaults")
199
setTimeout(() => // this is called synchronously, but we need to call setConfig after first tick, when all configs are defined
200
setConfig({}, false))
src/debounceAsync.ts
+7
-4
@@ -7,21 +7,23 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
7
options: {
8
// time to wait after invocation of the debounced function. If you call again while waiting, the timer starts again.
9
wait?: number,
10
- // in a train of invocations, should we execute also the first one, or just the last one?
11
- leading?: boolean,
10
// since the wait-ing is renewed at each invocation, indefinitely, do you want to put a cap to it?
11
maxWait?: number,
12
+ // in a train of invocations, should we execute also the first one, or just the last one?
13
+ leading?: boolean,
14
// for how long do you want to cache last success value, and return that at next invocation?
15
retain?: number,
16
// for how long do you want to cache last failure value, and return that at next invocation?
17
retainFailure?: number,
18
+ // if a call is overlapping another, return the same promise, instead of queuing
19
+ reuseRunning?: boolean,
20
// should we offer a cancel method to the returned function? if we do, the awaited-type will include undefined
21
cancelable?: Cancelable
22
} = {}
23
) {
24
type MaybeUndefined<T> = Cancelable extends true ? undefined | T : T
25
type MaybeR = MaybeUndefined<R>
24
- const { wait=0, leading=false, maxWait=Infinity, cancelable=false, retain=0, retainFailure } = options
26
+ const { wait=0, leading=false, maxWait=Infinity, cancelable=false, retain=0, retainFailure, reuseRunning } = options
27
let started = 0 // latest callback invocation
28
let runningCallback: Promise<R> | undefined // latest callback invocation result
29
let latestDebouncer: Promise<MaybeR | R> // latest wrapper invocation
@@ -44,7 +46,7 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
46
})
47
48
async function debouncer(...args: A) {
47
- if (runningCallback)
49
+ if (reuseRunning && runningCallback)
50
return runningCallback as MaybeR
51
const now = Date.now()
52
if (latestCallback && now - latestTimestamp < (latestHasFailed ? retainFailure ?? retain : retain))
@@ -61,6 +63,7 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
63
}
64
if (whoIsWaiting !== args) // another fresher call is waiting
65
return latestDebouncer
66
+ await runningCallback // in case we don't reuseRunning
67
return exec()
68
}
69
src/nat.ts
+1
-1
@@ -88,7 +88,7 @@ export const getNatInfo = debounceAsync(async () => {
88
externalPort,
89
proto: status?.https?.listening ? 'https' : status?.http?.listening ? 'http' : '',
90
}
91
-})
91
+}, { reuseRunning: true })
92
getNatInfo()
93
94
function findGateway(): Promise<string | undefined> {
src/outboundProxy.ts
+22
-1
@@ -1,9 +1,13 @@
1
import { defineConfig } from './config'
2
import { parse } from 'node:url'
3
import { httpStream } from './util-http'
4
+import { reg } from './util-os'
5
+import events from './events'
6
+import { IS_WINDOWS } from './const'
7
+import { prefix } from './cross'
8
9
// don't move this in util-http, where it would mostly belong, as a require to config.ts would prevent tests using util-http
6
-defineConfig('outbound_proxy', '', v => {
10
+const outboundProxy = defineConfig('outbound_proxy', '', v => {
11
try {
12
parse(v)
13
httpStream.defaultProxy = v
@@ -13,3 +17,20 @@ defineConfig('outbound_proxy', '', v => {
17
return ''
18
}
19
})
20
+
21
+
22
+events.once('configReady', async startedWithoutConfig => {
23
+ if (!IS_WINDOWS || !startedWithoutConfig) return
24
+ // try to read Windows system setting for proxy
25
+ const out = await reg('query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings')
26
+ if (!/ProxyEnable.+(\d)/.exec(out)?.[1]) return
27
+ const read = /ProxyServer.+?([\d:.]+)/.exec(out)?.[1]
28
+ if (!read) return
29
+ // it can be like "IP:PORT" or "http=IP:PORT;https=IP:PORT;ftp=IP:PORT"
30
+ const url = prefix('https://', /https=([\d:.]+)/.exec(out)?.[1]) // prefer https
31
+ || prefix('http://', /http=([\d:.]+)/.exec(out)?.[1])
32
+ || !read.includes('=') && 'http://' + read // simpler form
33
+ if (!url) return
34
+ outboundProxy.set(url)
35
+ console.log("detected proxy", read)
36
+})
\ No newline at end of file
src/util-os.ts
+5
-1
@@ -1,6 +1,6 @@
1
import { dirname } from 'path'
2
import { existsSync, statfsSync } from 'fs'
3
-import { exec, ExecOptions } from 'child_process'
3
+import { exec, execFile, ExecOptions } from 'child_process'
4
import { isWindowsDrive, onlyTruthy, promiseBestEffort } from './misc'
5
import Parser from '@gregoranders/csv';
6
import { pid, ppid } from 'node:process'
@@ -87,3 +87,7 @@ export const RUNNING_AS_SERVICE = IS_WINDOWS && getWindowsServicePids().then(x =
87
console.log("couldn't determine if we are running as a service")
88
console.debug(e)
89
})
90
+
91
+export function reg(...pars: string[]) {
92
+ return promisify(execFile)('reg', pars).then(x => x.stdout)
93
+}