@revfanc/guard
v0.14.0
Published
A small framework-independent browser Back guard.
Maintainers
Readme
@revfanc/guard
A small framework-independent guard for same-document browser Back.
- LIFO Handler stack
- Current-entry and
pushState()protection - Scoped History management
- Asynchronous decisions
- No runtime dependencies
- ESM, CommonJS, and TypeScript declarations
pnpm add @revfanc/guardInstall before the router performs its first pushState(). The current entry is
protected immediately because the default scope matches every URL:
import { create, install } from "@revfanc/guard"
install()
const stop = create(async (resolve) => {
if (await confirmLeaving()) resolve()
})
stop()Use scope when only part of the application should create private edges:
install({
scope: (url) =>
url.pathname === "/landing" || url.pathname.startsWith("/landing/"),
})This matches /landing and /landing/**, but not /landing-page. Scope is
evaluated for the current URL during the first installation and for the resolved
destination URL of every later pushState().
install() protects a matching current entry without changing its visible URL
or business state fields. It places one private edge before each matching entry
created through history.pushState(). Back first traverses to that edge. Guard
immediately restores the original business entry and invokes only the top
Handler. Forward transparently skips the edge without invoking business code.
Guard stores versioned private metadata under history.state.__revfanc_guard__
on protected business entries and private edges. Applications must not read,
modify, or depend on this field. Public replaceState() calls keep the current
business-entry marker while preserving the caller's state fields, structured
clone values, URL handling, and native errors without mutating the input object.
This lets a new Document adopt the existing edge after a normal reload instead
of adding another History entry.
Calling resolve() removes that Handler. If another Handler remains, the page
stays in place and the next Back reaches the next layer. If the stack is empty,
Guard continues the original Back. Without Handlers, private edges are skipped
automatically.
Scope and lifecycle
Only single-step Back across protected entries is guarded. Forward remains
navigable but never invokes a Handler. Protecting the current entry adds one
History entry. Each matching application pushState() adds two native History
entries: one private edge and the requested business entry. Out-of-scope
pushState() retains native behavior. On a protected entry, public
replaceState() performs the requested native write and then restores only the
private marker.
Installation and the History wrapper are permanent for the lifetime of the
Document. Scope limits creation of new edges; it does not uninstall the wrapper.
Edges already created remain recognizable and are skipped even after navigation
leaves scope. Repeated install() calls are ignored and the first successful
configuration wins.
Automatic hash entries, history.go(±N) for |N| > 1, long-press history
selection, address-bar or other cross-document navigation, tab closing, iframe
joint History, and native methods captured before installation are outside the
contract. Normal reload of a protected entry is supported. Repeated modules in
the same Window reuse the first installed runtime.
scope receives a public URL; relative destinations are resolved against the
current location. If it throws, no History write is attempted. The package
assumes successful internal History writes after the browser accepts the public
pushState() and intentionally has no rollback state machine.
The root History state used for protection must be null, a plain object, or an
array. Nested structured-clone values such as Map, Date, arrays, cycles, and
shared references are preserved. A missing edge or invalid/unsupported private
marker fails open rather than starting speculative History traversal.
Development
pnpm install
pnpm check
pnpm test:e2e
pnpm pack:checkAutomated E2E tests exercise Chromium, Firefox, and WebKit. Release validation also requires manual Back checks using the real Chrome toolbar.
License
MIT
