'use strict'; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /* * Nexmo Client SDK * Conversation Object Model * * Copyright (c) Nexmo Inc. */ const WildEmitter = require('wildemitter'); const loglevel_1 = require("loglevel"); const nexmoClientError_1 = require("./nexmoClientError"); const member_1 = __importDefault(require("./member")); const nxmEvent_1 = __importDefault(require("./events/nxmEvent")); const text_event_1 = __importDefault(require("./events/text_event")); const message_event_1 = __importDefault(require("./events/message_event")); const media_1 = __importDefault(require("./modules/media")); const conversation_events_1 = __importDefault(require("./handlers/conversation_events")); const utils_1 = __importDefault(require("./utils")); const page_config_1 = __importDefault(require("./pages/page_config")); const events_page_1 = __importDefault(require("./pages/events_page")); const members_page_1 = __importDefault(require("./pages/members_page")); const application_1 = __importDefault(require("./application")); /** * A single conversation Object. * @class Conversation * @property {Member} me - my Member object that belongs to this conversation * @property {Application} application - the parent Application * @property {string} name - the name of the Conversation (unique) * @property {string} [display_name] - the display_name of the Conversation * @property {Map<string, Member>} [members] - the members of the Conversation keyed by a member's id * @property {Map<string, NXMEvent>} [events] - the events of the Conversation keyed by an event's id * @property {number} [sequence_number] - the last event id */ class Conversation { constructor(application, params) { this.log = loglevel_1.getLogger(this.constructor.name); this.application = application; this.id = null; this.name = null; this.display_name = null; this.timestamp = null; this.members = new Map(); this.events = new Map(); this.sequence_number = 0; this.pageConfig = new page_config_1.default(((this.application.session || {}).config || {}).events_page_config); this.events_page_last = null; this.members_page_last = null; this.conversationEventHandler = new conversation_events_1.default(application, this); this.media = new media_1.default(this); /** * A Member Object representing the current user. * Only set if the user is or has been a member of the Conversation, * otherwise the value will be `null`. * @type Member */ this.me = null; // We are not in the conversation ourselves by default // Map the params (which includes the id) this._updateObjectInstance(params); WildEmitter.mixin(Conversation); } /** Update Conversation object params * @property {object} params the params to update * @private */ _updateObjectInstance(params) { for (let key in params) { switch (key) { case 'id': this.id = params.id; break; case 'name': this.name = params.name; break; case 'display_name': this.display_name = params.display_name; break; case 'members': // update the conversation javascript object params.members.forEach((m) => { if (this.members.has(m.member_id)) { this.members.get(m.member_id)._normalise(m); if (m.user_id === this.application.me.id && m.state !== 'LEFT') { this.me = this.members.get(m.member_id); this.members.set(this.me.id, this.me); } } else { const member = new member_1.default(this, m); if (m.user_id === this.application.me.id && m.state !== 'LEFT') { this.me = member; } this.members.set(member.id, member); } }); break; case 'timestamp': this.timestamp = params.timestamp; break; case 'sequence_number': this.sequence_number = params.sequence_number; break; case 'member_id': // filter needed params to create the object // the conversation list gives us the member_id to prepare the member/this object const object_params = { id: params.member_id, state: params.state, user: this.application.me }; // update the member object or create a new instance if (this.members.has(params.member_id)) { const member_object = this.members.get(params.member_id); Object.assign(member_object, object_params); } else { const member = new member_1.default(this, object_params); this.me = member; this.members.set(member.id, member); } break; } } } /** * Join the given User to this Conversation. Will typically be used this to join * ourselves to a Conversation we create. * Accept an invitation if our Member has state INVITED and no user_id / user_name is given * * @param {object} [params = this.application.me.id] The User to join (defaults to this) * @param {string} params.user_name the user_name of the User to join * @param {string} params.user_id the user_id of the User to join * @returns {Promise<Member>} * * @example <caption>join a user to the Conversation</caption> * * conversation.join().then((member) => { * console.log("joined as member: ", member) * }).catch((error) => { * console.error("error joining conversation ", error); * }); */ async join(params) { var _a, _b; try { let data = { state: 'joined', channel: { type: 'app' }, user: { ...(!params && { name: this.application.me.name, id: this.application.me.id }), ...(params && params.user_name && { name: params.user_name }), ...(params && params.user_id && { id: params.user_id }), }, }; if (((_a = this === null || this === void 0 ? void 0 : this.me) === null || _a === void 0 ? void 0 : _a.id) && ((_b = this === null || this === void 0 ? void 0 : this.me) === null || _b === void 0 ? void 0 : _b.state) !== 'LEFT') { data["from"] = this.me.id; } const response = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/members`, version: 'v0.3', data }); const member = new member_1.default(this, response); if (response._embedded.user.id === this.application.me.id) { this.me = member; this.members.set(member.id, member); } // use case where between the time we got the conversation and the time we finished joining // the conversation object changed. this.application.getConversation(this.id, application_1.default.CONVERSATION_API_VERSION.v3); return member; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Delete a conversation * @returns {Promise} * @example <caption>delete the Conversation</caption> * * conversation.del().then(() => { * console.log("conversation deleted"); * }).catch((error) => { * console.error("error deleting conversation ", error); * }); */ async del() { try { const response = await this.application.session.sendNetworkRequest({ type: 'DELETE', path: `conversations/${this.id}` }); this.application.conversations.delete(this.id); return response; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Delete an NXMEvent (e.g. Text) * @param {NXMEvent} event * @returns {Promise} * @example <caption>delete an Event</caption> * * conversation.deleteEvent(eventToBeDeleted).then(() => { * console.log("event was deleted"); * }).catch((error) => { * console.error("error deleting the event ", error); * }); * */ deleteEvent(event) { return event.del(); } /** * Invite the given user (id or name) to this conversation * @param {Member} params * @param {string} [params.id or user_name] - the id or the username of the User to invite * * @returns {Promise<Member>} * * @example <caption>invite a user to a Conversation</caption> * const user_id = 'id of User to invite'; * const user_name = 'username of User to invite'; * * conversation.invite({ * id: user_id, * user_name: user_name * }).then((member) => { * displayMessage(member.state + " user: " + user_id + " " + user_name); * }).catch((error) => { * console.error("error inviting user ", error); * }); * */ async invite(params) { var _a, _b; if (!params || (!params.id && !params.user_name)) { throw new nexmoClientError_1.NexmoClientError('error:invite:missing:params'); } const data = { state: 'invited', user: { ...(params.id && { id: params.id }), ...(params.user_name && { name: params.user_name }) }, media: params.media, channel: { from: { type: 'app' }, to: { type: 'app' }, type: 'app' } }; if (((_a = this === null || this === void 0 ? void 0 : this.me) === null || _a === void 0 ? void 0 : _a.id) && ((_b = this === null || this === void 0 ? void 0 : this.me) === null || _b === void 0 ? void 0 : _b.state) !== 'LEFT') { data["from"] = this.me.id; } try { const response = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/members`, version: 'v0.3', data }); const member = new member_1.default(this, response); return member; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Invite the given user (id or name) to this conversation with media audio * @param {Member} params * @param {string} [params.id or user_name] - the id or the username of the User to invite * * @returns {Promise<Member>} * * @example <caption>invite a user to a conversation</caption> * const user_id = 'id of User to invite'; * const user_name = 'username of User to invite'; * * conversation.inviteWithAudio({ * id: user_id, * user_name: user_name * }).then((member) => { * displayMessage(member.state + " user: " + user_id + " " + user_name); * }).catch((error) => { * console.error("error inviting user ", error); * }); * */ inviteWithAudio(params) { if (!params || (!params.id && !params.user_name)) { return Promise.reject(new nexmoClientError_1.NexmoClientError('error:invite:missing:params')); } params.media = { audio_settings: { enabled: true, muted: false, earmuffed: false } }; return this.invite(params); } /** * Leave from the Conversation * @param {object} [reason] the reason for leaving the conversation * @param {string} [reason.reason_code] the code of the reason * @param {string} [reason.reason_text] the description of the reason * @returns {Promise} * @example <caption>leave the Conversation</caption> * * conversation.leave({reason_code: "mycode", reason_text: "my reason for leaving"}).then(() => { * console.log("successfully left conversation"); * }).catch((error) => { * console.error("error leaving conversation ", error); * }); * */ leave(reason) { return this.me.kick(reason); } /** * Send a text message to the conversation, which will be relayed to every other member of the conversation * @param {string} text - the text message to be sent * * @returns {Promise<TextEvent>} - the text message that was sent * * @example <caption> sending a text </caption> * conversation.sendText("Hi Vonage").then((event) => { * console.log("message was sent", event); * }).catch((error)=>{ * console.error("error sending the message ", error); * }); * * @deprecated since version 8.3.0 * */ async sendText(text) { try { if (this.me === null) { throw new nexmoClientError_1.NexmoClientError('error:self'); } const msg = { type: 'text', cid: this.id, from: this.me.id, body: { text } }; const { id, timestamp } = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data: msg }); msg.id = id; msg.body.timestamp = timestamp; return new text_event_1.default(this, msg); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Send a custom event to the Conversation * @param {object} params - params of the custom event * @param {string} params.type the name of the custom event. Must not exceed 100 char length and contain only alpha numerics and '-' and '_' characters. * @param {object} params.body customizable key value pairs * * @returns {Promise<NXMEvent>} - the custom event that was sent * * @example <caption> sending a custom event </caption> * conversation.sendCustomEvent({ type: "my-event", body: { mykey: "my value" }}).then((event) => { * console.log("custom event was sent", event); * }).catch((error)=>{ * console.error("error sending the custom event", error); * }); * */ async sendCustomEvent({ type, body }) { try { if (this.me === null) { throw new nexmoClientError_1.NexmoClientError('error:self'); } else if (!type || typeof type !== 'string' || type.length < 1) { throw new nexmoClientError_1.NexmoClientError('error:custom-event:invalid'); } const data = { type: `custom:${type}`, cid: this.id, from: this.me.id, body }; const { id, timestamp } = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data }); data.id = id; data.timestamp = timestamp; return new nxmEvent_1.default(this, data); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Send an ephemeral event to the Conversation * @param {object} body - customizable key value pairs * * @returns {Promise<NXMEvent>} - the ephemeral event that was sent * * @example <caption> sending an ephemeral event </caption> * conversation.sendEphemeralEvent({ mykey: "my value" }).then((event) => { * console.log("ephemeral event was sent", event); * }).catch((error)=>{ * console.error("error sending the ephemeral event", error); * }); * */ async sendEphemeralEvent(body) { try { if (this.me === null) { throw new nexmoClientError_1.NexmoClientError('error:self'); } const data = { type: 'ephemeral', cid: this.id, from: this.me.id, body }; const { id, timestamp } = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data }); data.timestamp = timestamp; return new nxmEvent_1.default(this, data); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Uploads an Image to Media Service. * implements xhr (https://xhr.spec.whatwg.org/) - this.imageRequest * * @param {File} file single input file (jpeg/jpg) * @param {object} params - params of image sent * @param {string} [params.quality_ratio = 100] a value between 0 and 100. 0 indicates 'maximum compression' and the lowest quality, 100 will result in the highest quality image * @param {string} [params.medium_size_ratio = 50] a value between 1 and 100. 1 indicates the new image is 1% of original, 100 - same size as original * @param {string} [params.thumbnail_size_ratio = 30] a value between 1 and 100. 1 indicates the new image is 1% of original, 100 - same size as original * * @returns {Promise<XMLHttpRequest>} * * @example <caption>uploading an image</caption> * const params = { * quality_ratio : "90", * medium_size_ratio: "40", * thumbnail_size_ratio: "20" * } * conversation.uploadImage(fileInput.files[0], params).then((uploadImageRequest) => { * uploadImageRequest.onprogress = (e) => { * console.log("Image request progress: ", e); * console.log("Image progress: " + e.loaded + "/" + e.total); * }; * uploadImageRequest.onabort = (e) => { * console.log("Image request aborted: ", e); * console.log("Image: " + e.type); * }; * uploadImageRequest.onloadend = (e) => { * console.log("Image request successful: ", e); * console.log("Image: " + e.type); * }; * uploadImageRequest.onreadystatechange = () => { * if (uploadImageRequest.readyState === 4 && uploadImageRequest.status === 200) { * const representations = JSON.parse(uploadImageRequest.responseText); * console.log("Original image url: ", representations.original.url); * console.log("Medium image url: ", representations.medium.url); * console.log("Thumbnail image url: ", representations.thumbnail.url); * } * }; * }).catch((error) => { * console.error("error uploading the image ", error); * }); */ async uploadImage(fileInput, params = { quality_ratio: '100', medium_size_ratio: '50', thumbnail_size_ratio: '30' }) { const formData = new FormData(); formData.append('file', fileInput); formData.append('quality_ratio', params.quality_ratio); formData.append('medium_size_ratio', params.medium_size_ratio); formData.append('thumbnail_size_ratio', params.thumbnail_size_ratio); const imageRequest = await utils_1.default.networkRequest({ type: 'POST', url: this.application.session.config.ips_url, data: formData, token: this.application.session.config.token }); imageRequest.upload.addEventListener('progress', (evt) => { if (evt.lengthComputable) { this.log.debug('uploading image ' + evt.loaded + '/' + evt.total); } }, false); imageRequest.onreadystatechange = () => { if (imageRequest.status !== 200) { this.log.error(imageRequest); } }; return imageRequest; } /** * Send an Image message to the conversation, which will be relayed to every other member of the conversation. * implements xhr (https://xhr.spec.whatwg.org/) - this.imageRequest * * @param {File} file single input file (jpeg/jpg) * @param {object} params - params of image sent * @param {string} [params.quality_ratio = 100] a value between 0 and 100. 0 indicates 'maximum compression' and the lowest quality, 100 will result in the highest quality image * @param {string} [params.medium_size_ratio = 50] a value between 1 and 100. 1 indicates the new image is 1% of original, 100 - same size as original * @param {string} [params.thumbnail_size_ratio = 30] a value between 1 and 100. 1 indicates the new image is 1% of original, 100 - same size as original * * @returns {Promise<XMLHttpRequest>} * * @example <caption>sending an image</caption> * const params = { * quality_ratio : "90", * medium_size_ratio: "40", * thumbnail_size_ratio: "20" * } * conversation.sendImage(fileInput.files[0], params).then((imageRequest) => { * imageRequest.onprogress = (e) => { * console.log("Image request progress: ", e); * console.log("Image progress: " + e.loaded + "/" + e.total); * }; * imageRequest.onabort = (e) => { * console.log("Image request aborted: ", e); * console.log("Image: " + e.type); * }; * imageRequest.onloadend = (e) => { * console.log("Image request successful: ", e); * console.log("Image: " + e.type); * }; * }).catch((error) => { * console.error("error sending the image ", error); * }); * * @deprecated since version 8.3.0 */ async sendImage(fileInput, params = { quality_ratio: '100', medium_size_ratio: '50', thumbnail_size_ratio: '30' }) { const imageRequest = await this.uploadImage(fileInput, params); imageRequest.onreadystatechange = () => { if (imageRequest.readyState === 4 && imageRequest.status === 200) { try { this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data: { type: 'image', from: this.me.id, body: { representations: JSON.parse(imageRequest.responseText) } } }); this.log.info(imageRequest); } catch (error) { this.log.error(new nexmoClientError_1.NexmoApiError(error)); } } }; return imageRequest; } /** * Cancel uploading or sending an Image message to the conversation. * * @param {XMLHttpRequest} imageRequest * * @returns void * * @example <caption>cancel sending an image</caption> * conversation.sendImage(fileInput.files[0]).then((imageRequest) => { * conversation.abortSendImage(imageRequest); * }).catch((error) => { * console.error("error sending the image ", error); * }); * * @example <caption>cancel uploading an image</caption> * conversation.uploadImage(fileInput.files[0]).then((imageRequest) => { * conversation.abortSendImage(imageRequest); * }).catch((error) => { * console.error("error uploading the image ", error); * }); */ abortSendImage(imageRequest) { if (imageRequest instanceof XMLHttpRequest) { return imageRequest.abort(); } else { return new nexmoClientError_1.NexmoClientError('error:invalid:param:type'); } } /** * Send a message event to the conversation, which will be relayed to every other member of the conversation * * @param {object} params the content of the message you want sent * @param {string} params.message_type the type of the message. It should be one of 'text', 'image', 'audio', 'video', 'file' * @param {string} [params.text] the text content when message type is 'text * @param {object} [params.image] * @param {string} params.image.url the image url when message type is 'image' * @param {object} [params.audio] * @param {string} params.audio.url the audio url when message type is 'audio' * @param {object} [params.video] * @param {string} params.video.url the video url when message type is 'video' * @param {object} [params.file] * @param {string} params.file.url the file url when message type is 'file' * * @returns {Promise<MessageEvent>} - the message that was sent * * @example <caption> sending a message </caption> * conversation.sendMessage({ "message_type": "text", "text": "Hi Vonage!" }).then((event) => { * console.log("message was sent", event); * }).catch((error)=>{ * console.error("error sending the message ", error); * }); * */ async sendMessage(params) { if (this.me === null) { throw new nexmoClientError_1.NexmoClientError('error:self'); } else if (!(params === null || params === void 0 ? void 0 : params.message_type)) { throw new nexmoClientError_1.NexmoClientError('error:message-event:invalid'); } try { const msg = { type: 'message', cid: this.id, from: this.me.id, body: { ...params } }; const { id, timestamp } = await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data: msg }); msg.id = id; msg.body.timestamp = timestamp; return new message_event_1.default(this, msg); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } async _typing(state) { const params = { activity: (state === 'on') ? 1 : 0 }; const data = { type: 'text:typing:' + state, cid: this.id, from: this.me.id, body: params }; try { await this.application.session.sendNetworkRequest({ type: 'POST', path: `conversations/${this.id}/events`, data }); return `text:typing:${state}:success`; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Send start typing indication * * @returns {Promise} - resolves the promise on successful sent * * @example <caption>send start typing event when key is pressed</caption> * messageTextarea.addEventListener('keypress', (event) => { * conversation.startTyping(); * }); */ startTyping() { return this._typing('on'); } /** * Send stop typing indication * * @returns {Promise} - resolves the promise on successful sent * * @example <caption>send stop typing event when a key has not been pressed for half a second</caption> * let timeout = null; * messageTextarea.addEventListener('keyup', (event) => { * clearTimeout(timeout); * timeout = setTimeout(() => { * conversation.stopTyping(); * }, 500); * }); */ stopTyping() { return this._typing('off'); } /** * Query the service to get a list of events in this conversation. * * @param {object} params configure defaults for paginated events query * @param {string} params.order 'asc' or 'desc' ordering of resources based on creation time * @param {number} params.page_size the number of resources returned in a single request list * @param {string} [params.cursor] string to access the starting point of a dataset * @param {string} [params.event_type] the type of event used to filter event requests. Supports wildcard options with :* eg. 'members:*' * * @returns {Promise<EventsPage<Map<Events>>>} - Populate Conversations.events. * @example <caption>Get Events</caption> * conversation.getEvents({ event_type: 'member:*' }).then((events_page) => { * events_page.items.forEach(event => { * render(event) * }) * }).catch((error) => { * console.error("error getting the events ", error); * }); */ async getEvents(params = {}) { const url = `${this.application.session.config.nexmo_api_url}/beta2/conversations/${this.id}/events`; // Create pageConfig if given params otherwise use default let pageConfig = Object.keys(params).length === 0 ? this.pageConfig : new page_config_1.default(params); try { const response = await utils_1.default.paginationRequest(url, pageConfig, this.application.session.config.token); response.application = this.application; response.conversation = this; const events_page = new events_page_1.default(response); this.events_page_last = events_page; return events_page; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Query the service to get a list of members in this conversation. * * @param {object} params configure defaults for paginated events query * @param {string} params.order 'asc' or 'desc' ordering of resources based on creation time * @param {number} params.page_size the number of resources returned in a single request list * @param {string} [params.cursor] string to access the starting point of a dataset * * @returns {Promise<MembersPage<Map<Member>>>} * @example <caption>Get Members</caption> * const params = { * order: "desc", * page_size: 100 * } * conversation.getMembers(params).then((members_page) => { * members_page.items.forEach(member => { * render(member) * }) * }).catch((error) => { * console.error("error getting the members ", error); * }); */ async getMembers(params = {}) { const url = `${this.application.session.config.nexmo_api_url}/beta2/conversations/${this.id}/members`; // Create pageConfig if given params otherwise use default let pageConfig = Object.keys(params).length === 0 ? this.pageConfig : new page_config_1.default(params); try { const response = await utils_1.default.paginationRequest(url, pageConfig, this.application.session.config.token); response.application = this.application; response.conversation = this; const members_page = new members_page_1.default(response); this.members_page_last = members_page; return members_page; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Query the service to get my member in this conversation. * * @returns {Promise<Member>} * @example <caption>Get My Member</caption> * conversation.getMyMember().then((member) => { * render(member) * }).catch((error) => { * console.error("error getting my member", error); * }); */ async getMyMember() { try { const response = await this.application.session.sendNetworkRequest({ type: 'GET', path: `conversations/${this.id}/members/me`, version: 'v0.3' }); const member = new member_1.default(this, response); return member; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Query the service to get a member in this conversation. * * @param {string} member_id the id of the member to return * * @returns {Promise<Member>} * @example <caption>Get Member</caption> * conversation.getMember("MEM-id").then((member) => { * render(member) * }).catch((error) => { * console.error("error getting member", error); * }); */ async getMember(member_id) { try { const response = await this.application.session.sendNetworkRequest({ type: 'GET', path: `conversations/${this.id}/members/${member_id}`, version: 'v0.3' }); const member = new member_1.default(this, response); return member; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Handle and event from the cloud. * using conversationEventHandler * @param {object} event * @private */ _handleEvent(event) { var _a, _b; if (event.type.startsWith('rtc')) { // keep the rtc events going to the application layer, we use them in media module this.emit(event.type, event); return Promise.resolve(event); } this.sequence_number++; // make sure the event_id is not a string if (event.body && event.body.event_id && typeof event.body.event_id === 'string') { event.body.event_id = parseInt(event.body.event_id); } let memberInfo = { memberId: event.from }; if ((_a = event === null || event === void 0 ? void 0 : event.body) === null || _a === void 0 ? void 0 : _a.user) { const { id, name, display_name, image_url, custom_data } = event.body.user; memberInfo = { ...memberInfo, ...{ ...(id && { userId: id }), ...(name && { userName: name }), ...(display_name && { displayName: display_name }), ...(image_url && { imageUrl: image_url }), ...(custom_data && { customData: custom_data }) } }; } else if ((_b = event === null || event === void 0 ? void 0 : event._embedded) === null || _b === void 0 ? void 0 : _b.from_user) { const { id, name, display_name, image_url, custom_data } = event._embedded.from_user; memberInfo = { ...memberInfo, ...{ ...(id && { userId: id }), ...(name && { userName: name }), ...(display_name && { displayName: display_name }), ...(image_url && { imageUrl: image_url }), ...(custom_data && { customData: custom_data }) } }; } let constructed_event = this.conversationEventHandler.handleEvent(event); // Unless they are typing events, add the event to the conversation.events map if (!['text:typing:on', 'text:typing:off', 'ephemeral'].includes(event.type)) { this.events.set(constructed_event.id, constructed_event); } // For custom events remove the custom: prefix before emitting event if (event.type.startsWith('custom:')) { this.emit(constructed_event.type, memberInfo, constructed_event); return Promise.resolve(event); } this.emit(event.type, memberInfo, constructed_event); return Promise.resolve(event); } } exports.default = Conversation; module.exports = Conversation;