Skip to content

Commit 1f05ddf

Browse files
committed
add actions to storing and recovering fetched conversations from BrowserStorage
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
1 parent 1397252 commit 1f05ddf

3 files changed

Lines changed: 126 additions & 7 deletions

File tree

src/components/LeftSidebar/LeftSidebar.vue

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,10 @@ export default {
410410
this.abortSearch()
411411
})
412412
413+
// Restore last fetched conversations from browser storage,
414+
// before updated ones come from server
415+
this.restoreConversations()
416+
413417
this.fetchConversations()
414418
},
415419
@@ -630,12 +634,14 @@ export default {
630634
},
631635
632636
debounceFetchConversations: debounce(function() {
633-
if (!this.isFetchingConversations) {
634-
this.fetchConversations()
635-
}
637+
this.fetchConversations()
636638
}, 3000),
637639
638640
async fetchConversations() {
641+
if (this.isFetchingConversations) {
642+
return
643+
}
644+
639645
this.isFetchingConversations = true
640646
if (this.forceFullRoomListRefreshAfterXLoops === 0) {
641647
this.roomListModifiedBefore = 0
@@ -668,16 +674,24 @@ export default {
668674
* Emits a global event that is used in App.vue to update the page title once the
669675
* ( if the current route is a conversation and once the conversations are received)
670676
*/
671-
EventBus.$emit('conversations-received', {
672-
singleConversation: false,
673-
})
677+
EventBus.$emit('conversations-received', { singleConversation: false })
674678
this.isFetchingConversations = false
675679
} catch (error) {
676680
console.debug('Error while fetching conversations: ', error)
677681
this.isFetchingConversations = false
678682
}
679683
},
680684
685+
async restoreConversations() {
686+
try {
687+
await this.$store.dispatch('restoreConversations')
688+
this.initialisedConversations = true
689+
EventBus.$emit('conversations-received', { singleConversation: false })
690+
} catch (error) {
691+
console.debug('Error while restoring conversations: ', error)
692+
}
693+
},
694+
681695
// Checks whether the conversations list is scrolled all the way to the top
682696
// or not
683697
handleScroll() {

src/store/conversationsStore.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
PARTICIPANT,
3232
WEBINAR,
3333
} from '../constants.js'
34+
import BrowserStorage from '../services/BrowserStorage.js'
3435
import {
3536
makePublic,
3637
makePrivate,
@@ -315,8 +316,9 @@ const actions = {
315316
* @param {object} payload the payload
316317
* @param {object[]} payload.conversations new conversations list
317318
* @param {boolean} payload.withRemoving whether to remove conversations that are not in the new list
319+
* @param {boolean} payload.withCaching whether to cache conversations to BrowserStorage with patch
318320
*/
319-
patchConversations(context, { conversations, withRemoving = false }) {
321+
patchConversations(context, { conversations, withRemoving = false, withCaching = false }) {
320322
const currentConversations = context.state.conversations
321323
const newConversations = Object.fromEntries(
322324
conversations.map((conversation) => [conversation.token, conversation])
@@ -339,6 +341,45 @@ const actions = {
339341
context.dispatch('updateConversationIfHasChanged', newConversation)
340342
}
341343
}
344+
345+
if (withCaching) {
346+
context.dispatch('cacheConversations')
347+
}
348+
},
349+
350+
/**
351+
* Restores conversations from BrowserStorage and add them to the store state
352+
*
353+
* @param {object} context default store context
354+
*/
355+
restoreConversations(context) {
356+
const cachedConversations = BrowserStorage.getItem('cachedConversations')
357+
if (!cachedConversations?.length) {
358+
return
359+
}
360+
361+
context.dispatch('patchConversations', {
362+
conversations: JSON.parse(cachedConversations),
363+
withRemoving: true,
364+
})
365+
366+
console.debug('Conversations have been restored from BrowserStorage')
367+
},
368+
369+
/**
370+
* Save conversations to BrowserStorage from the store state
371+
*
372+
* @param {object} context default store context
373+
*/
374+
cacheConversations(context) {
375+
const conversations = context.getters.conversationsList
376+
if (!conversations.length) {
377+
return
378+
}
379+
380+
const serializedConversations = JSON.stringify(conversations)
381+
BrowserStorage.setItem('cachedConversations', serializedConversations)
382+
console.debug(`Conversations were saved to BrowserStorage. Estimated object size: ${(serializedConversations.length / 1024).toFixed(2)} kB`)
342383
},
343384

344385
/**
@@ -723,6 +764,8 @@ const actions = {
723764
withRemoving: modifiedSince === 0,
724765
})
725766

767+
dispatch('cacheConversations')
768+
726769
return response
727770
} catch (error) {
728771
if (error?.response) {

src/store/conversationsStore.spec.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
PARTICIPANT,
1212
ATTENDEE,
1313
} from '../constants.js'
14+
import BrowserStorage from '../services/BrowserStorage.js'
1415
import {
1516
makePublic,
1617
makePrivate,
@@ -56,6 +57,11 @@ jest.mock('../services/conversationsService', () => ({
5657

5758
jest.mock('@nextcloud/event-bus')
5859

60+
jest.mock('../services/BrowserStorage.js', () => ({
61+
getItem: jest.fn(),
62+
setItem: jest.fn(),
63+
}))
64+
5965
describe('conversationsStore', () => {
6066
const testToken = 'XXTOKENXX'
6167
const previousLastMessage = {
@@ -196,6 +202,31 @@ describe('conversationsStore', () => {
196202
expect(deleteConversation).not.toHaveBeenCalled()
197203
})
198204

205+
test('restores conversations cached in BrowserStorage', () => {
206+
const testConversations = [
207+
{
208+
token: 'one_token',
209+
attendeeId: 'attendee-id-1',
210+
lastActivity: Date.parse('2023-02-01T00:00:00.000Z') / 1000,
211+
},
212+
{
213+
token: 'another_token',
214+
attendeeId: 'attendee-id-2',
215+
lastActivity: Date.parse('2023-01-01T00:00:00.000Z') / 1000,
216+
},
217+
]
218+
219+
BrowserStorage.getItem.mockReturnValueOnce(
220+
'[{"token":"one_token","attendeeId":"attendee-id-1","lastActivity":1675209600},{"token":"another_token","attendeeId":"attendee-id-2","lastActivity":1672531200}]'
221+
)
222+
223+
store.dispatch('restoreConversations')
224+
225+
expect(BrowserStorage.getItem).toHaveBeenCalledWith('cachedConversations')
226+
expect(store.getters.conversationsList).toHaveLength(2)
227+
expect(store.getters.conversationsList).toEqual(testConversations)
228+
})
229+
199230
test('deletes conversation from server', async () => {
200231
store.dispatch('addConversation', testConversation)
201232

@@ -258,6 +289,37 @@ describe('conversationsStore', () => {
258289
expect(store.getters.conversationsList).toStrictEqual(testConversations)
259290
})
260291

292+
test('sets fetched conversations to BrowserStorage', async () => {
293+
const testConversations = [
294+
{
295+
token: 'one_token',
296+
attendeeId: 'attendee-id-1',
297+
lastActivity: Date.parse('2023-02-01T00:00:00.000Z') / 1000,
298+
},
299+
{
300+
token: 'another_token',
301+
attendeeId: 'attendee-id-2',
302+
lastActivity: Date.parse('2023-01-01T00:00:00.000Z') / 1000,
303+
},
304+
]
305+
306+
const response = {
307+
data: {
308+
ocs: {
309+
data: testConversations,
310+
},
311+
},
312+
}
313+
314+
fetchConversations.mockResolvedValue(response)
315+
316+
await store.dispatch('fetchConversations', {})
317+
318+
expect(BrowserStorage.setItem).toHaveBeenCalledWith('cachedConversations',
319+
'[{"token":"one_token","attendeeId":"attendee-id-1","lastActivity":1675209600},{"token":"another_token","attendeeId":"attendee-id-2","lastActivity":1672531200}]'
320+
)
321+
})
322+
261323
test('fetches all conversations and add new received conversations', async () => {
262324
const oldConversation = {
263325
token: 'tokenOne',

0 commit comments

Comments
 (0)