diff --git a/src/App.vue b/src/App.vue index e1bc9614a269..52c812215de4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -244,6 +244,10 @@ export default { EventBus.$on('should-refresh-conversations', this.debounceRefreshCurrentConversation) } + // Restore last fetched conversations from browser storage, + // before updated ones come from server + this.$store.dispatch('getConversationsFromStorage') + if (this.$route.name === 'conversation') { // Update current token in the token store this.$store.dispatch('updateToken', this.$route.params.token) diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index 36e037354e01..2487b5f470d7 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -31,6 +31,7 @@ import { PARTICIPANT, WEBINAR, } from '../constants.js' +import BrowserStorage from '../services/BrowserStorage.js' import { makePublic, makePrivate, @@ -339,6 +340,47 @@ const actions = { context.dispatch('updateConversationIfHasChanged', newConversation) } } + + context.dispatch('putConversationsToStorage') + }, + + /** + * Restores conversations from BrowserStorage and add them to the store state + * + * @param {object} context default store context + */ + getConversationsFromStorage(context) { + const cachedConversations = BrowserStorage.getItem('cachedConversations_') + if (!cachedConversations?.length) { + return + } + + const conversations = JSON.parse(cachedConversations) + console.debug(`${conversations.length} conversations have been restored from BrowserStorage`) + + const currentConversations = context.state.conversations + for (const conversation of Object.values(conversations)) { + // Check if the conversation hasn't been added already + if (currentConversations[conversation.token] === undefined) { + context.dispatch('addConversation', conversation) + } + } + }, + + /** + * Save conversations to BrowserStorage from the store state + * + * @param {object} context default store context + */ + putConversationsToStorage(context) { + const conversations = context.getters.conversationsList + if (!conversations.length) { + return + } + + const serializedConversations = JSON.stringify(conversations) + BrowserStorage.setItem('cachedConversations_', serializedConversations) + console.debug(`${conversations.length} chats were saved to BrowserStorage. Estimated object size: ${serializedConversations.length / 1000} kB`) }, /** diff --git a/src/store/conversationsStore.spec.js b/src/store/conversationsStore.spec.js index f76d6768165f..5cf30af00628 100644 --- a/src/store/conversationsStore.spec.js +++ b/src/store/conversationsStore.spec.js @@ -11,6 +11,7 @@ import { PARTICIPANT, ATTENDEE, } from '../constants.js' +import BrowserStorage from '../services/BrowserStorage.js' import { makePublic, makePrivate, @@ -56,6 +57,11 @@ jest.mock('../services/conversationsService', () => ({ jest.mock('@nextcloud/event-bus') +jest.mock('../services/BrowserStorage.js', () => ({ + getItem: jest.fn(), + setItem: jest.fn(), +})) + describe('conversationsStore', () => { const testToken = 'XXTOKENXX' const previousLastMessage = { @@ -196,6 +202,31 @@ describe('conversationsStore', () => { expect(deleteConversation).not.toHaveBeenCalled() }) + test('restores conversations cached in BrowserStorage', () => { + const testConversations = [ + { + token: 'one_token', + attendeeId: 'attendee-id-1', + lastActivity: Date.parse('2023-02-01T00:00:00.000Z') / 1000, + }, + { + token: 'another_token', + attendeeId: 'attendee-id-2', + lastActivity: Date.parse('2023-01-01T00:00:00.000Z') / 1000, + }, + ] + + BrowserStorage.getItem.mockReturnValueOnce( + '[{"token":"one_token","attendeeId":"attendee-id-1","lastActivity":1675209600},{"token":"another_token","attendeeId":"attendee-id-2","lastActivity":1672531200}]' + ) + + store.dispatch('getConversationsFromStorage') + + expect(BrowserStorage.getItem).toHaveBeenCalledWith('cachedConversations_') + expect(store.getters.conversationsList).toHaveLength(2) + expect(store.getters.conversationsList).toEqual(testConversations) + }) + test('deletes conversation from server', async () => { store.dispatch('addConversation', testConversation) @@ -258,6 +289,37 @@ describe('conversationsStore', () => { expect(store.getters.conversationsList).toStrictEqual(testConversations) }) + test('sets fetched conversations to BrowserStorage', async () => { + const testConversations = [ + { + token: 'one_token', + attendeeId: 'attendee-id-1', + lastActivity: Date.parse('2023-02-01T00:00:00.000Z') / 1000, + }, + { + token: 'another_token', + attendeeId: 'attendee-id-2', + lastActivity: Date.parse('2023-01-01T00:00:00.000Z') / 1000, + }, + ] + + const response = { + data: { + ocs: { + data: testConversations, + }, + }, + } + + fetchConversations.mockResolvedValue(response) + + await store.dispatch('fetchConversations', {}) + + expect(BrowserStorage.setItem).toHaveBeenCalledWith('cachedConversations_', + '[{"token":"one_token","attendeeId":"attendee-id-1","lastActivity":1675209600},{"token":"another_token","attendeeId":"attendee-id-2","lastActivity":1672531200}]' + ) + }) + test('fetches all conversations and add new received conversations', async () => { const oldConversation = { token: 'tokenOne',