forgeframe
v0.1.0
Published
Modern cross-domain iframe component framework - a TypeScript alternative to zoid
Downloads
1,318
Maintainers
Readme
ForgeFrame
A TypeScript-first framework for embedding cross-domain iframes and popups with seamless communication. Pass data and callbacks across domains for payment forms, auth widgets, third-party integrations, and micro-frontends. Zero runtime dependencies with an ESM build.
Used By
Terminology
ForgeFrame involves two sides:
Consumer — The outer app that renders the iframe and passes props into it
Host — The inner app running inside the iframe that receives props via window.hostProps
Real-world example
Imagine a payment company (like Stripe) wants to let merchants embed a checkout form:
| | Consumer | Host |
|--|----------|------|
| Who builds it | Merchant (e.g., shop.com) | Payment company (e.g., stripe.com) |
| What they do | Embeds the checkout, receives onSuccess | Provides the checkout UI, calls onSuccess when paid |
| Their domain | shop.com | stripe.com |
┌─────────────────────────────────────────────────────────────────┐
│ Consumer (merchant's site - shop.com) │
│ │
│ Checkout({ amount: 99, onSuccess: (payment) => { │
│ // Payment complete! Fulfill the order │
│ }}).render('#checkout-container'); │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Host (payment form - stripe.com) │ │
│ │ │ │
│ │ const { amount, onSuccess, close } │ │
│ │ = window.hostProps; │ │
│ │ │ │
│ │ // User enters card, pays... │ │
│ │ onSuccess({ paymentId: 'xyz', amount }); │ │
│ │ close(); │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘Which side are you building?
| If you want to... | You're building the... | |-------------------|------------------------| | Embed someone else's component into your app | Consumer | | Build a component/widget for others to embed | Host | | Build both sides (e.g., your own micro-frontends) | Both |
Table of Contents
- Installation
- Start Here (Most Users)
- Quick Start
- Step-by-Step Guide
- Props System
- Host Window API (hostProps)
- Templates (Advanced)
- React Integration (Optional)
- Advanced Features
- Migrating from 0.0.15 to 0.1.0
- API Reference
- TypeScript
- Browser Support
Installation
npm install forgeframeStart Here (Most Users)
Use this path for typical integrations:
- Follow Quick Start to get a working component.
- Use Step-by-Step Guide to add typed props and callbacks.
- Use Props System and Host Window API (hostProps) as your primary references.
- Treat sections marked Advanced as optional unless you specifically need them.
Quick Start
Consumer
import ForgeFrame, { prop } from 'forgeframe';
const PaymentForm = ForgeFrame.create({
tag: 'payment-form',
url: 'https://checkout.stripe.com/payment',
dimensions: { width: 400, height: 300 },
props: {
amount: prop.number(),
onSuccess: prop.function<(txn: { transactionId: string }) => void>(),
},
});
const payment = PaymentForm({
amount: 99.99,
onSuccess: (txn) => console.log('Payment complete:', txn),
});
await payment.render('#payment-container');
Host
import { initHost, prop, type HostProps } from 'forgeframe';
interface PaymentProps {
amount: number;
onSuccess: (txn: { transactionId: string }) => void;
}
const paymentProps = {
amount: prop.number(),
onSuccess: prop.function<(txn: { transactionId: string }) => void>(),
};
const allowedConsumerDomains = ['https://shop.example.com'];
declare global {
interface Window {
hostProps: HostProps<PaymentProps>;
}
}
initHost(paymentProps, allowedConsumerDomains);
const { amount, onSuccess, close } = window.hostProps;
document.getElementById('total')!.textContent = `$${amount}`;
document.getElementById('pay-btn')!.onclick = async () => {
await onSuccess({ transactionId: 'TXN_123' });
await close();
};That's it! ForgeFrame handles all the cross-domain communication automatically.
Before reading window.hostProps directly, call initHost(propDefinitions, allowedConsumerDomains) as shown in Host Init with initHost.
For intentionally open widgets, omit allowedConsumerDomains; for payment, auth, or account flows, use an allowlist.
Step-by-Step Guide
1. Define a Component
Consumer
Components are defined using ForgeFrame.create(). This creates a reusable component factory.
import ForgeFrame, { prop } from 'forgeframe';
interface LoginProps {
email?: string;
onLogin: (user: { id: number; name: string }) => void;
onCancel?: () => void;
}
const LoginForm = ForgeFrame.create<LoginProps>({
tag: 'login-form',
url: 'https://auth.stripe.com/login',
dimensions: { width: 400, height: 350 },
props: {
email: prop.string().optional(),
onLogin: prop.function<(user: { id: number; name: string }) => void>(),
onCancel: prop.function().optional(),
},
});tag(required): Unique identifier for the componenturl(required): URL of the host page to embeddimensions: Width and height of the iframeprops: Schema definitions for props passed to the host
2. Create the Host Page
Host
The host page runs inside the iframe at the URL you specified. It receives props via window.hostProps.
import { initHost, prop, type HostProps } from 'forgeframe';
interface LoginProps {
email?: string;
onLogin: (user: { id: number; name: string }) => void;
onCancel?: () => void;
}
const loginProps = {
email: prop.string().optional(),
onLogin: prop.function<(user: { id: number; name: string }) => void>(),
onCancel: prop.function().optional(),
};
const allowedConsumerDomains = ['https://app.example.com'];
declare global {
interface Window {
hostProps: HostProps<LoginProps>;
}
}
initHost(loginProps, allowedConsumerDomains);
const { email, onLogin, onCancel, close } = window.hostProps;
if (email) document.getElementById('email')!.value = email;
document.getElementById('login-form')!.onsubmit = async (e) => {
e.preventDefault();
await onLogin({
id: 1,
name: 'John Doe',
});
await close();
};
document.getElementById('cancel')!.onclick = async () => {
await onCancel?.();
await close();
};HostProps<LoginProps>: Combines your props with built-in methods (close,resize, etc.)- Host init: Call
initHost(propDefinitions, allowedConsumerDomains)before the first directwindow.hostPropsread. UseallowedConsumerDomainsfor security-sensitive embeds; omit it only for intentionally open widgets. If your host defines a component withForgeFrame.create(...), that component path still initializes the host runtime automatically. window.hostProps: Contains all props passed from the consumer plus built-in methodsclose(): Built-in method to close the iframe/popup
3. Render the Component
Consumer
Back in your consumer app, create an instance with props and render it.
const login = LoginForm({
email: '[email protected]',
onLogin: (user) => console.log('User logged in:', user),
onCancel: () => console.log('Login cancelled'),
});
await login.render('#login-container');4. Handle Events
Consumer
Subscribe to lifecycle events for better control.
const instance = LoginForm({ /* props */ });
instance.event.on('rendered', () => console.log('Login form is ready'));
instance.event.on('close', () => console.log('Login form closed'));
instance.event.on('error', (err) => console.error('Error:', err));
instance.event.on('resize', (dimensions) => console.log('New size:', dimensions));
await instance.render('#container');Available Events:
| Event | Description |
|-------|-------------|
| render | Rendering started |
| rendered | Fully rendered and initialized |
| prerender | Prerender (loading) started |
| prerendered | Prerender complete |
| display | Component became visible |
| close | Component is closing |
| destroy | Component destroyed |
| error | An error occurred |
| props | Props were updated |
| resize | Component was resized |
| focus | Component received focus |
If all you need is embed + typed props + callbacks, you can stop here and use the API reference as needed.
Props System
ForgeFrame uses a fluent, Zod-like schema API for defining props. All schemas implement Standard Schema, enabling seamless integration with external validation libraries.
Defining Props
Props define what data can be passed to your component.
import ForgeFrame, { prop } from 'forgeframe';
const MyComponent = ForgeFrame.create({
tag: 'my-component',
url: 'https://widgets.stripe.com/component',
props: {
name: prop.string(),
count: prop.number(),
createdAt: prop.date(),
enabled: prop.boolean(),
config: prop.object(),
items: prop.array(),
position: prop.tuple(prop.number(), prop.number()),
onSubmit: prop.function<(data: FormData) => void>(),
nickname: prop.string().optional(),
theme: prop.string().default('light'),
email: prop.string().email(),
age: prop.number().min(0).max(120),
username: prop.string().min(3).max(20),
slug: prop.string().pattern(/^[a-z0-9-]+$/),
status: prop.enum(['pending', 'active', 'completed']),
tags: prop.array().of(prop.string()),
scores: prop.array().of(prop.number().min(0).max(100)),
metadata: prop.record(prop.string()),
themeId: prop.union(prop.string(), prop.number()),
user: prop.object().shape({
name: prop.string(),
email: prop.string().email(),
age: prop.number().optional(),
}),
},
});| Prop | Description |
|------|-------------|
| name, count, enabled, config, items | Basic types: string, number, boolean, object, array |
| createdAt | prop.date() validates real Date instances |
| position | prop.tuple(...) validates fixed positional arrays |
| onSubmit | Functions are automatically serialized for cross-domain calls |
| nickname | .optional() makes the prop accept undefined |
| theme | .default('light') provides a fallback value |
| email | .email() validates email format |
| age | .min(0).max(120) constrains the range |
| username | .min(3).max(20) constrains string length |
| slug | .pattern(/.../) validates against a regex |
| status | prop.enum([...]) restricts to specific values |
| tags | .of(prop.string()) validates each array item |
| scores | Array items can have their own validation chain |
| metadata | prop.record(...) validates dictionary values by key |
| themeId | prop.union(...) preserves type-safe multi-type props |
| user | .shape({...}) defines nested object structure |
Prop Schema Methods
All schemas support these base methods:
| Method | Description |
|--------|-------------|
| .optional() | Makes the prop optional (accepts undefined) |
| .nullable() | Accepts null values |
| .default(value) | Sets a default value (or factory function) |
Schema Types
| Type | Factory | Methods |
|------|---------|---------|
| String | prop.string() | .min(), .max(), .length(), .email(), .url(), .uuid(), .pattern(), .trim(), .nonempty() |
| Number | prop.number() | .min(), .max(), .int(), .positive(), .negative(), .nonnegative() |
| Date | prop.date() | .min(), .max() |
| Boolean | prop.boolean() | - |
| Function | prop.function<T>() | - |
| Array | prop.array() | .of(schema), .min(), .max(), .nonempty() |
| Tuple | prop.tuple(...schemas) | - |
| Object | prop.object() | .shape({...}), .strict() |
| Record | prop.record(schema) | - |
| Enum | prop.enum([...]) | - |
| Literal | prop.literal(value) | - |
| Union | prop.union(...schemas) | - |
| Any | prop.any() | - |
Using Standard Schema Libraries
ForgeFrame accepts any Standard Schema compliant library (Zod, Valibot, ArkType, etc.):
import ForgeFrame from 'forgeframe';
import { z } from 'zod';
import * as v from 'valibot';
const MyComponent = ForgeFrame.create({
tag: 'my-component',
url: 'https://widgets.stripe.com/component',
props: {
email: z.string().email(),
user: z.object({ name: z.string(), role: z.enum(['admin', 'user']) }),
count: v.pipe(v.number(), v.minValue(0)),
},
});Note: ForgeFrame runs schema validation synchronously. Schemas with async ~standard.validate are not supported.
Advanced Prop Definitions
Use the object form when a prop needs transport rules in addition to validation.
import ForgeFrame, { prop, PROP_SERIALIZATION } from 'forgeframe';
const SecureWidget = ForgeFrame.create({
tag: 'secure-widget',
url: 'https://widgets.example.com/secure',
props: {
profile: {
schema: prop.object(),
serialization: PROP_SERIALIZATION.DOTIFY,
},
secret: {
schema: prop.string(),
sameDomain: true,
},
auditId: {
schema: prop.string(),
queryParam: true,
},
internalState: {
schema: prop.any(),
sendToHost: false,
},
},
});| Option | Description |
|--------|-------------|
| sendToHost | Skip sending the prop to the host when set to false |
| sameDomain | Only deliver the prop after the loaded host is verified to be same-origin. It is not included in the initial bootstrap payload |
| trustedDomains | Only send the prop to matching host domains |
| serialization | Choose how object props are transferred: JSON (default), BASE64, or DOTIFY |
| queryParam / bodyParam | Include the prop in the host page's initial HTTP request |
- Use
sameDomainfor values that should never be exposed during cross-origin bootstrap. DOTIFYsafely preserves nested object keys that contain separators such as.,&, or=.
Passing Props via URL or POST Body (Advanced)
Use prop definition flags to include specific values in the host page's initial HTTP request:
const Checkout = ForgeFrame.create({
tag: 'checkout',
url: 'https://payments.example.com/checkout',
props: {
checkoutId: { schema: prop.string(), queryParam: true }, // ?checkoutId=...
requestNonce: { schema: prop.string(), bodyParam: true }, // POST body field
userId: { schema: prop.string(), bodyParam: 'user_id' }, // custom body field name
},
});queryParam: appends to the URL query string for initial load.bodyParam: sends values in a hidden formPOSTfor initial load (iframe and popup).bodyParamonly affects the initial navigation; laterupdateProps()uses postMessage.- Object values are JSON-stringified. Function and
undefinedvalues are skipped. - Never put credentials, bearer tokens, session identifiers, or sensitive personal data in
queryParam; URLs commonly leak through browser history, referrers, analytics, and server logs. sendToHost,sameDomain, andtrustedDomainsare enforced for query and body parameters as well as postMessage props. Because same-origin status is not verified until the host loads,sameDomainprops are never included in the initial request.- Most apps do not need this unless the host server requires initial URL/body parameters.
Updating Props
Props can be updated after rendering.
const instance = MyComponent({ name: 'Initial' });
await instance.render('#container');
await instance.updateProps({ name: 'Updated' });The host receives updates via onProps:
window.hostProps.onProps((newProps) => {
console.log('Props updated:', newProps);
});Built-in window.hostProps names are reserved. Consumer props with names such as
uid, tag, close, focus, resize, show, hide, onProps, onError,
getConsumer, getConsumerDomain, export, consumer, getPeerInstances, and
children are kept in hostProps.consumer.props, but they do not override the
top-level ForgeFrame methods and metadata exposed on window.hostProps.
Host Window API (hostProps)
In host windows, window.hostProps provides access to props and control methods.
When rendering in iframe mode, ForgeFrame applies a default sandbox of
allow-scripts allow-same-origin allow-forms allow-popups unless you explicitly
set attributes.sandbox on the consumer component. An explicit sandbox value is
used as-is.
TypeScript Setup
import { initHost, type HostProps } from 'forgeframe';
interface MyProps {
email: string;
onLogin: (user: { id: number }) => void;
}
declare global {
interface Window {
hostProps?: HostProps<MyProps>;
}
}
initHost();
const { email, onLogin, close, resize } = window.hostProps!;Host Init with initHost
initHost(propDefinitions?, allowedConsumerDomains?) is required when your host bundle reads window.hostProps directly.
It consumes the initial ForgeFrame window.name payload, clears that payload after a successful parse, validates props when definitions are provided, and attaches window.hostProps.
Supported host boot patterns:
- Call
initHost(propDefinitions, allowedConsumerDomains)during host startup, then readwindow.hostProps. - Call
initHost(propDefinitions)orinitHost()only for hosts that are intentionally embeddable by any consumer origin. - Define the host with
ForgeFrame.create(...)and let component creation initialize the host runtime.
Use initHost() when:
- You read
window.hostPropsdirectly. - Your host boot flow delays the first
window.hostPropsaccess (for example: lazy-loaded modules, async startup, or gated initialization). - You want deterministic init timing in tests or instrumentation.
- You need the host side to enforce which consumer domains may embed or message it.
Available Methods
const props = window.hostProps;
props.email;
props.onLogin(user);
props.uid;
props.tag;
await props.close();
await props.focus();
await props.resize({ width: 500, height: 400 });
await props.show();
await props.hide();
const { cancel } = props.onProps((newProps) => { /* handle updates */ });
await props.onError(new Error('Something failed'));
await props.export({ validate: () => true });
props.getConsumer();
props.getConsumerDomain();
props.consumer.props;
await props.consumer.export(data);
const peers = await props.getPeerInstances();
cancel();| Method | Description |
|--------|-------------|
| email, onLogin | Your custom props and callbacks |
| uid, tag | Built-in identifiers |
| close() | Close the component |
| focus() | Request focus for iframe/popup |
| resize() | Resize the component |
| show(), hide() | Toggle visibility |
| onProps() | Listen for prop updates (returns { cancel() }) |
| onError() | Report errors to consumer |
| export() | Export methods/data to consumer |
| getConsumer() | Get consumer window reference |
| getConsumerDomain() | Get consumer origin |
| consumer.props | Access consumer's props |
| consumer.export() | Send data to consumer from host context |
| getPeerInstances() | Get peer component instances from the same consumer |
| children | Nested component factories provided by consumer (if configured) |
Exporting Data to Consumer
Host components can export methods/data for the consumer to use.
Host
await window.hostProps.export({
validate: () => document.getElementById('form').checkValidity(),
getFormData: () => ({ email: document.getElementById('email').value }),
});
Consumer
const instance = MyComponent({ /* props */ });
await instance.render('#container');
const isValid = await instance.exports.validate();
const data = await instance.exports.getFormData();Templates (Advanced)
Use this section only when you need custom containers/loading UI beyond the default behavior.
Container Template
Customize how the component container is rendered. Perfect for modals.
const ModalComponent = ForgeFrame.create({
tag: 'modal',
url: 'https://widgets.stripe.com/modal',
dimensions: { width: 500, height: 400 },
containerTemplate: ({ doc, frame, prerenderFrame, close }) => {
const overlay = doc.createElement('div');
Object.assign(overlay.style, {
position: 'fixed',
inset: '0',
background: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: '1000',
});
overlay.onclick = (e) => { if (e.target === overlay) close(); };
const modal = doc.createElement('div');
Object.assign(modal.style, { background: 'white', borderRadius: '8px', overflow: 'hidden' });
const closeBtn = doc.createElement('button');
closeBtn.textContent = '×';
closeBtn.onclick = () => close();
modal.appendChild(closeBtn);
const body = doc.createElement('div');
if (prerenderFrame) body.appendChild(prerenderFrame);
if (frame) body.appendChild(frame);
modal.appendChild(body);
overlay.appendChild(modal);
return overlay;
},
});Prerender Template
Customize the loading state shown while the host loads.
const MyComponent = ForgeFrame.create({
tag: 'my-component',
url: 'https://widgets.stripe.com/component',
prerenderTemplate: ({ doc, dimensions }) => {
const loader = doc.createElement('div');
loader.innerHTML = `
<div style="
display: flex;
align-items: center;
justify-content: center;
width: ${dimensions.width}px;
height: ${dimensions.height}px;
background: #f5f5f5;
">
<span>Loading...</span>
</div>
`;
return loader.firstElementChild as HTMLElement;
},
});React Integration (Optional)
Basic Usage
import React, { useState } from 'react';
import ForgeFrame, { prop, createReactComponent } from 'forgeframe';
const LoginComponent = ForgeFrame.create({
tag: 'login',
url: 'https://auth.stripe.com/login',
dimensions: { width: 400, height: 350 },
props: {
email: prop.string().optional(),
onLogin: prop.function<(user: { id: number; name: string }) => void>(),
},
});
const Login = createReactComponent(LoginComponent, { React });
function App() {
const [user, setUser] = useState(null);
return (
<div>
<h1>My App</h1>
<Login
email="[email protected]"
onLogin={(loggedInUser) => setUser(loggedInUser)}
onRendered={() => console.log('Ready')}
onError={(err) => console.error(err)}
onClose={() => console.log('Closed')}
className="login-frame"
style={{ border: '1px solid #ccc' }}
/>
</div>
);
}React Props
The React component accepts all your component props plus:
| Prop | Type | Description |
|------|------|-------------|
| onRendered | () => void | Called when component is ready |
| onError | (err: Error) => void | Called on error |
| onClose | () => void | Called when closed |
| context | 'iframe' \| 'popup' | Render mode |
| className | string | Container CSS class |
| style | CSSProperties | Container inline styles |
Factory Pattern
For multiple components, use withReactComponent:
import { withReactComponent } from 'forgeframe';
const createComponent = withReactComponent(React);
const LoginReact = createComponent(LoginComponent);
const PaymentReact = createComponent(PaymentComponent);
const ProfileReact = createComponent(ProfileComponent);Advanced Features
Most integrations can skip this section initially and return only when a specific requirement appears.
Popup Windows
Render as a popup instead of iframe.
await instance.render('#container', 'popup');
const PopupComponent = ForgeFrame.create({
tag: 'popup-component',
url: 'https://widgets.stripe.com/popup',
defaultContext: 'popup',
});Domain Security
Restrict which domains can embed or communicate.
String domain patterns support * wildcards (for example, 'https://*.myapp.com'), and arrays can mix strings and RegExp.
domainis consumer-side trust: it restricts which host origins the consumer will message.allowedConsumerDomainsis host-side trust: it restricts which consumer origins may embed and initialize the host.- Use both for payment, auth, account, or other sensitive workflows.
const secureProps = {
accountId: prop.string(),
onComplete: prop.function<(result: { ok: true }) => void>(),
};
const SecureComponent = ForgeFrame.create({
tag: 'secure',
url: 'https://secure.stripe.com/widget',
props: secureProps,
domain: 'https://secure.stripe.com',
allowedConsumerDomains: [
'https://myapp.com',
'https://*.myapp.com',
/^https:\/\/.*\.trusted\.com$/,
],
});On the host page, pass the same host prop definitions and the consumer allowlist into initHost:
initHost(secureProps, [
'https://myapp.com',
'https://*.myapp.com',
/^https:\/\/.*\.trusted\.com$/,
]);When allowedConsumerDomains is configured, the host fails closed if the browser does not provide a usable consumer origin. A no-referrer navigation can therefore prevent initialization; do not weaken the allowlist to work around that browser privacy setting.
Public API removals or behavioral changes are documented in GitHub release notes. Pre-1.0 releases may include breaking changes, so pin an exact version for production integrations and review release notes before upgrading.
Eligibility Checks
Conditionally allow rendering.
const FeatureComponent = ForgeFrame.create({
tag: 'feature',
url: 'https://widgets.stripe.com/feature',
eligible: ({ props }) => {
if (!props.userId) return { eligible: false, reason: 'User must be logged in' };
return { eligible: true };
},
});
if (instance.isEligible()) {
await instance.render('#container');
}Nested Components
Define nested components that can be rendered from within the host.
Consumer
const ContainerComponent = ForgeFrame.create({
tag: 'container',
url: 'https://widgets.stripe.com/container',
children: () => ({
CardField: CardFieldComponent,
CVVField: CVVFieldComponent,
}),
});
Host
// Import or define the same child factories before initializing hostProps.
// Their schemas remain executable code and are never JSON-serialized.
import { CardFieldComponent, CVVFieldComponent } from './child-components';
initHost(containerPropDefinitions, allowedConsumerDomains);
const { children } = window.hostProps;
children.CardField({ onValid: () => {} }).render('#card-container');Each child tag must be registered in the host bundle before hostProps is initialized. Current hosts resolve executable validators, defaults, decorators, and regular expressions from that local registry instead of reconstructing them from JSON. Protocol-v1 metadata is still emitted for older hosts during staged upgrades, so nested child URLs must remain static strings.
Migrating from 0.0.15 to 0.1.0
Version 0.1.0 keeps ForgeFrame's existing iframe/popup model, but makes several previously permissive or ambiguous behaviors explicit. Ordinary integrations using an HTTP(S) URL, a matching domain policy, standard props, and await instance.render(container) should upgrade without code changes.
Upgrade checklist
Before upgrading, check the integration for these less-common patterns:
autoResizeconfiguration or anAutoResizeOptionstype import.render()orrenderTo()calls without a container, orupdateProps()calls made before rendering finishes.- Computed component URLs that are not HTTP(S) or do not match
domain. name,src, orsrcdocinside custom iframeattributes.- Sensitive or server-required
queryParam/bodyParamprops whose delivery depends onsendToHost,sameDomain, ortrustedDomains. - Nested components that are not also imported or defined by the host bundle.
- Consumer diagnostics that depend on receiving the host's remote
Error.stack.
If none of these apply, the upgrade should be drop-in for the normal create, render, callback, prop-update, export, resize, and close flows.
| Area | Change in 0.1.0 | Migration action |
|------|-----------------|------------------|
| Component URLs | Only HTTP(S) URLs are accepted, and domain is enforced against the resolved host origin. | Ensure every static or computed URL uses HTTP(S) and matches domain. |
| Rendering | render() and renderTo() require a container. Concurrent render() calls share one operation, and updateProps() rejects while rendering is in progress. | Always pass a container and await render() before updating props. |
| Initial query/body props | sendToHost, sameDomain, and trustedDomains now apply to queryParam and bodyParam; sameDomain values are withheld from the initial request. | Check any server bootstrap fields and never place secrets in the URL. |
| Iframe attributes | ForgeFrame owns name, src, and srcdoc. | Remove those keys from custom attributes; configure navigation with url. |
| Auto-resize | The unused autoResize option and exported AutoResizeOptions type were removed, along with the internal content-dimension helper. | Remove autoResize and AutoResizeOptions imports; use instance.resize() or host-driven sizing. |
| Nested components | Hosts resolve child factories from their local component registry so executable schemas are preserved. | Import or define child components in the host bundle before initHost(). Keep child URLs static for protocol-v1 compatibility. |
| Remote errors | Error messages still cross the bridge, but remote stack traces do not. | Do not depend on a remote Error.stack; collect host-side diagnostics locally. |
The wire protocol remains version 1 and retains legacy nested-component metadata for staged upgrades. Upgrade both sides together where practical. If upgrading separately, ensure the host bundle registers every nested child before switching the consumer, and do not rely on newly callable host exports until both sides are current.
API Reference
ForgeFrame Object
import ForgeFrame, { prop } from 'forgeframe';
ForgeFrame.create(options) // Create a component
ForgeFrame.destroy(instance) // Destroy an instance
ForgeFrame.destroyByTag(tag) // Destroy all instances of a tag
ForgeFrame.destroyAll() // Destroy all instances
ForgeFrame.isHost() // Check if in host context
ForgeFrame.isEmbedded() // Alias for isHost() - more intuitive naming
ForgeFrame.initHost(props?, allowedConsumerDomains?) // Required before direct window.hostProps access
ForgeFrame.getHostProps() // Get hostProps in host context
ForgeFrame.isStandardSchema(val) // Check if value is a Standard Schema
ForgeFrame.prop // Prop schema builders (also exported as `prop`)
ForgeFrame.PROP_SERIALIZATION // Prop serialization constants
ForgeFrame.CONTEXT // Context constants (IFRAME, POPUP)
ForgeFrame.EVENT // Event name constants
ForgeFrame.PopupOpenError // Popup blocker/open failures
ForgeFrame.VERSION // Library versionComponent Options
interface ComponentOptions<P> {
tag: string;
url: string | ((props: P) => string);
dimensions?: { width?: number | string; height?: number | string } | ((props: P) => { width?: number | string; height?: number | string });
props?: PropsDefinition<P>;
defaultContext?: 'iframe' | 'popup';
containerTemplate?: (ctx: TemplateContext<P>) => HTMLElement | null;
prerenderTemplate?: (ctx: TemplateContext<P>) => HTMLElement | null;
domain?: DomainMatcher; // Consumer-side trusted host origins
allowedConsumerDomains?: DomainMatcher; // Host-side allowed consumer origins
eligible?: (opts: { props: P }) => { eligible: boolean; reason?: string };
validate?: (opts: { props: P }) => void;
attributes?: IframeAttributes | ((props: P) => IframeAttributes);
style?: IframeStyles | ((props: P) => IframeStyles);
timeout?: number;
children?: (opts: { props: P }) => Record<string, ForgeFrameComponent>;
}Instance Methods
const instance = MyComponent(props);
await instance.render(container, context?) // Render into a container (container is required)
await instance.renderTo(window, container) // Supports only current window; throws for other windows
await instance.close() // Close and destroy
await instance.focus() // Focus
await instance.resize({ width, height }) // Resize
await instance.show() // Show
await instance.hide() // Hide
await instance.updateProps(newProps) // Update props (normalized + validated)
instance.clone() // Clone with same props
instance.isEligible() // Check eligibility
instance.uid // Unique ID
instance.event // Event emitter
instance.state // Mutable state
instance.exports // Host exportsTypeScript
ForgeFrame is written in TypeScript and exports all types.
import ForgeFrame, {
prop,
PropSchema,
StringSchema,
NumberSchema,
BooleanSchema,
FunctionSchema,
ArraySchema,
ObjectSchema,
createReactComponent,
withReactComponent,
type ComponentOptions,
type ForgeFrameComponent,
type ForgeFrameComponentInstance,
type HostProps,
type StandardSchemaV1,
type TemplateContext,
type Dimensions,
type EventHandler,
type GetPeerInstancesOptions,
} from 'forgeframe';Typing Host hostProps
import { initHost, prop, type HostProps } from 'forgeframe';
interface MyProps {
name: string;
onSubmit: (data: FormData) => void;
}
const hostProps = {
name: prop.string(),
onSubmit: prop.function<(data: FormData) => void>(),
};
const allowedConsumerDomains = ['https://app.example.com'];
declare global {
interface Window {
hostProps?: HostProps<MyProps>;
}
}
initHost(hostProps, allowedConsumerDomains);
window.hostProps!.name;
window.hostProps!.onSubmit;
window.hostProps!.close;
window.hostProps!.resize;Browser Support
ForgeFrame ships ES2022 output. Use modern evergreen browsers or transpile the package for older targets in your consumer build pipeline.
Note: Internet Explorer is not supported. If you require IE-era compatibility, use Zoid.
License
MIT
