UNPKG

adam-sdk

Version:

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

171 lines (170 loc) 6.43 kB
class d { 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, s = {}) { this.iframe = e, this.targetOrigin = s.targetOrigin || new URL(e.src).origin, this.logLevel = s.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, s) => { if (this.isReady) return e(); const t = setTimeout(() => { clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), s(new Error("Connection timed out. The avatar did not respond.")); }, 1e4); this.iframe.onerror = () => { clearTimeout(t), clearInterval(this.handshakeInterval), clearInterval(this.fastPollInterval), s(new Error("The avatar iframe failed to load.")); }, this.on("ready", () => { clearTimeout(t), 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); }); } speak(e, s = {}) { let t; if (typeof e == "string") { const n = e, r = s || {}; if (!n || !n.trim()) return Promise.reject(new Error("Text must be a non-empty string")); t = { ...r, text: n }; } else if (typeof e == "object" && e !== null) { if (t = { ...e }, typeof t.text != "string" || !t.text.trim()) return Promise.reject(new Error("payload.text must be a non-empty string")); } else return Promise.reject(new Error("Invalid arguments to speak")); return console.log("[AvatarSDK] speak", t), this._sendCommand("speak", 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, s = !1) { return this._sendCommand("playAnimation", { name: e, loop: s }); } /** * 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, s) { this.eventListeners.has(e) || this.eventListeners.set(e, []), this.eventListeners.get(e)?.push(s); } /** * 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, s) { const t = this.eventListeners.get(e); if (t) { const n = t.indexOf(s); n > -1 && t.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, s = {}) { if (!this.isReady) return Promise.reject(new Error("Not connected to avatar.")); const t = `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, n = { type: "AVATAR_COMMAND", command: e, payload: s, commandId: t }; return new Promise((r, i) => { const a = setTimeout(() => { this.pendingCommands.has(t) && (this.pendingCommands.delete(t), i(new Error(`Command '${e}' timed out after 30s`))); }, 3e4); this.pendingCommands.set(t, { resolve: r, reject: i, timeout: a }), this._postMessage(n); }); } _handleMessage(e) { if (e.origin !== this.targetOrigin) return; const { type: s, commandId: t, event: n, payload: r } = e.data; if (s === "AVATAR_READY") { this.isReady = !0, this._emitEvent("ready", void 0); return; } if (s === "AVATAR_EVENT" && (this._emitEvent(n, r), t && this.pendingCommands.has(t))) { const { resolve: i, reject: a, timeout: o } = this.pendingCommands.get(t); clearTimeout(o), n === "command:success" ? i(r) : n === "command:error" && a(new Error(r?.error || "Command failed")), this.pendingCommands.delete(t); } } _postMessage(e) { this.iframe.contentWindow && this.iframe.contentWindow.postMessage(e, this.targetOrigin); } _log(e, s) { if (this.logLevel === "none" || e === "none") return; const t = { error: 1, warn: 2, info: 3 }; t[e] <= t[this.logLevel] && console[e](s); } _emitEvent(e, s) { const t = this.eventListeners.get(e); t && t.forEach((n) => { try { n(s); } catch (r) { console.error(`Error in event listener for ${e}:`, r); } }); } } export { d as AvatarSDK };