"use strict"; import {ContactsService} from "./ContactsService"; export {}; import {XMPPService} from "../connection/XMPPService"; import {RESTService} from "../connection/RESTService"; import {ErrorManager} from "../common/ErrorManager"; import {Conversation} from "../common/models/Conversation"; import {Call} from "../common/models/Call"; import {Deferred, isDefined, logEntryExit, randomString} from "../common/Utils"; import * as PubSub from "pubsub-js"; import {ConversationEventHandler} from "../connection/XMPPServiceHandler/conversationEventHandler"; import {ConversationHistoryHandler} from "../connection/XMPPServiceHandler/conversationHistoryHandler"; import {shortnameToUnicode} from "../common/Emoji"; import {FileViewerElementFactory as fileViewerElementFactory} from "../common/models/FileViewer"; import {isStarted} from "../common/Utils"; import {BubblesService} from "./BubblesService"; import {FileStorageService} from "./FileStorageService"; import {FileServerService} from "./FileServerService"; import {Logger} from "../common/Logger"; import {EventEmitter} from "events"; import {Contact} from "../common/models/Contact"; import {S2SService} from "./S2SService"; import {Core} from "../Core"; import {PresenceService} from "./PresenceService"; import {Message} from "../common/models/Message"; import bubble, {Bubble} from "../common/models/Bubble"; import {GenericService} from "./GenericService"; import {use} from "chai"; import {error} from "winston"; import moment, {lang} from "moment"; import {setInterval} from "timers"; import {DataStoreType} from "../config/config.js"; const LOG_ID = "CONVERSATIONS/SVCE - "; const API_ID = "API_CALL - "; @logEntryExit(LOG_ID) @isStarted([]) /** * @module * @name ConversationsService * @version SDKVERSION * @public * @description * This module is the basic module for handling conversations in Rainbow. In Rainbow, conversations are the way to get in touch with someone or something (i.e. a Rainbow contact, a external phone number, a connected thing, ...) so a conversation is the "long tail" of communication between you and someone or something else like a bubble.
* A Rainbow conversation by default supports sending and receiving Instant Messages with a single recipient (one-to-one conversation) or with several persons (bubble). Using the FileStorage service, you can share files in conversations.
*
* The main methods and events proposed in that service allow to:
* - Create or close a Rainbow conversation (one-to-one of bubble),
* - Get all conversations or get a conversation by Id, bubbleID or bubbleJid
* - Retrieve all information linked to that conversation,
*
* */ class ConversationsService extends GenericService { get conversationHistoryHandler(): ConversationHistoryHandler { return this._conversationHistoryHandler; } get pendingMessages(): any { return this._pendingMessages; } private _contactsService: ContactsService; private _fileStorageService: FileStorageService; private _fileServerService: FileServerService; private _presenceService: PresenceService; private _pendingMessages: any; private _conversationEventHandler: ConversationEventHandler; private _conversationHandlerToken: any; private _conversationHistoryHandlerToken: any; public conversations: any; // { jid : Conversation } private _conversationServiceEventHandler: any; private _bubblesService: BubblesService; public activeConversation: any; public inCallConversations: any; public idleConversations: any; public involvedContactIds: any; public involvedRoomIds: any; public waitingBotConversations: any; public botServiceReady: any; private _conversationHistoryHandler: ConversationHistoryHandler; private conversationsRetrievedFormat: string = "small"; private nbMaxConversations: number; private autoLoadConversations: boolean; private autoLoadConversationHistory: boolean; private storeMessagesInConversation: boolean; get startConfig(): { start_up: boolean; optional: boolean } { return this._startConfig; } static getClassName(){ return 'ConversationsService'; } getClassName(){ return ConversationsService.getClassName(); } static getAccessorName(){ return 'conversations'; } getAccessorName(){ return ConversationsService.getAccessorName(); } constructor(_core:Core, _eventEmitter : EventEmitter, _logger : Logger, _startConfig: { start_up:boolean, optional:boolean }, _conversationsRetrievedFormat : string, _nbMaxConversations : number,_autoLoadConversations: boolean, _autoLoadConversationHistory: boolean) { super(_logger, LOG_ID, _eventEmitter); this.setLogLevels(this); this._startConfig = _startConfig; this._xmpp = null; this._rest = null; this._s2s = null; this._options = {}; this._useXMPP = false; this._useS2S = false; this._contactsService = null; this._fileStorageService = null; this._fileServerService = null; this._eventEmitter = _eventEmitter; this._logger = _logger; this._core = _core; this._pendingMessages = {}; this._conversationEventHandler = null; this._conversationHandlerToken = []; this._conversationHistoryHandlerToken = []; this.conversationsRetrievedFormat = _conversationsRetrievedFormat; this.nbMaxConversations = _nbMaxConversations; this.autoLoadConversations = _autoLoadConversations; this.autoLoadConversationHistory = _autoLoadConversationHistory; //that._eventEmitter.removeListener("evt_internal_onreceipt", that._onReceipt.bind(that)); this._eventEmitter.on("evt_internal_onreceipt", this._onReceipt.bind(this)); } start(_options) { // , _xmpp : XMPPService, _s2s : S2SService, _rest : RESTService, _contacts : ContactsService, _bubbles : BubblesService, _fileStorageService : FileStorageService, _fileServerService : FileServerService let that = this; that.initStartDate(); that._conversationHandlerToken = []; that._conversationHistoryHandlerToken= []; return new Promise((resolve, reject) => { try { that._xmpp = that._core._xmpp; that._rest = that._core._rest; that._options = _options; that.storeMessagesInConversation = _options._imOptions.storeMessagesInConversation; that._s2s = that._core._s2s; that._useXMPP = that._options.useXMPP; that._useS2S = that._options.useS2S; that._contactsService = that._core.contacts; that._bubblesService = that._core.bubbles; that._fileStorageService = that._core.fileStorage; that._fileServerService = that._core.fileServer; that._presenceService = that._core.presence; that.activeConversation = null; that.conversations = {}; that.inCallConversations = []; that.idleConversations = []; that.involvedContactIds = []; that.involvedRoomIds = []; //all conversations with Bots that.waitingBotConversations = []; that.botServiceReady = false; that.attachHandlers(); that.setStarted (); resolve(undefined); } catch (err) { that._logger.log(that.ERROR, LOG_ID + "(start) !!! Catch error."); that._logger.log(that.INTERNALERROR, LOG_ID + "(start) !!! Catch error : ", err); return reject(err); } }); } stop() { let that = this; return new Promise((resolve, reject) => { try { that._xmpp = null; that._rest = null; delete that._conversationEventHandler; that._conversationEventHandler = null; if (that._conversationHandlerToken) { that._conversationHandlerToken.forEach((token) => PubSub.unsubscribe(token)); } that._conversationHandlerToken = []; if (that._conversationHistoryHandlerToken) { that._conversationHistoryHandlerToken.forEach((token) => PubSub.unsubscribe(token)); } that._conversationHistoryHandlerToken = []; //that._eventEmitter.removeListener("evt_internal_onreceipt", that._onReceipt.bind(that)); that.setStopped (); resolve(undefined); } catch (err) { return reject(err); } }); } async init (useRestAtStartup : boolean) { let that = this; that.setInitialized(); } attachHandlers() { let that = this; that._conversationEventHandler = new ConversationEventHandler(that._xmpp, that, that._options?._imOptions, that._fileStorageService, that._fileServerService, that._bubblesService, that._contactsService, that._presenceService); that._conversationHandlerToken = [ //PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE, that._conversationEventHandler.onMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_CHAT, that._conversationEventHandler.onChatMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_GROUPCHAT, that._conversationEventHandler.onChatMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE, that._conversationEventHandler.onMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_WEBRTC, that._conversationEventHandler.onWebRTCMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_MANAGEMENT, that._conversationEventHandler.onManagementMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_ERROR, that._conversationEventHandler.onErrorMessageReceived.bind(that._conversationEventHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationEventHandler.MESSAGE_CLOSE, that._conversationEventHandler.onCloseMessageReceived.bind(that._conversationEventHandler)) ]; that._conversationHistoryHandler = new ConversationHistoryHandler(that._xmpp, that, that._contactsService, that._options); that._conversationHistoryHandlerToken = [ PubSub.subscribe( that._xmpp.hash + "." + that._conversationHistoryHandler.MESSAGE, that._conversationHistoryHandler.onMessageReceived.bind(that._conversationHistoryHandler)), /* PubSub.subscribe( that._xmpp.hash + "." + that._conversationHistoryHandler.MESSAGE_MAM, that._conversationHistoryHandler.onMamMessageReceived.bind(that._conversationHistoryHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationHistoryHandler.FIN_MAM, that._conversationHistoryHandler.onMamMessageReceived.bind(that._conversationHistoryHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationHistoryHandler.MESSAGE_MAM_BULK, that._conversationHistoryHandler.onMamMessageReceived.bind(that._conversationHistoryHandler)), PubSub.subscribe( that._xmpp.hash + "." + that._conversationHistoryHandler.FIN_MAM_BULK, that._conversationHistoryHandler.onMamMessageReceived.bind(that._conversationHistoryHandler)), // */ ]; } async _onReceipt(receipt) { let that = this; let messageInfo = this._pendingMessages[receipt.id]; if (messageInfo && messageInfo.message) { let data = messageInfo.message; let conversation = messageInfo.conversation; let message: Message = await Message.create( null, null, data.id, data.type, data.date, data.fromJid, Message.Side.RIGHT, data.status, Message.ReceiptStatus.NONE, !!data.isMarkdown, data.subject, data.geoloc, data.voiceMessage, data.alternativeContent, data.attention, data.mentions, data.urgency, data.urgencyAck, data.urgencyHandler, //data.translatedText, //data.isMerged, data.historyIndex, //data.showCorrectedMessages, //data.replaceMsgs, data.attachedMsgId, data.attachIndex, data.attachNumber, data.resource, data.toJid, data.content, data.lang, data.cc, data.cctype, data.isEvent, data.event, data.oob, data.fromBubbleJid, data.fromBubbleUserJid, data.answeredMsg, data.answeredMsgId, data.answeredMsgDate, data.answeredMsgStamp, data.eventJid, data.originalMessageReplaced, data.confOwnerId, data.confOwnerDisplayName, data.confOwnerJid, data.isForwarded, data.forwardedMsg ); //that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) with data Message : ", data); message.updateMessage(data); //that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) with data updated Message : ", data); that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) Receive server ack (" + conversation.id + ", " + message.id + ")"); that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) Receive server ack (" + conversation.id + ", " + message.id + ") : ", conversation); //message.setReceiptStatus(Message.ReceiptStatus.SENT); if (conversation && conversation.id) { conversation = await that.getConversationById(conversation.id); that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) getConversationById conversation received."); // that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) getConversationById method result : ", conversation); if (conversation) { message.conversation = conversation; if (conversation.addOrUpdateMessage) { that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) conversation.addOrUpdateMessage."); that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) conversation.addOrUpdateMessage : ", message); if (that.storeMessagesInConversation) { conversation.addOrUpdateMessage(message); } that.removePendingMessage(message); //delete this.pendingMessages[message.id]; // Send event that._eventEmitter.emit("evt_internal_conversationupdated", conversation); //that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) after sent evt_internal_conversationupdated, conversations : ", that.getConversations()); } else { that._logger.log(that.ERROR, LOG_ID + "(_onReceipt) Error addMessage method not defined in Conversation, so message not added to conversation (" + conversation.id, ") : ", conversation); } } else { that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) conversation unknown can not store the pending message to conversation : ", conversation); // that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) conversation unknown can not store the pending message to conversation : ", conversation); } } else { that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) conversation not saved with pending message."); that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) conversation not saved with pending message : ", conversation); } } else { that._logger.log(that.DEBUG, LOG_ID + "(_onReceipt) Receive server ack message not found."); that._logger.log(that.INTERNAL, LOG_ID + "(_onReceipt) Receive server ack message not found : ", messageInfo); } } sortFunction (aa, bb) { let aLast = aa.lastModification; let aCreation = aa.creationDate; let bLast = bb.lastModification; let bCreation = bb.creationDate; let aDate: any; let bDate: any; //get the most recent of the creation date or the last message date if (!aLast && aCreation) { aDate = aCreation; } else { aDate = aLast; } if (!bLast && bCreation) { bDate = bCreation; } else { bDate = bLast; } return (bDate - aDate); }; /* formatDate (date){ return moment(date).utc().format("YYYY-MM-DDTHH:mm:ss") + "Z"; }; // */ /** * @private * @method * @instance * @description * Get a pstn conference
*/ getRoomConferences(conversation) { let that = this; return new Promise((resolve) => { let confEndpoints = conversation.bubble.confEndpoints; if (confEndpoints) { confEndpoints.forEach(function(confEndpoint) { if (confEndpoint.mediaType === "pstnAudio") { // TODO later // let conferenceSession = pstnConferenceService.getConferenceSessionById(confEndpoint.confEndpointId); // if (conferenceSession) { // conversation.pstnConferenceSession = conferenceSession; // } } }); } resolve(undefined); }); } /** * @private * @method * @instance * @description * Update a pstn conference
*/ updateRoomConferences() { let that = this; let conversations = that.getConversations(); conversations.forEach(function(conversation) { if (conversation.bubble && conversation.bubble.confEndpoints) { // TODO Later // let conferenceSession = pstnConferenceService.getConferenceSessionById(conversation.bubble.getPstnConfEndpointId()); // if (conferenceSession) { // conversation.pstnConferenceSession = conferenceSession; // } else { // conversation.pstnConferenceSession = null; // } } else { // A room conversation without confEndpoint should not have a conferenceSession attached conversation.pstnConferenceSession = null; } }); } //region MESSAGES /*********************************************************/ /** MESSAGES STUFF **/ /*********************************************************/ /** * @public * @nodered true * @method ackAllMessages * @instance * @category MESSAGES * @description * Mark all unread messages in the conversation as read.
* @param {string} conversationDbId ID of the conversation (dbId field) * @param {boolean} maskRead=false if true Im won't be shown as read on peer conversation side. Default value : false * @async * @return {Promise} * @fulfil {Conversation[]} - Array of Conversation object * @category async */ ackAllMessages(conversationDbId : string, maskRead : boolean = false) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getAll) is conversationDbId defined : ", isDefined(conversationDbId)); return this._rest.ackAllMessages(conversationDbId, maskRead ); } resetHistoryPageForConversation(conversation : Conversation) { let that = this; if (!conversation) { that._logger.log(that.DEBUG, LOG_ID + "(resetHistoryPageForConversation) undefined conversation.id so no reset done."); return; } that._logger.log(that.DEBUG, LOG_ID + "(resetHistoryPageForConversation) id : ", conversation.id, ", dbid : ", conversation.dbId); conversation.resetHistory(); } /** * @public * @method getHistoryPage * @instance * @category MESSAGES * @description * Retrieve the remote history of a specific conversation.
* @param {Conversation} conversation Conversation to retrieve * @param {number} size=30 Maximum number of element to retrieve * @param {boolean} useBulk=false Does the history should be retrieved with a bulk (group) of messages * @async * @return {Promise} * @fulfil {Conversation[]} - Array of Conversation object * @category async */ async getHistoryPage(conversation : Conversation, size: number = 30, useBulk: boolean = false) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getHistoryPage) conversation.id : ", conversation?.id); // if conversation.historyDefered is already exist, then a getHistory is yet running, wait before calling it. if (conversation.historyDefered && conversation.historyDefered.promise) { that._logger.log(that.DEBUG, LOG_ID + "(getHistoryPage) conversation.historyDefered already defined, wait for end promise's treatment."); await conversation.historyDefered.promise; that._logger.log(that.DEBUG, LOG_ID + "(getHistoryPage) conversation.historyDefered already defined, promise's treatment Ended."); } // Avoid to call several time the same request if (conversation.currentHistoryId && conversation.currentHistoryId === conversation.historyIndex) { that._logger.log(that.DEBUG, LOG_ID + "(getHistoryPage) (", conversation.id, ", ", size, ", ", conversation.historyIndex, ") already asked"); return Promise.resolve(undefined); } conversation.currentHistoryId = conversation.historyIndex; that._logger.log(that.DEBUG, LOG_ID + "(getHistoryPage) (", conversation.id, ", ", size, ", ", conversation.historyIndex, ")"); // Create the defered object let defered = conversation.historyDefered = new Deferred(); // Do nothing for userContact if (that._contactsService.isUserContact(conversation.contact)) { defered.reject(); return defered.promise; } if (conversation.historyComplete) { that._logger.log(that.DEBUG, LOG_ID + "(getHistoryPage) (" + conversation.id + ") : already complete"); defered.resolve(await that.getConversationById(conversation?.id)); return defered.promise; } let randomId = randomString(10, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); let mamRequest = { "queryid": "id:"+randomId+conversation.id, "with": conversation.id, "max": size, "before": "" }; if (conversation.historyIndex !== -1) { mamRequest.before = conversation.historyIndex; } else { that.resetHistoryPageForConversation(conversation); } // Request for history messages for the room chat if (conversation.bubble) { mamRequest = { "queryid": "id:"+randomId+conversation.id, "with": that._xmpp.jid_im, "max": size, "before": "" }; if (conversation.historyIndex !== -1) { mamRequest.before = conversation.historyIndex; } that._xmpp.mamQueryMuc(conversation.id, conversation.bubble.jid, mamRequest, useBulk); } else { // Request for history messages for the conversation that._xmpp.mamQuery(conversation.id, mamRequest, useBulk); } return defered.promise; } /** * @method getS2SMessagesByConversationId * @instance * @category MESSAGES * @description * Retrieve the remote history of a specific conversation.
* @param {string} conversationId Id of conversation * @param {number} limit Maximum number of messages to return (0 for counting) * @param {number} before Get messages before this Epoch timestamp in microseconds * @param {number} after Get messages after this Epoch timestamp in microseconds * @returns {Promise} * @fulfil {Pomise} - result object * @category async */ async getS2SMessagesByConversationId(conversationId :string, limit:number = undefined, before:number = undefined, after:number = undefined): Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getS2SMessagesByConversationId) conversationId : ", conversationId, ", limit : ", limit, ", before : ", before, ", after : ", after); return that.callRestMethod("getS2SMessagesByConversationId", arguments); } /** * @public * @nodered true * @method loadConversationHistory * @instance * @category MESSAGES * @async * @description * Retrieve the remote history of a specific conversation.
* @param {Conversation} conversation Conversation to retrieve * @param {string} pageSize=30 number of message in each page to retrieve messages. * @param {boolean} useBulk=false Does the history should be retrieved with a bulk (group) of messages * @async * @return {Promise} * @fulfil {Conversation[]} - Array of Conversation object * @category async */ async loadConversationHistory(conversation, pageSize : number = 30, useBulk : boolean = false) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(loadConversationHistory) conversation.id : ", conversation?.id); that.resetHistoryPageForConversation(conversation); /* function getConversationHistory(conversation, _pageSize) { that._logger.log("debug", "(loadConversationHistory) - getConversationHistory"); return that.getHistoryPage(conversation, _pageSize).then((conversationUpdated) => { that._logger.log("debug", "(loadConversationHistory) - getConversationHistory getHistoryPage"); let result = conversationUpdated.historyComplete ? conversationUpdated:getConversationHistory(conversationUpdated, pageSize); that._logger.log("debug", "(loadConversationHistory) - getConversationHistory getHistoryPage result : ", result); return result; }); } // */ async function getConversationHistory(conversation: Conversation, pageSize: number, useBulk2: boolean): Promise { let currentConversation = conversation; while (! currentConversation.historyComplete) { that._logger.log("debug", "(loadConversationHistory) - getConversationHistory"); await that.getHistoryPage(currentConversation, pageSize, useBulk2).then((conversationUpdated) => { that._logger.log("debug", "(loadConversationHistory) - getConversationHistory getHistoryPage completed"); currentConversation = conversationUpdated; }).catch((err) => { that._logger.log("debug", "(loadConversationHistory) - getHistoryPage CATCH Error : ", err); }); } return Promise.resolve(currentConversation); } return getConversationHistory(conversation, pageSize, useBulk); } /** * @public * @nodered true * @method loadConversationHistoryAsync * @instance * @category MESSAGES * @description * Retrieve the remote history of a specific conversation asynchronously. The result only said that the request has succesfully started (or not). *
The result of the loading process is sent with the event `rainbow_onloadConversationHistoryCompleted`
* @param {Conversation} conversation Conversation to retrieve * @param {string} pageSize=30 number of message in each page to retrieve messages. * @param {boolean} useBulk=false Does the history should be retrieved with a bulk (group) of messages * @async * @return {Promise<{code:number,label:string}>} * @category async */ loadConversationHistoryAsync(conversation: Conversation, pageSize: number = 30, useBulk : boolean = false): Promise<{code:number,label:string}> { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(loadConversationHistoryAsync) conversation.id : ", conversation?.id); that.resetHistoryPageForConversation(conversation); that.loadConversationHistory(conversation, pageSize, useBulk).then((conversationUpdated: any) => { that._logger.log(that.DEBUG, "(loadConversationHistoryAsync) loadConversationHistory done."); //that._logger.log(that.INTERNAL, "(loadConversationHistoryAsync) loadConversationHistory conversationUpdated : ", conversationUpdated); return conversationUpdated; }).then((conversationHistoryUpdated: any) => { that._logger.log(that.INFO, "(loadConversationHistoryAsync) loadConversationHistory done."); // raise rainbow_onloadConversationHistoryCompleted this._eventEmitter.emit("evt_internal_loadConversationHistoryCompleted", conversationHistoryUpdated); //return Promise.reject({code:-1, label:"load failed."}); }).catch((error: any)=>{ that._logger.log(that.ERROR, "(loadConversationHistoryAsync) loadConversationHistory Failed : ", error); this._eventEmitter.emit("evt_internal_loadConversationHistoryFailed", error); }); return Promise.resolve({code:1, label:"load started, you should wait for conversation rainbow_onloadConversationHistoryCompleted for load result."}); } /** * @private * @method getAllS2SMessagesByConversationId * @instance * @category MESSAGES * @description * Retrieve the remote history of a specific conversation.
* * ⚠️ Warning: It is useable in S2S connection mode. * * @param {string} conversationDbId dbId of the Conversation to retrieve messages. * @async * @return {Promise} * @fulfil {Promise} - Array of Conversation object * @category async */ async getAllS2SMessagesByConversationId(conversationDbId: string):Promise { let that = this; let messages = []; //let contactEmailToSearch = "vincent01@vbe.test.openrainbow.net"; await that.getS2SMessagesByConversationId(conversationDbId).then(async (result) => { // that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, conversation.messages.length : ", result.messages.length); //_logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, result : ", result); //for (let i = 0; i < result.messages.length; i++) { /* let msg = { "id": result.messages[i].id, "from": result.messages[i].from, "date": result.messages[i].datetime, "side": result.messages[i].side, "type": result.messages[i].type, "alternativeContent": result.messages[i].contents, "subject": result.messages[i].subject, "body": result.messages[i].body, "lang": result.messages[i].lang, "urgency": result.messages[i].urgency, "deleted": result.messages[i].isDeleted, "modified": result.messages[i].isModified } _logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, iter : [" + i + "], result.messages : ", result.messages[i], ", msg : ", msg); // */ // that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, iter : [" + i + "], result.messages : ", result.messages[i]); // } messages = [].concat(messages, result.messages); let urlNext = result._links.next; let urlPrev = result._links.prev; async function getS2SMessagesByConversationIdNext(urlNext) { //that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationIdNext, urlNext : ", urlNext); const match = urlNext.match(/after=(\d+)&/); if (match && match[1]) { const afterValue = match[1]; await that.getS2SMessagesByConversationId(conversationDbId, 10, undefined, afterValue).then(async (resultNext) => { //that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, resultNext.messages.length : ", resultNext.messages.length); // _logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, resultNext : ", resultNext); //for (let i = 0; i < resultNext.messages.length; i++) { // that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, iter : [" + i + "], resultNext.messages : ", resultNext.messages[i]); //} messages = [].concat(messages, resultNext.messages); let urlNextNext = resultNext._links.next; if (urlNextNext) await getS2SMessagesByConversationIdNext(urlNextNext); }); } } async function getS2SMessagesByConversationIdPrev(urlPrev) { //that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationIdPrev, urlPrev : ", urlPrev); const matchPrev = urlPrev.match(/before=(\d+)&/); if (matchPrev && matchPrev[1]) { const beforeValue = matchPrev[1]; await that.getS2SMessagesByConversationId(conversationDbId, 10, beforeValue, undefined).then( async (resultPrev) => { //that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, resultPrev.messages.length : ", resultPrev.messages.length); //that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, resultPrev : ", resultPrev); //for (let i = 0; i < resultPrev.messages.length; i++) { // that._logger.log("debug", "(getAllS2SMessagesByConversationId) - getS2SMessagesByConversationId, iter : [" + i + "], resultPrev.messages : ", resultPrev.messages[i]); //} messages = [].concat(messages, resultPrev.messages); let urlPrevPrev = resultPrev._links.prev; if (urlPrevPrev) await getS2SMessagesByConversationIdPrev(urlPrevPrev); }); } } await getS2SMessagesByConversationIdNext(urlNext); await getS2SMessagesByConversationIdPrev(urlPrev); }); that._logger.log("debug", "(getAllS2SMessagesByConversationId) messages.length : ", messages?.length); return messages; } /** * @private * @method loadEveryConversationsHistory * @instance * @category MESSAGES * @description * Retrieve the remote history of a specific conversation.
* @param {string} pageSize=30 number of message in each page to retrieve messages. * @param {boolean} useBulk=true Does the history should be retrieved with a bulk (group) of messages * @async * @return {Promise} * @fulfil {Conversation[]} - Array of Conversation object * @category async */ loadEveryConversationsHistory( pageSize : number = 30, useBulk :boolean = true) { let that = this; let nbConversations = that.conversations?that.conversations.length:0 ; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(loadEveryConversationsHistory) ."); for (let variableKey in that.conversations){ if (that.conversations.hasOwnProperty(variableKey)){ let conversation = that.conversations[variableKey]; //that.openConversationForContact(conversation).then(async function (conversationOpenned) { //logger.log("debug", "MAIN - testloadConversationHistory - openConversationForContact, conversation : ", conversation); let conversationOpenned = conversation; that._logger.log(that.DEBUG, "MAIN - testloadConversationHistory - openConversationForContact, conversation.messages.length : ", conversationOpenned.messages.length); that.loadConversationHistory(conversationOpenned, pageSize, useBulk).then((conversationLoadedHistory) => { that._logger.log(that.DEBUG, "(loadEveryConversationsHistory) loadConversationHistory result : ", conversationLoadedHistory.messages.length); }, (err) => { that._logger.log(that.DEBUG, "(loadEveryConversationsHistory) loadConversationHistory error : ", err); }); //}); } } } /** * * @public * @nodered true * @method getOneMessageFromConversationId * @instance * @category MESSAGES * @description * To retrieve ONE message archived on server exchanged in a conversation based on the specified message Id and the timestamp
*
* Time stamp is mandatory - the search is performed using it.
* Once results are returned, we look for a message with the message id specified.
* @param {string} conversationId : Id of the conversation * @param {string} messageId : Id of the message * @param {string} stamp : Time stamp. Time stamp is mandatory - the search is performed using it. * @async * @return {Promise} */ getOneMessageFromConversationId(conversationId:string, messageId : string, stamp:string) : Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getOneMessageFromConversationId) conversationId : ", conversationId); return new Promise(async (resolve, reject) => { that._logger.log(that.DEBUG, LOG_ID + "(getOneMessageFromConversationId) conversationId : ", conversationId, ", messageId : ", messageId); let conversation = that.getConversationById(conversationId); that._logger.log(that.DEBUG, LOG_ID + "(getOneMessageFromConversationId) conversation found, conversation.id: ", conversation.id); if (conversation) { let msg = conversation.getMessageById(messageId); if (msg != null) { return resolve(msg); } else { if (that._useS2S) { that._logger.log(that.DEBUG, LOG_ID + "(getOneMessageFromConversationId) S2S is used, so can not retrieve message from server, it can only be used in XMPP Event Mode context."); return reject(); } /* MessagesPool pool = new MessagesPool(conversation.Id, conversation.Jid_im, currentContactJid); // */ let conversationUpdated = await that.searchMessageArchivedFromServer(conversation, messageId, stamp) ; let message = conversationUpdated.getMessageById(messageId); resolve (message); // */ } } else { that._logger.log(that.DEBUG, LOG_ID + "(getOneMessageFromConversationId) No conversation found with this conversation ID : ", conversationId); return reject(undefined); } }); } /** * @public * @nodered true * @method getTheNumberOfHitsOfASubstringInAllUsersconversations * @instance * @category CONVERSATIONS * @async * @since 2.21.0 * @return {Object} The result * * * | Champ | Type | Description | * | --- | --- | --- | * | jid | String | the JID of the peer (P2P, BOT or ROOM) | * | count | Integer | The number of hits | * * @description * This API can be used to search a text substring in all conversations for a given user from recent to old messages.
* For technical reasons, the same limit value applies on all peer to peer conversations but also on all room conversations.
* This API can only be used by user himself (i.e. userId of logged-in user).
* @param {string} userId User unique identifier * @param {string} substring Text to search * @param {number} limit=100 Max number of matching messages count (expect up to 2x limit counts since the limit applies both to P2P and Room messages). Default value : 100 * @param {boolean} webinar=true Include webinars (excluded by default). Default value : false */ async getTheNumberOfHitsOfASubstringInAllUsersconversations (userId: string, substring : string, limit : number = 100, webinar : boolean = true) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) userId : ", userId); //that._logger.log(that.INTERNAL, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) parameters : userId : ", userId); return new Promise(function (resolve, reject) { try { let meId = userId ? userId : that._rest.account.id; if (!substring) { that._logger.log(that.ERROR, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) bad or empty 'substring' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } that._rest.getTheNumberOfHitsOfASubstringInAllUsersconversations(meId, substring, limit, webinar).then((result : any) => { that._logger.log(that.INTERNAL, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) Successfully result : ", result); resolve(result); }).catch((err) => { that._logger.log(that.ERROR, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) Error when updating informations."); that._logger.log(that.INTERNALERROR, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) Error : ", err); return reject(err); }); } catch (err) { that._logger.log(that.INTERNALERROR, LOG_ID + "(getTheNumberOfHitsOfASubstringInAllUsersconversations) error : ", err); return reject(err); } }); } /** * * @public * @nodered true * @method getContactsMessagesFromConversationId * @instance * @category MESSAGES * @description * To retrieve messages exchanged by contacts in a conversation. The result is the messages without event type.
* @param {string} conversationId : Id of the conversation * @param {boolean} useBulk=false Does the history should be retrieved with a bulk (group) of messages * @async * @return {Promise} */ async getContactsMessagesFromConversationId(conversationId:string, useBulk : boolean = false) : Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getContactsMessagesFromConversationId) conversationId : ", conversationId); if (!conversationId) { that._logger.log(that.DEBUG, LOG_ID + "(getContactsMessagesFromConversationId) bad or empty 'conversationId' parameter : ", conversationId); return null; } let conversation = that.getConversationById(conversationId); if (!conversation) { return null; } that._logger.log(that.DEBUG, LOG_ID + "(getContactsMessagesFromConversationId) conversation found, conversation.id: ", conversation.id); if (!conversation.messages) { that._logger.log(that.DEBUG, LOG_ID + "(getContactsMessagesFromConversationId) 'conversation.messages' undefined!"); return null; } if (conversation.historyComplete == false) { that._logger.log(that.INFO, LOG_ID + "(getContactsMessagesFromConversationId) 'conversation.messages' empty, load the history !"); await that.loadConversationHistory(conversation, 50, useBulk); if (!conversation.messages) { that._logger.log(that.WARN, LOG_ID + "(getContactsMessagesFromConversationId) after load history 'conversation.messages' undefined!"); return null; } } return conversation.messages.filter((msg) => { return !msg.isEvent ; }); } async searchMessageArchivedFromServer(conversation: Conversation, messageId: string, stamp: string) { let that = this; /* // Avoid to call several time the same request if (conversation.currentHistoryId && conversation.currentHistoryId === conversation.historyIndex) { that._logger.log(that.DEBUG, LOG_ID + "[conversationServiceHistory] getHistoryPage(", conversation.id, ", ", size, ", ", conversation.historyIndex, ") already asked"); return Promise.resolve(undefined); } conversation.currentHistoryId = conversation.historyIndex; // */ that._logger.log(that.DEBUG, LOG_ID + "(searchMessageArchivedFromServer) conversationId : ", conversation.id, ", messageId", messageId, ", stamp : ", stamp, ")"); if (conversation.historyDefered && conversation.historyDefered.promise) { that._logger.log(that.DEBUG, LOG_ID + "(searchMessageArchivedFromServer) conversation.historyDefered already defined, wait for end promise's treatment."); await conversation.historyDefered.promise; that._logger.log(that.DEBUG, LOG_ID + "(searchMessageArchivedFromServer) conversation.historyDefered already defined, promise's treatment Ended."); } // Create the defered object let defered = conversation.historyDefered = new Deferred(); // Do nothing for userContact if (that._contactsService.isUserContact(conversation.contact)) { defered.reject(); return defered.promise; } let randomId = randomString(10, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); /* if (conversation.historyComplete) { that._logger.log(that.DEBUG, LOG_ID + "getHistoryPage(" + conversation.id + ") : already complete"); defered.reject(); return defered.promise; } // */ let mamRequest = { "queryid": "id:"+randomId+conversation.id, "with": conversation.id, "start": new Date(parseInt(stamp) - 300).toISOString(), "end": new Date(parseInt(stamp) + 300).toISOString() }; if (conversation.historyIndex !== -1) { //mamRequest.before = conversation.historyIndex; } // Request for history messages for the room chat if (conversation.bubble) { mamRequest = { "queryid": "id:"+randomId+conversation.id, "with": that._xmpp.jid_im, "start": new Date(parseInt(stamp) - 300).toISOString(), "end": new Date(parseInt(stamp) + 300).toISOString() }; if (conversation.historyIndex !== -1) { // mamRequest.before = conversation.historyIndex; } that._xmpp.mamQueryMuc(conversation.id, conversation.bubble.jid, mamRequest, false); } else { // Request for history messages for the conversation that._xmpp.mamQuery(conversation.id, mamRequest, false); } // */ return defered.promise; } /** * @private * @method sendFSMessage * @instance * @category MESSAGES * @description * Send an file sharing message
*/ sendFSMessage(conversation, file, data, p_messagesDataStore: DataStoreType) { //let message = conversation.sendFSMessage(file, data); //Conversation.prototype.sendFSMessage = function(file, data) { let that = this; return new Promise((resolve, reject) => { that._logger.log(that.INFO, LOG_ID + "sendFSMessage"); // Add message in messages array let fileExtension = file.name.split(".").pop(); //let fileMimeType = file.type; let viewers = []; //let message = typeof (data) === "object" ? data : undefined; let message = data; let currentFileDescriptor; if (conversation.type === Conversation.Type.ONE_TO_ONE) { viewers.push(fileViewerElementFactory(conversation.contact.id, "user", undefined, undefined)); /*viewers = fileViewerFactory([{ "viewerId": this.contact.dbId, "type": "user" }]); // */ } else { viewers.push(fileViewerElementFactory(conversation.bubble.id, "room", undefined, undefined)) ; /*viewers = fileViewerFactory([{ "viewerId": this.room.dbId, "type": "room" }]); // */ } that._fileStorageService.createFileDescriptor(file.name, fileExtension, file.size, viewers).then(function (fileDescriptor: any) { currentFileDescriptor = fileDescriptor; fileDescriptor.fileToSend = file; if (fileDescriptor.isImage()) { // let URLObj = $window.URL || $window.webkitURL; // fileDescriptor.previewBlob = URLObj.createObjectURL(file); if (file.preview) { fileDescriptor.previewBlob = file.preview; } } /* if (!message) { message = that.addFSMessage(fileDescriptor.id, fileMimeType, data, "uploading"); } message.fileId = fileDescriptor.id; message.fileName = fileDescriptor.fileName; // */ // Upload file fileDescriptor.state = "uploading"; /*if (that.chatRenderer) { that.chatRenderer.updateFileTransferState(message, fileDescriptor); } */ return that._fileServerService.uploadAFileByChunk(fileDescriptor, file.path /*, message , function (msg, fileDesc) { if (that.chatRenderer) { that.chatRenderer.updateFileTransferState(msg, fileDesc); } } */) .then(function successCallback(fileDesc) { that._logger.log(that.DEBUG, LOG_ID + "uploadAFileByChunk success"); // resolve(fileDescriptor); return Promise.resolve(fileDesc); }, function errorCallback(error) { that._logger.log(that.ERROR, LOG_ID + "uploadAFileByChunk error."); that._logger.log(that.INTERNALERROR, LOG_ID + "uploadAFileByChunk error : ", error); //do we need to delete the file descriptor from the server if error ?? that._fileStorageService.deleteFileDescriptor(currentFileDescriptor.id); // .then(function() { // let msgKey = error.translatedMessage ? error.translatedMessage : "Unable to share file"; // $rootScope.$broadcast("ON_SHOW_INFO_MESSAGE", { type: "error", messageKey: msgKey }); // currentFileDescriptor.state = "uploadError"; // message.receiptStatus = Message.ReceiptStatus.ERROR; // message.fileErrorMsg = msgKey; // that.updateMessage(message); // }); // let msgKey = error.translatedMessage ? error.translatedMessage : "Unable to share file"; //$rootScope.$broadcast("ON_SHOW_INFO_MESSAGE", {type: "error", messageKey: msgKey}); currentFileDescriptor.state = "uploadError"; //message.receiptStatus = Message.ReceiptStatus.ERROR; //message.fileErrorMsg = msgKey; //that.updateMessage(message); return Promise.reject(error); }); }) .then(function successCallback(fileDescriptorResult) { fileDescriptorResult.state = "uploaded"; fileDescriptorResult.chunkPerformed = 0; fileDescriptorResult.chunkTotalNumber = 0; let messagefs = that.sendExistingFSMessage(conversation, message, fileDescriptorResult, p_messagesDataStore); that.storePendingMessage(conversation, messagefs); resolve(messagefs); }, function errorCallback(error) { that._logger.log(that.ERROR, LOG_ID + "createFileDescriptor error"); return reject(error); }); //}; /* todo: VBR What is this pendingMessages list coming from WebSDK ? Is it necessary for node SDK ? this.pendingMessages[message.id] = { conversation: conversation, message: message }; // */ }); } /** * @public * @nodered true * @method sendExistingFSMessage * @instance * @category MESSAGES * @description * Send a message to this conversation
* @return {Message} The message sent * @param {Conversation} conversation * @param {string} message * @param {any} fileDescriptor * @param {DataStoreType} p_messagesDataStore=undefined used to override the general of SDK's parameter "messagesDataStore". default value `undefined` to use the general value.
* DataStoreType.NoStore Tell the server to NOT store the messages for delay distribution or for history of the bot and the contact.
* DataStoreType.NoPermanentStore Tell the server to NOT store the messages for history of the bot and the contact. But being stored temporarily as a normal part of delivery (e.g. if the recipient is offline at the time of sending).
* DataStoreType.StoreTwinSide The messages are fully stored.
* DataStoreType.UsestoreMessagesField to follow the storeMessages SDK's parameter behaviour.
* DataStoreType.Store Offline storage and Message Archive Management (XEP-0313) [4] can define their own rules on what messages to store and usually only store messages that contain a body element. * However a sender may want to indicate that a message is worth storing even though it might not match those rules * (e.g. an encrypted message that carries the payload outside the body element). Such a message can be marked with a hint. */ sendExistingFSMessage(conversation : Conversation, message : string, fileDescriptor : any, p_messagesDataStore: DataStoreType = undefined) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendExistingFSMessage) conversation.id : ", conversation?.id); //conversation.sendAckReadMessages(); let unicodeData = message ; if (!message) { return null; } let lang = 'en'; if (conversation.type === Conversation.Type.ONE_TO_ONE) { let to = conversation.contact.jid; return that._xmpp.sendChatExistingFSMessage(unicodeData, to, lang, fileDescriptor, p_messagesDataStore); } else { let to = conversation.bubble.jid; return that._xmpp.sendChatExistingFSMessageToBubble(unicodeData, to, lang, fileDescriptor, p_messagesDataStore); } } /** * @private * @method * @instance * @category MESSAGES * @description * Send an existing file sharing message
*/ /* sendEFSMessage(conversation: Conversation, fileDescriptor, data) { let message = conversation.sendEFSMessage(fileDescriptor, data); this.storePendingMessage(conversation, message); return message; } */ /** * @private * @method * @instance * @category MESSAGES * @description * Send a instant message to a conversation
* This method works for sending messages to a one-to-one conversation or to a bubble conversation
* @param {Conversation} conversation The conversation to clean * @param {string} data Test message to send * @param answeredMsg */ /* sendChatMessage(conversation : Conversation, data : string, answeredMsg) { let message = conversation.sendChatMessage(data, answeredMsg); this.storePendingMessage(conversation, message); return message; } */ /** * SEND CORRECTED MESSAGE */ /** * @public * @nodered true * @method sendCorrectedChatMessage * @category MESSAGES * @instance * @description * Send a corrected message to a conversation
* This method works for sending messages to a one-to-one conversation or to a bubble conversation
* The new message has the property originalMessageReplaced which spot on original message // Warning this is a circular depend.
* The original message has the property replacedByMessage which spot on the new message // Warning this is a circular depend.
* Note: The connected user must be the sender of the original message.
* @param {Conversation} conversation * @param {string} data The message string corrected * @param {string} origMsgId The id of the original corrected message. * @param {Object} content Allows to send alternative text base content * @param {String} content.type The content message type `text/markdown` * @param {String} content.message The content message body * @param {string} urgency=std The urgence of the message. Value can be : 'high' Urgent message, 'middle' important message, 'low' information message, "std' or null standard message * @param {DataStoreType} p_messagesDataStore=undefined used to override the general of SDK's parameter "messagesDataStore". default value `undefined` to use the general value.
* DataStoreType.NoStore Tell the server to NOT store the messages for delay distribution or for history of the bot and the contact.
* DataStoreType.NoPermanentStore Tell the server to NOT store the messages for history of the bot and the contact. But being stored temporarily as a normal part of delivery (e.g. if the recipient is offline at the time of sending).
* DataStoreType.StoreTwinSide The messages are fully stored.
* DataStoreType.UsestoreMessagesField to follow the storeMessages SDK's parameter behaviour.
* DataStoreType.Store Offline storage and Message Archive Management (XEP-0313) [4] can define their own rules on what messages to store and usually only store messages that contain a body element. * However a sender may want to indicate that a message is worth storing even though it might not match those rules * (e.g. an encrypted message that carries the payload outside the body element). Such a message can be marked with a hint. * @returns {Promise} message the message new correction message sent. Throw an error if the send fails. */ async sendCorrectedChatMessage(conversation : Conversation, data : string, origMsgId : string, content : { message : string, type : string } = null, urgency: string = "std", p_messagesDataStore: DataStoreType = undefined) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendCorrectedChatMessage) conversation.id : ", conversation?.id); if (!conversation) { that._logger.log(that.ERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'conversation' parameter"); that._logger.log(that.INTERNALERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'conversation' parameter : ", conversation); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); } /* if (data==undefined) { that._logger.log(that.ERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'data' parameter"); that._logger.log(that.INTERNALERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'data' parameter : ", data); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); }//*/ if (!origMsgId) { that._logger.log(that.ERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'origMsgId' parameter"); that._logger.log(that.INTERNALERROR, LOG_ID + "(sendCorrectedChatMessage) bad or empty 'origMsgId' parameter : ", origMsgId); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); } that._logger.log(that.INTERNAL, LOG_ID + "(sendCorrectedChatMessage) _entering_ conversation.id : ", conversation.id, ", data : \'", data, "\', origMsgId : ", origMsgId, " content : ", content); let originalMessage = conversation.getMessageById(origMsgId); that._logger.log(that.INFO, LOG_ID + "(sendCorrectedChatMessage) originalMessage : ", originalMessage); if (originalMessage) { let originalMessageFrom = originalMessage.fromJid || originalMessage.from; if (originalMessageFrom!==that._rest.loggedInUser.jid_im) { that._logger.log(that.ERROR, LOG_ID + "(sendCorrectedChatMessage) forbidden Action - only sent messages can be modified"); throw ErrorManager.getErrorManager().OTHERERROR("(sendCorrectedChatMessage) forbidden Action - only sent messages can be modified", "(sendCorrectedChatMessage) forbidden Action - only sent messages can be modified"); } /* let lastEditableMsg = conversation.getlastEditableMsg(); if (lastEditableMsg.id!==originalMessage.id) { that._logger.log(that.ERROR, LOG_ID + "(sendCorrectedChatMessage) forbidden Action - only last sent message can be modified"); throw ErrorManager.getErrorManager().OTHERERROR("(sendCorrectedChatMessage) forbidden Action - only last sent message can be modified", "(sendCorrectedChatMessage) forbidden Action - only last sent message can be modified"); } // */ let messageUnicode = data==="" ? "":(data ? shortnameToUnicode(data):undefined); try { if (this._useXMPP) { let sentMessageId = await that._xmpp.sendCorrectedChatMessage(conversation, originalMessage, messageUnicode, origMsgId, originalMessage.lang, content, urgency, p_messagesDataStore); // , attention, urgency: string = null, p_messagesDataStore: DataStoreType let newMsg = Object.assign({}, originalMessage); newMsg.id = sentMessageId; newMsg.content = messageUnicode; newMsg.date = new Date(); newMsg.alternativeContent = content; newMsg.originalMessageReplaced = originalMessage; // Warning this is a circular depend. originalMessage.replacedByMessage = newMsg; // Warning this is a circular depend. that._logger.log(that.INTERNAL, LOG_ID + "(sendCorrectedChatMessage) id : ", sentMessageId, ", This is a replace msg, so set newMsg.originalMessageReplaced.replacedByMessage : ", newMsg.originalMessageReplaced.replacedByMessage); this._pendingMessages[sentMessageId] = {conversation: conversation, message: newMsg}; return newMsg; } if ((this._useS2S)) { let msg = { "message": { "contents": content, // [ // { // "type": "text/markdown", // "data": "## Hello Bob" // } // ], "body": messageUnicode, } }; if (!conversation.dbId) { conversation = await that.createServerConversation(conversation); that._logger.log(that.INTERNAL, LOG_ID + "(sendMessageToConversation) conversation : ", conversation); } let sentMessageId = await that._s2s.sendCorrectedChatMessage(conversation.dbId, origMsgId, msg); let newMsg:any = Object.assign({}, originalMessage); newMsg.id = sentMessageId; newMsg.content = messageUnicode; newMsg.date = new Date(); newMsg.alternativeContent = content; newMsg.modified = true; newMsg.originalMessageReplaced = originalMessage; // Warning this is a circular depend. originalMessage.replacedByMessage = newMsg; // Warning this is a circular depend. that._logger.log(that.INTERNAL, LOG_ID + "(sendCorrectedChatMessage) id : ", sentMessageId, ", This is a replace msg, so set newMsg.originalMessageReplaced.replacedByMessage : ", newMsg.originalMessageReplaced.replacedByMessage); this._pendingMessages[sentMessageId] = {conversation: conversation, message: newMsg}; return newMsg; } } catch (err) { that._logger.log(that.ERROR, LOG_ID + "sendCorrectedChatMessage error"); let error = ErrorManager.getErrorManager().OTHERERROR(err.message, "(sendCorrectedChatMessage) error while sending corrected message : " + err); // @ts-ignore error.newMessageText = data; // @ts-ignore error.originaleMessageId = origMsgId; throw error; } } else { that._logger.log(that.WARN, LOG_ID + "(sendCorrectedChatMessage) error the original message was not found in conversation : ", conversation); let error = ErrorManager.getErrorManager().OTHERERROR("sendCorrectedChatMessage error the original message was not found.", "sendCorrectedChatMessage error the original message was not found."); // @ts-ignore error.newMessageText = data; // @ts-ignore error.originaleMessageId = origMsgId; throw error; } } /** * @public * @since 1.58 * @nodered true * @method deleteMessage * @category MESSAGES * @instance * @async * @description * Delete a message by sending an empty string in a correctedMessage
* @param {Conversation} conversation The conversation object * @param {string} messageId The id of the message to be deleted * @param {DataStoreType} p_messagesDataStore=undefined used to override the general of SDK's parameter "messagesDataStore". default value `undefined` to use the general value.
* DataStoreType.NoStore Tell the server to NOT store the messages for delay distribution or for history of the bot and the contact.
* DataStoreType.NoPermanentStore Tell the server to NOT store the messages for history of the bot and the contact. But being stored temporarily as a normal part of delivery (e.g. if the recipient is offline at the time of sending).
* DataStoreType.StoreTwinSide The messages are fully stored.
* DataStoreType.UsestoreMessagesField to follow the storeMessages SDK's parameter behaviour.
* DataStoreType.Store Offline storage and Message Archive Management (XEP-0313) [4] can define their own rules on what messages to store and usually only store messages that contain a body element. * However a sender may want to indicate that a message is worth storing even though it might not match those rules * (e.g. an encrypted message that carries the payload outside the body element). Such a message can be marked with a hint.
* @return {Message} - message object with updated replaceMsgs property */ async deleteMessage (conversation : Conversation, messageId : string, p_messagesDataStore: DataStoreType = undefined) : Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(deleteMessage) conversation.id : ", conversation?.id, ", messageId : ", messageId); if (!conversation) { that._logger.log(that.ERROR, LOG_ID + "(deleteMessage) Parameter 'conversation' is missing or null"); throw ErrorManager.getErrorManager().BAD_REQUEST(); } if (!messageId) { that._logger.log(that.ERROR, LOG_ID + "(deleteMessage) Parameter 'messageId' is missing or empty"); throw ErrorManager.getErrorManager().BAD_REQUEST(); } let urgency = "std"; let messageOrig = conversation.getMessageById(messageId); await that.sendCorrectedChatMessage(conversation, "", messageId, undefined, urgency, p_messagesDataStore); return messageOrig; } /** * * @public * @since 1.67.0 * @nodered true * @method deleteAllMessageInOneToOneConversation * @category MESSAGES * @instance * @async * @description * Delete all messages for the connected user on a one to one conversation.
* @param {Conversation} conversation The conversation object * @return {Message} - message object with updated replaceMsgs property */ deleteAllMessageInOneToOneConversation (conversation : Conversation) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(deleteAllMessageInOneToOneConversation) conversation.id : ", conversation?.id); if (!conversation) { that._logger.log(that.ERROR, LOG_ID + "(deleteAllMessageInOne2OneConversation) bad or empty 'conversation' parameter."); that._logger.log(that.INTERNALERROR, LOG_ID + "(deleteAllMessageInOne2OneConversation) bad or empty 'conversation' parameter : ", conversation); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); } let conversationObj = that.getConversationById(conversation.id); if (conversationObj.type !== Conversation.Type.ONE_TO_ONE) { that._logger.log(that.ERROR, LOG_ID + "(deleteAllMessageInOne2OneConversation) bad or empty 'conversation.type' parameter."); that._logger.log(that.INTERNALERROR, LOG_ID + "(deleteAllMessageInOne2OneConversation) bad or empty 'conversation.type' parameter : ", conversationObj); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); } return that._xmpp.deleteAllMessageInOneToOneConversation(conversationObj.id); } /** * @private * @category MESSAGES * @description * Store the message in a pending list. This pending list is used to wait the "_onReceipt" event from server when a message is sent.
* It allow to give back the status of the sending process.
* @param conversation * @param message */ storePendingMessage(conversation, message) { this._pendingMessages[message.id] = { conversation: conversation, message: message }; } /** * @private * @category MESSAGES * @description * delete the message in a pending list. This pending list is used to wait the "_onReceipt" event from server when a message is sent.
* It allow to give back the status of the sending process.
* @param message */ removePendingMessage(message) { delete this._pendingMessages[message.id]; } /** * @public * @nodered true * @method removeAllMessages * @category MESSAGES * @instance * @description * Cleanup a conversation by removing all previous messages
* * If conversation is P2P then the messages are deleted on Bot side. * * This method returns a promise
* @param {Conversation} conversation The conversation to clean * @async * @return {Promise} * @fulfil {} Return nothing in case success * @category async */ removeAllMessages(conversation : Conversation) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(removeAllMessages) conversation.id : ", conversation?.id); return new Promise((resolve) => { if (!conversation) { that._logger.log(that.ERROR, LOG_ID + "(removeAllMessages) bad or empty 'conversation' parameter."); that._logger.log(that.INTERNALERROR, LOG_ID + "(removeAllMessages) bad or empty 'conversation' parameter : ", conversation); return Promise.reject(ErrorManager.getErrorManager().BAD_REQUEST); } that._logger.log(that.INFO, LOG_ID + "(removeAllMessage) _entering_ " + conversation.id); // Id must be filled by lower layer let mamRequest = { //"queryid": mamRequestId, "with": conversation.id, "onComplete": function(result) { that._logger.log(that.INTERNAL, LOG_ID + " removeAllMessage " + conversation.id, ", result : ", result); // FIXME : handle error message (ask Andreï) resolve(result); } }; that._pendingMessages = {}; that._xmpp.mamDelete( mamRequest); /* let mamRequest = { "deleteid": "remove_" + conversation.id, "with": conversation.id, "before": moment().add(1, 'minutes') .utc() .format("YYYY-MM-DDTHH:mm:ss") + "Z", "onComplete": function (result) { that._logger.log(that.DEBUG, LOG_ID + " removeAllMessage " + conversation.id, ", result : ", result); // FIXME : handle error message (ask Andreï) resolve(result); } }; that.pendingMessages = []; // Request for history messages that._xmpp.mamDelete(conversation.id, mamRequest); // */ }); } /** * @public * @nodered true * @method removeMessagesFromConversation * @category MESSAGES * @instance * @description * Remove a specific range of message in a conversation
* This method returns a promise
* @param {Conversation} conversation The conversation to clean * @param {Date} date The date since when the message should be deleted. * @param {number} number max number of messages to delete. * @async * @return {Promise} * @fulfil {} Return nothing in case success * @category async */ removeMessagesFromConversation(conversation : Conversation, date : Date, number : number) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(removeMessagesFromConversation) conversation.id : ", conversation?.id, " removing number : " + number + " messages after :" + date); return new Promise((resolve) => { // that._logger.log(that.INFO, LOG_ID + " removeMessagesFromConversation " + conversation.id); // that._logger.log(that.INFO, LOG_ID + " removing " + number + " messages after " + date); let mamRequest = { "deleteid": "remove_" + conversation.id, "with": conversation.id, "start": moment(date).format("YYYY-MM-DDTHH:mm:ss.SSSSSSZ"), "max": number, "onComplete": () => { that ._logger .log("info", LOG_ID + " MAM Message deleted !!!"); resolve(undefined); } }; that._pendingMessages = Object.fromEntries(Object.entries(that._pendingMessages).filter(([key, messagePending ] : [string, Message]) => { if (messagePending.date > date) { return false; } })); //that.pendingMessages = that.pendingMessages.filter((messagePending) => { if (messagePending.date > date) { return false; } }); // Request for history messages that._xmpp.mamDelete(mamRequest); //that._xmpp.mamDelete(conversation.id, mamRequest); }); } /** * @public * @nodered true * @method sendIsTypingState * @category MESSAGES * @instance * @description * Switch the "is typing" state in a conversation
* @param {Conversation} conversation The conversation recipient * @param {boolean} status=false The status, true for setting "is Typing", false to remove it. Default Value : false. * @param {DataStoreType} p_messagesDataStore=undefined used to override the general of SDK's parameter "messagesDataStore". default value `undefined` to use the general value.
* DataStoreType.NoStore Tell the server to NOT store the messages for delay distribution or for history of the bot and the contact.
* DataStoreType.NoPermanentStore Tell the server to NOT store the messages for history of the bot and the contact. But being stored temporarily as a normal part of delivery (e.g. if the recipient is offline at the time of sending).
* DataStoreType.StoreTwinSide The messages are fully stored.
* DataStoreType.UsestoreMessagesField to follow the storeMessages SDK's parameter behaviour.
* DataStoreType.Store Offline storage and Message Archive Management (XEP-0313) [4] can define their own rules on what messages to store and usually only store messages that contain a body element. * However a sender may want to indicate that a message is worth storing even though it might not match those rules * (e.g. an encrypted message that carries the payload outside the body element). Such a message can be marked with a hint.
* @return a promise with no success parameter */ sendIsTypingState(conversation : Conversation, status : boolean= false, p_messagesDataStore: DataStoreType) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendIsTypingState) conversation.id : ", conversation?.id, " status : ", status); return new Promise((resolve, reject) => { if (!conversation) { return reject(Object.assign( ErrorManager.getErrorManager().BAD_REQUEST, {msg: "Parameter 'conversation' is missing or null"})); } /* else if (!status) { reject(Object.assign( ErrorManager.BAD_REQUEST, {msg: "Parameter 'status' is missing or null"})); } // */ else { conversation = conversation.id ? that.getConversationById(conversation.id) : null; if (!conversation) { return reject(Object.assign( ErrorManager.getErrorManager().OTHERERROR("ERRORNOTFOUND", "Parameter \'conversation\': this conversation doesn\'t exist"), {msg: "Parameter 'conversation': this conversation doesn't exist"})); } else { resolve(that._xmpp.sendIsTypingState(conversation, status, p_messagesDataStore)); } } }); } /** * @public * @nodered true * @method updateConversationBookmark * @instance * @category MESSAGES * @async * @since 2.21.0 * @return {Object} The result * * * | Champ | Type | Description | * | --- | --- | --- | * | status | String | Status message. | * | data | Object\[\] | No data (empty Array) | * * @description * This API can be used to set or replace a bookmarked message in a conversation. This API can only be used by user himself.
* @param {string} userId User unique identifier. * @param {string} conversationId conversation unique identifier (the dbId property in Conversation). * @param {string} messageId message unique identifier. */ updateConversationBookmark (userId : string, conversationId : string, messageId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(updateConversationBookmark) conversationId : ", conversationId, " userId : ", userId, ", messageId : ", messageId); //that._logger.log(that.INTERNAL, LOG_ID + "(updateConversationBookmark) parameters : userId : ", userId); return new Promise(function (resolve, reject) { try { let meId = userId ? userId : that._rest.account.id; if (!conversationId) { that._logger.log(that.ERROR, LOG_ID + "(updateConversationBookmark) bad or empty 'conversationId' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } if (!messageId) { that._logger.log(that.ERROR, LOG_ID + "(updateConversationBookmark) bad or empty 'messageId' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } that._rest.updateConversationBookmark(meId, conversationId, messageId).then((result : any) => { that._logger.log(that.INTERNAL, LOG_ID + "(updateConversationBookmark) Successfully result : ", result); resolve(result); }).catch((err) => { that._logger.log(that.ERROR, LOG_ID + "(updateConversationBookmark) Error when updating informations."); that._logger.log(that.INTERNALERROR, LOG_ID + "(updateConversationBookmark) Error : ", err); return reject(err); }); } catch (err) { that._logger.log(that.INTERNALERROR, LOG_ID + "(updateConversationBookmark) error : ", err); return reject(err); } }); } /** * @public * @nodered true * @method deleteConversationBookmark * @instance * @category MESSAGES * @async * @since 2.21.0 * @return {Object} The result * * * | Champ | Type | Description | * | --- | --- | --- | * | status | String | Status message. | * | data | Object\[\] | No data (empty Array) | * * @description * This API can be used to set or replace a bookmarked message in a conversation. This API can only be used by user himself.
* @param {string} userId User unique identifier. * @param {string} conversationId conversation unique identifier (the dbId property in Conversation). */ deleteConversationBookmark (userId : string, conversationId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(deleteConversationBookmark) conversationId : ", conversationId, " userId : ", userId); // that._logger.log(that.INTERNAL, LOG_ID + "(deleteConversationBookmark) parameters : userId : ", userId); return new Promise(function (resolve, reject) { try { let meId = userId ? userId : that._rest.account.id; if (!conversationId) { that._logger.log(that.ERROR, LOG_ID + "(deleteConversationBookmark) bad or empty 'conversationId' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } that._rest.deleteConversationBookmark(meId, conversationId).then((result : any) => { that._logger.log(that.INTERNAL, LOG_ID + "(deleteConversationBookmark) Successfully result : ", result); resolve(result); }).catch((err) => { that._logger.log(that.ERROR, LOG_ID + "(deleteConversationBookmark) Error when updating informations."); that._logger.log(that.INTERNALERROR, LOG_ID + "(deleteConversationBookmark) Error : ", err); return reject(err); }); } catch (err) { that._logger.log(that.INTERNALERROR, LOG_ID + "(deleteConversationBookmark) error : ", err); return reject(err); } }); } /** * @public * @nodered true * @method showAllMatchingMessagesForAPeer * @since 2.21.0 * @instance * @category MESSAGES * @description * This API can be used to return all matching messages for one specific peer. This API can only be used by user himself.
* @return {Promise} An object of the result * * * | Champ | Type | Description | * | --- | --- | --- | * | timestamp | Integer | Message timestamp in microseconds | * | msgId | String | The message ID | * * @param {string} userId User unique identifier * @param {string} substring Text to search * @param {string} peer Peer JID * @param {boolean} isRoom Is the peer a room ? * @param {number} limit=20 Max number of matching messages references. Default value : 20 */ showAllMatchingMessagesForAPeer (userId : string, substring : string, peer : string, isRoom : boolean = undefined, limit : number = 20) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(showAllMatchingMessagesForAPeer) substring : ", substring, " userId : ", userId); //that._logger.log(that.INTERNAL, LOG_ID + "(showAllMatchingMessagesForAPeer) parameters : userId : ", userId); return new Promise(function (resolve, reject) { try { let meId = userId ? userId : that._rest.account.id; if (!substring) { that._logger.log(that.ERROR, LOG_ID + "(showAllMatchingMessagesForAPeer) bad or empty 'substring' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } if (!peer) { that._logger.log(that.ERROR, LOG_ID + "(showAllMatchingMessagesForAPeer) bad or empty 'peer' parameter"); reject(ErrorManager.getErrorManager().BAD_REQUEST); return; } that._rest.showAllMatchingMessagesForAPeer(meId, substring, peer, isRoom, limit).then((result : any) => { that._logger.log(that.INTERNAL, LOG_ID + "(showAllMatchingMessagesForAPeer) Successfully result : ", result); resolve(result); }).catch((err) => { that._logger.log(that.ERROR, LOG_ID + "(showAllMatchingMessagesForAPeer) Error when updating informations."); that._logger.log(that.INTERNALERROR, LOG_ID + "(showAllMatchingMessagesForAPeer) Error : ", err); return reject(err); }); } catch (err) { that._logger.log(that.INTERNALERROR, LOG_ID + "(showAllMatchingMessagesForAPeer) error : ", err); return reject(err); } }); } // endregion MESSAGES //region CONVERSATIONS /** * @public * @nodered true * @method getAllConversations * @category CONVERSATIONS * @instance * @description * Allows to get the list of existing conversations (p2p and bubbles)
* @return {Conversation[]} An array of Conversation object */ getAllConversations() { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getAllConversations) ."); return that.getConversations(); }; /** * @private * @method * @category CONVERSATIONS * @instance * @description * Get all conversation
* @return {Conversation[]} The conversation list to retrieve */ getConversations() { let conversationArray = []; for (let key in this.conversations) { if (this.conversations.hasOwnProperty(key)) { conversationArray.push(this.conversations[key]); } } return conversationArray; } /** * @public * @nodered true * @method openConversationForContact * @category CONVERSATIONS * @instance * @description * Open a conversation to a contact
* Create a new one if the conversation doesn't exist or reopen a closed conversation
* This method returns a promise
* @param {Contact} contact The contact involved in the conversation * @return {Conversation} The conversation (created or retrieved) or null in case of error */ openConversationForContact (contact : Contact): Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(openConversationForContact) contact : ", contact); return new Promise(function (resolve, __reject) { if (!contact) { return __reject({ code: ErrorManager.getErrorManager().BAD_REQUEST, label: "Parameter 'contact' is missing or null" }); } else { that._logger.log(that.INFO, LOG_ID + " :: Try to create of get a conversation."); that._logger.log(that.INTERNAL, LOG_ID + " :: Try to create of get a conversation with " + contact.lastName + " " + contact.firstName); that.getOrCreateOneToOneConversation(contact.jid).then(async function (conversation: any) { that._logger.log(that.INFO, LOG_ID + " :: Conversation retrieved or created " + conversation.id); if (!conversation.dbId) { conversation = await that.createServerConversation(conversation); that._logger.log(that.INTERNAL, LOG_ID + "(openConversationForContact) conversation : ", conversation); } resolve(conversation); }).catch(function (result) { that._logger.log(that.ERROR, LOG_ID + "(openConversationForContact) Error."); that._logger.log(that.INTERNALERROR, LOG_ID + "(openConversationForContact) Error : ", result); return __reject(result); }); } }); } /** * @public * @nodered true * @method openConversationForBubble * @since 1.65 * @category CONVERSATIONS * @instance * @description * Open a conversation to a bubble
* Create a new one if the conversation doesn't exist or reopen a closed conversation
* This method returns a promise
* @param {Bubble} bubble The bubble involved in this conversation * @return {Promise} The conversation (created or retrieved) or null in case of error */ openConversationForBubble(bubble : Bubble) : Promise{ let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(openConversationForBubble) bubble.id : ", bubble?.id); return new Promise(function (resolve, __reject) { if (!bubble) { return __reject({ code: ErrorManager.getErrorManager().BAD_REQUEST, label: "Parameter 'bubble' is missing or null" }); } else { that._logger.log(that.INFO, LOG_ID + "(openConversationForBubble), Try to create of get a conversation for bubble."); that._logger.log(that.INTERNAL, LOG_ID + "(openConversationForBubble), Try to create of get a conversation with bubble : ", bubble); that.getBubbleConversation(bubble.jid,undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined).then(function (conversation) { that._logger.log(that.INTERNAL, LOG_ID + "(openConversationForBubble), Conversation retrieved or created, conversation : ", conversation); resolve(conversation) }).catch(function (result) { that._logger.log(that.INTERNAL, LOG_ID + "(openConversationForBubble) Error : ", result); __reject(result); }); } }); } /** * @public * @nodered true * @method getS2SServerConversation * @since 1.65 * @category CONVERSATIONS * @instance * @description * get a conversation from id on S2S API Server.
* This method returns a promise
* @param {string} conversationId The id of the conversation to find. * @return {Conversation} The conversation (created or retrieved) or null in case of error */ getS2SServerConversation(conversationId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getS2SServerConversation) conversationId : ", conversationId); return new Promise(function (resolve, __reject) { if (!conversationId) { return __reject({ code: ErrorManager.getErrorManager().BAD_REQUEST, label: "Parameter 'conversationId' is missing or null" }); } else { that._logger.log(that.INFO, LOG_ID + "(getS2SServerConversation), Try to create of get a conversation for bubble."); that._logger.log(that.INTERNAL, LOG_ID + "(getS2SServerConversation), Try to create of get a conversation with bubble : ", conversationId); that.getS2SServerConversation(conversationId).then(function (conversationInfos) { that._logger.log(that.INTERNAL, LOG_ID + "(getS2SServerConversation), Conversation retrieved or created, conversation : ", conversationInfos); /*that._logger.log(that.INFO, LOG_ID + "[Conversation] Create bubble conversation (" + bubble.jid + ")"); let conversation = Conversation.createBubbleConversation(bubble); conversation.dbId = conversationId; conversation.lastModification = undefined; conversation.lastMessageText = undefined; conversation.muted = false; conversation.creationDate = new Date(); conversation.preload = false; conversation.lastMessageSender = undefined; conversation.missedCounter = 0; that.conversations[conversation.id] = conversation; resolve(conversation) */ resolve(conversationInfos) }).catch(function (result) { that._logger.log(that.INTERNAL, LOG_ID + "(getS2SServerConversation) Error : ", result); __reject(result); }); } }); } /** * @public * @nodered true * @method deleteServerConversation * @category CONVERSATIONS * @instance * @description * Allows to delete a conversation on server (p2p and bubbles)
* @param {string} conversationId of the conversation (id field) * @return {Promise} */ deleteServerConversation(conversationId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(deleteServerConversation) conversationId : ", conversationId); //that._logger.log(that.INFO, LOG_ID + "deleteServerConversation conversationId : ", conversationId); // Ignore conversation without dbId if (!conversationId) { return Promise.resolve(undefined); } return that._rest.deleteServerConversation(conversationId).then( (result ) => { // TODO ? that.orderConversations(); return Promise.resolve(result); }).catch( (err) => { that._logger.log(that.INTERNALERROR, LOG_ID + "(deleteServerConversation) err : ", err); // Check particular case where we are trying to remove an already removed conversation if (err.errorDetailsCode === 404002 || err.error.errorDetailsCode === 404002 ) { that._logger.log(that.INFO, LOG_ID + "deleteServerConversation success: " + conversationId); return Promise.resolve(undefined); } let errorMessage = "deleteServerConversation failure: " + err.error ? err.error.errorDetails : err.errorDetails; that._logger.log(that.WARN, LOG_ID + "Error."); that._logger.log(that.INTERNALERROR, LOG_ID + "Error : ", errorMessage); return Promise.reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage,errorMessage)); }); } /** * @private * @method * @category CONVERSATIONS * @instance * @description * Allows to mute notification in a conversations (p2p and bubbles)
* When a conversation is muted/unmuted, all user's resources will receive the notification
* @param {string} conversationId ID of the conversation (dbId field) * @param {Boolean} mute mutation state * @return {Promise} */ updateServerConversation(conversationId : string, mute : boolean) { // Ignore conversation without dbId if (!conversationId) { return Promise.resolve(undefined); } return this._rest.updateServerConversation(conversationId, mute); } /** * @public * @nodered true * @method sendConversationByEmail * @category CONVERSATIONS * @instance * @description * Allows to get the specified conversation as mail attachment to the login email of the current user (p2p and bubbles)
* can be used to backup a conversation between a rainbow user and another one, or between a user and a room,
* The backup of the conversation is restricted to a number of days before now. By default the limit is 30 days.
* @param {string} conversationDbId ID of the conversation (dbId field) * @param {Array} emails Allows to send the backup to users from an emails list.
When one email matchs with a Rainbow user loginEmail, the mail sent is localized using this user's language. * @param {string} lang="en" Language of the email notification if user language value is not available (for no Rainbow users). Default value : en * @async * @return {Promise} * @fulfil {Conversation[]} - Array of Conversation object * @category async */ sendConversationByEmail(conversationDbId : string, emails : Array = undefined, lang : string = "en" ) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(sendConversationByEmail) conversationDbId : ", conversationDbId); return this._rest.sendConversationByEmail(conversationDbId, emails, lang); } /** * @private * @method * @category CONVERSATIONS * @instance */ async getOrCreateOneToOneConversation(conversationId : string, conversationDbId? : string, lastModification? : string, lastMessageText? :string, missedIMCounter? : number, muted?: boolean, creationDate?:string) : Promise{ let that = this; return new Promise((resolve, reject) => { // Fetch the conversation let conv = that.getConversationById(conversationId); if (conv) { conv.preload = true; that._logger.log(that.INFO, LOG_ID + "getOrCreateOneToOneConversation, getConversationById found the conversation : " + conversationId + " " + conversationDbId + " " + missedIMCounter); resolve(conv); return; } that._logger.log(that.INFO, LOG_ID + "getOrCreateOneToOneConversation " + conversationId + " " + conversationDbId + " " + missedIMCounter); // No conversation found, then create it that._contactsService.getOrCreateContact(conversationId,undefined) /* Get or create the conversation*/ .then( (contact) => { that._logger.log(that.INFO, LOG_ID + "[Conversation] Create one to one conversation for contact.id : (" + contact.id + ")"); let conversation = Conversation.createOneToOneConversation(contact, that._logger, that._options._imOptions); conversation.lastModification = lastModification ? new Date(lastModification) : undefined; conversation.lastMessageText = lastMessageText; conversation.muted = muted; conversation.creationDate = creationDate ? new Date(creationDate) : new Date(); conversation.preload = false; // TODO ? that.computeCapabilitiesForContact(contact); conversation.dbId = conversationDbId; conversation.missedCounter = missedIMCounter ? missedIMCounter : 0; that.conversations[contact.jid_im] = conversation; return Promise.resolve(conversation); //return that.createServerConversation(conversation); }) .then( (conversation) => { // TODO ? $rootScope.$broadcast("ON_CONVERSATIONS_UPDATED_EVENT", conversation); resolve(conversation); }) .catch( (error) => { let errorMessage = "getOrCreateOneToOneConversation " + conversationId + " failure " + error.message; that._logger.log(that.ERROR, LOG_ID + "(getOrCreateOneToOneConversation) Error : ", error ); that._logger.log(that.INTERNALERROR, LOG_ID + "(getOrCreateOneToOneConversation) Error : ", errorMessage); return reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage,errorMessage)); }); }); } /** * @public * @nodered true * @method getConversationById * @category CONVERSATIONS * @instance * @description * Get a p2p conversation by id
* @param {string} conversationId Conversation id of the conversation to clean * @return {Conversation} The conversation to retrieve */ getConversationById(conversationId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationById) conversationId : ", conversationId); //that._logger.log(that.DEBUG, LOG_ID + " (getConversationById) conversationId : ", conversationId); if (!this.conversations) { return null; } let conv = this.conversations[conversationId]; that._logger.log(that.INTERNAL, LOG_ID + " (getConversationById) conversation by id, id of conversation found : ", conv ? conv.id : ""); if (!conv) { conv = that.getConversationByDbId(conversationId); that._logger.log(that.INTERNAL, LOG_ID + " (getConversationById) conversation not found by id, so searched by dbId result : ", conv); } return conv; } /** * @public * @nodered true * @method getConversationByDbId * @category CONVERSATIONS * @instance * @description * Get a conversation by db id
* @param {string} dbId db id of the conversation to retrieve * @return {Conversation} The conversation to retrieve */ getConversationByDbId(dbId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationByDbId) dbId : ", dbId); if (that.conversations && isDefined(dbId)) { for (let key in that.conversations) { if (that.conversations.hasOwnProperty(key) && that.conversations[key].dbId === dbId ) { return that.conversations[key]; } } } return null; }; /** * @public * @nodered true * @method getConversationByBubbleId * @category CONVERSATIONS * @instance * @description * Get a bubble conversation by bubble id
* @param {string} bubbleId Bubble id of the conversation to retrieve * @return {Conversation} The conversation to retrieve */ async getConversationByBubbleId(bubbleId : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationByBubbleId) bubbleId : ", bubbleId); // that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationByBubbleId) bubbleId : ", bubbleId); if (this.conversations) { for (let key in this.conversations) { if (this.conversations.hasOwnProperty(key) && this.conversations[key].bubble && this.conversations[key].bubble.id === bubbleId) { return this.conversations[key]; } } } return null; } /** * @public * @nodered true * @method getConversationByBubbleJid * @category CONVERSATIONS * @instance * @description * Get a bubble conversation by bubble id
* @param {string} bubbleJid Bubble jid of the conversation to retrieve * @return {Conversation} The conversation to retrieve */ getConversationByBubbleJid(bubbleJid : string) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationByBubbleJid) bubbleJid : ", bubbleJid); //that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getConversationByBubbleJid) bubbleJid : ", bubbleJid); if (this.conversations) { for (let key in this.conversations) { if (this.conversations.hasOwnProperty(key) && this.conversations[key].bubble && this.conversations[key].bubble.jid === bubbleJid) { return this.conversations[key]; } } } return null; } /** * @public * @nodered true * @method getBubbleConversation * @category CONVERSATIONS * @instance * @description * Get a conversation associated to a bubble (using the bubble ID to retrieve it)
* @param {string} bubbleJid JID of the bubble (dbId field) * @param {string} conversationDbId * @param {Date} lastModification * @param {string} lastMessageText * @param {number} missedIMCounter * @param {boolean} noError * @param {boolean} muted * @param {Date} creationDate * @param {string} lastMessageSender * @async * @return {Promise} * @fulfil {Conversation} - Conversation object or null if not found * @category async */ getBubbleConversation(bubbleJid : string, conversationDbId? : string, lastModification? : Date, lastMessageText? : string, missedIMCounter? : number, noError? : boolean, muted? : boolean, creationDate? : Date, lastMessageSender? : string) : Promise { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getBubbleConversation) bubbleJid : ", bubbleJid, ", conversationDbId : ", conversationDbId); // that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(getBubbleConversation) bubbleJid : ", bubbleJid); //that._logger.log(that.INTERNAL, LOG_ID + "getBubbleConversation bubbleJib : ", bubbleJid); // Fetch the conversation in memory let conversationResult = that.getConversationById(conversationDbId); if (conversationResult) { conversationResult.preload = true; that._logger.log(that.INTERNAL, LOG_ID + "(getBubbleConversation) conversation found by Id : ", conversationDbId, " : conversation : ", conversationResult); return Promise.resolve(conversationResult); } let conversation = that.getConversationByBubbleJid(bubbleJid); if (conversation) { conversation.preload = true; that._logger.log(that.INTERNAL, LOG_ID + "(getBubbleConversation) conversation found by BubbleJid : ", bubbleJid, " : conversation : ", conversation); return Promise.resolve(conversation); } // No conversation found, then create it return new Promise((resolve, reject) => { // Get the associated bubble that._bubblesService.getBubbleByJid(bubbleJid).then((bubble) => { if (!bubble) { that._logger.log(that.DEBUG, LOG_ID + "(getBubbleConversation) (" + bubbleJid + ") failure : no such bubble"); let obj = { jid: bubbleJid, conversationDbId: conversationDbId, lastModification: lastModification, lastMessageText: lastMessageText, missedIMCounter: missedIMCounter, muted: muted, creationDate: creationDate }; that.waitingBotConversations.push(obj); that.unlockWaitingBotConversations(); resolve(undefined); } else { that._logger.log(that.INFO, LOG_ID + "[Conversation] Create bubble conversation (" + bubble.jid + ")"); conversation = Conversation.createBubbleConversation(bubble, that._logger, that._options._imOptions); conversation.dbId = conversationDbId; conversation.lastModification = lastModification ? new Date(lastModification) : undefined; conversation.lastMessageText = lastMessageText; conversation.muted = muted; conversation.creationDate = creationDate ? new Date(creationDate) : new Date(); conversation.preload = false; conversation.lastMessageSender = lastMessageSender; if (missedIMCounter) { conversation.missedCounter = missedIMCounter; } that.conversations[conversation.id] = conversation; if (conversationDbId) { that.getRoomConferences(conversation).then(function () { that._eventEmitter.emit("evt_internal_conversationupdated", conversation); resolve(conversation); } // Create server side if necessary ); } else { // that.createServerConversation(conversation) Promise.resolve(conversation).then(function (__conversation) { if (bubble) { that._presenceService.sendInitialBubblePresenceSync(bubble).catch((errOfSent) => { that._logger.log(that.WARN, LOG_ID + "(getBubbleConversation) Error while sendInitialBubblePresenceSync : ", errOfSent); }); } // Send conversations update event that._eventEmitter.emit("evt_internal_conversationupdated", __conversation); resolve(__conversation); }).catch(async function (error) { let errorMessage = "(getBubbleConversation) (" + bubbleJid + ") failure : " + error.message; that._logger.log(that.ERROR, LOG_ID + "Error."); that._logger.log(that.INTERNALERROR, LOG_ID + "Error : ", errorMessage); await that.deleteServerConversation(conversationDbId); if (noError) { resolve(undefined); } else { return reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage, errorMessage)); } }); } } }).catch(async (error) => { let errorMessage = "(getBubbleConversation) (" + bubbleJid + ") failure : " + error.message; that._logger.log(that.ERROR, LOG_ID + "Error."); that._logger.log(that.INTERNALERROR, LOG_ID + "Error : ", errorMessage); await that.deleteServerConversation(conversationDbId); if (noError) { resolve(undefined); } else { return reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage, errorMessage)); } }); }); } /** * @public * @nodered true * @method closeConversation * @category CONVERSATIONS * @instance * @description * Close a conversation
* This method returns a promise
* @param {Conversation} conversation The conversation to close * @async * @return {Promise} * @fulfil {} Return nothing in case success * @category async */ closeConversation(conversation : Conversation) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(closeConversation) conversation.id : ", conversation?.id); return new Promise((resolve, reject) => { //that._logger.log(that.INFO, LOG_ID + "closeConversation " + conversation.id); // Remove this contact from favorite group that .deleteServerConversation(conversation.dbId) .then( () => { that.removeConversation(conversation); resolve(undefined); }) .catch( (error) => { return reject(error); }); }); } /** * @private * @method * @category CONVERSATIONS * @instance * @description * Remove locally a conversation
* This method returns a promise
* @param {Conversation} conversation The conversation to remove */ removeConversation(conversation : Conversation) { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(removeConversation) conversation.id : ", conversation?.id); //that._logger.log(that.INFO, LOG_ID + "remove conversation " + conversation.id); if (conversation.videoCall && conversation.videoCall.status !== Call.Status.UNKNOWN) { that._logger.log(that.INFO, LOG_ID + "Ignore conversation deletion message for conversation" + conversation.id); return; } delete that.conversations[conversation.id]; /* that.orderConversations(); let conversations = that.getOrderedConversations(); if (that.activeConversation && !(conversations.idle.indexOf(service.activeConversation) >= 0)) { if (conversations.idle.length > 0) { that.setActiveConversation(conversations.idle.first()); } else if (conversations.inCall.length > 0) { service.setActiveConversation(conversations.inCall.first()); } else { service.setActiveConversation(null); } }*/ // To avoid leak if (conversation.contact) { conversation.contact.conversation = null; conversation.contact = null; } that._eventEmitter.emit("evt_internal_conversationdeleted", { "conversationId": conversation.id}); //conversation = null; } /** * @public * @nodered true * @method cleanConversations * @category CONVERSATIONS * @instance * @async * @description * Allows to clean openned conversations. It keep openned the maxConversations last modified conversations. If maxConversations is not defined then keep the last 15 conversations.
* @return {Promise} the result of the deletion. * @category async */ async cleanConversations() { let that = this; that._logger.log(that.INFOAPI, LOG_ID + API_ID + "(cleanConversations) ."); return new Promise((resolve,reject) => { that._rest.getServerConversations(that.conversationsRetrievedFormat).then(async (conversations: []) => { resolve (await that.removeOlderConversations(conversations)); }).catch((error) => { that._logger.log(that.WARN, LOG_ID + "getServerConversations Failed to retrieve conversations for removeOlderConversations : ", error); that._logger.log(that.INTERNALERROR, LOG_ID + "getServerConversations Failed to retrieve conversations for removeOlderConversations : ", error); // The remove of old conversations is not mandatory, so lets continue the treatment. }); }); } /** * @private * @method * @category CONVERSATIONS * @instance * @description * Allows to get the list of existing conversations from server (p2p and bubbles)
* @return {Conversation[]} An array of Conversation object */ getServerConversations() { let that = this; return new Promise(async (resolve, reject) => { await that._rest.getServerConversations(that.conversationsRetrievedFormat).then(async (conversations : []) => { await that.removeOlderConversations(conversations); }).catch((error) => { that._logger.log(that.WARN, LOG_ID + "(getServerConversations) Failed to retrieve conversations for removeOlderConversations : ", error); that._logger.log(that.INTERNALERROR, LOG_ID + "(getServerConversations) Failed to retrieve conversations for removeOlderConversations : ", error); // The remove of old conversations is not mandatory, so lets continue the treatment. }); that._rest.getServerConversations(that.conversationsRetrievedFormat).then((conversations : []) => { // Create conversation promises let conversationPromises = []; that._logger.log(that.DEBUG, LOG_ID + "(getServerConversations) conversations.length retrieved : ", conversations.length); conversations.forEach(function (conversationData : any) { let missedImCounter = parseInt(conversationData.unreadMessageNumber, 10); let conversationPromise: Promise; let muted = (conversationData.mute === true); //that._logger.log(that.DEBUG, LOG_ID + "getServerConversations conversationData retrieved : ", conversationData); if (conversationData.type === "user") { conversationPromise = that.getOrCreateOneToOneConversation(conversationData.jid_im, conversationData.id, conversationData.lastMessageDate, conversationData.lastMessageText, missedImCounter, muted, conversationData.creationDate).catch( (err) => { that._logger.log(that.WARN, LOG_ID + "(getServerConversations) getOrCreateOneToOneConversation warn error : ", err); return err; }); } else { conversationPromise = that.getBubbleConversation(conversationData.jid_im, conversationData.id, conversationData.lastMessageDate, conversationData.lastMessageText, missedImCounter, true, muted, conversationData.creationDate, conversationData.lastMessageSender).catch( (err) => { that._logger.log(that.WARN, LOG_ID + "(getServerConversations) getBubbleConversation warn error : ", err); return err; }); } // */ conversationPromises.push(conversationPromise); }); // Resolve all promises return Promise .all(conversationPromises) /*.then(async (result) => { await that.removeOlderConversations(); return result; }) // */ .then((conversationsResult) => { //that.orderConversations(); resolve(conversationsResult); }) .catch((error) => { let errorMessage = "(getServerConversations) failure: " + error.message; that._logger.log(that.ERROR, LOG_ID + "error."); that._logger.log(that.INTERNALERROR, LOG_ID + "error : ", errorMessage); return reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage,errorMessage)); }); }) .catch((err) => { let errorMessage = "(getServerConversations) failure: no server response"; if (err) { errorMessage = "(getServerConversations) failure: " + JSON.stringify(err); } that._logger.log(that.ERROR, LOG_ID + "(getServerConversations) error : ", err); that._logger.log(that.INTERNALERROR, LOG_ID + "(getServerConversations) error : ", errorMessage); return reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage,errorMessage)); }); }); } /** * @private * @method * @category CONVERSATIONS * @instance * @description * Allows to create a conversations on server (p2p and bubbles)
* @param {Conversation} conversation of the conversation (dbId field) * @param {boolean} mute=false The true value when conversation is muted, false otherwise. Default value : false. * @return {Conversation} Created conversation object */ createServerConversation(conversation : Conversation, mute : boolean = false ) { let that = this; // Ignore already stored existing conversation if (conversation.dbId) { return Promise.resolve(conversation); } // Prepare global variables let data = {peerId:null, type: null, mute: null}; // Handle one to one conversation if (conversation.type === Conversation.Type.ONE_TO_ONE) { // Ignore conversation with user without dbId if (!conversation.contact.id) { return Promise.resolve(conversation); } // Fill conversation request data data.peerId = conversation.contact.id; data.type = "user"; } else if (conversation.type === Conversation.Type.BOT) { conversation.type = Conversation.Type.ONE_TO_ONE; // Ignore conversation with user without dbId if (!conversation.contact.id) { return Promise.resolve(conversation); } // Fill conversation request data data.peerId = conversation.contact.id; data.type = "bot"; } // Handle bubble conversation else { // Fill conversation request data data.peerId = conversation.bubble.id; data.type = "room"; } if (conversation.bubble && conversation.bubble.avatar) { let avatarRoom = conversation.bubble.avatar; } data.mute = mute; return this._rest.createServerConversation( data ).then((result : any)=> { that._logger.log(that.INFO, LOG_ID + "createServerConversation success: " + conversation.id); conversation.dbId = result.id; conversation.lastModification = result.lastMessageDate ? new Date(result.lastMessageDate) : undefined; conversation.creationDate = result.creationDate ? new Date(result.creationDate) : new Date(); conversation.missedCounter = parseInt(result.unreadMessageNumber, 10); /* if (avatarRoom) { conversation.bubble.avatar = avatarRoom; } */ // TODO ? that.orderConversations(); return Promise.resolve(conversation); }).catch( (err) => { let errorMessage = "createServerConversation failure: " + err.errorDetails; that._logger.log(that.ERROR, LOG_ID + "" + errorMessage); return Promise.reject(ErrorManager.getErrorManager().OTHERERROR(errorMessage,errorMessage)); }); } // */ removeOlderConversations (conversations? : [] ) { let that = this; return new Promise((resolve,reject) => { // if (!authService.fromSDK) { let maxConversations = that.nbMaxConversations; //add protection when the local storage does not work correctly ... if (!maxConversations || maxConversations < 15) { that.nbMaxConversations = 15; maxConversations = 15; } let orderedConversations = conversations? conversations.sort(that.sortFunction) : that.getConversations().sort(that.sortFunction); that._logger.log(that.DEBUG, LOG_ID + "(removeOlderConversations) -- maxConversations : ", maxConversations); if (orderedConversations.length > maxConversations) { that._logger.log(that.DEBUG, LOG_ID + "(removeOlderConversations) -- orderedConversations : ", orderedConversations.length); let removePromises = []; for (let index = maxConversations; index < orderedConversations.length; index++) { let conv = orderedConversations[index]; if (conv) { removePromises.push(that.deleteServerConversation(conv.id)); } else { that._logger.log(that.DEBUG, LOG_ID + "(removeOlderConversations) -- conversation undefined, so cannot delete it."); } } Promise.all(removePromises).then((result) => { resolve(result); }).catch((err) => { resolve(err); }); } else { resolve(undefined); } }); }; //endregion CONVERSATION //region EVENTS /** * @private */ async onRoomChangedEvent(__event, bubble, action) { let that = this; if (bubble) { let conversation = this.getConversationById(bubble.jid); if (conversation) { if (action === "remove") { await this.closeConversation(conversation).catch(err=>{ that._logger.log(that.WARN, LOG_ID + "(onRoomChangedEvent) closeConversation error : ", err); }); } else { conversation.bubble = bubble; } } } } /** * @private */ /* onRoomHistoryChangedEvent(__event, room) { if (room) { let conversation = this.getConversationById(room.jid); if (conversation && conversation.chatRenderer) { conversation.reset(); conversation .chatRenderer .loadMore(); } } } // */ /** * @private */ /* onRoomAdminMessageEvent(__event, roomJid, userJid, type, msgId) { that._logger.log(that.INFO, LOG_ID + " onRoomAdminMessageEvent"); let conversation = this.getConversationById(roomJid); if (conversation && (type === "welcome" || type === "conferenceAdd" || type === "conferenceRemove") && conversation.bubble && conversation.bubble.ownerContact) { userJid = conversation.bubble.ownerContact.jid; } let contact = this._contactsService.getContactByJid(userJid, true).catch((err)=>{return undefined;}); if (conversation && contact) { // If invitation msg and I'm not the owner if (!conversation.bubble.owner && type === "invitation") { return; } if (conversation.bubble && conversation.bubble.isMeetingBubble()) { return; } this._conversationServiceEventHandler.onRoomAdminMessageReceived(conversation, contact, type, msgId); } } */ //endregion EVENTS /*********************************************************************/ /** Remove the conversation history **/ /*********************************************************************/ /** * @private * */ reinit() { let that = this; return new Promise((resolve) => { that._logger.log(that.INFO, LOG_ID + " Re-initialize conversation service"); // Remove all my conversation for (let variableKey in that.conversations){ if (that.conversations.hasOwnProperty(variableKey)){ delete that.conversations[variableKey]; } } that.conversations = {}; if (that.autoLoadConversations) { // bot service is ready / TODO ? service.botServiceReady = true; Fetch // conversations from server that._rest.getServerConversations(that.conversationsRetrievedFormat).then(function () { // TODO ? service.linkAllActiveCallsToConversations(); resolve(undefined); }) .catch(function () { setInterval(() => { that._logger.log(that.INFO, LOG_ID + " getServerConversations failure, try again"); that._rest.getServerConversations(that.conversationsRetrievedFormat).then(function () { // TODO ? that.linkAllActiveCallsToConversations(); }).catch(err=>{ that._logger.log(that.WARN, LOG_ID + "(reinit) getServerConversations error : ", err); }); }, 10000);//, 1, true); resolve(undefined); }); } else { return; } }); } /*********************************************************************/ /** BOT SERVICE IS RUNNING, CREATE ALL BOT CONVERSATIONS **/ /*********************************************************************/ unlockWaitingBotConversations (isBotServiceReady?) { let that = this; if (isBotServiceReady) { that.botServiceReady = true; } if (that.botServiceReady) { //stop infinite loop in case of error that.botServiceReady = false; that.waitingBotConversations.forEach(async function(obj, index) { let contact : Contact = await that._contactsService.getContactByJid(obj.jid, false).catch((err)=>{ that._logger.log(that.ERROR, LOG_ID + "(unlockWaitingBotConversations) getContactByJid failed : ", err); return undefined;}); if (contact) { await that.getOrCreateOneToOneConversation(contact.jid, null, obj.lastModification, obj.lastMessageText, obj.missedIMCounter, obj.muted, obj.creationDate); that.waitingBotConversations.splice(index, 1); } }); that.waitingBotConversations = []; } } } module.exports.ConversationsService = ConversationsService; export {ConversationsService as ConversationsService};