Percus SmartEmbed SDK
The Percus SmartEmbed SDK (percus-embed-sdk) is the client-side library used to embed the Percus Player into any web page. It handles iframe creation, the postMessage communication protocol, and exposes a clean JavaScript API for controlling playback.
Installation
Via <script> tag (IIFE)
<script src="https://cdn.percus.example/percus-embed.js"></script>
The SDK is exposed as window.PercusEmbed.
Via npm / bundler (ESM)
npm install @percus/percus-embed-sdk
import { PercusEmbed } from "@percus/percus-embed-sdk";
Build outputs
| File | Format | Use case |
|---|---|---|
dist/percus-embed.js | IIFE | Plain <script> tag |
dist/percus-embed.esm.js | ESM | Webpack, Vite, Rollup |
dist/percus-embed.d.ts | TypeScript declarations | Type-safe usage |
Quick start
<div id="player-host" style="width: 800px; height: 450px;"></div>
<script src="./dist/percus-embed.js"></script>
<script>
const controller = PercusEmbed.init({
target: "#player-host",
playerUrl: "https://player.percus.example/runtime",
templateUrl: "https://cdn.example.com/template.json",
manifestUrl: "https://cdn.example.com/manifest.json",
data: { userId: "abc123", name: "María" },
onReady: (msg) => console.log("Player ready", msg),
onProgress: (msg) => console.log("Time:", msg.payload.time),
onError: (msg) => console.error("Error:", msg.payload.message),
});
// Control playback
controller.play();
controller.pause();
controller.seek(30); // seek to 30 seconds
controller.destroy(); // tear down
</script>
API Reference
initEmbed(params) / PercusEmbed.init(params)
Creates and injects an <iframe>, then initializes communication with the Player Runtime.
function initEmbed(params: EmbedInitParams): EmbedController
Returns an EmbedController.
EmbedInitParams
| Property | Type | Required | Description |
|---|---|---|---|
target | string | HTMLElement | Yes | CSS selector or DOM element that will contain the iframe. |
playerUrl | string | No | URL of the Percus Player runtime (defaults to "about:blank"). |
templateUrl | string | No | URL of the Lottie JSON animation template. |
manifestUrl | string | No | URL of the binding manifest JSON. |
data | unknown | No | Inline personalization data object. Mutually exclusive with dataUrl. |
dataUrl | string | No | URL to fetch personalization data from. Mutually exclusive with data. |
options | Record<string, unknown> | No | Additional configuration forwarded to the Player Runtime. |
onReady | (event: PercusReadyMessage) => void | No | Callback fired once the player finishes loading. |
onProgress | (event: PercusProgressMessage) => void | No | Callback fired every ~500 ms during playback. |
onError | (event: PercusErrorMessage) => void | No | Callback fired when the player encounters an error. |
EmbedController
The object returned by initEmbed().
| Member | Signature | Description |
|---|---|---|
play() | () => void | Sends PERCUS/PLAY to the player iframe. |
pause() | () => void | Sends PERCUS/PAUSE to the player iframe. |
seek(seconds) | (seconds: number) => void | Seeks to seconds into the animation. |
destroy() | () => void | Removes the iframe, unregisters all event listeners, and frees resources. |
iframe | HTMLIFrameElement | Direct reference to the injected iframe element. |
Time units:
seek()accepts seconds. The SDK converts to milliseconds before forwarding to the Player Runtime.
Events (Player → Host callbacks)
onReady
Fired once after the player has loaded the template and applied bindings.
{
version: 1;
type: "PERCUS/READY";
payload: {
playerVersion?: string; // e.g. "0.1.0"
};
}
onProgress
Fired approximately every 500 ms during active playback.
{
version: 1;
type: "PERCUS/PROGRESS";
payload: {
time: number; // Current position in seconds
duration?: number; // Total duration in seconds (if known)
state?: "playing" | "paused" | "ended"; // Current playback state
};
}
onError
Fired when the player encounters an unrecoverable error.
{
version: 1;
type: "PERCUS/ERROR";
payload: {
code: string; // Machine-readable code (e.g. "LOAD_FAILED")
message: string; // Human-readable description
details?: unknown; // Optional structured context
};
}
Message contract helpers
The SDK exports utility functions used internally (and available for advanced use cases):
import {
isPercusMessage,
makeInitMessage,
makePlayMessage,
makePauseMessage,
makeSeekMessage,
} from "@percus/percus-embed-sdk";
isPercusMessage(value) // type-guard: true if value is a valid PercusMessage
makeInitMessage(payload) // build a PERCUS/INIT envelope
makePlayMessage() // build a PERCUS/PLAY envelope
makePauseMessage() // build a PERCUS/PAUSE envelope
makeSeekMessage(time) // build a PERCUS/SEEK envelope (time in seconds)
The protocol version constant is exported as:
PERCUS_MESSAGE_VERSION = 1
Advanced configuration
options (passed through EmbedInitParams.options) supports additional internal keys:
| Key | Type | Default | Description |
|---|---|---|---|
postMessageTargetOrigin | string | "*" | Target origin for outbound postMessage calls. Pin this to the player domain in production. |
iframe.allow | string | "autoplay; fullscreen" | Value for the iframe allow attribute. |
iframe.referrerPolicy | string | — | Referrer policy for the iframe. |
iframe.title | string | "Percus Player" | Accessible title for the iframe element. |
iframe.className | string | — | CSS class added to the iframe element. |
iframe.style | CSSStyleDeclaration | border:0; width:100%; height:100% | Inline styles applied to the iframe. |
Iframe defaults
When the SDK injects the <iframe>, it sets:
<iframe
src="{playerUrl}"
title="Percus Player"
allow="autoplay; fullscreen"
style="border: 0; width: 100%; height: 100%;"
></iframe>
The iframe is appended as the only child of the target element.
Lifecycle
initEmbed(params)
└── Create <iframe src="playerUrl">
└── Append to target element
└── Register window "message" listener
└── On iframe "load"
└── postMessage PERCUS/INIT → player
└── On PERCUS/READY → call onReady(event)
└── On PERCUS/PROGRESS → call onProgress(event)
└── On PERCUS/ERROR → call onError(event)
controller.play() → postMessage PERCUS/PLAY
controller.pause() → postMessage PERCUS/PAUSE
controller.seek(s) → postMessage PERCUS/SEEK { time: s }
controller.destroy()
└── Remove <iframe> from DOM
└── Remove "message" listener
TypeScript support
The SDK ships full TypeScript declarations. All message types, payload shapes, and function signatures are exported:
import type {
EmbedInitParams,
EmbedController,
PercusReadyMessage,
PercusProgressMessage,
PercusErrorMessage,
PercusInitMessage,
PercusPlayMessage,
PercusPauseMessage,
PercusSeekMessage,
PercusMessageType,
} from "@percus/percus-embed-sdk";
Planned features
The following capabilities are not yet implemented. The proposed API shapes below are provisional and intended to guide design discussions.
Analytics and tracking
A tracking configuration block will be added to EmbedInitParams. When enabled, the SDK collects player events and forwards them to a configurable backend without requiring any additional code on the host page.
// Proposed addition to EmbedInitParams
tracking?: {
enabled: boolean;
trackerGroupKey?: string; // Groups events across sessions/campaigns
googleAnalytics?: {
enabled: boolean;
eventName?: string; // Custom GA event name (default: "percus_player")
};
endpoint?: string; // Custom analytics ingestion URL
};
A trackerKey will be generated per session and included in all outbound analytics events for correlation.
Consent management
Tracking will be gated behind user consent. Two new callbacks will be added:
// Proposed additions to EmbedInitParams
consentRequired?: boolean; // Default: false
onConsentAccepted?: (event: PercusConsentMessage) => void;
onConsentDeclined?: (event: PercusConsentMessage) => void;
When consentRequired is true, the SDK delays all tracking activity until onConsentAccepted fires. This supports GDPR, Law 19.628 (Chile), and LGPD (Brazil) requirements applicable to Percus clients.
Completion callbacks
Two new callbacks will fire at the end of playback:
// Proposed additions to EmbedInitParams
onPlayComplete?: (event: PercusPlayCompleteMessage) => void; // Viewer watched to the end
onPlayIncomplete?: (event: PercusPlayIncompleteMessage) => void; // Viewer left early
The onPlayIncomplete payload will include how far the viewer got (time and duration) to enable partial-watch scoring.
Call-to-action callback
// Proposed addition to EmbedInitParams
onCTA?: (event: PercusCTAMessage) => void;
Fired whenever the animation reaches a CTA marker. The host page handles the action (open a modal, redirect, log a conversion) without needing to know how CTAs are defined in the template.
Additional event callbacks
onChapterEnter?: (event: PercusChapterMessage) => void;
onChapterExit?: (event: PercusChapterMessage) => void;
onEvent?: (event: PercusEventMessage) => void; // Generic in-video event
onAutoplayFailure?: (event: PercusAutoplayFailureMessage) => void;
Playback options at init time
// Proposed additions to EmbedInitParams
autoplay?: boolean; // Attempt autoplay on ready (default: false)
mute?: boolean; // Start muted — required for autoplay in most browsers
loop?: boolean; // Loop the animation (default: false)
aspectRatio?: string; // e.g. "16x9", "9x16", "1x1" — SDK handles all responsive CSS
Modal / lightbox mode
// Proposed addition to EmbedInitParams
modal?: {
enabled: boolean;
autoOpenTime?: number; // Milliseconds before the modal opens automatically (0 = manual)
};
When modal.enabled is true, the SDK renders a full-screen overlay with a close button instead of embedding the player inline.
Multiple player instances
A PercusEmbed.get(id) / PercusEmbed.getAll() registry API will allow the host page to retrieve and control any previously initialised player by a custom id passed at init time.
// Proposed
const c1 = PercusEmbed.init({ id: "hero-player", target: "#hero", ... });
const c2 = PercusEmbed.init({ id: "sidebar-player", target: "#sidebar", ... });
PercusEmbed.get("hero-player").pause();
PercusEmbed.getAll().forEach(c => c.destroy());
Security considerations
| Concern | Recommendation |
|---|---|
postMessageTargetOrigin | Pin to the actual player domain (e.g. "https://player.percus.example") instead of "*". |
| Origin validation | The SDK validates that incoming messages come from the injected iframe's contentWindow. |
| Iframe sandbox | Add restrictive sandbox attributes on the container if the player origin is not fully trusted. |
PII in data | Personalization data is never stored by the SDK — it is forwarded once to the player at init time. |
Examples
ESM / TypeScript with full type safety
import { PercusEmbed } from "@percus/percus-embed-sdk";
import type { PercusProgressMessage } from "@percus/percus-embed-sdk";
const controller = PercusEmbed.init({
target: document.getElementById("player-host")!,
playerUrl: "https://player.percus.example/runtime",
templateUrl: "https://cdn.example.com/pension-statement.json",
manifestUrl: "https://cdn.example.com/pension-statement.manifest.json",
dataUrl: "https://api.example.com/personalization?id=abc123",
onReady: () => controller.play(),
onProgress: (msg: PercusProgressMessage) => {
const pct = msg.payload.duration
? (msg.payload.time / msg.payload.duration) * 100
: 0;
progressBar.style.width = `${pct}%`;
},
onError: (msg) => {
console.error(`[${msg.payload.code}] ${msg.payload.message}`);
},
});
Seeking on a custom button click
document.getElementById("skip-btn").addEventListener("click", () => {
controller.seek(60); // jump to 1:00
});
Cleaning up on SPA navigation
router.on("beforeLeave", () => {
controller.destroy();
});