Skip to content

useLivenessProbe ​

Watches whether something the app depends on is still answering, and says so with one boolean.

Why it exists ​

A connectivity banner looks like five lines of code. It is five lines of code, and three of them are decisions that are easy to get wrong in a way nobody notices for months:

  • probe only while down. A healthy target polled every 30 seconds costs one request per user per interval, forever, to tell you what you already knew.
  • re-probe when the browser says online. That event is the one moment a re-check is certain to be worth making.
  • keep exactly one retry chain. This is the one that bites. Two online events while down and a naive implementation has two independent loops running, each spawning its own successor forever, with teardown able to cancel only the last one scheduled. The symptom is a background request storm that nobody attributes to a banner.

Quickstart ​

What is being probed is your business — a liveness endpoint, a socket handshake, a HEAD against a CDN. Anything that rejects when unreachable will do, and the resolved value is ignored, so an existing call can be handed over as-is:

ts
import { useLivenessProbe } from '@guebbit/vue-toolkit'

const { down } = useLivenessProbe(getHealth)
vue
<div v-if="down" role="status">{{ t('api-offline') }}</div>

Lifecycle ​

One probe on creation, one on every online event, then a slow retry loop only while down, which stops the moment a probe succeeds.

Teardown is automatic: created inside an effect scope — a component setup, a Pinia setup store, a bare effectScope — it stops with that scope. Created outside one, stop() is yours to call. stop() is idempotent and final: a probe that outlives teardown cannot write to down, and any check() called after it — a late "Retry" click on a banner already unmounted — resolves without probing and starts no retry chain.

check() is what every re-probe actually calls underneath — the retry loop and the online listener are both just check() on a timer or an event, never a separate path. Calling it yourself (a manual "retry now" button, say) re-probes exactly the same way, cancelling any pending retry first. With immediate: false, down starts false (Up) without ever having actually been checked — call check() yourself to know for sure before trusting it.

Options ​

OptionDefaultWhat it does
retryDelay30000Milliseconds between retries while down. Never used while up.
immediatetrueWhether to probe as soon as the composable is created.
targetglobalThisWhat to listen to for online. The only DOM this composable touches.

Returns ​

NameTypeWhat it is
downRef<boolean>True while the last probe failed.
check() => Promise<void>Probe now, cancelling any pending retry. Never rejects.
stop() => voidStop the retry chain and unsubscribe. Idempotent.

check is what a "Retry" button binds to. It never rejects — an unreachable target is the state this reports, not a failure of the report — so the promise is safe to await without a catch.

Out-of-order probes ​

Only the newest probe may write to down. A slow failing probe interrupted by an online event that finds the target back up would otherwise land a moment later and leave the banner up over a working connection.

SSR and node ​

globalThis is an EventTarget in a browser and not one under SSR or in a node test runner, so the capability is checked rather than assumed. Where there is no online event the probe simply never re-runs on its own, and nothing throws.

A probe that throws synchronously, instead of returning a rejected promise, is read as unreachable exactly like a rejection — check() never lets it escape.

Pass a target of your own to drive the re-check from a different signal — or to assert the wiring in a runner with no DOM at all:

ts
const target = new EventTarget()
const { down } = useLivenessProbe(probe, { target })

target.dispatchEvent(new Event('online')) // re-probes

Types ​

TypeShapeWhat it's for
ILivenessProbeSettings{ retryDelay?, immediate?, target? }The options object useLivenessProbe takes.
ILivenessProbeReturnType<typeof useLivenessProbe>The whole return value, for a store or component prop that needs to name it.

See also ​

Documentation for @guebbit/vue-toolkit v5.2.0. Released under the AGPL-3.0 license.