From 728b43be9ebee98289def41d2000d905acf74508 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 9 Jun 2020 05:31:08 +1000 Subject: [PATCH] cache-pull-from-db-finish --- ts/session/sending/PendingMessageCache.ts | 73 ++++++++++--------- ts/session/types/RawMessage.ts | 9 +++ .../sending/PendingMessageCache_test.ts | 62 +++++++++++++++- 3 files changed, 105 insertions(+), 39 deletions(-) diff --git a/ts/session/sending/PendingMessageCache.ts b/ts/session/sending/PendingMessageCache.ts index 1ee45e338..b6a11856e 100644 --- a/ts/session/sending/PendingMessageCache.ts +++ b/ts/session/sending/PendingMessageCache.ts @@ -1,5 +1,9 @@ -import { createOrUpdateItem, getItemById } from '../../../js/modules/data'; -import { RawMessage } from '../types/RawMessage'; +import { + createOrUpdateItem, + getItemById, + bulkAddItems, +} from '../../../js/modules/data'; +import { RawMessage, BareRawMessage } from '../types/RawMessage'; import { ContentMessage } from '../messages/outgoing'; import { PubKey } from '../types'; import * as MessageUtils from '../utils'; @@ -33,23 +37,14 @@ export class PendingMessageCache { } public getForDevice(device: PubKey): Array { - const pending = this.cache.filter(m => m.device === device.key); - - return pending.sort((a, b) => a.timestamp - b.timestamp); + return this.getAllPending().filter(m => m.device === device.key); } public getDevices(): Array { // Gets all unique devices with pending messages const pubkeyStrings = [...new Set(this.cache.map(m => m.device))]; - const pubkeys: Array = []; - pubkeyStrings.forEach(pubkey => { - if (PubKey.validate(pubkey)) { - pubkeys.push(new PubKey(pubkey)); - } - }); - - return pubkeys; + return pubkeyStrings.map(PubKey.from).filter((k): k is PubKey => !!k); } public async add( @@ -113,34 +108,40 @@ export class PendingMessageCache { return []; } - const barePending = JSON.parse(String(data.value)); - - const pending = barePending.map((message: any) => { - const { - identifier, - plainTextBuffer, - timestamp, - device, - ttl, - encryption, - } = message; - - return { - identifier, - plainTextBuffer, - timestamp, - device, - ttl, - encryption, - } as RawMessage; + const barePending = JSON.parse(String(data.value)) as Array; + + // Rebuild plainTextBuffer + // tslint:disable-next-line: no-unnecessary-local-variable + const pending = barePending.map((message: BareRawMessage) => { + const rebuiltMessage = { ...message }; + + // From Array to ArrayBuffer + const bufferArray = Uint8Array.from(message.plainTextBuffer); + + // From ArrayBuffer into Buffer + const buffer = Buffer.alloc(bufferArray.byteLength); + for (let i = 0; i < buffer.length; i++) { + buffer[i] = bufferArray[i]; + } + + rebuiltMessage.plainTextBuffer = buffer; + + return rebuiltMessage as RawMessage; }); - return pending as Array; + return pending; } private async saveToDB() { - // Only call when adding / removing from cache. - const encodedPendingMessages = JSON.stringify(this.cache) || '[]'; + // For each plainTextBuffer in cache, save in as a simple Array to avoid + // Node issues with JSON stringifying Buffer without strict typing + const encodedCache = [...this.cache].map(item => { + const plainTextBuffer = Array.from(item.plainTextBuffer); + + return { ...item, plainTextBuffer }; + }); + + const encodedPendingMessages = JSON.stringify(encodedCache) || '[]'; await createOrUpdateItem({ id: 'pendingMessages', value: encodedPendingMessages, diff --git a/ts/session/types/RawMessage.ts b/ts/session/types/RawMessage.ts index 30d2e0d9b..490dbf041 100644 --- a/ts/session/types/RawMessage.ts +++ b/ts/session/types/RawMessage.ts @@ -10,3 +10,12 @@ export interface RawMessage { ttl: number; encryption: EncryptionType; } + +export interface BareRawMessage { + identifier: string; + plainTextBuffer: any; + timestamp: number; + device: string; + ttl: number; + encryption: number; +} diff --git a/ts/test/session/sending/PendingMessageCache_test.ts b/ts/test/session/sending/PendingMessageCache_test.ts index 6fbca0f35..d732d859e 100644 --- a/ts/test/session/sending/PendingMessageCache_test.ts +++ b/ts/test/session/sending/PendingMessageCache_test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai'; - +import * as _ from 'lodash'; import * as MessageUtils from '../../../session/utils'; import { TestUtils } from '../../../test/test-utils'; import { PendingMessageCache } from '../../../session/sending/PendingMessageCache'; @@ -12,14 +12,15 @@ interface StorageItem { describe('PendingMessageCache', () => { // Initialize new stubbed cache + let data: StorageItem; let pendingMessageCacheStub: PendingMessageCache; beforeEach(async () => { // Stub out methods which touch the database const storageID = 'pendingMessages'; - let data: StorageItem = { + data = { id: storageID, - value: '', + value: '[]', }; TestUtils.stubData('getItemById') @@ -200,4 +201,59 @@ describe('PendingMessageCache', () => { const finalCache = pendingMessageCacheStub.getAllPending(); expect(finalCache).to.have.length(0); }); + + it('can restore from db', async () => { + const cacheItems = [ + { + device: TestUtils.generateFakePubkey(), + message: TestUtils.generateUniqueChatMessage(), + }, + { + device: TestUtils.generateFakePubkey(), + message: TestUtils.generateUniqueChatMessage(), + }, + { + device: TestUtils.generateFakePubkey(), + message: TestUtils.generateUniqueChatMessage(), + }, + ]; + + cacheItems.forEach(async item => { + await pendingMessageCacheStub.add(item.device, item.message); + }); + + const addedMessages = pendingMessageCacheStub.getAllPending(); + expect(addedMessages).to.have.length(cacheItems.length); + + // Rebuild from DB + const freshCache = new PendingMessageCache(); + await freshCache.isReady; + + // Verify messages + const rebuiltMessages = freshCache.getAllPending(); + + rebuiltMessages.forEach((message, index) => { + const addedMessage = addedMessages[index]; + + // Pull out plainTextBuffer for a separate check + const buffersCompare = + Buffer.compare( + message.plainTextBuffer, + addedMessage.plainTextBuffer + ) === 0; + expect(buffersCompare).to.equal( + true, + 'buffers were not loaded properly from database' + ); + + // Compare all other valures + const trimmedAdded = _.omit(addedMessage, ['plainTextBuffer']); + const trimmedRebuilt = _.omit(message, ['plainTextBuffer']); + + expect(_.isEqual(trimmedAdded, trimmedRebuilt)).to.equal( + true, + 'cached messages were not rebuilt properly' + ); + }); + }); });