dreamstack/sdk/dreamstack-embed.js

135 lines
4.7 KiB
JavaScript

/**
* DreamStack Embed SDK — ~3KB standalone
* Enables embedding DreamStack apps in any website.
*
* Usage:
* <script src="dreamstack-embed.js"></script>
* <ds-stream src="https://yourapp.com"></ds-stream>
*
* Or via JS API:
* DreamStack.connect('https://yourapp.com', '#container');
*/
(function (root, factory) {
if (typeof module !== 'undefined' && module.exports) module.exports = factory();
else root.DreamStack = factory();
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';
// ── Iframe Embed ──
function embed(src, container, options) {
var opts = options || {};
var el = typeof container === 'string' ? document.querySelector(container) : container;
if (!el) throw new Error('[DreamStack] Container not found: ' + container);
var iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.border = 'none';
iframe.style.width = opts.width || '100%';
iframe.style.height = opts.height || '400px';
iframe.style.borderRadius = opts.borderRadius || '12px';
iframe.style.overflow = 'hidden';
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
iframe.setAttribute('loading', 'lazy');
if (opts.className) iframe.className = opts.className;
el.appendChild(iframe);
return {
iframe: iframe,
destroy: function () { el.removeChild(iframe); },
resize: function (w, h) {
iframe.style.width = typeof w === 'number' ? w + 'px' : w;
iframe.style.height = typeof h === 'number' ? h + 'px' : h;
}
};
}
// ── Signal Bridge (bidirectional) ──
function connect(src, container, options) {
var handle = embed(src, container, options);
var listeners = {};
// Listen for messages from the DreamStack app
window.addEventListener('message', function (e) {
if (e.source !== handle.iframe.contentWindow) return;
var data = e.data;
if (data && data.type === 'ds:signal') {
var name = data.name;
if (listeners[name]) {
listeners[name].forEach(function (fn) { fn(data.value); });
}
if (listeners['*']) {
listeners['*'].forEach(function (fn) { fn(name, data.value); });
}
}
});
return {
iframe: handle.iframe,
destroy: handle.destroy,
resize: handle.resize,
// Send a signal value to the DreamStack app
send: function (name, value) {
handle.iframe.contentWindow.postMessage(
{ type: 'ds:signal', name: name, value: value }, '*'
);
},
// Listen for signal changes from the DreamStack app
on: function (name, fn) {
if (!listeners[name]) listeners[name] = [];
listeners[name].push(fn);
return function () {
listeners[name] = listeners[name].filter(function (f) { return f !== fn; });
};
}
};
}
// ── Web Component: <ds-stream> ──
if (typeof customElements !== 'undefined') {
customElements.define('ds-stream', class extends HTMLElement {
constructor() {
super();
this._handle = null;
}
connectedCallback() {
var src = this.getAttribute('src');
if (!src) return;
var shadow = this.attachShadow({ mode: 'open' });
var wrapper = document.createElement('div');
wrapper.style.width = '100%';
wrapper.style.height = this.getAttribute('height') || '400px';
shadow.appendChild(wrapper);
this._handle = embed(src, wrapper, {
width: '100%',
height: '100%',
borderRadius: this.getAttribute('radius') || '12px'
});
}
disconnectedCallback() {
if (this._handle) this._handle.destroy();
}
static get observedAttributes() { return ['src', 'height']; }
attributeChangedCallback(name, old, val) {
if (name === 'src' && this._handle) {
this._handle.iframe.src = val;
} else if (name === 'height' && this._handle) {
this._handle.resize('100%', val);
}
}
});
}
return {
embed: embed,
connect: connect,
version: '0.1.0'
};
});