better code

Massimo Melina committed Nov 2, 2025 at 11:50 UTC 9d4461f636f3dd8eb99ddf220315953ab254c4fa
7 files changed +15 -13
admin/src/InternetPage.ts
+2 -3
@@ -12,7 +12,7 @@ import { alertDialog, confirmDialog, formDialog, promptDialog, toast, waitDialog
12 import { BoolField, Form, MultiSelectField, NumberField, SelectField } from '@hfs/mui-grid-form'
13 import { suggestMakingCert } from './OptionsPage'
14 import { changeBaseUrl } from './FileForm'
15 -import { getNatInfo } from '../../src/nat'
15 +import apiNet from '../../src/api.net'
16 import { ALL, WITH_IP } from './countries'
17 import _ from 'lodash'
18 import { SvgIconProps } from '@mui/material/SvgIcon/SvgIcon'
@@ -37,8 +37,7 @@ export default function InternetPage({ setTitleSide }: PageProps) {
37 const baseUrl = config.data?.[CFG.base_url]
38 const localColor = with_([status.data?.http?.error, status.data?.https?.error], ([h, s]) =>
39 h && s ? 'error' : h || s ? 'warning' : 'success')
40 - type GetNat = Awaited<ReturnType<typeof getNatInfo>>
41 - const nat = useApiEx<GetNat>('get_nat', {}, { timeout: 20 })
40 + const nat = useApiEx<typeof apiNet.get_nat>('get_nat', {}, { timeout: 20 })
41 const { data: publicIps } = useApiEx('get_public_ips')
42 const { data } = nat
43 const port = data?.internalPort
admin/src/VfsPage.ts
+2 -2
@@ -11,7 +11,7 @@ import {
11 import { Flex, useBreakpoint } from './mui'
12 import { reactJoin } from '@hfs/shared'
13 import _ from 'lodash'
14 -import { Account } from './AccountsPage'
14 +import apiAccounts from '../../src/api.accounts'
15 import FileForm from './FileForm'
16 import { Add, Delete } from '@mui/icons-material'
17 import { alertDialog, confirmDialog } from './dialog'
@@ -43,7 +43,7 @@ export default function VfsPage({ setTitleSide }: PageProps) {
43 ret.unshift(b)
44 return ret
45 }, [status, config])
46 - const accountsApi = useApiEx<{ list: Account[] }>('get_accounts') // load accounts once and for all, or !isSideBreakpoint will cause a call for each selection
46 + const accountsApi = useApiEx<typeof apiAccounts.get_accounts>('get_accounts') // load accounts once and for all, or !isSideBreakpoint will cause a call for each selection
47 const accounts = useMemo(() => _.sortBy(accountsApi?.data?.list, 'username'), [accountsApi.data])
48
49 // this will take care of closing the dialog, for the user's convenience, after "cut" button is pressed
frontend/src/login.ts
+1 -1
@@ -54,7 +54,7 @@ export async function loginDialog(closable=true, reloadAfter=true) {
54 if (fallbackToBasicAuth())
55 return location.href = '/?get=login'
56 if (closeLoginDialog)
57 - return lastPromise
57 + return lastPromise // this refers to the previous promise, as lastPromise wille be updated only after this function ends
58 let going = false
59 const { close } = newDialog({
60 closable,
src/adminApis.ts
+1 -1
@@ -5,7 +5,7 @@ import { configFile, defineConfig, getWholeConfig, setConfig } from './config'
5 import { getBaseUrlOrDefault, getIps, getServerStatus, getUrls } from './listen'
6 import {
7 API_VERSION, BUILD_TIMESTAMP, COMPATIBLE_API_VERSION, HFS_STARTED, IS_WINDOWS, VERSION,
8 - HTTP_UNAUTHORIZED, HTTP_SERVER_ERROR, HTTP_FORBIDDEN
8 + HTTP_UNAUTHORIZED, HTTP_SERVER_ERROR
9 } from './const'
10 import vfsApis from './api.vfs'
11 import accountsApis from './api.accounts'
src/api.net.ts
+2 -4
@@ -12,7 +12,7 @@ import { getNatInfo, getPublicIps, upnpClient } from './nat'
12 import { makeCert } from './acme'
13 import { selfCheck } from './selfCheck'
14
15 -const apis: ApiHandlers = {
15 +export default {
16 get_nat: getNatInfo,
17 get_public_ips: getPublicIps,
18
@@ -86,6 +86,4 @@ const apis: ApiHandlers = {
86 get_cert() {
87 return getCertObject() || { none: true }
88 }
89 -}
90 -
91 -export default apis
\ No newline at end of file
89 +} satisfies ApiHandlers
\ No newline at end of file
src/cross.ts
+6 -1
@@ -132,7 +132,12 @@ export function wait<T=undefined>(ms: number, val?: T): Promise<T | undefined> {
132
133 // throws after ms
134 export function haveTimeout<T>(ms: number, job: Promise<T>, error?: any) {
135 - return Promise.race([job, wait(ms).then(() => { throw error || Error('timeout') })])
135 + let h: Timeout
136 + return Promise.race([
137 + job.finally(() => clearTimeout(h)), // don't leave pending timeout if the job is done first
138 + new Promise<never>((_resolve, reject) =>
139 + h = setTimeout(() => reject(error || Error('timeout')), ms))
140 + ])
141 }
142
143 export function objSameKeys<S extends object,VR=any>(src: S, newValue:(value:Truthy<S[keyof S]>, key:keyof S)=>VR) {
src/vfs.ts
+1 -1
@@ -177,7 +177,7 @@ defineConfig('vfs', vfs).sub(reviewVfs)
177 async function reviewVfs(data=vfs) {
178 await (async function recur(node) {
179 if (node.source && !node.children?.length && node.isFolder === undefined) {
180 - const isFolder = /[\\/]$/.test(node.source) || (await nodeStats(node).catch(() => {}))?.isDirectory()
180 + const isFolder = /[\\/]$/.test(node.source) || await nodeStats(node).then(x => x?.isDirectory(), () => undefined)
181 setHidden(node, { isFolder })
182 }
183 if (node.children)