@hyperfrontend/features/hosteeHostee
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
| Export | Purpose |
|---|---|
createFeature | Connect a feature app to its host; returns send/on/ready/close. |
FeatureHandle | Type 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
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
| Name | Type | Description |
|---|---|---|
§options | FeatureOptions | Feature name, contract, and optional version, root-element, and security settings. |
Returns
FeatureHandlesend, 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
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.Structurally compatible with nexus's channel contract action shape so the same contract can drive both messaging and the shell type generator.
Properties
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.This is the same shape the on-disk
*.contract.json files and the shell generator consume.Properties
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.Properties
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.version?:stringSemver version of the contract cut this feature holds; takes precedence over contract.version.request.Properties
◆ Types
type EventHandler = ( data: unknown) => voidtype RequestHandler = ( data: unknown) => unknownnone 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
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.