@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:

  1. REQUEST_CONNECTION - Initiator sends contract and process ID
  2. ACCEPT_CONNECTION - Responder validates and replies with own contract
  3. 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.

📖 Full documentation

License

MIT

API Reference

Filter:

ƒFunctions

function

byType<T>(messageType: string): (handler: MessageHandler<T>) => MessageHandler<T>

Creates a filter that only passes messages of a specific type

Parameters

NameTypeDescription
messageTypestring

Returns

(handler: MessageHandler<T>) => MessageHandler<T>A higher-order function that wraps a handler
function

cancelFilter(handler: CancelEventHandler): EventHandler

Creates a filter that only passes CANCEL events to the handler

Parameters

NameTypeDescription
handlerCancelEventHandler

Returns

EventHandlerWrapped handler that filters for CANCEL events
function

closeFilter(handler: CloseEventHandler): EventHandler

Creates a filter that only passes CLOSE events to the handler

Parameters

NameTypeDescription
handlerCloseEventHandler

Returns

EventHandlerWrapped handler that filters for CLOSE events
function

compose<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

NameTypeDescription
...filtersMessageFilter<T>[]

Returns

MessageFilter<T>A single composed filter
function

createBroker(config: { contract: IChannelContract; name: string; settings: Partial<indexedAccess> }): BrokerHandle

Creates a message broker instance

Parameters

NameTypeDescription
config{ contract: IChannelContract; name: string; settings: Partial<indexedAccess> }

Returns

BrokerHandleBroker handle with public API
function

