Skip to content

Commit

Permalink
auto-detect proxy on Windows #812
Browse files Browse the repository at this point in the history
  • Loading branch information
rejetto committed Jan 13, 2025
1 parent 988e40a commit a7416d3
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/adminApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ const frpDebounced = debounceAsync(async () => {
catch {
return false
}
})
}, { retain: 10_000 })

export function anyAccountCanLoginAdmin() {
return Boolean(_.find(accountsConfig.get(), accountCanLoginAdmin))
Expand Down
10 changes: 2 additions & 8 deletions src/api.vfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import {
IS_WINDOWS, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR, HTTP_CONFLICT, HTTP_NOT_ACCEPTABLE,
IS_BINARY, APP_PATH
} from './const'
import { getDiskSpace, getDiskSpaces, getDrives } from './util-os'
import { getDiskSpace, getDiskSpaces, getDrives, reg } from './util-os'
import { getBaseUrlOrDefault, getServerStatus } from './listen'
import { promisify } from 'util'
import { execFile } from 'child_process'
import { SendListReadable } from './SendList'

// to manipulate the tree we need the original node
Expand Down Expand Up @@ -253,7 +251,7 @@ const apis: ApiHandlers = {
async windows_integrated() {
return {
is: await reg('query', WINDOWS_REG_KEY)
.then(x => x.stdout.includes('REG_SZ'), () => false)
.then(x => x.includes('REG_SZ'), () => false)
}
},

Expand Down Expand Up @@ -284,7 +282,3 @@ function simplifyName(node: VfsNode) {
}

const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3'

function reg(...pars: string[]) {
return promisify(execFile)('reg', pars)
}
5 changes: 4 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export function setConfig(newCfg: Record<string,unknown>, save?: boolean) {
if (!newCfg.hasOwnProperty(k))
apply(k, newCfg[k], true)
started = true
events.emit('configReady')
events.emit('configReady', startedWithoutConfig)
if (version !== VERSION) // be sure to save version
saveConfigAsap()

Expand Down Expand Up @@ -186,12 +186,15 @@ function stringify(obj: any) {
return yaml.stringify(obj, { lineWidth:1000 })
}

let startedWithoutConfig = false
console.log("config", filePath)
export const configFile = watchLoad(filePath, text => {
startedWithoutConfig = !text
try { setConfig(yaml.parse(text, { uniqueKeys: false }) || {}, false) }
catch(e: any) { console.error("Error in", filePath, ':', e.message || String(e)) }
}, {
failedOnFirstAttempt(){
startedWithoutConfig = true
console.log("No config file, using defaults")
setTimeout(() => // this is called synchronously, but we need to call setConfig after first tick, when all configs are defined
setConfig({}, false))
Expand Down
11 changes: 7 additions & 4 deletions src/debounceAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
options: {
// time to wait after invocation of the debounced function. If you call again while waiting, the timer starts again.
wait?: number,
// in a train of invocations, should we execute also the first one, or just the last one?
leading?: boolean,
// since the wait-ing is renewed at each invocation, indefinitely, do you want to put a cap to it?
maxWait?: number,
// in a train of invocations, should we execute also the first one, or just the last one?
leading?: boolean,
// for how long do you want to cache last success value, and return that at next invocation?
retain?: number,
// for how long do you want to cache last failure value, and return that at next invocation?
retainFailure?: number,
// if a call is overlapping another, return the same promise, instead of queuing
reuseRunning?: boolean,
// should we offer a cancel method to the returned function? if we do, the awaited-type will include undefined
cancelable?: Cancelable
} = {}
) {
type MaybeUndefined<T> = Cancelable extends true ? undefined | T : T
type MaybeR = MaybeUndefined<R>
const { wait=0, leading=false, maxWait=Infinity, cancelable=false, retain=0, retainFailure } = options
const { wait=0, leading=false, maxWait=Infinity, cancelable=false, retain=0, retainFailure, reuseRunning } = options
let started = 0 // latest callback invocation
let runningCallback: Promise<R> | undefined // latest callback invocation result
let latestDebouncer: Promise<MaybeR | R> // latest wrapper invocation
Expand All @@ -44,7 +46,7 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
})

async function debouncer(...args: A) {
if (runningCallback)
if (reuseRunning && runningCallback)
return runningCallback as MaybeR
const now = Date.now()
if (latestCallback && now - latestTimestamp < (latestHasFailed ? retainFailure ?? retain : retain))
Expand All @@ -61,6 +63,7 @@ export function debounceAsync<Cancelable extends boolean = false, A extends unkn
}
if (whoIsWaiting !== args) // another fresher call is waiting
return latestDebouncer
await runningCallback // in case we don't reuseRunning
return exec()
}

Expand Down
2 changes: 1 addition & 1 deletion src/nat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const getNatInfo = debounceAsync(async () => {
externalPort,
proto: status?.https?.listening ? 'https' : status?.http?.listening ? 'http' : '',
}
})
}, { reuseRunning: true })
getNatInfo()

function findGateway(): Promise<string | undefined> {
Expand Down
23 changes: 22 additions & 1 deletion src/outboundProxy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { defineConfig } from './config'
import { parse } from 'node:url'
import { httpStream } from './util-http'
import { reg } from './util-os'
import events from './events'
import { IS_WINDOWS } from './const'
import { prefix } from './cross'

// don't move this in util-http, where it would mostly belong, as a require to config.ts would prevent tests using util-http
defineConfig('outbound_proxy', '', v => {
const outboundProxy = defineConfig('outbound_proxy', '', v => {
try {
parse(v)
httpStream.defaultProxy = v
Expand All @@ -13,3 +17,20 @@ defineConfig('outbound_proxy', '', v => {
return ''
}
})


events.once('configReady', async startedWithoutConfig => {
if (!IS_WINDOWS || !startedWithoutConfig) return
// try to read Windows system setting for proxy
const out = await reg('query', 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings')
if (!/ProxyEnable.+(\d)/.exec(out)?.[1]) return
const read = /ProxyServer.+?([\d:.]+)/.exec(out)?.[1]
if (!read) return
// it can be like "IP:PORT" or "http=IP:PORT;https=IP:PORT;ftp=IP:PORT"
const url = prefix('https://', /https=([\d:.]+)/.exec(out)?.[1]) // prefer https
|| prefix('http://', /http=([\d:.]+)/.exec(out)?.[1])
|| !read.includes('=') && 'http://' + read // simpler form
if (!url) return
outboundProxy.set(url)
console.log("detected proxy", read)
})
6 changes: 5 additions & 1 deletion src/util-os.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dirname } from 'path'
import { existsSync, statfsSync } from 'fs'
import { exec, ExecOptions } from 'child_process'
import { exec, execFile, ExecOptions } from 'child_process'
import { isWindowsDrive, onlyTruthy, promiseBestEffort } from './misc'
import Parser from '@gregoranders/csv';
import { pid, ppid } from 'node:process'
Expand Down Expand Up @@ -87,3 +87,7 @@ export const RUNNING_AS_SERVICE = IS_WINDOWS && getWindowsServicePids().then(x =
console.log("couldn't determine if we are running as a service")
console.debug(e)
})

export function reg(...pars: string[]) {
return promisify(execFile)('reg', pars).then(x => x.stdout)
}

0 comments on commit a7416d3

Please sign in to comment.