UNPKG

adam-sdk

Version:

A JavaScript SDK for integrating A.D.A.M. 3D avatars into web applications.

167 lines (166 loc) 6.39 kB
class h { iframe; targetOrigin; eventListeners = /* @__PURE__ */ new Map(); pendingCommands = /* @__PURE__ */ new Map(); isReady = !1; handshakeInterval; fastPollInterval; logLevel; /** * Creates an instance of the AvatarSDK. * @param iframeElement The HTMLIFrameElement that contains the avatar. * @param options Configuration options for the SDK. */ constructor(e, t = {}) { this.iframe = e, this.targetOrigin = t.targetOrigin || new URL(e.src).origin, this.logLevel = t.logLevel || "info", window.addEventListener("message", this._handleMessage.bind(this)), this._log("info", "AvatarSDK: Message listener attached"); } /** * Establishes a connection with the avatar iframe. * This method must be called before any other commands can be sent to the avatar. * It returns a promise that resolves when the connection is established. * @returns A promise that resolves when the connection is established. * @throws An error if the connection times out or the iframe fails to load. */ connect() { return new Promise((e, t) => { if (this.isReady) return e(); const s = setTimeout(() => { clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), t(new Error("Connection timed out. The avatar did not respond.")); }, 1e4); this.iframe.onerror = () => { clearTimeout(s), clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), t(new Error("The avatar iframe failed to load.")); }, this.on("ready", () => { clearTimeout(s), clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), e(); }), this._postMessage({ type: "CHECK_READY_STATUS" }); let n = 0; this.fastPollInterval = setInterval(() => { n < 8 ? (this._postMessage({ type: "CHECK_READY_STATUS" }), n++) : (clearInterval(this.fastPollInterval), this.handshakeInterval = setInterval(() => { this.isReady || this._postMessage({ type: "CHECK_READY_STATUS" }); }, 500)); }, 250); }); } /** * Makes the avatar speak the provided text. * @param text The text for the avatar to speak. * @param options Additional options for speech, such as voice, rate, and pitch. * @returns A promise that resolves when the command is acknowledged by the iframe. * @throws An error if the text is not a non-empty string. */ speak(e, t = {}) { return console.log("[AvatarSDK] speak", e, t), typeof e != "string" || !e.trim() ? Promise.reject(new Error("Text must be a non-empty string")) : this._sendCommand("speak", { text: e, ...t }); } /** * Triggers a specific animation by name. * @param name The name of the animation to play. * @param loop Whether the animation should loop. * @returns A promise that resolves with the command result. */ playAnimation(e, t = !1) { return this._sendCommand("playAnimation", { name: e, loop: t }); } /** * Sets the avatar's facial expression. * @param name The name of the expression to set. * @returns A promise that resolves with the command result. */ setExpression(e) { return this._sendCommand("setExpression", { name: e }); } /** * Interrupts the current speech or action. * @returns A promise that resolves when the interrupt is complete. */ interrupt() { return this._sendCommand("interrupt"); } /** * Registers an event listener for a specific event. * @param eventName The name of the event to listen for. * @param callback The callback function to execute when the event is triggered. * @example * ```javascript * sdk.on('speech:start', (payload) => { * console.log('Avatar started speaking:', payload.text); * }); * ``` */ on(e, t) { this.eventListeners.has(e) || this.eventListeners.set(e, []), this.eventListeners.get(e)?.push(t); } /** * Removes an event listener for a specific event. * @param eventName The name of the event to remove the listener from. * @param callback The callback function to remove. */ off(e, t) { const s = this.eventListeners.get(e); if (s) { const n = s.indexOf(t); n > -1 && s.splice(n, 1); } } /** * Cleans up resources and removes event listeners. * This should be called when the SDK is no longer needed to prevent memory leaks. */ destroy() { window.removeEventListener("message", this._handleMessage.bind(this)), clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), this.eventListeners.clear(), this.pendingCommands.clear(), this.isReady = !1; } _sendCommand(e, t = {}) { if (!this.isReady) return Promise.reject(new Error("Not connected to avatar.")); const s = `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, n = { type: "AVATAR_COMMAND", command: e, payload: t, commandId: s }; return new Promise((r, i) => { const a = setTimeout(() => { this.pendingCommands.has(s) && (this.pendingCommands.delete(s), i(new Error(`Command '${e}' timed out after 30s`))); }, 3e4); this.pendingCommands.set(s, { resolve: r, reject: i, timeout: a }), this._postMessage(n); }); } _handleMessage(e) { if (e.origin !== this.targetOrigin) return; const { type: t, commandId: s, event: n, payload: r } = e.data; if (t === "AVATAR_READY") { this.isReady = !0, this._emitEvent("ready", void 0); return; } if (t === "AVATAR_EVENT" && (this._emitEvent(n, r), s && this.pendingCommands.has(s))) { const { resolve: i, reject: a, timeout: o } = this.pendingCommands.get(s); clearTimeout(o), n === "command:success" ? i(r) : n === "command:error" && a(new Error(r?.error || "Command failed")), this.pendingCommands.delete(s); } } _postMessage(e) { this.iframe.contentWindow && this.iframe.contentWindow.postMessage(e, this.targetOrigin); } _log(e, t) { if (this.logLevel === "none" || e === "none") return; const s = { error: 1, warn: 2, info: 3 }; s[e] <= s[this.logLevel] && console[e](t); } _emitEvent(e, t) { const s = this.eventListeners.get(e); s && s.forEach((n) => { try { n(t); } catch (r) { console.error(`Error in event listener for ${e}:`, r); } }); } } export { h as AvatarSDK };