@embedmetrics/sdk
v0.1.6
Published
Embeddable conversational analytics widget for SaaS apps
Maintainers
Readme
@embedmetrics/sdk
Embeddable conversational analytics widget for SaaS apps.
Install
npm i @embedmetrics/sdk
# or
yarn add @embedmetrics/sdkWhat's New
See the changelog for the latest updates and improvements:
- Packaged file: CHANGELOG.md
- CDN (works on npm web page): unpkg.com/@embedmetrics/sdk/CHANGELOG.md
Quick start
Call initEmbedMetrics() once at app startup (client-side) before mounting the widget.
React (recommended)
import { EmbedMetricsApp } from '@embedmetrics/sdk/react';
import { initEmbedMetrics } from '@embedmetrics/sdk';
initEmbedMetrics({ apiUrl: 'https://api.embedmetrics.com' });
export default function MyApp() {
return (
<EmbedMetricsApp
appId="your-app-id"
token="your-jwt-token"
onAuthError={async (err) => {
if (err.code === 'TOKEN_EXPIRED') return await refreshTokenViaBackend();
return null; // show auth error
}}
onError={(err) => {
console.error('EmbedMetrics:', err.code, err.message); // or send to Sentry
}}
config={{ header: { visible: false } }}
theme={{ '--em-text-default-override': '#007acc' }}
/>
);
}The widget fills its parent container. Give the parent a fixed height (e.g., height: 600px) so it's visible.
Vanilla JS
<div id="embedmetrics"></div>
<script type="module">
import { initEmbedMetrics } from "@embedmetrics/sdk";
import { createApp } from "@embedmetrics/sdk/vanilla";
initEmbedMetrics({ apiUrl: "https://api.embedmetrics.com" });
const app = createApp({
appId: "your-app-id",
token: "your-jwt-token",
onAuthError: async (err) =>
err.code === "TOKEN_EXPIRED" ? await refreshTokenViaBackend() : null,
config: { header: { visible: false } },
});
await app.mount(document.getElementById("embedmetrics"));
// Schedule proactive token refresh before expiration (simplified example)
// setInterval(async () => {
// const newToken = await refreshTokenViaBackend();
// app.update({ token: newToken }); // preserves chat history
// }, 14 * 60 * 1000); // every 14 minutes for 15min tokens
</script>The widget fills its parent container. Give the parent a fixed height (e.g., height: 600px) so it's visible.
Minimal API surface
// init once at app start
initEmbedMetrics({ apiUrl: string });
// React
type AppProps = {
appId: string;
token: string;
conversationId?: string;
config?: EmbedConfig;
theme?: Record<string, string>;
onAuthError?: (err: AuthErrorInfo) => void | Promise<string | null>;
onError?: (err: ErrorInfo) => void;
};
// Vanilla
type AppOptions = {
appId: string;
token: string;
conversationId?: string;
config?: EmbedConfig;
theme?: Record<string, string>;
onAuthError?: (err: AuthErrorInfo) => void | Promise<string | null>;
onError?: (err: ErrorInfo) => void;
};
type AppInstance = {
mount(el: HTMLElement): Promise<void>;
update(newConfig: Partial<AppOptions>): void; // proactively update props (e.g., token) without remounting
unmount(): void;
};
// Common types
type AuthErrorInfo = {
code: 'TOKEN_EXPIRED' | 'TOKEN_INVALID' | 'TOKEN_MISSING' | 'FORBIDDEN' | 'UNKNOWN_AUTH_ERROR';
message: string;
};
type EmbedConfig = {
header?: { visible?: boolean }; // default true
narrative?: { enableMarkdown?: boolean }; // default true
analyticsToolkit?: { enabled?: boolean }; // default false
chatRail?: { enabled?: boolean }; // default true
};
// AuthErrorInfo.code values:
- TOKEN_EXPIRED – refresh then return new token
- TOKEN_INVALID – user must re-auth
- TOKEN_MISSING – obtain a token
- FORBIDDEN – user/app not permitted
- UNKNOWN_AUTH_ERROR – fallback/log
// Where to import types from
import type { AuthErrorInfo, ErrorInfo, EmbedConfig } from '@embedmetrics/sdk';
import type { AppProps } from '@embedmetrics/sdk/react';
import type { AppOptions, AppInstance } from '@embedmetrics/sdk/vanilla';Auth in two minutes
Mint tokens from your backend:
POST https://api.embedmetrics.com/api/apps/{appId}/user-tokens
Authorization: Bearer <app-client-secret>⚠️ Server-to-server only. Do not call this from a browser or mobile client.
Request body:
{
"user_id": "string (required)",
"display_name": "string (optional, max 80)",
"duration": "PT30M" // optional ISO-8601 duration; default 15 minutes
}durationis ISO-8601 (e.g.,PT15M,PT30M,PT1H).- If omitted, the server defaults to 15 minutes (and may cap max TTL).
Response (201 Created):
{
"token": "<signed-jwt>",
"expires_at": "2025-09-18T04:00:10Z"
}Tip: schedule refresh from expires_at (not your requested duration).
Returning a string from onAuthError retries with that token; returning null suppresses retry and shows an auth error state.
🔄 Token refresh strategies:
- Proactive (recommended): Call
app.update({ token: newToken })(Vanilla) or update thetokenprop (React) before expiration to avoid any interruptions. - Reactive (automatic): The SDK calls
onAuthErrorwhen a request fails with 401. Return a new token to retry automatically.
cURL example:
curl -X POST "https://api.embedmetrics.com/api/apps/${APP_ID}/user-tokens" \
-H "Authorization: Bearer ${APP_CLIENT_SECRET}" \
-H "Content-Type: application/json" \
-d '{"user_id":"user-123","display_name":"Ada","duration":"PT30M"}'Errors:
400Invalid request (missinguser_id, badduration)401Invalid/missing client secret403App disabled/forbidden429Rate limited (useRetry-Afterif present)5xxServer error (retry with backoff)
Proactive refresh (recommended): refresh before expires_at to avoid interruptions.
- React: update the
tokenprop with your new token. - Vanilla: use
app.update({ token: newToken })method to refresh the token without remounting the widget (preserves chat history and UI state).
Events
embedmetrics:ready
Dispatched after the widget mounts. Use it to hide host-side preloaders. Listen on the same container element you pass to the widget.
const container = document.getElementById('embedmetrics');
container?.addEventListener('embedmetrics:ready', () => {
// Hide your preloader here
});Theming (CSS variables on the Shadow Host)
The widget renders inside a Shadow DOM for style isolation. Global CSS from your app will not affect the widget. To customize its look-and-feel, pass theme overrides via the theme prop using --em-*-override variables on the Shadow host.
theme = {
'--em-bg-surface-override': '#fff',
'--em-text-default-override': '#0d0d0d',
'--em-accent-override': '#10a37f',
'--em-font-family-override': 'Inter, sans-serif',
};Typed usage (TypeScript)
For IDE autocomplete and safer theming, use the exported types:
import type { ThemeOverrides } from '@embedmetrics/sdk';
// Optional: THEME_TOKEN_KEYS lists all supported override variable names
import { THEME_TOKEN_KEYS } from '@embedmetrics/sdk';
const theme: ThemeOverrides = {
'--em-text-default-override': '#0d0d0d',
'--em-bg-surface-override': '#ffffff',
};
<EmbedMetricsApp appId="..." token="..." theme={theme} />Notes:
- All values must be strings (e.g., "700", "16px", "#fff", '"Inter", sans-serif').
- You can also pass a plain
Record<string, string>; types are optional but recommended for DX.
Build & bundling
@embedmetrics/sdk/reactexternalizesreact,react-dom, and@embedmetrics/sdk/vanilla.@embedmetrics/sdk/vanillabundles React/DOM and runs standalone (Shadow DOM isolation).- No SSR rendering (client only).
- Module formats: ESM and CJS (works with Vite, Webpack, Next.js, etc.).
Next.js: use in client components only (no SSR). Example: dynamic import with { ssr: false }, call initEmbedMetrics() in a useEffect.
License
@embedmetrics/sdk is proprietary and UNLICENSED software. No rights are granted
except as expressly authorized in a separate agreement with EmbedMetrics, Inc.
Self-serve customers accept our online Terms during account creation. For access,
visit your account portal or contact [email protected].
Support
For support and questions:
