fix(02-review): WR-03 never drop OpenWrtGateway Connect form params under concurrent load

load(params) routed every call -- including the Connect form's own
credentials -- through routerResource.refresh(), which resources.ts dedupes
per key. A second load({host, ssh_user, ssh_password}) call arriving while
an unrelated refresh was already in flight (e.g. useCachedResource's own
TTL-gated auto-revalidation) would just await that already-in-flight
promise; the caller's own params were silently never sent, with no error
surfaced.

load(params) now bypasses routerResource.refresh() entirely when explicit
params are supplied, calling rpcClient directly and writing the resolved
result into routerResource.entry so cache/TTL/status-panel rendering stays
consistent with a normal refresh() success. The plain reconnect path
(no params) is unchanged. The now-redundant pendingParams indirection is
removed since the fetcher only ever needs `{}` params going forward.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-31 03:30:54 -04:00
parent 0486045d29
commit 69358bf6c7

View File

@ -78,16 +78,15 @@ interface ScannedNetwork {
// paint the last-known state instantly and a mount inside the TTL issues no
// new RPC. No item id in this key there is exactly one configured gateway
// per node, not a per-item collection (route is the fixed `server/openwrt`,
// no :id param). `load(params)` is the explicit force-refresh path (connect,
// tollgate provision/save all want an unconditional re-fetch); `pendingParams`
// lets the fetcher read the params `load()` was called with without changing
// the cache key.
let pendingParams: Record<string, string> | undefined
// no :id param). This fetcher only ever re-fetches with `{}` params (the
// server-persisted config, no credentials needed) an explicit-params call
// (Connect form submit, or any caller passing connectedParams) never routes
// through this fetcher at all; see load()'s bypass path below (WR-03).
const routerResource = useCachedResource<RouterStatus>({
key: 'server.openwrt-status',
fetcher: (signal) => rpcClient.call<RouterStatus>({
method: 'openwrt.get-status',
params: pendingParams ?? {},
params: {},
signal,
timeout: 30000,
dedup: true,
@ -149,9 +148,43 @@ const masqEnabled = ref(true)
// Force-fetch: always refetches regardless of TTL. Used by every explicit
// user action (connect, tollgate provision/save) that must reflect a fresh
// server-side state, and by onMounted below when the cache is missing/stale.
//
// When explicit params are supplied, this bypasses routerResource.refresh()
// and calls rpcClient directly instead (WR-03): resources.ts's
// store.refresh() dedupes concurrent calls for the same key, so routing an
// explicit-params call (e.g. the Connect form's submit) through it could
// silently coalesce into an unrelated refresh already in flight (e.g.
// useCachedResource's own TTL-gated onActivated/focus revalidation)
// the caller's own params would never actually be sent, and the caller has
// no way to tell. Writing the resolved result directly into
// routerResource.entry keeps the cache/TTL/status-panel rendering
// consistent with what a normal refresh() success does.
async function load(params?: Record<string, string>) {
error.value = ''
pendingParams = params
if (params) {
try {
const result = await rpcClient.call<RouterStatus>({
method: 'openwrt.get-status',
params,
timeout: 30000,
maxRetries: 1,
})
routerResource.entry.data = result
routerResource.entry.error = null
routerResource.entry.fetchedAt = Date.now()
routerResource.entry.loadState = 'ready'
showConnectForm.value = false
connectedParams.value = params
} catch (e) {
const message = e instanceof Error ? e.message : String(e)
if (message.includes('No router configured')) {
showConnectForm.value = true
} else {
error.value = message
}
}
return
}
await routerResource.refresh()
const err = routerResource.error.value
if (err) {
@ -162,7 +195,6 @@ async function load(params?: Record<string, string>) {
}
} else {
showConnectForm.value = false
if (params) connectedParams.value = params
}
}