From 464ecca54ed375f5b30b1869e421daf9f9ee2026 Mon Sep 17 00:00:00 2001 From: Doljinsuren Enkhbayar Date: Tue, 2 Apr 2024 15:17:16 +0800 Subject: [PATCH 1/7] created chat service --- addon/services/chat.js | 3 +++ app/services/chat.js | 1 + tests/unit/services/chat-test.js | 12 ++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 addon/services/chat.js create mode 100644 app/services/chat.js create mode 100644 tests/unit/services/chat-test.js diff --git a/addon/services/chat.js b/addon/services/chat.js new file mode 100644 index 0000000..7a43cd6 --- /dev/null +++ b/addon/services/chat.js @@ -0,0 +1,3 @@ +import Service from '@ember/service'; + +export default class ChatService extends Service {} diff --git a/app/services/chat.js b/app/services/chat.js new file mode 100644 index 0000000..19d51c4 --- /dev/null +++ b/app/services/chat.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-core/services/chat'; diff --git a/tests/unit/services/chat-test.js b/tests/unit/services/chat-test.js new file mode 100644 index 0000000..defa706 --- /dev/null +++ b/tests/unit/services/chat-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'dummy/tests/helpers'; + +module('Unit | Service | chat', function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test('it exists', function (assert) { + let service = this.owner.lookup('service:chat'); + assert.ok(service); + }); +}); From 1198f4a2984ee5630a8c4199422fa46dfc72bfc4 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 3 Apr 2024 18:11:43 +0800 Subject: [PATCH 2/7] implemented basic chat service --- addon/services/chat.js | 88 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/addon/services/chat.js b/addon/services/chat.js index 7a43cd6..f9ed56f 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -1,3 +1,87 @@ -import Service from '@ember/service'; +import Service, { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { isArray } from '@ember/array'; +import { task } from 'ember-concurrency'; -export default class ChatService extends Service {} +export default class ChatService extends Service { + @service store; + @service currentUser; + @tracked channels = []; + @tracked openChannels = []; + + openChannel(chatChannelRecord) { + this.openChannels.pushObject(chatChannelRecord); + } + + closeChannel(chatChannelRecord) { + const index = this.openChannels.findIndex((_) => _.id === chatChannelRecord.id); + if (index >= 0) { + this.openChannels.removeAt(index); + } + } + + getOpenChannels() { + return this.openChannels; + } + + createChatChannel(name) { + const chatChannel = this.store.createRecord('chat-channel', { name }); + return chatChannel.save(); + } + + deleteChatChannel(chatChannelRecord) { + return chatChannelRecord.destroyRecord(); + } + + updateChatChannel(chatChannelRecord, props = {}) { + chatChannelRecord.setProperties(props); + return chatChannelRecord.save(); + } + + addParticipant(chatChannelRecord, userRecord) { + const chatParticipant = this.store.createRecord('chat-participant', { + chat_channel_uuid: chatChannelRecord.id, + user_uuid: userRecord.id, + }); + return chatParticipant.save(); + } + + removeParticipant(chatParticipant) { + return chatParticipant.destroyRecord(); + } + + async sendMessage(chatChannelRecord, senderRecord, messageContent = '') { + const chatMessage = this.store.createRecord('chat-message', { + chat_channel_uuid: chatChannelRecord.id, + sender_uuid: senderRecord.id, + content: messageContent, + }); + await chatMessage.save(); + chatChannelRecord.messages.pushObject(chatMessage); + await this.loadMessages.perform(chatChannelRecord); + } + + deleteMessage(chatMessageRecord) { + return chatMessageRecord.destroyRecord(); + } + + @task *loadMessages(chatChannelRecord) { + const messages = yield this.store.query('chat-message', { chat_channel_uuid: chatChannelRecord.id }); + chatChannelRecord.set('messages', messages); + return messages; + } + + @task *loadChannels(options = {}) { + const params = options.params || {}; + const channels = yield this.store.query('chat-channel', params); + if (isArray(channels)) { + this.channels = channels; + } + + if (typeof options.withChannels === 'function') { + options.withChannels(channels); + } + + return channels; + } +} From 6fc4a8f85bc22567ced0426866ba20e7546c3d32 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 3 Apr 2024 18:48:31 +0800 Subject: [PATCH 3/7] made chat service evented --- addon/services/chat.js | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/addon/services/chat.js b/addon/services/chat.js index f9ed56f..d9040f5 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -1,9 +1,10 @@ import Service, { inject as service } from '@ember/service'; +import Evented from '@ember/object/evented'; import { tracked } from '@glimmer/tracking'; import { isArray } from '@ember/array'; import { task } from 'ember-concurrency'; -export default class ChatService extends Service { +export default class ChatService extends Service.extend(Evented) { @service store; @service currentUser; @tracked channels = []; @@ -11,12 +12,14 @@ export default class ChatService extends Service { openChannel(chatChannelRecord) { this.openChannels.pushObject(chatChannelRecord); + this.trigger('chat.opened', chatChannelRecord); } closeChannel(chatChannelRecord) { const index = this.openChannels.findIndex((_) => _.id === chatChannelRecord.id); if (index >= 0) { this.openChannels.removeAt(index); + this.trigger('chat.closed', chatChannelRecord); } } @@ -26,16 +29,22 @@ export default class ChatService extends Service { createChatChannel(name) { const chatChannel = this.store.createRecord('chat-channel', { name }); - return chatChannel.save(); + return chatChannel.save().finally(() => { + this.trigger('chat.created', chatChannelRecord); + }); } deleteChatChannel(chatChannelRecord) { - return chatChannelRecord.destroyRecord(); + return chatChannelRecord.destroyRecord().finally(() => { + this.trigger('chat.deleted', chatChannelRecord); + }); } updateChatChannel(chatChannelRecord, props = {}) { chatChannelRecord.setProperties(props); - return chatChannelRecord.save(); + return chatChannelRecord.save().finally(() => { + this.trigger('chat.updated', chatChannelRecord); + }); } addParticipant(chatChannelRecord, userRecord) { @@ -43,11 +52,15 @@ export default class ChatService extends Service { chat_channel_uuid: chatChannelRecord.id, user_uuid: userRecord.id, }); - return chatParticipant.save(); + return chatParticipant.save().finally(() => { + this.trigger('chat.added_participant', chatChannelRecord, chatParticipant); + }); } removeParticipant(chatParticipant) { - return chatParticipant.destroyRecord(); + return chatParticipant.destroyRecord().finally(() => { + this.trigger('chat.removed_participant', chatChannelRecord, chatParticipant); + }); } async sendMessage(chatChannelRecord, senderRecord, messageContent = '') { @@ -56,13 +69,17 @@ export default class ChatService extends Service { sender_uuid: senderRecord.id, content: messageContent, }); - await chatMessage.save(); + await chatMessage.save().finally(() => { + this.trigger('chat.message_created', chatMessage, chatChannelRecord); + }); chatChannelRecord.messages.pushObject(chatMessage); await this.loadMessages.perform(chatChannelRecord); } deleteMessage(chatMessageRecord) { - return chatMessageRecord.destroyRecord(); + return chatMessageRecord.destroyRecord().finally(() => { + this.trigger('chat.message_deleted', chatMessageRecord); + }); } @task *loadMessages(chatChannelRecord) { From c3937a6831924df755709450aadf97c9df9791e9 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 5 Apr 2024 17:27:27 +0800 Subject: [PATCH 4/7] fixes to socket service, and updated methods in chat service --- addon/services/chat.js | 40 ++++++++++++++++++++++++++++++---------- addon/services/socket.js | 39 +++++++++++++++++++++++---------------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/addon/services/chat.js b/addon/services/chat.js index d9040f5..4f82157 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -3,6 +3,7 @@ import Evented from '@ember/object/evented'; import { tracked } from '@glimmer/tracking'; import { isArray } from '@ember/array'; import { task } from 'ember-concurrency'; +import isModel from '../utils/is-model'; export default class ChatService extends Service.extend(Evented) { @service store; @@ -28,8 +29,8 @@ export default class ChatService extends Service.extend(Evented) { } createChatChannel(name) { - const chatChannel = this.store.createRecord('chat-channel', { name }); - return chatChannel.save().finally(() => { + const chatChannelRecord = this.store.createRecord('chat-channel', { name }); + return chatChannelRecord.save().finally(() => { this.trigger('chat.created', chatChannelRecord); }); } @@ -53,13 +54,13 @@ export default class ChatService extends Service.extend(Evented) { user_uuid: userRecord.id, }); return chatParticipant.save().finally(() => { - this.trigger('chat.added_participant', chatChannelRecord, chatParticipant); + this.trigger('chat.added_participant', chatParticipant, chatChannelRecord); }); } - removeParticipant(chatParticipant) { + removeParticipant(chatChannelRecord, chatParticipant) { return chatParticipant.destroyRecord().finally(() => { - this.trigger('chat.removed_participant', chatChannelRecord, chatParticipant); + this.trigger('chat.removed_participant', chatParticipant, chatChannelRecord); }); } @@ -69,11 +70,17 @@ export default class ChatService extends Service.extend(Evented) { sender_uuid: senderRecord.id, content: messageContent, }); - await chatMessage.save().finally(() => { - this.trigger('chat.message_created', chatMessage, chatChannelRecord); - }); - chatChannelRecord.messages.pushObject(chatMessage); - await this.loadMessages.perform(chatChannelRecord); + await chatMessage + .save() + .then((chatMessage) => { + if (!this.messageExistInChat(chatChannelRecord, chatMessage)) { + chatChannelRecord.messages.pushObject(chatMessage); + } + return chatMessage; + }) + .finally(() => { + this.trigger('chat.message_created', chatMessage, chatChannelRecord); + }); } deleteMessage(chatMessageRecord) { @@ -82,6 +89,19 @@ export default class ChatService extends Service.extend(Evented) { }); } + insertMessageFromSocket(chatChannelRecord, data) { + const normalizedChatMessage = this.store.normalize('chat-message', data); + const chatMessageRecord = this.store.push(normalizedChatMessage); + if (this.messageExistInChat(chatChannelRecord, chatMessageRecord)) { + return; + } + chatChannelRecord.messages.pushObject(chatMessageRecord); + } + + messageExistInChat(chatChannelRecord, chatMessageRecord) { + return chatChannelRecord.messages.find((_) => _.id === chatMessageRecord.id) !== undefined; + } + @task *loadMessages(chatChannelRecord) { const messages = yield this.store.query('chat-message', { chat_channel_uuid: chatChannelRecord.id }); chatChannelRecord.set('messages', messages); diff --git a/addon/services/socket.js b/addon/services/socket.js index 0feed69..0e700b7 100644 --- a/addon/services/socket.js +++ b/addon/services/socket.js @@ -1,6 +1,7 @@ import Service from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { isBlank } from '@ember/utils'; +import { later } from '@ember/runloop'; import toBoolean from '../utils/to-boolean'; import config from 'ember-get-config'; @@ -29,22 +30,28 @@ export default class SocketService extends Service { } async listen(channelId, callback) { - const channel = this.socket.subscribe(channelId); - - // Track channel - this.channels.pushObject(channel); - - // Listen to channel for events - await channel.listener('subscribe').once(); - - // Listen for channel subscription - (async () => { - for await (let output of channel) { - if (typeof callback === 'function') { - callback(output); - } - } - })(); + later( + this, + async () => { + const channel = this.socket.subscribe(channelId); + + // Track channel + this.channels.pushObject(channel); + + // Listen to channel for events + await channel.listener('subscribe').once(); + + // Listen for channel subscription + (async () => { + for await (let output of channel) { + if (typeof callback === 'function') { + callback(output); + } + } + })(); + }, + 300 + ); } closeChannels() { From 70b1fe39fbb1de6cd51c0dc0567d74c26f90626b Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 8 Apr 2024 15:11:19 +0800 Subject: [PATCH 5/7] sendMessage in chat service should return the message record --- addon/services/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addon/services/chat.js b/addon/services/chat.js index 4f82157..6acb469 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -70,7 +70,7 @@ export default class ChatService extends Service.extend(Evented) { sender_uuid: senderRecord.id, content: messageContent, }); - await chatMessage + return chatMessage .save() .then((chatMessage) => { if (!this.messageExistInChat(chatChannelRecord, chatMessage)) { From 9fcf29a5d00e78cb4d48e7e73573ad2428632fdc Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Fri, 12 Apr 2024 19:12:58 +0800 Subject: [PATCH 6/7] patch file download utility, chat can now remember which is open, and several chat service improvements --- addon/services/app-cache.js | 8 ++- addon/services/chat.js | 133 ++++++++++++++++++++++++++++++++---- addon/utils/download.js | 7 +- 3 files changed, 131 insertions(+), 17 deletions(-) diff --git a/addon/services/app-cache.js b/addon/services/app-cache.js index a616f3d..4aaab00 100644 --- a/addon/services/app-cache.js +++ b/addon/services/app-cache.js @@ -38,8 +38,12 @@ export default class AppCacheService extends Service { return this; } - @action get(key) { - return this.localCache.get(`${this.cachePrefix}${dasherize(key)}`); + @action get(key, defaultValue = null) { + const value = this.localCache.get(`${this.cachePrefix}${dasherize(key)}`); + if (value === undefined) { + return defaultValue; + } + return value; } @action has(key) { diff --git a/addon/services/chat.js b/addon/services/chat.js index 6acb469..cb50571 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -3,16 +3,19 @@ import Evented from '@ember/object/evented'; import { tracked } from '@glimmer/tracking'; import { isArray } from '@ember/array'; import { task } from 'ember-concurrency'; +import { all } from 'rsvp'; import isModel from '../utils/is-model'; export default class ChatService extends Service.extend(Evented) { @service store; @service currentUser; + @service appCache; @tracked channels = []; @tracked openChannels = []; openChannel(chatChannelRecord) { this.openChannels.pushObject(chatChannelRecord); + this.rememberOpenedChannel(chatChannelRecord); this.trigger('chat.opened', chatChannelRecord); } @@ -22,6 +25,45 @@ export default class ChatService extends Service.extend(Evented) { this.openChannels.removeAt(index); this.trigger('chat.closed', chatChannelRecord); } + this.forgetOpenedChannel(chatChannelRecord); + } + + rememberOpenedChannel(chatChannelRecord) { + let openedChats = this.appCache.get('open-chats', []); + if (isArray(openedChats) && !openedChats.includes(chatChannelRecord.id)) { + openedChats.pushObject(chatChannelRecord.id); + } else { + openedChats = [chatChannelRecord.id]; + } + this.appCache.set('open-chats', openedChats); + } + + forgetOpenedChannel(chatChannelRecord) { + let openedChats = this.appCache.get('open-chats', []); + if (isArray(openedChats)) { + openedChats.removeObject(chatChannelRecord.id); + } else { + openedChats = []; + } + this.appCache.set('open-chats', openedChats); + } + + restoreOpenedChats() { + const openedChats = this.appCache.get('open-chats', []); + if (isArray(openedChats)) { + const findAll = openedChats.map((id) => this.store.findRecord('chat-channel', id)); + return all(findAll).then((openedChatRecords) => { + if (isArray(openedChatRecords)) { + for (let i = 0; i < openedChatRecords.length; i++) { + const chatChannelRecord = openedChatRecords[i]; + this.openChannel(chatChannelRecord); + } + } + return openedChatRecords; + }); + } + + return []; } getOpenChannels() { @@ -64,19 +106,26 @@ export default class ChatService extends Service.extend(Evented) { }); } - async sendMessage(chatChannelRecord, senderRecord, messageContent = '') { + async sendMessage(chatChannelRecord, senderRecord, messageContent = '', attachments = []) { const chatMessage = this.store.createRecord('chat-message', { chat_channel_uuid: chatChannelRecord.id, sender_uuid: senderRecord.id, content: messageContent, + attachment_files: attachments, }); + return chatMessage .save() - .then((chatMessage) => { - if (!this.messageExistInChat(chatChannelRecord, chatMessage)) { - chatChannelRecord.messages.pushObject(chatMessage); + .then((chatMessageRecord) => { + if (this.doesntExistsInFeed(chatChannelRecord, 'message', chatMessageRecord)) { + chatChannelRecord.feed.pushObject({ + type: 'message', + created_at: chatMessageRecord.created_at, + data: chatMessageRecord.serialize(), + record: chatMessageRecord, + }); } - return chatMessage; + return chatMessageRecord; }) .finally(() => { this.trigger('chat.message_created', chatMessage, chatChannelRecord); @@ -89,17 +138,77 @@ export default class ChatService extends Service.extend(Evented) { }); } - insertMessageFromSocket(chatChannelRecord, data) { - const normalizedChatMessage = this.store.normalize('chat-message', data); - const chatMessageRecord = this.store.push(normalizedChatMessage); - if (this.messageExistInChat(chatChannelRecord, chatMessageRecord)) { + insertChatMessageFromSocket(chatChannelRecord, data) { + // normalize and create record + const normalized = this.store.normalize('chat-message', data); + const record = this.store.push(normalized); + + // make sure it doesn't exist in feed already + if (this.existsInFeed(chatChannelRecord, 'message', record)) { return; } - chatChannelRecord.messages.pushObject(chatMessageRecord); + + // create feed item + const item = { + type: 'message', + created_at: record.created_at, + data, + record, + }; + + // add item to feed + chatChannelRecord.feed.pushObject(item); + } + + insertChatLogFromSocket(chatChannelRecord, data) { + // normalize and create record + const normalized = this.store.normalize('chat-log', data); + const record = this.store.push(normalized); + + // make sure it doesn't exist in feed already + if (this.existsInFeed(chatChannelRecord, 'log', record)) { + return; + } + + // create feed item + const item = { + type: 'log', + created_at: record.created_at, + data, + record, + }; + + // add item to feed + chatChannelRecord.feed.pushObject(item); + } + + insertChatAttachmentFromSocket(chatChannelRecord, data) { + // normalize and create record + const normalized = this.store.normalize('chat-attachment', data); + const record = this.store.push(normalized); + + // Find the chat message the record belongs to in the feed + const chatMessage = chatChannelRecord.feed.find((item) => { + return item.type === 'message' && item.record.id === record.chat_message_uuid; + }); + + // If we have the chat message then we can insert it to attachments + // This should work because chat message will always be created before the chat attachment + if (chatMessage) { + // Make sure the attachment isn't already attached to the message + const isNotAttached = chatMessage.record.attachments.find((attachment) => attachment.id === record.id) === undefined; + if (isNotAttached) { + chatMessage.record.attachments.pushObject(record); + } + } + } + + existsInFeed(chatChannelRecord, type, record) { + return chatChannelRecord.feed.find((_) => _.type === type && _.record.id === record.id) !== undefined; } - messageExistInChat(chatChannelRecord, chatMessageRecord) { - return chatChannelRecord.messages.find((_) => _.id === chatMessageRecord.id) !== undefined; + doesntExistsInFeed(chatChannelRecord, type, record) { + return chatChannelRecord.feed.find((_) => _.type === type && _.record.id === record.id) === undefined; } @task *loadMessages(chatChannelRecord) { diff --git a/addon/utils/download.js b/addon/utils/download.js index 4f3fc61..bd6a781 100644 --- a/addon/utils/download.js +++ b/addon/utils/download.js @@ -91,10 +91,11 @@ export default function download(data, strFileName, strMimeType) { anchor.className = 'download-js-link'; anchor.innerHTML = 'downloading...'; anchor.style.display = 'none'; - anchor.addEventListener('click', function (e) { + function handleClick(e) { e.stopPropagation(); - this.removeEventListener('click', arguments.callee); - }); + anchor.removeEventListener('click', handleClick); + } + anchor.addEventListener('click', handleClick); document.body.appendChild(anchor); later( this, From 0c8d482a7be0280ea4101ee12e7966f9f7ed5a2b Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Sat, 13 Apr 2024 19:05:08 +0800 Subject: [PATCH 7/7] completed chat service for basic functionality --- .eslintrc.js | 1 + addon/services/chat.js | 48 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 7ffba41..ccc278f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,6 +34,7 @@ module.exports = { 'ember/no-incorrect-calls-with-inline-anonymous-functions': 'off', 'ember/no-private-routing-service': 'off', 'no-useless-escape': 'off', + 'no-inner-declarations': 'off', 'n/no-unpublished-require': [ 'error', { diff --git a/addon/services/chat.js b/addon/services/chat.js index cb50571..eaee386 100644 --- a/addon/services/chat.js +++ b/addon/services/chat.js @@ -4,7 +4,6 @@ import { tracked } from '@glimmer/tracking'; import { isArray } from '@ember/array'; import { task } from 'ember-concurrency'; import { all } from 'rsvp'; -import isModel from '../utils/is-model'; export default class ChatService extends Service.extend(Evented) { @service store; @@ -14,6 +13,9 @@ export default class ChatService extends Service.extend(Evented) { @tracked openChannels = []; openChannel(chatChannelRecord) { + if (this.openChannels.includes(chatChannelRecord)) { + return; + } this.openChannels.pushObject(chatChannelRecord); this.rememberOpenedChannel(chatChannelRecord); this.trigger('chat.opened', chatChannelRecord); @@ -117,7 +119,7 @@ export default class ChatService extends Service.extend(Evented) { return chatMessage .save() .then((chatMessageRecord) => { - if (this.doesntExistsInFeed(chatChannelRecord, 'message', chatMessageRecord)) { + if (chatChannelRecord.doesntExistsInFeed('message', chatMessageRecord)) { chatChannelRecord.feed.pushObject({ type: 'message', created_at: chatMessageRecord.created_at, @@ -128,12 +130,14 @@ export default class ChatService extends Service.extend(Evented) { return chatMessageRecord; }) .finally(() => { + this.trigger('chat.feed_updated', chatMessage, chatChannelRecord); this.trigger('chat.message_created', chatMessage, chatChannelRecord); }); } deleteMessage(chatMessageRecord) { return chatMessageRecord.destroyRecord().finally(() => { + this.trigger('chat.feed_updated', chatMessageRecord); this.trigger('chat.message_deleted', chatMessageRecord); }); } @@ -144,7 +148,7 @@ export default class ChatService extends Service.extend(Evented) { const record = this.store.push(normalized); // make sure it doesn't exist in feed already - if (this.existsInFeed(chatChannelRecord, 'message', record)) { + if (chatChannelRecord.existsInFeed('message', record)) { return; } @@ -158,6 +162,10 @@ export default class ChatService extends Service.extend(Evented) { // add item to feed chatChannelRecord.feed.pushObject(item); + + // trigger event + this.trigger('chat.feed_updated', record, chatChannelRecord); + this.trigger('chat.message_created', record, chatChannelRecord); } insertChatLogFromSocket(chatChannelRecord, data) { @@ -166,7 +174,7 @@ export default class ChatService extends Service.extend(Evented) { const record = this.store.push(normalized); // make sure it doesn't exist in feed already - if (this.existsInFeed(chatChannelRecord, 'log', record)) { + if (chatChannelRecord.existsInFeed('log', record)) { return; } @@ -180,6 +188,10 @@ export default class ChatService extends Service.extend(Evented) { // add item to feed chatChannelRecord.feed.pushObject(item); + + // trigger event + this.trigger('chat.feed_updated', record, chatChannelRecord); + this.trigger('chat.log_created', record, chatChannelRecord); } insertChatAttachmentFromSocket(chatChannelRecord, data) { @@ -199,16 +211,34 @@ export default class ChatService extends Service.extend(Evented) { const isNotAttached = chatMessage.record.attachments.find((attachment) => attachment.id === record.id) === undefined; if (isNotAttached) { chatMessage.record.attachments.pushObject(record); + // trigger event + this.trigger('chat.feed_updated', record, chatChannelRecord); + this.trigger('chat.attachment_created', record, chatChannelRecord); } } } - existsInFeed(chatChannelRecord, type, record) { - return chatChannelRecord.feed.find((_) => _.type === type && _.record.id === record.id) !== undefined; - } + insertChatReceiptFromSocket(chatChannelRecord, data) { + // normalize and create record + const normalized = this.store.normalize('chat-receipt', data); + const record = this.store.push(normalized); + + // Find the chat message the record belongs to in the feed + const chatMessage = chatChannelRecord.feed.find((item) => { + return item.type === 'message' && item.record.id === record.chat_message_uuid; + }); - doesntExistsInFeed(chatChannelRecord, type, record) { - return chatChannelRecord.feed.find((_) => _.type === type && _.record.id === record.id) === undefined; + // If we have the chat message then we can insert it to receipts + // This should work because chat message will always be created before the chat receipt + if (chatMessage) { + // Make sure the receipt isn't already attached to the message + const isNotAttached = chatMessage.record.receipts.find((receipt) => receipt.id === record.id) === undefined; + if (isNotAttached) { + chatMessage.record.receipts.pushObject(record); + // trigger event + this.trigger('chat.receipt_created', record, chatChannelRecord); + } + } } @task *loadMessages(chatChannelRecord) {