diff options
author | Grigorii K. Shartsev <me@shgk.me> | 2023-07-27 21:33:24 +0200 |
---|---|---|
committer | backportbot-nextcloud[bot] <backportbot-nextcloud[bot]@users.noreply.github.com> | 2023-07-28 08:58:43 +0000 |
commit | 391e77fa139c2cad3b85c4ad79a8697af6ce415b (patch) | |
tree | 3b1efc11928a325c82f9a25f464068fb06b61e37 /src | |
parent | 4560eddedc6b75737c1f54c6f8b601ba5e9a2c76 (diff) |
fix(conversations): fix duplicating messages
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
Diffstat (limited to 'src')
-rw-r--r-- | src/store/conversationsStore.js | 98 | ||||
-rw-r--r-- | src/utils/__tests__/objectUtils.spec.js | 118 | ||||
-rw-r--r-- | src/utils/objectUtils.js | 61 |
3 files changed, 55 insertions, 222 deletions
diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index f3bc8b8a5..36e037354 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -62,7 +62,6 @@ import { startCallRecording, stopCallRecording, } from '../services/recordingService.js' -import { patchObject } from '../utils/objectUtils.js' const DUMMY_CONVERSATION = { token: '', @@ -85,6 +84,21 @@ const DUMMY_CONVERSATION = { isDummyConversation: true, } +/** + * Emit global event for user status update with the status from a 1-1 conversation + * + * @param {object} conversation - a 1-1 conversation + */ +function emitUserStatusUpdated(conversation) { + emit('user_status:status.updated', { + status: conversation.status, + message: conversation.statusMessage, + icon: conversation.statusIcon, + clearAt: conversation.statusClearAt, + userId: conversation.name, + }) +} + const state = { conversations: { }, @@ -118,16 +132,42 @@ const mutations = { }, /** - * Patch a conversation object: - * - add new properties - * - update existing properties recursively - * - delete properties + * Update stored conversation if a new one has changes * * @param {object} state the state * @param {object} conversation the new conversation object */ - patchConversation(state, conversation) { - patchObject(state.conversations[conversation.token], conversation) + updateConversationIfHasChanged(state, conversation) { + const oldConversation = state.conversations[conversation.token] + + // Update 1-1 conversation, if its status was changed + if (conversation.type === CONVERSATION.TYPE.ONE_TO_ONE + && (oldConversation.status !== conversation.status + || oldConversation.statusMessage !== conversation.statusMessage + || oldConversation.statusIcon !== conversation.statusIcon + || oldConversation.statusClearAt !== conversation.statusClearAt + ) + ) { + emitUserStatusUpdated(conversation) + state.conversations[conversation.token] = conversation + return + } + + // Update conversation if lastActivity updated (e.g. new message came up, call state changed) + if (oldConversation.lastActivity !== conversation.lastActivity) { + state.conversations[conversation.token] = conversation + return + } + + // Check if any property were changed (no properties except status-related supposed to be added or deleted) + for (const key of Object.keys(conversation)) { + // "lastMessage" is the only property with non-primitive (object) value and cannot be compared by === + // If "lastMessage" was actually changed, it is already checked by "lastActivity" + if (key !== 'lastMessage' && oldConversation[key] !== conversation[key]) { + state.conversations[conversation.token] = conversation + return + } + } }, /** @@ -209,7 +249,9 @@ const actions = { * @param {object} conversation the conversation; */ addConversation(context, conversation) { - context.dispatch('emitUserStatusUpdatedWhenChanged', conversation) + if (conversation.type === CONVERSATION.TYPE.ONE_TO_ONE) { + emitUserStatusUpdated(conversation) + } context.commit('addConversation', conversation) @@ -242,43 +284,13 @@ const actions = { }, /** - * Patch a conversation object and emit user status changes when needed + * Update conversation in store according to a new conversation object * * @param {object} context store context * @param {object} newConversation the new conversation object */ - patchConversation(context, newConversation) { - context.dispatch('emitUserStatusUpdatedWhenChanged', newConversation) - - // TODO: We can also check if lastActivity >= modifiedSince and skip the update - context.commit('patchConversation', newConversation) - }, - - /** - * Emits an event when the user status changed in a new conversation data - * - * @param {object} context the store context - * @param {object} conversation the new conversation - */ - emitUserStatusUpdatedWhenChanged(context, conversation) { - if (conversation.type === CONVERSATION.TYPE.ONE_TO_ONE && conversation.status) { - const prevStatus = context.state.conversations[conversation.token] - - // Only emit the event when something actually changed - if (!prevStatus - || prevStatus.status !== conversation.status - || prevStatus.statusMessage !== conversation.statusMessage - || prevStatus.statusIcon !== conversation.statusIcon - || prevStatus.statusClearAt !== conversation.statusClearAt) { - emit('user_status:status.updated', { - status: conversation.status, - message: conversation.statusMessage, - icon: conversation.statusIcon, - clearAt: conversation.statusClearAt, - userId: conversation.name, - }) - } - } + updateConversationIfHasChanged(context, newConversation) { + context.commit('updateConversationIfHasChanged', newConversation) }, /** @@ -297,7 +309,7 @@ const actions = { * Patch conversations: * - Add new conversations * - Remove conversations that are not in the new list - * - Patch existing conversations + * - Update existing conversations * * @param {object} context default store context * @param {object} payload the payload @@ -324,7 +336,7 @@ const actions = { if (currentConversations[token] === undefined) { context.dispatch('addConversation', newConversation) } else { - context.dispatch('patchConversation', newConversation) + context.dispatch('updateConversationIfHasChanged', newConversation) } } }, diff --git a/src/utils/__tests__/objectUtils.spec.js b/src/utils/__tests__/objectUtils.spec.js deleted file mode 100644 index 639d3e379..000000000 --- a/src/utils/__tests__/objectUtils.spec.js +++ /dev/null @@ -1,118 +0,0 @@ -/* - * @copyright Copyright (c) 2023 Grigorii Shartsev <me@shgk.me> - * - * @author Grigorii Shartsev <me@shgk.me> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -import { isObject, patchObject } from '../objectUtils.js' - -describe('objectUtils', () => { - describe('isObject', () => { - it('should return true for plain object', () => { - expect(isObject({})).toBeTruthy() - }) - - it('should return false for null', () => { - expect(isObject(null)).toBeFalsy() - }) - - it('should return false for Array', () => { - expect(isObject([])).toBeFalsy() - }) - }) - - describe('patchObject', () => { - it('should delete removed properties', () => { - const target = { - a: 1, - b: 2, - toRemove1: 3, - toRemove2: 4, - } - const newObject = { - a: 1, - b: 2, - } - patchObject(target, newObject) - expect(target).toEqual({ a: 1, b: 2 }) - }) - - it('should add new properties', () => { - const target = { - a: 1, - b: 2, - } - const newObject = { - a: 1, - b: 2, - newKey1: 3, - newKey2: 4, - } - patchObject(target, newObject) - expect(target).toEqual({ a: 1, b: 2, newKey1: 3, newKey2: 4 }) - }) - - it('should update existing primitive properties', () => { - const target = { - a: 1, - b: 2, - } - const newObject = { - a: 3, - b: 4, - } - patchObject(target, newObject) - expect(target).toEqual({ a: 3, b: 4 }) - }) - - it('should update existing array properties as primitive properties', () => { - const targetSubArray = [1, 2] - const target = { - a: targetSubArray, - } - const newObjectSubArray = [3, 4] - const newObject = { - a: newObjectSubArray, - } - patchObject(target, newObject) - expect(target).toEqual({ a: [3, 4] }) - expect(target.a).toBe(newObjectSubArray) // Re-assigned, not mutated - }) - - it('should update existing object properties recursively', () => { - const targetSubObject = { - c: 2, - d: 3, - } - const target = { - a: 1, - b: targetSubObject, - } - const newObject = { - a: 1, - b: { - c: 4, - d: 5, - }, - } - patchObject(target, newObject) - expect(target).toEqual({ a: 1, b: { c: 4, d: 5 } }) - expect(target.b).toBe(targetSubObject) // Mutated, not re-assigned - }) - }) -}) diff --git a/src/utils/objectUtils.js b/src/utils/objectUtils.js deleted file mode 100644 index 95508ec64..000000000 --- a/src/utils/objectUtils.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * @copyright Copyright (c) 2023 Grigorii Shartsev <me@shgk.me> - * - * @author Grigorii Shartsev <me@shgk.me> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -import Vue from 'vue' - -/** - * Check if value is a plain object - * - * @param {any} value value to check - * @return {boolean} true if value is a plain object - */ -export const isObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) - -/** - * Apply mutations to object based on new object - * - * @param {object} target target object - * @param {object} newObject new object to get changes from - */ -export function patchObject(target, newObject) { - // Delete removed properties - for (const key of Object.keys(target)) { - if (newObject[key] === undefined) { - Vue.delete(target, key) - } - } - - // Add new properties and update existing ones - for (const [key, newValue] of Object.entries(newObject)) { - const oldValue = target[key] - - if (oldValue === undefined) { - // Add new property - Vue.set(target, key, newValue) - } else if (isObject(oldValue) && isObject(newValue)) { - // This property is an object in both - update recursively - patchObject(oldValue, newValue) - } else { - // Update the property - Vue.set(target, key, newValue) - } - } -} |