Hyperfrontend Architecture

Hyperfrontend is a layered architecture designed for runtime micro-frontend integration. At its core, it enables independently deployed frontend applications ("features") to communicate through secure, contract-validated messaging, regardless of what framework they use.


Library Stack

The architecture is composed of specialized libraries that layer on top of each other:

LayerPackageResponsibility
SDK@hyperfrontend/featuresHost/hostee runtime SDK, shell generation, CLI
Communication@hyperfrontend/nexusBroker-channel messaging with contracts
Security@hyperfrontend/network-protocolEncryption pipelines and obfuscation
Crypto@hyperfrontend/cryptographyAES-GCM encryption, PBKDF2 key derivation, hashing
Foundation@hyperfrontend/state-machineState management patterns
@hyperfrontend/loggingStructured logging
@hyperfrontend/web-workerWeb Worker utilities
@hyperfrontend/utils/*Data, string, list, time, function utilities

Communication Layer: Nexus

@hyperfrontend/nexus implements a session protocol over the browser's postMessage API. It provides secure, contract-validated messaging between browser contexts (iframes, windows, tabs).

Canonical protocol story. The session model described here — wire handshake, pinned origins, versioned contracts, the four-state heartbeat, and polite teardown — is the canonical description of the shipped runtime. The article Microfrontends from first principles derives the same model from scratch and is the canonical rationale; the per-library architecture guides (nexus, features) document the same protocol in implementation depth. Where an older document disagrees with these, this model wins.

Broker-Channel Model

Broker: A singleton within each window context that routes messages to the appropriate channel. The broker holds the channel registry, validates incoming messages against contracts, and applies security policies.

Channel: A bidirectional communication pipe between two browser contexts. Each channel manages its own:

  • Connection lifecycle (pending → active → closed)
  • Message queue for buffering before connection
  • Event subscriptions (lifecycle and domain messages)
  • Security transport adapter (for encryption)

Connection Protocol

Channels establish sessions through a wire handshake — REQUEST, ACCEPT, OPEN — and nothing opens without it:

  • Symmetric initiation. Either side may connect first; simultaneous requests (glare) resolve deterministically by a broker-id tie-break, and handshake replays are idempotent.
  • Deadlines and retries. Pending REQUEST/ACCEPT messages re-send on a retry cadence, and every wait has a deadline: an unanswered attempt fires connect-timeout instead of hanging.
  • Origins are learned, then pinned. Each side pins the counterpart's origin at the handshake and drops anything that does not match; receive-side routing resolves by the sending window, never by claimed identity.
  • Instances are distinguished from windows. A window outlives the documents loaded into it, so each side also records which incarnation of the counterpart it is talking to and ignores frames from any other: traffic left over from a reloaded document cannot enter the session that replaced it.
  • Contracts gate the session. Required actions must appear in the counterpart's contract, and contracts carry an optional semver version checked by a compatibility rule at the handshake gate. An incompatible pair is denied (deny with a reason) before anything opens.
  • Payloads validate twice. Send validates against the sender's own emitted schemas and throws in the sender's frame; receive validates against the receiver's own accepted schemas and drops invalid payloads with a diagnosable error event.

Each connection attempt is tracked by a Process ID (UUID), enabling multiple concurrent connection attempts and clean lifecycle management.

Session Lifecycle: Heartbeat and Teardown

A session that is open is not necessarily alive, so the runtime judges liveness with four states rather than a boolean:

StateMeaning
healthyBeats are arriving within the expected budget.
unobservableSilence carries no information yet: a page is hidden (throttled timers pause the watchdog), or watching has just resumed and no beat has yet earned healthy back.
suspectThe pages are visible and the miss budget is exhausted; the feature is probably unhealthy.
goneThe session is closed or destroyed.

The feature pulses a hidden beat; the host watchdog counts misses only while both pages are visible (each side reports its own visibility). Entering suspect runs the host's unresponsive policy once per episode, and a recovering beat returns the session to healthy and re-arms it. Transitions surface as the shell's status event.

Teardown is a protocol, not an event. The polite form is a short exchange: one side proposes closing (CLOSE), the other receives a closing notice while the channel still delivers (its flush window for unsaved work), then confirms (ACK), and only then does each side fire its single close. An unacknowledged close completes at a deadline, so an unresponsive counterpart cannot hold the channel open, and the impolite forms (crash, tab close) remain covered by the heartbeat. Dirty state is a contract event: the feature declares unsaved work (setDirty), and the host can take it into account (isDirty, dirty-state) before starting a polite teardown.

A reload is a third form: the feature's document is replaced, so the session ends without either side asking. The host is told which one it was (close carries reason: 'peer-reload') and keeps the mount; the new document handshakes on the same frame and is re-announced its presentation, so a refresh costs a session, not the feature.

Contract System

Contracts define the communication interface between a host and feature:

const contract = {
  emitted: [
    { type: 'CONFIG', schema: configSchema }, // Host sends configuration
    { type: 'NAVIGATION', schema: navSchema }, // Host sends navigation events
  ],
  accepted: [
    { type: 'READY', schema: readySchema }, // Feature signals readiness
    { type: 'DATA', schema: dataSchema }, // Feature sends data updates
  ],
}
  • Emitted actions: Message types this context sends
  • Accepted actions: Message types this context receives
  • JSON Schema validation: Optional runtime validation of message payloads

Security Layer: Network Protocol

Read the model first. What this layer defends against, what it does not, and which party owns each control is stated once, in the Security Model. This section describes the transport mechanics; the model tells you what they are worth.

@hyperfrontend/network-protocol provides defense-in-depth security for message transport. It sits between the application layer and the raw postMessage transport.

Protocol Versions

VersionSecurity LevelUse Case
v1ObfuscationTrusted environments, same-origin features
v2Full EncryptionCross-origin features, sensitive data

Message Pipeline

Messages pass through staged queues for transformation:

Outbound Pipeline

Inbound Pipeline

Security Features

FeatureDescription
Dynamic Key EncryptionKeys are exchanged per-message via the packet's key field
Time-Based Password RotationPasswords rotate based on UTC time intervals, synchronized across endpoints
Clock Skew HandlingAutomatically attempts ±1 time windows for deobfuscation
Packet ObfuscationMakes ciphertext unrecognizable as encrypted data

Cryptography Layer

@hyperfrontend/cryptography provides isomorphic cryptographic primitives: identical APIs for browser (Web Crypto API) and Node.js (crypto module).

Capabilities

CapabilityImplementationDetails
EncryptionAES-256-GCMAuthenticated encryption with password-derived keys
Key DerivationPBKDF2100,000 iterations, unique salt per operation
HashingSHA-256Hexadecimal output
Time PasswordsUTC-synchronizedGenerates passwords for current/previous/next time windows
Vault StorageIn-memory encryptedPassword-protected storage with optional single-use mode

Platform Parity

// Browser
import { encrypt, decrypt } from '@hyperfrontend/cryptography/browser'

// Node.js
import { encrypt, decrypt } from '@hyperfrontend/cryptography/node'

// Same API, platform-optimized implementation
const encrypted = await encrypt('sensitive data', 'password')
const decrypted = await decrypt(encrypted, 'password')

The Shell Pattern

Two applications, two SDK surfaces. A feature app (the hostee) declares a contract and connects through @hyperfrontend/features/hostee; a host app mounts that feature through @hyperfrontend/features/host. Neither writes protocol code.

// In the feature app — declare the contract, connect to whatever host embeds it.
import { createFeature } from '@hyperfrontend/features/hostee'

const feature = createFeature({
  name: 'clock',
  contract: { emitted: [{ type: 'tick' }], accepted: [{ type: 'set-timezone' }] },
})
await feature.ready()
// In the host app — take the feature's contract, pick a mount, pick a mode, open.
import { builtInDisplayModes, createShell, DisplayMode } from '@hyperfrontend/features/host'

const shell = createShell({
  modes: builtInDisplayModes,
  contract: { emitted: [{ type: 'tick' }], accepted: [{ type: 'set-timezone' }] },
  url: 'https://features.example.com/clock',
  container: '#clock-slot',
  displayMode: DisplayMode.Embedded,
})
shell.on('tick', (time) => console.log('feature said', time))
shell.open()

The shell is the outward-facing package a feature ships so a host can do that without installing the SDK at all: hf build inlines the feature's contract, bakes its declared display modes, security protocol and permissions as defaults, bundles every dependency, and packs a tarball. The host is never the shell: the host is the application the shell is installed into.

What the Shell Contains

⚠️ The shell does not contain the feature app code. Features load at runtime from their deployment URL.

Distribution

hf build emits an ESM and a CommonJS bundle with TypeScript declarations, then packs them into a publishable tarball:

import { createFeatureShell } from '@org/clock-shell'

The published manifest declares no runtime dependencies (every @hyperfrontend library is bundled into the shell), so a host installs one package and inherits no transitive install burden.

Consumption

// Any framework, or none — the shell exposes one factory and a handle.
import { createFeatureShell } from '@org/clock-shell'

const clock = createFeatureShell({ container: '#clock-slot' })
clock.on('open', () => console.log('connected'))
clock.on('tick', (time) => render(time))
clock.open()
clock.send('set-timezone', { tz: 'UTC' })

Framework bindings are deliberately out of scope (see the Manifesto): the handle is vanilla JavaScript, and a React hook or Vue composable around it is a few lines a team writes in its own idiom.


Runtime Flow

Here's the whole sequence, from the host building the shell to both sides closing the session:

Steps 3 through 7 are the session model described above, and the host writes none of it: the SDK owns the handshake, the pinning, the geometry, the watchdog, and the teardown exchange.


Security Integration

When security is enabled, the communication flow adds encryption layers:


Isomorphic Design

All security-related packages work identically in browser and Node.js environments:

PackageBrowserNode.js
@hyperfrontend/cryptographyWeb Crypto APINode crypto module
@hyperfrontend/network-protocolWeb Crypto APINode crypto module

Entry points follow a consistent pattern:

@hyperfrontend/cryptography
├── /browser     # Web Crypto API implementation
├── /node        # Node crypto implementation
└── /common      # Platform-agnostic utilities

@hyperfrontend/network-protocol
├── /browser/v1  # Browser obfuscation protocol
├── /browser/v2  # Browser encryption protocol
├── /node/v1     # Node obfuscation protocol
└── /node/v2     # Node encryption protocol

This enables server-side features (SSR, API routes) to use the same security protocols as browser features.


Deep Dive Resources

Each package contains its own architecture documentation with implementation details:

Two cross-cutting documents sit beside them:

  • Security Model: the trust model, the browser/protocol/operator split, the capability model, and the status of every control.
  • Microfrontends from first principles: why the boundary is drawn where it is, derived from scratch. The canonical rationale for everything above.

Design Principles

PrincipleImplementation
Runtime IntegrationFeatures load at runtime, not build-time. No coordination required.
Contract-FirstCommunication interfaces are declared, validated, and type-safe.
Framework AgnosticThe protocol is the common language. Any framework works.
Zero-Dependency ShellsAll dependencies bundled. A host installs one package.
Defense in DepthOptional layered security: encryption, obfuscation, origin validation.
Functional CorePure functions with dependency injection. Side effects at boundaries.
Isomorphic APIsSame code runs in browser and Node.js.

What's Next

See the Manifesto for the project philosophy, scope boundaries, and planned features.