fix: inconsistent frontend after logout (both admin button and HFS.userBelongsTo plugins API)

Massimo Melina committed Feb 28, 2026 at 17:21 UTC 60175f92d96277ec5642dfa1fc8529e5beea7425
4 files changed +11 -13
frontend/src/login.ts
+1 -1
@@ -41,7 +41,7 @@ export function logout() {
41 return apiCall('logout', {}, { modal: working }).catch(res => {
42 if (res.code !== HTTP_UNAUTHORIZED) // we expect this error code
43 throw res
44 - state.username = ''
44 + refreshSession()
45 if (fallbackToBasicAuth())
46 return location.reload() // reloading avoids nasty warnings with ff52
47 reloadList()
frontend/src/state.ts
+1 -2
@@ -38,9 +38,8 @@ export const state = proxy<typeof FRONTEND_OPTIONS & {
38 canChangePassword: boolean
39 uri: string
40 uploadOnExisting: 'skip' | 'overwrite' | 'rename'
41 - expandedUsername: string[]
41 + expandedUsername?: string[]
42 }>({
43 - expandedUsername: [],
43 uploadOnExisting: getHFS().dontOverwriteUploading ? 'rename' : 'skip',
44 uri: '',
45 canChangePassword: false,
shared/index.ts
+8 -9
@@ -2,7 +2,7 @@
2
3 import _ from 'lodash'
4 import { apiCall } from './api'
5 -import { DAY, Dict, formatBytes, HOUR, MINUTE, objSameKeys, typedEntries, wantArray } from '../src/cross'
5 +import { DAY, Dict, formatBytes, HOUR, MINUTE, objFromKeys, objSameKeys, typedEntries, wantArray } from '../src/cross'
6 export * from './react'
7 export * from './dialogs'
8 export * from './md'
@@ -32,7 +32,7 @@ Object.assign(HFS, {
32 getPluginPublic: () => getScriptAttr('src')?.match(/^.*\//)?.[0],
33 getPluginConfig: () => HFS.plugins[HFS.getPluginKey()] || {},
34 loadScript: (uri: string) => loadScript(uri.includes('//') || uri.startsWith('/') ? uri : HFS.getPluginPublic() + uri),
35 - userBelongsTo: (username: string | string[]) => wantArray(username).some(x => HFS.state.expandedUsername.includes(x)),
35 + userBelongsTo: (username: string | string[]) => wantArray(username).some(x => HFS.state.expandedUsername?.includes(x)),
36 cpuSpeedIndex,
37 copyTextToClipboard,
38 urlParams,
@@ -135,20 +135,19 @@ export function getPrefixUrl() {
135
136 export function makeSessionRefresher(state: any) {
137 let timeout: any
138 - const initial = getHFS().session
139 - refreshSession(initial)
138 + refreshSession(getHFS().session)
139 return refreshSession
140
142 - function refreshSession(response: any) {
143 - if (!response) return
141 + function refreshSession(response?: any) {
142 + clearTimeout(timeout)
143 + const keys = ['username', 'adminUrl', 'canChangePassword', 'accountExp', 'expandedUsername', 'requireChangePassword']
144 + response ??= objFromKeys(keys, () => undefined)
145 const { exp } = response
145 - Object.assign(initial, response) // keep it updated, not necessary, just in case someone is looking at this instead of the state
146 - Object.assign(state, _.pick(response, ['username', 'adminUrl', 'canChangePassword', 'accountExp', 'expandedUsername']))
146 + getHFS().session = Object.assign(state, _.pick(response, keys))
147 if (!response.username || !exp) return
148 const delta = new Date(exp).getTime() - Date.now()
149 const t = _.clamp(delta - 30_000, 4_000, 600_000)
150 console.debug('session refresh in', Math.round(t / 1000))
151 - clearTimeout(timeout)
151 timeout = setTimeout(() => apiCall('refresh_session').then(refreshSession), t)
152 }
153 }
src/api.auth.ts
+1 -1
@@ -115,7 +115,7 @@ export const refresh_session: ApiHandler = async ({}, ctx) => {
115 adminUrl: ctxAdminAccess(ctx) ? ctx.state.revProxyPath + ADMIN_URI : undefined,
116 canChangePassword: accountCanChangePassword(ctx.state.account),
117 requireChangePassword: ctx.state.account?.require_password_change,
118 - exp: keepSessionAlive.get() ? new Date(Date.now() + sessionDuration.compiled()) : undefined,
118 + exp: username && keepSessionAlive.get() ? new Date(Date.now() + sessionDuration.compiled()) : undefined,
119 accountExp: ctx.state.account?.expire,
120 }
121 }