"use strict"; //const Call = require("./Call"); import {Call} from "./Call"; //const uuid4 = require("uuid/v4"); import { v4 as uuid4 } from 'uuid'; import {Message} from "./Message"; import {Logger} from "../Logger.js"; import {FIFOQueue} from "../FIFOQueue.js"; import {formattStringOnNbChars, isDefined, isString, pause, pauseSync, safeJsonParse} from "../Utils.js"; import {randomUUID} from "node:crypto"; import {Queue} from "ts-generic-collections-linq"; import BackoffError from "../../connection/request-rate-limiter/src/BackoffError.js"; let AsyncLock = require('async-lock'); let locks = require('locks'); const LOG_ID = "CONVERSATION/CONV - "; class MessagesQueue extends FIFOQueue { private maxSize = 0; private rwlock: any; isFull: () => boolean; //updateMessageIfExistsElseEnqueueIt: (message: any, forceDequeueIfFull?: boolean) => Message; updateMessageIfExistsElseEnqueueIt: (message: any, forceDequeueIfFull?: boolean) => Message; removeMessage: (message: any) => Message; removeMessageById: (messageId: string) => Message; replaceMessage: (messageOld: any, messageNew : Message) => Message; replaceMessageById: (idMessageOld: string, messageNew : Message) => Message; constructor(_logger: any, _maxSize : number = 100) { super(_logger, (_logger) => { return { /* get: (object, key, args) => { if (key === 'length') { return object.length; } else if (typeof Array.prototype[key] == 'function') { //array methods if (typeof object[key] == 'function') { return object[key](); } else { return object[key]; //return this.emulateArrayMethod(object, key, container, getter); } } else { try { //access array by index if(key === parseInt(key).toString()) { if(0 <= key && key < object.length) { return object.queue[key]; } else { throw "index out of bondary"; } } else { throw "float index"; } } catch (err) { //access to object by literal return Reflect.get(object, key); } } }, // */ /* updateMessageIfExistsElseEnqueueIt : (message: any, forceDequeueIfFull: boolean = false) => { return 10; } // */ /* set: (object, key, value) => { object[key] = value; return true; } // */ }; }); this.updateMessageIfExistsElseEnqueueIt = (message: any, forceDequeueIfFull: boolean = false): Message => { let that: any = this; let timestamp = (new Date()).toUTCString(); let result: any; let messageObj: Message; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - timestamp : ", timestamp, " - id : ", id, " - updateMessageIfExistsElseEnqueueIt Message"); // that.lock(async () => { that.rwlock.writeLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); //that.logger.log("debug", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - id : ", id, " - dequeue will start."); // Check if this message already exist in message store let messageIndice = that.findIndex(function (item, index, tab) { return item.id===message.id }); if (messageIndice!= -1) { // update the already existing message and return this new value. // instance1 instanceof Message if (that[messageIndice] instanceof Message) { that[messageIndice].updateMessage(message); } else { that[messageIndice] = message; } messageObj = that[messageIndice]; that.logger.log("debug", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - id : ", id, " - message updated."); } else { // Store the message //that.queue.push(message); if (this.length >= this.maxSize) { if (!forceDequeueIfFull) { throw new Error("Queue is full"); } else { //this.dequeue(); that.logger.log("debug", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - id : ", id, " - queue is fulled, dequeue an elmt."); super.dequeue(); } } //this.enqueue(message); super.enqueue(message); messageObj = message; that.logger.log("debug", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - id : ", id, " - message enqueued."); } } catch (err) { that.logger.log("error", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); /* }, id).then(() => { that.logger.log("debug", LOG_ID + "(add) - id : ", id, " - lock succeed."); }).catch((error) => { that.logger.log("error", LOG_ID + "(add) - id : ", id, " - Catch Error, error : ", error); }); // */ } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(updateMessageIfExistsElseEnqueueIt) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return messageObj; }; this.removeMessage = (message: any): Message => { let that: any = this; let timestamp = (new Date()).toUTCString(); let result: any; let messageObj: Message; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(removeMessage) - timestamp : ", timestamp, " - id : ", id, " - removeMessage Message.id", message?.id); that.rwlock.writeLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); // that.logger.log("debug", LOG_ID + "(removeMessage) - id : ", id, " - removeMessage will start."); // Check if this message already exist in message store let messageIndice = that.findIndex(function (item, index, tab) { return item.id===message.id }); if (messageIndice!= -1) { // update the already existing message and return this new value. let messageDeleted = super.splice(messageIndice, 1); messageObj = messageDeleted.length > 0 ? messageDeleted[0]:undefined; } else { messageObj = undefined; } // that.logger.log("debug", LOG_ID + "(removeMessage) - id : ", id, " - splice started and finished."); } catch (err) { that.logger.log("error", LOG_ID + "(removeMessage) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(removeMessage) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return messageObj; }; this.removeMessageById = (messageId: string): Message => { return this.removeMessage({"id": messageId}); } this.replaceMessage = (messageOld: any, messageNew : Message): Message => { let that: any = this; let timestamp = (new Date()).toUTCString(); let result: any; let messageObj: Message; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(replaceMessage) - timestamp : ", timestamp, " - id : ", id, " - removeMessage messageOld.id", messageOld?.id, ", messageNew.id", messageNew?.id); that.rwlock.writeLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); // that.logger.log("debug", LOG_ID + "(replaceMessage) - id : ", id, " - replaceMessage will start."); // Check if this message already exist in message store let messageIndice = that.findIndex(function (item, index, tab) { return item.id===messageOld.id }); if (messageIndice!= -1) { // update the already existing message and return this new value. that[messageIndice] = messageNew; messageObj = that[messageIndice]; //let messageDeleted = super.splice(messageIndice, 1); //messageObj = messageDeleted.length > 0 ? messageDeleted[0]:undefined; } else { messageObj = undefined; } // that.logger.log("debug", LOG_ID + "(replaceMessage) - id : ", id, " - replaceMessage started and finished."); } catch (err) { that.logger.log("error", LOG_ID + "(replaceMessage) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(replaceMessage) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return messageObj; }; this.replaceMessageById = (idMessageOld: string, messageNew : Message): Message => { return this.replaceMessage({"id":idMessageOld}, messageNew); }; //region FIFOQueue // Ajoute un élément à la fin de la file d'attente this.enqueue = (item: Message, forceDequeueIfFull: boolean = false): void => { let that = this; let timestamp = (new Date()).toUTCString(); try { let id = item?.id; that.logger.log("debug", LOG_ID + "(enqueue) - timestamp : ", timestamp, " - id : ", id, " - enqueue Message"); that.rwlock.writeLock(() => { try { that.logger.log("debug", LOG_ID + "(enqueue) - id : ", id, " - enqueue will start."); if (this.queue.length >= this.maxSize) { if (!forceDequeueIfFull) { throw new Error("Queue is full"); } else { super.dequeue(); } } super.enqueue(item); that.logger.log("debug", LOG_ID + "(enqueue) - id : ", id, " - enqueue started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(enqueue) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(enqueue) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } } // Retire et retourne le premier élément de la file d'attente this.dequeue = (): Message | undefined => { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(dequeue) - timestamp : ", timestamp, " - id : ", id, " - dequeue Message"); that.rwlock.writeLock(() => { try { that.logger.log("debug", LOG_ID + "(dequeue) - id : ", id, " - dequeue will start."); result = super.dequeue(); that.logger.log("debug", LOG_ID + "(dequeue) - id : ", id, " - dequeue started and finished. Will pause before leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(dequeue) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(dequeue) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } // Retourne le premier élément de la file d'attente sans le retirer this.peek = (): Message | undefined => { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(peek) - timestamp : ", timestamp, " - id : ", id, " - peek Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(peek) - id : ", id, " - peek will start."); result = super.peek(); that.logger.log("debug", LOG_ID + "(peek) - id : ", id, " - peek started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(peek) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(peek) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } // Retourne vrai si la file d'attente est vide, faux sinon this.isEmpty = (): boolean => { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(isEmpty) - timestamp : ", timestamp, " - id : ", id, " - isEmpty Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(isEmpty) - id : ", id, " - isEmpty will start."); result = super.isEmpty(); that.logger.log("debug", LOG_ID + "(isEmpty) - id : ", id, " - isEmpty started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(isEmpty) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(isEmpty) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } this.isFull = (): boolean => { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(isFull) - timestamp : ", timestamp, " - id : ", id, " - isFull Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(isFull) - id : ", id, " - isFull will start."); result = this.length===this.maxSize; that.logger.log("debug", LOG_ID + "(isFull) - id : ", id, " - isFull started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(isFull) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(isFull) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } // Retourne la taille de la file d'attente this.size = (): number => { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; let lockdone = false; let lockProm: Promise; try { let id = randomUUID(); //that.logger.log("debug", LOG_ID + "(size) - timestamp : ", timestamp, " - id : ", id, " - size Message"); that.rwlock.readLock(() => { try { //that.logger.log("debug", LOG_ID + "(size) - id : ", id, " - size will start."); result = super.size(); //that.logger.log("debug", LOG_ID + "(size) - id : ", id, " - size started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(size) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(size) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } // Vide la file d'attente this.clear = (): void => { let that = this; let timestamp = (new Date()).toUTCString(); try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(clear) - timestamp : ", timestamp, " - id : ", id, " - clear Message"); that.rwlock.writeLock(() => { try { that.logger.log("debug", LOG_ID + "(clear) - id : ", id, " - clear will start."); super.clear(); that.logger.log("debug", LOG_ID + "(clear) - id : ", id, " - clear started and finished. Will leave lock."); } catch (err) { that.logger.log("error", LOG_ID + "(clear) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err: err}; that.logger.log("error", LOG_ID + "(clear) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } } // */ //endregion FIFOQueue this.toSmallString = () => { let that = this; let res="\n"; // 2024-07-03T15:48:17.322Z-Id:[node_e4093315-49fa-4772-b3c1-509af178939d19 ] - Content:[hello from node_6 ] - Deleted:[ ] - Modified:[ ] - Event:[] // @ts-ignore that.sort((msg1, msg2) => new Date(msg2.date) - new Date(msg1.date)); for (let i = 0; i < that.size(); i++) { let msg = that[i]; // res+= "MESSAGE from Queue, id : " + msg.id + ", side : " + msg.side + ", isEvent : " + msg.isEvent + ", event : " + msg.event + ", deleted : " + msg.deleted + ", modified : " + msg.modified + ", content : " + msg.content + "\n"; /* const [err, contentFromJson] = safeJsonParse(msg.content); if (err) { console.log('Failed to parse JSON: ' + err.message); } else { console.log(contentFromJson); } // */ res+=msg.date?.toJSON() + "-Id:[" + formattStringOnNbChars(msg.id) + "] - Content:[" + formattStringOnNbChars(isString(msg.content)? msg.content?.replace(/\n/g,"").replace(/\r/g,""):(msg.content?"" + JSON.stringify(msg.content) : "")) + "] - Deleted:[" + (msg.deleted?"X":" " ) + "] - Modified:[" + (msg.modified?"X":" " ) + "] - Event:[" + msg.event + "]\n"; } return res; } //region Array methods supercharged // Implémentation de toutes les méthodes de Array /*concat(...items: ConcatArray[]): Message[]; concat(...items: (Message | ConcatArray)[]): Message[]; concat(...items: any): Message[] { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(concat) - timestamp : ", timestamp, " - id : ", id, " - concat Message"); that.rwlock.readLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(concat) - id : ", id, " - concat will start."); result = super.concat(...items); that.logger.log("debug", LOG_ID + "(concat) - id : ", id, " - concat started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(concat) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(concat) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } copyWithin(target: number, start: number, end?: number): this { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(copyWithin) - timestamp : ", timestamp, " - id : ", id, " - copyWithin Message"); that.rwlock.readLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(copyWithin) - id : ", id, " - copyWithin will start."); result = super.copyWithin(target, start, end); that.logger.log("debug", LOG_ID + "(copyWithin) - id : ", id, " - copyWithin started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(copyWithin) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(copyWithin) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } entries(): IterableIterator<[number, Message]> { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(entries) - timestamp : ", timestamp, " - id : ", id, " - entries Message"); that.rwlock.readLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(entries) - id : ", id, " - entries will start."); result = super.entries(); that.logger.log("debug", LOG_ID + "(entries) - id : ", id, " - entries started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(entries) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(entries) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } every(callbackfn: (value: Message, index: number, array: Message[]) => unknown, thisArg?: any): boolean { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(every) - timestamp : ", timestamp, " - id : ", id, " - every Message"); that.rwlock.readLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(every) - id : ", id, " - every will start."); result = super.every(callbackfn, thisArg); that.logger.log("debug", LOG_ID + "(every) - id : ", id, " - every started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(every) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(every) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } fill(value: Message, start?: number, end?: number): this { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(fill) - timestamp : ", timestamp, " - id : ", id, " - fill Message"); that.rwlock.readLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(fill) - id : ", id, " - fill will start."); result = super.fill(value, start, end); that.logger.log("debug", LOG_ID + "(fill) - id : ", id, " - fill started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(fill) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(fill) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } find(predicate: any) { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(find) - timestamp : ", timestamp, " - id : ", id, " - find Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(find) - id : ", id, " - find will start."); result = this.queue.find(predicate); that.logger.log("debug", LOG_ID + "(find) - id : ", id, " - find started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(find) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(find) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } filter(predicate: any) { let that = this; let timestamp = (new Date()).toUTCString(); let result : any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(filter) - timestamp : ", timestamp, " - id : ", id, " - filter Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(filter) - id : ", id, " - filter will start."); result = this.queue.filter(predicate); that.logger.log("debug", LOG_ID + "(filter) - id : ", id, " - filter started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(filter) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(filter) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } forEach(predicate: any) { let that = this; let timestamp = (new Date()).toUTCString(); try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(forEach) - timestamp : ", timestamp, " - id : ", id, " - forEach Message"); that.rwlock.readLock(() => { try { that.logger.log("debug", LOG_ID + "(forEach) - id : ", id, " - forEach will start."); this.queue.forEach(predicate); that.logger.log("debug", LOG_ID + "(forEach) - id : ", id, " - forEach started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(forEach) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(forEach) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } } unshift(...items: Message[]) { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(unshift) - timestamp : ", timestamp, " - id : ", id, " - unshift Message"); that.rwlock.writeLock(() => { try { //deferedItem.start.bind(deferedItem)(); //await pause(that.timeBetweenXmppRequests); that.logger.log("debug", LOG_ID + "(unshift) - id : ", id, " - unshift will start."); result = super.unshift(...items); that.logger.log("debug", LOG_ID + "(unshift) - id : ", id, " - unshift started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(unshift) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } //await pause(that.timeBetweenXmppRequests); that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(unshift) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } values() : IterableIterator { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(values) - timestamp : ", timestamp, " - id : ", id, " - values Message"); that.rwlock.writeLock(() => { try { that.logger.log("debug", LOG_ID + "(values) - id : ", id, " - values will start."); result = super.values(); that.logger.log("debug", LOG_ID + "(values) - id : ", id, " - values started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(values) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(values) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } keys() : IterableIterator { let that = this; let timestamp = (new Date()).toUTCString(); let result: any; try { let id = randomUUID(); that.logger.log("debug", LOG_ID + "(keys) - timestamp : ", timestamp, " - id : ", id, " - keys Message"); that.rwlock.writeLock(() => { try { that.logger.log("debug", LOG_ID + "(keys) - id : ", id, " - keys will start."); result = super.keys(); that.logger.log("debug", LOG_ID + "(keys) - id : ", id, " - keys started and finished. Will pause before leave lock." ); } catch (err) { that.logger.log("error", LOG_ID + "(keys) - id : ", id, " - CATCH Error !!! in lock, error : ", err); } that.rwlock.unlock(); }); } catch (err) { let error = {err : err}; that.logger.log("error", LOG_ID + "(keys) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); throw error; } return result; } // */ //endregion Array methods supercharged //region SDK treatments // updateMessageIfExistsElseEnqueueIt(message: any, forceDequeueIfFull: boolean = false) : Message { // let that = this; // let timestamp = (new Date()).toUTCString(); // let result : any; // let messageObj: Message; // try { // let id = randomUUID(); // that.logger.log("debug", LOG_ID + "(add) - timestamp : ", timestamp, " - id : ", id, " - dequeue Message"); // // that.lock(async () => { // that.rwlock.writeLock(() => { // try { // //deferedItem.start.bind(deferedItem)(); // //await pause(that.timeBetweenXmppRequests); // that.logger.log("debug", LOG_ID + "(add) - id : ", id, " - dequeue will start."); // // Check if this message already exist in message store // let messageIndice = that.queue.findIndex(function(item, index, tab) { // return item.id === message.id // }); // if (messageIndice != -1) { // // update the already existing message and return this new value. // that.queue[messageIndice] = message; // messageObj = that.queue[messageIndice]; // } else { // // Store the message // //that.queue.push(message); // if (this.queue.length >= this.maxSize ) { // if (!forceDequeueIfFull) { // throw new Error("Queue is full"); // } else { // super.dequeue(); // } // } // super.enqueue(message); // messageObj = message; // } // that.logger.log("debug", LOG_ID + "(add) - id : ", id, " - dequeue started and finished. Will pause before leave lock." ); // } catch (err) { // that.logger.log("error", LOG_ID + "(add) - id : ", id, " - CATCH Error !!! in lock, error : ", err); // } // //await pause(that.timeBetweenXmppRequests); // that.rwlock.unlock(); // }); // /* }, id).then(() => { // that.logger.log("debug", LOG_ID + "(add) - id : ", id, " - lock succeed."); // }).catch((error) => { // that.logger.log("error", LOG_ID + "(add) - id : ", id, " - Catch Error, error : ", error); // }); // */ // } catch (err) { // let error = {err : err}; // that.logger.log("error", LOG_ID + "(add) - timestamp : ", timestamp, " - CATCH Error !!! error : ", error); // throw error; // } // return messageObj; // } //endregion SDK treatments this.logger = _logger; if (_maxSize <= 0) { throw new Error("Maximum size should be greater than zero"); } this.maxSize = _maxSize; this.rwlock = locks.createReadWriteLock(); } get length(){ let result = this.size(); return result; } // */ //region operators /* get (key) { return this.queue[key]; } // */ // Méthode pour obtenir un itérateur /* [Symbol.for("[]")](index: any): any { return this.queue[index]; } // */ /* [Symbol.iterator](): Iterator { let index = 0; const dataArray = this.queue; // Fonction next() de l'itérateur const next = (): IteratorResult => { if (index < dataArray.length) { return { value: dataArray[index++], done: false }; } else { return { value: undefined, done: true }; } }; // Retourne l'itérateur return { next }; } // */ //endregion operators } /** * @class * @name Conversation * @public * @description * This class represents a conversation
* A conversation is a "long" interaction (aka the "long tail") between the user ane one or several contacts (Rainbow users or not) based on the IM media if the recipients are Rainbow users.
* A conversation never ends and all interactions done can be retrieved.
*/ class Conversation { get messages(): MessagesQueue { //this.logger.log("internal", LOG_ID + "(get messages) id : ", this.id, ", get messages : ", this._messages); return this._messages; } set messages(value: any) { //this.logger.log("internal", LOG_ID + "(set messages) id : ", this.id, ", set messages : ", value); this._messages = value; } /* updateMessages(index: number , value: any) { //this.logger.log("internal", LOG_ID + "(updateMessages) id : ", this.id, ", add message : ", value); this._messages[index] = value; } // */ public id: any; public dbId: any; public type: any; public owner: any; public contact: any; public bubble: any; public capabilities: any; public avatar: any; public presenceStatus: any; public name: any; public filterName: any; public missedCounter: any; public missedCalls: any; //private _messages: any; private _messages: MessagesQueue; public participantStatuses: any; public draft: any; public uploadFile: any; public status: any; public historyIndex: any; public historyMessages: any; public historyDefered: any; public historyComplete: any; public lastModification: any; public creationDate: any; public lastMessageText: any; public lastMessageSender: any; public pip: any; public videoCall: any; public audioCall: any; public pstnConferenceSession: any; public webConferenceSession: any; public isMutedAudio: any; public isMutedVideo: any; public infoVisible: any; public muted: any; public randomBase: any; public messageId: any; public currentHistoryId: any; public static Status: any; public static Type: any; public static PEERTYPE: any; private static randomBase: string; private static messageId: string; preload: boolean; isFavorite: boolean; bookmark : { "messageId" : string, "timestamp" : string, "unreadMessageNumber" : string }; pendingPromise: Array; private logger : any; constructor(conversationId, logger : Logger, imsOptions) { /** * @public * @property {string} id The conversation ID * @readonly */ this.id = conversationId; this.logger = logger ? logger : console; /** * @public * @property {string} dbId The database ID * @readonly */ this.dbId = null; /** * @public * @property {ConversationType} type The type of the conversation. Can be ONE_TO_ONE (0), BUBBLE (1) or BOT (2) * @readonly */ this.type = null; /** * @private * @readonly */ this.owner = null; /** * @public * @property {Contact} contact (ONE_TO_ONE conversation only) The recipient of the conversation * @link Contact * @readonly */ this.contact = null; /** * @public * @property {Bubble} bubble (BUBBLE conversation only) The conversation bubble * @link Bubble * @readonly */ this.bubble = null; /** * @public * @property {Object} capabilities The capabilities of the conversation * @readonly */ this.capabilities = null; // Display information /** * @public * @property {Object} avatar (ONE_TO_ONE conversation only) The avatar of the conversation * @readonly */ this.avatar = null; /** * @private * @readonly */ this.presenceStatus = null; /** * @private * @readonly */ this.name = function () { return {}; }; /** * @public * @property {string} filterName The name of the conversation (the display name of the recipient for ONE_TO_ONE conversation or the room name for a ROOM conversation) * @readonly */ this.filterName = ""; /** * @public * @property {number} missedCounter The number of instant messages not read * @readonly */ this.missedCounter = 0; /** * @public * @property {number} missedCalls (ONE_TO_ONE conversation only) The number of call missed with this recipient (only WebRTC call) * @readonly */ this.missedCalls = 0; /** * @public * @property {Message[]} messages The list of messages downloaded for this conversation * @link Message * @readonly */ this._messages = new MessagesQueue(logger, imsOptions.maxMessagesStoredInConversation?imsOptions.maxMessagesStoredInConversation:1000); //this._messages = []; /** * @private * @readonly */ this.participantStatuses = {}; /** * @private * @readonly */ this.draft = ""; /** * @private * @readonly */ this.uploadFile = null; /** * @public * @property {ConversationStatus} status The status of the conversation * @readonly */ this.status = Conversation.Status.ACTIVE; // History stuff /** * @private * @readonly */ this.historyIndex = -1; /** * @private * @readonly */ this.historyMessages = []; /** * @private * @readonly */ this.historyDefered = null; /** * @public * @property {Boolean} True if the history has been completely loaded * @readonly */ this.historyComplete = false; // LastModification /** * @public * @property {Date} lastModification The date of the last modification of the conversation * @readonly */ this.lastModification = undefined; // CreationDate /** * @public * @property {Date} creationDate The date of the creation of the conversation * @since 1.21 * @readonly */ this.creationDate = new Date(); // LastMessageText /** * @public * @property {string} lastMessageText The text of the last message received of the conversation * @readonly */ this.lastMessageText = ""; // LastMessageSenderID /** * @public * @property {string} lastMessageSender The ID of the user for the last message * @readonly */ this.lastMessageSender = ""; // Picture in picture /** * @private * @readonly */ this.pip = true; // Call references /** * @public * @property {Call} videoCall Link to a WebRTC call (audio/video/sharing) if exists * @readonly */ this.videoCall = { status: Call.Status.UNKNOWN }; /** * @public * @property {Call} audioCall Link to a telephony call (from a PBX) if exists * @readonly */ this.audioCall = null; /** * @public * @property {ConferenceSession} pstnConferenceSession Link to a pstn conference session if exists * @readonly * @since 1.30 */ this.pstnConferenceSession = null; /** * @public * @property {ConferenceSession} webConferenceSession Link to a webrtc conference session if exists * @readonly * @since 1.30 */ this.webConferenceSession = null; //is muted /** * @private * @readonly */ this.isMutedAudio = false; /** * @private * @readonly */ this.isMutedVideo = false; /** * @private * @readonly */ this.infoVisible = null; //is conversation muted this.muted = false; this.pendingPromise = undefined; //message ID let randomBase = this.generateRandomID(); let messageId = 0; this.logger.log("debug", LOG_ID + "(Conversation) constructed : ", this.id); } /** * @private * @method addMessage * @memberof Conversation * @instance */ addOrUpdateMessage(message) { let that = this; let messageObj = undefined ; this.logger.log("debug", LOG_ID + "(addOrUpdateMessage) id : ", this.id, ", message : ", message?message.id:undefined); // Check if this message already exist in message store messageObj = that.messages.updateMessageIfExistsElseEnqueueIt(message, true); /* let messageIndice = that.messages.findIndex(function(item, index, tab) { return item.id === message.id }); if (messageIndice != -1) { // update the already existing message and return this new value. that.updateMessages(messageIndice, message); messageObj = that.messages[messageIndice]; } else { // Store the message that.messages.push(message); messageObj = message; } */ // Update lastModification that.lastModification = new Date(); // Update lastMessageText that.lastMessageText = message.content; //update last activity date for rooms when we receive/sent messages if (this.bubble) { // dev-code-console // //console.log("conversation bubble : ", this.bubble); this.logger.log("internal", LOG_ID + "(addOrUpdateMessage) id : ", this.id, ", bubble : ", this.bubble.id); // end-dev-code-console // this.bubble.lastActivityDate = this.lastModification; } return messageObj; } /*************************************************************/ /* STATIC FACTORIES */ /*************************************************************/ static createOneToOneConversation(participant : any, logger : Logger, imsOptions : any ) { // Create the conversation object let conversation = new Conversation(participant.jid_im, logger, imsOptions); // Attach it to contact conversation.contact = participant; participant.conversation = conversation; // Fill display information if (participant.isBot) { conversation.avatar = ""; conversation.type = Conversation.Type.BOT; } else { conversation.avatar = participant.avatar ? participant.avatar.src : null; conversation.type = Conversation.Type.ONE_TO_ONE; } conversation.name = participant.name; // TODO ? conversation.filterName = // utilService.removeDiacritis(participant.displayName.toLowerCase()); return conversation; } static createBubbleConversation(bubble, logger: Logger, imsOptions) { // Create the conversation object let conversation = new Conversation(bubble.jid, logger, imsOptions); conversation.type = Conversation.Type.ROOM; conversation.bubble = bubble; // TODO ? conversation.filterName = // utilService.removeDiacritis(room.name.toLowerCase()); return conversation; } generateRandomID() { return uuid4(); } static getUniqueMessageId() { let messageToSendID = "node_" + this.randomBase + this.messageId; this.messageId = this.messageId + 1; return messageToSendID; } /*************************************************************/ /* PUBLIC STATIC METHODS */ /*************************************************************/ static stringToStatus(status) { switch (status) { case "composing": return Conversation.Status.COMPOSING; case "paused": return Conversation.Status.PAUSED; default: return Conversation.Status.ACTIVE; } } /*************************************************************/ /* PUBLIC METHODS */ /*************************************************************/ reset() { this.logger.log("debug", LOG_ID + "(reset) id : ", this.id); //this.messages = []; this.messages.clear(); this.lastMessageText = null; this.resetHistory(); } resetHistory() { this.logger.log("debug", LOG_ID + "(resetHistory) id : ", this.id); //this.messages = []; //this.lastMessageText = null; this.historyIndex = -1; this.historyMessages = []; this.historyComplete = false; this.currentHistoryId = null; } getMessageById(messId) { return this ._messages .find((item) => { return item.id === messId; }); } getlastEditableMsg() { let messgs = this._messages.filter((mess) => { return (mess.side === Message.Side.RIGHT) ; }); if (messgs.length === 0) { return undefined; } let latestObject = messgs[0]; for (let i = 1; i < messgs.length; i++) { const currentDate = new Date(latestObject.date); const nextDate = new Date(messgs[i].date); if (nextDate > currentDate) { latestObject = messgs[i]; } } return latestObject; /* messgs.sort((a, b) => { let dateElmt1 = new Date(a.date); let dateElmt2 = new Date(b.date); return dateElmt2.getTime() - dateElmt1.getTime(); }); return messgs[0]; // return this.messages.slice(-1)[0]; // */ } } /** * Enum conversation type * @public * @enum {number} * @readonly */ Conversation.Type = { /** One-to-one conversation */ ONE_TO_ONE: 0, /** Room conversation with multiple participants */ ROOM: 1, /** Conversation with a Bot */ BOT: 2 }; /** * Enum conversation status * @public * @enum {Object} * @readonly */ Conversation.Status = { /** Active conversation */ ACTIVE: { key: 0, value: "active" }, /** Inactive conversation */ INACTIVE: { key: 1, value: "inactive" }, /** When composing a message */ COMPOSING: { key: 2, value: "composing" }, /** When a message is written but not sent */ PAUSED: { key: 3, value: "paused" } }; /** * Enum peer type * * ``` * { * "UNKNOWN" = "unknown", // when the peer type is unkwon * "USER" = "user", // when the peer type is a user of the rainbow system * "ROOM" = "room" // when the peer type is a bubble * } * ``` * * @public * @enum {number} * @readonly * */ enum PEERTYPE { /** * when the peer type is unkwon */ "UNKNOWN"= "unknown", /** * when the peer type is a user of the rainbow system */ "USER" = "user", /** * when the peer type is a bubble */ "ROOM" = "room" }; module.exports.Conversation = Conversation; module.exports.MessagesQueue = MessagesQueue; module.exports.PEERTYPE = PEERTYPE; export {Conversation, MessagesQueue, PEERTYPE}; //export {Conversation, MessagesQueue};