UNPKG

fc-nexmo-client1

Version:
1,139 lines (1,138 loc) 2.21 MB
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ '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 * Application Object Model * * Copyright (c) Nexmo Inc. */ const WildEmitter = require('wildemitter'); const loglevel_1 = require("loglevel"); const nexmoClientError_1 = require("./nexmoClientError"); const user_1 = __importDefault(require("./user")); const conversation_1 = __importDefault(require("./conversation")); const nxmCall_1 = __importDefault(require("./modules/nxmCall")); const sip_events_1 = __importDefault(require("./handlers/sip_events")); const rtc_events_1 = __importDefault(require("./handlers/rtc_events")); const application_events_1 = __importDefault(require("./handlers/application_events")); const utils_1 = __importDefault(require("./utils")); const page_config_1 = __importDefault(require("./pages/page_config")); const conversations_page_1 = __importDefault(require("./pages/conversations_page")); const user_sessions_page_1 = __importDefault(require("./pages/user_sessions_page")); const events_queue_1 = require("./handlers/events_queue"); const member_1 = __importDefault(require("./member")); let sipEventHandler = null; let rtcEventHandler = null; let applicationEventsHandler = null; /** * Core application class for the SDK. * Application is the parent object holding the list of conversations, the session object. * Provides methods to create conversations and retrieve a list of the user's conversations, while it holds the listeners for * user's invitations * @class Application * @param {NexmoClient} SDK session Object * @param {object} params * @example <caption>Accessing the list of conversations</caption> * rtc.createSession(token).then((application) => { * console.log(application.conversations); * console.log(application.me.name, application.me.id); * }).catch((error) => { * console.error(error); * }); * @emits Application#member:invited * @emits Application#member:joined * @emits Application#NXM-errors * @emits Application#rtcstats:analytics */ class Application { constructor(session, params) { this.log = loglevel_1.getLogger(this.constructor.name); this.session = session; this.conversations = new Map(); this.synced_conversations_count = 0; this.start_sync_time = 0; this.stop_sync_time = 0; // conversation_id, nxmCall this.calls = new Map(); // knocking_id, nxmCall this._call_draft_list = new Map(); this.pageConfig = new page_config_1.default((session.config || {}).conversations_page_config); this.conversations_page_last = null; this.activeStreams = []; sipEventHandler = new sip_events_1.default(this); rtcEventHandler = new rtc_events_1.default(this); applicationEventsHandler = new application_events_1.default(this); this.me = null; Object.assign(this, params); WildEmitter.mixin(Application); } /** * Update Conversation instance or create a new one. * * Pre-created conversation exist from getConversations * like initialised templates. When we explicitly ask to * getConversation(), we receive members and other details * * @param {object} payload Conversation payload * @private */ updateOrCreateConversation(payload) { const conversation = this.conversations.get(payload.id); if (conversation) { conversation._updateObjectInstance(payload); this.conversations.set(payload.id, conversation); } else { this.conversations.set(payload.id, new conversation_1.default(this, payload)); } return this.conversations.get(payload.id); } /** * Application listening for member invited events. * * @event Application#member:invited * * @property {Member} member - The invited member * @property {NXMEvent} event - The invitation event * * @example <caption>listen for member invited events on Application level</caption> * application.on("member:invited",(member, event) => { * console.log("Invited to the conversation: " + event.conversation.display_name || event.conversation.name); * // identify the sender. * console.log("Invited by: " + member.invited_by); * //accept an invitation. * application.conversations.get(event.conversation.id).join(); * //decline the invitation. * application.conversations.get(event.conversation.id).leave(); * }); */ /** * Application listening for member joined events. * * @event Application#member:joined * * @property {Member} member - the member that joined the conversation * @property {NXMEvent} event - the join event * * @example <caption>listen for member joined events on Application level</caption> * application.on("member:joined",(member, event) => { * console.log("JOINED", "Joined conversation: " + event.conversation.display_name || event.conversation.name); * }); */ /** * Entry point for queing events in Application level * @private */ async _enqueueEvent(response) { if (this.session.config.enableEventsQueue) { if (!this.eventsQueue) { this.eventsQueue = new events_queue_1.EventsQueue((event) => this._handleEvent(event)); } this.eventsQueue.enqueue(response, this); } else { this._handleEvent(response); } } /** * Entry point for events in Application level * @private */ async _handleEvent(event) { var _a, _b, _c, _d, _e, _f, _g; const isEventFromMe = ((_a = event._embedded) === null || _a === void 0 ? void 0 : _a.from_user) ? ((_c = (_b = event._embedded) === null || _b === void 0 ? void 0 : _b.from_user) === null || _c === void 0 ? void 0 : _c.id) === ((_d = this.me) === null || _d === void 0 ? void 0 : _d.id) : ((_f = (_e = event.body) === null || _e === void 0 ? void 0 : _e.user) === null || _f === void 0 ? void 0 : _f.user_id) === ((_g = this.me) === null || _g === void 0 ? void 0 : _g.id); // check if user is already part of the conversation and if it has a member on a valid // state (INVITED, JOINED) otherwise user is being re-invited and we need to fetch the // conversation and members info again const isUserReInvited = utils_1.default._checkIfUserIsReInvited(this.conversations, event); if (event.type.startsWith('sip')) { sipEventHandler._handleSipCallEvent(event); return event; } if (this.conversations.has(event.cid) && event.type !== "rtc:transfer" && !isUserReInvited) { if (event.type.startsWith('rtc')) { rtcEventHandler._handleRtcEvent(event); } this.conversations.get(event.cid)._handleEvent(event); if ((event.type === 'member:joined' || event.type === 'member:invited') && isEventFromMe) { this._handleApplicationEvent(event); } return event; } else { // if event has cid get the conversation you don't know about (case: joined by another user) if (event.cid) { try { if (isUserReInvited) this.conversations.delete(event.cid); let conversation; if (utils_1.default._isCallEvent(event)) { conversation = await this.getConversation(event.cid, Application.CONVERSATION_API_VERSION.v1); } else { conversation = await this.getConversation(event.cid, Application.CONVERSATION_API_VERSION.v3); } this.conversations.set(event.cid, conversation); await conversation._handleEvent(event); await this._handleApplicationEvent(event); if (event.type.startsWith("rtc")) { rtcEventHandler._handleRtcEvent(event); } return Promise.resolve(event); } catch (error) { this.log.error(error); return Promise.reject(error); } } } } /** * Update user's token that was generated when they were first authenticated. * @param {string} token - the new token * @returns {Promise} * @example <caption>listen for expired-token error events and then update the token on Application level</caption> * application.on('system:error:expired-token', 'NXM-errors', (error) => { * console.log('token expired'); * application.updateToken(token); * }); */ async updateToken(token) { // SDK can be disconnected because of expired token // this lets us update token for next reconnection attempt if (this.session.connection && this.session.connection.disconnected) { this.session.config.token = token; this.session.connection.io.opts.query.token = token; return Promise.resolve(); } const reqObj = { url: `${this.session.config.nexmo_api_url}/v0.2/sessions/${this.session.session_id}`, type: 'PUT', token }; try { await utils_1.default.networkRequest(reqObj); if (this.me) { this.session.config.token = token; this.session.connection.io.opts.query.token = token; } } catch (error) { throw (new nexmoClientError_1.NexmoApiError(error)); } } /** * Update the event to map local generated events * in case we need a more specific event to pass in the application listener * or f/w the event as it comes * @private */ async _handleApplicationEvent(event) { try { this.log.debug("_handleApplicationEvent: ", { event }); const processed_event = applicationEventsHandler.handleEvent(event); const conversation = this.conversations.get(event.cid); let member; if (conversation.members.has((processed_event || {}).from)) { member = conversation.members.get(processed_event.from); } else if (event.type === 'member:joined' || event.type === 'member:invited') { const params = { ...event.body, ...(event.from && { member_id: event.from }) }; member = new member_1.default(conversation, params); } else { try { member = await conversation.getMember(processed_event.from); } catch (error) { this.log.warn(`There is an error getting the member ${error}`); } } this.emit(processed_event.type, member, processed_event); return event; } catch (e) { this.log.error("_handleApplicationEvent: ", e); throw (e); } } /** * Creates a call to specified user/s. * @classdesc creates a call between the defined users * @param {string[]} usernames - the user names for those we want to call * @returns {Promise<NXMCall>} a NXMCall object with all the call properties * @example <caption>Create a call with users</caption> * application.on("call:status:changed", (nxmCall) => { * if (nxmCall.status === nxmCall.CALL_STATUS.STARTED) { * console.log('the call has started'); * } * }); * * application.inAppCall(usernames).then(() => { * console.log('Calling user(s)...'); * }).catch((error) => { * console.error(error); * }); */ async inAppCall(usernames) { if (!usernames || !Array.isArray(usernames) || usernames.length === 0) { return Promise.reject(new nexmoClientError_1.NexmoClientError('error:application:call:params')); } try { const nxmCall = new nxmCall_1.default(this); await nxmCall.createCall(usernames); nxmCall.direction = nxmCall.CALL_DIRECTION.OUTBOUND; return nxmCall; } catch (error) { throw error; } } /** * Creates a call to phone a number. * The call object is created under application.calls when the call has started. * listen for it with application.on("call:status:changed") * * You don't need to start the stream, the SDK will play the audio for you * * @classdesc creates a call to a phone number * @param {string} user the phone number or the username you want to call * @param {string} [type="phone"] the type of the call you want to have. possible values "phone" or "app" (default is "phone") * @param {object} [custom_data] custom data to be included in the call object, i.e. { yourCustomKey: yourCustomValue } * @returns {Promise<NXMCall>} * @example <caption>Create a call to a phone</caption> * application.on("call:status:changed", (nxmCall) => { * if (nxmCall.status === nxmCall.CALL_STATUS.STARTED) { * console.log('the call has started'); * } * }); * * application.callServer(phone_number).then((nxmCall) => { * console.log('Calling phone ' + phone_number); * console.log('Call Object ': nxmCall); * }).catch((error) => { * console.error(error); * }); */ async callServer(user, type = 'phone', custom_data = {}, ringingMuted = false) { try { const nxmCall = new nxmCall_1.default(this); nxmCall.direction = nxmCall.CALL_DIRECTION.OUTBOUND; await nxmCall.createServerCall(user, type, custom_data, ringingMuted); return nxmCall; } catch (error) { throw error; } } /** * Reconnect a leg to an ongoing call. * You don't need to start the stream, the SDK will play the audio for you * * @classdesc reconnect leg to an ongoing call * @param {string} conversation_id the conversation that you want to reconnect * @param {string} rtc_id the id of the leg that will be reconnected * @param {object} [mediaParams] - MediaStream params (same as Media.enable()) * @returns {Promise<NXMCall>} * @example <caption>Reconnect a leg to an ongoing call</caption> * application.reconnectCall("conversation_id", "rtc_id").then((nxmCall) => { * console.log(nxmCall); * }).catch((error) => { * console.error(error); * }); * * @example <caption>Reconnect a leg to an ongoing call without auto playing audio</caption> * application.reconnectCall("conversation_id", "rtc_id", { autoPlayAudio: false }).then((nxmCall) => { * console.log(nxmCall); * }).catch((error) => { * console.error(error); * }); * * @example <caption>Reconnect a leg to an ongoing call choosing device ID</caption> * application.reconnectCall("conversation_id", "rtc_id", { audioConstraints: { deviceId: "device_id" } }).then((nxmCall) => { * console.log(nxmCall); * }).catch((error) => { * console.error(error); * }); */ async reconnectCall(conversationId, rtcId, mediaParams = {}) { try { if (!conversationId || !rtcId) { throw new nexmoClientError_1.NexmoClientError('error:missing:params'); } const conversation = await this.getConversation(conversationId, Application.CONVERSATION_API_VERSION.v1); await conversation.media.enable({ ...mediaParams, reconnectRtcId: rtcId }); const nxmCall = new nxmCall_1.default(this, conversation); // assigning the correct call status taking into account the sip status (outbound) // on inbound calls the reconnect will happen after the call is estabilished and both legs are answered const event_types = Array.from(conversation.events.values()).map(event => event.type); if (event_types.includes('sip:answered')) nxmCall.status = nxmCall.CALL_STATUS.ANSWERED; else if (event_types.includes('sip:ringing')) nxmCall.status = nxmCall.CALL_STATUS.RINGING; else nxmCall.status = nxmCall.CALL_STATUS.STARTED; nxmCall.rtcObjects = conversation.media.rtcObjects; this.calls.set(conversation.id, nxmCall); return nxmCall; } catch (error) { throw error; } } /** * Query the service to create a new conversation * The conversation name must be unique per application. * @param {object} [params] - leave empty to get a GUID as name * @param {string} params.name - the name of the conversation. A UID will be assigned if this is skipped * @param {string} params.display_name - the display_name of the conversation. * @returns {Promise<Conversation>} - the created Conversation * @example <caption>Create a conversation and join</caption> * application.newConversation().then((conversation) => { * //join the created conversation * conversation.join().then((member) => { * //Get the user's member belonging in this conversation. * //You can also access it via conversation.me * console.log("Joined as " + member.user.name); * }); * }).catch((error) => { * console.error(error); * }); */ async newConversation(data = {}) { try { const response = await this.session.sendNetworkRequest({ type: 'POST', path: 'conversations', data }); const conv = new conversation_1.default(this, response); this.conversations.set(conv.id, conv); // do a get conversation to get the whole model as shaped in the service, return this.getConversation(conv.id, Application.CONVERSATION_API_VERSION.v1); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Query the service to create a new conversation and join it * The conversation name must be unique per application. * @param {object} [params] - leave empty to get a GUID as name * @param {string} params.name - the name of the conversation. A UID will be assigned if this is skipped * @param {string} params.display_name - the display_name of the conversation. * @returns {Promise<Conversation>} - the created Conversation * @example <caption>Create a conversation and join</caption> * application.newConversationAndJoin().then((conversation) => { * console.log("Joined as " + conversation.me.display_name); * }).catch((error) => { * console.error("Error creating a conversation and joining ", error); * }); */ async newConversationAndJoin(params) { const conversation = await this.newConversation(params); await conversation.join(); return conversation; } /** * Query the service to see if this conversation exists with the * logged in user as a member and retrieve the data object * Result added (or updated) in this.conversations * * @param {string} id - the id of the conversation to fetch * @param {string} version=Application.CONVERSATION_API_VERSION.v3 {Application.CONVERSATION_API_VERSION.v1 || Application.CONVERSATION_API_VERSION.v3} - the version of the Conversation Service API to use (v1 includes the full list of the members of the conversation but v3 does not) * @returns {Promise<Conversation>} - the requested conversation * @example <caption>Get a conversation</caption> * application.getConversation(id).then((conversation) => { * console.log("Retrieved conversation: ", conversation); * }).catch((error) => { * console.error(error); * }); */ async getConversation(id, version = Application.CONVERSATION_API_VERSION.v3) { if (version !== Application.CONVERSATION_API_VERSION.v1 && version !== Application.CONVERSATION_API_VERSION.v3) { throw new nexmoClientError_1.NexmoClientError('error:conversation-service:version'); } let response; if (version === Application.CONVERSATION_API_VERSION.v1) { try { response = await this.session.sendNetworkRequest({ type: 'GET', path: `conversations/${id}` }); response['id'] = response['uuid']; delete response['uuid']; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } else { try { response = await this.session.sendNetworkRequest({ type: 'GET', path: `conversations/${id}`, version: 'v0.3' }); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } const conversation_object = this.updateOrCreateConversation(response); if (version === Application.CONVERSATION_API_VERSION.v3 && !conversation_object.me) { try { const member = await conversation_object.getMyMember(); conversation_object.me = member; conversation_object.members.set(member.id, member); } catch (error) { // add a retry in case of a failure in fetching the member try { const member = await conversation_object.getMyMember(); conversation_object.me = member; conversation_object.members.set(member.id, member); } catch (error) { this.log.warn(`You don't have any membership in ${conversation_object.id}`); } } } if (this.session.config.sync === 'full') { // Populate the events const { items } = await conversation_object.getEvents(); conversation_object.events = items; return conversation_object; } else { return conversation_object; } } /** * Query the service to obtain a complete list of conversations of which the * logged-in user is a member with a state of `JOINED` or `INVITED`. * @param {object} params configure defaults for paginated conversations 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<Page<Map<Conversation>>>} - Populate Application.conversations. * @example <caption>Get Conversations</caption> * application.getConversations({ page_size: 20 }).then((conversations_page) => { * conversations_page.items.forEach(conversation => { * render(conversation) * }) * }).catch((error) => { * console.error(error); * }); * */ async getConversations(params = {}) { const url = `${this.session.config.nexmo_api_url}/beta2/users/${this.me.id}/conversations`; // Create pageConfig if some elements given 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.session.config.token); response.application = this; const conversations_page = new conversations_page_1.default(response); this.conversations_page_last = conversations_page; return conversations_page; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Application listening for sync status events. * * @event Application#sync:progress * * @property {number} status.sync_progress - Percentage of fetched conversations * @example <caption>listen for changes in the synchronisation progress events on Application level</caption> * application.on("sync:progress",(status) => { * console.log(status.sync_progress); * }); */ /** * Fetching all the conversations and sync progress events */ syncConversations(conversations) { const conversation_array = Array.from(conversations.values()); const conversations_length = conversation_array.length; const d = new Date(); this.start_sync_time = (typeof window !== 'undefined' && window.performance) ? window.performance.now() : d.getTime(); const fetchConversationForStorage = async () => { this.synced_conversations_percentage = Number(((this.synced_conversations_count / conversations_length) * 100).toFixed(2)); const status_payload = { sync_progress: this.synced_conversations_percentage }; this.emit('sync:progress', status_payload); this.log.info('Loading sync progress: ' + this.synced_conversations_count + '/' + conversations_length + ' - ' + this.synced_conversations_percentage + '%'); if (this.synced_conversations_percentage >= 100) { const d = new Date(); this.stop_sync_time = (typeof window !== 'undefined' && window.performance) ? window.performance.now() : d.getTime(); this.log.info('Loaded conversations in ' + (this.stop_sync_time - this.start_sync_time) + 'ms'); } if (this.synced_conversations_count < conversations_length) { await this.getConversation(conversation_array[this.synced_conversations_count].id); fetchConversationForStorage(); this.synced_conversations_count++; this.sync_progress_buffer++; } }; fetchConversationForStorage(); } /** * Get Details of a user by using their id. If no id is present, will return your own user details. * @param {string} id - the id of the user to fetch, if skipped, it returns your own user details * @returns {Promise<User>} * @example <caption>Get User details</caption> * application.getUser(id).then((user) => { * console.log('User details: 'user); * }).catch((error) => { * console.error(error); * }); */ async getUser(user_id = this.me.id) { try { const response = await this.session.sendNetworkRequest({ type: 'GET', path: `users/${user_id}` }); return new user_1.default(this, response); } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } /** * Query the service to obtain a complete list of userSessions of a given user * @param {object} params configure defaults for paginated user sessions 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.user_id] the user id that the sessions are being fetched * * @returns {Promise<Page<Map<UserSession>>>} * @example <caption>Get User Sessions</caption> * application.getUserSessions({ user_id: "id", page_size: 20 }).then((user_sessions_page) => { * user_sessions_page.items.forEach(user_session => { * render(user_session) * }) * }).catch((error) => { * console.error(error); * }); * */ async getUserSessions(params = {}) { var _a; const user_id = ((_a = params) === null || _a === void 0 ? void 0 : _a.user_id) || this.me.id; const url = `${this.session.config.nexmo_api_url}/v0.3/users/${user_id}/sessions`; // Create pageConfig if some elements given 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.session.config.token, Application.CONVERSATION_API_VERSION.v3); response.application = this; const user_sessions_page = new user_sessions_page_1.default(response); this.user_sessions_page_last = user_sessions_page; return user_sessions_page; } catch (error) { throw new nexmoClientError_1.NexmoApiError(error); } } } exports.default = Application; /** * Enum for Application getConversation version. * @readonly * @enum {string} * @alias Application.CONVERSATION_API_VERSION */ Application.CONVERSATION_API_VERSION = { v1: 'v0.1', v3: 'v0.3' }; module.exports = Application; },{"./conversation":2,"./handlers/application_events":7,"./handlers/events_queue":9,"./handlers/rtc_events":10,"./handlers/sip_events":11,"./member":13,"./modules/nxmCall":16,"./nexmoClientError":20,"./pages/conversations_page":22,"./pages/page_config":26,"./pages/user_sessions_page":27,"./user":29,"./utils":31,"loglevel":63,"wildemitter":107}],2:[function(require,module,exports){ '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); } } /** * 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, d