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)