createChannel(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

NameTypeDescription
configIChannelConfig
depsChannelDependencies

Returns

ChannelHandleChannel handle with public API

Example

const channel = createChannel(
  { name: 'my-channel', target: childWindow },
  { actions, processManager, cleanup }
)
channel.connect()
channel.send('greet', { message: 'Hello!' })
function

createEventFilter(eventType: ChannelEvent): (handler: EventHandler) => EventHandler

Creates an event filter that only calls the handler for a specific event type

Parameters

NameTypeDescription
eventTypeChannelEvent

Returns

(handler: EventHandler) => EventHandlerA higher-order function that wraps a handler
function

createMessageFilter<T>(predicate: MessagePredicate<T>): (handler: MessageHandler<T>) => MessageHandler<T>

Creates a message filter that only calls the handler when predicate returns true

Parameters

NameTypeDescription
predicateMessagePredicate<T>

Returns

(handler: MessageHandler<T>) => MessageHandler<T>A higher-order function that wraps a handler
function

denyFilter(handler: DenyEventHandler): EventHandler

Creates a filter that only passes DENY events to the handler

Parameters

NameTypeDescription
handlerDenyEventHandler

Returns

EventHandlerWrapped handler that filters for DENY events
function

invalidFilter(handler: InvalidEventHandler): EventHandler

Creates a filter that only passes INVALID events to the handler

Parameters

NameTypeDescription
handlerInvalidEventHandler

Returns

EventHandlerWrapped handler that filters for INVALID events
function

mergeContracts(...contracts: IChannelContract[]): IChannelContract

Merges multiple channel contracts into a single contract

Parameters

NameTypeDescription
...contractsIChannelContract[]

Returns

IChannelContractA single merged contract containing all accepted and provided actions

Example

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' }] }
function

openFilter(handler: OpenEventHandler): EventHandler

Creates a filter that only passes OPEN events to the handler

Parameters

NameTypeDescription
handlerOpenEventHandler

Returns

EventHandlerWrapped handler that filters for OPEN events

Interfaces

interface

BrokerConfig

Broker configuration passed to factory

Properties

readonly id:stringUnique broker identifier
readonly name:stringBroker name
readonly settings:BrokerSettingsBroker settings
interface

BrokerHandle

Broker handle returned by factory

Properties

readonly acceptedActionTypes:unknown
readonly channels:unknown
readonly contract:IChannelContract
readonly debugMode:boolean
readonly id:string
readonly name:string
readonly settings:BrokerSettings
interface

BrokerSettings

Broker settings configuration

Properties

readonly blacklist?:unknownList of blocked origins
readonly contract:IChannelContractDefault contract for all channels
readonly contractExtension?:booleanAllow contract extension
readonly debug?:booleanEnable debug logging
readonly security?:BrokerSecurityConfigSecurity configuration for protocol negotiation and encryption
readonly securityPolicy?:SecurityPolicyCustom security validation function
readonly whitelist?:unknownList of allowed origins (takes precedence over blacklist)
interface

BrokerState

Internal broker state

Properties

readonly contract:IChannelContract
readonly id:string
readonly name:string
readonly settings:BrokerSettings
readonly window:Window
interface

CancelEventData

Data payload for CANCEL event

Properties

notify:booleanWhether remote end was notified
interface

ChannelHandle

Channel handle returned by createChannel factory. Provides methods for interacting with the channel.

Properties

readonly id:stringChannel unique identifier (for registry compatibility)
readonly name:stringChannel name (for registry compatibility)
readonly target:WindowTarget window (for registry compatibility)
interface

ChannelJSON

Safe serializable representation of a channel for callbacks. Contains only data, no methods or internal references.

Properties

active:booleanWhether channel is active
connectTimestamp:numberWhen channel connected
contract:IChannelContractChannel contract
id:stringChannel unique identifier
name:stringChannel name
origin:stringOrigin of connected channel
queuedMessagesCount:numberNumber of queued messages
interface

CloseEventData

Data payload for CLOSE event

Properties

notify:booleanWhether remote end was notified
interface

DenyEventData

Data payload for DENY event

Properties

reason:stringReason for denial
interface

IActionDescription

Properties

description?:string
schema?:object
type:string
interface

IChannelConfig

Configuration for creating a new channel

Properties

name:stringChannel identifier/name
settings?:IChannelSettingsChannel behavior settings
target:WindowTarget window for postMessage communication
interface

IChannelContract

Properties

accepted:IActionDescription[]
emitted:IActionDescription[]
interface

IChannelSettings

Channel behavior settings

Properties

brokerManaged?:booleanWhether the channel is managed by a broker (auto-activates on connect)
contract?:IChannelContractExpected channel contract (if not set, inherited from broker)
debug?:booleanEnable debug logging
origin?:stringExpected origin ('*' for any, or specific URL)
queueMessages?:booleanQueue messages when channel is not yet active
security?:ChannelSecuritySettingsSecurity settings for protocol negotiation and encryption
interface

IMessage

User message interface for application-level communication. Only 'type' is required; data and other properties are optional.

Properties

data?:unknownOptional payload data (must be serializable via postMessage)
timestamp?:numberOptional timestamp for when message was created
type:stringMessage type identifier (e.g., 'user-logged-in', 'data-updated')
interface

InvalidEventData

Data payload for INVALID event

Properties

action?:IActionThe invalid action that was received (if available)
error:stringError message describing what was invalid
interface

MessageEnvelope

Internal message envelope for routing and tracking. Wraps user messages with metadata for internal use.

Properties

channelId:stringID of the channel handling this message
direction:"inbound" | "outbound"Direction of message flow
message:IMessageThe user message being transmitted
interface

OpenEventData

Data payload for OPEN event

Properties

contract:IChannelContractNegotiated channel contract
origin:stringOrigin of the connected channel

Types

type

ActionType

Extract action type union from ACTION_TYPES

type ActionType = indexedAccess
type

CancelEventHandler

Type-safe event handler for CANCEL events

type CancelEventHandler = (event: "cancel", data: CancelEventData, channel: ChannelJSON) => void
type

ChannelEvent

Channel lifecycle event types

type ChannelEvent = "open" | "close" | "cancel" | "deny" | "invalid" | "security-negotiated" | "security-ready" | "security-error"
type

CloseEventHandler

Type-safe event handler for CLOSE events

type CloseEventHandler = (event: "close", data: CloseEventData, channel: ChannelJSON) => void
type

DenyEventHandler

Type-safe event handler for DENY events

type DenyEventHandler = (event: "deny", data: DenyEventData, channel: ChannelJSON) => void
type

EventData

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" }
type

EventHandler

Generic event handler that receives all events

type EventHandler = (event: ChannelEvent, data: OpenEventData | CloseEventData | CancelEventData | DenyEventData | InvalidEventData, channel: ChannelJSON) => void
type

IAction

Union type representing all possible action structures

type IAction = IActionWithContract | IActionWithError | IActionWithData | IActionWithProcess | IActionBase
type

InvalidEventHandler

Type-safe event handler for INVALID events

type InvalidEventHandler = (event: "invalid", data: InvalidEventData, channel: ChannelJSON) => void
type

MessageFilter

Type for a filter function that transforms handlers

type MessageFilter = (handler: MessageHandler<T>) => MessageHandler<T>
type

MessageHandler

Generic message handler that receives all messages

type MessageHandler = (message: T, channel: ChannelJSON) => void
type

MessagePredicate

Predicate function to test if a message should be handled

type MessagePredicate = (message: T) => boolean
type

OpenEventHandler

Type-safe event handler for OPEN events

type OpenEventHandler = (event: "open", data: OpenEventData, channel: ChannelJSON) => void
type

SecurityPolicy

Security policy function type Validates whether a connection request should be allowed

type SecurityPolicy = (event: MessageEvent) => boolean

Variables

constDEFAULT_CONTRACT
Type: IChannelContract
Value: ...
constdefaultBroker
Type: BrokerHandle
Value: ...

Related