import { Injectable } from '@angular/core'; import { RochetChatConnectorService } from 'src/app/services/chat/rochet-chat-connector.service'; import { MessageService, MessageServiceDateLabel } from 'src/app/services/chat/message.service'; import { showDateDuration } from 'src/plugin/showDateDuration'; import { chatHistory } from 'src/app/models/chatMethod'; import { Storage } from '@ionic/storage'; import { Platform } from '@ionic/angular'; import { SqliteService } from 'src/app/services/sqlite.service'; import { NativeNotificationService } from 'src/app/services/native-notification.service'; import { SessionStore } from 'src/app/store/session.service'; import { capitalizeTxt } from 'src/plugin/text' import { SortService } from '../functions/sort.service'; import { chatUser } from 'src/app/models/chatMethod'; import { environment } from 'src/environments/environment'; import { ChatService } from 'src/app/services/chat.service'; import { NfService } from 'src/app/services/chat/nf.service'; import { v4 as uuidv4 } from 'uuid' import { ChatMethodsService } from './chat-methods.service'; import { DeleteMessageModel, MessageModel } from '../../models/beast-orm'; import { AESEncrypt } from '../aesencrypt.service'; import { IncomingChatMessage, ChatMessageInterface, falseTypingMethod } from 'src/app/models/message.model'; import { AttachmentsService } from 'src/app/services/attachments.service'; import { ConnectionStatus, NetworkServiceService} from 'src/app/services/network-service.service'; import { ChatSystemService } from './chat-system.service'; import { ViewedMessageService } from './viewed-message.service' import * as FIFOProcessQueue from 'fifo-process-queue'; import { NotificationsService } from '../notifications.service'; @Injectable({ providedIn: 'root' }) export class RoomService { messages: MessageService[] = [] storageMessage: any[] = []; lastMessage: MessageService; messagesBeforeOfflineAPI: MessageService[] = [] customFields: any; id = '' t = '' name = '' _updatedAt = {} hasLoadHistory = false restoreFromOffline = false duration = '' isTyping = false otherUserType = false lastTimeType = null message = '' lastMessageTxt = '' userThatIsTyping = '' messagesLocalReference: string[] = [] members: chatUser[] = [] membersExcludeMe: chatUser[] = [] u sessionStore = SessionStore countDownTime = '' chatOpen = false messageUnread = false status = { receive: { message: false, typing: false, readMessage: false, deleteMessage: false } } isGroup: boolean subscribeAttempt = false scrollDown = () => { } /** * @description get user list from ws-chat-methods.service * @returns chatUser[] */ getAllUsers = (): chatUser[] => { return [] } sortRoomList = () => {} chatServiceDeleteRoom = (roomId) => {} constructor( public RochetChatConnectorService: RochetChatConnectorService, private MessageService: MessageService, private storage: Storage, private platform: Platform, private sqlservice: SqliteService, private NativeNotificationService: NativeNotificationService, private sortService: SortService, private chatService: ChatService, private NfService: NfService, private ChatMethodsService: ChatMethodsService, private AESEncrypt: AESEncrypt, private AttachmentsService: AttachmentsService, private NetworkServiceService: NetworkServiceService, private ChatSystemService: ChatSystemService, private ViewedMessageService: ViewedMessageService, private notificationService: NotificationsService ) { /* this.NativeNotificationService.askForPermission() */ this.RochetChatConnectorService.getUserStatus((d) => { const userId = d.fields.args[0][0] const statusNum = d.fields.args[0][2] const statusText = this.statusNumberToText(statusNum) this.ViewedMessageService.request(this, userId, statusNum, statusText) }) this.RochetChatConnectorService.registerCallback({ type: 'Offline', funx: () => { /** * @description when the phone is in the background for a long time it could disconnects from the socket then the socket reconnects automatically, * when the connection is lost the subscribe is also lost, so we have to subscribe again when reconnection is establish. */ this.resetStatus(); this.hasLoadHistory = false this.subscribeAttempt = false } }); } /** * @description convert rocketchat statues num to readable string * @param text * @returns */ statusNumberToText(text) { if(text == '0') { return "offline" } else if(text == '1') { return "online" } else if(text == '2') { return "away" } else if(text == '3') { return "busy" } } resetStatus() { this.status = { receive: { message: false, typing: false, readMessage: false, deleteMessage: false } } } setData({membersExcludeMe, members, u, customFields = {}, id, name, t, lastMessage = new MessageService(this.NfService, this.RochetChatConnectorService, this.ChatMethodsService, this.AESEncrypt, this.AttachmentsService, this.NetworkServiceService, this.ChatSystemService,this.notificationService), _updatedAt }) { this.customFields = customFields this.id = id this.name = name this.t = t this.lastMessage = lastMessage this._updatedAt = _updatedAt this.u = u this.members = members this.membersExcludeMe = membersExcludeMe this.calDateDuration(); if(this.customFields?.countDownDate) { this.countDownDate(this.customFields.countDownDate); } } updateInfo() { // this.chatService.getRoomInfo(this.id).toPromise(); } get online() { if(!this.isGroup) { for(let user of this.ChatSystemService.users) { for (const members of this.membersExcludeMe) { if(members._id == user._id) { return user.status } } } } return 'offline' } countDownDate(date) { let difference = new Date(date).getTime() - new Date().getTime(); let c_day = Math.floor(difference/(1000*60*60*24)); let c_hours = Math.floor((difference % (1000*60*60*24)) / (1000*60*60)); let c_minutes = Math.floor((difference % (1000*60*60)) / (1000*60)); let c_seconds = Math.floor((difference % (1000*60)) / 1000); this.countDownTime = this.addZero(c_day) + " : " + this.addZero(c_hours) + " : " + this.addZero(c_minutes) + " : " + this.addZero(c_seconds) ; if(difference < 0) { this.deleteRoom(); } else { setTimeout(() => { this.countDownDate(date) }, 1000) } } addZero(i) { if (i < 10) { i = "0" + i; } return i; } deleteRoom() { this.countDownTime = "Expired"; let body = { "roomId": this.id } // this.chatService.getRoomInfo(this.id).subscribe( room =>{}); if(this.t === 'p') { this.chatService.deleteGroup(body).subscribe(res=>{ this.ChatSystemService.deleteRoom(this.id); this.ChatSystemService.getAllRooms(); this.chatServiceDeleteRoom(this.id); }); } else { this.chatService.deleteChannel(body).subscribe(res=>{ this.ChatSystemService.deleteRoom(this.id); this.ChatSystemService.getAllRooms(); this.chatServiceDeleteRoom(this.id); }); } } isSenderIsNotMe(ChatMessage) { return SessionStore.user.UserName != ChatMessage.u.username } senderId(ChatMessage) { return ChatMessage.u._id } receiveMessage() { this.RochetChatConnectorService.updateRoomEventss( this.id, "stream-room-messages", async (IncomingChatMessage:IncomingChatMessage) => { this.appendReceiveMessage.push(IncomingChatMessage) } ) this.RochetChatConnectorService.receiveStreamNotifyRoom((message) => { if(message.fields.eventName == this.id+'/'+'typing') { const args = message.fields.args if (typeof args[1] != 'object') { this.userThatIsTyping = this.usernameToDisplayName(args[0]) this.isTyping = args[1] this.otherUserType = args[1] const user = args[0] if(SessionStore.user.UserName != user) { this.readAllMessage() } } else if(args[0]?.method == 'viewMessage' || args[1]?.method == 'viewMessage') { const user = args[0] if(SessionStore.user.UserName != user) { this.readAllMessage() } } else if(args[0]?.method == 'deleteMessage' || args[1]?.method == 'deleteMessage') { this.deleteMessage(args[1]?.method?._id) } else { } } else if (message.fields.eventName == this.id+'/'+'deleteMessage') {} }) } appendReceiveMessage = FIFOProcessQueue(async (IncomingChatMessage:IncomingChatMessage, done) => { let IncomingChatMessageArgs = IncomingChatMessage.fields.args[0] let ChatMessage : ChatMessageInterface = this.fix_updatedAt(IncomingChatMessageArgs) let found = this.findMessageBy_id(ChatMessage._id) || this.findMessageBy_localReference(ChatMessage?.localReference) // || await this.findMessageInDBByData({_id:ChatMessage._id, localReference:ChatMessage.localReference }) if(!found) { ChatMessage.origin = 'stream' const message = await this.prepareCreate({message: ChatMessage, save: false}); this.registerSendMessage(message) message.from = 'stream' message.loadHistory = this.hasLoadHistory this.lastMessage = message; this.calDateDuration(ChatMessage._updatedAt); if (message.t == 'r') { this.name = message.msg; } if(this.isSenderIsNotMe(ChatMessage)) { /* this.NativeNotificationService.sendNotificationChat({ message: message.msg, title: this.name }); */ } if(this.hasLoadHistory == true) { this.messages.push(message) await message.addMessageDB() } else { this.messagesBeforeOfflineAPI.push(message) } this.messageUnread = true setTimeout(() => { this.scrollDown() }, 50) } if(ChatMessage?.t == "au" || ChatMessage?.t == "ru" || ChatMessage?.t == "ul") { this.updateContacts() } else if (ChatMessage?.t == "r") { this.name = ChatMessage.msg } setTimeout(() => { done() }, 5) }) getUsersByStatus(status: 'offline' | 'online') { return this.getAllUsers().filter((user => { for(const member of this.membersExcludeMe) { if(user._id == member._id && user.status == status) { return true } } })) } getRoomMembersIds(): string[] { try { return this.membersExcludeMe.map((user)=> user._id) } catch(error) { return [] } } getAllMemberThatIsNotOffline(): string[] { const membersIds = this.getRoomMembersIds() const allChatUsers = this.getAllUsers() const AllMemberThatIsNotOffline = [] for(let user of allChatUsers) { if(membersIds.includes(user._id)) { if(user.status != 'offline') { AllMemberThatIsNotOffline.push(user._id) } } } return AllMemberThatIsNotOffline } getAllMemberThatIsOffline(): string[] { const membersIds = this.getRoomMembersIds() const allChatUsers = this.getAllUsers() const AllMemberThatIsNotOffline = [] for(let user of allChatUsers) { if(membersIds.includes(user._id)) { if(user.status == 'offline') { AllMemberThatIsNotOffline.push(user._id) } } } return AllMemberThatIsNotOffline } async deleteMessageToReceive(userId) { const allDeleteMessages = await DeleteMessageModel.filter({rid: this.id}).execute() for(let message_ of allDeleteMessages) { if(message_.needToReceiveBy.includes(userId)) { message_.needToReceiveBy = message_.needToReceiveBy.filter((e)=> e != userId) this.sendFalseTypingReadMessage('deleteMessage',{_id:message_.messageId}) if(message_.needToReceiveBy.length == 0) { const deleteMessage = await DeleteMessageModel.get({messageId: message_.messageId}) await deleteMessage.delete() } else { await DeleteMessageModel.update(message_) } } } } async receiveMessageDelete() { this.RochetChatConnectorService.updateRoomEventss( this.id, "stream-notify-room", async (ChatMessage) => { const DeletedMessageId = ChatMessage.fields.args[0]._id; const message = this.messages.find((e) => e._id == DeletedMessageId) if(message.delate == false) { this.deleteMessage(DeletedMessageId) } } ) } /** * @description delete message in the view * @param id message ID */ async deleteMessage(_id) { const id = _id for (let i =0; i <= this.messages.length; i++) { if(this.messages[i]?._id == id ) { if (SessionStore.user.UserName == this.messages[i]?.u?.username) { const allMemberThatIsOffline = this.getAllMemberThatIsOffline() DeleteMessageModel.create({ messageId: this.messages[i]._id, rid: this.messages[i].rid, ts: this.messages[i].ts, u: this.messages[i].u, needToReceiveBy: allMemberThatIsOffline }) } this.messages[i]?.delateDB() this.messages.splice(i, 1) //Get previous last message from room const previousLastMessage = this.messages.slice(-1)[0]; if(previousLastMessage) { this.lastMessage = previousLastMessage; this.calDateDuration(previousLastMessage._updatedAt) this.sortRoomList() } return true } else { // } } } deleteAll() { this.messages.forEach((message) => { if(message?._id) { this.sendDeleteRequest(message._id) } }) } async delateMessageToSendToOthers(userId) { const deleteMessage = await DeleteMessageModel.all(); const toSend = deleteMessage.filter((DeleteMessage:string[])=> ! DeleteMessage.includes(userId)) } async sendDeleteRequest(msgId) { const message = this.messages.find((e)=>e._id == msgId) await message.delateStatusFalse() if(this.NetworkServiceService.getCurrentNetworkStatus() == ConnectionStatus.Online) { this.RochetChatConnectorService.deleteMessage(msgId).then(async() => { message.delateRequest = true await message.saveChanges(); this.deleteMessage(msgId); }) } else { this.RochetChatConnectorService.registerCallback({ type: 'reConnect', funx: async ()=> { this.sendDeleteRequest(msgId) return true } }) } } /** * @description sen text message */ async send({file = null, attachments = null, temporaryData = {}, attachmentsModelData = {}}) { if(file && this.message) { this.send({}) } const localReference = uuidv4(); let offlineChatMessage = { rid: this.id, msg: this.message, attachments, file, temporaryData, localReference, origin: 'local', attachmentsModelData } console.log('offlineChatMessage', offlineChatMessage) this.message= '' const message: MessageService = await this.prepareCreate({message:offlineChatMessage, save: environment.chatOffline}) this.registerSendMessage(message) if(this.hasLoadHistory == true) { await message.addMessageDB() } message.send() message.from = 'send' message.loadHistory = this.hasLoadHistory if (environment.chatOffline) { setTimeout(() => { this.scrollDown() }, 150) this.lastMessage = message this.calDateDuration(message._updatedAt) this.sortRoomList() } } /** * Register all send message so that * the incoming message wont be confuse to * other user the localReference is the identifier */ registerSendMessage(message: MessageService) { this.messagesLocalReference.push(message.localReference) } localReferenceExist(message: MessageService) { for( const localReference of this.messagesLocalReference) { if(localReference == message?.localReference) { return true } } return false } sendTyping(text:string = this.message) { if(this.lastMessageTxt == text) { return false } this.lastTimeType = new Date().getTime() const lastIsTyping = this.isTyping if(text.length >= 1) { this.isTyping = true } else { this.isTyping = false } if(lastIsTyping != this.isTyping) { this.RochetChatConnectorService.sendStreamNotifyRoom(this.id, SessionStore.user.UserName, 'typing', this.isTyping).catch((error) => console.error(error)); } this.lastMessageTxt = this.message this.typingWatch() } sendFalseTypingReadMessage(method,param: object) { this.RochetChatConnectorService.sendStreamNotifyRoom(this.id, SessionStore.user.UserName, 'typing', {method:method, params: param} as falseTypingMethod).catch((error) => console.error(error)) this.setTypingOff() } private typingWatch() { setTimeout(() => { const now = new Date().getTime() if((now - this.lastTimeType) >= 2888) { if(this.isTyping == true) { this.isTyping = false this.RochetChatConnectorService.sendStreamNotifyRoom(this.id, SessionStore.user.UserName, 'typing', this.isTyping).catch((error) => console.error(error)) } } }, 3000) } private setTypingOff() { this.sendTyping('') } roomLeave() { this.setTypingOff() this.chatOpen = false } open() { // this.typing(this.message) this.chatOpen = true this.messageUnread = false this.sendReadMessage() } leave(rid?) { this.RochetChatConnectorService.leaveRoom(this.id).catch((error) => console.error(error)) } isJson(str) { try { JSON.parse(str); } catch (e) { return ""; } return JSON.parse(str); } sortArrayISODate(messages: any): any[] { return messages.sort((a,b) => new Date(b._updatedAt ).getTime() - new Date(a._updatedAt).getTime()) } restoreOnce = false labelDates = [] goshPush(ChatMessage) { let currentDateMessage = new Date(ChatMessage._updatedAt).toLocaleDateString() if(currentDateMessage) { if(!this.labelDates.find( e => e == currentDateMessage)) { this.labelDates.push(currentDateMessage) const newMessage = this.fix_updatedAt({...ChatMessage}) newMessage.msg = currentDateMessage const cloneMessage = new MessageServiceDateLabel() cloneMessage.ChatSystemService = this.ChatSystemService cloneMessage.setData({...newMessage} as any) // cloneMessage._id = "" cloneMessage._updatedAt = "" cloneMessage.msg = currentDateMessage cloneMessage.dateLabel = true cloneMessage.delate = false if(new Date().toLocaleDateString() == currentDateMessage) { cloneMessage.msg = "hoje" } else { cloneMessage.msg = currentDateMessage } this.messages.push(cloneMessage as any) console.log(currentDateMessage) } } } async restoreMessageFromDB() { if(environment.chatOffline && this.restoreOnce == false) { this.restoreOnce = true const messages = this.sortArrayISODate(await MessageModel.filter({rid:this.id}).execute()).reverse() console.log('MessageModel', messages) for (let ChatMessage of messages) { this.goshPush(ChatMessage) const wewMessage = await this.simplePrepareMessage(ChatMessage) wewMessage.from = 'Offline' wewMessage.loadHistory = this.hasLoadHistory wewMessage.messageModelInstance = ChatMessage if(wewMessage.offline == false) { const message = await this.prepareCreate({message:ChatMessage}) message.from = 'Offline' message.loadHistory = this.hasLoadHistory wewMessage.messageModelInstance = ChatMessage message?.decryptMessage() } else { const offlineMessage = await this.prepareCreate({message:ChatMessage}) offlineMessage.from = 'Offline' offlineMessage.loadHistory = this.hasLoadHistory wewMessage.messageModelInstance = ChatMessage this.registerSendMessage(offlineMessage) offlineMessage?.decryptMessage() offlineMessage.send() } if(wewMessage.delate && !wewMessage.offline && !wewMessage.delateRequest) { this.sendDeleteRequest(wewMessage._id) } } if(!this.lastMessage) { this.lastMessage = messages.pop(); } setTimeout(() => { this.scrollDown() }, 50) } } // runs onces only loadHistoryCount = 0 async loadHistory({limit = 1000, forceUpdate = false }) { if(forceUpdate == false) { if (this.hasLoadHistory) { return false } } this.hasLoadHistory = true await this.restoreMessageFromDB() const chatHistory: chatHistory = await this.RochetChatConnectorService.loadHistory(this.id, limit) if(chatHistory?.result?.messages) { const users = this.getUsersByStatus('online') for(let message of chatHistory.result.messages.reverse()) { message.origin = 'history' message.from = 'History' const messagesToSave = await this.prepareMessageCreateIfNotExist({message: message}); if(messagesToSave != null) { messagesToSave.received = users.map((user) => user._id) messagesToSave.addMessageDB() } } for( const message of this.messagesBeforeOfflineAPI) { const messagesToSave = await this.prepareMessageCreateIfNotExist({message: message}); if(messagesToSave != null) { messagesToSave.received = users.map((user) => user._id) this.goshPush(messagesToSave) this.addMessageDB.push(messagesToSave) } } this.messagesBeforeOfflineAPI = [] setTimeout(() => { this.scrollDown() }, 50) } if(chatHistory?.result?.messages) { if(!this.lastMessage) { this.lastMessage = chatHistory?.result?.messages.pop() as any } } } addMessageDB = FIFOProcessQueue(async ({messagesToSave}, callback) => { await messagesToSave.addMessageDB() callback() }) async readAllMessage() { this.ViewedMessageService.requestReadAll(this) } async ChatMessageIsPresentInTheView(ChatMessage:ChatMessageInterface) { let foundIndex; const found = this.messages.find((MessageService, index) => { if (MessageService._id == ChatMessage._id) { foundIndex = index return true } else { return false } }) if (foundIndex) { return { found, foundIndex} } else { return false } } async prepareCreate({message, save = true}): Promise { message = this.fix_updatedAt(message) const wewMessage = new MessageService(this.NfService, this.RochetChatConnectorService, this.ChatMethodsService, this.AESEncrypt, this.AttachmentsService, this.NetworkServiceService, this.ChatSystemService,this.notificationService) wewMessage.setData(message) wewMessage.loadHistory = this.hasLoadHistory if(save) { this.messages.push(wewMessage) } return wewMessage } simplePrepareMessage(message) { message = this.fix_updatedAt(message) const wewMessage = new MessageService(this.NfService, this.RochetChatConnectorService, this.ChatMethodsService, this.AESEncrypt, this.AttachmentsService, this.NetworkServiceService, this.ChatSystemService,this.notificationService) wewMessage.setData(message) wewMessage.loadHistory = this.hasLoadHistory return wewMessage } private findMessageBy_id (id) { for( let m of this.messages) { if(m?._id == id) { return true } } return false } private findMessageBy_localReference (localReference) { for( let m of this.messages) { if(m?.localReference == localReference) { return true } } return false } private async findMessageInDBByLocalReference({localReference}) { const a = await MessageModel.get({localReference: localReference}) return typeof a.id == 'number' } private async findMessageInDBByLocalId({_id}) { const a = await MessageModel.get({_id: _id}) return typeof a.id == 'number' } private async findMessageInDBByData({localReference, _id}) { const a = await MessageModel.filter({localReference: localReference}).execute() if(a.length >= 1) { return true } const c = await MessageModel.filter({_id: _id}).execute() if(c.length >= 1) { return true } return false } async prepareMessageCreateIfNotExist({message}) { message = this.fix_updatedAt(message) let found = await this.findMessageBy_id(message._id) || await this.findMessageBy_localReference(message.localReference) // || await this.findMessageInDBByData({_id:message._id, localReference:message.localReference }) if (!found) { const wewMessage = this.simplePrepareMessage(message) this.messages.push(wewMessage) return wewMessage } else { return null } } private calDateDuration(date = null) { this.duration = showDateDuration(date || this._updatedAt); this._updatedAt = date || this._updatedAt } private fix_updatedAt(message): ChatMessageInterface { if (message?.result) { message.result._updatedAt = message.result._updatedAt['$date'] } else if(message?._updatedAt) { if(message._updatedAt.hasOwnProperty('$date')) { message._updatedAt = message._updatedAt['$date'] } } return message } usernameToDisplayName(username) { try { const firstName = capitalizeTxt(username.split('.')[0]) const lastName = capitalizeTxt(username.split('.')[1]) return firstName + ' ' + lastName } catch(error) { return username } } sendReadMessage() { this.RochetChatConnectorService.readMessage(this.id).catch((error) =>{ console.error(error) }) this.sendFalseTypingReadMessage('viewMessage', {}) this.messageUnread = false } async addContacts(userId:any) { let body = { "roomId": this.id, "userId": userId, } await this.chatService.addUserToGroup(body).toPromise(); this.ChatSystemService.getGroupRoom(this.id).updateContacts() } async updateContacts() { let res let error = false if(this.t == 'd') { try { res = await this.chatService.getMembers(this.id).toPromise(); } catch (e) { await this.chatService.refreshtoken(); error = true } if(error) { res = await this.chatService.getMembers(this.id).toPromise(); } } else { if (this.t === 'p') { try { res = await this.chatService.getGroupMembers(this.id).toPromise() } catch (e) { await this.chatService.refreshtoken(); error = true } if(error) { res = await this.chatService.getGroupMembers(this.id).toPromise() } } else { try { res = await this.chatService.getChannelMembers(this.id).toPromise() } catch (e) { await this.chatService.refreshtoken(); error = true } if(error) { res = await this.chatService.getChannelMembers(this.id).toPromise() } } } const members = res['members']; const users = members.filter(data => data.username != this.sessionStore.user.UserName); this.members = members this.membersExcludeMe = users } }