@jamplus/kronos
v1.0.1
Published
Designer initialization package for Olympus.
Readme
Kronos
Kronos is a lightweight iframe and postMessage manager for embedding a JAM+ designer into any web page. It handles iframe creation, sandboxed module loading, and a typed async RPC + event bus between your page and the designer — no build tooling or framework required on the integrator side.
How it works
createDesigner injects an iframe into a container element you provide. It either loads the designer from a URL directly, or bootstraps a designer JS module into a blob iframe (handling Vite HMR stubs, base URL resolution, and method auto-registration). Once the designer signals ready, you get back a DesignerInstance handle.
All communication between your page and the designer goes through postMessage. Kronos manages call IDs, pending response queues, and event handlers transparently.
Installation
You can pull the latest Kronos ESM module from NPM
npm install @jamplus/kronosimport DesignerManager from '@jamplus/kronos';API
createDesigner(options): Promise<DesignerInstance>
Creates the iframe and returns a handle once the designer is ready.
type DesignerOptions = {
container: HTMLElement; // element to append the iframe into
url?: string; // load a prebuilt designer iframe directly. For externally hosted designers
moduleUrl?: string; // load a designer JS module (auto-wires exports). For dynamically building the designer from an ESM module.
iframeName?: string; // iframe name attribute (default: 'designer-iframe')
classes?: string[]; // CSS classes to add to the iframe. The designer uses tailwind, so these should be tailwind classes
timeout?: number; // ms to wait for ready signal (default: 10000)
};Exactly one of url or moduleUrl is required.
DesignerInstance
The handle returned by createDesigner.
| Member | Description |
|--------|-------------|
| designer.init(payload) | Initialize the designer with config and product data |
| designer.call(method, ...args) | Call any exported method on the designer (returns Promise) |
| designer.on(event, handler) | Listen for events emitted by the designer (returns unsubscribe fn) |
| designer.emit(event, payload) | Send an event into the designer |
| designer.destroy() | Remove the iframe and clean up |
| designer.iframe | The raw HTMLIFrameElement |
Designer Events
Events the designer emits that you can listen to with designer.on(...):
| Event | Payload | Description |
|-------|---------|-------------|
| save | { design, designId? } | User clicked save |
| analytics | { event, payload } | Tracking event |
| review | { product?, quantity } | User clicked "Add to Cart" / review |
| persistence | { isDirty } | Design dirty state changed |
| logoClick | {} | User clicked the logo |
Events you can send into the designer with designer.emit(...):
| Event | Payload | Description |
|-------|---------|-------------|
| saved | { success, designId?, error? } | Acknowledge a save response |
Examples
Minimal setup
<div id="designer-container" style="position:relative; width:100%; height:600px;"></div>
<script type="module">
import DesignerManager from 'kronos';
const designer = await DesignerManager.createDesigner({
container: document.getElementById('designer-container'),
moduleUrl: 'https://your-cdn.com/hermes/designer.js',
});
designer.init({
product: { /* product config */ },
endpoints: { /* API endpoints */ },
});
</script>Handling save
designer.on('save', async (payload) => {
const resp = await fetch('/api/design', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: payload }),
});
const result = await resp.json();
/* Acknowledge back to the designer (re-enables the save button, etc.) */
designer.emit('saved', result);
});Loading an existing design
const design = await fetch('/api/design/abc123').then(r => r.json());
designer.init({
design, // design to load
product: { ... },
endpoints: { ... },
});Loading from a prebuilt URL
If the designer is deployed as a standalone page (e.g. a separate domain or CDN), use url instead of moduleUrl:
const designer = await DesignerManager.createDesigner({
container: document.getElementById('designer-container'),
url: 'https://designer.example.com/iframe.html',
});Cleanup
/* Destroy the iframe and cleanup resources */
designer.destroy();Extending types for a custom designer (TypeScript)
createDesigner accepts two type parameters — TAdditionalMethods and TEvents — so you can describe the full surface of your specific designer and get typed call, on, and emit throughout.
import DesignerManager, { DesignerInstance } from '@jamplus/kronos';
// Describe any methods your designer module exports beyond the base `init`
type MyCustomMethods = {
getVersion: () => string;
exportPng: (opts: { scale: number }) => Blob;
};
// Describe any events your designer emits beyond the built-ins
type MyCustomDesignerEvents = {
fontsLoaded: { count: number };
};
const designer = await DesignerManager.createDesigner<MyMethods, MyEvents>({
container: document.getElementById('designer-container')!,
moduleUrl: 'https://your-cdn.com/hermes/designer.js',
});
/* call() and direct method access are now fully typed */
const version = await designer.call('getVersion');
const version2 = await designer.getVersion();
const png = await designer.call('exportPng', { scale: 2 });
const png2 = await designer.exportPng({scale: 2});
/* Your on() handlers will also receive the correct payload type */
designer.on('fontsLoaded', ({ count }) => {
console.log(`${count} fonts loaded`);
});
// You can also alias the instance type for reuse elsewhere
type MyDesigner = DesignerInstance<MyMethods, MyEvents>;