@hyperfrontend/features/hostee

Hostee

Hostee-side SDK for feature apps: feature initialization, contract declaration, and lifecycle.

import { createFeature } from '@hyperfrontend/features/hostee'

const feature = createFeature({
  name: 'clock',
  contract: {
    emitted: [{ type: 'tick' }],
    accepted: [{ type: 'set-timezone' }],
  },
})

await feature.ready()
feature.on('set-timezone', ({ tz }) => render(tz))
setInterval(() => feature.send('tick', Date.now()), 1000)

API

ExportPurpose
createFeatureConnect a feature app to its host; returns send/on/ready/close.
FeatureHandleType of the handle returned by createFeature; carries hosted and displayMode too.

ready() resolves once the wire handshake with the host completes, and rejects if the host does not open the connection within readyTimeoutMs (default 10 s; an error with reason: 'ready-timeout' is also emitted). Sends issued before the handshake completes queue and flush on open. send emits a contract action to the host; on subscribes to host messages and the open/closing/close/error/presentation/resize lifecycle events (closing is the flush window before a polite close completes). setDirty declares unsaved work to the host. close disconnects from the host politely.

Presentation

The host owns how the feature is surfaced; the SDK receives that decision and prepares the document, so the app author only has to make the layout responsive.

Right after open, the host announces the display mode along with the frame's initial dimensions: read the mode from feature.displayMode or the presentation event ({ mode }); the dimensions arrive as the first resize event, no extra round trip. In the iframe modes the host then reports every change to the frame's usable space as exact pixels; the SDK sizes html/body to match and re-emits each as resize ({ width, height }). In popup/standalone the browser window is the viewport and resize comes from the feature's own window. Responding to the reported width and height (media/container queries, reflow, breakpoints) is the app author's job.

In dialog mode the frame spans the host's viewport, transparent. The SDK places your root element (the body's first element child, or pass root to createFeature) at the agreed position (centered by default) and sizes it to the agreed inner-box dimensions; everything around it is the backdrop. You style the box itself (background, border, shadow) since an unstyled box is invisible against the transparent backdrop.

The SDK detects pointer interaction on the bare backdrop and Escape presses and reports them to the host as dismiss signals, pure reports: the SDK tears nothing down itself, and if the host's policy is to close, the ordinary polite close (closing flush window included) follows. Because the pane covers the whole viewport, dragging or resizing the box is ordinary in-document CSS/pointer work if you want it: nothing crosses the boundary.

The body reset (resetBody, on by default) keeps html/body margin-free and transparent, with a color-scheme pin matched to the host frame: overriding the background or color-scheme with an opaque/dark scheme breaks the transparency that embedded blending and dialog backdrops depend on.

It applies on a direct top-level visit too: the reset does not need a host. A feature distinguishes that case with feature.hosted, true when a parent or opener window exists, false when the document is top-level, known synchronously from the moment createFeature returns; apps never sniff window.parent themselves. hosted: true promises a host window, not a host that speaks: connection state stays with ready() and the lifecycle events, and unhosted, displayMode stays null and ready() stays pending.

The reset stylesheet is injected when createFeature runs, landing after the page's own stylesheet and winning at equal specificity. So paint the feature's background on its root layout element, never on body; a body { background: … } rule silently loses to the reset. Pass resetBody: false to opt out entirely and own the reset yourself.

API Reference§

ƒ Functions

§function

createFeature(options: FeatureOptions): FeatureHandle

Initializes a feature app on the hostee side and waits for the host connection.
Creates a nexus broker for the feature, resolves the host window, and returns a handle for messaging and lifecycle whose hosted flag reports synchronously whether a host window exists at all. When protocol selects the v3 or v4 envelope, the feature negotiates it with the host during the connection handshake and messages travel sealed once the session is keyed. A version announces the contract cut this feature holds (overriding any contract.version), so the handshake can deny hosts built against an incompatible cut.
Parameters
NameTypeDescription
§options
FeatureOptionsFeature name, contract, and optional version, root-element, and security settings.
Returns
FeatureHandle
A handle exposing send, on, ready, and close.
Example

Initializing a clock feature

const feature = createFeature({ name: 'clock', contract, version: '1.2.0', protocol: 'v4', sharedKey: 'a-key-of-sixteen-or-more' })
feature.ready().then(() => feature.send('timeUpdated', { time: Date.now() }))
feature.on('setTimezone', (data) => console.log(data))

