summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGrigorii K. Shartsev <me@shgk.me>2023-07-27 21:33:24 +0200
committerbackportbot-nextcloud[bot] <backportbot-nextcloud[bot]@users.noreply.github.com>2023-07-28 08:58:43 +0000
commit391e77fa139c2cad3b85c4ad79a8697af6ce415b (patch)
tree3b1efc11928a325c82f9a25f464068fb06b61e37 /src
parent4560eddedc6b75737c1f54c6f8b601ba5e9a2c76 (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.js98
-rw-r--r--src/utils/__tests__/objectUtils.spec.js118
-rw-r--r--src/utils/objectUtils.js61
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)
- }
- }
-}