@samitouri / QOSami-HFS / commits / f8fce64b

better typing

Massimo Melina committed Sep 2, 2023 at 23:42 UTC f8fce64bc79313d3bab3a2314c6e3306355dbff1
3 files changed +32 -22
src/api.net.ts
+9 -4
@@ -2,7 +2,7 @@
2
3 import { ApiError, ApiHandlers } from './apiMiddleware'
4 import { Client } from 'nat-upnp'
5 -import { HTTP_SERVICE_UNAVAILABLE, IS_MAC, IS_WINDOWS } from './const'
5 +import { HTTP_FAILED_DEPENDENCY, HTTP_SERVICE_UNAVAILABLE, IS_MAC, IS_WINDOWS } from './const'
6 import axios from 'axios'
7 import {parse} from 'node-html-parser'
8 import _ from 'lodash'
@@ -65,7 +65,9 @@ const apis: ApiHandlers = {
65 async map_port({ external }) {
66 const { gatewayIp, mapped, internalPort } = await getNatInfo()
67 if (!gatewayIp)
68 - throw new ApiError(HTTP_SERVICE_UNAVAILABLE, 'upnp failed')
68 + return new ApiError(HTTP_SERVICE_UNAVAILABLE, 'upnp failed')
69 + if (!internalPort)
70 + return new ApiError(HTTP_FAILED_DEPENDENCY, 'no internal port')
71 const client = new Client()
72 if (mapped)
73 await client.removeMapping({ private: mapped.private.port, public: mapped.public.port, protocol: 'tcp' })
@@ -76,7 +78,10 @@ const apis: ApiHandlers = {
78
79 async check_server() {
80 const { publicIp, internalPort, externalPort } = await getNatInfo()
79 - if (!publicIp) return new ApiError(HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip')
81 + if (!publicIp)
82 + return new ApiError(HTTP_SERVICE_UNAVAILABLE, 'cannot detect public ip')
83 + if (!internalPort)
84 + return new ApiError(HTTP_FAILED_DEPENDENCY, 'no internal port')
85 const prjInfo = await getProjectInfo()
86 const port = externalPort || internalPort
87 console.log(`checking server ${publicIp}:${port}`)
@@ -95,7 +100,7 @@ const apis: ApiHandlers = {
100 const service = new URL(svc.url).hostname
101 console.log('trying service', service)
102 const api = (axios as any)[svc.method]
98 - const body = svc.body?.replace('$IP', publicIp).replace('$PORT', port) || ''
103 + const body = svc.body?.replace('$IP', publicIp).replace('$PORT', String(port)) || ''
104 const res = await api(svc.url, body, {headers: svc.headers})
105 console.debug(service, 'responded')
106 const parsed = parse(res.data).querySelector(svc.selector)?.innerText
src/debounceAsync.ts
+21 -16
@@ -1,28 +1,33 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 // like lodash.debounce, but also avoids async invocations to overlap
4 -export default function debounceAsync<CB extends (...args: any[]) => Promise<R>, R>(
5 - callback: CB,
4 +export default function debounceAsync<Cancelable extends boolean = false, A extends unknown[] = unknown[], R = unknown>(
5 + callback: (...args: A) => Promise<R>,
6 wait: number=100,
7 - { leading=false, maxWait=Infinity }={}
7 + options: { leading?: boolean, maxWait?:number, cancelable?: Cancelable }={}
8 ) {
9 + type MaybeUndefined<T> = Cancelable extends true ? undefined | T : T
10 + type MaybeR = MaybeUndefined<R>
11 + const { leading=false, maxWait=Infinity, cancelable=false } = options
12 let started = 0 // latest callback invocation
13 let runningCallback: Promise<R> | undefined // latest callback invocation result
11 - let runningDebouncer: Promise<R | undefined> // latest wrapper invocation
14 + let runningDebouncer: Promise<MaybeR> // latest wrapper invocation
15 let waitingSince = 0 // we are delaying invocation since
13 - let whoIsWaiting: undefined | any[] // args' array object identifies the pending instance, and incidentally stores args
14 - const interceptingWrapper = (...args:any[]) => runningDebouncer = debouncer.apply(null, args)
16 + let whoIsWaiting: undefined | A // args object identifies the pending instance, and incidentally stores args
17 + const interceptingWrapper = (...args: A) => runningDebouncer = debouncer(...args)
18 return Object.assign(interceptingWrapper, {
16 - cancel: () => {
17 - waitingSince = 0
18 - whoIsWaiting = undefined
19 - },
19 flush: () => runningCallback ?? exec(),
20 + ...cancelable && {
21 + cancel() {
22 + waitingSince = 0
23 + whoIsWaiting = undefined
24 + }
25 + }
26 })
27
23 - async function debouncer(...args:any[]) {
28 + async function debouncer(...args: A) {
29 if (runningCallback)
25 - return runningCallback
30 + return runningCallback as MaybeR
31 whoIsWaiting = args
32 waitingSince ||= Date.now()
33 const waitingCap = maxWait - (Date.now() - (waitingSince || started))
@@ -30,19 +35,19 @@ export default function debounceAsync<CB extends (...args: any[]) => Promise<R>,
35 if (waitFor > 0)
36 await new Promise(resolve => setTimeout(resolve, waitFor))
37 if (!whoIsWaiting) // canceled
33 - return void(waitingSince = 0)
38 + return void(waitingSince = 0) as MaybeR
39 if (whoIsWaiting !== args) // another fresher call is waiting
40 return runningDebouncer
41 return exec()
42 }
43
44 async function exec() {
40 - if (!whoIsWaiting) return
45 + if (!whoIsWaiting) return undefined as MaybeR
46 waitingSince = 0
47 started = Date.now()
48 try {
44 - runningCallback = callback.apply(null, whoIsWaiting)
45 - return await runningCallback // await necessary to go-finally at the right time and even on exceptions
49 + runningCallback = callback(...whoIsWaiting)
50 + return await runningCallback as MaybeUndefined<R> // await necessary to go-finally at the right time and even on exceptions
51 }
52 finally {
53 whoIsWaiting = undefined
src/listen.ts
+2 -2
@@ -200,13 +200,13 @@ export async function getServerStatus() {
200 https: await serverStatus(httpsSrv, httpsPortCfg.get()),
201 }
202
203 - async function serverStatus(h: typeof httpSrv, configuredPort?: number) {
203 + async function serverStatus(h: typeof httpSrv, configuredPort: number) {
204 const busy = await h?.busy
205 await wait(0) // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
206 return {
207 ..._.pick(h, ['listening', 'error']),
208 busy,
209 - port: (h?.address() as any)?.port || configuredPort,
209 + port: (h?.address() as any)?.port as number || configuredPort,
210 }
211 }}
212