@ivorycom/embed
v0.1.1
Published
Embed the full white-labeled Ivorycom CRM (or any view of it) into your own product with a single <script> + <ivory-crm> web component, or the typed React wrapper.
Maintainers
Readme
@ivorycom/embed
Drop the full white‑labeled Ivorycom CRM — or a single view of it — into your own product with no second login. One <script> + the <ivory-crm> web component (or the typed React wrapper) renders the CRM in a secured iframe and speaks to it over an origin‑pinned postMessage bridge.
- 🔐 Seamless SSO — your backend mints a short‑lived session token; the user never sees an Ivorycom login.
- 🧩 Framework‑agnostic — a standards‑based web component, plus a first‑class React wrapper (
@ivorycom/embed/react). - 🎯 Scoped & tier‑bounded — the embed only reaches the feature areas you grant (leads, pipeline, …), enforced server‑side.
- 🎨 White‑label & theming — brand color, logo, and light/dark/auto.
- 📡 Two‑way bridge — receive real CRM events (
lead.created,deal.won) and drive the CRM (navigate,openRecord,runAgent, …).
Pairs with
@ivorycom/embed-node— the zero‑dependency server SDK that mints the session token. The secret stays on your server; the browser only ever holds a short‑lived token.
Installation
npm install @ivorycom/embedOr load the loader straight from the first‑party CDN (no bundler required):
<script src="https://embed.ivorycomcrm.com/v1.js"></script>
<!-- pin a version for production: -->
<script src="https://embed.ivorycomcrm.com/v1/0.1.0/v1.js"></script>The flow (how a token gets to the browser)
┌────────────┐ 1. user opens your page ┌──────────────────────┐
│ Browser │ ───────────────────────────► │ Your backend │
│ <ivory-crm>│ │ @ivorycom/embed-node│
│ │ ◄─────────────────────────── │ mintSession() │
└────────────┘ 2. short-lived embedToken └──────────┬───────────┘
│ │ client_id + SECRET
│ 3. renders the CRM in a secured iframe ▼
│ (token sent via postMessage, never URL) Ivorycom auth
▼
embed.ivorycomcrm.com ──► the white-labeled CRMYour server calls mintSession() with your embed app's client_id + secret; you hand the returned embedToken to <ivory-crm token="…">. The secret never leaves your server; the token is short‑lived (default 15 min) and scope‑bounded.
Quick start
1. Web component (any stack)
<script src="https://embed.ivorycomcrm.com/v1.js"></script>
<ivory-crm
client-id="iem_your_public_client_id"
token="EMBED_SESSION_TOKEN_FROM_YOUR_BACKEND"
view="pipeline"
theme="auto"
style="width:100%;height:800px;border:0;">
</ivory-crm>
<script>
const el = document.querySelector("ivory-crm");
// Listen for real CRM events
el.on("lead.created", (payload) => console.log("new lead", payload));
el.on("deal.won", (payload) => console.log("deal won", payload));
el.on("ready", () => console.log("embed ready"));
// Drive the CRM from your app
el.command("navigate", { path: "/leads" });
</script>2. React
import { IvoryCrm, type IvoryCrmHandle } from "@ivorycom/embed/react";
import { useRef } from "react";
export function CrmPanel({ token }: { token: string }) {
const ref = useRef<IvoryCrmHandle>(null);
return (
<IvoryCrm
ref={ref}
clientId="iem_your_public_client_id"
token={token}
view="pipeline"
theme="auto"
onReady={() => console.log("ready")}
onEvent={(name, payload) => console.log(name, payload)}
style={{ width: "100%", height: 800, border: 0 }}
/>
);
// ref.current?.command("navigate", { path: "/leads" });
}
reactandreact-domare peer dependencies — the wrapper ships no React of its own.
API
<ivory-crm> — attributes
| Attribute | Required | Values | Description |
| --- | --- | --- | --- |
| token | ✅ | string | Short‑lived embed session token from your backend. Sent to the iframe via postMessage, never placed in the URL. |
| client-id | – | iem_… | Your public embed app id. Safe in the browser; lets the embed origin resolve your app's allowed frame origins. |
| view | – | pipeline · inbox · leads · contact · reports · activity | Render a single surface instead of the full app. Must be within the app's enabled views. |
| record-id | – | string | The record to open when view targets a single record. |
| theme | – | auto · light · dark | Color theme. Defaults to auto (follows the host). |
| origin | – | URL | Override the embed origin (defaults to https://embed.ivorycomcrm.com). |
Changing token, view, record-id, or theme after mount re‑initializes the embed.
element.on(event, handler) → unsubscribe
Subscribe to events emitted by the CRM. Returns an unsubscribe function.
const off = el.on("deal.won", (payload) => { /* … */ });
off(); // stop listeningelement.command(command, payload?)
Drive the embedded CRM from your host app. See Commands.
React: <IvoryCrm> props
| Prop | Type | Description |
| --- | --- | --- |
| token | string (required) | Embed session token. |
| clientId | string | Public embed app id. |
| view | "pipeline" \| "inbox" \| "leads" \| "contact" \| "reports" \| "activity" | Single‑surface view. |
| recordId | string | Record id for record‑scoped views. |
| theme | "auto" \| "light" \| "dark" | Color theme. |
| onReady | () => void | Fires when the embed has booted. |
| onEvent | (name, payload) => void | Fires on every CRM event. |
| …plus standard HTMLElement props (style, className, …). |
The ref exposes an IvoryCrmHandle with command(name, payload?).
Events
Emitted by the CRM, consumed via el.on(name, …) / React onEvent:
| Event | Payload | When |
| --- | --- | --- |
| ready | – | The embed booted and established its session. |
| lead.created | { id, name, email, status } | A lead was created in the CRM. |
| deal.won | { id, name, value } | An opportunity was marked won. |
More domain events are added over time; unknown events are safely ignored.
Commands
Sent from your host via el.command(name, payload?) / React ref.command(...):
| Command | Payload | Effect |
| --- | --- | --- |
| navigate | { page } | Navigate the CRM to a page. |
| setTheme | { theme: "auto"\|"light"\|"dark" } | Change the theme at runtime. |
| openRecord | { … } | Open a specific record. |
| prefill | { … } | Prefill a form. |
| runAgent | { prompt } | Run the embedded ARIA/MCP agent (requires the agent scope). |
Scopes & views
An embed app is registered with a subset of the tenant's plan — the coarse scope vocabulary:
leads · contacts · pipeline · inbox · activity · reports · campaigns · automations · agent (or * for all)
Enforcement is default‑closed and server‑side on every service (the token can only reach the granted areas — and never beyond the tenant's subscription tier). The view you render must be within the app's enabled views. You choose scopes/views when you register the embed app and when you mint the session (see @ivorycom/embed-node).
Security model
- The secret never reaches the browser. Only your server holds the embed app secret; the browser holds a short‑lived, scope‑bounded token.
- Origin‑pinned bridge. Every
postMessageis validated against the verified parent/embed origin on both sides; messages from any other origin are ignored. - Clickjacking‑safe. The embed origin serves per‑app
frame-ancestorsand fails closed ('none') for unknown apps. - Token in
postMessage, not the URL. The session token is handed to the iframe after load, never placed in a URL or referrer. - Tenant‑isolated (RLS) + tier‑bounded. The token is scoped to one tenant; server‑side plan gates still apply.
CDN & versioning
The loader is served first‑party from Cloudflare R2:
| URL | Cache | Use |
| --- | --- | --- |
| https://embed.ivorycomcrm.com/v1.js | short (latest) | Auto‑updates within the v1 line. |
| https://embed.ivorycomcrm.com/v1/<version>/v1.js | immutable | Pin an exact build for production. |
The npm package ships the same loader plus the web component and React wrapper.
TypeScript
Fully typed. Import the types you need:
import type {
IvoryCrmProps,
IvoryCrmHandle,
EmbedEventName,
EmbedCommandName,
} from "@ivorycom/embed/react";Related
@ivorycom/embed-node— server SDK to mint embed session tokens.
License
MIT © Ivorycom
