@funda-ai/iframe-sdk
v0.1.1
Published
postMessage protocol + client/host helpers for Funda iframe apps.
Maintainers
Readme
@funda-ai/iframe-sdk
postMessage protocol + client/host helpers for Funda iframe apps.
Install
npm install @funda-ai/iframe-sdk
# or
pnpm add @funda-ai/iframe-sdk
# or
yarn add @funda-ai/iframe-sdkreact and react-dom are optional peer dependencies — only required if you
import from @funda-ai/iframe-sdk/host. The /client subpath is React-free.
Three ways to consume
1. From npm (iframe app built with a bundler)
import { createFundaClient } from "@funda-ai/iframe-sdk/client";
const client = createFundaClient({ appId: "my-app", hostOrigin: "*" });
const { authToken, theme, locale } = await client.ready();2. Inside the Funda monorepo (workspace import)
// Host side — mount a third-party iframe app in a React component
import { FundaIframeApp } from "@funda-ai/iframe-sdk/host";
<FundaIframeApp
manifest={{
appId: "my-app",
src: "https://cdn.example.com/my-app/index.html",
allowedOrigins: ["https://cdn.example.com"],
permissions: ["funda.quote"],
authToken,
resolve: async (resource, params) => {
// forward to tRPC / internal API
},
}}
theme="light"
locale="en"
/>;// Iframe side — for React/Vite apps inside the Funda monorepo
import { createFundaClient } from "@funda-ai/iframe-sdk/client";3. Standalone iframe apps (<script> tag)
For hand-written HTML dashboards in funda-data-flow/*, the
app-generator skill's output, or any static iframe app that can't use a
bundler, load the IIFE build directly:
Load the IIFE from a public CDN — we publish to npm, so unpkg and jsdelivr pick it up automatically:
<!-- Pin a version for prod. Omit @0.1.0 or use @latest at your risk. -->
<script src="https://unpkg.com/@funda-ai/[email protected]/dist/iframe-sdk-v1.umd.iife.js"></script>
<script>
const client = FundaSDK.createFundaClient({
appId: "my-app",
// Use a specific origin when known (e.g. "https://app.funda.ai"). Pass "*"
// for dashboards served from static storage that must work across beta +
// prod — the SDK locks to the first v1 reply's origin.
hostOrigin: "*",
});
// Wait for the host to deliver the session context
const { authToken, theme, locale } = await client.ready();
// Call host-mediated resources (the host enforces the permission allowlist)
const quote = await client.requestData("funda.quote", { ticker: "NVDA" });
// React to theme / locale changes from the parent
client.onThemeChange((t) => document.documentElement.dataset.theme = t);
client.onLocaleChange((l) => console.log("locale changed", l));
// Report content height so the host can resize the iframe (opt-in on host side)
client.notifyResize(document.documentElement.scrollHeight);
</script>jsdelivr works identically:
<script src="https://cdn.jsdelivr.net/npm/@funda-ai/[email protected]/dist/iframe-sdk-v1.umd.iife.js"></script>The IIFE is zero-deps, ~1.7 KB minified / ~0.9 KB gzipped. Message validation on the client relies on the origin check (the host is trusted once verified); zod schema validation runs on the host side where cross-origin messages arrive.
Build
pnpm --filter @funda-ai/iframe-sdk buildRuns tsdown to produce:
dist/{index,client,host,protocol}.{js,cjs,d.ts,d.cts}— ESM + CJS + types for every public entry.dist/iframe-sdk-v1.umd.iife.js— standalone IIFE exposingwindow.FundaSDK.
Bump the -v1 suffix in the IIFE entry and the matching PROTOCOL_VERSION
constant in src/version.ts in lockstep for a breaking
protocol change — pinned CDN URLs from older dashboards keep resolving to
the old bundle.
Protocol reference
Messages are validated with zod in src/protocol.ts.
Summary:
iframe → host
{v:1, id, type:"ready", appId}— mounted, waiting for context{v:1, id, type:"data:request", resource, params?}— ask host for data{v:1, id, type:"resize", height}— report content height{v:1, id, type:"navigate", href}— ask host to navigate{type:"app:ready"}— legacy (app-generator) handshake{type:"app:resize", height}— legacy resize
host → iframe
{v:1, id, type:"init", theme, locale, authToken?, permissions}— session context{v:1, id, type:"data:response", ok, data?, error?}— reply todata:request{v:1, id, type:"theme:change", theme}— dark/light toggle{v:1, id, type:"locale:change", locale}— i18n switch{type:"app:config", config}— legacy reply toapp:ready