◈ Interfaces

§interface

FeatureHandle

Public handle returned by the hostee-side feature factory.
Properties
§readonly displayMode:DisplayModeThe display mode the host announced for this mount, or null before the announcement arrives (it is the first message after open) and after the channel closes.
§readonly hosted:booleanWhether a host window exists for this feature at all: true when the document has a parent window (an embedding iframe) or an opener, false on a direct top-level visit. Known synchronously by the time createFeature returns and never changes; it does not promise the host will speak, since connection state stays with ready() and the lifecycle events. Where displayMode answers how the host mounted the feature, hosted answers whether a host exists: unhosted, displayMode stays null and ready() stays pending.
§interface

ActionDescription

Description of a single action a feature can emit or accept.
Structurally compatible with nexus's channel contract action shape so the same contract can drive both messaging and the shell type generator.
Properties
§description?:stringHuman-readable explanation of the action, surfaced in tooling.
§required?:booleanMarks an accepted action as essential for correct operation: the connection is denied at handshake time unless the counterpart emits this type. Only meaningful on accepted entries. Unflagged actions never gate the connection, so additive contract evolution stays non-breaking.
§respondsWith?:stringWhen this action is used as a request, the type of the action in the other direction that answers it.
§schema?:objectOptional JSON-schema-like shape describing the action payload.
§type:stringWire type string that identifies the action.
§interface

FeatureContract

The set of actions a feature emits to, and accepts from, its counterpart.
This is the same shape the on-disk *.contract.json files and the shell generator consume.
Properties
§accepted:ActionDescription[]Actions this side handles from the other side.
§emitted:ActionDescription[]Actions this side sends to the other side.
§version?:stringOptional semver version announcing the contract cut this side holds. Builds canonicalize and bake it into the generated shell; the two sides compare their announcements during the connection handshake and incompatible cuts are denied before the channel opens. Absent on either side, the check passes, so unversioned peers keep connecting.
§interface

FeatureOptions

Options accepted by the hostee-side feature factory.
Properties
§contract:FeatureContractContract describing the actions the feature emits and accepts.
§name:stringStable identifier for the feature, used to name its messaging channel.
§protocol?:SecurityProtocolSecurity envelope to negotiate with the host; defaults to none.
§readyTimeoutMs?:numberMilliseconds the feature waits for the host to complete the connection handshake before ready() rejects and an error with reason: 'ready-timeout' is emitted; defaults to 10000.
§resetBody?:booleanWhether to neutralize the feature page's html/body; defaults to true. Zeroes margin and padding, forces background: transparent, and pins color-scheme: normal; on a standalone visit too, and injected late enough to outrank the page's own body rules, so paint the feature's background on its root layout element instead.
§root?:string | HTMLElementThe feature's root layout element (or a CSS selector for it); defaults to the body's first element child. In dialog mode the hostee SDK centers this element inside the full-viewport pane and applies the agreed inner dialog box dimensions to it; the area around it is the backdrop.
§sharedKey?:stringPre-shared key the v4 protocol binds the session to; at least 16 characters, never given with another protocol.
§version?:stringSemver version of the contract cut this feature holds; takes precedence over contract.version.
§interface

RequestOptions

Per-request settings accepted by request.
Properties
§timeoutMs?:numberMilliseconds to wait for the response before rejecting; defaults to 30000.
§interface

ViewportPayload

Payload of the reserved viewport control message: the exact pixel dimensions of the space the feature's frame occupies, reported by the host whenever the measured space changes (iframe modes only).
Properties
§height:numberUsable height in pixels.
§width:numberUsable width in pixels.

◆ Types

§type

EventHandler

Handler invoked when a subscribed event fires.
type EventHandler = (data: unknown) => void
§type

RequestHandler

Answers one request type; may return the response value directly or a promise of it.
type RequestHandler = (data: unknown) => unknown
§type

SecurityProtocol

Union of the supported security envelope selectors.
none is the local default (opt-in security); production builds must pick v3 (ephemeral session keys) or v4 (session keys bound to a pre-shared key).
type SecurityProtocol = "none" | "v3" | "v4"

● Variables

§type

DisplayMode

Supported ways a host can surface an embedded feature.
The host selects the mode; a feature declares which modes it supports in its feature.config.* display.modes, and the generated shell composes exactly those.