@hyperfrontend/nexus
Secure cross-window communication library for micro-frontends with contract-validated messaging, origin-based security policies, and connection lifecycle management.
What is Nexus?
Nexus provides a complete infrastructure for building secure, reliable communication between browser windows, iframes, and micro-frontend applications. At its core, Nexus implements a broker-channel architecture where a central broker manages multiple channels, each representing a connection to another window or frame.
Unlike raw postMessage usage, Nexus enforces communication contracts—typed agreements defining which message types each participant can send and receive. This contract-first approach catches integration errors at development time rather than production, making micro-frontend systems significantly more maintainable as they scale.
Key Features
- Contract-Validated Messaging — Define accepted and emitted message types with optional JSON Schema validation
- Broker-Channel Architecture — Central broker manages multiple independent channels to different windows
- Origin-Based Security — Whitelist/blacklist filtering plus custom security policy functions
- Connection Lifecycle Management — Full state machine for connect, disconnect, cancel, deny, and destroy operations
- Event Subscription System — Subscribe to lifecycle events (open, close, cancel, deny, invalid) and user messages
- Message Queueing — Automatically queue messages when channel is not yet active
- Contract Extension & Merging — Dynamically extend contracts or merge multiple contracts together
- Functional API Design — Factory functions with closure-based encapsulation for clean, testable code
Architecture Highlights
Nexus uses a functional programming approach with factory functions (createBroker, createChannel) that return handle objects. Internal state is encapsulated via closures, making the system highly testable and avoiding the complexity of class-based inheritance. The routing layer uses a handler registry pattern, allowing protocol actions (REQUEST_CONNECTION, ACCEPT_CONNECTION, etc.) to be processed by dedicated handlers.
For a comprehensive deep dive into the library's internals, see the Architecture Documentation.
Why Use Nexus?
Type-Safe Contracts Prevent Integration Bugs
Micro-frontend architectures often fail at integration points where different teams assume different message formats. Nexus contracts explicitly declare what each window sends and accepts:
const contract: IChannelContract = {
emitted: [
{
type: 'USER_UPDATED',
schema: {
/* JSON Schema */
},
},
{ type: 'NAVIGATION_REQUEST' },
],
accepted: [{ type: 'USER_DATA' }, { type: 'NAVIGATION_COMPLETE' }],
}
This makes communication agreements explicit, version-controlled, and enforced at runtime.
Origin-Based Security Without Boilerplate
Cross-origin messaging is a common attack vector. Nexus provides built-in security through whitelist/blacklist filtering and custom policy functions—eliminating the need to manually check event.origin in every message handler:
const broker = createBroker({
name: 'secure-broker',
contract,
settings: {
whitelist: ['https://app1.example.com', 'https://app2.example.com'],
},
})
// Or use custom logic
broker.setSecurityPolicy((origin, contract) => {
return origin.endsWith('.example.com')
})
Hub-and-Spoke Patterns for Complex Topologies
Real micro-frontend systems often have complex communication needs: a shell application coordinating multiple micro-apps, broadcast messages to all participants, or direct messaging between specific windows. Nexus's broker architecture naturally supports these patterns:
// Central hub managing multiple spokes
const hub = createBroker({ name: 'shell', contract })
const userApp = hub.addChannel('user-app', userFrame.contentWindow)
const cartApp = hub.addChannel('cart-app', cartFrame.contentWindow)
const checkoutApp = hub
.addChannel('checkout-app', checkoutFrame.contentWindow)
[
// Connect all
(userApp, cartApp, checkoutApp)
].forEach((ch) => ch.connect())
// Broadcast to all
hub.channels.forEach((ch) => ch.send('THEME_CHANGED', { theme: 'dark' }))
Full Lifecycle Control
Connection management in distributed systems is notoriously tricky. Nexus provides explicit lifecycle events and state transitions:
const channel = broker.addChannel('partner', partnerWindow)
channel.on((event, data, channelInfo) => {
switch (event) {
case 'open':
/* connection established */ break
case 'close':
/* graceful disconnect */ break
case 'cancel':
/* connection attempt cancelled */ break
case 'deny':
/* connection request denied */ break
case 'invalid':
/* protocol violation detected */ break
}
})
channel.connect()
Message Filtering for Clean Handler Code
Instead of large switch statements, use composable message filters:
import { byType, compose } from '@hyperfrontend/nexus'
channel.onMessage(compose(byType('USER_LOGIN', handleLogin), byType('USER_LOGOUT', handleLogout), byType('DATA_SYNC', handleSync)))
Protocol Overview
Nexus implements a three-way handshake protocol for establishing secure connections, with support for graceful disconnection, cancellation, and error handling.
Connection Handshake
Initiator Responder
| |
|--- REQUEST_CONNECTION ---->|
| | (validates contract & origin)
|<-- ACCEPT_CONNECTION ------|
| (validates contract) |
|--- OPEN_CONNECTION ------->|
| |
[Connected] [Connected]
Protocol Actions:
- REQUEST_CONNECTION - Initiator sends contract and process ID
- ACCEPT_CONNECTION - Responder validates and replies with own contract
- OPEN_CONNECTION - Initiator confirms, completing handshake
Disconnection Flow
Initiator Responder
| |
|--- CLOSE_CONNECTION ------>|
| | (closes channel)
|<-- CLOSE_ACKNOWLEDGED -----|
| |
[Closed] [Closed]
Cancellation Flow
Either party can cancel before connection completes:
Initiator Responder
| |
|--- CANCEL_CONNECTION ----->|
|<-- CANCEL_ACKNOWLEDGED ----|
| |
[Cancelled] [Cancelled]
Denial & Invalid Messages
- DENY_CONNECTION - Responder rejects based on contract/origin validation failure
- INVALID - Protocol violation detected (malformed action, unknown type, etc.)
Lifecycle Events
Channels emit events at key points in the connection lifecycle:
- open - Connection successfully established (both sides)
- close - Graceful disconnection completed
- cancel - Connection attempt cancelled before completion
- deny - Connection request rejected by responder
- invalid - Protocol violation detected
Example:
channel.on((event, data, channelInfo) => {
switch (event) {
case 'open':
console.log('Connected to', data.origin)
break
case 'close':
console.log('Disconnected from', data.origin)
break
case 'deny':
console.error('Connection denied:', data.error)
break
case 'invalid':
console.error('Protocol violation:', data.reason)
break
}
})
Security Policies
Security is enforced before connections are established. Configure at broker level:
// Whitelist approach
const broker = createBroker({
name: 'secure-app',
contract,
settings: {
whitelist: ['https://trusted1.com', 'https://trusted2.com'],
},
})
// Blacklist approach
const broker = createBroker({
name: 'public-app',
contract,
settings: {
blacklist: ['https://blocked.com'],
},
})
// Custom policy function
broker.setSecurityPolicy((origin, contract) => {
// Custom validation logic
return origin.endsWith('.mycompany.com') && contract.emitted.length > 0
})
Security policies are applied during REQUEST_CONNECTION handling. Rejected connections receive a DENY_CONNECTION response.
Installation
npm install @hyperfrontend/nexus
Quick Start
import { createBroker } from '@hyperfrontend/nexus'
// Define communication contract
const contract = {
emitted: [{ type: 'PING' }],
accepted: [{ type: 'PONG' }],
}
// Create broker
const broker = createBroker({
name: 'main-app',
contract,
settings: { debug: true },
})
// Add channel to iframe
const iframe = document.querySelector('iframe')
const channel = broker.addChannel('child-app', iframe.contentWindow)
// Subscribe to messages
channel.onMessage((message) => {
console.log('Received:', message.type, message.data)
})
// Connect and send
channel.connect()
channel.send('PING', { timestamp: Date.now() })
Using the Default Broker
For quick prototyping, use the pre-configured singleton broker:
import { broker } from '@hyperfrontend/nexus'
const channel = broker.addChannel('my-channel', targetWindow)
channel.connect()
channel.send('MESSAGE', { hello: 'world' })
API Overview
Core Factory Functions
| Export | Description |
|---|---|
createBroker(config) |
Creates a message broker that manages multiple channels |
createChannel(config, deps) |
Creates a single channel (typically called via broker.addChannel) |
mergeContracts(...contracts) |
Combines multiple contracts into one, deduplicating action types |
Broker Handle
| Property/Method | Description |
|---|---|
id |
Unique broker identifier |
name |
Broker name |
contract |
Current communication contract |
channels |
List of active channels |
addChannel(name, target, settings?) |
Creates and registers a new channel |
getChannel(ref) |
Retrieves channel by name, id, or window reference |
removeChannel(ref) |
Removes a channel from the broker |
setSecurityPolicy(fn) |
Sets custom origin validation function |
extendContract(contract) |
Extends broker contract (if enabled) |
Channel Handle
| Property/Method | Description |
|---|---|
id |
Unique channel identifier |
name |
Channel name |
isActive() |
Returns connection status |
connect() |
Initiates connection handshake |
disconnect(notify?) |
Gracefully closes connection |
cancel(notify?) |
Cancels pending connection |
destroy(notify?) |
Forcefully terminates channel |
send(type, data) |
Sends a user message |
on(handler) |
Subscribes to lifecycle events |
onMessage(handler) |
Subscribes to user messages |
toJSON() |
Returns serializable channel state |
Filter Utilities
| Export | Description |
|---|---|
open, close, cancel, deny, invalid |
Event-specific filter creators |
byType(type, handler) |
Message type filter |
compose(...filters) |
Combines multiple message filters |
Types
| Type | Description |
|---|---|
IChannelContract |
Contract with accepted and emitted action arrays |
IActionDescription |
Action type definition with optional schema |
BrokerHandle |
Broker instance interface |
ChannelHandle |
Channel instance interface |
ChannelEvent |
Lifecycle event types: open, close, cancel, deny, invalid |
IMessage |
User message with type and optional data |
Compatibility
| Platform | Support |
|---|---|
| Browser | ✅ |
| Node.js | ✅ |
| Web Workers | ✅ |
| Deno, Bun, Cloudflare Workers | ✅ |
Output Formats
| Format | File | Tree-Shakeable |
|---|---|---|
| ESM | index.esm.js |
✅ |
| CJS | index.cjs.js |
❌ |
| IIFE | bundle/index.iife.min.js |
❌ |
| UMD | bundle/index.umd.min.js |
❌ |
Bundle size: 21 KB (minified, self-contained)
CDN Usage
<!-- unpkg -->
<script src="https://unpkg.com/@hyperfrontend/nexus"></script>
<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@hyperfrontend/nexus"></script>
<script>
const { createBroker, createChannel } = HyperfrontendNexus
</script>
Global variable: HyperfrontendNexus
Peer Dependencies
| Package | Type |
|---|---|
| @hyperfrontend/network-protocol | Optional |
Part of hyperfrontend
This library is part of the hyperfrontend monorepo.
- Optionally uses @hyperfrontend/network-protocol for encrypted messaging
License
MIT
API Reference
ƒFunctions
byType<T>(messageType: string): (handler: MessageHandler<T>) => MessageHandler<T>
Creates a filter that only passes messages of a specific type
Parameters
| Name | Type | Description |
|---|---|---|
| messageType | string |
Returns
(handler: MessageHandler<T>) => MessageHandler<T>A higher-order function that wraps a handlercancelFilter(handler: CancelEventHandler): EventHandler
Creates a filter that only passes CANCEL events to the handler
Parameters
| Name | Type | Description |
|---|---|---|
| handler | CancelEventHandler |
Returns
EventHandlerWrapped handler that filters for CANCEL eventscloseFilter(handler: CloseEventHandler): EventHandler
Creates a filter that only passes CLOSE events to the handler
Parameters
| Name | Type | Description |
|---|---|---|
| handler | CloseEventHandler |
Returns
EventHandlerWrapped handler that filters for CLOSE eventscompose<T>(...filters: MessageFilter<T>[]): MessageFilter<T>
Composes multiple message filters into a single filter. Filters are applied right-to-left during execution (rightmost filter executes first).
Parameters
| Name | Type | Description |
|---|---|---|
| ...filters | MessageFilter<T>[] |
Returns
MessageFilter<T>A single composed filtercreateBroker(config: { contract: IChannelContract; name: string; settings: Partial<indexedAccess> }): BrokerHandle
Creates a message broker instance
Parameters
| Name | Type | Description |
|---|---|---|
| config | { contract: IChannelContract; name: string; settings: Partial<indexedAccess> } |
Returns
BrokerHandleBroker handle with public APIcreateChannel(config: IChannelConfig, deps: ChannelDependencies): ChannelHandle
Creates a new message channel. Uses functional programming with closures for encapsulation. Returns a public handle with methods while keeping state private.
Parameters
| Name | Type | Description |
|---|---|---|
| config | IChannelConfig | |
| deps | ChannelDependencies |
Returns
ChannelHandleChannel handle with public APIExample
const channel = createChannel(
{ name: 'my-channel', target: childWindow },
{ actions, processManager, cleanup }
)
channel.connect()
channel.send('greet', { message: 'Hello!' })createEventFilter(eventType: ChannelEvent): (handler: EventHandler) => EventHandler
Creates an event filter that only calls the handler for a specific event type
Parameters
| Name | Type | Description |
|---|---|---|
| eventType | ChannelEvent |
Returns
(handler: EventHandler) => EventHandlerA higher-order function that wraps a handlercreateMessageFilter<T>(predicate: MessagePredicate<T>): (handler: MessageHandler<T>) => MessageHandler<T>
Creates a message filter that only calls the handler when predicate returns true
Parameters
| Name | Type | Description |
|---|---|---|
| predicate | MessagePredicate<T> |
Returns
(handler: MessageHandler<T>) => MessageHandler<T>A higher-order function that wraps a handlerdenyFilter(handler: DenyEventHandler): EventHandler
Creates a filter that only passes DENY events to the handler
Parameters
| Name | Type | Description |
|---|---|---|
| handler | DenyEventHandler |
Returns
EventHandlerWrapped handler that filters for DENY eventsinvalidFilter(handler: InvalidEventHandler): EventHandler
Creates a filter that only passes INVALID events to the handler
Parameters
| Name | Type | Description |
|---|---|---|
| handler | InvalidEventHandler |
Returns
EventHandlerWrapped handler that filters for INVALID eventsmergeContracts(...contracts: IChannelContract[]): IChannelContract
Merges multiple channel contracts into a single contract
Parameters
| Name | Type | Description |
|---|---|---|
| ...contracts | IChannelContract[] |
Returns
IChannelContractA single merged contract containing all accepted and provided actionsExample
const contract1 = { accepted: [{ type: 'a' }], provided: [{ type: 'b' }] }
const contract2 = { accepted: [{ type: 'c' }], provided: [{ type: 'd' }] }
const merged = mergeContracts(contract1, contract2)
// merged = { accepted: [{ type: 'a' }, { type: 'c' }], emitted: [{ type: 'b' }, { type: 'd' }] }openFilter(handler: OpenEventHandler): EventHandler
Creates a filter that only passes OPEN events to the handler
Parameters
| Name | Type | Description |
|---|---|---|
| handler | OpenEventHandler |
Returns
EventHandlerWrapped handler that filters for OPEN events◈Interfaces
BrokerConfig
Broker configuration passed to factory
Properties
readonly id:string— Unique broker identifierreadonly name:string— Broker namereadonly settings:BrokerSettings— Broker settingsBrokerHandle
Broker handle returned by factory
Properties
readonly acceptedActionTypes:unknownreadonly channels:unknownreadonly contract:IChannelContractreadonly debugMode:booleanreadonly id:stringreadonly name:stringreadonly settings:BrokerSettingsBrokerSettings
Broker settings configuration
Properties
readonly blacklist?:unknown— List of blocked originsreadonly contract:IChannelContract— Default contract for all channelsreadonly contractExtension?:boolean— Allow contract extensionreadonly debug?:boolean— Enable debug loggingreadonly security?:BrokerSecurityConfig— Security configuration for protocol negotiation and encryptionreadonly securityPolicy?:SecurityPolicy— Custom security validation functionreadonly whitelist?:unknown— List of allowed origins (takes precedence over blacklist)BrokerState
Internal broker state
Properties
readonly contract:IChannelContractreadonly id:stringreadonly name:stringreadonly settings:BrokerSettingsreadonly window:WindowCancelEventData
Data payload for CANCEL event
Properties
notify:boolean— Whether remote end was notifiedChannelHandle
Channel handle returned by createChannel factory. Provides methods for interacting with the channel.
Properties
readonly id:string— Channel unique identifier (for registry compatibility)readonly name:string— Channel name (for registry compatibility)readonly target:Window— Target window (for registry compatibility)ChannelJSON
Safe serializable representation of a channel for callbacks. Contains only data, no methods or internal references.
Properties
active:boolean— Whether channel is activeconnectTimestamp:number— When channel connectedcontract:IChannelContract— Channel contractid:string— Channel unique identifiername:string— Channel nameorigin:string— Origin of connected channelqueuedMessagesCount:number— Number of queued messagesCloseEventData
Data payload for CLOSE event
Properties
notify:boolean— Whether remote end was notifiedDenyEventData
Data payload for DENY event
Properties
reason:string— Reason for denialIActionDescription
Properties
description?:stringschema?:objecttype:stringIChannelConfig
Configuration for creating a new channel
Properties
name:string— Channel identifier/namesettings?:IChannelSettings— Channel behavior settingstarget:Window— Target window for postMessage communicationIChannelContract
Properties
accepted:IActionDescription[]emitted:IActionDescription[]IChannelSettings
Channel behavior settings
Properties
brokerManaged?:boolean— Whether the channel is managed by a broker (auto-activates on connect)contract?:IChannelContract— Expected channel contract (if not set, inherited from broker)debug?:boolean— Enable debug loggingorigin?:string— Expected origin ('*' for any, or specific URL)queueMessages?:boolean— Queue messages when channel is not yet activesecurity?:ChannelSecuritySettings— Security settings for protocol negotiation and encryptionIMessage
User message interface for application-level communication. Only 'type' is required; data and other properties are optional.
Properties
data?:unknown— Optional payload data (must be serializable via postMessage)timestamp?:number— Optional timestamp for when message was createdtype:string— Message type identifier (e.g., 'user-logged-in', 'data-updated')InvalidEventData
Data payload for INVALID event
Properties
action?:IAction— The invalid action that was received (if available)error:string— Error message describing what was invalidMessageEnvelope
Internal message envelope for routing and tracking. Wraps user messages with metadata for internal use.
Properties
channelId:string— ID of the channel handling this messagedirection:"inbound" | "outbound"— Direction of message flowmessage:IMessage— The user message being transmittedOpenEventData
Data payload for OPEN event
Properties
contract:IChannelContract— Negotiated channel contractorigin:string— Origin of the connected channel◆Types
ActionType
Extract action type union from ACTION_TYPES
type ActionType = indexedAccessCancelEventHandler
Type-safe event handler for CANCEL events
type CancelEventHandler = (event: "cancel", data: CancelEventData, channel: ChannelJSON) => voidChannelEvent
Channel lifecycle event types
type ChannelEvent = "open" | "close" | "cancel" | "deny" | "invalid" | "security-negotiated" | "security-ready" | "security-error"CloseEventHandler
Type-safe event handler for CLOSE events
type CloseEventHandler = (event: "close", data: CloseEventData, channel: ChannelJSON) => voidDenyEventHandler
Type-safe event handler for DENY events
type DenyEventHandler = (event: "deny", data: DenyEventData, channel: ChannelJSON) => voidEventData
Discriminated union of all event data types
type EventData = { data: OpenEventData; event: "open" } | { data: CloseEventData; event: "close" } | { data: CancelEventData; event: "cancel" } | { data: DenyEventData; event: "deny" } | { data: InvalidEventData; event: "invalid" } | { data: SecurityNegotiatedEventData; event: "security-negotiated" } | { data: SecurityReadyEventData; event: "security-ready" } | { data: SecurityErrorEventData; event: "security-error" }EventHandler
Generic event handler that receives all events
type EventHandler = (event: ChannelEvent, data: OpenEventData | CloseEventData | CancelEventData | DenyEventData | InvalidEventData, channel: ChannelJSON) => voidIAction
Union type representing all possible action structures
type IAction = IActionWithContract | IActionWithError | IActionWithData | IActionWithProcess | IActionBaseInvalidEventHandler
Type-safe event handler for INVALID events
type InvalidEventHandler = (event: "invalid", data: InvalidEventData, channel: ChannelJSON) => voidMessageFilter
Type for a filter function that transforms handlers
type MessageFilter = (handler: MessageHandler<T>) => MessageHandler<T>MessageHandler
Generic message handler that receives all messages
type MessageHandler = (message: T, channel: ChannelJSON) => voidMessagePredicate
Predicate function to test if a message should be handled
type MessagePredicate = (message: T) => booleanOpenEventHandler
Type-safe event handler for OPEN events
type OpenEventHandler = (event: "open", data: OpenEventData, channel: ChannelJSON) => voidSecurityPolicy
Security policy function type Validates whether a connection request should be allowed
type SecurityPolicy = (event: MessageEvent) => boolean●Variables
DEFAULT_CONTRACTIChannelContract...defaultBrokerBrokerHandle...