diff --git a/ACKNOWLEDGMENTS.md b/ACKNOWLEDGMENTS.md index 216358ddd..74ab7ea21 100644 --- a/ACKNOWLEDGMENTS.md +++ b/ACKNOWLEDGMENTS.md @@ -2513,18 +2513,6 @@ Signal Desktop makes use of the following open source projects. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## p-props - - MIT License - - Copyright (c) Sindre Sorhus (https://sindresorhus.com) - - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ## p-queue MIT License diff --git a/app/attachment_channel.ts b/app/attachment_channel.ts index 2eeba8769..028818f90 100644 --- a/app/attachment_channel.ts +++ b/app/attachment_channel.ts @@ -68,11 +68,11 @@ async function cleanupOrphanedAttachments({ }: CleanupOrphanedAttachmentsOptionsType): Promise { await deleteAllBadges({ userDataPath, - pathsToKeep: await sql.sqlCall('getAllBadgeImageFileLocalPaths'), + pathsToKeep: await sql.sqlRead('getAllBadgeImageFileLocalPaths'), }); const allStickers = await getAllStickers(userDataPath); - const orphanedStickers = await sql.sqlCall( + const orphanedStickers = await sql.sqlWrite( 'removeKnownStickers', allStickers ); @@ -82,7 +82,7 @@ async function cleanupOrphanedAttachments({ }); const allDraftAttachments = await getAllDraftAttachments(userDataPath); - const orphanedDraftAttachments = await sql.sqlCall( + const orphanedDraftAttachments = await sql.sqlWrite( 'removeKnownDraftAttachments', allDraftAttachments ); @@ -100,7 +100,7 @@ async function cleanupOrphanedAttachments({ ); { - const attachments: ReadonlyArray = await sql.sqlCall( + const attachments: ReadonlyArray = await sql.sqlRead( 'getKnownConversationAttachments' ); @@ -142,7 +142,7 @@ function deleteOrphanedAttachments({ let attachments: ReadonlyArray; // eslint-disable-next-line no-await-in-loop - ({ attachments, cursor } = await sql.sqlCall( + ({ attachments, cursor } = await sql.sqlRead( 'getKnownMessageAttachments', cursor )); @@ -166,7 +166,7 @@ function deleteOrphanedAttachments({ } while (cursor !== undefined && !cursor.done); } finally { if (cursor !== undefined) { - await sql.sqlCall('finishGetKnownMessageAttachments', cursor); + await sql.sqlRead('finishGetKnownMessageAttachments', cursor); } } diff --git a/app/main.ts b/app/main.ts index 343b7f0c6..0b3795853 100644 --- a/app/main.ts +++ b/app/main.ts @@ -396,14 +396,14 @@ async function getLocaleOverrideSetting(): Promise { const zoomFactorService = new ZoomFactorService({ async getZoomFactorSetting() { - const item = await sql.sqlCall('getItemById', 'zoomFactor'); + const item = await sql.sqlRead('getItemById', 'zoomFactor'); if (typeof item?.value !== 'number') { return null; } return item.value; }, async setZoomFactorSetting(zoomFactor) { - await sql.sqlCall('createOrUpdateItem', { + await sql.sqlWrite('createOrUpdateItem', { id: 'zoomFactor', value: zoomFactor, }); @@ -1433,8 +1433,8 @@ async function showSettingsWindow() { async function getIsLinked() { try { - const number = await sql.sqlCall('getItemById', 'number_id'); - const password = await sql.sqlCall('getItemById', 'password'); + const number = await sql.sqlRead('getItemById', 'number_id'); + const password = await sql.sqlRead('getItemById', 'password'); return Boolean(number && password); } catch (e) { return false; @@ -1686,7 +1686,7 @@ async function initializeSQL( try { // This should be the first awaited call in this function, otherwise - // `sql.sqlCall` will throw an uninitialized error instead of waiting for + // `sql.sqlRead` will throw an uninitialized error instead of waiting for // init to finish. await sql.initialize({ appVersion: app.getVersion(), @@ -2154,10 +2154,10 @@ app.on('ready', async () => { try { const IDB_KEY = 'indexeddb-delete-needed'; - const item = await sql.sqlCall('getItemById', IDB_KEY); + const item = await sql.sqlRead('getItemById', IDB_KEY); if (item && item.value) { - await sql.sqlCall('removeIndexedDBFiles'); - await sql.sqlCall('removeItemById', IDB_KEY); + await sql.sqlWrite('removeIndexedDBFiles'); + await sql.sqlWrite('removeItemById', IDB_KEY); } } catch (err) { getLogger().error( diff --git a/app/sql_channel.ts b/app/sql_channel.ts index 59bac5960..8911e1c6a 100644 --- a/app/sql_channel.ts +++ b/app/sql_channel.ts @@ -7,14 +7,22 @@ import type { MainSQL } from '../ts/sql/main'; import { remove as removeUserConfig } from './user_config'; import { remove as removeEphemeralConfig } from './ephemeral_config'; -let sql: Pick | undefined; +let sql: + | Pick< + MainSQL, + 'sqlRead' | 'sqlWrite' | 'pauseWriteAccess' | 'resumeWriteAccess' + > + | undefined; let initialized = false; -const SQL_CHANNEL_KEY = 'sql-channel'; +const SQL_READ_KEY = 'sql-channel:read'; +const SQL_WRITE_KEY = 'sql-channel:write'; const ERASE_SQL_KEY = 'erase-sql-key'; +const PAUSE_WRITE_ACCESS = 'pause-sql-writes'; +const RESUME_WRITE_ACCESS = 'resume-sql-writes'; -export function initialize(mainSQL: Pick): void { +export function initialize(mainSQL: typeof sql): void { if (initialized) { throw new Error('sqlChannels: already initialized!'); } @@ -22,15 +30,36 @@ export function initialize(mainSQL: Pick): void { sql = mainSQL; - ipcMain.handle(SQL_CHANNEL_KEY, (_event, callName, ...args) => { + ipcMain.handle(SQL_READ_KEY, (_event, callName, ...args) => { if (!sql) { - throw new Error(`${SQL_CHANNEL_KEY}: Not yet initialized!`); + throw new Error(`${SQL_READ_KEY}: Not yet initialized!`); } - return sql.sqlCall(callName, ...args); + return sql.sqlRead(callName, ...args); + }); + + ipcMain.handle(SQL_WRITE_KEY, (_event, callName, ...args) => { + if (!sql) { + throw new Error(`${SQL_WRITE_KEY}: Not yet initialized!`); + } + return sql.sqlWrite(callName, ...args); }); ipcMain.handle(ERASE_SQL_KEY, () => { removeUserConfig(); removeEphemeralConfig(); }); + + ipcMain.handle(PAUSE_WRITE_ACCESS, () => { + if (!sql) { + throw new Error(`${PAUSE_WRITE_ACCESS}: Not yet initialized!`); + } + return sql.pauseWriteAccess(); + }); + + ipcMain.handle(RESUME_WRITE_ACCESS, () => { + if (!sql) { + throw new Error(`${PAUSE_WRITE_ACCESS}: Not yet initialized!`); + } + return sql.resumeWriteAccess(); + }); } diff --git a/package-lock.json b/package-lock.json index 99a8894b3..e0bb59959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,7 +70,6 @@ "nop": "1.0.0", "normalize-path": "3.0.0", "p-map": "2.1.0", - "p-props": "4.0.0", "p-queue": "6.6.2", "p-timeout": "4.1.0", "parchment": "1.1.4", @@ -12652,6 +12651,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -15491,6 +15491,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "engines": { "node": ">=6" } @@ -22852,6 +22853,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } @@ -28836,34 +28838,6 @@ "node": ">=6" } }, - "node_modules/p-props": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-props/-/p-props-4.0.0.tgz", - "integrity": "sha512-3iKFbPdoPG7Ne3cMA53JnjPsTMaIzE9gxKZnvKJJivTAeqLEZPBu6zfi6DYq9AsH1nYycWmo3sWCNI8Kz6T2Zg==", - "dependencies": { - "p-map": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-props/node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-queue": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", diff --git a/package.json b/package.json index a9477a870..3cd897a7c 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,6 @@ "nop": "1.0.0", "normalize-path": "3.0.0", "p-map": "2.1.0", - "p-props": "4.0.0", "p-queue": "6.6.2", "p-timeout": "4.1.0", "parchment": "1.1.4", diff --git a/test/test.js b/test/test.js index 8af4ae32d..1efebbc78 100644 --- a/test/test.js +++ b/test/test.js @@ -8,14 +8,6 @@ mocha.setup('bdd'); mocha.setup({ timeout: 10000 }); -function deleteIndexedDB() { - return new Promise((resolve, reject) => { - const idbReq = indexedDB.deleteDatabase('test'); - idbReq.onsuccess = resolve; - idbReq.error = reject; - }); -} - window.Events = { getThemeSetting: () => 'light', }; @@ -23,8 +15,6 @@ window.Events = { /* Delete the database before running any tests */ before(async () => { await window.testUtilities.initialize(); - await deleteIndexedDB(); - await window.Signal.Data.removeAll(); await window.storage.fetch(); }); diff --git a/ts/CI.ts b/ts/CI.ts index 19ab32ca8..d058121df 100644 --- a/ts/CI.ts +++ b/ts/CI.ts @@ -7,7 +7,7 @@ import type { IPCResponse as ChallengeResponseType } from './challenge'; import type { MessageAttributesType } from './model-types.d'; import * as log from './logging/log'; import { explodePromise } from './util/explodePromise'; -import { ipcInvoke } from './sql/channels'; +import { AccessType, ipcInvoke } from './sql/channels'; import { backupsService } from './services/backups'; import { SECOND } from './util/durations'; import { isSignalRoute } from './util/signalRoutes'; @@ -128,6 +128,7 @@ export function getCI({ deviceName, backupData }: GetCIOptionsType): CIType { async function getMessagesBySentAt(sentAt: number) { const messages = await ipcInvoke>( + AccessType.Read, 'getMessagesBySentAt', [sentAt] ); diff --git a/ts/CI/benchmarkConversationOpen.ts b/ts/CI/benchmarkConversationOpen.ts index c841958f8..18fd4277a 100644 --- a/ts/CI/benchmarkConversationOpen.ts +++ b/ts/CI/benchmarkConversationOpen.ts @@ -6,6 +6,7 @@ import { v4 as uuid } from 'uuid'; import { incrementMessageCounter } from '../util/incrementMessageCounter'; import { ReadStatus } from '../messages/MessageReadStatus'; import { SendStatus } from '../messages/MessageSendState'; +import { DataWriter } from '../sql/Client'; import { BodyRange } from '../types/BodyRange'; import { strictAssert } from '../util/assert'; import { MINUTE } from '../util/durations'; @@ -86,13 +87,13 @@ export async function populateConversationWithMessages({ timestamp += 1; } - await window.Signal.Data.saveMessages(messages, { + await DataWriter.saveMessages(messages, { forceSave: true, ourAci, }); conversation.set('active_at', Date.now()); - await window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); log.info(`${logId}: populating conversation complete`); } diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index 876734a12..b4551ae51 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -14,7 +14,7 @@ import type { } from './model-types.d'; import type { ConversationModel } from './models/conversations'; -import dataInterface from './sql/Client'; +import { DataReader, DataWriter } from './sql/Client'; import * as log from './logging/log'; import * as Errors from './types/errors'; import { getAuthorId } from './messages/helpers'; @@ -127,11 +127,15 @@ const { getAllConversations, getAllGroupsInvolvingServiceId, getMessagesBySentAt, +} = DataReader; + +const { migrateConversationMessages, removeConversation, saveConversation, updateConversation, -} = dataInterface; + updateConversations, +} = DataWriter; // We have to run this in background.js, after all backbone models and collections on // Whisper.* have been created. Once those are in typescript we can use more reasonable @@ -443,12 +447,12 @@ export class ConversationController { conversation.set({ profileAvatar: { hash: SIGNAL_AVATAR_PATH, path: SIGNAL_AVATAR_PATH }, }); - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); } if (!conversation.get('profileName')) { conversation.set({ profileName: 'Signal' }); - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); } this._signalConversationId = conversation.id; @@ -934,7 +938,7 @@ export class ConversationController { ); existing.set({ e164: undefined }); - updateConversation(existing.attributes); + drop(updateConversation(existing.attributes)); byE164[e164] = conversation; @@ -1144,7 +1148,7 @@ export class ConversationController { group.set({ members: currentAdded, }); - updateConversation(group.attributes); + drop(updateConversation(group.attributes)); }); } @@ -1337,7 +1341,7 @@ export class ConversationController { ); convo.set('isPinned', true); - window.Signal.Data.updateConversation(convo.attributes); + drop(updateConversation(convo.attributes)); } } @@ -1353,7 +1357,7 @@ export class ConversationController { `updating ${sharedWith.length} conversations` ); - await window.Signal.Data.updateConversations( + await updateConversations( sharedWith.map(c => { c.unset('shareMyPhoneNumber'); return c.attributes; @@ -1440,7 +1444,7 @@ export class ConversationController { const isChanged = maybeDeriveGroupV2Id(conversation); if (isChanged) { - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); } // In case a too-large draft was saved to the database @@ -1449,7 +1453,7 @@ export class ConversationController { conversation.set({ draft: draft.slice(0, MAX_MESSAGE_BODY_LENGTH), }); - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); } // Clean up the conversations that have service id as their e164. @@ -1457,7 +1461,7 @@ export class ConversationController { const serviceId = conversation.getServiceId(); if (e164 && isServiceIdString(e164) && serviceId) { conversation.set({ e164: undefined }); - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); log.info( `Cleaning up conversation(${serviceId}) with invalid e164` diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts index a4c013c2c..7f175c1ee 100644 --- a/ts/SignalProtocolStore.ts +++ b/ts/SignalProtocolStore.ts @@ -18,10 +18,13 @@ import { SignedPreKeyRecord, } from '@signalapp/libsignal-client'; +import { DataReader, DataWriter } from './sql/Client'; +import type { ItemType } from './sql/Interface'; import * as Bytes from './Bytes'; import { constantTimeEqual, sha256 } from './Crypto'; import { assertDev, strictAssert } from './util/assert'; import { isNotNil } from './util/isNotNil'; +import { drop } from './util/drop'; import { Zone } from './util/Zone'; import { isMoreRecentThan } from './util/timestamp'; import { @@ -294,7 +297,9 @@ export class SignalProtocolStore extends EventEmitter { await Promise.all([ (async () => { this.ourIdentityKeys.clear(); - const map = await window.Signal.Data.getItemById('identityKeyMap'); + const map = (await DataReader.getItemById( + 'identityKeyMap' + )) as unknown as ItemType<'identityKeyMap'>; if (!map) { return; } @@ -313,7 +318,9 @@ export class SignalProtocolStore extends EventEmitter { })(), (async () => { this.ourRegistrationIds.clear(); - const map = await window.Signal.Data.getItemById('registrationIdMap'); + const map = (await DataReader.getItemById( + 'registrationIdMap' + )) as unknown as ItemType<'registrationIdMap'>; if (!map) { return; } @@ -329,32 +336,32 @@ export class SignalProtocolStore extends EventEmitter { _fillCaches( this, 'identityKeys', - window.Signal.Data.getAllIdentityKeys() + DataReader.getAllIdentityKeys() ), _fillCaches( this, 'kyberPreKeys', - window.Signal.Data.getAllKyberPreKeys() + DataReader.getAllKyberPreKeys() ), _fillCaches( this, 'sessions', - window.Signal.Data.getAllSessions() + DataReader.getAllSessions() ), _fillCaches( this, 'preKeys', - window.Signal.Data.getAllPreKeys() + DataReader.getAllPreKeys() ), _fillCaches( this, 'senderKeys', - window.Signal.Data.getAllSenderKeys() + DataReader.getAllSenderKeys() ), _fillCaches( this, 'signedPreKeys', - window.Signal.Data.getAllSignedPreKeys() + DataReader.getAllSignedPreKeys() ), ]); } @@ -470,7 +477,7 @@ export class SignalProtocolStore extends EventEmitter { }, }; - await window.Signal.Data.createOrUpdateKyberPreKey(confirmedItem.fromDB); + await DataWriter.createOrUpdateKyberPreKey(confirmedItem.fromDB); kyberPreKeyCache.set(id, confirmedItem); } @@ -505,7 +512,7 @@ export class SignalProtocolStore extends EventEmitter { toSave.push(kyberPreKey); }); - await window.Signal.Data.bulkAddKyberPreKeys(toSave); + await DataWriter.bulkAddKyberPreKeys(toSave); toSave.forEach(kyberPreKey => { kyberPreKeyCache.set(kyberPreKey.id, { hydrated: false, @@ -546,7 +553,7 @@ export class SignalProtocolStore extends EventEmitter { const ids = keyIds.map(keyId => this._getKeyId(ourServiceId, keyId)); log.info('removeKyberPreKeys: Removing kyber prekeys:', formatKeys(keyIds)); - const changes = await window.Signal.Data.removeKyberPreKeyById(ids); + const changes = await DataWriter.removeKyberPreKeyById(ids); log.info(`removeKyberPreKeys: Removed ${changes} kyber prekeys`); ids.forEach(id => { kyberPreKeyCache.delete(id); @@ -564,7 +571,7 @@ export class SignalProtocolStore extends EventEmitter { if (this.kyberPreKeys) { this.kyberPreKeys.clear(); } - const changes = await window.Signal.Data.removeAllKyberPreKeys(); + const changes = await DataWriter.removeAllKyberPreKeys(); log.info(`clearKyberPreKeyStore: Removed ${changes} kyber prekeys`); } @@ -646,7 +653,7 @@ export class SignalProtocolStore extends EventEmitter { }); log.info(`storePreKeys: Saving ${toSave.length} prekeys`); - await window.Signal.Data.bulkAddPreKeys(toSave); + await DataWriter.bulkAddPreKeys(toSave); toSave.forEach(preKey => { preKeyCache.set(preKey.id, { hydrated: false, @@ -668,7 +675,7 @@ export class SignalProtocolStore extends EventEmitter { log.info('removePreKeys: Removing prekeys:', formatKeys(keyIds)); - const changes = await window.Signal.Data.removePreKeyById(ids); + const changes = await DataWriter.removePreKeyById(ids); log.info(`removePreKeys: Removed ${changes} prekeys`); ids.forEach(id => { preKeyCache.delete(id); @@ -683,7 +690,7 @@ export class SignalProtocolStore extends EventEmitter { if (this.preKeys) { this.preKeys.clear(); } - const changes = await window.Signal.Data.removeAllPreKeys(); + const changes = await DataWriter.removeAllPreKeys(); log.info(`clearPreKeyStore: Removed ${changes} prekeys`); } @@ -769,7 +776,7 @@ export class SignalProtocolStore extends EventEmitter { }, }; - await window.Signal.Data.createOrUpdateSignedPreKey(confirmedItem.fromDB); + await DataWriter.createOrUpdateSignedPreKey(confirmedItem.fromDB); signedPreKeyCache.set(id, confirmedItem); } @@ -796,7 +803,7 @@ export class SignalProtocolStore extends EventEmitter { confirmed: Boolean(confirmed), }; - await window.Signal.Data.createOrUpdateSignedPreKey(fromDB); + await DataWriter.createOrUpdateSignedPreKey(fromDB); this.signedPreKeys.set(id, { hydrated: false, fromDB, @@ -818,7 +825,7 @@ export class SignalProtocolStore extends EventEmitter { 'removeSignedPreKeys: Removing signed prekeys:', formatKeys(keyIds) ); - await window.Signal.Data.removeSignedPreKeyById(ids); + await DataWriter.removeSignedPreKeyById(ids); ids.forEach(id => { signedPreKeyCache.delete(id); }); @@ -828,7 +835,7 @@ export class SignalProtocolStore extends EventEmitter { if (this.signedPreKeys) { this.signedPreKeys.clear(); } - const changes = await window.Signal.Data.removeAllSignedPreKeys(); + const changes = await DataWriter.removeAllSignedPreKeys(); log.info(`clearSignedPreKeysStore: Removed ${changes} signed prekeys`); } @@ -992,7 +999,7 @@ export class SignalProtocolStore extends EventEmitter { try { const id = this.getSenderKeyId(qualifiedAddress, distributionId); - await window.Signal.Data.removeSenderKeyById(id); + await DataWriter.removeSenderKeyById(id); this.senderKeys.delete(id); } catch (error) { @@ -1011,7 +1018,7 @@ export class SignalProtocolStore extends EventEmitter { if (this.pendingSenderKeys) { this.pendingSenderKeys.clear(); } - await window.Signal.Data.removeAllSenderKeys(); + await DataWriter.removeAllSenderKeys(); }); } @@ -1191,7 +1198,7 @@ export class SignalProtocolStore extends EventEmitter { // Commit both sender keys, sessions and unprocessed in the same database transaction // to unroll both on error. - await window.Signal.Data.commitDecryptResult({ + await DataWriter.commitDecryptResult({ senderKeys: Array.from(pendingSenderKeys.values()).map( ({ fromDB }) => fromDB ), @@ -1593,7 +1600,7 @@ export class SignalProtocolStore extends EventEmitter { const id = qualifiedAddress.toString(); log.info('removeSession: deleting session for', id); try { - await window.Signal.Data.removeSessionById(id); + await DataWriter.removeSessionById(id); this.sessions.delete(id); this.pendingSessions.delete(id); } catch (e) { @@ -1640,7 +1647,7 @@ export class SignalProtocolStore extends EventEmitter { } } - await window.Signal.Data.removeSessionsByConversation(id); + await DataWriter.removeSessionsByConversation(id); } ); } @@ -1665,7 +1672,7 @@ export class SignalProtocolStore extends EventEmitter { } } - await window.Signal.Data.removeSessionsByServiceId(serviceId); + await DataWriter.removeSessionsByServiceId(serviceId); }); } @@ -1772,7 +1779,7 @@ export class SignalProtocolStore extends EventEmitter { this.sessions.clear(); } this.pendingSessions.clear(); - const changes = await window.Signal.Data.removeAllSessions(); + const changes = await DataWriter.removeAllSessions(); log.info(`clearSessionStore: Removed ${changes} sessions`); }); } @@ -1893,9 +1900,7 @@ export class SignalProtocolStore extends EventEmitter { await this._saveIdentityKey(newRecord); this.identityKeys.delete(record.fromDB.id); - const changes = await window.Signal.Data.removeIdentityKeyById( - record.fromDB.id - ); + const changes = await DataWriter.removeIdentityKeyById(record.fromDB.id); log.info( `getOrMigrateIdentityRecord: Removed ${changes} old identity keys for ${record.fromDB.id}` @@ -2042,7 +2047,7 @@ export class SignalProtocolStore extends EventEmitter { const { id } = data; - await window.Signal.Data.createOrUpdateIdentityKey(data); + await DataWriter.createOrUpdateIdentityKey(data); this.identityKeys.set(id, { hydrated: false, fromDB: data, @@ -2345,7 +2350,7 @@ export class SignalProtocolStore extends EventEmitter { // We only want to clear previousIdentityKey on a match, or on successfully emit. conversation.set({ previousIdentityKey: undefined }); - window.Signal.Data.updateConversation(conversation.attributes); + drop(DataWriter.updateConversation(conversation.attributes)); } catch (error) { log.error( 'saveIdentity: error triggering keychange:', @@ -2462,20 +2467,20 @@ export class SignalProtocolStore extends EventEmitter { const id = serviceId; this.identityKeys.delete(id); - await window.Signal.Data.removeIdentityKeyById(serviceId); + await DataWriter.removeIdentityKeyById(serviceId); await this.removeSessionsByServiceId(serviceId); } // Not yet processed messages - for resiliency getUnprocessedCount(): Promise { return this.withZone(GLOBAL_ZONE, 'getUnprocessedCount', async () => { - return window.Signal.Data.getUnprocessedCount(); + return DataReader.getUnprocessedCount(); }); } getAllUnprocessedIds(): Promise> { return this.withZone(GLOBAL_ZONE, 'getAllUnprocessedIds', () => { - return window.Signal.Data.getAllUnprocessedIds(); + return DataWriter.getAllUnprocessedIds(); }); } @@ -2486,14 +2491,14 @@ export class SignalProtocolStore extends EventEmitter { GLOBAL_ZONE, 'getAllUnprocessedByIdsAndIncrementAttempts', async () => { - return window.Signal.Data.getUnprocessedByIdsAndIncrementAttempts(ids); + return DataWriter.getUnprocessedByIdsAndIncrementAttempts(ids); } ); } getUnprocessedById(id: string): Promise { return this.withZone(GLOBAL_ZONE, 'getUnprocessedById', async () => { - return window.Signal.Data.getUnprocessedById(id); + return DataReader.getUnprocessedById(id); }); } @@ -2531,7 +2536,7 @@ export class SignalProtocolStore extends EventEmitter { data: UnprocessedUpdateType ): Promise { return this.withZone(GLOBAL_ZONE, 'updateUnprocessedWithData', async () => { - await window.Signal.Data.updateUnprocessedWithData(id, data); + await DataWriter.updateUnprocessedWithData(id, data); }); } @@ -2542,14 +2547,14 @@ export class SignalProtocolStore extends EventEmitter { GLOBAL_ZONE, 'updateUnprocessedsWithData', async () => { - await window.Signal.Data.updateUnprocessedsWithData(items); + await DataWriter.updateUnprocessedsWithData(items); } ); } removeUnprocessed(idOrArray: string | Array): Promise { return this.withZone(GLOBAL_ZONE, 'removeUnprocessed', async () => { - await window.Signal.Data.removeUnprocessed(idOrArray); + await DataWriter.removeUnprocessed(idOrArray); }); } @@ -2557,7 +2562,7 @@ export class SignalProtocolStore extends EventEmitter { removeAllUnprocessed(): Promise { log.info('removeAllUnprocessed'); return this.withZone(GLOBAL_ZONE, 'removeAllUnprocessed', async () => { - await window.Signal.Data.removeAllUnprocessed(); + await DataWriter.removeAllUnprocessed(); }); } @@ -2603,9 +2608,9 @@ export class SignalProtocolStore extends EventEmitter { 'registrationIdMap', omit(storage.get('registrationIdMap') || {}, oldPni) ), - window.Signal.Data.removePreKeysByServiceId(oldPni), - window.Signal.Data.removeSignedPreKeysByServiceId(oldPni), - window.Signal.Data.removeKyberPreKeysByServiceId(oldPni), + DataWriter.removePreKeysByServiceId(oldPni), + DataWriter.removeSignedPreKeysByServiceId(oldPni), + DataWriter.removeKyberPreKeysByServiceId(oldPni), ]); } @@ -2695,7 +2700,7 @@ export class SignalProtocolStore extends EventEmitter { } async removeAllData(): Promise { - await window.Signal.Data.removeAll(); + await DataWriter.removeAll(); await this.hydrateCaches(); window.storage.reset(); @@ -2716,7 +2721,7 @@ export class SignalProtocolStore extends EventEmitter { conversation.unset('senderKeyInfo'); }); - await window.Signal.Data.removeAllConfiguration(); + await DataWriter.removeAllConfiguration(); await this.hydrateCaches(); diff --git a/ts/background.ts b/ts/background.ts index 33958caf6..8ff3087d1 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -209,6 +209,7 @@ import { isEnabled } from './RemoteConfig'; import { AttachmentBackupManager } from './jobs/AttachmentBackupManager'; import { getConversationIdForLogging } from './util/idForLogging'; import { encryptConversationAttachments } from './util/encryptConversationAttachments'; +import { DataReader, DataWriter } from './sql/Client'; export function isOverHourIntoPast(timestamp: number): boolean { return isNumber(timestamp) && isOlderThan(timestamp, HOUR); @@ -256,7 +257,7 @@ export async function startApp(): Promise { let initialBadgesState: BadgesStateType = { byId: {} }; async function loadInitialBadgesState(): Promise { initialBadgesState = { - byId: makeLookup(await window.Signal.Data.getAllBadges(), 'id'), + byId: makeLookup(await DataReader.getAllBadges(), 'id'), }; } @@ -436,7 +437,7 @@ export async function startApp(): Promise { window.i18n ); - const version = await window.Signal.Data.getItemById('version'); + const version = await DataReader.getItemById('version'); if (!version) { const isIndexedDBPresent = await indexedDb.doesDatabaseExist(); if (isIndexedDBPresent) { @@ -472,8 +473,8 @@ export async function startApp(): Promise { await Promise.all([ indexedDb.removeDatabase(), - window.Signal.Data.removeAll(), - window.Signal.Data.removeIndexedDBFiles(), + DataWriter.removeAll(), + DataWriter.removeIndexedDBFiles(), ]); log.info('Done with SQL deletion and IndexedDB file deletion.'); } catch (error) { @@ -485,7 +486,7 @@ export async function startApp(): Promise { // Set a flag to delete IndexedDB on next startup if it wasn't deleted just now. // We need to use direct data calls, since window.storage isn't ready yet. - await window.Signal.Data.createOrUpdateItem({ + await DataWriter.createOrUpdateItem({ id: 'indexeddb-delete-needed', value: true, }); @@ -826,7 +827,7 @@ export async function startApp(): Promise { log.info('background/shutdown: closing the database'); // Shut down the data interface cleanly - await window.Signal.Data.shutdown(); + await DataWriter.shutdown(); }, }); @@ -926,12 +927,12 @@ export async function startApp(): Promise { key: legacyChallengeKey, }); - await window.Signal.Data.clearAllErrorStickerPackAttempts(); + await DataWriter.clearAllErrorStickerPackAttempts(); } if (window.isBeforeVersion(lastVersion, 'v5.51.0-beta.2')) { await window.storage.put('groupCredentials', []); - await window.Signal.Data.removeAllProfileKeyCredentials(); + await DataWriter.removeAllProfileKeyCredentials(); } if (window.isBeforeVersion(lastVersion, 'v6.38.0-beta.1')) { @@ -963,9 +964,9 @@ export async function startApp(): Promise { if (newVersion || window.storage.get('needOrphanedAttachmentCheck')) { await window.storage.remove('needOrphanedAttachmentCheck'); - await window.Signal.Data.cleanupOrphanedAttachments(); + await DataWriter.cleanupOrphanedAttachments(); - drop(window.Signal.Data.ensureFilePermissions()); + drop(DataWriter.ensureFilePermissions()); } if ( @@ -1004,9 +1005,8 @@ export async function startApp(): Promise { const batchWithIndex = await migrateMessageData({ numMessagesPerBatch: NUM_MESSAGES_PER_BATCH, upgradeMessageSchema, - getMessagesNeedingUpgrade: - window.Signal.Data.getMessagesNeedingUpgrade, - saveMessages: window.Signal.Data.saveMessages, + getMessagesNeedingUpgrade: DataReader.getMessagesNeedingUpgrade, + saveMessages: DataWriter.saveMessages, }); log.info('idleDetector/idle: Upgraded messages:', batchWithIndex); isMigrationWithIndexComplete = batchWithIndex.done; @@ -1056,9 +1056,7 @@ export async function startApp(): Promise { } try { - await window.Signal.Data.deleteSentProtosOlderThan( - now - sentProtoMaxAge - ); + await DataWriter.deleteSentProtosOlderThan(now - sentProtoMaxAge); } catch (error) { log.error( 'background/onready/setInterval: Error deleting sent protos: ', @@ -1412,7 +1410,7 @@ export async function startApp(): Promise { log.info('Expiration start timestamp cleanup: starting...'); const messagesUnexpectedlyMissingExpirationStartTimestamp = - await window.Signal.Data.getMessagesUnexpectedlyMissingExpirationStartTimestamp(); + await DataReader.getMessagesUnexpectedlyMissingExpirationStartTimestamp(); log.info( `Expiration start timestamp cleanup: Found ${messagesUnexpectedlyMissingExpirationStartTimestamp.length} messages for cleanup` ); @@ -1446,7 +1444,7 @@ export async function startApp(): Promise { }; }); - await window.Signal.Data.saveMessages(newMessageAttributes, { + await DataWriter.saveMessages(newMessageAttributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -1454,10 +1452,10 @@ export async function startApp(): Promise { { log.info('Startup/syncTasks: Fetching tasks'); - const syncTasks = await window.Signal.Data.getAllSyncTasks(); + const syncTasks = await DataWriter.getAllSyncTasks(); log.info(`Startup/syncTasks: Queueing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info('`Startup/syncTasks: Done'); } @@ -2402,7 +2400,7 @@ export async function startApp(): Promise { `for ${sender.idForLogging()}` ); sender.set({ shareMyPhoneNumber: true }); - window.Signal.Data.updateConversation(sender.attributes); + drop(DataWriter.updateConversation(sender.attributes)); } if (!message.get('unidentifiedDeliveryReceived')) { @@ -2585,7 +2583,7 @@ export async function startApp(): Promise { const conversation = window.ConversationController.get(id)!; conversation.enableProfileSharing(); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); // Then we update our own profileKey if it's different from what we have const ourId = window.ConversationController.getOurConversationId(); @@ -3037,14 +3035,14 @@ export async function startApp(): Promise { window.ConversationController.getOurConversation(); if (ourConversation) { ourConversation.unset('username'); - window.Signal.Data.updateConversation(ourConversation.attributes); + await DataWriter.updateConversation(ourConversation.attributes); } // Then make sure outstanding conversation saves are flushed - await window.Signal.Data.flushUpdateConversationBatcher(); + await DataWriter.flushUpdateConversationBatcher(); // Then make sure that all previously-outstanding database saves are flushed - await window.Signal.Data.getItemById('manifestVersion'); + await DataReader.getItemById('manifestVersion'); // Finally, conversations in the database, and delete all config tables await window.textsecure.storage.protocol.removeAllConfiguration(); @@ -3319,13 +3317,13 @@ export async function startApp(): Promise { log.info(`${logId}: Saving ${syncTasks.length} sync tasks`); - await window.Signal.Data.saveSyncTasks(syncTasks); + await DataWriter.saveSyncTasks(syncTasks); confirm(); log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info(`${logId}: Done`); } @@ -3391,13 +3389,13 @@ export async function startApp(): Promise { log.info(`${logId}: Saving ${syncTasks.length} sync tasks`); - await window.Signal.Data.saveSyncTasks(syncTasks); + await DataWriter.saveSyncTasks(syncTasks); confirm(); log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info(`${logId}: Done`); } @@ -3463,13 +3461,13 @@ export async function startApp(): Promise { log.info(`${logId}: Saving ${syncTasks.length} sync tasks`); - await window.Signal.Data.saveSyncTasks(syncTasks); + await DataWriter.saveSyncTasks(syncTasks); confirm(); log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info(`${logId}: Done`); } @@ -3544,13 +3542,13 @@ export async function startApp(): Promise { log.info(`${logId}: Saving ${syncTasks.length} sync tasks`); - await window.Signal.Data.saveSyncTasks(syncTasks); + await DataWriter.saveSyncTasks(syncTasks); confirm(); log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info(`${logId}: Done`); } @@ -3579,13 +3577,13 @@ export async function startApp(): Promise { sentAt: timestamp, type: item.type, })); - await window.Signal.Data.saveSyncTasks(syncTasks); + await DataWriter.saveSyncTasks(syncTasks); confirm(); log.info(`${logId}: Queuing ${syncTasks.length} sync tasks`); - await queueSyncTasks(syncTasks, window.Signal.Data.removeSyncTaskById); + await queueSyncTasks(syncTasks, DataWriter.removeSyncTaskById); log.info(`${logId}: Done`); } diff --git a/ts/badges/badgeImageFileDownloader.ts b/ts/badges/badgeImageFileDownloader.ts index 19ba3db73..05916144b 100644 --- a/ts/badges/badgeImageFileDownloader.ts +++ b/ts/badges/badgeImageFileDownloader.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import PQueue from 'p-queue'; +import { DataWriter } from '../sql/Client'; import * as log from '../logging/log'; import { MINUTE } from '../util/durations'; import { missingCaseError } from '../util/missingCaseError'; @@ -100,7 +101,7 @@ async function downloadBadgeImageFile(url: string): Promise { imageFileData ); - await window.Signal.Data.badgeImageFileDownloaded(url, localPath); + await DataWriter.badgeImageFileDownloaded(url, localPath); window.reduxActions.badges.badgeImageFileDownloaded(url, localPath); diff --git a/ts/groups.ts b/ts/groups.ts index 3882242a2..45d2f6c18 100644 --- a/ts/groups.ts +++ b/ts/groups.ts @@ -19,7 +19,7 @@ import { maybeFetchNewCredentials, } from './services/groupCredentialFetcher'; import { storageServiceUploadJob } from './services/storage'; -import dataInterface from './sql/Client'; +import { DataReader, DataWriter } from './sql/Client'; import { toWebSafeBase64, fromWebSafeBase64 } from './util/webSafeBase64'; import { assertDev, strictAssert } from './util/assert'; import { isMoreRecentThan } from './util/timestamp'; @@ -263,7 +263,7 @@ const groupFieldsCache = new LRU({ max: MAX_CACHED_GROUP_FIELDS, }); -const { updateConversation } = dataInterface; +const { updateConversation } = DataWriter; if (!isNumber(MAX_MESSAGE_SCHEMA)) { throw new Error( @@ -1602,9 +1602,7 @@ export async function modifyGroupV2({ groupMembersV2: membersV2, }); - await dataInterface.replaceAllEndorsementsForGroup( - groupEndorsementData - ); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); } }); @@ -1921,7 +1919,7 @@ export async function createGroupV2( groupMembersV2: membersV2, }); - await dataInterface.replaceAllEndorsementsForGroup(groupEndorsementData); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); } catch (error) { if (!(error instanceof HTTPError)) { throw error; @@ -2023,7 +2021,7 @@ export async function createGroupV2( details: [{ type: 'create' }], }, }; - await dataInterface.saveMessages([createdTheGroupMessage], { + await DataWriter.saveMessages([createdTheGroupMessage], { forceSave: true, ourAci, }); @@ -2482,7 +2480,7 @@ export async function initiateMigrationToGroupV2( } // Save these most recent updates to conversation - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); strictAssert( Bytes.isNotEmpty(groupSendEndorsementResponse), @@ -2496,7 +2494,7 @@ export async function initiateMigrationToGroupV2( groupMembersV2: membersV2, }); - await dataInterface.replaceAllEndorsementsForGroup(groupEndorsementData); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); }); } catch (error) { const logId = conversation.idForLogging(); @@ -2960,7 +2958,7 @@ export async function respondToGroupV2Migration({ } // Save these most recent updates to conversation - updateConversation(conversation.attributes); + await updateConversation(conversation.attributes); // Finally, check for any changes to the group since its initial creation using normal // group update codepaths. @@ -2983,7 +2981,7 @@ export async function respondToGroupV2Migration({ groupMembersV2: membersV2, }); - await dataInterface.replaceAllEndorsementsForGroup(groupEndorsementData); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); } } @@ -3364,7 +3362,7 @@ async function appendChangeMessages( const ourAci = window.textsecure.storage.user.getCheckedAci(); - let lastMessage = await dataInterface.getLastConversationMessage({ + let lastMessage = await DataReader.getLastConversationMessage({ conversationId: conversation.id, }); @@ -3400,7 +3398,7 @@ async function appendChangeMessages( strictAssert(first !== undefined, 'First message must be there'); log.info(`appendChangeMessages/${logId}: updating ${first.id}`); - await dataInterface.saveMessage(first, { + await DataWriter.saveMessage(first, { ourAci, // We don't use forceSave here because this is an update of existing @@ -3410,7 +3408,7 @@ async function appendChangeMessages( log.info( `appendChangeMessages/${logId}: saving ${rest.length} new messages` ); - await dataInterface.saveMessages(rest, { + await DataWriter.saveMessages(rest, { ourAci, forceSave: true, }); @@ -3418,7 +3416,7 @@ async function appendChangeMessages( log.info( `appendChangeMessages/${logId}: saving ${mergedMessages.length} new messages` ); - await dataInterface.saveMessages(mergedMessages, { + await DataWriter.saveMessages(mergedMessages, { ourAci, forceSave: true, }); @@ -3766,7 +3764,7 @@ async function updateGroupViaState({ groupMembersV2: membersV2, }); - await dataInterface.replaceAllEndorsementsForGroup(groupEndorsementData); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); } return { @@ -3890,7 +3888,7 @@ async function updateGroupViaLogs({ strictAssert(groupId != null, 'Group must have groupId'); let cachedEndorsementsExpiration = - await dataInterface.getGroupSendCombinedEndorsementExpiration(groupId); + await DataReader.getGroupSendCombinedEndorsementExpiration(groupId); let response: GroupLogResponseType; let groupSendEndorsementResponse: Uint8Array | null = null; @@ -3925,7 +3923,7 @@ async function updateGroupViaLogs({ 'updateGroupViaLogs: Received paginated response, deleting group endorsements' ); // eslint-disable-next-line no-await-in-loop - await dataInterface.deleteAllEndorsementsForGroup(groupId); + await DataWriter.deleteAllEndorsementsForGroup(groupId); cachedEndorsementsExpiration = null; // gets sent as 0 in header } @@ -3971,7 +3969,7 @@ async function updateGroupViaLogs({ groupSecretParamsBase64: secretParams, }); - await dataInterface.replaceAllEndorsementsForGroup(groupEndorsementData); + await DataWriter.replaceAllEndorsementsForGroup(groupEndorsementData); } return updates; diff --git a/ts/groups/joinViaLink.ts b/ts/groups/joinViaLink.ts index b16698edf..bb6f1b2e0 100644 --- a/ts/groups/joinViaLink.ts +++ b/ts/groups/joinViaLink.ts @@ -7,6 +7,7 @@ import type { ConversationAttributesType } from '../model-types.d'; import type { ConversationModel } from '../models/conversations'; import type { PreJoinConversationType } from '../state/ducks/conversations'; +import { DataWriter } from '../sql/Client'; import * as Bytes from '../Bytes'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; @@ -160,7 +161,7 @@ export async function joinViaLink(value: string): Promise { const active_at = existingConversation.get('active_at') || Date.now(); // eslint-disable-next-line camelcase existingConversation.set({ active_at, timestamp }); - window.Signal.Data.updateConversation(existingConversation.attributes); + await DataWriter.updateConversation(existingConversation.attributes); // We're waiting for the left pane to re-sort before we navigate to that conversation await sleep(200); @@ -320,7 +321,7 @@ export async function joinViaLink(value: string): Promise { temporaryMemberCount: memberCount, timestamp, }); - window.Signal.Data.updateConversation( + await DataWriter.updateConversation( targetConversation.attributes ); } @@ -343,9 +344,7 @@ export async function joinViaLink(value: string): Promise { // We want to keep this conversation around, since the join succeeded isTemporary: undefined, }); - window.Signal.Data.updateConversation( - tempConversation.attributes - ); + await DataWriter.updateConversation(tempConversation.attributes); } window.reduxActions.conversations.showConversation({ @@ -357,7 +356,7 @@ export async function joinViaLink(value: string): Promise { window.ConversationController.dangerouslyRemoveById( tempConversation.id ); - await window.Signal.Data.removeConversation(tempConversation.id); + await DataWriter.removeConversation(tempConversation.id); } throw error; diff --git a/ts/jobs/AttachmentBackupManager.ts b/ts/jobs/AttachmentBackupManager.ts index 829e33f75..8774c6814 100644 --- a/ts/jobs/AttachmentBackupManager.ts +++ b/ts/jobs/AttachmentBackupManager.ts @@ -7,7 +7,7 @@ import { PassThrough } from 'node:stream'; import * as durations from '../util/durations'; import * as log from '../logging/log'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import * as Errors from '../types/errors'; import { redactGenericText } from '../util/privacy'; @@ -81,10 +81,10 @@ const THUMBNAIL_RETRY_CONFIG = { export class AttachmentBackupManager extends JobManager { private static _instance: AttachmentBackupManager | undefined; static defaultParams: JobManagerParamsType = { - markAllJobsInactive: dataInterface.markAllAttachmentBackupJobsInactive, - saveJob: dataInterface.saveAttachmentBackupJob, - removeJob: dataInterface.removeAttachmentBackupJob, - getNextJobs: dataInterface.getNextAttachmentBackupJobs, + markAllJobsInactive: DataWriter.markAllAttachmentBackupJobsInactive, + saveJob: DataWriter.saveAttachmentBackupJob, + removeJob: DataWriter.removeAttachmentBackupJob, + getNextJobs: DataWriter.getNextAttachmentBackupJobs, runJob: runAttachmentBackupJob, shouldHoldOffOnStartingQueuedJobs: () => { const reduxState = window.reduxStore?.getState(); @@ -604,7 +604,7 @@ async function copyToBackupTier({ // Update our local understanding of what's in the backup cdn const sizeOnBackupCdn = getAesCbcCiphertextLength(ciphertextLength); - await window.Signal.Data.saveBackupCdnObjectMetadata([ + await DataWriter.saveBackupCdnObjectMetadata([ { mediaId, cdnNumber: response.cdn, sizeOnBackupCdn }, ]); diff --git a/ts/jobs/AttachmentDownloadManager.ts b/ts/jobs/AttachmentDownloadManager.ts index 2dbcb122c..6720e53f3 100644 --- a/ts/jobs/AttachmentDownloadManager.ts +++ b/ts/jobs/AttachmentDownloadManager.ts @@ -14,7 +14,7 @@ import { AttachmentPermanentlyUndownloadableError, downloadAttachment as downloadAttachmentUtil, } from '../util/downloadAttachment'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import { getValue } from '../RemoteConfig'; import { isInCall as isInCallSelector } from '../state/selectors/calling'; @@ -103,10 +103,10 @@ export class AttachmentDownloadManager extends JobManager { const reduxState = window.reduxStore?.getState(); @@ -321,7 +321,7 @@ async function runDownloadAttachmentJob({ } finally { // This will fail if the message has been deleted before the download finished, which // is good - await dataInterface.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/jobs/JobQueueDatabaseStore.ts b/ts/jobs/JobQueueDatabaseStore.ts index fdce520ee..24746a6b7 100644 --- a/ts/jobs/JobQueueDatabaseStore.ts +++ b/ts/jobs/JobQueueDatabaseStore.ts @@ -6,7 +6,7 @@ import { AsyncQueue } from '../util/AsyncQueue'; import { concat, wrapPromise } from '../util/asyncIterables'; import type { JobQueueStore, StoredJob } from './types'; import { formatJobForInsert } from './formatJobForInsert'; -import databaseInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import * as log from '../logging/log'; type Database = { @@ -107,6 +107,8 @@ export class JobQueueDatabaseStore implements JobQueueStore { } } -export const jobQueueDatabaseStore = new JobQueueDatabaseStore( - databaseInterface -); +export const jobQueueDatabaseStore = new JobQueueDatabaseStore({ + getJobsInQueue: DataReader.getJobsInQueue, + insertJob: DataWriter.insertJob, + deleteJob: DataWriter.deleteJob, +}); diff --git a/ts/jobs/groupAvatarJobQueue.ts b/ts/jobs/groupAvatarJobQueue.ts index cfa2ea39a..9df6b257b 100644 --- a/ts/jobs/groupAvatarJobQueue.ts +++ b/ts/jobs/groupAvatarJobQueue.ts @@ -5,7 +5,7 @@ import * as z from 'zod'; import type { LoggerType } from '../types/Logging'; import { applyNewAvatar } from '../groups'; import { isGroupV2 } from '../util/whatTypeOfConversation'; -import Data from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import type { JOB_STATUS } from './JobQueue'; import { JobQueue } from './JobQueue'; @@ -46,7 +46,7 @@ export class GroupAvatarJobQueue extends JobQueue { const patch = await applyNewAvatar(newAvatarUrl, attributes, logId); convo.set(patch); - await Data.updateConversation(convo.attributes); + await DataWriter.updateConversation(convo.attributes); return undefined; } diff --git a/ts/jobs/helpers/sendDeleteForEveryone.ts b/ts/jobs/helpers/sendDeleteForEveryone.ts index 427d25627..6bd9ba4a9 100644 --- a/ts/jobs/helpers/sendDeleteForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteForEveryone.ts @@ -17,6 +17,7 @@ import { } from './handleMultipleSendErrors'; import { ourProfileKeyService } from '../../services/ourProfileKey'; import { wrapWithSyncMessageSend } from '../../util/wrapWithSyncMessageSend'; +import { DataWriter } from '../../sql/Client'; import type { ConversationModel } from '../../models/conversations'; import type { @@ -302,7 +303,7 @@ async function updateMessageWithSuccessfulSends( deletedForEveryoneSendStatus: {}, deletedForEveryoneFailed: undefined, }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -325,7 +326,7 @@ async function updateMessageWithSuccessfulSends( deletedForEveryoneSendStatus, deletedForEveryoneFailed: undefined, }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -341,7 +342,7 @@ async function updateMessageWithFailure( ); message.set({ deletedForEveryoneFailed: true }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/jobs/helpers/sendDeleteStoryForEveryone.ts b/ts/jobs/helpers/sendDeleteStoryForEveryone.ts index b895e0bb9..ae00353e7 100644 --- a/ts/jobs/helpers/sendDeleteStoryForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteStoryForEveryone.ts @@ -10,6 +10,7 @@ import { maybeExpandErrors, } from './handleMultipleSendErrors'; import { ourProfileKeyService } from '../../services/ourProfileKey'; +import { DataWriter } from '../../sql/Client'; import type { ConversationModel } from '../../models/conversations'; import type { @@ -277,7 +278,7 @@ async function updateMessageWithSuccessfulSends( deletedForEveryoneSendStatus: {}, deletedForEveryoneFailed: undefined, }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -300,7 +301,7 @@ async function updateMessageWithSuccessfulSends( deletedForEveryoneSendStatus, deletedForEveryoneFailed: undefined, }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -316,7 +317,7 @@ async function updateMessageWithFailure( ); message.set({ deletedForEveryoneFailed: true }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/jobs/helpers/sendNormalMessage.ts b/ts/jobs/helpers/sendNormalMessage.ts index e733f7d75..444487233 100644 --- a/ts/jobs/helpers/sendNormalMessage.ts +++ b/ts/jobs/helpers/sendNormalMessage.ts @@ -5,6 +5,7 @@ import { isNumber } from 'lodash'; import PQueue from 'p-queue'; import { v4 as generateUuid } from 'uuid'; +import { DataWriter } from '../../sql/Client'; import * as Errors from '../../types/errors'; import { strictAssert } from '../../util/assert'; import type { MessageModel } from '../../models/messages'; @@ -650,7 +651,7 @@ async function getMessageSendData({ ]); // Save message after uploading attachments - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -1106,7 +1107,7 @@ async function markMessageFailed({ }): Promise { message.markFailed(targetTimestamp); void message.saveErrors(errors, { skipSave: true }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/jobs/helpers/sendReaction.ts b/ts/jobs/helpers/sendReaction.ts index e1f982998..60b2205a1 100644 --- a/ts/jobs/helpers/sendReaction.ts +++ b/ts/jobs/helpers/sendReaction.ts @@ -11,6 +11,7 @@ import type { CallbackResultType } from '../../textsecure/Types.d'; import type { MessageModel } from '../../models/messages'; import type { MessageReactionType } from '../../model-types.d'; import type { ConversationModel } from '../../models/conversations'; +import { DataWriter } from '../../sql/Client'; import * as reactionUtil from '../../reactions/util'; import { isSent, SendStatus } from '../../messages/MessageSendState'; @@ -86,7 +87,7 @@ export async function sendReaction( if (!canReact(message.attributes, ourConversationId, findAndFormatContact)) { log.info(`could not react to ${messageId}. Removing this pending reaction`); markReactionFailed(message, pendingReaction); - await window.Signal.Data.saveMessage(message.attributes, { ourAci }); + await DataWriter.saveMessage(message.attributes, { ourAci }); return; } @@ -95,7 +96,7 @@ export async function sendReaction( `reacting to message ${messageId} ran out of time. Giving up on sending it` ); markReactionFailed(message, pendingReaction); - await window.Signal.Data.saveMessage(message.attributes, { ourAci }); + await DataWriter.saveMessage(message.attributes, { ourAci }); return; } @@ -335,7 +336,7 @@ export async function sendReaction( await reactionMessage.hydrateStoryContext(message.attributes, { shouldSave: false, }); - await window.Signal.Data.saveMessage(reactionMessage.attributes, { + await DataWriter.saveMessage(reactionMessage.attributes, { ourAci, forceSave: true, }); @@ -374,7 +375,7 @@ export async function sendReaction( toThrow: originalError || thrownError, }); } finally { - await window.Signal.Data.saveMessage(message.attributes, { ourAci }); + await DataWriter.saveMessage(message.attributes, { ourAci }); } } diff --git a/ts/jobs/helpers/sendStory.ts b/ts/jobs/helpers/sendStory.ts index 466fa190b..9b15267c2 100644 --- a/ts/jobs/helpers/sendStory.ts +++ b/ts/jobs/helpers/sendStory.ts @@ -23,7 +23,7 @@ import type { ServiceIdString } from '../../types/ServiceId'; import type { StoryDistributionIdString } from '../../types/StoryDistributionId'; import * as Errors from '../../types/errors'; import type { StoryMessageRecipientsType } from '../../types/Stories'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { SignalService as Proto } from '../../protobuf'; import { getMessagesById } from '../../messages/getMessagesById'; import { @@ -252,7 +252,7 @@ export async function sendStory( const distributionList = isGroupV2(conversation.attributes) ? undefined - : await dataInterface.getStoryDistributionWithMembers(receiverId); + : await DataReader.getStoryDistributionWithMembers(receiverId); let messageSendErrors: Array = []; @@ -541,7 +541,7 @@ export async function sendStory( } message.set('sendStateByConversationId', newSendStateByConversationId); - return window.Signal.Data.saveMessage(message.attributes, { + return DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); }) @@ -688,7 +688,7 @@ async function markMessageFailed( ): Promise { message.markFailed(); void message.saveErrors(errors, { skipSave: true }); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/messageModifiers/Deletes.ts b/ts/messageModifiers/Deletes.ts index 7f3da01dc..b94c87703 100644 --- a/ts/messageModifiers/Deletes.ts +++ b/ts/messageModifiers/Deletes.ts @@ -3,6 +3,7 @@ import type { MessageAttributesType } from '../model-types.d'; import { getAuthorId } from '../messages/helpers'; +import { DataReader } from '../sql/Client'; import * as log from '../logging/log'; import * as Errors from '../types/errors'; import { deleteForEveryone } from '../util/deleteForEveryone'; @@ -72,7 +73,7 @@ export async function onDelete(del: DeleteAttributesType): Promise { targetConversation.queueJob('Deletes.onDelete', async () => { log.info(`${logId}: Handling DOE`); - const messages = await window.Signal.Data.getMessagesBySentAt( + const messages = await DataReader.getMessagesBySentAt( del.targetSentTimestamp ); diff --git a/ts/messageModifiers/DeletesForMe.ts b/ts/messageModifiers/DeletesForMe.ts index 9177153af..4ecd5d839 100644 --- a/ts/messageModifiers/DeletesForMe.ts +++ b/ts/messageModifiers/DeletesForMe.ts @@ -18,9 +18,9 @@ import { getConversationFromTarget, getMessageQueryFromTarget, } from '../util/deleteForMe'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; -const { removeSyncTaskById } = dataInterface; +const { removeSyncTaskById } = DataWriter; export type DeleteForMeAttributesType = { conversation: ConversationToDelete; diff --git a/ts/messageModifiers/Edits.ts b/ts/messageModifiers/Edits.ts index dbd9d76d8..1b9d5c413 100644 --- a/ts/messageModifiers/Edits.ts +++ b/ts/messageModifiers/Edits.ts @@ -4,6 +4,7 @@ import type { MessageAttributesType } from '../model-types.d'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; +import { DataReader } from '../sql/Client'; import { drop } from '../util/drop'; import { getAuthorId } from '../messages/helpers'; import { handleEditMessage } from '../util/handleEditMessage'; @@ -117,7 +118,7 @@ export async function onEdit(edit: EditAttributesType): Promise { targetConversation.queueJob('Edits.onEdit', async () => { log.info(`${logId}: Handling edit`); - const messages = await window.Signal.Data.getMessagesBySentAt( + const messages = await DataReader.getMessagesBySentAt( edit.targetSentTimestamp ); diff --git a/ts/messageModifiers/MessageReceipts.ts b/ts/messageModifiers/MessageReceipts.ts index c083f3eba..68a9df53c 100644 --- a/ts/messageModifiers/MessageReceipts.ts +++ b/ts/messageModifiers/MessageReceipts.ts @@ -18,8 +18,8 @@ import { UNDELIVERED_SEND_STATUSES, sendStateReducer, } from '../messages/MessageSendState'; +import { DataReader, DataWriter } from '../sql/Client'; import type { DeleteSentProtoRecipientOptionsType } from '../sql/Interface'; -import dataInterface from '../sql/Client'; import * as log from '../logging/log'; import { getSourceServiceId } from '../messages/helpers'; import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp'; @@ -31,7 +31,7 @@ import { } from '../types/Receipt'; import { drop } from '../util/drop'; -const { deleteSentProtoRecipient, removeSyncTaskById } = dataInterface; +const { deleteSentProtoRecipient, removeSyncTaskById } = DataWriter; export const messageReceiptTypeSchema = z.enum(['Delivery', 'Read', 'View']); @@ -99,11 +99,11 @@ const processReceiptBatcher = createWaitBatcher({ const messagesMatchingTimestamp = // eslint-disable-next-line no-await-in-loop - await window.Signal.Data.getMessagesBySentAt(sentAt); + await DataReader.getMessagesBySentAt(sentAt); if (messagesMatchingTimestamp.length === 0) { // eslint-disable-next-line no-await-in-loop - const reaction = await window.Signal.Data.getReactionByTimestamp( + const reaction = await DataReader.getReactionByTimestamp( window.ConversationController.getOurConversationIdOrThrow(), sentAt ); diff --git a/ts/messageModifiers/Reactions.ts b/ts/messageModifiers/Reactions.ts index 72eca4c3b..80c33dbc8 100644 --- a/ts/messageModifiers/Reactions.ts +++ b/ts/messageModifiers/Reactions.ts @@ -5,6 +5,7 @@ import type { AciString } from '../types/ServiceId'; import type { MessageAttributesType } from '../model-types.d'; import type { MessageModel } from '../models/messages'; import type { ReactionSource } from '../reactions/ReactionSource'; +import { DataReader } from '../sql/Client'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; import { getAuthor } from '../messages/helpers'; @@ -66,9 +67,7 @@ async function findMessageForReaction({ reactionSenderConversationId: string; logId: string; }): Promise { - const messages = await window.Signal.Data.getMessagesBySentAt( - targetTimestamp - ); + const messages = await DataReader.getMessagesBySentAt(targetTimestamp); const matchingMessages = messages.filter(message => isMessageAMatchForReaction({ diff --git a/ts/messageModifiers/ReadSyncs.ts b/ts/messageModifiers/ReadSyncs.ts index bed78ca3e..5cf2efd73 100644 --- a/ts/messageModifiers/ReadSyncs.ts +++ b/ts/messageModifiers/ReadSyncs.ts @@ -16,9 +16,9 @@ import { notificationService } from '../services/notifications'; import { queueUpdateMessage } from '../util/messageBatcher'; import { strictAssert } from '../util/assert'; import { isAciString } from '../util/isAciString'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; -const { removeSyncTaskById } = dataInterface; +const { removeSyncTaskById } = DataWriter; export const readSyncTaskSchema = z.object({ type: z.literal('ReadSync').readonly(), @@ -51,7 +51,7 @@ async function maybeItIsAReactionReadSync( const { readSync } = sync; const logId = `ReadSyncs.onSync(timestamp=${readSync.timestamp})`; - const readReaction = await window.Signal.Data.markReactionAsRead( + const readReaction = await DataWriter.markReactionAsRead( readSync.senderAci, Number(readSync.timestamp) ); @@ -129,9 +129,7 @@ export async function onSync(sync: ReadSyncAttributesType): Promise { const logId = `ReadSyncs.onSync(timestamp=${readSync.timestamp})`; try { - const messages = await window.Signal.Data.getMessagesBySentAt( - readSync.timestamp - ); + const messages = await DataReader.getMessagesBySentAt(readSync.timestamp); const found = messages.find(item => { const sender = window.ConversationController.lookupOrCreate({ diff --git a/ts/messageModifiers/ViewOnceOpenSyncs.ts b/ts/messageModifiers/ViewOnceOpenSyncs.ts index 43d206ec7..f7cb77933 100644 --- a/ts/messageModifiers/ViewOnceOpenSyncs.ts +++ b/ts/messageModifiers/ViewOnceOpenSyncs.ts @@ -3,6 +3,7 @@ import type { AciString } from '../types/ServiceId'; import type { MessageModel } from '../models/messages'; +import { DataReader } from '../sql/Client'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; import { getMessageIdForLogging } from '../util/idForLogging'; @@ -66,9 +67,7 @@ export async function onSync( const logId = `ViewOnceOpenSyncs.onSync(timestamp=${sync.timestamp})`; try { - const messages = await window.Signal.Data.getMessagesBySentAt( - sync.timestamp - ); + const messages = await DataReader.getMessagesBySentAt(sync.timestamp); const found = messages.find(item => { const itemSourceAci = item.sourceServiceId; diff --git a/ts/messageModifiers/ViewSyncs.ts b/ts/messageModifiers/ViewSyncs.ts index 24ba6b5c4..8f36b40fb 100644 --- a/ts/messageModifiers/ViewSyncs.ts +++ b/ts/messageModifiers/ViewSyncs.ts @@ -18,9 +18,7 @@ import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads'; import { queueUpdateMessage } from '../util/messageBatcher'; import { AttachmentDownloadUrgency } from '../jobs/AttachmentDownloadManager'; import { isAciString } from '../util/isAciString'; -import dataInterface from '../sql/Client'; - -const { removeSyncTaskById } = dataInterface; +import { DataReader, DataWriter } from '../sql/Client'; export const viewSyncTaskSchema = z.object({ type: z.literal('ViewSync').readonly(), @@ -42,7 +40,7 @@ export type ViewSyncAttributesType = { const viewSyncs = new Map(); async function remove(sync: ViewSyncAttributesType): Promise { - await removeSyncTaskById(sync.syncTaskId); + await DataWriter.removeSyncTaskById(sync.syncTaskId); } export async function forMessage( @@ -92,9 +90,7 @@ export async function onSync(sync: ViewSyncAttributesType): Promise { const logId = `ViewSyncs.onSync(timestamp=${viewSync.timestamp})`; try { - const messages = await window.Signal.Data.getMessagesBySentAt( - viewSync.timestamp - ); + const messages = await DataReader.getMessagesBySentAt(viewSync.timestamp); const found = messages.find(item => { const sender = window.ConversationController.lookupOrCreate({ diff --git a/ts/messages/copyQuote.ts b/ts/messages/copyQuote.ts index 1833af978..ec6075317 100644 --- a/ts/messages/copyQuote.ts +++ b/ts/messages/copyQuote.ts @@ -4,6 +4,7 @@ import { omit } from 'lodash'; import * as log from '../logging/log'; +import { DataReader, DataWriter } from '../sql/Client'; import type { QuotedMessageType } from '../model-types'; import type { MessageModel } from '../models/messages'; import { SignalService } from '../protobuf'; @@ -50,7 +51,7 @@ export const copyFromQuotedMessage = async ( queryMessage = matchingMessage; } else { log.info('copyFromQuotedMessage: db lookup needed', id); - const messages = await window.Signal.Data.getMessagesBySentAt(id); + const messages = await DataReader.getMessagesBySentAt(id); const found = messages.find(item => isQuoteAMatch(item, conversationId, result) ); @@ -142,7 +143,7 @@ export const copyQuoteContentFromOriginal = async ( originalMessage.attributes ); originalMessage.set(upgradedMessage); - await window.Signal.Data.saveMessage(upgradedMessage, { + await DataWriter.saveMessage(upgradedMessage, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/messages/getMessageById.ts b/ts/messages/getMessageById.ts index 20ab18929..5b0b5094e 100644 --- a/ts/messages/getMessageById.ts +++ b/ts/messages/getMessageById.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { DataReader } from '../sql/Client'; import type { MessageAttributesType } from '../model-types.d'; import * as Errors from '../types/errors'; import type { MessageModel } from '../models/messages'; @@ -16,7 +17,7 @@ export async function __DEPRECATED$getMessageById( let found: MessageAttributesType | undefined; try { - found = await window.Signal.Data.getMessageById(messageId); + found = await DataReader.getMessageById(messageId); } catch (err: unknown) { log.error( `failed to load message with id ${messageId} ` + diff --git a/ts/messages/getMessagesById.ts b/ts/messages/getMessagesById.ts index 8912a214e..d01776385 100644 --- a/ts/messages/getMessagesById.ts +++ b/ts/messages/getMessagesById.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { DataReader } from '../sql/Client'; import type { MessageModel } from '../models/messages'; import type { MessageAttributesType } from '../model-types.d'; import * as Errors from '../types/errors'; @@ -23,7 +24,7 @@ export async function getMessagesById( let rawMessagesFromDatabase: Array; try { - rawMessagesFromDatabase = await window.Signal.Data.getMessagesById( + rawMessagesFromDatabase = await DataReader.getMessagesById( messageIdsToLookUpInDatabase ); } catch (err: unknown) { diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 02987dd71..67b43d474 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -15,6 +15,7 @@ import type { QuotedMessageType, SenderKeyInfoType, } from '../model-types.d'; +import { DataReader, DataWriter } from '../sql/Client'; import { getConversation } from '../util/getConversation'; import { drop } from '../util/drop'; import { isShallowEqual } from '../util/isShallowEqual'; @@ -195,7 +196,6 @@ const { writeNewAttachmentData, } = window.Signal.Migrations; const { - addStickerPackReference, getConversationRangeCenteredOnMessage, getOlderMessagesByConversation, getMessageMetricsForConversation, @@ -203,7 +203,8 @@ const { getMostRecentAddressableMessages, getMostRecentAddressableNondisappearingMessages, getNewerMessagesByConversation, -} = window.Signal.Data; +} = DataReader; +const { addStickerPackReference } = DataWriter; const FIVE_MINUTES = MINUTE * 5; const FETCH_TIMEOUT = SECOND * 30; @@ -471,7 +472,7 @@ export class ConversationModel extends window.Backbone getSenderKeyInfo: () => this.get('senderKeyInfo'), saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) => { this.set({ senderKeyInfo }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); }, }; } @@ -828,7 +829,7 @@ export class ConversationModel extends window.Backbone }); if (shouldSave) { - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } const e164 = this.get('e164'); @@ -882,7 +883,7 @@ export class ConversationModel extends window.Backbone }); if (shouldSave) { - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } if ( @@ -1015,7 +1016,7 @@ export class ConversationModel extends window.Backbone drop(this.queueJob('removeContact', () => this.maybeSetContactRemoved())); if (shouldSave) { - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } } @@ -1048,7 +1049,7 @@ export class ConversationModel extends window.Backbone await this.maybeClearContactRemoved(); if (shouldSave) { - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } } @@ -1239,7 +1240,7 @@ export class ConversationModel extends window.Backbone this.set({ masterKey, secretParams, publicParams, groupVersion: 2 }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } getGroupV2Info( @@ -1429,7 +1430,7 @@ export class ConversationModel extends window.Backbone removalStage: 'messageRequest', }); await this.maybeClearContactRemoved(); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } void this.addSingleMessage(message); @@ -1831,7 +1832,7 @@ export class ConversationModel extends window.Backbone const upgradedMessage = await upgradeMessageSchema(attributes); message.set(upgradedMessage); // eslint-disable-next-line no-await-in-loop - await window.Signal.Data.saveMessage(upgradedMessage, { ourAci }); + await DataWriter.saveMessage(upgradedMessage, { ourAci }); upgraded += 1; } } @@ -1900,7 +1901,7 @@ export class ConversationModel extends window.Backbone void this.addChangeNumberNotification(oldValue, e164); } - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); this.trigger('idUpdated', this, 'e164', oldValue); this.captureChange('updateE164'); } @@ -1917,7 +1918,7 @@ export class ConversationModel extends window.Backbone ? normalizeServiceId(serviceId, 'Conversation.updateServiceId') : undefined ); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); this.trigger('idUpdated', this, 'serviceId', oldValue); // We should delete the old sessions and identity information in all situations except @@ -1950,7 +1951,7 @@ export class ConversationModel extends window.Backbone this.set({ previousIdentityKey: identityKey, }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } updatePni(pni: PniString | undefined, pniSignatureVerified: boolean): void { @@ -2025,7 +2026,7 @@ export class ConversationModel extends window.Backbone ); } - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); this.trigger('idUpdated', this, 'pni', oldValue); this.captureChange('updatePni'); } @@ -2034,7 +2035,7 @@ export class ConversationModel extends window.Backbone const oldValue = this.get('groupId'); if (groupId && groupId !== oldValue) { this.set('groupId', groupId); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); this.trigger('idUpdated', this, 'groupId', oldValue); } } @@ -2048,14 +2049,14 @@ export class ConversationModel extends window.Backbone } this.set('reportingToken', newValue); - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } incrementMessageCount(): void { this.set({ messageCount: (this.get('messageCount') || 0) + 1, }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } incrementSentMessageCount({ dry = false }: { dry?: boolean } = {}): @@ -2073,7 +2074,7 @@ export class ConversationModel extends window.Backbone return update; } this.set(update); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); return undefined; } @@ -2093,7 +2094,7 @@ export class ConversationModel extends window.Backbone const first = messages ? messages[0] : undefined; // eslint-disable-next-line no-await-in-loop - messages = await window.Signal.Data.getOlderMessagesByConversation({ + messages = await DataReader.getOlderMessagesByConversation({ conversationId: this.get('id'), includeStoryReplies: !isGroup(this.attributes), limit: 100, @@ -2143,7 +2144,7 @@ export class ConversationModel extends window.Backbone ); const shouldSave = await registered.queueAttachmentDownloads(); if (shouldSave) { - await window.Signal.Data.saveMessage(registered.attributes, { + await DataWriter.saveMessage(registered.attributes, { ourAci, }); } @@ -2178,7 +2179,7 @@ export class ConversationModel extends window.Backbone messageRequestResponseEvent: event, }; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -2358,7 +2359,7 @@ export class ConversationModel extends window.Backbone } } finally { if (shouldSave) { - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } } } @@ -2439,7 +2440,7 @@ export class ConversationModel extends window.Backbone messageRequestResponseType: messageRequestEnum.ACCEPT, active_at: this.get('active_at') || Date.now(), }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } async cancelJoinRequest(): Promise { @@ -2631,7 +2632,7 @@ export class ConversationModel extends window.Backbone if (oldVerified !== verified) { this.set({ verified }); this.captureChange(`updateVerified from=${oldVerified} to=${verified}`); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } return; @@ -2694,7 +2695,7 @@ export class ConversationModel extends window.Backbone this.set({ verified }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); if (beginningVerified !== verified) { this.captureChange( @@ -2938,7 +2939,7 @@ export class ConversationModel extends window.Backbone // this type does not fully implement the interface it is expected to } as unknown as MessageAttributesType; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); const model = window.MessageCache.__DEPRECATED$register( @@ -2990,7 +2991,7 @@ export class ConversationModel extends window.Backbone // this type does not fully implement the interface it is expected to } as unknown as MessageAttributesType; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); const model = window.MessageCache.__DEPRECATED$register( @@ -3044,7 +3045,7 @@ export class ConversationModel extends window.Backbone schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, }; - await window.Signal.Data.saveMessage(message, { + await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -3108,7 +3109,7 @@ export class ConversationModel extends window.Backbone schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, }; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -3161,7 +3162,7 @@ export class ConversationModel extends window.Backbone schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, }; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -3212,7 +3213,7 @@ export class ConversationModel extends window.Backbone verifiedChanged: verifiedChangeId, }; - await window.Signal.Data.saveMessage(message, { + await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -3255,7 +3256,7 @@ export class ConversationModel extends window.Backbone // TODO: DESKTOP-722 } as unknown as MessageAttributesType; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); const model = window.MessageCache.__DEPRECATED$register( @@ -3300,7 +3301,7 @@ export class ConversationModel extends window.Backbone ...extra, }; - const id = await window.Signal.Data.saveMessage( + const id = await DataWriter.saveMessage( // TODO: DESKTOP-722 message as MessageAttributesType, { @@ -3395,7 +3396,7 @@ export class ConversationModel extends window.Backbone const message = window.MessageCache.__DEPRECATED$getById(notificationId); if (message) { - await window.Signal.Data.removeMessage(message.id, { + await DataWriter.removeMessage(message.id, { singleProtoJobQueue, }); } @@ -3422,7 +3423,7 @@ export class ConversationModel extends window.Backbone 'contact-removed-notification' ); this.set('pendingRemovedContactNotification', notificationId); - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } async maybeClearContactRemoved(): Promise { @@ -3438,7 +3439,7 @@ export class ConversationModel extends window.Backbone const message = window.MessageCache.__DEPRECATED$getById(notificationId); if (message) { - await window.Signal.Data.removeMessage(message.id, { + await DataWriter.removeMessage(message.id, { singleProtoJobQueue, }); } @@ -4015,7 +4016,7 @@ export class ConversationModel extends window.Backbone log.info( `enqueueMessageForSend: saving message ${message.id} and job ${jobToInsert.id}` ); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { jobToInsert, forceSave: true, ourAci: window.textsecure.storage.user.getCheckedAci(), @@ -4063,7 +4064,7 @@ export class ConversationModel extends window.Backbone ); } - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); return attributes; } @@ -4127,7 +4128,7 @@ export class ConversationModel extends window.Backbone }); } - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); this.captureChange('clearUsername'); } @@ -4152,7 +4153,7 @@ export class ConversationModel extends window.Backbone this.captureChange('updateUsername'); if (shouldSave) { - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } } @@ -4169,7 +4170,7 @@ export class ConversationModel extends window.Backbone const conversationId = this.id; - const stats = await window.Signal.Data.getConversationMessageStats({ + const stats = await DataReader.getConversationMessageStats({ conversationId, includeStoryReplies: !isGroup(this.attributes), }); @@ -4256,14 +4257,14 @@ export class ConversationModel extends window.Backbone : false, }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } setArchived(isArchived: boolean): void { const before = this.get('isArchived'); this.set({ isArchived }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); const after = this.get('isArchived'); @@ -4279,7 +4280,7 @@ export class ConversationModel extends window.Backbone const previousMarkedUnread = this.get('markedUnread'); this.set({ markedUnread }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); if (Boolean(previousMarkedUnread) !== Boolean(markedUnread)) { this.captureChange('markedUnread'); @@ -4562,7 +4563,7 @@ export class ConversationModel extends window.Backbone // the pending flags. await this.maybeRemoveUniversalTimer(); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); // When we add a disappearing messages notification to the conversation, we want it // to be above the message that initiated that change, hence the subtraction. @@ -4596,7 +4597,7 @@ export class ConversationModel extends window.Backbone type: 'timer-notification' as const, }; - await window.Signal.Data.saveMessage(attributes, { + await DataWriter.saveMessage(attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -4655,11 +4656,8 @@ export class ConversationModel extends window.Backbone includeStoryReplies: !isGroup(this.attributes), }; const [unreadCount, unreadMentionsCount] = await Promise.all([ - window.Signal.Data.getTotalUnreadForConversation(this.id, options), - window.Signal.Data.getTotalUnreadMentionsOfMeForConversation( - this.id, - options - ), + DataReader.getTotalUnreadForConversation(this.id, options), + DataReader.getTotalUnreadMentionsOfMeForConversation(this.id, options), ]); const prevUnreadCount = this.get('unreadCount'); @@ -4672,7 +4670,7 @@ export class ConversationModel extends window.Backbone unreadCount, unreadMentionsCount, }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } } @@ -4870,7 +4868,7 @@ export class ConversationModel extends window.Backbone // We will update the conversation during storage service sync if (!viaStorageServiceSync) { - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } return true; @@ -4971,7 +4969,7 @@ export class ConversationModel extends window.Backbone this.set({ lastProfile: { profileKey, profileKeyVersion } }); - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } async removeLastProfile( @@ -4997,7 +4995,7 @@ export class ConversationModel extends window.Backbone profileAvatar: undefined, }); - await window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); } hasMember(serviceId: ServiceIdString): boolean { @@ -5044,7 +5042,7 @@ export class ConversationModel extends window.Backbone active_at: null, pendingUniversalTimer: undefined, }); - window.Signal.Data.updateConversation(this.attributes); + await DataWriter.updateConversation(this.attributes); const ourConversation = window.ConversationController.getOurConversationOrThrow(); @@ -5114,7 +5112,7 @@ export class ConversationModel extends window.Backbone } log.info(`${logId}: Starting delete`); - await window.Signal.Data.removeMessagesInConversation(this.id, { + await DataWriter.removeMessagesInConversation(this.id, { fromSync: source !== 'local-delete-sync', logId: this.idForLogging(), singleProtoJobQueue, @@ -5227,7 +5225,7 @@ export class ConversationModel extends window.Backbone ); this.set({ hideStory }); this.captureChange('hideStory'); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } setMuteExpiration( @@ -5247,7 +5245,7 @@ export class ConversationModel extends window.Backbone if (!viaStorageServiceSync) { this.captureChange('mutedUntilTimestamp'); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } } @@ -5572,7 +5570,7 @@ export class ConversationModel extends window.Backbone if (this.get('isArchived')) { this.set({ isArchived: false }); } - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } unpin(): void { @@ -5591,7 +5589,7 @@ export class ConversationModel extends window.Backbone this.writePinnedConversations([...pinnedConversationIds]); this.set('isPinned', false); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } writePinnedConversations(pinnedConversationIds: Array): void { @@ -5612,7 +5610,7 @@ export class ConversationModel extends window.Backbone } this.set({ dontNotifyForMentionsIfMuted: newValue }); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); this.captureChange('dontNotifyForMentionsIfMuted'); } @@ -5620,7 +5618,7 @@ export class ConversationModel extends window.Backbone groupNameCollisions: ReadonlyDeep ): void { this.set('acknowledgedGroupNameCollisions', groupNameCollisions); - window.Signal.Data.updateConversation(this.attributes); + drop(DataWriter.updateConversation(this.attributes)); } onOpenStart(): void { diff --git a/ts/models/messages.ts b/ts/models/messages.ts index f49c1c44c..9dcc5cfe4 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -134,7 +134,7 @@ import { addToAttachmentDownloadQueue, shouldUseAttachmentDownloadQueue, } from '../util/attachmentDownloadQueue'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import { shouldReplyNotifyUser } from '../util/shouldReplyNotifyUser'; import type { RawBodyRange } from '../types/BodyRange'; import { BodyRange } from '../types/BodyRange'; @@ -163,7 +163,7 @@ window.Whisper = window.Whisper || {}; const { Message: TypedMessage } = window.Signal.Types; const { upgradeMessageSchema } = window.Signal.Migrations; -const { getMessageBySender } = window.Signal.Data; +const { getMessageBySender } = DataReader; export class MessageModel extends window.Backbone.Model { CURRENT_PROTOCOL_VERSION?: number; @@ -459,9 +459,7 @@ export class MessageModel extends window.Backbone.Model { isQuoteAMatch(message.attributes, this.get('conversationId'), quote) ); if (!matchingMessage) { - const messages = await window.Signal.Data.getMessagesBySentAt( - Number(sentAt) - ); + const messages = await DataReader.getMessagesBySentAt(Number(sentAt)); const found = messages.find(item => isQuoteAMatch(item, this.get('conversationId'), quote) ); @@ -540,12 +538,12 @@ export class MessageModel extends window.Backbone.Model { this.getConversation()?.debouncedUpdateLastMessage(); if (shouldPersist) { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } - await window.Signal.Data.deleteSentProtoByMessageId(this.id); + await DataWriter.deleteSentProtoByMessageId(this.id); } override isEmpty(): boolean { @@ -675,7 +673,7 @@ export class MessageModel extends window.Backbone.Model { this.set({ errors }); if (!skipSave && !this.doNotSave) { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -695,7 +693,7 @@ export class MessageModel extends window.Backbone.Model { if (storyDistributionListId) { const storyDistribution = - await dataInterface.getStoryDistributionWithMembers( + await DataReader.getStoryDistributionWithMembers( storyDistributionListId ); @@ -755,7 +753,7 @@ export class MessageModel extends window.Backbone.Model { timestamp: this.attributes.timestamp, }, async jobToInsert => { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -770,7 +768,7 @@ export class MessageModel extends window.Backbone.Model { revision: conversation.get('revision'), }, async jobToInsert => { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -918,7 +916,7 @@ export class MessageModel extends window.Backbone.Model { } if (!this.doNotSave) { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -1092,7 +1090,7 @@ export class MessageModel extends window.Backbone.Model { } if (!this.doNotSave) { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -1151,7 +1149,7 @@ export class MessageModel extends window.Backbone.Model { } throw error; } finally { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -1305,7 +1303,7 @@ export class MessageModel extends window.Backbone.Model { return result; } - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); return result; @@ -1521,7 +1519,7 @@ export class MessageModel extends window.Backbone.Model { sendStateByConversationId, unidentifiedDeliveries: [...unidentifiedDeliveriesSet], }); - await window.Signal.Data.saveMessage(toUpdate.attributes, { + await DataWriter.saveMessage(toUpdate.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -1780,7 +1778,7 @@ export class MessageModel extends window.Backbone.Model { if (storyDistributionListId) { const storyDistribution = - await dataInterface.getStoryDistributionWithMembers( + await DataReader.getStoryDistributionWithMembers( storyDistributionListId ); @@ -2056,7 +2054,7 @@ export class MessageModel extends window.Backbone.Model { conversation.setArchived(false); } - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); const giftBadge = message.get('giftBadge'); if (giftBadge) { @@ -2295,7 +2293,7 @@ export class MessageModel extends window.Backbone.Model { shouldSave: false, }); // Note: generatedMessage comes with an id, so we have to force this save - await window.Signal.Data.saveMessage(generatedMessage.attributes, { + await DataWriter.saveMessage(generatedMessage.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -2320,9 +2318,7 @@ export class MessageModel extends window.Backbone.Model { targetConversation.set({ active_at: messageToAdd.get('timestamp'), }); - window.Signal.Data.updateConversation( - targetConversation.attributes - ); + await DataWriter.updateConversation(targetConversation.attributes); } } @@ -2414,14 +2410,14 @@ export class MessageModel extends window.Backbone.Model { } if (reaction.remove) { - await window.Signal.Data.removeReactionFromConversation({ + await DataWriter.removeReactionFromConversation({ emoji: reaction.emoji, fromId: reaction.fromId, targetAuthorServiceId: reaction.targetAuthorAci, targetTimestamp: reaction.targetTimestamp, }); } else { - await window.Signal.Data.addReaction( + await DataWriter.addReaction( { conversationId: this.get('conversationId'), emoji: reaction.emoji, @@ -2465,7 +2461,7 @@ export class MessageModel extends window.Backbone.Model { await generatedMessage.hydrateStoryContext(this.attributes, { shouldSave: false, }); - await window.Signal.Data.saveMessage(generatedMessage.attributes, { + await DataWriter.saveMessage(generatedMessage.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), forceSave: true, }); @@ -2499,7 +2495,7 @@ export class MessageModel extends window.Backbone.Model { jobToInsert.id }` ); - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -2508,7 +2504,7 @@ export class MessageModel extends window.Backbone.Model { await conversationJobQueue.add(jobData); } } else if (shouldPersist && !isStory(this.attributes)) { - await window.Signal.Data.saveMessage(this.attributes, { + await DataWriter.saveMessage(this.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/reactions/enqueueReactionForSend.ts b/ts/reactions/enqueueReactionForSend.ts index 44709e71f..63c4174e2 100644 --- a/ts/reactions/enqueueReactionForSend.ts +++ b/ts/reactions/enqueueReactionForSend.ts @@ -4,6 +4,7 @@ import noop from 'lodash/noop'; import { v4 as generateUuid } from 'uuid'; +import { DataWriter } from '../sql/Client'; import type { ReactionAttributesType } from '../messageModifiers/Reactions'; import { ReactionSource } from './ReactionSource'; import { __DEPRECATED$getMessageById } from '../messages/getMessageById'; @@ -63,9 +64,7 @@ export async function enqueueReactionForSend({ log.info('Enabling profile sharing for reaction send'); if (!messageConversation.get('profileSharing')) { messageConversation.set('profileSharing', true); - await window.Signal.Data.updateConversation( - messageConversation.attributes - ); + await DataWriter.updateConversation(messageConversation.attributes); } await messageConversation.restoreContact(); } diff --git a/ts/services/MessageCache.ts b/ts/services/MessageCache.ts index c913ae331..ba3ba8950 100644 --- a/ts/services/MessageCache.ts +++ b/ts/services/MessageCache.ts @@ -6,6 +6,7 @@ import { throttle } from 'lodash'; import LRU from 'lru-cache'; import type { MessageAttributesType } from '../model-types.d'; import type { MessageModel } from '../models/messages'; +import { DataReader, DataWriter } from '../sql/Client'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; import { getEnvironment, Environment } from '../environment'; @@ -152,7 +153,7 @@ export class MessageCache { let messageAttributesFromDatabase: MessageAttributesType | undefined; try { - messageAttributesFromDatabase = await window.Signal.Data.getMessageById( + messageAttributesFromDatabase = await DataReader.getMessageById( messageId ); } catch (err: unknown) { @@ -260,7 +261,7 @@ export class MessageCache { return; } - return window.Signal.Data.saveMessage(nextMessageAttributes, { + return DataWriter.saveMessage(nextMessageAttributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index a06f9bca0..031dbcbb9 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -9,7 +9,12 @@ import pTimeout from 'p-timeout'; import { Readable } from 'stream'; import { Backups, SignalService } from '../../protobuf'; -import Data from '../../sql/Client'; +import { + DataReader, + DataWriter, + pauseWriteAccess, + resumeWriteAccess, +} from '../../sql/Client'; import type { PageMessagesCursorType } from '../../sql/Interface'; import * as log from '../../logging/log'; import { GiftBadgeStates } from '../../components/conversation/Message'; @@ -183,15 +188,16 @@ export class BackupExportStream extends Readable { (async () => { log.info('BackupExportStream: starting...'); drop(AttachmentBackupManager.stop()); - await Data.pauseWriteAccess(); + await pauseWriteAccess(); try { await this.unsafeRun(backupLevel); } catch (error) { this.emit('error', error); } finally { - await Data.resumeWriteAccess(); + await resumeWriteAccess(); + // TODO (DESKTOP-7344): Clear & add backup jobs in a single transaction - await Data.clearAllAttachmentBackupJobs(); + await DataWriter.clearAllAttachmentBackupJobs(); await Promise.all( this.attachmentBackupJobs.map(job => AttachmentBackupManager.addJobAndMaybeThumbnailJob(job) @@ -248,7 +254,8 @@ export class BackupExportStream extends Readable { stats.conversations += 1; } - const distributionLists = await Data.getAllStoryDistributionsWithMembers(); + const distributionLists = + await DataReader.getAllStoryDistributionsWithMembers(); for (const list of distributionLists) { const { PrivacyMode } = Backups.DistributionList; @@ -296,7 +303,7 @@ export class BackupExportStream extends Readable { stats.distributionLists += 1; } - const stickerPacks = await Data.getInstalledStickerPacks(); + const stickerPacks = await DataReader.getInstalledStickerPacks(); for (const { id, key } of stickerPacks) { this.pushFrame({ @@ -379,7 +386,9 @@ export class BackupExportStream extends Readable { try { while (!cursor?.done) { // eslint-disable-next-line no-await-in-loop - const { messages, cursor: newCursor } = await Data.pageMessages(cursor); + const { messages, cursor: newCursor } = await DataReader.pageMessages( + cursor + ); // eslint-disable-next-line no-await-in-loop const items = await pMap( @@ -413,7 +422,7 @@ export class BackupExportStream extends Readable { } } finally { if (cursor !== undefined) { - await Data.finishPageMessages(cursor); + await DataReader.finishPageMessages(cursor); } } diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index ef617032f..4c372c851 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -9,7 +9,7 @@ import { Writable } from 'stream'; import { isNumber } from 'lodash'; import { Backups, SignalService } from '../../protobuf'; -import Data from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import type { StoryDistributionWithMembersType } from '../../sql/Interface'; import * as log from '../../logging/log'; import { GiftBadgeStates } from '../../components/conversation/Message'; @@ -112,14 +112,14 @@ async function processConversationOpBatch( `updates=${updates.length}` ); - await Data.saveConversations(saves); - await Data.updateConversations(updates); + await DataWriter.saveConversations(saves); + await DataWriter.updateConversations(updates); } async function processMessagesBatch( ourAci: AciString, batch: ReadonlyArray ): Promise { - const ids = await Data.saveMessages(batch, { + const ids = await DataWriter.saveMessages(batch, { forceSave: true, ourAci, }); @@ -138,7 +138,7 @@ async function processMessagesBatch( if (editHistory?.length) { drop( - Data.saveEditedMessages( + DataWriter.saveEditedMessages( attributes, ourAci, editHistory.slice(0, -1).map(({ timestamp }) => ({ @@ -966,7 +966,7 @@ export class BackupImportStream extends Writable { }; } - await Data.createNewStoryDistribution(result); + await DataWriter.createNewStoryDistribution(result); } private async fromChat(chat: Backups.IChat): Promise { diff --git a/ts/services/backups/index.ts b/ts/services/backups/index.ts index ce2371b3e..158ef97ef 100644 --- a/ts/services/backups/index.ts +++ b/ts/services/backups/index.ts @@ -12,6 +12,7 @@ import { createCipheriv, createHmac, randomBytes } from 'crypto'; import { noop } from 'lodash'; import { BackupLevel } from '@signalapp/libsignal-client/zkgroup'; +import { DataReader, DataWriter } from '../../sql/Client'; import * as log from '../../logging/log'; import * as Bytes from '../../Bytes'; import { strictAssert } from '../../util/assert'; @@ -187,7 +188,7 @@ export class BackupsService { public async fetchAndSaveBackupCdnObjectMetadata(): Promise { log.info('fetchAndSaveBackupCdnObjectMetadata: clearing existing metadata'); - await window.Signal.Data.clearAllBackupCdnObjectMetadata(); + await DataWriter.clearAllBackupCdnObjectMetadata(); let cursor: string | undefined; const PAGE_SIZE = 1000; @@ -198,7 +199,7 @@ export class BackupsService { const listResult = await this.api.listMedia({ cursor, limit: PAGE_SIZE }); // eslint-disable-next-line no-await-in-loop - await window.Signal.Data.saveBackupCdnObjectMetadata( + await DataWriter.saveBackupCdnObjectMetadata( listResult.storedMediaObjects.map(object => ({ mediaId: object.mediaId, cdnNumber: object.cdn, @@ -220,9 +221,7 @@ export class BackupsService { ): Promise< { isInBackupTier: true; cdnNumber: number } | { isInBackupTier: false } > { - const storedInfo = await window.Signal.Data.getBackupCdnObjectMetadata( - mediaId - ); + const storedInfo = await DataReader.getBackupCdnObjectMetadata(mediaId); if (!storedInfo) { return { isInBackupTier: false }; } diff --git a/ts/services/backups/util/mediaId.ts b/ts/services/backups/util/mediaId.ts index 3e2085477..1e628c4b0 100644 --- a/ts/services/backups/util/mediaId.ts +++ b/ts/services/backups/util/mediaId.ts @@ -1,6 +1,7 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { DataReader } from '../../../sql/Client'; import * as Bytes from '../../../Bytes'; import { getBackupKey } from '../crypto'; import type { AttachmentType } from '../../../types/Attachment'; @@ -63,9 +64,7 @@ export type GetBackupCdnInfoType = ( export const getBackupCdnInfo: GetBackupCdnInfoType = async ( mediaId: string ) => { - const savedInfo = await window.Signal.Data.getBackupCdnObjectMetadata( - mediaId - ); + const savedInfo = await DataReader.getBackupCdnObjectMetadata(mediaId); if (!savedInfo) { return { isInBackupTier: false }; } diff --git a/ts/services/callHistoryLoader.ts b/ts/services/callHistoryLoader.ts index e4d27c0d0..599c8f725 100644 --- a/ts/services/callHistoryLoader.ts +++ b/ts/services/callHistoryLoader.ts @@ -1,7 +1,7 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import type { CallHistoryDetails } from '../types/CallDisposition'; import { strictAssert } from '../util/assert'; @@ -9,9 +9,9 @@ let callsHistoryData: ReadonlyArray; let callsHistoryUnreadCount: number; export async function loadCallsHistory(): Promise { - await dataInterface.cleanupCallHistoryMessages(); - callsHistoryData = await dataInterface.getAllCallHistory(); - callsHistoryUnreadCount = await dataInterface.getCallHistoryUnreadCount(); + await DataWriter.cleanupCallHistoryMessages(); + callsHistoryData = await DataReader.getAllCallHistory(); + callsHistoryUnreadCount = await DataReader.getCallHistoryUnreadCount(); } export function getCallsHistoryForRedux(): ReadonlyArray { diff --git a/ts/services/callLinksLoader.ts b/ts/services/callLinksLoader.ts index bf205ce13..34268a54e 100644 --- a/ts/services/callLinksLoader.ts +++ b/ts/services/callLinksLoader.ts @@ -1,14 +1,14 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import dataInterface from '../sql/Client'; +import { DataReader } from '../sql/Client'; import type { CallLinkType } from '../types/CallLink'; import { strictAssert } from '../util/assert'; let callLinksData: ReadonlyArray; export async function loadCallLinks(): Promise { - callLinksData = await dataInterface.getAllCallLinks(); + callLinksData = await DataReader.getAllCallLinks(); } export function getCallLinksForRedux(): ReadonlyArray { diff --git a/ts/services/calling.ts b/ts/services/calling.ts index a3c0ed85e..c97eb2ec6 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -105,7 +105,7 @@ import { callingMessageToProto } from '../util/callingMessageToProto'; import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions'; import OS from '../util/os/osMain'; import { SignalService as Proto } from '../protobuf'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import { notificationService, NotificationSetting, @@ -159,11 +159,11 @@ import type { import { CallLinkRestrictions } from '../types/CallLink'; import { getConversationIdForLogging } from '../util/idForLogging'; +const { wasGroupCallRingPreviouslyCanceled } = DataReader; const { processGroupCallRingCancellation, cleanExpiredGroupCallRingCancellations, - wasGroupCallRingPreviouslyCanceled, -} = dataInterface; +} = DataWriter; const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map< HttpMethod, @@ -933,7 +933,7 @@ export class CallingClass { } public async cleanupStaleRingingCalls(): Promise { - const calls = await dataInterface.getRecentStaleRingsAndMarkOlderMissed(); + const calls = await DataWriter.getRecentStaleRingsAndMarkOlderMissed(); const results = await Promise.all( calls.map(async call => { @@ -950,7 +950,7 @@ export class CallingClass { return result.callId; }); - await dataInterface.markCallHistoryMissed(staleCallIds); + await DataWriter.markCallHistoryMissed(staleCallIds); } public async peekGroupCall(conversationId: string): Promise { @@ -3281,11 +3281,10 @@ export class CallingClass { return; } - const prevMessageId = - await window.Signal.Data.getCallHistoryMessageByCallId({ - conversationId: conversation.id, - callId: groupCallMeta.callId, - }); + const prevMessageId = await DataReader.getCallHistoryMessageByCallId({ + conversationId: conversation.id, + callId: groupCallMeta.callId, + }); const isNewCall = prevMessageId == null; diff --git a/ts/services/contactSync.ts b/ts/services/contactSync.ts index 87d7a35e8..68d992ef3 100644 --- a/ts/services/contactSync.ts +++ b/ts/services/contactSync.ts @@ -3,6 +3,7 @@ import PQueue from 'p-queue'; +import { DataWriter } from '../sql/Client'; import type { ContactSyncEvent } from '../textsecure/messageReceiverEvents'; import type { ContactDetailsWithAvatar } from '../textsecure/ContactsParser'; import { normalizeAci } from '../util/normalizeAci'; @@ -167,7 +168,7 @@ async function doContactSync({ // Save new conversation attributes promises.push( - window.Signal.Data.updateConversations( + DataWriter.updateConversations( [...updatedConversations, ...notUpdated].map(convo => convo.attributes) ) ); diff --git a/ts/services/distributionListLoader.ts b/ts/services/distributionListLoader.ts index 566a81efb..2c270990a 100644 --- a/ts/services/distributionListLoader.ts +++ b/ts/services/distributionListLoader.ts @@ -1,7 +1,7 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import dataInterface from '../sql/Client'; +import { DataReader } from '../sql/Client'; import type { StoryDistributionWithMembersType } from '../sql/Interface'; import type { StoryDistributionListDataType } from '../state/ducks/storyDistributionLists'; import { strictAssert } from '../util/assert'; @@ -9,7 +9,7 @@ import { strictAssert } from '../util/assert'; let distributionLists: Array | undefined; export async function loadDistributionLists(): Promise { - distributionLists = await dataInterface.getAllStoryDistributionsWithMembers(); + distributionLists = await DataReader.getAllStoryDistributionsWithMembers(); } export function getDistributionListsForRedux(): Array { diff --git a/ts/services/expiringMessagesDeletion.ts b/ts/services/expiringMessagesDeletion.ts index 237f11e86..f868705cb 100644 --- a/ts/services/expiringMessagesDeletion.ts +++ b/ts/services/expiringMessagesDeletion.ts @@ -4,6 +4,7 @@ import { batch } from 'react-redux'; import { debounce } from 'lodash'; +import { DataReader, DataWriter } from '../sql/Client'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { sleep } from '../util/sleep'; import { SECOND } from '../util/durations'; @@ -27,7 +28,7 @@ class ExpiringMessagesDeletionService { window.SignalContext.log.info( 'destroyExpiredMessages: Loading messages...' ); - const messages = await window.Signal.Data.getExpiredMessages(); + const messages = await DataReader.getExpiredMessages(); window.SignalContext.log.info( `destroyExpiredMessages: found ${messages.length} messages to expire` ); @@ -45,7 +46,7 @@ class ExpiringMessagesDeletionService { inMemoryMessages.push(message); }); - await window.Signal.Data.removeMessages(messageIds, { + await DataWriter.removeMessages(messageIds, { singleProtoJobQueue: this.singleProtoJobQueue, }); @@ -82,7 +83,7 @@ class ExpiringMessagesDeletionService { 'checkExpiringMessages: checking for expiring messages' ); - const soonestExpiry = await window.Signal.Data.getSoonestMessageExpiry(); + const soonestExpiry = await DataReader.getSoonestMessageExpiry(); if (!soonestExpiry) { window.SignalContext.log.info( 'checkExpiringMessages: found no messages to expire' diff --git a/ts/services/profiles.ts b/ts/services/profiles.ts index 4512658cd..3326b8c7b 100644 --- a/ts/services/profiles.ts +++ b/ts/services/profiles.ts @@ -11,6 +11,7 @@ import type { GetProfileUnauthOptionsType, } from '../textsecure/WebAPI'; import type { ServiceIdString } from '../types/ServiceId'; +import { DataWriter } from '../sql/Client'; import * as log from '../logging/log'; import * as Errors from '../types/errors'; import * as Bytes from '../Bytes'; @@ -594,7 +595,7 @@ async function doGetProfile(c: ConversationModel): Promise { }); } - window.Signal.Data.updateConversation(c.attributes); + await DataWriter.updateConversation(c.attributes); } export type UpdateIdentityKeyOptionsType = Readonly<{ diff --git a/ts/services/storage.ts b/ts/services/storage.ts index dc54eef04..0c4d9cd01 100644 --- a/ts/services/storage.ts +++ b/ts/services/storage.ts @@ -5,7 +5,7 @@ import { debounce, isNumber, chunk } from 'lodash'; import pMap from 'p-map'; import Long from 'long'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import * as Bytes from '../Bytes'; import { getRandomBytes, @@ -71,13 +71,14 @@ import { redactExtendedStorageID, redactStorageID } from '../util/privacy'; type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier; +const { getItemById } = DataReader; + const { eraseStorageServiceState, flushUpdateConversationBatcher, - getItemById, updateConversation, updateConversations, -} = dataInterface; +} = DataWriter; const uploadBucket: Array = []; @@ -319,7 +320,7 @@ async function generateManifest( storageVersion: version, storageID, }); - updateConversation(conversation.attributes); + drop(updateConversation(conversation.attributes)); }); } } @@ -360,7 +361,7 @@ async function generateManifest( ); deleteKeys.add(droppedID); - drop(dataInterface.deleteStoryDistribution(storyDistributionList.id)); + drop(DataWriter.deleteStoryDistribution(storyDistributionList.id)); continue; } @@ -374,7 +375,7 @@ async function generateManifest( if (isNewItem) { postUploadUpdateFunctions.push(() => { - void dataInterface.modifyStoryDistribution({ + void DataWriter.modifyStoryDistribution({ ...storyDistributionList, storageID, storageVersion: version, @@ -407,7 +408,7 @@ async function generateManifest( if (isNewItem) { postUploadUpdateFunctions.push(() => { - void dataInterface.addUninstalledStickerPack({ + void DataWriter.addUninstalledStickerPack({ ...stickerPack, storageID, storageVersion: version, @@ -449,7 +450,7 @@ async function generateManifest( if (isNewItem) { postUploadUpdateFunctions.push(() => { - void dataInterface.createOrUpdateStickerPack({ + void DataWriter.createOrUpdateStickerPack({ ...stickerPack, storageID, storageVersion: version, @@ -1088,9 +1089,9 @@ async function getNonConversationRecords(): Promise = ( storyDistributionListRecord.recipientServiceIds || [] @@ -1714,7 +1714,7 @@ export async function mergeStoryDistributionListRecord( }; if (!localStoryDistributionList) { - await dataInterface.createNewStoryDistribution(storyDistribution); + await DataWriter.createNewStoryDistribution(storyDistribution); const shouldSave = false; window.reduxActions.storyDistributionLists.createDistributionList( @@ -1766,7 +1766,7 @@ export async function mergeStoryDistributionListRecord( ); details.push('updated'); - await dataInterface.modifyStoryDistributionWithMembers(storyDistribution, { + await DataWriter.modifyStoryDistributionWithMembers(storyDistribution, { toAdd, toRemove, }); @@ -1804,7 +1804,7 @@ export async function mergeStickerPackRecord( const details: Array = []; const id = Bytes.toHex(stickerPackRecord.packId); - const localStickerPack = await dataInterface.getStickerPackInfo(id); + const localStickerPack = await DataReader.getStickerPackInfo(id); if (stickerPackRecord.$unknownFields) { details.push('adding unknown fields'); @@ -1897,7 +1897,7 @@ export async function mergeStickerPackRecord( } } - await dataInterface.updateStickerPackInfo(stickerPack); + await DataWriter.updateStickerPackInfo(stickerPack); return { details: [...details, ...conflictDetails], diff --git a/ts/services/storyLoader.ts b/ts/services/storyLoader.ts index 17c9bd238..e95b4d8c2 100644 --- a/ts/services/storyLoader.ts +++ b/ts/services/storyLoader.ts @@ -6,7 +6,7 @@ import type { MessageAttributesType } from '../model-types.d'; import type { StoryDataType } from '../state/ducks/stories'; import * as durations from '../util/durations'; import * as log from '../logging/log'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import type { GetAllStoriesResultType } from '../sql/Interface'; import { getAttachmentsForMessage, @@ -22,7 +22,7 @@ import { SIGNAL_ACI } from '../types/SignalConversation'; let storyData: GetAllStoriesResultType | undefined; export async function loadStories(): Promise { - storyData = await dataInterface.getAllStories({}); + storyData = await DataReader.getAllStories({}); await repairUnexpiredStories(); } @@ -171,7 +171,7 @@ async function repairUnexpiredStories(): Promise { await Promise.all( storiesWithExpiry.map(messageAttributes => { - return window.Signal.Data.saveMessage(messageAttributes, { + return DataWriter.saveMessage(messageAttributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); }) diff --git a/ts/services/tapToViewMessagesDeletionService.ts b/ts/services/tapToViewMessagesDeletionService.ts index 11fd773df..6f9ef294b 100644 --- a/ts/services/tapToViewMessagesDeletionService.ts +++ b/ts/services/tapToViewMessagesDeletionService.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { debounce } from 'lodash'; +import { DataReader } from '../sql/Client'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { DAY } from '../util/durations'; import * as Errors from '../types/errors'; @@ -11,8 +12,7 @@ async function eraseTapToViewMessages() { window.SignalContext.log.info( 'eraseTapToViewMessages: Loading messages...' ); - const messages = - await window.Signal.Data.getTapToViewMessagesNeedingErase(); + const messages = await DataReader.getTapToViewMessagesNeedingErase(); await Promise.all( messages.map(async fromDB => { const message = window.MessageCache.__DEPRECATED$register( @@ -54,7 +54,7 @@ class TapToViewMessagesDeletionService { private async checkTapToViewMessages() { const receivedAt = - await window.Signal.Data.getNextTapToViewMessageTimestampToAgeOut(); + await DataReader.getNextTapToViewMessageTimestampToAgeOut(); if (!receivedAt) { return; } diff --git a/ts/services/writeProfile.ts b/ts/services/writeProfile.ts index 521f10ce9..94ee0e858 100644 --- a/ts/services/writeProfile.ts +++ b/ts/services/writeProfile.ts @@ -1,7 +1,7 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import type { ConversationType } from '../state/ducks/conversations'; import * as Errors from '../types/errors'; import * as log from '../logging/log'; @@ -134,7 +134,7 @@ export async function writeProfile( ...maybeProfileAvatarUpdate, }); - dataInterface.updateConversation(model.attributes); + await DataWriter.updateConversation(model.attributes); model.captureChange('writeProfile'); try { diff --git a/ts/shims/deleteAllData.ts b/ts/shims/deleteAllData.ts index d4e82d935..705eae013 100644 --- a/ts/shims/deleteAllData.ts +++ b/ts/shims/deleteAllData.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import { deleteAllLogs } from '../util/deleteAllLogs'; import * as Errors from '../types/errors'; @@ -11,19 +12,19 @@ export async function deleteAllData(): Promise { log.info('deleteAllData: deleted all logs'); - await window.Signal.Data.removeAll(); + await DataWriter.removeAll(); log.info('deleteAllData: emptied database'); - await window.Signal.Data.close(); + await DataWriter.close(); log.info('deleteAllData: closed database'); - await window.Signal.Data.removeDB(); + await DataWriter.removeDB(); log.info('deleteAllData: removed database'); - await window.Signal.Data.removeOtherData(); + await DataWriter.removeOtherData(); log.info('deleteAllData: removed all other data'); } catch (error) { diff --git a/ts/signal.ts b/ts/signal.ts index 63c1de6c1..9e4f9a579 100644 --- a/ts/signal.ts +++ b/ts/signal.ts @@ -6,7 +6,6 @@ import * as Crypto from './Crypto'; import * as Curve from './Curve'; import { start as conversationControllerStart } from './ConversationController'; -import Data from './sql/Client'; import * as Groups from './groups'; import OS from './util/os/osMain'; import * as RemoteConfig from './RemoteConfig'; @@ -455,7 +454,6 @@ export const setup = (options: { Curve, // Note: used in test/index.html, and not type-checked! conversationControllerStart, - Data, Groups, Migrations, OS, diff --git a/ts/sql/Client.ts b/ts/sql/Client.ts index 86fdba87f..39c947af0 100644 --- a/ts/sql/Client.ts +++ b/ts/sql/Client.ts @@ -3,7 +3,7 @@ import { ipcRenderer as ipc } from 'electron'; -import { has, get, groupBy, isTypedArray, last, map, omit } from 'lodash'; +import { groupBy, isTypedArray, last, map, omit } from 'lodash'; import { deleteExternalFiles } from '../types/Conversation'; import { update as updateExpiringMessagesService } from '../services/expiringMessagesDeletion'; @@ -23,13 +23,16 @@ import * as Errors from '../types/errors'; import type { StoredJob } from '../jobs/types'; import { formatJobForInsert } from '../jobs/formatJobForInsert'; import { cleanupMessages } from '../util/cleanup'; -import { ipcInvoke, doShutdown } from './channels'; +import { AccessType, ipcInvoke, doShutdown } from './channels'; import type { + ClientInterfaceWrap, AdjacentMessagesByConversationOptionsType, AllItemsType, - ClientInterface, - ClientExclusiveInterface, + ServerReadableDirectInterface, + ServerWritableDirectInterface, + ClientReadableInterface, + ClientWritableInterface, ClientSearchResultMessageType, ConversationType, GetConversationRangeCenteredOnMessageResultType, @@ -45,13 +48,14 @@ import type { PreKeyIdType, PreKeyType, StoredPreKeyType, - ServerInterface, ServerSearchResultMessageType, SignedPreKeyIdType, SignedPreKeyType, StoredSignedPreKeyType, KyberPreKeyType, StoredKyberPreKeyType, + ClientOnlyReadableInterface, + ClientOnlyWritableInterface, } from './Interface'; import { getMessageIdForLogging } from '../util/idForLogging'; import type { MessageAttributesType } from '../model-types'; @@ -67,44 +71,54 @@ const ERASE_TEMP_KEY = 'erase-temp'; const ERASE_DRAFTS_KEY = 'erase-drafts'; const CLEANUP_ORPHANED_ATTACHMENTS_KEY = 'cleanup-orphaned-attachments'; const ENSURE_FILE_PERMISSIONS = 'ensure-file-permissions'; +const PAUSE_WRITE_ACCESS = 'pause-sql-writes'; +const RESUME_WRITE_ACCESS = 'resume-sql-writes'; -const exclusiveInterface: ClientExclusiveInterface = { - createOrUpdateIdentityKey, +const clientOnlyReadable: ClientOnlyReadableInterface = { getIdentityKeyById, - bulkAddIdentityKeys, getAllIdentityKeys, - createOrUpdateKyberPreKey, getKyberPreKeyById, - bulkAddKyberPreKeys, getAllKyberPreKeys, - createOrUpdatePreKey, getPreKeyById, - bulkAddPreKeys, getAllPreKeys, - createOrUpdateSignedPreKey, getSignedPreKeyById, - bulkAddSignedPreKeys, getAllSignedPreKeys, - createOrUpdateItem, getItemById, getAllItems, + searchMessages, + + getRecentStoryReplies, + getOlderMessagesByConversation, + getNewerMessagesByConversation, + getConversationRangeCenteredOnMessage, +}; + +const clientOnlyWritable: ClientOnlyWritableInterface = { + createOrUpdateIdentityKey, + bulkAddIdentityKeys, + + createOrUpdateKyberPreKey, + bulkAddKyberPreKeys, + + createOrUpdatePreKey, + bulkAddPreKeys, + + createOrUpdateSignedPreKey, + bulkAddSignedPreKeys, + + createOrUpdateItem, + updateConversation, removeConversation, - searchMessages, removeMessage, removeMessages, - getRecentStoryReplies, - getOlderMessagesByConversation, - getConversationRangeCenteredOnMessage, - getNewerMessagesByConversation, - // Client-side only flushUpdateConversationBatcher, @@ -117,48 +131,82 @@ const exclusiveInterface: ClientExclusiveInterface = { ensureFilePermissions, }; -type ClientOverridesType = ClientExclusiveInterface & +type ClientOverridesType = ClientOnlyWritableInterface & Pick< - ServerInterface, + ClientInterfaceWrap, | 'saveAttachmentDownloadJob' | 'saveMessage' | 'saveMessages' | 'updateConversations' >; -const channels: ServerInterface = new Proxy({} as ServerInterface, { - get(_target, name) { - return async (...args: ReadonlyArray) => - ipcInvoke(String(name), args); - }, -}); - -const clientExclusiveOverrides: ClientOverridesType = { - ...exclusiveInterface, +const clientOnlyWritableOverrides: ClientOverridesType = { + ...clientOnlyWritable, saveAttachmentDownloadJob, saveMessage, saveMessages, updateConversations, }; -const dataInterface: ClientInterface = new Proxy( +type ReadableChannelInterface = + ClientInterfaceWrap; + +const readableChannel: ReadableChannelInterface = new Proxy( + {} as ReadableChannelInterface, { - ...clientExclusiveOverrides, - } as ClientInterface, + get(_target, name) { + return async (...args: ReadonlyArray) => + ipcInvoke(AccessType.Read, String(name), args); + }, + } +); + +type WritableChannelInterface = + ClientInterfaceWrap; + +const writableChannel: WritableChannelInterface = new Proxy( + {} as WritableChannelInterface, + { + get(_target, name) { + return async (...args: ReadonlyArray) => + ipcInvoke(AccessType.Write, String(name), args); + }, + } +); + +export const DataReader: ClientReadableInterface = new Proxy( + { + ...clientOnlyReadable, + } as ClientReadableInterface, { get(target, name) { return async (...args: ReadonlyArray) => { - if (has(target, name)) { - return get(target, name)(...args); + if (Reflect.has(target, name)) { + return Reflect.get(target, name)(...args); } - return get(channels, name)(...args); + return Reflect.get(readableChannel, name)(...args); }; }, } ); -export default dataInterface; +export const DataWriter: ClientWritableInterface = new Proxy( + { + ...clientOnlyWritableOverrides, + } as ClientWritableInterface, + { + get(target, name) { + return async (...args: ReadonlyArray) => { + if (Reflect.has(target, name)) { + return Reflect.get(target, name)(...args); + } + + return Reflect.get(writableChannel, name)(...args); + }; + }, + } +); function _cleanData( data: unknown @@ -246,9 +294,6 @@ async function shutdown(): Promise { // Stop accepting new SQL jobs, flush outstanding queue await doShutdown(); - - // Close database - await channels.close(); } // Identity Keys @@ -256,12 +301,12 @@ async function shutdown(): Promise { const IDENTITY_KEY_SPEC = ['publicKey']; async function createOrUpdateIdentityKey(data: IdentityKeyType): Promise { const updated: StoredIdentityKeyType = specFromBytes(IDENTITY_KEY_SPEC, data); - await channels.createOrUpdateIdentityKey(updated); + await writableChannel.createOrUpdateIdentityKey(updated); } async function getIdentityKeyById( id: IdentityKeyIdType ): Promise { - const data = await channels.getIdentityKeyById(id); + const data = await readableChannel.getIdentityKeyById(id); return specToBytes(IDENTITY_KEY_SPEC, data); } @@ -271,10 +316,10 @@ async function bulkAddIdentityKeys( const updated: Array = map(array, data => specFromBytes(IDENTITY_KEY_SPEC, data) ); - await channels.bulkAddIdentityKeys(updated); + await writableChannel.bulkAddIdentityKeys(updated); } async function getAllIdentityKeys(): Promise> { - const keys = await channels.getAllIdentityKeys(); + const keys = await readableChannel.getAllIdentityKeys(); return keys.map(key => specToBytes(IDENTITY_KEY_SPEC, key)); } @@ -287,12 +332,12 @@ async function createOrUpdateKyberPreKey(data: KyberPreKeyType): Promise { KYBER_PRE_KEY_SPEC, data ); - await channels.createOrUpdateKyberPreKey(updated); + await writableChannel.createOrUpdateKyberPreKey(updated); } async function getKyberPreKeyById( id: PreKeyIdType ): Promise { - const data = await channels.getPreKeyById(id); + const data = await readableChannel.getPreKeyById(id); return specToBytes(KYBER_PRE_KEY_SPEC, data); } @@ -302,10 +347,10 @@ async function bulkAddKyberPreKeys( const updated: Array = map(array, data => specFromBytes(KYBER_PRE_KEY_SPEC, data) ); - await channels.bulkAddKyberPreKeys(updated); + await writableChannel.bulkAddKyberPreKeys(updated); } async function getAllKyberPreKeys(): Promise> { - const keys = await channels.getAllKyberPreKeys(); + const keys = await readableChannel.getAllKyberPreKeys(); return keys.map(key => specToBytes(KYBER_PRE_KEY_SPEC, key)); } @@ -314,12 +359,12 @@ async function getAllKyberPreKeys(): Promise> { async function createOrUpdatePreKey(data: PreKeyType): Promise { const updated: StoredPreKeyType = specFromBytes(PRE_KEY_SPEC, data); - await channels.createOrUpdatePreKey(updated); + await writableChannel.createOrUpdatePreKey(updated); } async function getPreKeyById( id: PreKeyIdType ): Promise { - const data = await channels.getPreKeyById(id); + const data = await readableChannel.getPreKeyById(id); return specToBytes(PRE_KEY_SPEC, data); } @@ -327,10 +372,10 @@ async function bulkAddPreKeys(array: Array): Promise { const updated: Array = map(array, data => specFromBytes(PRE_KEY_SPEC, data) ); - await channels.bulkAddPreKeys(updated); + await writableChannel.bulkAddPreKeys(updated); } async function getAllPreKeys(): Promise> { - const keys = await channels.getAllPreKeys(); + const keys = await readableChannel.getAllPreKeys(); return keys.map(key => specToBytes(PRE_KEY_SPEC, key)); } @@ -342,17 +387,17 @@ async function createOrUpdateSignedPreKey( data: SignedPreKeyType ): Promise { const updated: StoredSignedPreKeyType = specFromBytes(PRE_KEY_SPEC, data); - await channels.createOrUpdateSignedPreKey(updated); + await writableChannel.createOrUpdateSignedPreKey(updated); } async function getSignedPreKeyById( id: SignedPreKeyIdType ): Promise { - const data = await channels.getSignedPreKeyById(id); + const data = await readableChannel.getSignedPreKeyById(id); return specToBytes(PRE_KEY_SPEC, data); } async function getAllSignedPreKeys(): Promise> { - const keys = await channels.getAllSignedPreKeys(); + const keys = await readableChannel.getAllSignedPreKeys(); return keys.map(key => specToBytes(PRE_KEY_SPEC, key)); } @@ -362,7 +407,7 @@ async function bulkAddSignedPreKeys( const updated: Array = map(array, data => specFromBytes(PRE_KEY_SPEC, data) ); - await channels.bulkAddSignedPreKeys(updated); + await writableChannel.bulkAddSignedPreKeys(updated); } // Items @@ -398,13 +443,13 @@ async function createOrUpdateItem( ? specFromBytes(spec, data) : (data as unknown as StoredItemType); - await channels.createOrUpdateItem(updated); + await writableChannel.createOrUpdateItem(updated); } async function getItemById( id: K ): Promise | undefined> { const spec = ITEM_SPECS[id]; - const data = await channels.getItemById(id); + const data = await readableChannel.getItemById(id); try { return spec ? specToBytes(spec, data) : (data as unknown as ItemType); @@ -414,7 +459,7 @@ async function getItemById( } } async function getAllItems(): Promise { - const items = await channels.getAllItems(); + const items = await readableChannel.getAllItems(); const result = Object.create(null); @@ -458,7 +503,7 @@ const updateConversationBatcher = createBatcher({ }, }); -function updateConversation(data: ConversationType): void { +async function updateConversation(data: ConversationType): Promise { updateConversationBatcher.add(data); } async function flushUpdateConversationBatcher(): Promise { @@ -473,16 +518,16 @@ async function updateConversations( !pathsChanged.length, `Paths were cleaned: ${JSON.stringify(pathsChanged)}` ); - await channels.updateConversations(cleaned); + await writableChannel.updateConversations(cleaned); } async function removeConversation(id: string): Promise { - const existing = await channels.getConversationById(id); + const existing = await readableChannel.getConversationById(id); // Note: It's important to have a fully database-hydrated model to delete here because // it needs to delete all associated on-disk files along with the database delete. if (existing) { - await channels.removeConversation(id); + await writableChannel.removeConversation(id); await deleteExternalFiles(existing, { deleteAttachmentData: window.Signal.Migrations.deleteAttachmentData, }); @@ -528,7 +573,7 @@ async function searchMessages({ contactServiceIdsMatchingQuery?: Array; conversationId?: string; }): Promise> { - const messages = await channels.searchMessages({ + const messages = await readableChannel.searchMessages({ query, conversationId, options, @@ -548,7 +593,7 @@ async function saveMessage( ourAci: AciString; } ): Promise { - const id = await channels.saveMessage(_cleanMessageData(data), { + const id = await writableChannel.saveMessage(_cleanMessageData(data), { ...options, jobToInsert: options.jobToInsert && formatJobForInsert(options.jobToInsert), }); @@ -565,7 +610,7 @@ async function saveMessages( arrayOfMessages: ReadonlyArray, options: { forceSave?: boolean; ourAci: AciString } ): Promise> { - const result = await channels.saveMessages( + const result = await writableChannel.saveMessages( arrayOfMessages.map(message => _cleanMessageData(message)), options ); @@ -583,15 +628,15 @@ async function removeMessage( fromSync?: boolean; } ): Promise { - const message = await channels.getMessageById(id); + const message = await readableChannel.getMessageById(id); // Note: It's important to have a fully database-hydrated model to delete here because // it needs to delete all associated on-disk files along with the database delete. if (message) { - await channels.removeMessage(id); + await writableChannel.removeMessage(id); await cleanupMessages([message], { ...options, - markCallHistoryDeleted: dataInterface.markCallHistoryDeleted, + markCallHistoryDeleted: DataWriter.markCallHistoryDeleted, }); } } @@ -607,12 +652,12 @@ export async function deleteAndCleanup( const ids = messages.map(message => message.id); log.info(`deleteAndCleanup/${logId}: Deleting ${ids.length} messages...`); - await channels.removeMessages(ids); + await writableChannel.removeMessages(ids); log.info(`deleteAndCleanup/${logId}: Cleanup for ${ids.length} messages...`); await cleanupMessages(messages, { ...options, - markCallHistoryDeleted: dataInterface.markCallHistoryDeleted, + markCallHistoryDeleted: DataWriter.markCallHistoryDeleted, }); log.info(`deleteAndCleanup/${logId}: Complete`); @@ -625,12 +670,12 @@ async function removeMessages( singleProtoJobQueue: SingleProtoJobQueue; } ): Promise { - const messages = await channels.getMessagesById(messageIds); + const messages = await readableChannel.getMessagesById(messageIds); await cleanupMessages(messages, { ...options, - markCallHistoryDeleted: dataInterface.markCallHistoryDeleted, + markCallHistoryDeleted: DataWriter.markCallHistoryDeleted, }); - await channels.removeMessages(messageIds); + await writableChannel.removeMessages(messageIds); } function handleMessageJSON( @@ -642,7 +687,9 @@ function handleMessageJSON( async function getNewerMessagesByConversation( options: AdjacentMessagesByConversationOptionsType ): Promise> { - const messages = await channels.getNewerMessagesByConversation(options); + const messages = await readableChannel.getNewerMessagesByConversation( + options + ); return handleMessageJSON(messages); } @@ -651,7 +698,10 @@ async function getRecentStoryReplies( storyId: string, options?: GetRecentStoryRepliesOptionsType ): Promise> { - const messages = await channels.getRecentStoryReplies(storyId, options); + const messages = await readableChannel.getRecentStoryReplies( + storyId, + options + ); return handleMessageJSON(messages); } @@ -659,7 +709,9 @@ async function getRecentStoryReplies( async function getOlderMessagesByConversation( options: AdjacentMessagesByConversationOptionsType ): Promise> { - const messages = await channels.getOlderMessagesByConversation(options); + const messages = await readableChannel.getOlderMessagesByConversation( + options + ); return handleMessageJSON(messages); } @@ -667,7 +719,9 @@ async function getOlderMessagesByConversation( async function getConversationRangeCenteredOnMessage( options: AdjacentMessagesByConversationOptionsType ): Promise> { - const result = await channels.getConversationRangeCenteredOnMessage(options); + const result = await readableChannel.getConversationRangeCenteredOnMessage( + options + ); return { ...result, @@ -721,7 +775,7 @@ async function removeMessagesInConversation( async function saveAttachmentDownloadJob( job: AttachmentDownloadJobType ): Promise { - await channels.saveAttachmentDownloadJob(_cleanData(job)); + await writableChannel.saveAttachmentDownloadJob(_cleanData(job)); } // Other @@ -758,3 +812,11 @@ async function invokeWithTimeout(name: string): Promise { `callChannel call to ${name}` )(); } + +export function pauseWriteAccess(): Promise { + return invokeWithTimeout(PAUSE_WRITE_ACCESS); +} + +export function resumeWriteAccess(): Promise { + return invokeWithTimeout(RESUME_WRITE_ACCESS); +} diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index e8daf2c6d..587507e19 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -1,6 +1,7 @@ // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import type { Database } from '@signalapp/better-sqlite3'; import type { ConversationAttributesType, MessageAttributesType, @@ -15,7 +16,6 @@ import type { QualifiedAddressStringType } from '../types/QualifiedAddress'; import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import type { AciString, PniString, ServiceIdString } from '../types/ServiceId'; import type { BadgeType } from '../badges/types'; -import type { LoggerType } from '../types/Logging'; import type { ReadStatus } from '../messages/MessageReadStatus'; import type { RawBodyRange } from '../types/BodyRange'; import type { @@ -37,6 +37,9 @@ import type { SyncTaskType } from '../util/syncTasks'; import type { AttachmentBackupJobType } from '../types/AttachmentBackup'; import type { SingleProtoJobQueue } from '../jobs/singleProtoJobQueue'; +export type ReadableDB = Database & { __readable_db: never }; +export type WritableDB = ReadableDB & { __writable_db: never }; + export type AdjacentMessagesByConversationOptionsType = Readonly<{ conversationId: string; messageId?: string; @@ -421,11 +424,6 @@ export type GetAllStoriesResultType = ReadonlyArray< } >; -export type FTSOptimizationStateType = Readonly<{ - steps: number; - done?: boolean; -}>; - export type EditedMessageType = Readonly<{ conversationId: string; messageId: string; @@ -439,593 +437,6 @@ export type BackupCdnMediaObjectType = { sizeOnBackupCdn: number; }; -export type DataInterface = { - close: () => Promise; - pauseWriteAccess(): Promise; - resumeWriteAccess(): Promise; - - removeDB: () => Promise; - removeIndexedDBFiles: () => Promise; - - removeIdentityKeyById: (id: IdentityKeyIdType) => Promise; - removeAllIdentityKeys: () => Promise; - - removeKyberPreKeyById: ( - id: PreKeyIdType | Array - ) => Promise; - removeKyberPreKeysByServiceId: (serviceId: ServiceIdString) => Promise; - removeAllKyberPreKeys: () => Promise; - - removePreKeyById: (id: PreKeyIdType | Array) => Promise; - removePreKeysByServiceId: (serviceId: ServiceIdString) => Promise; - removeAllPreKeys: () => Promise; - - removeSignedPreKeyById: ( - id: SignedPreKeyIdType | Array - ) => Promise; - removeSignedPreKeysByServiceId: (serviceId: ServiceIdString) => Promise; - removeAllSignedPreKeys: () => Promise; - - removeAllItems: () => Promise; - removeItemById: (id: ItemKeyType | Array) => Promise; - - createOrUpdateSenderKey: (key: SenderKeyType) => Promise; - getSenderKeyById: (id: SenderKeyIdType) => Promise; - removeAllSenderKeys: () => Promise; - getAllSenderKeys: () => Promise>; - removeSenderKeyById: (id: SenderKeyIdType) => Promise; - - insertSentProto: ( - proto: SentProtoType, - options: { - recipients: SentRecipientsType; - messageIds: SentMessagesType; - } - ) => Promise; - deleteSentProtosOlderThan: (timestamp: number) => Promise; - deleteSentProtoByMessageId: (messageId: string) => Promise; - insertProtoRecipients: (options: { - id: number; - recipientServiceId: ServiceIdString; - deviceIds: Array; - }) => Promise; - deleteSentProtoRecipient: ( - options: - | DeleteSentProtoRecipientOptionsType - | ReadonlyArray - ) => Promise; - getSentProtoByRecipient: (options: { - now: number; - recipientServiceId: ServiceIdString; - timestamp: number; - }) => Promise; - removeAllSentProtos: () => Promise; - getAllSentProtos: () => Promise>; - // Test-only - _getAllSentProtoRecipients: () => Promise>; - _getAllSentProtoMessageIds: () => Promise>; - - createOrUpdateSession: (data: SessionType) => Promise; - createOrUpdateSessions: (array: Array) => Promise; - commitDecryptResult(options: { - senderKeys: Array; - sessions: Array; - unprocessed: Array; - }): Promise; - bulkAddSessions: (array: Array) => Promise; - removeSessionById: (id: SessionIdType) => Promise; - removeSessionsByConversation: (conversationId: string) => Promise; - removeSessionsByServiceId: (serviceId: ServiceIdString) => Promise; - removeAllSessions: () => Promise; - getAllSessions: () => Promise>; - - getConversationCount: () => Promise; - saveConversation: (data: ConversationType) => Promise; - saveConversations: (array: Array) => Promise; - getConversationById: (id: string) => Promise; - // updateConversation is a normal data method on Server, a sync batch-add on Client - updateConversations: (array: Array) => Promise; - // removeConversation handles either one id or an array on Server, and one id on Client - _removeAllConversations: () => Promise; - updateAllConversationColors: ( - conversationColor?: ConversationColorType, - customColorData?: { - id: string; - value: CustomColorType; - } - ) => Promise; - removeAllProfileKeyCredentials: () => Promise; - - getAllConversations: () => Promise>; - getAllConversationIds: () => Promise>; - getAllGroupsInvolvingServiceId: ( - serviceId: ServiceIdString - ) => Promise>; - - replaceAllEndorsementsForGroup: ( - data: GroupSendEndorsementsData - ) => Promise; - deleteAllEndorsementsForGroup: (groupId: string) => Promise; - getGroupSendCombinedEndorsementExpiration: ( - groupId: string - ) => Promise; - - getMessageCount: (conversationId?: string) => Promise; - getStoryCount: (conversationId: string) => Promise; - saveMessage: ( - data: MessageType, - options: { - jobToInsert?: StoredJob; - forceSave?: boolean; - ourAci: AciString; - } - ) => Promise; - saveMessages: ( - arrayOfMessages: ReadonlyArray, - options: { forceSave?: boolean; ourAci: AciString } - ) => Promise>; - pageMessages: ( - cursor?: PageMessagesCursorType - ) => Promise; - finishPageMessages: (cursor: PageMessagesCursorType) => Promise; - getTotalUnreadForConversation: ( - conversationId: string, - options: { - storyId: string | undefined; - includeStoryReplies: boolean; - } - ) => Promise; - getTotalUnreadMentionsOfMeForConversation: ( - conversationId: string, - options: { - storyId?: string; - includeStoryReplies: boolean; - } - ) => Promise; - getOldestUnreadMentionOfMeForConversation( - conversationId: string, - options: { - storyId?: string; - includeStoryReplies: boolean; - } - ): Promise; - getUnreadByConversationAndMarkRead: (options: { - conversationId: string; - includeStoryReplies: boolean; - newestUnreadAt: number; - now?: number; - readAt?: number; - storyId?: string; - }) => Promise; - getUnreadEditedMessagesAndMarkRead: (options: { - conversationId: string; - newestUnreadAt: number; - }) => Promise; - getUnreadReactionsAndMarkRead: (options: { - conversationId: string; - newestUnreadAt: number; - storyId?: string; - }) => Promise>; - markReactionAsRead: ( - targetAuthorServiceId: ServiceIdString, - targetTimestamp: number - ) => Promise; - removeReactionFromConversation: (reaction: { - emoji: string; - fromId: string; - targetAuthorServiceId: ServiceIdString; - targetTimestamp: number; - }) => Promise; - getReactionByTimestamp: ( - fromId: string, - timestamp: number - ) => Promise; - addReaction: ( - reactionObj: ReactionType, - options: { - readStatus: ReactionReadStatus; - } - ) => Promise; - _getAllReactions: () => Promise>; - _removeAllReactions: () => Promise; - getMessageBySender: (options: { - source?: string; - sourceServiceId?: ServiceIdString; - sourceDevice?: number; - sent_at: number; - }) => Promise; - getMessageById: (id: string) => Promise; - getMessagesById: ( - messageIds: ReadonlyArray - ) => Promise>; - _getAllMessages: () => Promise>; - _getAllEditedMessages: () => Promise< - Array<{ messageId: string; sentAt: number }> - >; - _removeAllMessages: () => Promise; - getAllMessageIds: () => Promise>; - getMessagesBySentAt: (sentAt: number) => Promise>; - getExpiredMessages: () => Promise>; - getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Promise< - Array - >; - getSoonestMessageExpiry: () => Promise; - getNextTapToViewMessageTimestampToAgeOut: () => Promise; - getTapToViewMessagesNeedingErase: () => Promise>; - // getOlderMessagesByConversation is JSON on server, full message on Client - getAllStories: (options: { - conversationId?: string; - sourceServiceId?: ServiceIdString; - }) => Promise; - // getNewerMessagesByConversation is JSON on server, full message on Client - getMessageMetricsForConversation: (options: { - conversationId: string; - storyId?: string; - includeStoryReplies: boolean; - }) => Promise; - // getConversationRangeCenteredOnMessage is JSON on server, full message on client - getConversationMessageStats: (options: { - conversationId: string; - includeStoryReplies: boolean; - }) => Promise; - getLastConversationMessage(options: { - conversationId: string; - }): Promise; - getAllCallHistory: () => Promise>; - clearCallHistory: ( - target: CallLogEventTarget - ) => Promise>; - markCallHistoryDeleted: (callId: string) => Promise; - cleanupCallHistoryMessages: () => Promise; - getCallHistoryUnreadCount(): Promise; - markCallHistoryRead(callId: string): Promise; - markAllCallHistoryRead(target: CallLogEventTarget): Promise; - markAllCallHistoryReadInConversation( - target: CallLogEventTarget - ): Promise; - getCallHistoryMessageByCallId(options: { - conversationId: string; - callId: string; - }): Promise; - getCallHistory( - callId: string, - peerId: ServiceIdString | string - ): Promise; - getCallHistoryGroupsCount(filter: CallHistoryFilter): Promise; - getCallHistoryGroups( - filter: CallHistoryFilter, - pagination: CallHistoryPagination - ): Promise>; - saveCallHistory(callHistory: CallHistoryDetails): Promise; - hasGroupCallHistoryMessage: ( - conversationId: string, - eraId: string - ) => Promise; - markCallHistoryMissed(callIds: ReadonlyArray): Promise; - getRecentStaleRingsAndMarkOlderMissed(): Promise< - ReadonlyArray - >; - callLinkExists(roomId: string): Promise; - getAllCallLinks: () => Promise>; - getCallLinkByRoomId: (roomId: string) => Promise; - insertCallLink(callLink: CallLinkType): Promise; - updateCallLinkAdminKeyByRoomId( - roomId: string, - adminKey: string - ): Promise; - updateCallLinkState( - roomId: string, - callLinkState: CallLinkStateType - ): Promise; - migrateConversationMessages: ( - obsoleteId: string, - currentId: string - ) => Promise; - getMessagesBetween: ( - conversationId: string, - options: GetMessagesBetweenOptions - ) => Promise>; - getNearbyMessageFromDeletedSet: ( - options: GetNearbyMessageFromDeletedSetOptionsType - ) => Promise; - saveEditedMessage: ( - mainMessage: MessageType, - ourAci: AciString, - opts: EditedMessageType - ) => Promise; - saveEditedMessages: ( - mainMessage: MessageType, - ourAci: AciString, - history: ReadonlyArray - ) => Promise; - getMostRecentAddressableMessages: ( - conversationId: string, - limit?: number - ) => Promise>; - getMostRecentAddressableNondisappearingMessages: ( - conversationId: string, - limit?: number - ) => Promise>; - - removeSyncTaskById: (id: string) => Promise; - saveSyncTasks: (tasks: Array) => Promise; - getAllSyncTasks: () => Promise>; - - getUnprocessedCount: () => Promise; - getUnprocessedByIdsAndIncrementAttempts: ( - ids: ReadonlyArray - ) => Promise>; - getAllUnprocessedIds: () => Promise>; - updateUnprocessedWithData: ( - id: string, - data: UnprocessedUpdateType - ) => Promise; - updateUnprocessedsWithData: ( - array: Array<{ id: string; data: UnprocessedUpdateType }> - ) => Promise; - getUnprocessedById: (id: string) => Promise; - removeUnprocessed: (id: string | Array) => Promise; - - /** only for testing */ - removeAllUnprocessed: () => Promise; - - getAttachmentDownloadJob( - job: Pick< - AttachmentDownloadJobType, - 'messageId' | 'attachmentType' | 'digest' - > - ): AttachmentDownloadJobType; - getNextAttachmentDownloadJobs: (options: { - limit: number; - prioritizeMessageIds?: Array; - timestamp?: number; - }) => Promise>; - saveAttachmentDownloadJob: (job: AttachmentDownloadJobType) => Promise; - resetAttachmentDownloadActive: () => Promise; - removeAttachmentDownloadJob: ( - job: AttachmentDownloadJobType - ) => Promise; - - getNextAttachmentBackupJobs: (options: { - limit: number; - timestamp?: number; - }) => Promise>; - saveAttachmentBackupJob: (job: AttachmentBackupJobType) => Promise; - markAllAttachmentBackupJobsInactive: () => Promise; - removeAttachmentBackupJob: (job: AttachmentBackupJobType) => Promise; - clearAllAttachmentBackupJobs: () => Promise; - - clearAllBackupCdnObjectMetadata: () => Promise; - saveBackupCdnObjectMetadata: ( - mediaObjects: Array - ) => Promise; - getBackupCdnObjectMetadata: ( - mediaId: string - ) => Promise; - - createOrUpdateStickerPack: (pack: StickerPackType) => Promise; - updateStickerPackStatus: ( - id: string, - status: StickerPackStatusType, - options?: { timestamp: number } - ) => Promise; - updateStickerPackInfo: (info: StickerPackInfoType) => Promise; - createOrUpdateSticker: (sticker: StickerType) => Promise; - createOrUpdateStickers: ( - sticker: ReadonlyArray - ) => Promise; - updateStickerLastUsed: ( - packId: string, - stickerId: number, - lastUsed: number - ) => Promise; - addStickerPackReference: (messageId: string, packId: string) => Promise; - deleteStickerPackReference: ( - messageId: string, - packId: string - ) => Promise | undefined>; - getStickerCount: () => Promise; - deleteStickerPack: (packId: string) => Promise>; - getAllStickerPacks: () => Promise>; - addUninstalledStickerPack: ( - pack: UninstalledStickerPackType - ) => Promise; - removeUninstalledStickerPack: (packId: string) => Promise; - getInstalledStickerPacks: () => Promise>; - getUninstalledStickerPacks: () => Promise>; - installStickerPack: (packId: string, timestamp: number) => Promise; - uninstallStickerPack: (packId: string, timestamp: number) => Promise; - getStickerPackInfo: ( - packId: string - ) => Promise; - getAllStickers: () => Promise>; - getRecentStickers: (options?: { - limit?: number; - }) => Promise>; - clearAllErrorStickerPackAttempts: () => Promise; - - updateEmojiUsage: (shortName: string, timeUsed?: number) => Promise; - getRecentEmojis: (limit?: number) => Promise>; - - getAllBadges(): Promise>; - updateOrCreateBadges(badges: ReadonlyArray): Promise; - badgeImageFileDownloaded(url: string, localPath: string): Promise; - - _getAllStoryDistributions(): Promise>; - _getAllStoryDistributionMembers(): Promise< - Array - >; - _deleteAllStoryDistributions(): Promise; - createNewStoryDistribution( - distribution: StoryDistributionWithMembersType - ): Promise; - getAllStoryDistributionsWithMembers(): Promise< - Array - >; - getStoryDistributionWithMembers( - id: string - ): Promise; - modifyStoryDistribution(distribution: StoryDistributionType): Promise; - modifyStoryDistributionMembers( - listId: string, - options: { - toAdd: Array; - toRemove: Array; - } - ): Promise; - modifyStoryDistributionWithMembers( - distribution: StoryDistributionType, - options: { - toAdd: Array; - toRemove: Array; - } - ): Promise; - deleteStoryDistribution(id: StoryDistributionIdString): Promise; - - _getAllStoryReads(): Promise>; - _deleteAllStoryReads(): Promise; - addNewStoryRead(read: StoryReadType): Promise; - getLastStoryReadsForAuthor(options: { - authorId: ServiceIdString; - conversationId?: string; - limit?: number; - }): Promise>; - countStoryReadsByConversation(conversationId: string): Promise; - - removeAll: () => Promise; - removeAllConfiguration: () => Promise; - eraseStorageServiceState: () => Promise; - - getMessagesNeedingUpgrade: ( - limit: number, - options: { maxVersion: number } - ) => Promise>; - getMessagesWithVisualMediaAttachments: ( - conversationId: string, - options: { limit: number } - ) => Promise>; - getMessagesWithFileAttachments: ( - conversationId: string, - options: { limit: number } - ) => Promise>; - getMessageServerGuidsForSpam: ( - conversationId: string - ) => Promise>; - - getJobsInQueue(queueType: string): Promise>; - insertJob(job: Readonly): Promise; - deleteJob(id: string): Promise; - - wasGroupCallRingPreviouslyCanceled(ringId: bigint): Promise; - processGroupCallRingCancellation(ringId: bigint): Promise; - cleanExpiredGroupCallRingCancellations(): Promise; - - getMaxMessageCounter(): Promise; - - getStatisticsForLogging(): Promise>; - - optimizeFTS: ( - state?: FTSOptimizationStateType - ) => Promise; -}; - -export type ServerInterface = DataInterface & { - // Differing signature on client/server - - updateConversation: (data: ConversationType) => Promise; - removeConversation: (id: Array | string) => Promise; - - searchMessages: ({ - query, - conversationId, - options, - contactServiceIdsMatchingQuery, - }: { - query: string; - conversationId?: string; - options?: { limit?: number }; - contactServiceIdsMatchingQuery?: Array; - }) => Promise>; - removeMessage: (id: string) => Promise; - removeMessages: (ids: ReadonlyArray) => Promise; - - getRecentStoryReplies( - storyId: string, - options?: GetRecentStoryRepliesOptionsType - ): Promise>; - getOlderMessagesByConversation: ( - options: AdjacentMessagesByConversationOptionsType - ) => Promise>; - getNewerMessagesByConversation: ( - options: AdjacentMessagesByConversationOptionsType - ) => Promise>; - getConversationRangeCenteredOnMessage: ( - options: AdjacentMessagesByConversationOptionsType - ) => Promise< - GetConversationRangeCenteredOnMessageResultType - >; - - createOrUpdateIdentityKey: (data: StoredIdentityKeyType) => Promise; - getIdentityKeyById: ( - id: IdentityKeyIdType - ) => Promise; - bulkAddIdentityKeys: (array: Array) => Promise; - getAllIdentityKeys: () => Promise>; - - createOrUpdateKyberPreKey: (data: StoredKyberPreKeyType) => Promise; - getKyberPreKeyById: ( - id: PreKeyIdType - ) => Promise; - bulkAddKyberPreKeys: (array: Array) => Promise; - getAllKyberPreKeys: () => Promise>; - - createOrUpdatePreKey: (data: StoredPreKeyType) => Promise; - getPreKeyById: (id: PreKeyIdType) => Promise; - bulkAddPreKeys: (array: Array) => Promise; - getAllPreKeys: () => Promise>; - - createOrUpdateSignedPreKey: (data: StoredSignedPreKeyType) => Promise; - getSignedPreKeyById: ( - id: SignedPreKeyIdType - ) => Promise; - bulkAddSignedPreKeys: (array: Array) => Promise; - getAllSignedPreKeys: () => Promise>; - - createOrUpdateItem( - data: StoredItemType - ): Promise; - getItemById( - id: K - ): Promise | undefined>; - getAllItems: () => Promise; - - // Server-only - - initialize: (options: { - appVersion: string; - configDir: string; - key: string; - logger: LoggerType; - }) => Promise; - - getKnownMessageAttachments: ( - cursor?: MessageAttachmentsCursorType - ) => Promise; - finishGetKnownMessageAttachments: ( - cursor: MessageAttachmentsCursorType - ) => Promise; - getKnownConversationAttachments: () => Promise>; - removeKnownStickers: ( - allStickers: ReadonlyArray - ) => Promise>; - removeKnownDraftAttachments: ( - allStickers: ReadonlyArray - ) => Promise>; - getAllBadgeImageFileLocalPaths: () => Promise>; - - runCorruptionChecks: () => void; -}; - export type GetRecentStoryRepliesOptionsType = { limit?: number; messageId?: string; @@ -1033,28 +444,482 @@ export type GetRecentStoryRepliesOptionsType = { sentAt?: number; }; -// Differing signature on client/server -export type ClientExclusiveInterface = { - // Differing signature on client/server +type ReadableInterface = { + close: () => void; - updateConversation: (data: ConversationType) => void; - removeConversation: (id: string) => Promise; - flushUpdateConversationBatcher: () => Promise; + getSenderKeyById: (id: SenderKeyIdType) => SenderKeyType | undefined; + getAllSenderKeys: () => Array; - removeMessage: ( + getAllSentProtos: () => Array; + + // Test-only + _getAllSentProtoRecipients: () => Array; + _getAllSentProtoMessageIds: () => Array; + + getAllSessions: () => Array; + + getConversationCount: () => number; + getConversationById: (id: string) => ConversationType | undefined; + + getAllConversations: () => Array; + getAllConversationIds: () => Array; + getAllGroupsInvolvingServiceId: ( + serviceId: ServiceIdString + ) => Array; + + getGroupSendCombinedEndorsementExpiration: (groupId: string) => number | null; + + getMessageCount: (conversationId?: string) => number; + getStoryCount: (conversationId: string) => number; + + pageMessages: (cursor?: PageMessagesCursorType) => PageMessagesResultType; + finishPageMessages: (cursor: PageMessagesCursorType) => void; + + getTotalUnreadForConversation: ( + conversationId: string, + options: { + storyId: string | undefined; + includeStoryReplies: boolean; + } + ) => number; + getTotalUnreadMentionsOfMeForConversation: ( + conversationId: string, + options: { + storyId?: string; + includeStoryReplies: boolean; + } + ) => number; + getOldestUnreadMentionOfMeForConversation( + conversationId: string, + options: { + storyId?: string; + includeStoryReplies: boolean; + } + ): MessageMetricsType | undefined; + + getReactionByTimestamp: ( + fromId: string, + timestamp: number + ) => ReactionType | undefined; + _getAllReactions: () => Array; + + getMessageBySender: (options: { + source?: string; + sourceServiceId?: ServiceIdString; + sourceDevice?: number; + sent_at: number; + }) => MessageType | undefined; + getMessageById: (id: string) => MessageType | undefined; + getMessagesById: (messageIds: ReadonlyArray) => Array; + _getAllMessages: () => Array; + _getAllEditedMessages: () => Array<{ messageId: string; sentAt: number }>; + getAllMessageIds: () => Array; + getMessagesBySentAt: (sentAt: number) => Array; + getExpiredMessages: () => Array; + getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Array; + getSoonestMessageExpiry: () => undefined | number; + getNextTapToViewMessageTimestampToAgeOut: () => undefined | number; + getTapToViewMessagesNeedingErase: () => Array; + // getOlderMessagesByConversation is JSON on server, full message on Client + getAllStories: (options: { + conversationId?: string; + sourceServiceId?: ServiceIdString; + }) => GetAllStoriesResultType; + // getNewerMessagesByConversation is JSON on server, full message on Client + getMessageMetricsForConversation: (options: { + conversationId: string; + storyId?: string; + includeStoryReplies: boolean; + }) => ConversationMetricsType; + // getConversationRangeCenteredOnMessage is JSON on server, full message on client + getConversationMessageStats: (options: { + conversationId: string; + includeStoryReplies: boolean; + }) => ConversationMessageStatsType; + getLastConversationMessage(options: { + conversationId: string; + }): MessageType | undefined; + getAllCallHistory: () => ReadonlyArray; + getCallHistoryUnreadCount(): number; + getCallHistoryMessageByCallId(options: { + conversationId: string; + callId: string; + }): MessageType | undefined; + getCallHistory( + callId: string, + peerId: ServiceIdString | string + ): CallHistoryDetails | undefined; + getCallHistoryGroupsCount(filter: CallHistoryFilter): number; + getCallHistoryGroups( + filter: CallHistoryFilter, + pagination: CallHistoryPagination + ): Array; + hasGroupCallHistoryMessage: ( + conversationId: string, + eraId: string + ) => boolean; + callLinkExists(roomId: string): boolean; + getAllCallLinks: () => ReadonlyArray; + getCallLinkByRoomId: (roomId: string) => CallLinkType | undefined; + getMessagesBetween: ( + conversationId: string, + options: GetMessagesBetweenOptions + ) => Array; + getNearbyMessageFromDeletedSet: ( + options: GetNearbyMessageFromDeletedSetOptionsType + ) => string | null; + getMostRecentAddressableMessages: ( + conversationId: string, + limit?: number + ) => Array; + getMostRecentAddressableNondisappearingMessages: ( + conversationId: string, + limit?: number + ) => Array; + + getUnprocessedCount: () => number; + getUnprocessedById: (id: string) => UnprocessedType | undefined; + + getAttachmentDownloadJob( + job: Pick< + AttachmentDownloadJobType, + 'messageId' | 'attachmentType' | 'digest' + > + ): AttachmentDownloadJobType; + + getBackupCdnObjectMetadata: ( + mediaId: string + ) => BackupCdnMediaObjectType | undefined; + + getStickerCount: () => number; + getAllStickerPacks: () => Array; + getInstalledStickerPacks: () => Array; + getUninstalledStickerPacks: () => Array; + getStickerPackInfo: (packId: string) => StickerPackInfoType | undefined; + getAllStickers: () => Array; + getRecentStickers: (options?: { limit?: number }) => Array; + + getRecentEmojis: (limit?: number) => Array; + + getAllBadges(): Array; + + _getAllStoryDistributions(): Array; + _getAllStoryDistributionMembers(): Array; + getAllStoryDistributionsWithMembers(): Array; + getStoryDistributionWithMembers( + id: string + ): StoryDistributionWithMembersType | undefined; + + _getAllStoryReads(): Array; + getLastStoryReadsForAuthor(options: { + authorId: ServiceIdString; + conversationId?: string; + limit?: number; + }): Array; + countStoryReadsByConversation(conversationId: string): number; + + getMessagesNeedingUpgrade: ( + limit: number, + options: { maxVersion: number } + ) => Array; + getMessagesWithVisualMediaAttachments: ( + conversationId: string, + options: { limit: number } + ) => Array; + getMessagesWithFileAttachments: ( + conversationId: string, + options: { limit: number } + ) => Array; + getMessageServerGuidsForSpam: (conversationId: string) => Array; + + getJobsInQueue(queueType: string): Array; + + wasGroupCallRingPreviouslyCanceled(ringId: bigint): boolean; + + getMaxMessageCounter(): number | undefined; + + getStatisticsForLogging(): Record; +}; + +type WritableInterface = { + close: () => void; + + removeDB: () => void; + + removeIndexedDBFiles: () => void; + + removeIdentityKeyById: (id: IdentityKeyIdType) => number; + removeAllIdentityKeys: () => number; + + removeKyberPreKeyById: (id: PreKeyIdType | Array) => number; + removeKyberPreKeysByServiceId: (serviceId: ServiceIdString) => void; + removeAllKyberPreKeys: () => number; + + removePreKeyById: (id: PreKeyIdType | Array) => number; + removePreKeysByServiceId: (serviceId: ServiceIdString) => void; + removeAllPreKeys: () => number; + + removeSignedPreKeyById: ( + id: SignedPreKeyIdType | Array + ) => number; + removeSignedPreKeysByServiceId: (serviceId: ServiceIdString) => void; + removeAllSignedPreKeys: () => number; + + removeAllItems: () => number; + removeItemById: (id: ItemKeyType | Array) => number; + + createOrUpdateSenderKey: (key: SenderKeyType) => void; + removeAllSenderKeys: () => void; + removeSenderKeyById: (id: SenderKeyIdType) => void; + + getSentProtoByRecipient: (options: { + now: number; + recipientServiceId: ServiceIdString; + timestamp: number; + }) => SentProtoWithMessageIdsType | undefined; + insertSentProto: ( + proto: SentProtoType, + options: { + recipients: SentRecipientsType; + messageIds: SentMessagesType; + } + ) => number; + deleteSentProtosOlderThan: (timestamp: number) => void; + deleteSentProtoByMessageId: (messageId: string) => void; + insertProtoRecipients: (options: { + id: number; + recipientServiceId: ServiceIdString; + deviceIds: Array; + }) => void; + deleteSentProtoRecipient: ( + options: + | DeleteSentProtoRecipientOptionsType + | ReadonlyArray + ) => DeleteSentProtoRecipientResultType; + removeAllSentProtos: () => void; + + createOrUpdateSession: (data: SessionType) => void; + createOrUpdateSessions: (array: Array) => void; + commitDecryptResult(options: { + senderKeys: Array; + sessions: Array; + unprocessed: Array; + }): void; + bulkAddSessions: (array: Array) => void; + removeSessionById: (id: SessionIdType) => number; + removeSessionsByConversation: (conversationId: string) => void; + removeSessionsByServiceId: (serviceId: ServiceIdString) => void; + removeAllSessions: () => number; + + saveConversation: (data: ConversationType) => void; + saveConversations: (array: Array) => void; + // updateConversation is a normal data method on Server, a sync batch-add on Client + updateConversations: (array: Array) => void; + // removeConversation handles either one id or an array on Server, and one id on Client + _removeAllConversations: () => void; + updateAllConversationColors: ( + conversationColor?: ConversationColorType, + customColorData?: { + id: string; + value: CustomColorType; + } + ) => void; + removeAllProfileKeyCredentials: () => void; + + replaceAllEndorsementsForGroup: (data: GroupSendEndorsementsData) => void; + deleteAllEndorsementsForGroup: (groupId: string) => void; + + saveMessage: ( + data: MessageType, + options: { + jobToInsert?: StoredJob; + forceSave?: boolean; + ourAci: AciString; + } + ) => string; + saveMessages: ( + arrayOfMessages: ReadonlyArray, + options: { forceSave?: boolean; ourAci: AciString } + ) => Array; + + getUnreadByConversationAndMarkRead: (options: { + conversationId: string; + includeStoryReplies: boolean; + newestUnreadAt: number; + now?: number; + readAt?: number; + storyId?: string; + }) => GetUnreadByConversationAndMarkReadResultType; + getUnreadEditedMessagesAndMarkRead: (options: { + conversationId: string; + newestUnreadAt: number; + }) => GetUnreadByConversationAndMarkReadResultType; + getUnreadReactionsAndMarkRead: (options: { + conversationId: string; + newestUnreadAt: number; + storyId?: string; + }) => Array; + markReactionAsRead: ( + targetAuthorServiceId: ServiceIdString, + targetTimestamp: number + ) => ReactionType | undefined; + removeReactionFromConversation: (reaction: { + emoji: string; + fromId: string; + targetAuthorServiceId: ServiceIdString; + targetTimestamp: number; + }) => void; + addReaction: ( + reactionObj: ReactionType, + options: { + readStatus: ReactionReadStatus; + } + ) => void; + _removeAllReactions: () => void; + _removeAllMessages: () => void; + + clearCallHistory: (target: CallLogEventTarget) => ReadonlyArray; + markCallHistoryDeleted: (callId: string) => void; + cleanupCallHistoryMessages: () => void; + markCallHistoryRead(callId: string): void; + markAllCallHistoryRead(target: CallLogEventTarget): void; + markAllCallHistoryReadInConversation(target: CallLogEventTarget): void; + saveCallHistory(callHistory: CallHistoryDetails): void; + markCallHistoryMissed(callIds: ReadonlyArray): void; + getRecentStaleRingsAndMarkOlderMissed(): ReadonlyArray; + insertCallLink(callLink: CallLinkType): void; + updateCallLinkAdminKeyByRoomId(roomId: string, adminKey: string): void; + updateCallLinkState( + roomId: string, + callLinkState: CallLinkStateType + ): CallLinkType; + migrateConversationMessages: (obsoleteId: string, currentId: string) => void; + saveEditedMessage: ( + mainMessage: MessageType, + ourAci: AciString, + opts: EditedMessageType + ) => void; + saveEditedMessages: ( + mainMessage: MessageType, + ourAci: AciString, + history: ReadonlyArray + ) => void; + + removeSyncTaskById: (id: string) => void; + saveSyncTasks: (tasks: Array) => void; + getAllSyncTasks: () => Array; + + getAllUnprocessedIds: () => Array; + getUnprocessedByIdsAndIncrementAttempts: ( + ids: ReadonlyArray + ) => Array; + updateUnprocessedWithData: (id: string, data: UnprocessedUpdateType) => void; + updateUnprocessedsWithData: ( + array: Array<{ id: string; data: UnprocessedUpdateType }> + ) => void; + removeUnprocessed: (id: string | Array) => void; + + /** only for testing */ + removeAllUnprocessed: () => void; + + getNextAttachmentDownloadJobs: (options: { + limit: number; + prioritizeMessageIds?: Array; + timestamp?: number; + }) => Array; + saveAttachmentDownloadJob: (job: AttachmentDownloadJobType) => void; + resetAttachmentDownloadActive: () => void; + removeAttachmentDownloadJob: (job: AttachmentDownloadJobType) => void; + + getNextAttachmentBackupJobs: (options: { + limit: number; + timestamp?: number; + }) => Array; + saveAttachmentBackupJob: (job: AttachmentBackupJobType) => void; + markAllAttachmentBackupJobsInactive: () => void; + removeAttachmentBackupJob: (job: AttachmentBackupJobType) => void; + clearAllAttachmentBackupJobs: () => void; + + clearAllBackupCdnObjectMetadata: () => void; + saveBackupCdnObjectMetadata: ( + mediaObjects: Array + ) => void; + + createOrUpdateStickerPack: (pack: StickerPackType) => void; + updateStickerPackStatus: ( id: string, + status: StickerPackStatusType, + options?: { timestamp: number } + ) => void; + updateStickerPackInfo: (info: StickerPackInfoType) => void; + createOrUpdateSticker: (sticker: StickerType) => void; + createOrUpdateStickers: (sticker: ReadonlyArray) => void; + updateStickerLastUsed: ( + packId: string, + stickerId: number, + lastUsed: number + ) => void; + addStickerPackReference: (messageId: string, packId: string) => void; + deleteStickerPackReference: ( + messageId: string, + packId: string + ) => ReadonlyArray | undefined; + deleteStickerPack: (packId: string) => Array; + addUninstalledStickerPack: (pack: UninstalledStickerPackType) => void; + removeUninstalledStickerPack: (packId: string) => void; + installStickerPack: (packId: string, timestamp: number) => void; + uninstallStickerPack: (packId: string, timestamp: number) => void; + clearAllErrorStickerPackAttempts: () => void; + + updateEmojiUsage: (shortName: string, timeUsed?: number) => void; + + updateOrCreateBadges(badges: ReadonlyArray): void; + badgeImageFileDownloaded(url: string, localPath: string): void; + + _deleteAllStoryDistributions(): void; + createNewStoryDistribution( + distribution: StoryDistributionWithMembersType + ): void; + modifyStoryDistribution(distribution: StoryDistributionType): void; + modifyStoryDistributionMembers( + listId: string, options: { - fromSync?: boolean; - singleProtoJobQueue: SingleProtoJobQueue; + toAdd: Array; + toRemove: Array; } - ) => Promise; - removeMessages: ( - ids: ReadonlyArray, + ): void; + modifyStoryDistributionWithMembers( + distribution: StoryDistributionType, options: { - fromSync?: boolean; - singleProtoJobQueue: SingleProtoJobQueue; + toAdd: Array; + toRemove: Array; } - ) => Promise; + ): void; + deleteStoryDistribution(id: StoryDistributionIdString): void; + + _deleteAllStoryReads(): void; + addNewStoryRead(read: StoryReadType): void; + + removeAll: () => void; + removeAllConfiguration: () => void; + eraseStorageServiceState: () => void; + + insertJob(job: Readonly): void; + deleteJob(id: string): void; + + processGroupCallRingCancellation(ringId: bigint): void; + cleanExpiredGroupCallRingCancellations(): void; +}; + +// Adds a database argument +type AddReadonlyDB = { + [Key in keyof I]: I[Key] extends (...args: infer Args) => infer R + ? (db: ReadableDB, ...args: Args) => R + : never; +}; + +export type ServerReadableDirectInterface = ReadableInterface & { + // Differing signature on client/server searchMessages: ({ query, conversationId, @@ -1065,55 +930,188 @@ export type ClientExclusiveInterface = { conversationId?: string; options?: { limit?: number }; contactServiceIdsMatchingQuery?: Array; - }) => Promise>; + }) => Array; getRecentStoryReplies( storyId: string, options?: GetRecentStoryRepliesOptionsType - ): Promise>; + ): Array; getOlderMessagesByConversation: ( options: AdjacentMessagesByConversationOptionsType - ) => Promise>; + ) => Array; getNewerMessagesByConversation: ( options: AdjacentMessagesByConversationOptionsType - ) => Promise>; + ) => Array; getConversationRangeCenteredOnMessage: ( options: AdjacentMessagesByConversationOptionsType - ) => Promise>; + ) => GetConversationRangeCenteredOnMessageResultType; - createOrUpdateIdentityKey: (data: IdentityKeyType) => Promise; getIdentityKeyById: ( id: IdentityKeyIdType - ) => Promise; - bulkAddIdentityKeys: (array: Array) => Promise; - getAllIdentityKeys: () => Promise>; + ) => StoredIdentityKeyType | undefined; + getAllIdentityKeys: () => Array; - createOrUpdateKyberPreKey: (data: KyberPreKeyType) => Promise; - getKyberPreKeyById: ( - id: PreKeyIdType - ) => Promise; - bulkAddKyberPreKeys: (array: Array) => Promise; - getAllKyberPreKeys: () => Promise>; + getKyberPreKeyById: (id: PreKeyIdType) => StoredKyberPreKeyType | undefined; + getAllKyberPreKeys: () => Array; - createOrUpdatePreKey: (data: PreKeyType) => Promise; - getPreKeyById: (id: PreKeyIdType) => Promise; - bulkAddPreKeys: (array: Array) => Promise; - getAllPreKeys: () => Promise>; + getPreKeyById: (id: PreKeyIdType) => StoredPreKeyType | undefined; + getAllPreKeys: () => Array; - createOrUpdateSignedPreKey: (data: SignedPreKeyType) => Promise; getSignedPreKeyById: ( id: SignedPreKeyIdType - ) => Promise; - bulkAddSignedPreKeys: (array: Array) => Promise; - getAllSignedPreKeys: () => Promise>; + ) => StoredSignedPreKeyType | undefined; + getAllSignedPreKeys: () => Array; - createOrUpdateItem(data: ItemType): Promise; - getItemById(id: K): Promise | undefined>; - getAllItems: () => Promise; + getItemById(id: K): StoredItemType | undefined; + getAllItems: () => StoredAllItemsType; + + // Server-only + + getKnownMessageAttachments: ( + cursor?: MessageAttachmentsCursorType + ) => GetKnownMessageAttachmentsResultType; + finishGetKnownMessageAttachments: ( + cursor: MessageAttachmentsCursorType + ) => void; + getKnownConversationAttachments: () => Array; + + getAllBadgeImageFileLocalPaths: () => Set; +}; +export type ServerReadableInterface = + AddReadonlyDB; + +// Adds a database argument +type AddWritableDB = { + [Key in keyof I]: I[Key] extends (...args: infer Args) => infer R + ? (db: WritableDB, ...args: Args) => R + : never; +}; + +export type ServerWritableDirectInterface = WritableInterface & { + // Differing signature on client/server + + updateConversation: (data: ConversationType) => void; + removeConversation: (id: Array | string) => void; + + removeMessage: (id: string) => void; + removeMessages: (ids: ReadonlyArray) => void; + + createOrUpdateIdentityKey: (data: StoredIdentityKeyType) => void; + bulkAddIdentityKeys: (array: Array) => void; + + createOrUpdateKyberPreKey: (data: StoredKyberPreKeyType) => void; + bulkAddKyberPreKeys: (array: Array) => void; + + createOrUpdatePreKey: (data: StoredPreKeyType) => void; + bulkAddPreKeys: (array: Array) => void; + + createOrUpdateSignedPreKey: (data: StoredSignedPreKeyType) => void; + bulkAddSignedPreKeys: (array: Array) => void; + + createOrUpdateItem(data: StoredItemType): void; + + // Server-only + + removeKnownStickers: (allStickers: ReadonlyArray) => Array; + removeKnownDraftAttachments: ( + allStickers: ReadonlyArray + ) => Array; + + runCorruptionChecks: () => void; +}; + +export type ServerWritableInterface = + AddWritableDB; + +// Makes sync calls - async +export type ClientInterfaceWrap = { + [Key in keyof I]: I[Key] extends (...args: infer Args) => infer R + ? (...args: Args) => Promise + : never; +}; + +export type ClientOnlyReadableInterface = ClientInterfaceWrap<{ + // Differing signature on client/server + searchMessages: ({ + query, + conversationId, + options, + contactServiceIdsMatchingQuery, + }: { + query: string; + conversationId?: string; + options?: { limit?: number }; + contactServiceIdsMatchingQuery?: Array; + }) => Array; + + getRecentStoryReplies( + storyId: string, + options?: GetRecentStoryRepliesOptionsType + ): Array; + getOlderMessagesByConversation: ( + options: AdjacentMessagesByConversationOptionsType + ) => Array; + getNewerMessagesByConversation: ( + options: AdjacentMessagesByConversationOptionsType + ) => Array; + getConversationRangeCenteredOnMessage: ( + options: AdjacentMessagesByConversationOptionsType + ) => GetConversationRangeCenteredOnMessageResultType; + + getIdentityKeyById: (id: IdentityKeyIdType) => IdentityKeyType | undefined; + getAllIdentityKeys: () => Array; + + getKyberPreKeyById: (id: PreKeyIdType) => KyberPreKeyType | undefined; + getAllKyberPreKeys: () => Array; + + getPreKeyById: (id: PreKeyIdType) => PreKeyType | undefined; + getAllPreKeys: () => Array; + + getSignedPreKeyById: (id: SignedPreKeyIdType) => SignedPreKeyType | undefined; + getAllSignedPreKeys: () => Array; + + getItemById(id: K): ItemType | undefined; + getAllItems: () => AllItemsType; +}>; + +export type ClientOnlyWritableInterface = ClientInterfaceWrap<{ + // Differing signature on client/server + updateConversation: (data: ConversationType) => void; + removeConversation: (id: string) => void; + flushUpdateConversationBatcher: () => void; + + removeMessage: ( + id: string, + options: { + fromSync?: boolean; + singleProtoJobQueue: SingleProtoJobQueue; + } + ) => void; + removeMessages: ( + ids: ReadonlyArray, + options: { + fromSync?: boolean; + singleProtoJobQueue: SingleProtoJobQueue; + } + ) => void; + + createOrUpdateIdentityKey: (data: IdentityKeyType) => void; + bulkAddIdentityKeys: (array: Array) => void; + + createOrUpdateKyberPreKey: (data: KyberPreKeyType) => void; + bulkAddKyberPreKeys: (array: Array) => void; + + createOrUpdatePreKey: (data: PreKeyType) => void; + bulkAddPreKeys: (array: Array) => void; + + createOrUpdateSignedPreKey: (data: SignedPreKeyType) => void; + bulkAddSignedPreKeys: (array: Array) => void; + + createOrUpdateItem(data: ItemType): void; // Client-side only - shutdown: () => Promise; + shutdown: () => void; removeMessagesInConversation: ( conversationId: string, options: { @@ -1122,10 +1120,13 @@ export type ClientExclusiveInterface = { receivedAt?: number; singleProtoJobQueue: SingleProtoJobQueue; } - ) => Promise; - removeOtherData: () => Promise; - cleanupOrphanedAttachments: () => Promise; - ensureFilePermissions: () => Promise; -}; + ) => void; + removeOtherData: () => void; + cleanupOrphanedAttachments: () => void; + ensureFilePermissions: () => void; +}>; -export type ClientInterface = DataInterface & ClientExclusiveInterface; +export type ClientReadableInterface = ClientInterfaceWrap & + ClientOnlyReadableInterface; +export type ClientWritableInterface = ClientInterfaceWrap & + ClientOnlyWritableInterface; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 282a2e969..f30ae20ee 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -9,8 +9,6 @@ import rimraf from 'rimraf'; import { randomBytes } from 'crypto'; import type { Database, Statement } from '@signalapp/better-sqlite3'; import SQL from '@signalapp/better-sqlite3'; -import pProps from 'p-props'; -import pTimeout from 'p-timeout'; import { v4 as generateUuid } from 'uuid'; import { z } from 'zod'; @@ -42,7 +40,7 @@ import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import type { ServiceIdString, AciString } from '../types/ServiceId'; import { isServiceIdString } from '../types/ServiceId'; import type { StoredJob } from '../jobs/types'; -import { assertDev, assertSync, strictAssert } from '../util/assert'; +import { assertDev, strictAssert } from '../util/assert'; import { combineNames } from '../util/combineNames'; import { consoleLogger } from '../util/consoleLogger'; import { dropNull } from '../util/dropNull'; @@ -50,7 +48,6 @@ import { isNormalNumber } from '../util/isNormalNumber'; import { isNotNil } from '../util/isNotNil'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; import * as durations from '../util/durations'; -import { explodePromise } from '../util/explodePromise'; import { formatCountForLogging } from '../logging/formatCountForLogging'; import type { ConversationColorType, CustomColorType } from '../types/Colors'; import type { BadgeType, BadgeImageType } from '../badges/types'; @@ -87,6 +84,8 @@ import { import { updateSchema } from './migrations'; import type { + ReadableDB, + WritableDB, AdjacentMessagesByConversationOptionsType, StoredAllItemsType, ConversationMetricsType, @@ -95,7 +94,6 @@ import type { DeleteSentProtoRecipientResultType, EditedMessageType, EmojiType, - FTSOptimizationStateType, GetAllStoriesResultType, GetConversationRangeCenteredOnMessageResultType, GetKnownMessageAttachmentsResultType, @@ -126,7 +124,8 @@ import type { SentProtoWithMessageIdsType, SentRecipientsDBType, SentRecipientsType, - ServerInterface, + ServerReadableInterface, + ServerWritableInterface, SessionIdType, SessionType, SignedPreKeyIdType, @@ -219,94 +218,40 @@ type StickerRow = Readonly<{ // to this one default export, which does conform to the interface. // Note: In Javascript, you need to access the .default property when requiring it // https://github.com/microsoft/TypeScript/issues/420 -const dataInterface: ServerInterface = { - close, - pauseWriteAccess, - resumeWriteAccess, - removeDB, - removeIndexedDBFiles, +export const DataReader: ServerReadableInterface = { + close: closeReadable, - createOrUpdateIdentityKey, getIdentityKeyById, - bulkAddIdentityKeys, - removeIdentityKeyById, - removeAllIdentityKeys, getAllIdentityKeys, - createOrUpdateKyberPreKey, getKyberPreKeyById, - bulkAddKyberPreKeys, - removeKyberPreKeyById, - removeKyberPreKeysByServiceId, - removeAllKyberPreKeys, getAllKyberPreKeys, - createOrUpdatePreKey, getPreKeyById, - bulkAddPreKeys, - removePreKeyById, - removePreKeysByServiceId, - removeAllPreKeys, getAllPreKeys, - createOrUpdateSignedPreKey, getSignedPreKeyById, - bulkAddSignedPreKeys, - removeSignedPreKeyById, - removeSignedPreKeysByServiceId, - removeAllSignedPreKeys, getAllSignedPreKeys, - createOrUpdateItem, getItemById, - removeItemById, - removeAllItems, getAllItems, - createOrUpdateSenderKey, getSenderKeyById, - removeAllSenderKeys, getAllSenderKeys, - removeSenderKeyById, - insertSentProto, - deleteSentProtosOlderThan, - deleteSentProtoByMessageId, - insertProtoRecipients, - deleteSentProtoRecipient, - getSentProtoByRecipient, - removeAllSentProtos, getAllSentProtos, _getAllSentProtoRecipients, _getAllSentProtoMessageIds, - createOrUpdateSession, - createOrUpdateSessions, - commitDecryptResult, - bulkAddSessions, - removeSessionById, - removeSessionsByConversation, - removeSessionsByServiceId, - removeAllSessions, getAllSessions, getConversationCount, - saveConversation, - saveConversations, getConversationById, - updateConversation, - updateConversations, - removeConversation, - _removeAllConversations, - updateAllConversationColors, - removeAllProfileKeyCredentials, getAllConversations, getAllConversationIds, getAllGroupsInvolvingServiceId, - replaceAllEndorsementsForGroup, - deleteAllEndorsementsForGroup, getGroupSendCombinedEndorsementExpiration, searchMessages, @@ -314,27 +259,16 @@ const dataInterface: ServerInterface = { getMessageCount, getStoryCount, getRecentStoryReplies, - saveMessage, - saveMessages, - removeMessage, - removeMessages, - getUnreadByConversationAndMarkRead, - getUnreadReactionsAndMarkRead, - markReactionAsRead, + countStoryReadsByConversation, getReactionByTimestamp, - addReaction, - removeReactionFromConversation, _getAllReactions, - _removeAllReactions, getMessageBySender, getMessageById, getMessagesById, _getAllMessages, _getAllEditedMessages, - _removeAllMessages, getAllMessageIds, getMessagesBySentAt, - getUnreadEditedMessagesAndMarkRead, getExpiredMessages, getMessagesUnexpectedlyMissingExpirationStartTimestamp, getSoonestMessageExpiry, @@ -351,49 +285,165 @@ const dataInterface: ServerInterface = { getConversationMessageStats, getLastConversationMessage, getAllCallHistory, - clearCallHistory, - markCallHistoryDeleted, - cleanupCallHistoryMessages, getCallHistoryUnreadCount, - markCallHistoryRead, - markAllCallHistoryRead, - markAllCallHistoryReadInConversation, getCallHistoryMessageByCallId, getCallHistory, getCallHistoryGroupsCount, getCallHistoryGroups, - saveCallHistory, hasGroupCallHistoryMessage, - markCallHistoryMissed, - getRecentStaleRingsAndMarkOlderMissed, + callLinkExists, getAllCallLinks, getCallLinkByRoomId, + getMessagesBetween, + getNearbyMessageFromDeletedSet, + getMostRecentAddressableMessages, + getMostRecentAddressableNondisappearingMessages, + getUnprocessedCount, + getUnprocessedById, + getAttachmentDownloadJob, + + getStickerCount, + getAllStickerPacks, + getInstalledStickerPacks, + getUninstalledStickerPacks, + getStickerPackInfo, + getAllStickers, + getRecentStickers, + getRecentEmojis, + + getAllBadges, + getAllBadgeImageFileLocalPaths, + getAllStoryDistributionsWithMembers, + getStoryDistributionWithMembers, + _getAllStoryDistributions, + _getAllStoryDistributionMembers, + _getAllStoryReads, + getLastStoryReadsForAuthor, + getMessagesNeedingUpgrade, + getMessagesWithVisualMediaAttachments, + getMessagesWithFileAttachments, + getMessageServerGuidsForSpam, + + getJobsInQueue, + wasGroupCallRingPreviouslyCanceled, + getMaxMessageCounter, + + getStatisticsForLogging, + + getBackupCdnObjectMetadata, + + // Server-only + getKnownMessageAttachments, + finishGetKnownMessageAttachments, + pageMessages, + finishPageMessages, + getKnownConversationAttachments, +}; + +export const DataWriter: ServerWritableInterface = { + close: closeWritable, + removeDB, + removeIndexedDBFiles, + + createOrUpdateIdentityKey, + bulkAddIdentityKeys, + removeIdentityKeyById, + removeAllIdentityKeys, + + createOrUpdateKyberPreKey, + bulkAddKyberPreKeys, + removeKyberPreKeyById, + removeKyberPreKeysByServiceId, + removeAllKyberPreKeys, + + createOrUpdatePreKey, + bulkAddPreKeys, + removePreKeyById, + removePreKeysByServiceId, + removeAllPreKeys, + + createOrUpdateSignedPreKey, + bulkAddSignedPreKeys, + removeSignedPreKeyById, + removeSignedPreKeysByServiceId, + removeAllSignedPreKeys, + + createOrUpdateItem, + removeItemById, + removeAllItems, + + createOrUpdateSenderKey, + removeAllSenderKeys, + removeSenderKeyById, + + insertSentProto, + deleteSentProtosOlderThan, + deleteSentProtoByMessageId, + insertProtoRecipients, + deleteSentProtoRecipient, + removeAllSentProtos, + getSentProtoByRecipient, + + createOrUpdateSession, + createOrUpdateSessions, + commitDecryptResult, + bulkAddSessions, + removeSessionById, + removeSessionsByConversation, + removeSessionsByServiceId, + removeAllSessions, + + saveConversation, + saveConversations, + updateConversation, + updateConversations, + removeConversation, + _removeAllConversations, + updateAllConversationColors, + removeAllProfileKeyCredentials, + getUnreadByConversationAndMarkRead, + getUnreadReactionsAndMarkRead, + + replaceAllEndorsementsForGroup, + deleteAllEndorsementsForGroup, + + saveMessage, + saveMessages, + removeMessage, + removeMessages, + markReactionAsRead, + addReaction, + removeReactionFromConversation, + _removeAllReactions, + _removeAllMessages, + getUnreadEditedMessagesAndMarkRead, + clearCallHistory, + markCallHistoryDeleted, + cleanupCallHistoryMessages, + markCallHistoryRead, + markAllCallHistoryRead, + markAllCallHistoryReadInConversation, + saveCallHistory, + markCallHistoryMissed, insertCallLink, updateCallLinkAdminKeyByRoomId, updateCallLinkState, migrateConversationMessages, - getMessagesBetween, - getNearbyMessageFromDeletedSet, saveEditedMessage, saveEditedMessages, - getMostRecentAddressableMessages, - getMostRecentAddressableNondisappearingMessages, removeSyncTaskById, saveSyncTasks, getAllSyncTasks, - getUnprocessedCount, getUnprocessedByIdsAndIncrementAttempts, getAllUnprocessedIds, updateUnprocessedWithData, updateUnprocessedsWithData, - getUnprocessedById, removeUnprocessed, removeAllUnprocessed, - getAttachmentDownloadJob, getNextAttachmentDownloadJobs, saveAttachmentDownloadJob, resetAttachmentDownloadActive, @@ -407,7 +457,6 @@ const dataInterface: ServerInterface = { clearAllBackupCdnObjectMetadata, saveBackupCdnObjectMetadata, - getBackupCdnObjectMetadata, createOrUpdateStickerPack, updateStickerPackStatus, @@ -417,90 +466,52 @@ const dataInterface: ServerInterface = { updateStickerLastUsed, addStickerPackReference, deleteStickerPackReference, - getStickerCount, deleteStickerPack, - getAllStickerPacks, addUninstalledStickerPack, removeUninstalledStickerPack, - getInstalledStickerPacks, - getUninstalledStickerPacks, installStickerPack, uninstallStickerPack, - getStickerPackInfo, - getAllStickers, - getRecentStickers, clearAllErrorStickerPackAttempts, updateEmojiUsage, - getRecentEmojis, - - getAllBadges, updateOrCreateBadges, badgeImageFileDownloaded, - _getAllStoryDistributions, - _getAllStoryDistributionMembers, + getRecentStaleRingsAndMarkOlderMissed, + _deleteAllStoryDistributions, createNewStoryDistribution, - getAllStoryDistributionsWithMembers, - getStoryDistributionWithMembers, modifyStoryDistribution, modifyStoryDistributionMembers, modifyStoryDistributionWithMembers, deleteStoryDistribution, - _getAllStoryReads, _deleteAllStoryReads, addNewStoryRead, - getLastStoryReadsForAuthor, - countStoryReadsByConversation, removeAll, removeAllConfiguration, eraseStorageServiceState, - getMessagesNeedingUpgrade, - getMessagesWithVisualMediaAttachments, - getMessagesWithFileAttachments, - getMessageServerGuidsForSpam, - - getJobsInQueue, insertJob, deleteJob, - wasGroupCallRingPreviouslyCanceled, processGroupCallRingCancellation, cleanExpiredGroupCallRingCancellations, - getMaxMessageCounter, - - getStatisticsForLogging, - - optimizeFTS, - // Server-only - initialize, - - getKnownMessageAttachments, - finishGetKnownMessageAttachments, - pageMessages, - finishPageMessages, - getKnownConversationAttachments, removeKnownStickers, removeKnownDraftAttachments, - getAllBadgeImageFileLocalPaths, - runCorruptionChecks, }; -export default dataInterface; type DatabaseQueryCache = Map>>; const statementCache = new WeakMap(); export function prepare | Record>( - db: Database, + db: ReadableDB, query: string, { pluck = false }: { pluck?: boolean } = {} ): Statement { @@ -553,18 +564,18 @@ function rowToSticker(row: StickerRow): StickerType { }; } -function keyDatabase(db: Database, key: string): void { +function keyDatabase(db: WritableDB, key: string): void { // https://www.zetetic.net/sqlcipher/sqlcipher-api/#key db.pragma(`key = "x'${key}'"`); } -function switchToWAL(db: Database): void { +function switchToWAL(db: WritableDB): void { // https://sqlite.org/wal.html db.pragma('journal_mode = WAL'); db.pragma('synchronous = FULL'); } -function migrateSchemaVersion(db: Database): void { +function migrateSchemaVersion(db: WritableDB): void { const userVersion = getUserVersion(db); if (userVersion > 0) { return; @@ -584,14 +595,14 @@ function openAndMigrateDatabase( filePath: string, key: string, readonly: boolean -) { - let db: Database | undefined; +): WritableDB { + let db: WritableDB | undefined; // First, we try to open the database without any cipher changes try { db = new SQL(filePath, { readonly, - }); + }) as WritableDB; keyDatabase(db, key); switchToWAL(db); migrateSchemaVersion(db); @@ -606,7 +617,7 @@ function openAndMigrateDatabase( // If that fails, we try to open the database with 3.x compatibility to extract the // user_version (previously stored in schema_version, blown away by cipher_migrate). - db = new SQL(filePath); + db = new SQL(filePath) as WritableDB; keyDatabase(db, key); // https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0 @@ -616,7 +627,7 @@ function openAndMigrateDatabase( // After migrating user_version -> schema_version, we reopen database, because we can't // migrate to the latest ciphers after we've modified the defaults. - db = new SQL(filePath); + db = new SQL(filePath) as WritableDB; keyDatabase(db, key); db.pragma('cipher_migrate'); @@ -643,10 +654,6 @@ function openAndSetUpSQLCipher( return db; } -let pausedWriteQueue: Array<() => void> | undefined; - -let globalWritableInstance: Database | undefined; -let globalReadonlyInstance: Database | undefined; let logger = consoleLogger; let databaseFilePath: string | undefined; let indexedDBPath: string | undefined; @@ -655,20 +662,18 @@ SQL.setLogHandler((code, value) => { logger.warn(`Database log code=${code}: ${value}`); }); -async function initialize({ +export function initialize({ configDir, key, + isPrimary, logger: suppliedLogger, }: { appVersion: string; configDir: string; key: string; + isPrimary: boolean; logger: LoggerType; -}): Promise { - if (globalWritableInstance || globalReadonlyInstance) { - throw new Error('Cannot initialize more than once!'); - } - +}): WritableDB { if (!isString(configDir)) { throw new Error('initialize: configDir is required!'); } @@ -685,11 +690,10 @@ async function initialize({ databaseFilePath = join(dbDir, 'db.sqlite'); - let writable: Database | undefined; - let readonly: Database | undefined; + let db: WritableDB | undefined; try { - writable = openAndSetUpSQLCipher(databaseFilePath, { + db = openAndSetUpSQLCipher(databaseFilePath, { key, readonly: false, }); @@ -697,32 +701,23 @@ async function initialize({ // For profiling use: // db.pragma('cipher_profile=\'sqlcipher.log\''); - updateSchema(writable, logger); - - readonly = openAndSetUpSQLCipher(databaseFilePath, { key, readonly: true }); - - // At this point we can allow general access to the database - globalWritableInstance = writable; - globalReadonlyInstance = readonly; + // Only the first worker gets to upgrade the schema. The rest just folow. + if (isPrimary) { + updateSchema(db, logger); + } // test database - getMessageCountSync(); + getMessageCount(db); + + return db; } catch (error) { logger.error('Database startup error:', error.stack); - readonly?.close(); - writable?.close(); + db?.close(); throw error; } } -export function setupTests(db: Database): void { - if (globalWritableInstance || globalReadonlyInstance) { - throw new Error('Cannot initialize more than once!'); - } - - globalWritableInstance = db; - globalReadonlyInstance = db; - +export function setupTests(db: WritableDB): void { const silentLogger = { ...consoleLogger, info: noop, @@ -732,67 +727,25 @@ export function setupTests(db: Database): void { updateSchema(db, logger); } -export function teardownTests(): void { - globalWritableInstance = undefined; - globalReadonlyInstance = undefined; +function closeReadable(db: ReadableDB): void { + db.close(); } -async function close(): Promise { - globalReadonlyInstance?.close(); - globalReadonlyInstance = undefined; - +function closeWritable(db: WritableDB): void { // SQLLite documentation suggests that we run `PRAGMA optimize` right // before closing the database connection. - globalWritableInstance?.pragma('optimize'); + db.pragma('optimize'); - globalWritableInstance?.close(); - globalWritableInstance = undefined; + db.close(); } -async function pauseWriteAccess(): Promise { - strictAssert( - pausedWriteQueue === undefined, - 'Database writes are already paused' - ); - pausedWriteQueue = []; - - logger.warn('pauseWriteAccess: pausing write access'); -} - -async function resumeWriteAccess(): Promise { - strictAssert( - pausedWriteQueue !== undefined, - 'Database writes are not paused' - ); - const queue = pausedWriteQueue; - pausedWriteQueue = undefined; - - logger.warn( - `resumeWriteAccess: resuming write access, queue.length=${queue.length}` - ); - - for (const resumeOperation of queue) { - resumeOperation(); +function removeDB(db: WritableDB): void { + try { + db.close(); + } catch (error) { + logger.error('removeDB: Failed to close database:', error.stack); } -} -async function removeDB(): Promise { - if (globalReadonlyInstance) { - try { - globalReadonlyInstance.close(); - } catch (error) { - logger.error('removeDB: Failed to close readonly database:', error.stack); - } - globalReadonlyInstance = undefined; - } - if (globalWritableInstance) { - try { - globalWritableInstance.close(); - } catch (error) { - logger.error('removeDB: Failed to close database:', error.stack); - } - globalWritableInstance = undefined; - } if (!databaseFilePath) { throw new Error( 'removeDB: Cannot erase database without a databaseFilePath!' @@ -805,7 +758,7 @@ async function removeDB(): Promise { rimraf.sync(`${databaseFilePath}-wal`); } -async function removeIndexedDBFiles(): Promise { +function removeIndexedDBFiles(_db: WritableDB): void { if (!indexedDBPath) { throw new Error( 'removeIndexedDBFiles: Need to initialize and set indexedDBPath first!' @@ -817,193 +770,165 @@ async function removeIndexedDBFiles(): Promise { indexedDBPath = undefined; } -export function getReadonlyInstance(): Database { - if (!globalReadonlyInstance) { - throw new Error('getReadonlyInstance: globalReadonlyInstance not set!'); - } - - return globalReadonlyInstance; -} - -const WRITABLE_INSTANCE_MAX_WAIT = 5 * durations.MINUTE; - -export async function getWritableInstance(): Promise { - if (pausedWriteQueue) { - const { promise, resolve } = explodePromise(); - pausedWriteQueue.push(resolve); - await pTimeout(promise, WRITABLE_INSTANCE_MAX_WAIT); - } - - if (!globalWritableInstance) { - throw new Error('getWritableInstance: globalWritableInstance not set!'); - } - - return globalWritableInstance; -} - // This is okay to use for queries that: // // - Don't modify persistent tables, but create and do work in temporary // tables // - Integrity checks // -function getUnsafeWritableInstance( - reason: 'only temp table use' | 'integrity check' -): Database { - // Not actually used - void reason; - - if (!globalWritableInstance) { - throw new Error( - 'getUnsafeWritableInstance: globalWritableInstance not set!' - ); - } - - return globalWritableInstance; +function toUnsafeWritableDB( + db: ReadableDB, + _reason: 'only temp table use' | 'integrity check' +): WritableDB { + return db as unknown as WritableDB; } const IDENTITY_KEYS_TABLE = 'identityKeys'; -async function createOrUpdateIdentityKey( +function createOrUpdateIdentityKey( + db: WritableDB, data: StoredIdentityKeyType -): Promise { - return createOrUpdate(await getWritableInstance(), IDENTITY_KEYS_TABLE, data); +): void { + return createOrUpdate(db, IDENTITY_KEYS_TABLE, data); } -async function getIdentityKeyById( +function getIdentityKeyById( + db: ReadableDB, id: IdentityKeyIdType -): Promise { - return getById(getReadonlyInstance(), IDENTITY_KEYS_TABLE, id); +): StoredIdentityKeyType | undefined { + return getById(db, IDENTITY_KEYS_TABLE, id); } -async function bulkAddIdentityKeys( +function bulkAddIdentityKeys( + db: WritableDB, array: Array -): Promise { - return bulkAdd(await getWritableInstance(), IDENTITY_KEYS_TABLE, array); +): void { + return bulkAdd(db, IDENTITY_KEYS_TABLE, array); } -async function removeIdentityKeyById(id: IdentityKeyIdType): Promise { - return removeById(await getWritableInstance(), IDENTITY_KEYS_TABLE, id); +function removeIdentityKeyById(db: WritableDB, id: IdentityKeyIdType): number { + return removeById(db, IDENTITY_KEYS_TABLE, id); } -async function removeAllIdentityKeys(): Promise { - return removeAllFromTable(await getWritableInstance(), IDENTITY_KEYS_TABLE); +function removeAllIdentityKeys(db: WritableDB): number { + return removeAllFromTable(db, IDENTITY_KEYS_TABLE); } -async function getAllIdentityKeys(): Promise> { - return getAllFromTable(getReadonlyInstance(), IDENTITY_KEYS_TABLE); +function getAllIdentityKeys(db: ReadableDB): Array { + return getAllFromTable(db, IDENTITY_KEYS_TABLE); } const KYBER_PRE_KEYS_TABLE = 'kyberPreKeys'; -async function createOrUpdateKyberPreKey( +function createOrUpdateKyberPreKey( + db: WritableDB, data: StoredKyberPreKeyType -): Promise { - return createOrUpdate( - await getWritableInstance(), - KYBER_PRE_KEYS_TABLE, - data - ); +): void { + return createOrUpdate(db, KYBER_PRE_KEYS_TABLE, data); } -async function getKyberPreKeyById( +function getKyberPreKeyById( + db: ReadableDB, id: PreKeyIdType -): Promise { - return getById(getReadonlyInstance(), KYBER_PRE_KEYS_TABLE, id); +): StoredKyberPreKeyType | undefined { + return getById(db, KYBER_PRE_KEYS_TABLE, id); } -async function bulkAddKyberPreKeys( +function bulkAddKyberPreKeys( + db: WritableDB, array: Array -): Promise { - return bulkAdd(await getWritableInstance(), KYBER_PRE_KEYS_TABLE, array); +): void { + return bulkAdd(db, KYBER_PRE_KEYS_TABLE, array); } -async function removeKyberPreKeyById( +function removeKyberPreKeyById( + db: WritableDB, id: PreKeyIdType | Array -): Promise { - return removeById(await getWritableInstance(), KYBER_PRE_KEYS_TABLE, id); +): number { + return removeById(db, KYBER_PRE_KEYS_TABLE, id); } -async function removeKyberPreKeysByServiceId( +function removeKyberPreKeysByServiceId( + db: WritableDB, serviceId: ServiceIdString -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( 'DELETE FROM kyberPreKeys WHERE ourServiceId IS $serviceId;' ).run({ serviceId, }); } -async function removeAllKyberPreKeys(): Promise { - return removeAllFromTable(await getWritableInstance(), KYBER_PRE_KEYS_TABLE); +function removeAllKyberPreKeys(db: WritableDB): number { + return removeAllFromTable(db, KYBER_PRE_KEYS_TABLE); } -async function getAllKyberPreKeys(): Promise> { - return getAllFromTable(getReadonlyInstance(), KYBER_PRE_KEYS_TABLE); +function getAllKyberPreKeys(db: ReadableDB): Array { + return getAllFromTable(db, KYBER_PRE_KEYS_TABLE); } const PRE_KEYS_TABLE = 'preKeys'; -async function createOrUpdatePreKey(data: StoredPreKeyType): Promise { - return createOrUpdate(await getWritableInstance(), PRE_KEYS_TABLE, data); +function createOrUpdatePreKey(db: WritableDB, data: StoredPreKeyType): void { + return createOrUpdate(db, PRE_KEYS_TABLE, data); } -async function getPreKeyById( +function getPreKeyById( + db: ReadableDB, id: PreKeyIdType -): Promise { - return getById(getReadonlyInstance(), PRE_KEYS_TABLE, id); +): StoredPreKeyType | undefined { + return getById(db, PRE_KEYS_TABLE, id); } -async function bulkAddPreKeys(array: Array): Promise { - return bulkAdd(await getWritableInstance(), PRE_KEYS_TABLE, array); +function bulkAddPreKeys(db: WritableDB, array: Array): void { + return bulkAdd(db, PRE_KEYS_TABLE, array); } -async function removePreKeyById( +function removePreKeyById( + db: WritableDB, id: PreKeyIdType | Array -): Promise { - return removeById(await getWritableInstance(), PRE_KEYS_TABLE, id); +): number { + return removeById(db, PRE_KEYS_TABLE, id); } -async function removePreKeysByServiceId( +function removePreKeysByServiceId( + db: WritableDB, serviceId: ServiceIdString -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( 'DELETE FROM preKeys WHERE ourServiceId IS $serviceId;' ).run({ serviceId, }); } -async function removeAllPreKeys(): Promise { - return removeAllFromTable(await getWritableInstance(), PRE_KEYS_TABLE); +function removeAllPreKeys(db: WritableDB): number { + return removeAllFromTable(db, PRE_KEYS_TABLE); } -async function getAllPreKeys(): Promise> { - return getAllFromTable(getReadonlyInstance(), PRE_KEYS_TABLE); +function getAllPreKeys(db: ReadableDB): Array { + return getAllFromTable(db, PRE_KEYS_TABLE); } const SIGNED_PRE_KEYS_TABLE = 'signedPreKeys'; -async function createOrUpdateSignedPreKey( +function createOrUpdateSignedPreKey( + db: WritableDB, data: StoredSignedPreKeyType -): Promise { - return createOrUpdate( - await getWritableInstance(), - SIGNED_PRE_KEYS_TABLE, - data - ); +): void { + return createOrUpdate(db, SIGNED_PRE_KEYS_TABLE, data); } -async function getSignedPreKeyById( +function getSignedPreKeyById( + db: ReadableDB, id: SignedPreKeyIdType -): Promise { - return getById(getReadonlyInstance(), SIGNED_PRE_KEYS_TABLE, id); +): StoredSignedPreKeyType | undefined { + return getById(db, SIGNED_PRE_KEYS_TABLE, id); } -async function bulkAddSignedPreKeys( +function bulkAddSignedPreKeys( + db: WritableDB, array: Array -): Promise { - return bulkAdd(await getWritableInstance(), SIGNED_PRE_KEYS_TABLE, array); +): void { + return bulkAdd(db, SIGNED_PRE_KEYS_TABLE, array); } -async function removeSignedPreKeyById( +function removeSignedPreKeyById( + db: WritableDB, id: SignedPreKeyIdType | Array -): Promise { - return removeById(await getWritableInstance(), SIGNED_PRE_KEYS_TABLE, id); +): number { + return removeById(db, SIGNED_PRE_KEYS_TABLE, id); } -async function removeSignedPreKeysByServiceId( +function removeSignedPreKeysByServiceId( + db: WritableDB, serviceId: ServiceIdString -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( 'DELETE FROM signedPreKeys WHERE ourServiceId IS $serviceId;' ).run({ serviceId, }); } -async function removeAllSignedPreKeys(): Promise { - return removeAllFromTable(await getWritableInstance(), SIGNED_PRE_KEYS_TABLE); +function removeAllSignedPreKeys(db: WritableDB): number { + return removeAllFromTable(db, SIGNED_PRE_KEYS_TABLE); } -async function getAllSignedPreKeys(): Promise> { - const db = getReadonlyInstance(); +function getAllSignedPreKeys(db: ReadableDB): Array { const rows: JSONRows = db .prepare( ` @@ -1018,18 +943,19 @@ async function getAllSignedPreKeys(): Promise> { } const ITEMS_TABLE = 'items'; -async function createOrUpdateItem( +function createOrUpdateItem( + db: WritableDB, data: StoredItemType -): Promise { - return createOrUpdate(await getWritableInstance(), ITEMS_TABLE, data); +): void { + return createOrUpdate(db, ITEMS_TABLE, data); } -async function getItemById( +function getItemById( + db: ReadableDB, id: K -): Promise | undefined> { - return getById(getReadonlyInstance(), ITEMS_TABLE, id); +): StoredItemType | undefined { + return getById(db, ITEMS_TABLE, id); } -async function getAllItems(): Promise { - const db = getReadonlyInstance(); +function getAllItems(db: ReadableDB): StoredAllItemsType { const rows: JSONRows = db .prepare('SELECT json FROM items ORDER BY id ASC;') .all(); @@ -1046,21 +972,17 @@ async function getAllItems(): Promise { return result as unknown as StoredAllItemsType; } -async function removeItemById( +function removeItemById( + db: WritableDB, id: ItemKeyType | Array -): Promise { - return removeById(await getWritableInstance(), ITEMS_TABLE, id); +): number { + return removeById(db, ITEMS_TABLE, id); } -async function removeAllItems(): Promise { - return removeAllFromTable(await getWritableInstance(), ITEMS_TABLE); +function removeAllItems(db: WritableDB): number { + return removeAllFromTable(db, ITEMS_TABLE); } -async function createOrUpdateSenderKey(key: SenderKeyType): Promise { - const db = await getWritableInstance(); - createOrUpdateSenderKeySync(db, key); -} - -function createOrUpdateSenderKeySync(db: Database, key: SenderKeyType): void { +function createOrUpdateSenderKey(db: WritableDB, key: SenderKeyType): void { prepare( db, ` @@ -1080,39 +1002,36 @@ function createOrUpdateSenderKeySync(db: Database, key: SenderKeyType): void { ` ).run(key); } -async function getSenderKeyById( +function getSenderKeyById( + db: ReadableDB, id: SenderKeyIdType -): Promise { - const db = getReadonlyInstance(); +): SenderKeyType | undefined { const row = prepare(db, 'SELECT * FROM senderKeys WHERE id = $id').get({ id, }); return row; } -async function removeAllSenderKeys(): Promise { - const db = await getWritableInstance(); +function removeAllSenderKeys(db: WritableDB): void { prepare(db, 'DELETE FROM senderKeys').run(); } -async function getAllSenderKeys(): Promise> { - const db = getReadonlyInstance(); +function getAllSenderKeys(db: ReadableDB): Array { const rows = prepare(db, 'SELECT * FROM senderKeys').all(); return rows; } -async function removeSenderKeyById(id: SenderKeyIdType): Promise { - const db = await getWritableInstance(); +function removeSenderKeyById(db: WritableDB, id: SenderKeyIdType): void { prepare(db, 'DELETE FROM senderKeys WHERE id = $id').run({ id }); } -async function insertSentProto( +function insertSentProto( + db: WritableDB, proto: SentProtoType, options: { recipients: SentRecipientsType; messageIds: SentMessagesType; } -): Promise { - const db = await getWritableInstance(); +): number { const { recipients, messageIds } = options; // Note: we use `pluck` in this function to fetch only the first column of returned row. @@ -1204,9 +1123,7 @@ async function insertSentProto( })(); } -async function deleteSentProtosOlderThan(timestamp: number): Promise { - const db = await getWritableInstance(); - +function deleteSentProtosOlderThan(db: WritableDB, timestamp: number): void { prepare( db, ` @@ -1220,9 +1137,7 @@ async function deleteSentProtosOlderThan(timestamp: number): Promise { }); } -async function deleteSentProtoByMessageId(messageId: string): Promise { - const db = await getWritableInstance(); - +function deleteSentProtoByMessageId(db: WritableDB, messageId: string): void { prepare( db, ` @@ -1236,17 +1151,18 @@ async function deleteSentProtoByMessageId(messageId: string): Promise { }); } -async function insertProtoRecipients({ - id, - recipientServiceId, - deviceIds, -}: { - id: number; - recipientServiceId: ServiceIdString; - deviceIds: Array; -}): Promise { - const db = await getWritableInstance(); - +function insertProtoRecipients( + db: WritableDB, + { + id, + recipientServiceId, + deviceIds, + }: { + id: number; + recipientServiceId: ServiceIdString; + deviceIds: Array; + } +): void { db.transaction(() => { const statement = prepare( db, @@ -1273,13 +1189,12 @@ async function insertProtoRecipients({ })(); } -async function deleteSentProtoRecipient( +function deleteSentProtoRecipient( + db: WritableDB, options: | DeleteSentProtoRecipientOptionsType | ReadonlyArray -): Promise { - const db = await getWritableInstance(); - +): DeleteSentProtoRecipientResultType { const items = Array.isArray(options) ? options : [options]; // Note: we use `pluck` in this function to fetch only the first column of @@ -1386,21 +1301,22 @@ async function deleteSentProtoRecipient( })(); } -async function getSentProtoByRecipient({ - now, - recipientServiceId, - timestamp, -}: { - now: number; - recipientServiceId: ServiceIdString; - timestamp: number; -}): Promise { +function getSentProtoByRecipient( + db: WritableDB, + { + now, + recipientServiceId, + timestamp, + }: { + now: number; + recipientServiceId: ServiceIdString; + timestamp: number; + } +): SentProtoWithMessageIdsType | undefined { const HOUR = 1000 * 60 * 60; const oneDayAgo = now - HOUR * 24; - await deleteSentProtosOlderThan(oneDayAgo); - - const db = getReadonlyInstance(); + deleteSentProtosOlderThan(db, oneDayAgo); const row = prepare( db, @@ -1435,12 +1351,10 @@ async function getSentProtoByRecipient({ messageIds: messageIds ? messageIds.split(',') : [], }; } -async function removeAllSentProtos(): Promise { - const db = await getWritableInstance(); +function removeAllSentProtos(db: WritableDB): void { prepare(db, 'DELETE FROM sendLogPayloads;').run(); } -async function getAllSentProtos(): Promise> { - const db = getReadonlyInstance(); +function getAllSentProtos(db: ReadableDB): Array { const rows = prepare(db, 'SELECT * FROM sendLogPayloads;').all(); return rows.map(row => ({ @@ -1451,10 +1365,9 @@ async function getAllSentProtos(): Promise> { : true, })); } -async function _getAllSentProtoRecipients(): Promise< - Array -> { - const db = getReadonlyInstance(); +function _getAllSentProtoRecipients( + db: ReadableDB +): Array { const rows = prepare( db, 'SELECT * FROM sendLogRecipients;' @@ -1462,8 +1375,7 @@ async function _getAllSentProtoRecipients(): Promise< return rows; } -async function _getAllSentProtoMessageIds(): Promise> { - const db = getReadonlyInstance(); +function _getAllSentProtoMessageIds(db: ReadableDB): Array { const rows = prepare( db, 'SELECT * FROM sendLogMessageIds;' @@ -1473,7 +1385,7 @@ async function _getAllSentProtoMessageIds(): Promise> { } const SESSIONS_TABLE = 'sessions'; -function createOrUpdateSessionSync(db: Database, data: SessionType): void { +function createOrUpdateSession(db: WritableDB, data: SessionType): void { const { id, conversationId, ourServiceId, serviceId } = data; if (!id) { throw new Error( @@ -1511,59 +1423,55 @@ function createOrUpdateSessionSync(db: Database, data: SessionType): void { json: objectToJSON(data), }); } -async function createOrUpdateSession(data: SessionType): Promise { - const db = await getWritableInstance(); - return createOrUpdateSessionSync(db, data); -} -async function createOrUpdateSessions( +function createOrUpdateSessions( + db: WritableDB, array: Array -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { for (const item of array) { - assertSync(createOrUpdateSessionSync(db, item)); + createOrUpdateSession(db, item); } })(); } -async function commitDecryptResult({ - senderKeys, - sessions, - unprocessed, -}: { - senderKeys: Array; - sessions: Array; - unprocessed: Array; -}): Promise { - const db = await getWritableInstance(); - +function commitDecryptResult( + db: WritableDB, + { + senderKeys, + sessions, + unprocessed, + }: { + senderKeys: Array; + sessions: Array; + unprocessed: Array; + } +): void { db.transaction(() => { for (const item of senderKeys) { - assertSync(createOrUpdateSenderKeySync(db, item)); + createOrUpdateSenderKey(db, item); } for (const item of sessions) { - assertSync(createOrUpdateSessionSync(db, item)); + createOrUpdateSession(db, item); } for (const item of unprocessed) { - assertSync(saveUnprocessedSync(db, item)); + saveUnprocessed(db, item); } })(); } -async function bulkAddSessions(array: Array): Promise { - return bulkAdd(await getWritableInstance(), SESSIONS_TABLE, array); +function bulkAddSessions(db: WritableDB, array: Array): void { + return bulkAdd(db, SESSIONS_TABLE, array); } -async function removeSessionById(id: SessionIdType): Promise { - return removeById(await getWritableInstance(), SESSIONS_TABLE, id); +function removeSessionById(db: WritableDB, id: SessionIdType): number { + return removeById(db, SESSIONS_TABLE, id); } -async function removeSessionsByConversation( +function removeSessionsByConversation( + db: WritableDB, conversationId: string -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( ` DELETE FROM sessions @@ -1573,10 +1481,10 @@ async function removeSessionsByConversation( conversationId, }); } -async function removeSessionsByServiceId( +function removeSessionsByServiceId( + db: WritableDB, serviceId: ServiceIdString -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( ` DELETE FROM sessions @@ -1586,16 +1494,16 @@ async function removeSessionsByServiceId( serviceId, }); } -async function removeAllSessions(): Promise { - return removeAllFromTable(await getWritableInstance(), SESSIONS_TABLE); +function removeAllSessions(db: WritableDB): number { + return removeAllFromTable(db, SESSIONS_TABLE); } -async function getAllSessions(): Promise> { - return getAllFromTable(getReadonlyInstance(), SESSIONS_TABLE); +function getAllSessions(db: ReadableDB): Array { + return getAllFromTable(db, SESSIONS_TABLE); } // Conversations -async function getConversationCount(): Promise { - return getCountFromTable(getReadonlyInstance(), 'conversations'); +function getConversationCount(db: ReadableDB): number { + return getCountFromTable(db, 'conversations'); } function getConversationMembersList({ members, membersV2 }: ConversationType) { @@ -1608,7 +1516,7 @@ function getConversationMembersList({ members, membersV2 }: ConversationType) { return null; } -function saveConversationSync(db: Database, data: ConversationType): void { +function saveConversation(db: WritableDB, data: ConversationType): void { const { active_at, e164, @@ -1681,24 +1589,18 @@ function saveConversationSync(db: Database, data: ConversationType): void { }); } -async function saveConversation(data: ConversationType): Promise { - const db = await getWritableInstance(); - return saveConversationSync(db, data); -} - -async function saveConversations( +function saveConversations( + db: WritableDB, arrayOfConversations: Array -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { for (const conversation of arrayOfConversations) { - assertSync(saveConversationSync(db, conversation)); + saveConversation(db, conversation); } })(); } -function updateConversationSync(db: Database, data: ConversationType): void { +function updateConversation(db: WritableDB, data: ConversationType): void { const { id, active_at, @@ -1751,27 +1653,18 @@ function updateConversationSync(db: Database, data: ConversationType): void { }); } -async function updateConversation(data: ConversationType): Promise { - const db = await getWritableInstance(); - return updateConversationSync(db, data); -} - -async function updateConversations( +function updateConversations( + db: WritableDB, array: Array -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { for (const item of array) { - assertSync(updateConversationSync(db, item)); + updateConversation(db, item); } })(); } -function removeConversationsSync( - db: Database, - ids: ReadonlyArray -): void { +function removeConversations(db: WritableDB, ids: ReadonlyArray): void { // Our node interface doesn't seem to allow you to replace one single ? with an array db.prepare( ` @@ -1781,9 +1674,7 @@ function removeConversationsSync( ).run(ids); } -async function removeConversation(id: Array | string): Promise { - const db = await getWritableInstance(); - +function removeConversation(db: WritableDB, id: Array | string): void { if (!Array.isArray(id)) { db.prepare('DELETE FROM conversations WHERE id = $id;').run({ id, @@ -1796,18 +1687,17 @@ async function removeConversation(id: Array | string): Promise { throw new Error('removeConversation: No ids to delete!'); } - batchMultiVarQuery(db, id, ids => removeConversationsSync(db, ids)); + batchMultiVarQuery(db, id, ids => removeConversations(db, ids)); } -async function _removeAllConversations(): Promise { - const db = await getWritableInstance(); +function _removeAllConversations(db: WritableDB): void { db.prepare('DELETE from conversations;').run(); } -async function getConversationById( +function getConversationById( + db: ReadableDB, id: string -): Promise { - const db = getReadonlyInstance(); +): ConversationType | undefined { const row: { json: string } = db .prepare('SELECT json FROM conversations WHERE id = $id;') .get({ id }); @@ -1819,9 +1709,7 @@ async function getConversationById( return jsonToObject(row.json); } -function getAllConversationsSync( - db = getReadonlyInstance() -): Array { +function getAllConversations(db: ReadableDB): Array { const rows: ConversationRows = db .prepare( ` @@ -1835,12 +1723,7 @@ function getAllConversationsSync( return rows.map(row => rowToConversation(row)); } -async function getAllConversations(): Promise> { - return getAllConversationsSync(); -} - -async function getAllConversationIds(): Promise> { - const db = getReadonlyInstance(); +function getAllConversationIds(db: ReadableDB): Array { const rows: Array<{ id: string }> = db .prepare( ` @@ -1852,10 +1735,10 @@ async function getAllConversationIds(): Promise> { return rows.map(row => row.id); } -async function getAllGroupsInvolvingServiceId( +function getAllGroupsInvolvingServiceId( + db: ReadableDB, serviceId: ServiceIdString -): Promise> { - const db = getReadonlyInstance(); +): Array { const rows: ConversationRows = db .prepare( ` @@ -1873,22 +1756,25 @@ async function getAllGroupsInvolvingServiceId( return rows.map(row => rowToConversation(row)); } -async function searchMessages({ - query, - options, - conversationId, - contactServiceIdsMatchingQuery, -}: { - query: string; - options?: { limit?: number }; - conversationId?: string; - contactServiceIdsMatchingQuery?: Array; -}): Promise> { +function searchMessages( + db: ReadableDB, + { + query, + options, + conversationId, + contactServiceIdsMatchingQuery, + }: { + query: string; + options?: { limit?: number }; + conversationId?: string; + contactServiceIdsMatchingQuery?: Array; + } +): Array { const { limit = conversationId ? 100 : 500 } = options ?? {}; - const db = getUnsafeWritableInstance('only temp table use'); + const writable = toUnsafeWritableDB(db, 'only temp table use'); - const normalizedQuery = db + const normalizedQuery = writable .signalTokenize(query) .map(token => `"${token.replace(/"/g, '""')}"*`) .join(' '); @@ -1909,16 +1795,17 @@ async function searchMessages({ // the snippets and json. The benefit of this is that the `ORDER BY` and // `LIMIT` happen without virtual table and are thus covered by // `messages_searchOrder` index. - return db.transaction(() => { - db.exec( + return writable.transaction(() => { + writable.exec( ` CREATE TEMP TABLE tmp_results(rowid INTEGER PRIMARY KEY ASC); CREATE TEMP TABLE tmp_filtered_results(rowid INTEGER PRIMARY KEY ASC); ` ); - db.prepare( - ` + writable + .prepare( + ` INSERT INTO tmp_results (rowid) SELECT rowid @@ -1927,11 +1814,13 @@ async function searchMessages({ WHERE messages_fts.body MATCH $query; ` - ).run({ query: normalizedQuery }); + ) + .run({ query: normalizedQuery }); if (conversationId === undefined) { - db.prepare( - ` + writable + .prepare( + ` INSERT INTO tmp_filtered_results (rowid) SELECT tmp_results.rowid @@ -1942,10 +1831,12 @@ async function searchMessages({ ORDER BY messages.received_at DESC, messages.sent_at DESC LIMIT $limit; ` - ).run({ limit }); + ) + .run({ limit }); } else { - db.prepare( - ` + writable + .prepare( + ` INSERT INTO tmp_filtered_results (rowid) SELECT tmp_results.rowid @@ -1958,7 +1849,8 @@ async function searchMessages({ ORDER BY messages.received_at DESC, messages.sent_at DESC LIMIT $limit; ` - ).run({ conversationId, limit }); + ) + .run({ conversationId, limit }); } // The `MATCH` is necessary in order to for `snippet()` helper function to @@ -1987,14 +1879,17 @@ async function searchMessages({ if (!contactServiceIdsMatchingQuery?.length) { const [sqlQuery, params] = sql`${ftsFragment};`; - result = db.prepare(sqlQuery).all(params); + result = writable.prepare(sqlQuery).all(params); } else { - // If contactServiceIdsMatchingQuery is not empty, we due an OUTER JOIN between: - // 1) the messages that mention at least one of contactServiceIdsMatchingQuery, and + // If contactServiceIdsMatchingQuery is not empty, we due an OUTER JOIN + // between: + // 1) the messages that mention at least one of + // contactServiceIdsMatchingQuery, and // 2) the messages that match all the search terms via FTS // - // Note: this groups the results by rowid, so even if one message mentions multiple - // matching UUIDs, we only return one to be highlighted + // Note: this groups the results by rowid, so even if one message + // mentions multiple matching UUIDs, we only return one to be + // highlighted const [sqlQuery, params] = sql` SELECT messages.rowid as rowid, @@ -2027,10 +1922,10 @@ async function searchMessages({ ORDER BY received_at DESC, sent_at DESC LIMIT ${limit}; `; - result = db.prepare(sqlQuery).all(params); + result = writable.prepare(sqlQuery).all(params); } - db.exec( + writable.exec( ` DROP TABLE tmp_results; DROP TABLE tmp_filtered_results; @@ -2040,10 +1935,20 @@ async function searchMessages({ })(); } -function getMessageCountSync( - conversationId?: string, - db = getReadonlyInstance() -): number { +function getStoryCount(db: ReadableDB, conversationId: string): number { + return db + .prepare( + ` + SELECT count(1) + FROM messages + WHERE conversationId = $conversationId AND isStory = 1; + ` + ) + .pluck() + .get({ conversationId }); +} + +function getMessageCount(db: ReadableDB, conversationId?: string): number { if (conversationId === undefined) { return getCountFromTable(db, 'messages'); } @@ -2062,29 +1967,12 @@ function getMessageCountSync( return count; } -async function getStoryCount(conversationId: string): Promise { - const db = getReadonlyInstance(); - return db - .prepare( - ` - SELECT count(1) - FROM messages - WHERE conversationId = $conversationId AND isStory = 1; - ` - ) - .pluck() - .get({ conversationId }); -} - -async function getMessageCount(conversationId?: string): Promise { - return getMessageCountSync(conversationId); -} - // Note: we really only use this in 1:1 conversations, where story replies are always // shown, so this has no need to be story-aware. -function hasUserInitiatedMessages(conversationId: string): boolean { - const db = getReadonlyInstance(); - +function hasUserInitiatedMessages( + db: ReadableDB, + conversationId: string +): boolean { const exists: number = db .prepare( ` @@ -2103,16 +1991,8 @@ function hasUserInitiatedMessages(conversationId: string): boolean { return exists !== 0; } -async function getMostRecentAddressableMessages( - conversationId: string, - limit = 5 -): Promise> { - const db = getReadonlyInstance(); - return getMostRecentAddressableMessagesSync(db, conversationId, limit); -} - -export function getMostRecentAddressableMessagesSync( - db: Database, +export function getMostRecentAddressableMessages( + db: ReadableDB, conversationId: string, limit = 5 ): Array { @@ -2131,20 +2011,8 @@ export function getMostRecentAddressableMessagesSync( return rows.map(row => jsonToObject(row.json)); } -async function getMostRecentAddressableNondisappearingMessages( - conversationId: string, - limit = 5 -): Promise> { - const db = getReadonlyInstance(); - return getMostRecentAddressableNondisappearingMessagesSync( - db, - conversationId, - limit - ); -} - -export function getMostRecentAddressableNondisappearingMessagesSync( - db: Database, +export function getMostRecentAddressableNondisappearingMessages( + db: ReadableDB, conversationId: string, limit = 5 ): Array { @@ -2164,11 +2032,7 @@ export function getMostRecentAddressableNondisappearingMessagesSync( return rows.map(row => jsonToObject(row.json)); } -async function removeSyncTaskById(id: string): Promise { - const db = await getWritableInstance(); - removeSyncTaskByIdSync(db, id); -} -export function removeSyncTaskByIdSync(db: Database, id: string): void { +export function removeSyncTaskById(db: WritableDB, id: string): void { const [query, parameters] = sql` DELETE FROM syncTasks WHERE id IS ${id} @@ -2176,19 +2040,15 @@ export function removeSyncTaskByIdSync(db: Database, id: string): void { db.prepare(query).run(parameters); } -async function saveSyncTasks(tasks: Array): Promise { - const db = await getWritableInstance(); - return saveSyncTasksSync(db, tasks); -} -export function saveSyncTasksSync( - db: Database, +export function saveSyncTasks( + db: WritableDB, tasks: Array ): void { return db.transaction(() => { - tasks.forEach(task => assertSync(saveSyncTaskSync(db, task))); + tasks.forEach(task => saveSyncTask(db, task)); })(); } -export function saveSyncTaskSync(db: Database, task: SyncTaskType): void { +function saveSyncTask(db: WritableDB, task: SyncTaskType): void { const { id, attempts, createdAt, data, envelopeId, sentAt, type } = task; const [query, parameters] = sql` @@ -2213,11 +2073,7 @@ export function saveSyncTaskSync(db: Database, task: SyncTaskType): void { db.prepare(query).run(parameters); } -async function getAllSyncTasks(): Promise> { - const db = await getWritableInstance(); - return getAllSyncTasksSync(db); -} -export function getAllSyncTasksSync(db: Database): Array { +export function getAllSyncTasks(db: WritableDB): Array { return db.transaction(() => { const [selectAllQuery] = sql` SELECT * FROM syncTasks ORDER BY createdAt ASC, sentAt ASC, id ASC @@ -2253,7 +2109,7 @@ export function getAllSyncTasksSync(db: Database): Array { if (toDelete.length > 0) { log.warn(`getAllSyncTasks: Removing ${toDelete.length} expired tasks`); toDelete.forEach(task => { - assertSync(removeSyncTaskByIdSync(db, task.id)); + removeSyncTaskById(db, task.id); }); } @@ -2261,8 +2117,8 @@ export function getAllSyncTasksSync(db: Database): Array { })(); } -export function saveMessageSync( - db: Database, +export function saveMessage( + db: WritableDB, data: MessageType, options: { alreadyInTransaction?: boolean; @@ -2275,12 +2131,10 @@ export function saveMessageSync( if (!alreadyInTransaction) { return db.transaction(() => { - return assertSync( - saveMessageSync(db, data, { - ...options, - alreadyInTransaction: true, - }) - ); + return saveMessage(db, data, { + ...options, + alreadyInTransaction: true, + }); })(); } @@ -2400,7 +2254,7 @@ export function saveMessageSync( ).run(payload); if (jobToInsert) { - insertJobSync(db, jobToInsert); + insertJob(db, jobToInsert); } return id; @@ -2475,49 +2329,36 @@ export function saveMessageSync( }); if (jobToInsert) { - insertJobSync(db, jobToInsert); + insertJob(db, jobToInsert); } return toCreate.id; } -async function saveMessage( - data: MessageType, - options: { - jobToInsert?: StoredJob; - forceSave?: boolean; - alreadyInTransaction?: boolean; - ourAci: AciString; - } -): Promise { - const db = await getWritableInstance(); - return saveMessageSync(db, data, options); -} - -async function saveMessages( +function saveMessages( + db: WritableDB, arrayOfMessages: ReadonlyArray, options: { forceSave?: boolean; ourAci: AciString } -): Promise> { - const db = await getWritableInstance(); - +): Array { return db.transaction(() => { const result = new Array(); for (const message of arrayOfMessages) { result.push( - saveMessageSync(db, message, { ...options, alreadyInTransaction: true }) + saveMessage(db, message, { + ...options, + alreadyInTransaction: true, + }) ); } return result; })(); } -async function removeMessage(id: string): Promise { - const db = await getWritableInstance(); - +function removeMessage(db: WritableDB, id: string): void { db.prepare('DELETE FROM messages WHERE id = $id;').run({ id }); } -function removeMessagesSync(db: Database, ids: ReadonlyArray): void { +function removeMessagesBatch(db: WritableDB, ids: ReadonlyArray): void { db.prepare( ` DELETE FROM messages @@ -2526,18 +2367,12 @@ function removeMessagesSync(db: Database, ids: ReadonlyArray): void { ).run(ids); } -async function removeMessages(ids: ReadonlyArray): Promise { - const db = await getWritableInstance(); - batchMultiVarQuery(db, ids, batch => removeMessagesSync(db, batch)); +function removeMessages(db: WritableDB, ids: ReadonlyArray): void { + batchMultiVarQuery(db, ids, batch => removeMessagesBatch(db, batch)); } -async function getMessageById(id: string): Promise { - const db = getReadonlyInstance(); - return getMessageByIdSync(db, id); -} - -export function getMessageByIdSync( - db: Database, +export function getMessageById( + db: ReadableDB, id: string ): MessageType | undefined { const row = db @@ -2553,11 +2388,10 @@ export function getMessageByIdSync( return jsonToObject(row.json); } -async function getMessagesById( +function getMessagesById( + db: ReadableDB, messageIds: ReadonlyArray -): Promise> { - const db = getReadonlyInstance(); - +): Array { return batchMultiVarQuery( db, messageIds, @@ -2573,24 +2407,21 @@ async function getMessagesById( ); } -async function _getAllMessages(): Promise> { - const db = getReadonlyInstance(); +function _getAllMessages(db: ReadableDB): Array { const rows: JSONRows = db .prepare('SELECT json FROM messages ORDER BY id ASC;') .all(); return rows.map(row => jsonToObject(row.json)); } -async function _removeAllMessages(): Promise { - const db = await getWritableInstance(); +function _removeAllMessages(db: WritableDB): void { db.exec(` DELETE FROM messages; INSERT INTO messages_fts(messages_fts) VALUES('optimize'); `); } -async function getAllMessageIds(): Promise> { - const db = getReadonlyInstance(); +function getAllMessageIds(db: ReadableDB): Array { const rows: Array<{ id: string }> = db .prepare('SELECT id FROM messages ORDER BY id ASC;') .all(); @@ -2598,18 +2429,20 @@ async function getAllMessageIds(): Promise> { return rows.map(row => row.id); } -async function getMessageBySender({ - source, - sourceServiceId, - sourceDevice, - sent_at, -}: { - source?: string; - sourceServiceId?: ServiceIdString; - sourceDevice?: number; - sent_at: number; -}): Promise { - const db = getReadonlyInstance(); +function getMessageBySender( + db: ReadableDB, + { + source, + sourceServiceId, + sourceDevice, + sent_at, + }: { + source?: string; + sourceServiceId?: ServiceIdString; + sourceDevice?: number; + sent_at: number; + } +): MessageType | undefined { const rows: JSONRows = prepare( db, ` @@ -2658,22 +2491,24 @@ export function _storyIdPredicate( return sqlFragment`storyId IS ${storyId}`; } -async function getUnreadByConversationAndMarkRead({ - conversationId, - includeStoryReplies, - newestUnreadAt, - storyId, - readAt, - now = Date.now(), -}: { - conversationId: string; - includeStoryReplies: boolean; - newestUnreadAt: number; - storyId?: string; - readAt?: number; - now?: number; -}): Promise { - const db = await getWritableInstance(); +function getUnreadByConversationAndMarkRead( + db: WritableDB, + { + conversationId, + includeStoryReplies, + newestUnreadAt, + storyId, + readAt, + now = Date.now(), + }: { + conversationId: string; + includeStoryReplies: boolean; + newestUnreadAt: number; + storyId?: string; + readAt?: number; + now?: number; + } +): GetUnreadByConversationAndMarkReadResultType { return db.transaction(() => { const expirationStartTimestamp = Math.min(now, readAt ?? Infinity); @@ -2753,17 +2588,18 @@ async function getUnreadByConversationAndMarkRead({ })(); } -async function getUnreadReactionsAndMarkRead({ - conversationId, - newestUnreadAt, - storyId, -}: { - conversationId: string; - newestUnreadAt: number; - storyId?: string; -}): Promise> { - const db = await getWritableInstance(); - +function getUnreadReactionsAndMarkRead( + db: WritableDB, + { + conversationId, + newestUnreadAt, + storyId, + }: { + conversationId: string; + newestUnreadAt: number; + storyId?: string; + } +): Array { return db.transaction(() => { const unreadMessages: Array = db .prepare( @@ -2801,11 +2637,11 @@ async function getUnreadReactionsAndMarkRead({ })(); } -async function markReactionAsRead( +function markReactionAsRead( + db: WritableDB, targetAuthorServiceId: ServiceIdString, targetTimestamp: number -): Promise { - const db = await getWritableInstance(); +): ReactionType | undefined { return db.transaction(() => { const readReaction = db .prepare( @@ -2841,11 +2677,11 @@ async function markReactionAsRead( })(); } -async function getReactionByTimestamp( +function getReactionByTimestamp( + db: ReadableDB, fromId: string, timestamp: number -): Promise { - const db = getReadonlyInstance(); +): ReactionType | undefined { const [query, params] = sql` SELECT * FROM reactions WHERE fromId IS ${fromId} AND timestamp IS ${timestamp} @@ -2854,7 +2690,8 @@ async function getReactionByTimestamp( return db.prepare(query).get(params); } -async function addReaction( +function addReaction( + db: WritableDB, { conversationId, emoji, @@ -2866,11 +2703,9 @@ async function addReaction( timestamp, }: ReactionType, { readStatus }: { readStatus: ReactionReadStatus } -): Promise { - const db = await getWritableInstance(); - await db - .prepare( - `INSERT INTO reactions ( +): void { + db.prepare( + `INSERT INTO reactions ( conversationId, emoji, fromId, @@ -2891,54 +2726,51 @@ async function addReaction( $timestamp, $unread );` - ) - .run({ - conversationId, - emoji, - fromId, - messageId, - messageReceivedAt, - targetAuthorAci, - targetTimestamp, - timestamp, - unread: readStatus === ReactionReadStatus.Unread ? 1 : 0, - }); + ).run({ + conversationId, + emoji, + fromId, + messageId, + messageReceivedAt, + targetAuthorAci, + targetTimestamp, + timestamp, + unread: readStatus === ReactionReadStatus.Unread ? 1 : 0, + }); } -async function removeReactionFromConversation({ - emoji, - fromId, - targetAuthorServiceId, - targetTimestamp, -}: { - emoji: string; - fromId: string; - targetAuthorServiceId: ServiceIdString; - targetTimestamp: number; -}): Promise { - const db = await getWritableInstance(); - await db - .prepare( - `DELETE FROM reactions WHERE +function removeReactionFromConversation( + db: WritableDB, + { + emoji, + fromId, + targetAuthorServiceId, + targetTimestamp, + }: { + emoji: string; + fromId: string; + targetAuthorServiceId: ServiceIdString; + targetTimestamp: number; + } +): void { + db.prepare( + `DELETE FROM reactions WHERE emoji = $emoji AND fromId = $fromId AND targetAuthorAci = $targetAuthorAci AND targetTimestamp = $targetTimestamp;` - ) - .run({ - emoji, - fromId, - targetAuthorAci: targetAuthorServiceId, - targetTimestamp, - }); + ).run({ + emoji, + fromId, + targetAuthorAci: targetAuthorServiceId, + targetTimestamp, + }); } -async function _getAllReactions(): Promise> { - const db = getReadonlyInstance(); +function _getAllReactions(db: ReadableDB): Array { return db.prepare('SELECT * from reactions;').all(); } -async function _removeAllReactions(): Promise { - const db = await getWritableInstance(); +function _removeAllReactions(db: WritableDB): void { db.prepare('DELETE from reactions;').run(); } @@ -2947,17 +2779,11 @@ enum AdjacentDirection { Newer = 'Newer', } -async function getRecentStoryReplies( - storyId: string, - options?: GetRecentStoryRepliesOptionsType -): Promise> { - return getRecentStoryRepliesSync(storyId, options); -} - // This function needs to pull story replies from all conversations, because when we send // a story to one or more distribution lists, each reply to it will be in the sender's // 1:1 conversation with us. -function getRecentStoryRepliesSync( +function getRecentStoryReplies( + db: ReadableDB, storyId: string, { limit = 100, @@ -2966,7 +2792,6 @@ function getRecentStoryRepliesSync( sentAt = Number.MAX_VALUE, }: GetRecentStoryRepliesOptionsType = {} ): Array { - const db = getReadonlyInstance(); const timeFilters = { first: sqlFragment`received_at = ${receivedAt} AND sent_at < ${sentAt}`, second: sqlFragment`received_at < ${receivedAt}`, @@ -2994,7 +2819,8 @@ function getRecentStoryRepliesSync( return db.prepare(query).all(params); } -function getAdjacentMessagesByConversationSync( +function getAdjacentMessagesByConversation( + db: ReadableDB, direction: AdjacentDirection, { conversationId, @@ -3007,8 +2833,6 @@ function getAdjacentMessagesByConversationSync( storyId, }: AdjacentMessagesByConversationOptionsType ): Array { - const db = getReadonlyInstance(); - let timeFilters: { first: QueryFragment; second: QueryFragment }; let timeOrder: QueryFragment; @@ -3087,23 +2911,27 @@ function getAdjacentMessagesByConversationSync( return results; } -async function getOlderMessagesByConversation( +function getOlderMessagesByConversation( + db: ReadableDB, options: AdjacentMessagesByConversationOptionsType -): Promise> { - return getAdjacentMessagesByConversationSync( +): Array { + return getAdjacentMessagesByConversation( + db, AdjacentDirection.Older, options ); } -async function getAllStories({ - conversationId, - sourceServiceId, -}: { - conversationId?: string; - sourceServiceId?: ServiceIdString; -}): Promise { - const db = getReadonlyInstance(); +function getAllStories( + db: ReadableDB, + { + conversationId, + sourceServiceId, + }: { + conversationId?: string; + sourceServiceId?: ServiceIdString; + } +): GetAllStoriesResultType { const rows: ReadonlyArray<{ json: string; hasReplies: number; @@ -3145,15 +2973,18 @@ async function getAllStories({ })); } -async function getNewerMessagesByConversation( +function getNewerMessagesByConversation( + db: ReadableDB, options: AdjacentMessagesByConversationOptionsType -): Promise> { - return getAdjacentMessagesByConversationSync( +): Array { + return getAdjacentMessagesByConversation( + db, AdjacentDirection.Newer, options ); } function getOldestMessageForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3163,7 +2994,6 @@ function getOldestMessageForConversation( includeStoryReplies: boolean; } ): MessageMetricsType | undefined { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT received_at, sent_at, id FROM messages WHERE conversationId = ${conversationId} AND @@ -3182,6 +3012,7 @@ function getOldestMessageForConversation( return row; } function getNewestMessageForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3191,7 +3022,6 @@ function getNewestMessageForConversation( includeStoryReplies: boolean; } ): MessageMetricsType | undefined { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT received_at, sent_at, id FROM messages WHERE conversationId = ${conversationId} AND @@ -3215,12 +3045,11 @@ export type GetMessagesBetweenOptions = Readonly<{ includeStoryReplies: boolean; }>; -async function getMessagesBetween( +function getMessagesBetween( + db: ReadableDB, conversationId: string, options: GetMessagesBetweenOptions -): Promise> { - const db = getReadonlyInstance(); - +): Array { // In the future we could accept this as an option, but for now we just // use it for the story predicate. const storyId = undefined; @@ -3254,15 +3083,16 @@ async function getMessagesBetween( * is close to the set. Searching from the last selected message as a starting * point. */ -async function getNearbyMessageFromDeletedSet({ - conversationId, - lastSelectedMessage, - deletedMessageIds, - storyId, - includeStoryReplies, -}: GetNearbyMessageFromDeletedSetOptionsType): Promise { - const db = getReadonlyInstance(); - +function getNearbyMessageFromDeletedSet( + db: ReadableDB, + { + conversationId, + lastSelectedMessage, + deletedMessageIds, + storyId, + includeStoryReplies, + }: GetNearbyMessageFromDeletedSetOptionsType +): string | null { function runQuery(after: boolean) { const dir = after ? sqlFragment`ASC` : sqlFragment`DESC`; const compare = after ? sqlFragment`>` : sqlFragment`<`; @@ -3299,14 +3129,16 @@ async function getNearbyMessageFromDeletedSet({ return null; } -function getLastConversationActivity({ - conversationId, - includeStoryReplies, -}: { - conversationId: string; - includeStoryReplies: boolean; -}): MessageType | undefined { - const db = getReadonlyInstance(); +function getLastConversationActivity( + db: ReadableDB, + { + conversationId, + includeStoryReplies, + }: { + conversationId: string; + includeStoryReplies: boolean; + } +): MessageType | undefined { const row = prepare( db, ` @@ -3331,19 +3163,20 @@ function getLastConversationActivity({ return jsonToObject(row.json); } -function getLastConversationPreview({ - conversationId, - includeStoryReplies, -}: { - conversationId: string; - includeStoryReplies: boolean; -}): MessageType | undefined { +function getLastConversationPreview( + db: ReadableDB, + { + conversationId, + includeStoryReplies, + }: { + conversationId: string; + includeStoryReplies: boolean; + } +): MessageType | undefined { type Row = Readonly<{ json: string; }>; - const db = getReadonlyInstance(); - const index = includeStoryReplies ? 'messages_preview' : 'messages_preview_without_story'; @@ -3372,36 +3205,39 @@ function getLastConversationPreview({ return row ? jsonToObject(row.json) : undefined; } -async function getConversationMessageStats({ - conversationId, - includeStoryReplies, -}: { - conversationId: string; - includeStoryReplies: boolean; -}): Promise { - const db = getReadonlyInstance(); - +function getConversationMessageStats( + db: ReadableDB, + { + conversationId, + includeStoryReplies, + }: { + conversationId: string; + includeStoryReplies: boolean; + } +): ConversationMessageStatsType { return db.transaction(() => { return { - activity: getLastConversationActivity({ + activity: getLastConversationActivity(db, { conversationId, includeStoryReplies, }), - preview: getLastConversationPreview({ + preview: getLastConversationPreview(db, { conversationId, includeStoryReplies, }), - hasUserInitiatedMessages: hasUserInitiatedMessages(conversationId), + hasUserInitiatedMessages: hasUserInitiatedMessages(db, conversationId), }; })(); } -async function getLastConversationMessage({ - conversationId, -}: { - conversationId: string; -}): Promise { - const db = getReadonlyInstance(); +function getLastConversationMessage( + db: ReadableDB, + { + conversationId, + }: { + conversationId: string; + } +): MessageType | undefined { const row = db .prepare( ` @@ -3423,6 +3259,7 @@ async function getLastConversationMessage({ } function getOldestUnseenMessageForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3432,8 +3269,6 @@ function getOldestUnseenMessageForConversation( includeStoryReplies: boolean; } ): MessageMetricsType | undefined { - const db = getReadonlyInstance(); - const [query, params] = sql` SELECT received_at, sent_at, id FROM messages WHERE conversationId = ${conversationId} AND @@ -3453,24 +3288,14 @@ function getOldestUnseenMessageForConversation( return row; } -async function getOldestUnreadMentionOfMeForConversation( - conversationId: string, - options: { - storyId?: string; - includeStoryReplies: boolean; - } -): Promise { - return getOldestUnreadMentionOfMeForConversationSync(conversationId, options); -} - -export function getOldestUnreadMentionOfMeForConversationSync( +function getOldestUnreadMentionOfMeForConversation( + db: ReadableDB, conversationId: string, options: { storyId?: string; includeStoryReplies: boolean; } ): MessageMetricsType | undefined { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT received_at, sent_at, id FROM messages WHERE conversationId = ${conversationId} AND @@ -3485,16 +3310,8 @@ export function getOldestUnreadMentionOfMeForConversationSync( return db.prepare(query).get(params); } -async function getTotalUnreadForConversation( - conversationId: string, - options: { - storyId: string | undefined; - includeStoryReplies: boolean; - } -): Promise { - return getTotalUnreadForConversationSync(conversationId, options); -} -function getTotalUnreadForConversationSync( +function getTotalUnreadForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3504,7 +3321,6 @@ function getTotalUnreadForConversationSync( includeStoryReplies: boolean; } ): number { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT count(1) FROM messages @@ -3518,16 +3334,8 @@ function getTotalUnreadForConversationSync( return row; } -async function getTotalUnreadMentionsOfMeForConversation( - conversationId: string, - options: { - storyId?: string; - includeStoryReplies: boolean; - } -): Promise { - return getTotalUnreadMentionsOfMeForConversationSync(conversationId, options); -} -function getTotalUnreadMentionsOfMeForConversationSync( +function getTotalUnreadMentionsOfMeForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3537,7 +3345,6 @@ function getTotalUnreadMentionsOfMeForConversationSync( includeStoryReplies: boolean; } ): number { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT count(1) FROM messages @@ -3552,7 +3359,8 @@ function getTotalUnreadMentionsOfMeForConversationSync( return row; } -function getTotalUnseenForConversationSync( +function getTotalUnseenForConversation( + db: ReadableDB, conversationId: string, { storyId, @@ -3562,7 +3370,6 @@ function getTotalUnseenForConversationSync( includeStoryReplies: boolean; } ): number { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT count(1) FROM messages @@ -3577,26 +3384,24 @@ function getTotalUnseenForConversationSync( return row; } -async function getMessageMetricsForConversation(options: { - conversationId: string; - storyId?: string; - includeStoryReplies: boolean; -}): Promise { - return getMessageMetricsForConversationSync(options); -} -function getMessageMetricsForConversationSync(options: { - conversationId: string; - storyId?: string; - includeStoryReplies: boolean; -}): ConversationMetricsType { +function getMessageMetricsForConversation( + db: ReadableDB, + options: { + conversationId: string; + storyId?: string; + includeStoryReplies: boolean; + } +): ConversationMetricsType { const { conversationId } = options; - const oldest = getOldestMessageForConversation(conversationId, options); - const newest = getNewestMessageForConversation(conversationId, options); + const oldest = getOldestMessageForConversation(db, conversationId, options); + const newest = getNewestMessageForConversation(db, conversationId, options); const oldestUnseen = getOldestUnseenMessageForConversation( + db, conversationId, options ); - const totalUnseen = getTotalUnseenForConversationSync( + const totalUnseen = getTotalUnseenForConversation( + db, conversationId, options ); @@ -3611,40 +3416,38 @@ function getMessageMetricsForConversationSync(options: { }; } -async function getConversationRangeCenteredOnMessage( +function getConversationRangeCenteredOnMessage( + db: ReadableDB, options: AdjacentMessagesByConversationOptionsType -): Promise< - GetConversationRangeCenteredOnMessageResultType -> { - const db = getReadonlyInstance(); - +): GetConversationRangeCenteredOnMessageResultType { return db.transaction(() => { return { - older: getAdjacentMessagesByConversationSync( + older: getAdjacentMessagesByConversation( + db, AdjacentDirection.Older, options ), - newer: getAdjacentMessagesByConversationSync( + newer: getAdjacentMessagesByConversation( + db, AdjacentDirection.Newer, options ), - metrics: getMessageMetricsForConversationSync(options), + metrics: getMessageMetricsForConversation(db, options), }; })(); } -async function getAllCallHistory(): Promise> { - const db = getReadonlyInstance(); +function getAllCallHistory(db: ReadableDB): ReadonlyArray { const [query] = sql` SELECT * FROM callsHistory; `; return db.prepare(query).all(); } -async function clearCallHistory( +function clearCallHistory( + db: WritableDB, target: CallLogEventTarget -): Promise> { - const db = await getWritableInstance(); +): ReadonlyArray { return db.transaction(() => { const timestamp = getMessageTimestampForCallLogEventTarget(db, target); @@ -3703,8 +3506,7 @@ async function clearCallHistory( })(); } -async function markCallHistoryDeleted(callId: string): Promise { - const db = await getWritableInstance(); +function markCallHistoryDeleted(db: WritableDB, callId: string): void { const [query, params] = sql` UPDATE callsHistory SET @@ -3716,11 +3518,9 @@ async function markCallHistoryDeleted(callId: string): Promise { db.prepare(query).run(params); } -async function cleanupCallHistoryMessages(): Promise { - const db = await getWritableInstance(); - return db - .transaction(() => { - const [query, params] = sql` +function cleanupCallHistoryMessages(db: WritableDB): void { + return db.transaction(() => { + const [query, params] = sql` DELETE FROM messages WHERE messages.id IN ( SELECT messages.id FROM messages @@ -3729,16 +3529,17 @@ async function cleanupCallHistoryMessages(): Promise { AND callsHistory.status IS ${CALL_STATUS_DELETED} ) `; - db.prepare(query).run(params); - }) - .immediate(); + db.prepare(query).run(params); + })(); } -async function getCallHistoryMessageByCallId(options: { - conversationId: string; - callId: string; -}): Promise { - const db = getReadonlyInstance(); +function getCallHistoryMessageByCallId( + db: ReadableDB, + options: { + conversationId: string; + callId: string; + } +): MessageType | undefined { const [query, params] = sql` SELECT json FROM messages @@ -3753,12 +3554,11 @@ async function getCallHistoryMessageByCallId(options: { return jsonToObject(row.json); } -async function getCallHistory( +function getCallHistory( + db: ReadableDB, callId: string, peerId: ServiceIdString | string -): Promise { - const db = getReadonlyInstance(); - +): CallHistoryDetails | undefined { const [query, params] = sql` SELECT * FROM callsHistory WHERE callId IS ${callId} @@ -3783,8 +3583,7 @@ const CALL_STATUS_INCOMING = sqlConstant(CallDirection.Incoming); const CALL_MODE_ADHOC = sqlConstant(CallMode.Adhoc); const FOUR_HOURS_IN_MS = sqlConstant(4 * 60 * 60 * 1000); -async function getCallHistoryUnreadCount(): Promise { - const db = getReadonlyInstance(); +function getCallHistoryUnreadCount(db: ReadableDB): number { const [query, params] = sql` SELECT count(*) FROM messages LEFT JOIN callsHistory ON callsHistory.callId = messages.callId @@ -3797,9 +3596,7 @@ async function getCallHistoryUnreadCount(): Promise { return row; } -async function markCallHistoryRead(callId: string): Promise { - const db = await getWritableInstance(); - +function markCallHistoryRead(db: WritableDB, callId: string): void { const jsonPatch = JSON.stringify({ seenStatus: SeenStatus.Seen, }); @@ -3816,7 +3613,7 @@ async function markCallHistoryRead(callId: string): Promise { } function getMessageTimestampForCallLogEventTarget( - db: Database, + db: ReadableDB, target: CallLogEventTarget ): number { let { callId, peerId } = target; @@ -3867,10 +3664,10 @@ function getMessageTimestampForCallLogEventTarget( return messageTimestamp ?? target.timestamp; } -export function markAllCallHistoryReadSync( - db: Database, +export function markAllCallHistoryRead( + db: WritableDB, target: CallLogEventTarget, - inConversation: boolean + inConversation = false ): void { if (inConversation) { strictAssert(target.peerId, 'peerId is required'); @@ -3902,23 +3699,16 @@ export function markAllCallHistoryReadSync( })(); } -async function markAllCallHistoryRead( +function markAllCallHistoryReadInConversation( + db: WritableDB, target: CallLogEventTarget -): Promise { - const db = await getWritableInstance(); - markAllCallHistoryReadSync(db, target, false); -} - -async function markAllCallHistoryReadInConversation( - target: CallLogEventTarget -): Promise { +): void { strictAssert(target.peerId, 'peerId is required'); - const db = await getWritableInstance(); - markAllCallHistoryReadSync(db, target, true); + markAllCallHistoryRead(db, target, true); } -function getCallHistoryGroupDataSync( - db: Database, +function getCallHistoryGroupData( + db: WritableDB, isCount: boolean, filter: CallHistoryFilter, pagination: CallHistoryPagination @@ -3974,7 +3764,8 @@ function getCallHistoryGroupDataSync( } } - // peerId can be a conversation id (legacy), a serviceId, groupId, or call link roomId + // peerId can be a conversation id (legacy), a serviceId, groupId, or call + // link roomId const innerJoin = isUsingTempTable ? sqlFragment` INNER JOIN temp_callHistory_filtered_peers ON ( @@ -3997,13 +3788,14 @@ function getCallHistoryGroupDataSync( const offsetLimit = limit > 0 ? sqlFragment`LIMIT ${limit} OFFSET ${offset}` : sqlFragment``; - // COUNT(*) OVER(): As a result of GROUP BY in the query (to limit adhoc call history - // to the single latest call), COUNT(*) changes to counting each group's counts rather - // than the total number of rows. Example: Say we have 2 group calls (A and B) and - // 10 adhoc calls on a single link. COUNT(*) ... GROUP BY returns [1, 1, 10] - // corresponding with callId A, callId B, adhoc peerId (the GROUP conditions). - // However we want COUNT(*) to do the normal thing and return total rows - // (so in the example above we want 3). COUNT(*) OVER achieves this. + // COUNT(*) OVER(): As a result of GROUP BY in the query (to limit adhoc + // call history to the single latest call), COUNT(*) changes to counting + // each group's counts rather than the total number of rows. Example: Say + // we have 2 group calls (A and B) and 10 adhoc calls on a single link. + // COUNT(*) ... GROUP BY returns [1, 1, 10] corresponding with callId A, + // callId B, adhoc peerId (the GROUP conditions). However we want COUNT(*) + // to do the normal thing and return total rows (so in the example above + // we want 3). COUNT(*) OVER achieves this. const projection = isCount ? sqlFragment`COUNT(*) OVER() AS count` : sqlFragment`peerId, ringerId, mode, type, direction, status, timestamp, possibleChildren, inPeriod`; @@ -4131,13 +3923,14 @@ function getCallHistoryGroupDataSync( const countSchema = z.number().int().nonnegative(); -async function getCallHistoryGroupsCount( +function getCallHistoryGroupsCount( + db: ReadableDB, filter: CallHistoryFilter -): Promise { - // getCallHistoryGroupDataSync creates a temporary table and thus requires +): number { + // getCallHistoryGroupData creates a temporary table and thus requires // write access. - const db = getUnsafeWritableInstance('only temp table use'); - const result = getCallHistoryGroupDataSync(db, true, filter, { + const writable = toUnsafeWritableDB(db, 'only temp table use'); + const result = getCallHistoryGroupData(writable, true, filter, { limit: 0, offset: 0, }); @@ -4163,15 +3956,16 @@ const possibleChildrenSchema = z.array( }) ); -async function getCallHistoryGroups( +function getCallHistoryGroups( + db: ReadableDB, filter: CallHistoryFilter, pagination: CallHistoryPagination -): Promise> { - // getCallHistoryGroupDataSync creates a temporary table and thus requires +): Array { + // getCallHistoryGroupData creates a temporary table and thus requires // write access. - const db = getUnsafeWritableInstance('only temp table use'); + const writable = toUnsafeWritableDB(db, 'only temp table use'); const groupsData = groupsDataSchema.parse( - getCallHistoryGroupDataSync(db, false, filter, pagination) + getCallHistoryGroupData(writable, false, filter, pagination) ); const taken = new Set(); @@ -4207,8 +4001,8 @@ async function getCallHistoryGroups( .reverse(); } -export function saveCallHistorySync( - db: Database, +function saveCallHistory( + db: WritableDB, callHistory: CallHistoryDetails ): void { const [insertQuery, insertParams] = sql` @@ -4236,17 +4030,11 @@ export function saveCallHistorySync( db.prepare(insertQuery).run(insertParams); } -async function saveCallHistory(callHistory: CallHistoryDetails): Promise { - const db = await getWritableInstance(); - saveCallHistorySync(db, callHistory); -} - -async function hasGroupCallHistoryMessage( +function hasGroupCallHistoryMessage( + db: ReadableDB, conversationId: string, eraId: string -): Promise { - const db = getReadonlyInstance(); - +): boolean { const exists: number = db .prepare( ` @@ -4268,7 +4056,10 @@ async function hasGroupCallHistoryMessage( return exists !== 0; } -function _markCallHistoryMissed(db: Database, callIds: ReadonlyArray) { +function _markCallHistoryMissed( + db: WritableDB, + callIds: ReadonlyArray +) { batchMultiVarQuery(db, callIds, batch => { const [updateQuery, updateParams] = sql` UPDATE callsHistory @@ -4279,10 +4070,10 @@ function _markCallHistoryMissed(db: Database, callIds: ReadonlyArray) { }); } -async function markCallHistoryMissed( +function markCallHistoryMissed( + db: WritableDB, callIds: ReadonlyArray -): Promise { - const db = await getWritableInstance(); +): void { return db.transaction(() => _markCallHistoryMissed(db, callIds))(); } @@ -4290,10 +4081,9 @@ export type MaybeStaleCallHistory = Readonly< Pick >; -async function getRecentStaleRingsAndMarkOlderMissed(): Promise< - ReadonlyArray -> { - const db = await getWritableInstance(); +function getRecentStaleRingsAndMarkOlderMissed( + db: WritableDB +): ReadonlyArray { return db.transaction(() => { const [selectQuery, selectParams] = sql` SELECT callId, peerId FROM callsHistory @@ -4327,12 +4117,11 @@ async function getRecentStaleRingsAndMarkOlderMissed(): Promise< })(); } -async function migrateConversationMessages( +export function migrateConversationMessages( + db: WritableDB, obsoleteId: string, currentId: string -): Promise { - const db = await getWritableInstance(); - +): void { const PAGE_SIZE = 1000; const getPage = db.prepare(` @@ -4411,11 +4200,10 @@ async function migrateConversationMessages( })(); } -async function getMessagesBySentAt( +function getMessagesBySentAt( + db: ReadableDB, sentAt: number -): Promise> { - const db = getReadonlyInstance(); - +): Array { const [query, params] = sql` SELECT messages.json, received_at, sent_at FROM edited_messages INNER JOIN messages ON @@ -4432,8 +4220,7 @@ async function getMessagesBySentAt( return rows.map(row => jsonToObject(row.json)); } -async function getExpiredMessages(): Promise> { - const db = getReadonlyInstance(); +function getExpiredMessages(db: ReadableDB): Array { const now = Date.now(); const rows: JSONRows = db @@ -4449,10 +4236,9 @@ async function getExpiredMessages(): Promise> { return rows.map(row => jsonToObject(row.json)); } -async function getMessagesUnexpectedlyMissingExpirationStartTimestamp(): Promise< - Array -> { - const db = getReadonlyInstance(); +function getMessagesUnexpectedlyMissingExpirationStartTimestamp( + db: ReadableDB +): Array { const rows: JSONRows = db .prepare( ` @@ -4476,9 +4262,7 @@ async function getMessagesUnexpectedlyMissingExpirationStartTimestamp(): Promise return rows.map(row => jsonToObject(row.json)); } -async function getSoonestMessageExpiry(): Promise { - const db = getReadonlyInstance(); - +function getSoonestMessageExpiry(db: ReadableDB): undefined | number { // Note: we use `pluck` to only get the first column. const result: null | number = db .prepare( @@ -4497,10 +4281,9 @@ async function getSoonestMessageExpiry(): Promise { return result || undefined; } -async function getNextTapToViewMessageTimestampToAgeOut(): Promise< - undefined | number -> { - const db = getReadonlyInstance(); +function getNextTapToViewMessageTimestampToAgeOut( + db: ReadableDB +): undefined | number { const row = db .prepare( ` @@ -4523,8 +4306,7 @@ async function getNextTapToViewMessageTimestampToAgeOut(): Promise< return isNormalNumber(result) ? result : undefined; } -async function getTapToViewMessagesNeedingErase(): Promise> { - const db = getReadonlyInstance(); +function getTapToViewMessagesNeedingErase(db: ReadableDB): Array { const THIRTY_DAYS_AGO = Date.now() - 30 * 24 * 60 * 60 * 1000; const rows: JSONRows = db @@ -4548,7 +4330,7 @@ async function getTapToViewMessagesNeedingErase(): Promise> { const MAX_UNPROCESSED_ATTEMPTS = 10; -function saveUnprocessedSync(db: Database, data: UnprocessedType): string { +function saveUnprocessed(db: WritableDB, data: UnprocessedType): string { const { id, timestamp, @@ -4566,7 +4348,7 @@ function saveUnprocessedSync(db: Database, data: UnprocessedType): string { story, } = data; if (!id) { - throw new Error('saveUnprocessedSync: id was falsey'); + throw new Error('saveUnprocessed: id was falsey'); } prepare( @@ -4624,8 +4406,8 @@ function saveUnprocessedSync(db: Database, data: UnprocessedType): string { return id; } -function updateUnprocessedWithDataSync( - db: Database, +function updateUnprocessedWithData( + db: WritableDB, id: string, data: UnprocessedUpdateType ): void { @@ -4661,30 +4443,21 @@ function updateUnprocessedWithDataSync( }); } -async function updateUnprocessedWithData( - id: string, - data: UnprocessedUpdateType -): Promise { - const db = await getWritableInstance(); - return updateUnprocessedWithDataSync(db, id, data); -} - -async function updateUnprocessedsWithData( +function updateUnprocessedsWithData( + db: WritableDB, arrayOfUnprocessed: Array<{ id: string; data: UnprocessedUpdateType }> -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { for (const { id, data } of arrayOfUnprocessed) { - assertSync(updateUnprocessedWithDataSync(db, id, data)); + updateUnprocessedWithData(db, id, data); } })(); } -async function getUnprocessedById( +function getUnprocessedById( + db: ReadableDB, id: string -): Promise { - const db = getReadonlyInstance(); +): UnprocessedType | undefined { const row = db .prepare('SELECT * FROM unprocessed WHERE id = $id;') .get({ @@ -4698,14 +4471,12 @@ async function getUnprocessedById( }; } -async function getUnprocessedCount(): Promise { - return getCountFromTable(getReadonlyInstance(), 'unprocessed'); +function getUnprocessedCount(db: ReadableDB): number { + return getCountFromTable(db, 'unprocessed'); } -async function getAllUnprocessedIds(): Promise> { +function getAllUnprocessedIds(db: WritableDB): Array { log.info('getAllUnprocessedIds'); - const db = await getWritableInstance(); - return db.transaction(() => { // cleanup first const { changes: deletedStaleCount } = db @@ -4750,13 +4521,12 @@ async function getAllUnprocessedIds(): Promise> { })(); } -async function getUnprocessedByIdsAndIncrementAttempts( +function getUnprocessedByIdsAndIncrementAttempts( + db: WritableDB, ids: ReadonlyArray -): Promise> { +): Array { log.info('getUnprocessedByIdsAndIncrementAttempts', { totalIds: ids.length }); - const db = await getWritableInstance(); - batchMultiVarQuery(db, ids, batch => { return db .prepare( @@ -4788,11 +4558,8 @@ async function getUnprocessedByIdsAndIncrementAttempts( }); } -function removeUnprocessedsSync( - db: Database, - ids: ReadonlyArray -): void { - log.info('removeUnprocessedsSync', { totalIds: ids.length }); +function removeUnprocesseds(db: WritableDB, ids: ReadonlyArray): void { + log.info('removeUnprocesseds', { totalIds: ids.length }); db.prepare( ` DELETE FROM unprocessed @@ -4801,7 +4568,7 @@ function removeUnprocessedsSync( ).run(ids); } -function removeUnprocessedSync(db: Database, id: string | Array): void { +function removeUnprocessed(db: WritableDB, id: string | Array): void { log.info('removeUnprocessedSync', { id }); if (!Array.isArray(id)) { prepare(db, 'DELETE FROM unprocessed WHERE id = $id;').run({ id }); @@ -4815,31 +4582,22 @@ function removeUnprocessedSync(db: Database, id: string | Array): void { return; } - assertSync( - batchMultiVarQuery(db, id, batch => removeUnprocessedsSync(db, batch)) - ); + batchMultiVarQuery(db, id, batch => removeUnprocesseds(db, batch)); } -async function removeUnprocessed(id: string | Array): Promise { - const db = await getWritableInstance(); - - removeUnprocessedSync(db, id); -} - -async function removeAllUnprocessed(): Promise { - const db = await getWritableInstance(); +function removeAllUnprocessed(db: WritableDB): void { db.prepare('DELETE FROM unprocessed;').run(); } // Attachment Downloads function getAttachmentDownloadJob( + db: ReadableDB, job: Pick< AttachmentDownloadJobType, 'messageId' | 'attachmentType' | 'digest' > ): AttachmentDownloadJobType { - const db = getReadonlyInstance(); const [query, params] = sql` SELECT * FROM attachment_downloads WHERE @@ -4853,19 +4611,20 @@ function getAttachmentDownloadJob( return db.prepare(query).get(params); } -async function getNextAttachmentDownloadJobs({ - limit = 3, - prioritizeMessageIds, - timestamp = Date.now(), - maxLastAttemptForPrioritizedMessages, -}: { - limit: number; - prioritizeMessageIds?: Array; - timestamp?: number; - maxLastAttemptForPrioritizedMessages?: number; -}): Promise> { - const db = await getWritableInstance(); - +function getNextAttachmentDownloadJobs( + db: WritableDB, + { + limit = 3, + prioritizeMessageIds, + timestamp = Date.now(), + maxLastAttemptForPrioritizedMessages, + }: { + limit: number; + prioritizeMessageIds?: Array; + timestamp?: number; + maxLastAttemptForPrioritizedMessages?: number; + } +): Array { let priorityJobs = []; // First, try to get jobs for prioritized messages (e.g. those currently user-visible) @@ -4924,13 +4683,13 @@ async function getNextAttachmentDownloadJobs({ `getNextAttachmentDownloadJobs: Error with job for message ${row.messageId}, deleting.` ); - removeAttachmentDownloadJobSync(db, row); + removeAttachmentDownloadJob(db, row); throw new Error(error); } }); } catch (error) { if ('message' in error && error.message === INNER_ERROR) { - return getNextAttachmentDownloadJobs({ + return getNextAttachmentDownloadJobs(db, { limit, prioritizeMessageIds, timestamp, @@ -4941,11 +4700,10 @@ async function getNextAttachmentDownloadJobs({ } } -async function saveAttachmentDownloadJob( +function saveAttachmentDownloadJob( + db: WritableDB, job: AttachmentDownloadJobType -): Promise { - const db = await getWritableInstance(); - +): void { const [query, params] = sql` INSERT OR REPLACE INTO attachment_downloads ( messageId, @@ -4978,8 +4736,7 @@ async function saveAttachmentDownloadJob( db.prepare(query).run(params); } -async function resetAttachmentDownloadActive(): Promise { - const db = await getWritableInstance(); +function resetAttachmentDownloadActive(db: WritableDB): void { db.prepare( ` UPDATE attachment_downloads @@ -4989,8 +4746,8 @@ async function resetAttachmentDownloadActive(): Promise { ).run(); } -function removeAttachmentDownloadJobSync( - db: Database, +function removeAttachmentDownloadJob( + db: WritableDB, job: AttachmentDownloadJobType ): void { const [query, params] = sql` @@ -5006,22 +4763,13 @@ function removeAttachmentDownloadJobSync( db.prepare(query).run(params); } -async function removeAttachmentDownloadJob( - job: AttachmentDownloadJobType -): Promise { - const db = await getWritableInstance(); - return removeAttachmentDownloadJobSync(db, job); -} - // Backup Attachments -async function clearAllAttachmentBackupJobs(): Promise { - const db = await getWritableInstance(); +function clearAllAttachmentBackupJobs(db: WritableDB): void { db.prepare('DELETE FROM attachment_backup_jobs;').run(); } -async function markAllAttachmentBackupJobsInactive(): Promise { - const db = await getWritableInstance(); +function markAllAttachmentBackupJobsInactive(db: WritableDB): void { db.prepare( ` UPDATE attachment_backup_jobs @@ -5030,11 +4778,10 @@ async function markAllAttachmentBackupJobsInactive(): Promise { ).run(); } -async function saveAttachmentBackupJob( +function saveAttachmentBackupJob( + db: WritableDB, job: AttachmentBackupJobType -): Promise { - const db = await getWritableInstance(); - +): void { const [query, params] = sql` INSERT OR REPLACE INTO attachment_backup_jobs ( active, @@ -5059,15 +4806,16 @@ async function saveAttachmentBackupJob( db.prepare(query).run(params); } -async function getNextAttachmentBackupJobs({ - limit, - timestamp = Date.now(), -}: { - limit: number; - timestamp?: number; -}): Promise> { - const db = await getWritableInstance(); - +function getNextAttachmentBackupJobs( + db: WritableDB, + { + limit, + timestamp = Date.now(), + }: { + limit: number; + timestamp?: number; + } +): Array { const [query, params] = sql` SELECT * FROM attachment_backup_jobs WHERE @@ -5093,7 +4841,7 @@ async function getNextAttachmentBackupJobs({ `getNextAttachmentBackupJobs: invalid data, removing. mediaName: ${redactedMediaName}`, Errors.toLogFormat(parseResult.error) ); - removeAttachmentBackupJobSync(db, { mediaName: row.mediaName }); + removeAttachmentBackupJob(db, { mediaName: row.mediaName }); return null; } return parseResult.data; @@ -5101,68 +4849,53 @@ async function getNextAttachmentBackupJobs({ .filter(isNotNil); } -async function removeAttachmentBackupJob( - job: Pick -): Promise { - const db = await getWritableInstance(); - return removeAttachmentBackupJobSync(db, job); -} - -function removeAttachmentBackupJobSync( - db: Database, +function removeAttachmentBackupJob( + db: WritableDB, job: Pick ): void { const [query, params] = sql` - DELETE FROM attachment_backup_jobs - WHERE - mediaName = ${job.mediaName}; -`; - - db.prepare(query).run(params); -} - -// Attachments on backup CDN -async function clearAllBackupCdnObjectMetadata(): Promise { - const db = await getWritableInstance(); - db.prepare('DELETE FROM backup_cdn_object_metadata;').run(); -} - -function saveBackupCdnObjectMetadataSync( - db: Database, - storedMediaObject: BackupCdnMediaObjectType -) { - const { mediaId, cdnNumber, sizeOnBackupCdn } = storedMediaObject; - const [query, params] = sql` - INSERT OR REPLACE INTO backup_cdn_object_metadata - ( - mediaId, - cdnNumber, - sizeOnBackupCdn - ) VALUES ( - ${mediaId}, - ${cdnNumber}, - ${sizeOnBackupCdn} - ); + DELETE FROM attachment_backup_jobs + WHERE + mediaName = ${job.mediaName}; `; db.prepare(query).run(params); } -async function saveBackupCdnObjectMetadata( +// Attachments on backup CDN +function clearAllBackupCdnObjectMetadata(db: WritableDB): void { + db.prepare('DELETE FROM backup_cdn_object_metadata;').run(); +} + +function saveBackupCdnObjectMetadata( + db: WritableDB, storedMediaObjects: Array -): Promise { - const db = await getWritableInstance(); +): void { db.transaction(() => { for (const obj of storedMediaObjects) { - saveBackupCdnObjectMetadataSync(db, obj); + const { mediaId, cdnNumber, sizeOnBackupCdn } = obj; + const [query, params] = sql` + INSERT OR REPLACE INTO backup_cdn_object_metadata + ( + mediaId, + cdnNumber, + sizeOnBackupCdn + ) VALUES ( + ${mediaId}, + ${cdnNumber}, + ${sizeOnBackupCdn} + ); + `; + + db.prepare(query).run(params); } })(); } -async function getBackupCdnObjectMetadata( +function getBackupCdnObjectMetadata( + db: ReadableDB, mediaId: string -): Promise { - const db = getReadonlyInstance(); +): BackupCdnMediaObjectType | undefined { const [ query, params, @@ -5173,8 +4906,10 @@ async function getBackupCdnObjectMetadata( // Stickers -async function createOrUpdateStickerPack(pack: StickerPackType): Promise { - const db = await getWritableInstance(); +function createOrUpdateStickerPack( + db: WritableDB, + pack: StickerPackType +): void { const { attemptedStatus, author, @@ -5312,8 +5047,8 @@ async function createOrUpdateStickerPack(pack: StickerPackType): Promise { ` ).run(payload); } -function updateStickerPackStatusSync( - db: Database, +function updateStickerPackStatus( + db: WritableDB, id: string, status: StickerPackStatusType, options?: { timestamp: number } @@ -5333,24 +5068,17 @@ function updateStickerPackStatusSync( installedAt, }); } -async function updateStickerPackStatus( - id: string, - status: StickerPackStatusType, - options?: { timestamp: number } -): Promise { - const db = await getWritableInstance(); - return updateStickerPackStatusSync(db, id, status, options); -} -async function updateStickerPackInfo({ - id, - storageID, - storageVersion, - storageUnknownFields, - storageNeedsSync, - uninstalledAt, -}: StickerPackInfoType): Promise { - const db = await getWritableInstance(); - +function updateStickerPackInfo( + db: WritableDB, + { + id, + storageID, + storageVersion, + storageUnknownFields, + storageNeedsSync, + uninstalledAt, + }: StickerPackInfoType +): void { if (uninstalledAt) { db.prepare( ` @@ -5389,9 +5117,7 @@ async function updateStickerPackInfo({ }); } } -async function clearAllErrorStickerPackAttempts(): Promise { - const db = await getWritableInstance(); - +function clearAllErrorStickerPackAttempts(db: WritableDB): void { db.prepare( ` UPDATE sticker_packs @@ -5400,7 +5126,7 @@ async function clearAllErrorStickerPackAttempts(): Promise { ` ).run(); } -function createOrUpdateStickerSync(db: Database, sticker: StickerType): void { +function createOrUpdateSticker(db: WritableDB, sticker: StickerType): void { const { emoji, height, @@ -5468,26 +5194,22 @@ function createOrUpdateStickerSync(db: Database, sticker: StickerType): void { size: size || null, }); } -async function createOrUpdateSticker(sticker: StickerType): Promise { - const db = await getWritableInstance(); - return createOrUpdateStickerSync(db, sticker); -} -async function createOrUpdateStickers( +function createOrUpdateStickers( + db: WritableDB, stickers: ReadonlyArray -): Promise { - const db = await getWritableInstance(); +): void { db.transaction(() => { for (const sticker of stickers) { - createOrUpdateStickerSync(db, sticker); + createOrUpdateSticker(db, sticker); } })(); } -async function updateStickerLastUsed( +function updateStickerLastUsed( + db: WritableDB, packId: string, stickerId: number, lastUsed: number -): Promise { - const db = await getWritableInstance(); +): void { db.prepare( ` UPDATE stickers @@ -5510,12 +5232,11 @@ async function updateStickerLastUsed( lastUsed, }); } -async function addStickerPackReference( +function addStickerPackReference( + db: WritableDB, messageId: string, packId: string -): Promise { - const db = await getWritableInstance(); - +): void { if (!messageId) { throw new Error( 'addStickerPackReference: Provided data did not have a truthy messageId' @@ -5542,12 +5263,11 @@ async function addStickerPackReference( packId, }); } -async function deleteStickerPackReference( +function deleteStickerPackReference( + db: WritableDB, messageId: string, packId: string -): Promise | undefined> { - const db = await getWritableInstance(); - +): ReadonlyArray | undefined { if (!messageId) { throw new Error( 'addStickerPackReference: Provided data did not have a truthy messageId' @@ -5559,131 +5279,123 @@ async function deleteStickerPackReference( ); } - return db - .transaction(() => { - // We use an immediate transaction here to immediately acquire an exclusive lock, - // which would normally only happen when we did our first write. + return db.transaction(() => { + // We use an immediate transaction here to immediately acquire an exclusive lock, + // which would normally only happen when we did our first write. - // We need this to ensure that our five queries are all atomic, with no - // other changes happening while we do it: - // 1. Delete our target messageId/packId references - // 2. Check the number of references still pointing at packId - // 3. If that number is zero, get pack from sticker_packs database - // 4. If it's not installed, then grab all of its sticker paths - // 5. If it's not installed, then sticker pack (which cascades to all - // stickers and references) - db.prepare( - ` + // We need this to ensure that our five queries are all atomic, with no + // other changes happening while we do it: + // 1. Delete our target messageId/packId references + // 2. Check the number of references still pointing at packId + // 3. If that number is zero, get pack from sticker_packs database + // 4. If it's not installed, then grab all of its sticker paths + // 5. If it's not installed, then sticker pack (which cascades to all + // stickers and references) + db.prepare( + ` DELETE FROM sticker_references WHERE messageId = $messageId AND packId = $packId; ` - ).run({ - messageId, - packId, - }); + ).run({ + messageId, + packId, + }); - const count = db - .prepare( - ` + const count = db + .prepare( + ` SELECT count(1) FROM sticker_references WHERE packId = $packId; ` - ) - .pluck() - .get({ packId }); - if (count > 0) { - return undefined; - } + ) + .pluck() + .get({ packId }); + if (count > 0) { + return undefined; + } - const packRow: { status: StickerPackStatusType } = db - .prepare( - ` + const packRow: { status: StickerPackStatusType } = db + .prepare( + ` SELECT status FROM sticker_packs WHERE id = $packId; ` - ) - .get({ packId }); - if (!packRow) { - logger.warn('deleteStickerPackReference: did not find referenced pack'); - return undefined; - } - const { status } = packRow; + ) + .get({ packId }); + if (!packRow) { + logger.warn('deleteStickerPackReference: did not find referenced pack'); + return undefined; + } + const { status } = packRow; - if (status === 'installed') { - return undefined; - } + if (status === 'installed') { + return undefined; + } - const stickerPathRows: Array<{ path: string }> = db - .prepare( - ` + const stickerPathRows: Array<{ path: string }> = db + .prepare( + ` SELECT path FROM stickers WHERE packId = $packId; ` - ) - .all({ - packId, - }); - db.prepare( - ` + ) + .all({ + packId, + }); + db.prepare( + ` DELETE FROM sticker_packs WHERE id = $packId; ` - ).run({ - packId, - }); + ).run({ + packId, + }); - return (stickerPathRows || []).map(row => row.path); - }) - .immediate(); + return (stickerPathRows || []).map(row => row.path); + })(); } -async function deleteStickerPack(packId: string): Promise> { - const db = await getWritableInstance(); - +function deleteStickerPack(db: WritableDB, packId: string): Array { if (!packId) { throw new Error( 'deleteStickerPack: Provided data did not have a truthy packId' ); } - return db - .transaction(() => { - // We use an immediate transaction here to immediately acquire an exclusive lock, - // which would normally only happen when we did our first write. + return db.transaction(() => { + // We use an immediate transaction here to immediately acquire an exclusive lock, + // which would normally only happen when we did our first write. - // We need this to ensure that our two queries are atomic, with no other changes - // happening while we do it: - // 1. Grab all of target pack's sticker paths - // 2. Delete sticker pack (which cascades to all stickers and references) + // We need this to ensure that our two queries are atomic, with no other changes + // happening while we do it: + // 1. Grab all of target pack's sticker paths + // 2. Delete sticker pack (which cascades to all stickers and references) - const stickerPathRows: Array<{ path: string }> = db - .prepare( - ` + const stickerPathRows: Array<{ path: string }> = db + .prepare( + ` SELECT path FROM stickers WHERE packId = $packId; ` - ) - .all({ - packId, - }); - db.prepare( - ` + ) + .all({ + packId, + }); + db.prepare( + ` DELETE FROM sticker_packs WHERE id = $packId; ` - ).run({ packId }); + ).run({ packId }); - return (stickerPathRows || []).map(row => row.path); - }) - .immediate(); + return (stickerPathRows || []).map(row => row.path); + })(); } -async function getStickerCount(): Promise { - return getCountFromTable(getReadonlyInstance(), 'stickers'); +function getStickerCount(db: ReadableDB): number { + return getCountFromTable(db, 'stickers'); } -async function getAllStickerPacks(): Promise> { - const db = getReadonlyInstance(); - +function getAllStickerPacks(db: ReadableDB): Array { const rows = db .prepare( ` @@ -5703,8 +5415,8 @@ async function getAllStickerPacks(): Promise> { }; }); } -function addUninstalledStickerPackSync( - db: Database, +function addUninstalledStickerPack( + db: WritableDB, pack: UninstalledStickerPackType ): void { db.prepare( @@ -5729,26 +5441,14 @@ function addUninstalledStickerPackSync( storageNeedsSync: pack.storageNeedsSync ? 1 : 0, }); } -async function addUninstalledStickerPack( - pack: UninstalledStickerPackType -): Promise { - const db = await getWritableInstance(); - return addUninstalledStickerPackSync(db, pack); -} -function removeUninstalledStickerPackSync(db: Database, packId: string): void { +function removeUninstalledStickerPack(db: WritableDB, packId: string): void { db.prepare( 'DELETE FROM uninstalled_sticker_packs WHERE id IS $id' ).run({ id: packId }); } -async function removeUninstalledStickerPack(packId: string): Promise { - const db = await getWritableInstance(); - return removeUninstalledStickerPackSync(db, packId); -} -async function getUninstalledStickerPacks(): Promise< - Array -> { - const db = getReadonlyInstance(); - +function getUninstalledStickerPacks( + db: ReadableDB +): Array { const rows = db .prepare( 'SELECT * FROM uninstalled_sticker_packs ORDER BY id ASC' @@ -5757,9 +5457,7 @@ async function getUninstalledStickerPacks(): Promise< return rows || []; } -async function getInstalledStickerPacks(): Promise> { - const db = getReadonlyInstance(); - +function getInstalledStickerPacks(db: ReadableDB): Array { // If sticker pack has a storageID - it is being downloaded and about to be // installed so we better sync it back to storage service if asked. const rows = db @@ -5777,11 +5475,10 @@ async function getInstalledStickerPacks(): Promise> { return rows || []; } -async function getStickerPackInfo( +function getStickerPackInfo( + db: ReadableDB, packId: string -): Promise { - const db = getReadonlyInstance(); - +): StickerPackInfoType | undefined { return db.transaction(() => { const uninstalled = db .prepare( @@ -5812,26 +5509,26 @@ async function getStickerPackInfo( return undefined; })(); } -async function installStickerPack( +function installStickerPack( + db: WritableDB, packId: string, timestamp: number -): Promise { - const db = await getWritableInstance(); +): void { return db.transaction(() => { const status = 'installed'; - updateStickerPackStatusSync(db, packId, status, { timestamp }); + updateStickerPackStatus(db, packId, status, { timestamp }); - removeUninstalledStickerPackSync(db, packId); + removeUninstalledStickerPack(db, packId); })(); } -async function uninstallStickerPack( +function uninstallStickerPack( + db: WritableDB, packId: string, timestamp: number -): Promise { - const db = await getWritableInstance(); +): void { return db.transaction(() => { const status = 'downloaded'; - updateStickerPackStatusSync(db, packId, status); + updateStickerPackStatus(db, packId, status); db.prepare( ` @@ -5844,16 +5541,14 @@ async function uninstallStickerPack( ` ).run({ packId }); - addUninstalledStickerPackSync(db, { + addUninstalledStickerPack(db, { id: packId, uninstalledAt: timestamp, storageNeedsSync: true, }); })(); } -async function getAllStickers(): Promise> { - const db = getReadonlyInstance(); - +function getAllStickers(db: ReadableDB): Array { const rows = db .prepare( ` @@ -5865,11 +5560,10 @@ async function getAllStickers(): Promise> { return (rows || []).map(row => rowToSticker(row)); } -async function getRecentStickers({ limit }: { limit?: number } = {}): Promise< - Array -> { - const db = getReadonlyInstance(); - +function getRecentStickers( + db: ReadableDB, + { limit }: { limit?: number } = {} +): Array { // Note: we avoid 'IS NOT NULL' here because it does seem to bypass our index const rows = db .prepare( @@ -5889,12 +5583,11 @@ async function getRecentStickers({ limit }: { limit?: number } = {}): Promise< } // Emojis -async function updateEmojiUsage( +function updateEmojiUsage( + db: WritableDB, shortName: string, timeUsed: number = Date.now() -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { const rows = db .prepare( @@ -5926,8 +5619,7 @@ async function updateEmojiUsage( })(); } -async function getRecentEmojis(limit = 32): Promise> { - const db = getReadonlyInstance(); +function getRecentEmojis(db: ReadableDB, limit = 32): Array { const rows = db .prepare( ` @@ -5942,9 +5634,7 @@ async function getRecentEmojis(limit = 32): Promise> { return rows || []; } -async function getAllBadges(): Promise> { - const db = getReadonlyInstance(); - +function getAllBadges(db: ReadableDB): Array { const [badgeRows, badgeImageFileRows] = db.transaction(() => [ db.prepare('SELECT * FROM badges').all(), db.prepare('SELECT * FROM badgeImageFiles').all(), @@ -5977,11 +5667,10 @@ async function getAllBadges(): Promise> { } // This should match the logic in the badges Redux reducer. -async function updateOrCreateBadges( +function updateOrCreateBadges( + db: WritableDB, badges: ReadonlyArray -): Promise { - const db = await getWritableInstance(); - +): void { const insertBadge = prepare( db, ` @@ -6055,19 +5744,18 @@ async function updateOrCreateBadges( })(); } -async function badgeImageFileDownloaded( +function badgeImageFileDownloaded( + db: WritableDB, url: string, localPath: string -): Promise { - const db = await getWritableInstance(); +): void { prepare( db, 'UPDATE badgeImageFiles SET localPath = $localPath WHERE url = $url' ).run({ url, localPath }); } -async function getAllBadgeImageFileLocalPaths(): Promise> { - const db = getReadonlyInstance(); +function getAllBadgeImageFileLocalPaths(db: ReadableDB): Set { const localPaths = db .prepare( 'SELECT localPath FROM badgeImageFiles WHERE localPath IS NOT NULL' @@ -6077,10 +5765,10 @@ async function getAllBadgeImageFileLocalPaths(): Promise> { return new Set(localPaths); } -function runCorruptionChecks(): void { - let db: Database; +function runCorruptionChecks(db: ReadableDB): void { + let writable: WritableDB; try { - db = getUnsafeWritableInstance('integrity check'); + writable = toUnsafeWritableDB(db, 'integrity check'); } catch (error) { logger.error( 'runCorruptionChecks: not running the check, no writable instance', @@ -6089,7 +5777,7 @@ function runCorruptionChecks(): void { return; } try { - const result = db.pragma('integrity_check'); + const result = writable.pragma('integrity_check'); if (result.length === 1 && result.at(0)?.integrity_check === 'ok') { logger.info('runCorruptionChecks: general integrity is ok'); } else { @@ -6102,7 +5790,9 @@ function runCorruptionChecks(): void { ); } try { - db.exec("INSERT INTO messages_fts(messages_fts) VALUES('integrity-check')"); + writable.exec( + "INSERT INTO messages_fts(messages_fts) VALUES('integrity-check')" + ); logger.info('runCorruptionChecks: FTS5 integrity ok'); } catch (error) { logger.error( @@ -6168,38 +5858,34 @@ function freezeStoryDistribution( }; } -async function _getAllStoryDistributions(): Promise< - Array -> { - const db = getReadonlyInstance(); +function _getAllStoryDistributions( + db: ReadableDB +): Array { const storyDistributions = db .prepare('SELECT * FROM storyDistributions;') .all(); return storyDistributions.map(hydrateStoryDistribution); } -async function _getAllStoryDistributionMembers(): Promise< - Array -> { - const db = getReadonlyInstance(); +function _getAllStoryDistributionMembers( + db: ReadableDB +): Array { return db .prepare('SELECT * FROM storyDistributionMembers;') .all(); } -async function _deleteAllStoryDistributions(): Promise { - const db = await getWritableInstance(); +function _deleteAllStoryDistributions(db: WritableDB): void { db.prepare('DELETE FROM storyDistributions;').run(); } -async function createNewStoryDistribution( +function createNewStoryDistribution( + db: WritableDB, distribution: StoryDistributionWithMembersType -): Promise { +): void { strictAssert( distribution.name, 'Distribution list does not have a valid name' ); - const db = await getWritableInstance(); - db.transaction(() => { const payload = freezeStoryDistribution(distribution); @@ -6255,11 +5941,11 @@ async function createNewStoryDistribution( } })(); } -async function getAllStoryDistributionsWithMembers(): Promise< - Array -> { - const allDistributions = await _getAllStoryDistributions(); - const allMembers = await _getAllStoryDistributionMembers(); +function getAllStoryDistributionsWithMembers( + db: ReadableDB +): Array { + const allDistributions = _getAllStoryDistributions(db); + const allMembers = _getAllStoryDistributionMembers(db); const byListId = groupBy(allMembers, member => member.listId); @@ -6268,10 +5954,10 @@ async function getAllStoryDistributionsWithMembers(): Promise< members: (byListId[list.id] || []).map(member => member.serviceId), })); } -async function getStoryDistributionWithMembers( +function getStoryDistributionWithMembers( + db: ReadableDB, id: string -): Promise { - const db = getReadonlyInstance(); +): StoryDistributionWithMembersType | undefined { const storyDistribution: StoryDistributionForDatabase | undefined = prepare( db, 'SELECT * FROM storyDistributions WHERE id = $id;' @@ -6295,10 +5981,12 @@ async function getStoryDistributionWithMembers( members: members.map(({ serviceId }) => serviceId), }; } -function modifyStoryDistributionSync( - db: Database, - payload: StoryDistributionForDatabase +function modifyStoryDistribution( + db: WritableDB, + distribution: StoryDistributionType ): void { + const payload = freezeStoryDistribution(distribution); + if (payload.deletedAtTimestamp) { strictAssert( !payload.name, @@ -6329,14 +6017,14 @@ function modifyStoryDistributionSync( ` ).run(payload); } -function modifyStoryDistributionMembersSync( - db: Database, +function modifyStoryDistributionMembers( + db: WritableDB, listId: string, { toAdd, toRemove, }: { toAdd: Array; toRemove: Array } -) { +): void { const memberInsertStatement = prepare( db, ` @@ -6370,65 +6058,39 @@ function modifyStoryDistributionMembersSync( } ); } -async function modifyStoryDistributionWithMembers( +function modifyStoryDistributionWithMembers( + db: WritableDB, distribution: StoryDistributionType, { toAdd, toRemove, }: { toAdd: Array; toRemove: Array } -): Promise { - const payload = freezeStoryDistribution(distribution); - const db = await getWritableInstance(); - +): void { if (toAdd.length || toRemove.length) { db.transaction(() => { - modifyStoryDistributionSync(db, payload); - modifyStoryDistributionMembersSync(db, payload.id, { toAdd, toRemove }); + modifyStoryDistribution(db, distribution); + modifyStoryDistributionMembers(db, distribution.id, { toAdd, toRemove }); })(); } else { - modifyStoryDistributionSync(db, payload); + modifyStoryDistribution(db, distribution); } } -async function modifyStoryDistribution( - distribution: StoryDistributionType -): Promise { - const payload = freezeStoryDistribution(distribution); - const db = await getWritableInstance(); - modifyStoryDistributionSync(db, payload); -} -async function modifyStoryDistributionMembers( - listId: string, - { - toAdd, - toRemove, - }: { toAdd: Array; toRemove: Array } -): Promise { - const db = await getWritableInstance(); - - db.transaction(() => { - modifyStoryDistributionMembersSync(db, listId, { toAdd, toRemove }); - })(); -} -async function deleteStoryDistribution( +function deleteStoryDistribution( + db: WritableDB, id: StoryDistributionIdString -): Promise { - const db = await getWritableInstance(); +): void { db.prepare('DELETE FROM storyDistributions WHERE id = $id;').run({ id, }); } -async function _getAllStoryReads(): Promise> { - const db = getReadonlyInstance(); +function _getAllStoryReads(db: ReadableDB): Array { return db.prepare('SELECT * FROM storyReads;').all(); } -async function _deleteAllStoryReads(): Promise { - const db = await getWritableInstance(); +function _deleteAllStoryReads(db: WritableDB): void { db.prepare('DELETE FROM storyReads;').run(); } -async function addNewStoryRead(read: StoryReadType): Promise { - const db = await getWritableInstance(); - +function addNewStoryRead(db: WritableDB, read: StoryReadType): void { prepare( db, ` @@ -6446,18 +6108,20 @@ async function addNewStoryRead(read: StoryReadType): Promise { ` ).run(read); } -async function getLastStoryReadsForAuthor({ - authorId, - conversationId, - limit: initialLimit, -}: { - authorId: ServiceIdString; - conversationId?: string; - limit?: number; -}): Promise> { +function getLastStoryReadsForAuthor( + db: ReadableDB, + { + authorId, + conversationId, + limit: initialLimit, + }: { + authorId: ServiceIdString; + conversationId?: string; + limit?: number; + } +): Array { const limit = initialLimit || 5; - const db = getReadonlyInstance(); return db .prepare( ` @@ -6476,10 +6140,10 @@ async function getLastStoryReadsForAuthor({ }); } -async function countStoryReadsByConversation( +function countStoryReadsByConversation( + db: ReadableDB, conversationId: string -): Promise { - const db = getReadonlyInstance(); +): number { return db .prepare( ` @@ -6492,9 +6156,7 @@ async function countStoryReadsByConversation( } // All data in database -async function removeAll(): Promise { - const db = await getWritableInstance(); - +function removeAll(db: WritableDB): void { db.transaction(() => { db.exec(` --- Remove messages delete trigger for performance @@ -6557,9 +6219,7 @@ async function removeAll(): Promise { } // Anything that isn't user-visible data -async function removeAllConfiguration(): Promise { - const db = await getWritableInstance(); - +function removeAllConfiguration(db: WritableDB): void { db.transaction(() => { db.exec( ` @@ -6611,9 +6271,7 @@ async function removeAllConfiguration(): Promise { })(); } -async function eraseStorageServiceState(): Promise { - const db = await getWritableInstance(); - +function eraseStorageServiceState(db: WritableDB): void { db.exec(` -- Conversations UPDATE conversations @@ -6647,12 +6305,11 @@ async function eraseStorageServiceState(): Promise { const MAX_MESSAGE_MIGRATION_ATTEMPTS = 5; -async function getMessagesNeedingUpgrade( +function getMessagesNeedingUpgrade( + db: ReadableDB, limit: number, { maxVersion }: { maxVersion: number } -): Promise> { - const db = getReadonlyInstance(); - +): Array { const rows: JSONRows = db .prepare( ` @@ -6676,11 +6333,11 @@ async function getMessagesNeedingUpgrade( return rows.map(row => jsonToObject(row.json)); } -async function getMessagesWithVisualMediaAttachments( +function getMessagesWithVisualMediaAttachments( + db: ReadableDB, conversationId: string, { limit }: { limit: number } -): Promise> { - const db = getReadonlyInstance(); +): Array { const rows: JSONRows = db .prepare( ` @@ -6705,11 +6362,11 @@ async function getMessagesWithVisualMediaAttachments( return rows.map(row => jsonToObject(row.json)); } -async function getMessagesWithFileAttachments( +function getMessagesWithFileAttachments( + db: ReadableDB, conversationId: string, { limit }: { limit: number } -): Promise> { - const db = getReadonlyInstance(); +): Array { const rows = db .prepare( ` @@ -6730,11 +6387,10 @@ async function getMessagesWithFileAttachments( return map(rows, row => jsonToObject(row.json)); } -async function getMessageServerGuidsForSpam( +function getMessageServerGuidsForSpam( + db: ReadableDB, conversationId: string -): Promise> { - const db = getReadonlyInstance(); - +): Array { // The server's maximum is 3, which is why you see `LIMIT 3` in this query. Note that we // use `pluck` here to only get the first column! return db @@ -6863,15 +6519,16 @@ function getExternalDraftFilesForConversation( return files; } -async function getKnownMessageAttachments( +function getKnownMessageAttachments( + db: ReadableDB, cursor?: MessageAttachmentsCursorType -): Promise { +): GetKnownMessageAttachmentsResultType { const innerCursor = cursor as MessageCursorType | undefined as | PageMessagesCursorType | undefined; const result = new Set(); - const { messages, cursor: newCursor } = await pageMessages(innerCursor); + const { messages, cursor: newCursor } = pageMessages(db, innerCursor); for (const message of messages) { const externalFiles = getExternalFilesForMessage(message); @@ -6884,21 +6541,23 @@ async function getKnownMessageAttachments( }; } -async function finishGetKnownMessageAttachments( +function finishGetKnownMessageAttachments( + db: ReadableDB, cursor: MessageAttachmentsCursorType -): Promise { +): void { const innerCursor = cursor as MessageCursorType as PageMessagesCursorType; - await finishPageMessages(innerCursor); + finishPageMessages(db, innerCursor); } -async function pageMessages( +function pageMessages( + db: ReadableDB, cursor?: PageMessagesCursorType -): Promise { - const db = getUnsafeWritableInstance('only temp table use'); +): PageMessagesResultType { + const writable = toUnsafeWritableDB(db, 'only temp table use'); const chunkSize = 1000; - return db.transaction(() => { + return writable.transaction(() => { let count = cursor?.count ?? 0; strictAssert(!cursor?.done, 'pageMessages: iteration cannot be restarted'); @@ -6907,13 +6566,13 @@ async function pageMessages( if (cursor === undefined) { runId = randomBytes(8).toString('hex'); - const total = getMessageCountSync(); + const total = getMessageCount(db); logger.info( `pageMessages(${runId}): ` + `Starting iteration through ${total} messages` ); - db.exec( + writable.exec( ` CREATE TEMP TABLE tmp_${runId}_updated_messages (rowid INTEGER PRIMARY KEY ASC); @@ -6940,7 +6599,7 @@ async function pageMessages( ({ runId } = cursor); } - const rowids: Array = db + const rowids: Array = writable .prepare( ` DELETE FROM tmp_${runId}_updated_messages @@ -6953,10 +6612,10 @@ async function pageMessages( .all({ chunkSize }); const messages = batchMultiVarQuery( - db, + writable, rowids, (batch: ReadonlyArray): Array => { - const query = db.prepare( + const query = writable.prepare( `SELECT json FROM messages WHERE rowid IN (${Array(batch.length) .fill('?') .join(',')});` @@ -6977,12 +6636,11 @@ async function pageMessages( })(); } -async function finishPageMessages({ - runId, - count, - done, -}: PageMessagesCursorType): Promise { - const db = getUnsafeWritableInstance('only temp table use'); +function finishPageMessages( + db: ReadableDB, + { runId, count, done }: PageMessagesCursorType +): void { + const writable = toUnsafeWritableDB(db, 'only temp table use'); const logId = `finishPageMessages(${runId})`; if (!done) { @@ -6990,22 +6648,21 @@ async function finishPageMessages({ } logger.info(`${logId}: reached the end after processing ${count} messages`); - db.exec(` + writable.exec(` DROP TABLE tmp_${runId}_updated_messages; DROP TRIGGER tmp_${runId}_message_updates; DROP TRIGGER tmp_${runId}_message_inserts; `); } -async function getKnownConversationAttachments(): Promise> { - const db = getReadonlyInstance(); +function getKnownConversationAttachments(db: ReadableDB): Array { const result = new Set(); const chunkSize = 500; let complete = false; let id = ''; - const conversationTotal = await getConversationCount(); + const conversationTotal = getConversationCount(db); logger.info( 'getKnownConversationAttachments: About to iterate through ' + `${conversationTotal}` @@ -7046,16 +6703,16 @@ async function getKnownConversationAttachments(): Promise> { return Array.from(result); } -async function removeKnownStickers( +function removeKnownStickers( + db: WritableDB, allStickers: ReadonlyArray -): Promise> { - const db = await getWritableInstance(); +): Array { const lookup: Dictionary = fromPairs( map(allStickers, file => [file, true]) ); const chunkSize = 50; - const total = await getStickerCount(); + const total = getStickerCount(db); logger.info( `removeKnownStickers: About to iterate through ${total} stickers` ); @@ -7097,16 +6754,16 @@ async function removeKnownStickers( return Object.keys(lookup); } -async function removeKnownDraftAttachments( +function removeKnownDraftAttachments( + db: WritableDB, allStickers: ReadonlyArray -): Promise> { - const db = await getWritableInstance(); +): Array { const lookup: Dictionary = fromPairs( map(allStickers, file => [file, true]) ); const chunkSize = 50; - const total = await getConversationCount(); + const total = getConversationCount(db); logger.info( `removeKnownDraftAttachments: About to iterate through ${total} conversations` ); @@ -7157,55 +6814,8 @@ async function removeKnownDraftAttachments( return Object.keys(lookup); } -const OPTIMIZE_FTS_PAGE_COUNT = 64; - -// This query is incremental. It gets the `state` from the return value of -// previous `optimizeFTS` call. When `state.done` is `true` - optimization is -// complete. -async function optimizeFTS( - state?: FTSOptimizationStateType -): Promise { - // See https://www.sqlite.org/fts5.html#the_merge_command - let pageCount = OPTIMIZE_FTS_PAGE_COUNT; - if (state === undefined) { - pageCount = -pageCount; - } - const db = await getWritableInstance(); - const getChanges = prepare(db, 'SELECT total_changes() as changes;', { - pluck: true, - }); - - const changeDifference = db.transaction(() => { - const before: number = getChanges.get({}); - - prepare( - db, - ` - INSERT INTO messages_fts(messages_fts, rank) VALUES ('merge', $pageCount); - ` - ).run({ pageCount }); - - const after: number = getChanges.get({}); - - return after - before; - })(); - - const nextSteps = (state?.steps ?? 0) + 1; - - // From documentation: - // "If the difference is less than 2, then the 'merge' command was a no-op" - const done = changeDifference < 2; - - return { steps: nextSteps, done }; -} - -async function getJobsInQueue(queueType: string): Promise> { - const db = getReadonlyInstance(); - return getJobsInQueueSync(db, queueType); -} - -export function getJobsInQueueSync( - db: Database, +export function getJobsInQueue( + db: ReadableDB, queueType: string ): Array { return db @@ -7226,7 +6836,7 @@ export function getJobsInQueueSync( })); } -export function insertJobSync(db: Database, job: Readonly): void { +export function insertJob(db: WritableDB, job: Readonly): void { db.prepare( ` INSERT INTO jobs @@ -7242,22 +6852,14 @@ export function insertJobSync(db: Database, job: Readonly): void { }); } -async function insertJob(job: Readonly): Promise { - const db = await getWritableInstance(); - return insertJobSync(db, job); -} - -async function deleteJob(id: string): Promise { - const db = await getWritableInstance(); - +function deleteJob(db: WritableDB, id: string): void { db.prepare('DELETE FROM jobs WHERE id = $id').run({ id }); } -async function wasGroupCallRingPreviouslyCanceled( +function wasGroupCallRingPreviouslyCanceled( + db: ReadableDB, ringId: bigint -): Promise { - const db = getReadonlyInstance(); - +): boolean { return db .prepare( ` @@ -7275,9 +6877,10 @@ async function wasGroupCallRingPreviouslyCanceled( }); } -async function processGroupCallRingCancellation(ringId: bigint): Promise { - const db = await getWritableInstance(); - +function processGroupCallRingCancellation( + db: WritableDB, + ringId: bigint +): void { db.prepare( ` INSERT INTO groupCallRingCancellations (ringId, createdAt) @@ -7291,9 +6894,7 @@ async function processGroupCallRingCancellation(ringId: bigint): Promise { // that, it doesn't really matter what the value is. const MAX_GROUP_CALL_RING_AGE = 30 * durations.MINUTE; -async function cleanExpiredGroupCallRingCancellations(): Promise { - const db = await getWritableInstance(); - +function cleanExpiredGroupCallRingCancellations(db: WritableDB): void { db.prepare( ` DELETE FROM groupCallRingCancellations @@ -7304,9 +6905,7 @@ async function cleanExpiredGroupCallRingCancellations(): Promise { }); } -async function getMaxMessageCounter(): Promise { - const db = getReadonlyInstance(); - +function getMaxMessageCounter(db: ReadableDB): number | undefined { return db .prepare( ` @@ -7323,26 +6922,24 @@ async function getMaxMessageCounter(): Promise { .get(); } -async function getStatisticsForLogging(): Promise> { - const db = getReadonlyInstance(); - const counts = await pProps({ - messageCount: getMessageCount(), - conversationCount: getConversationCount(), +function getStatisticsForLogging(db: ReadableDB): Record { + const counts = { + messageCount: getMessageCount(db), + conversationCount: getConversationCount(db), sessionCount: getCountFromTable(db, 'sessions'), senderKeyCount: getCountFromTable(db, 'senderKeys'), - }); + }; return mapValues(counts, formatCountForLogging); } -async function updateAllConversationColors( +function updateAllConversationColors( + db: WritableDB, conversationColor?: ConversationColorType, customColorData?: { id: string; value: CustomColorType; } -): Promise { - const db = await getWritableInstance(); - +): void { db.prepare( ` UPDATE conversations @@ -7357,9 +6954,7 @@ async function updateAllConversationColors( }); } -async function removeAllProfileKeyCredentials(): Promise { - const db = await getWritableInstance(); - +function removeAllProfileKeyCredentials(db: WritableDB): void { db.exec( ` UPDATE conversations @@ -7369,20 +6964,17 @@ async function removeAllProfileKeyCredentials(): Promise { ); } -async function saveEditedMessagesSync( +function saveEditedMessages( + db: WritableDB, mainMessage: MessageType, ourAci: AciString, history: ReadonlyArray -): Promise { - const db = await getWritableInstance(); - +): void { db.transaction(() => { - assertSync( - saveMessageSync(db, mainMessage, { - ourAci, - alreadyInTransaction: true, - }) - ); + saveMessage(db, mainMessage, { + ourAci, + alreadyInTransaction: true, + }); for (const { conversationId, messageId, readStatus, sentAt } of history) { const [query, params] = sql` @@ -7404,27 +6996,18 @@ async function saveEditedMessagesSync( })(); } -async function saveEditedMessage( +function saveEditedMessage( + db: WritableDB, mainMessage: MessageType, ourAci: AciString, editedMessage: EditedMessageType -): Promise { - return saveEditedMessagesSync(mainMessage, ourAci, [editedMessage]); +): void { + return saveEditedMessages(db, mainMessage, ourAci, [editedMessage]); } -async function saveEditedMessages( - mainMessage: MessageType, - ourAci: AciString, - editedMessages: ReadonlyArray -): Promise { - return saveEditedMessagesSync(mainMessage, ourAci, editedMessages); -} - -async function _getAllEditedMessages(): Promise< - Array<{ messageId: string; sentAt: number }> -> { - const db = getReadonlyInstance(); - +function _getAllEditedMessages( + db: ReadableDB +): Array<{ messageId: string; sentAt: number }> { return db .prepare( ` @@ -7434,15 +7017,16 @@ async function _getAllEditedMessages(): Promise< .all({}); } -async function getUnreadEditedMessagesAndMarkRead({ - conversationId, - newestUnreadAt, -}: { - conversationId: string; - newestUnreadAt: number; -}): Promise { - const db = await getWritableInstance(); - +function getUnreadEditedMessagesAndMarkRead( + db: WritableDB, + { + conversationId, + newestUnreadAt, + }: { + conversationId: string; + newestUnreadAt: number; + } +): GetUnreadByConversationAndMarkReadResultType { return db.transaction(() => { const [selectQuery, selectParams] = sql` SELECT diff --git a/ts/sql/channels.ts b/ts/sql/channels.ts index 37950afa9..56d26d8d4 100644 --- a/ts/sql/channels.ts +++ b/ts/sql/channels.ts @@ -5,13 +5,21 @@ import { ipcRenderer } from 'electron'; import * as log from '../logging/log'; import createTaskWithTimeout from '../textsecure/TaskWithTimeout'; import { explodePromise } from '../util/explodePromise'; +import { missingCaseError } from '../util/missingCaseError'; -const SQL_CHANNEL_KEY = 'sql-channel'; +const SQL_READ_KEY = 'sql-channel:read'; +const SQL_WRITE_KEY = 'sql-channel:write'; let activeJobCount = 0; let resolveShutdown: (() => void) | undefined; let shutdownPromise: Promise | null = null; +export enum AccessType { + Read = 'Read', + Write = 'Write', +} + export async function ipcInvoke( + access: AccessType, name: string, args: ReadonlyArray ): Promise { @@ -19,21 +27,31 @@ export async function ipcInvoke( if (shutdownPromise && name !== 'close') { throw new Error( - `Rejecting SQL channel job (${fnName}); application is shutting down` + `Rejecting SQL channel job (${access}, ${fnName}); ` + + 'application is shutting down' ); } + let channel: string; + if (access === AccessType.Read) { + channel = SQL_READ_KEY; + } else if (access === AccessType.Write) { + channel = SQL_WRITE_KEY; + } else { + throw missingCaseError(access); + } + activeJobCount += 1; return createTaskWithTimeout(async () => { try { - return await ipcRenderer.invoke(SQL_CHANNEL_KEY, name, ...args); + return await ipcRenderer.invoke(channel, name, ...args); } finally { activeJobCount -= 1; if (activeJobCount === 0) { resolveShutdown?.(); } } - }, `SQL channel call (${fnName})`)(); + }, `SQL channel call (${access}, ${fnName})`)(); } export async function doShutdown(): Promise { diff --git a/ts/sql/main.ts b/ts/sql/main.ts index 5662b6f8b..7cc07477b 100644 --- a/ts/sql/main.ts +++ b/ts/sql/main.ts @@ -10,10 +10,15 @@ import { strictAssert } from '../util/assert'; import { explodePromise } from '../util/explodePromise'; import type { LoggerType } from '../types/Logging'; import { SqliteErrorKind } from './errors'; -import type DB from './Server'; +import type { + ServerReadableDirectInterface, + ServerWritableDirectInterface, +} from './Interface'; const MIN_TRACE_DURATION = 40; +const WORKER_COUNT = 4; + export type InitializeOptions = Readonly<{ appVersion: string; configDir: string; @@ -25,16 +30,19 @@ export type WorkerRequest = Readonly< | { type: 'init'; options: Omit; + isPrimary: boolean; } | { - type: 'close'; + type: 'close' | 'removeDB'; } | { - type: 'removeDB'; + type: 'sqlCall:read'; + method: keyof ServerReadableDirectInterface; + args: ReadonlyArray; } | { - type: 'sqlCall'; - method: keyof typeof DB; + type: 'sqlCall:write'; + method: keyof ServerWritableDirectInterface; args: ReadonlyArray; } >; @@ -71,14 +79,26 @@ type KnownErrorResolverType = Readonly<{ resolve: (err: Error) => void; }>; +type CreateWorkerResultType = Readonly<{ + worker: Worker; + onExit: Promise; +}>; + +type PoolEntry = { + readonly worker: Worker; + load: number; +}; + export class MainSQL { - private readonly worker: Worker; + private readonly pool = new Array(); + + private pauseWaiters: Array<() => void> | undefined; private isReady = false; private onReady: Promise | undefined; - private readonly onExit: Promise; + private readonly onExit: Promise; // Promise resolve callbacks for corruption and readonly errors. private errorResolvers = new Array(); @@ -93,10 +113,246 @@ export class MainSQL { private shouldTimeQueries = false; constructor() { - const scriptDir = join(app.getAppPath(), 'ts', 'sql', 'mainWorker.js'); - this.worker = new Worker(scriptDir); + const exitPromises = new Array>(); + for (let i = 0; i < WORKER_COUNT; i += 1) { + const { worker, onExit } = this.createWorker(); + this.pool.push({ worker, load: 0 }); - this.worker.on('message', (wrappedResponse: WrappedWorkerResponse) => { + exitPromises.push(onExit); + } + this.onExit = Promise.all(exitPromises); + } + + public async initialize({ + appVersion, + configDir, + key, + logger, + }: InitializeOptions): Promise { + if (this.isReady || this.onReady) { + throw new Error('Already initialized'); + } + + this.shouldTimeQueries = Boolean(process.env.TIME_QUERIES); + + this.logger = logger; + + this.onReady = (async () => { + const primary = this.pool[0]; + const rest = this.pool.slice(1); + + await this.send(primary, { + type: 'init', + options: { appVersion, configDir, key }, + isPrimary: true, + }); + + await Promise.all( + rest.map(worker => + this.send(worker, { + type: 'init', + options: { appVersion, configDir, key }, + isPrimary: false, + }) + ) + ); + })(); + + await this.onReady; + + this.onReady = undefined; + this.isReady = true; + } + + public pauseWriteAccess(): void { + strictAssert(this.pauseWaiters == null, 'Already paused'); + + this.pauseWaiters = []; + } + + public resumeWriteAccess(): void { + const { pauseWaiters } = this; + strictAssert(pauseWaiters != null, 'Not paused'); + this.pauseWaiters = undefined; + + for (const waiter of pauseWaiters) { + waiter(); + } + } + + public whenCorrupted(): Promise { + const { promise, resolve } = explodePromise(); + this.errorResolvers.push({ kind: SqliteErrorKind.Corrupted, resolve }); + return promise; + } + + public whenReadonly(): Promise { + const { promise, resolve } = explodePromise(); + this.errorResolvers.push({ kind: SqliteErrorKind.Readonly, resolve }); + return promise; + } + + public async close(): Promise { + if (!this.isReady) { + throw new Error('Not initialized'); + } + + await this.terminate({ type: 'close' }); + await this.onExit; + } + + public async removeDB(): Promise { + await this.terminate({ type: 'removeDB' }); + } + + public async sqlRead( + method: Method, + ...args: Parameters + ): Promise> { + type SqlCallResult = Readonly<{ + result: ReturnType; + duration: number; + }>; + + // pageMessages runs over several queries and needs to have access to + // the same temporary table. + const isPaging = method === 'pageMessages'; + + const entry = isPaging ? this.pool.at(-1) : this.getWorker(); + strictAssert(entry != null, 'Must have a pool entry'); + + const { result, duration } = await this.send(entry, { + type: 'sqlCall:read', + method, + args, + }); + + this.traceDuration(method, duration); + + return result; + } + + public async sqlWrite( + method: Method, + ...args: Parameters + ): Promise> { + type Result = ReturnType; + type SqlCallResult = Readonly<{ + result: Result; + duration: number; + }>; + + while (this.pauseWaiters != null) { + const { promise, resolve } = explodePromise(); + this.pauseWaiters.push(resolve); + // eslint-disable-next-line no-await-in-loop + await promise; + } + + // Special case since we need to broadcast this to every pool entry. + if (method === 'removeDB') { + return (await this.removeDB()) as Result; + } + + const primary = this.pool[0]; + + const { result, duration } = await this.send(primary, { + type: 'sqlCall:write', + method, + args, + }); + + this.traceDuration(method, duration); + + return result; + } + + private async send( + entry: PoolEntry, + request: WorkerRequest + ): Promise { + if (request.type === 'sqlCall:read' || request.type === 'sqlCall:write') { + if (this.onReady) { + await this.onReady; + } + + if (!this.isReady) { + throw new Error('Not initialized'); + } + } + + const { seq } = this; + // eslint-disable-next-line no-bitwise + this.seq = (this.seq + 1) >>> 0; + + const { promise: result, resolve, reject } = explodePromise(); + this.onResponse.set(seq, { resolve, reject }); + + const wrappedRequest: WrappedWorkerRequest = { + seq, + request, + }; + entry.worker.postMessage(wrappedRequest); + + try { + // eslint-disable-next-line no-param-reassign + entry.load += 1; + return await result; + } finally { + // eslint-disable-next-line no-param-reassign + entry.load -= 1; + } + } + + private async terminate(request: WorkerRequest): Promise { + const primary = this.pool[0]; + const rest = this.pool.slice(1); + + // Terminate non-primary workers first + await Promise.all(rest.map(worker => this.send(worker, request))); + + // Primary last + await this.send(primary, request); + } + + private onError(errorKind: SqliteErrorKind, error: Error): void { + if (errorKind === SqliteErrorKind.Unknown) { + return; + } + + const resolvers = new Array<(error: Error) => void>(); + this.errorResolvers = this.errorResolvers.filter(entry => { + if (entry.kind === errorKind) { + resolvers.push(entry.resolve); + return false; + } + return true; + }); + + for (const resolve of resolvers) { + resolve(error); + } + } + + private traceDuration(method: string, duration: number): void { + if (this.shouldTimeQueries && !app.isPackaged) { + const twoDecimals = Math.round(100 * duration) / 100; + this.logger?.info(`MainSQL query: ${method}, duration=${twoDecimals}ms`); + } + if (duration > MIN_TRACE_DURATION) { + strictAssert(this.logger !== undefined, 'Logger not initialized'); + this.logger.info( + `MainSQL: slow query ${method} duration=${Math.round(duration)}ms` + ); + } + } + + private createWorker(): CreateWorkerResultType { + const scriptPath = join(app.getAppPath(), 'ts', 'sql', 'mainWorker.js'); + + const worker = new Worker(scriptPath); + + worker.on('message', (wrappedResponse: WrappedWorkerResponse) => { if (wrappedResponse.type === 'log') { const { level, args } = wrappedResponse; strictAssert(this.logger !== undefined, 'Logger not initialized'); @@ -123,129 +379,21 @@ export class MainSQL { }); const { promise: onExit, resolve: resolveOnExit } = explodePromise(); - this.onExit = onExit; - this.worker.once('exit', resolveOnExit); + worker.once('exit', resolveOnExit); + + return { worker, onExit }; } - public async initialize({ - appVersion, - configDir, - key, - logger, - }: InitializeOptions): Promise { - if (this.isReady || this.onReady) { - throw new Error('Already initialized'); - } - - this.shouldTimeQueries = Boolean(process.env.TIME_QUERIES); - - this.logger = logger; - - this.onReady = this.send({ - type: 'init', - options: { appVersion, configDir, key }, - }); - - await this.onReady; - - this.onReady = undefined; - this.isReady = true; - } - - public whenCorrupted(): Promise { - const { promise, resolve } = explodePromise(); - this.errorResolvers.push({ kind: SqliteErrorKind.Corrupted, resolve }); - return promise; - } - - public whenReadonly(): Promise { - const { promise, resolve } = explodePromise(); - this.errorResolvers.push({ kind: SqliteErrorKind.Readonly, resolve }); - return promise; - } - - public async close(): Promise { - if (!this.isReady) { - throw new Error('Not initialized'); - } - - await this.send({ type: 'close' }); - await this.onExit; - } - - public async removeDB(): Promise { - await this.send({ type: 'removeDB' }); - } - - public async sqlCall( - method: Method, - ...args: Parameters - ): Promise> { - if (this.onReady) { - await this.onReady; - } - - if (!this.isReady) { - throw new Error('Not initialized'); - } - - type SqlCallResult = Readonly<{ - result: ReturnType; - duration: number; - }>; - - const { result, duration } = await this.send({ - type: 'sqlCall', - method, - args, - }); - - if (this.shouldTimeQueries && !app.isPackaged) { - const twoDecimals = Math.round(100 * duration) / 100; - this.logger?.info(`MainSQL query: ${method}, duration=${twoDecimals}ms`); - } - if (duration > MIN_TRACE_DURATION) { - strictAssert(this.logger !== undefined, 'Logger not initialized'); - this.logger.info( - `MainSQL: slow query ${method} duration=${Math.round(duration)}ms` - ); - } - - return result; - } - - private async send(request: WorkerRequest): Promise { - const { seq } = this; - this.seq += 1; - - const { promise: result, resolve, reject } = explodePromise(); - this.onResponse.set(seq, { resolve, reject }); - - const wrappedRequest: WrappedWorkerRequest = { - seq, - request, - }; - this.worker.postMessage(wrappedRequest); - - return result; - } - - private onError(errorKind: SqliteErrorKind, error: Error): void { - if (errorKind === SqliteErrorKind.Unknown) { - return; - } - - const resolvers = new Array<(error: Error) => void>(); - this.errorResolvers = this.errorResolvers.filter(entry => { - if (entry.kind === errorKind) { - resolvers.push(entry.resolve); - return false; + // Find first pool entry with minimal load + private getWorker(): PoolEntry { + let min = this.pool[0]; + for (const entry of this.pool) { + if (min && min.load < entry.load) { + continue; } - return true; - }); - for (const resolve of resolvers) { - resolve(error); + min = entry; } + return min; } } diff --git a/ts/sql/mainWorker.ts b/ts/sql/mainWorker.ts index ee4851a38..ec0180533 100644 --- a/ts/sql/mainWorker.ts +++ b/ts/sql/mainWorker.ts @@ -10,7 +10,8 @@ import type { WrappedWorkerResponse, WrappedWorkerLogEntry, } from './main'; -import db from './Server'; +import type { WritableDB } from './Interface'; +import { initialize, DataReader, DataWriter } from './Server'; import { SqliteErrorKind, parseSqliteError } from './errors'; if (!parentPort) { @@ -27,8 +28,8 @@ function respond(seq: number, error: Error | undefined, response?: any) { errorKind = parseSqliteError(error); errorString = Errors.toLogFormat(error); - if (errorKind === SqliteErrorKind.Corrupted) { - db.runCorruptionChecks(); + if (errorKind === SqliteErrorKind.Corrupted && db != null) { + DataWriter.runCorruptionChecks(db); } } @@ -75,11 +76,18 @@ const logger: LoggerType = { }, }; -port.on('message', async ({ seq, request }: WrappedWorkerRequest) => { +let db: WritableDB | undefined; +let isPrimary = false; +let isRemoved = false; + +port.on('message', ({ seq, request }: WrappedWorkerRequest) => { try { if (request.type === 'init') { - await db.initialize({ + isPrimary = request.isPrimary; + isRemoved = false; + db = initialize({ ...request.options, + isPrimary, logger, }); @@ -87,8 +95,24 @@ port.on('message', async ({ seq, request }: WrappedWorkerRequest) => { return; } + // 'close' is sent on shutdown, but we already removed the database. + if (isRemoved && request.type === 'close') { + respond(seq, undefined, undefined); + process.exit(0); + return; + } + + if (!db) { + throw new Error('Not initialized'); + } + if (request.type === 'close') { - await db.close(); + if (isPrimary) { + DataWriter.close(db); + } else { + DataReader.close(db); + } + db = undefined; respond(seq, undefined, undefined); process.exit(0); @@ -96,21 +120,30 @@ port.on('message', async ({ seq, request }: WrappedWorkerRequest) => { } if (request.type === 'removeDB') { - await db.removeDB(); + if (isPrimary) { + DataWriter.removeDB(db); + } else { + DataReader.close(db); + } + + isRemoved = true; + db = undefined; respond(seq, undefined, undefined); return; } - if (request.type === 'sqlCall') { + if (request.type === 'sqlCall:read' || request.type === 'sqlCall:write') { + const DataInterface = + request.type === 'sqlCall:read' ? DataReader : DataWriter; // eslint-disable-next-line @typescript-eslint/no-explicit-any - const method = (db as any)[request.method]; + const method = (DataInterface as any)[request.method]; if (typeof method !== 'function') { throw new Error(`Invalid sql method: ${method}`); } const start = performance.now(); - const result = await method.apply(db, request.args); + const result = method(db, ...request.args); const end = performance.now(); respond(seq, undefined, { result, duration: end - start }); diff --git a/ts/sql/migrations/1020-self-merges.ts b/ts/sql/migrations/1020-self-merges.ts index 184b524c0..031ccb019 100644 --- a/ts/sql/migrations/1020-self-merges.ts +++ b/ts/sql/migrations/1020-self-merges.ts @@ -1,17 +1,16 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import type { LoggerType } from '../../types/Logging'; import { sql } from '../util'; +import type { WritableDB } from '../Interface'; import { getOurUuid } from './41-uuid-keys'; export const version = 1020; export function updateToSchemaVersion1020( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 1020) { diff --git a/ts/sql/migrations/41-uuid-keys.ts b/ts/sql/migrations/41-uuid-keys.ts index c574f6544..c8ecf9865 100644 --- a/ts/sql/migrations/41-uuid-keys.ts +++ b/ts/sql/migrations/41-uuid-keys.ts @@ -1,17 +1,14 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import type { LoggerType } from '../../types/Logging'; import { isValidUuid } from '../../util/isValidUuid'; -import { assertSync } from '../../util/assert'; import Helpers from '../../textsecure/Helpers'; import { createOrUpdate, getById, removeById } from '../util'; import type { EmptyQuery, Query } from '../util'; -import type { ItemKeyType } from '../Interface'; +import type { ItemKeyType, ReadableDB, WritableDB } from '../Interface'; -export function getOurUuid(db: Database): string | undefined { +export function getOurUuid(db: ReadableDB): string | undefined { const UUID_ID: ItemKeyType = 'uuid_id'; const row: { json: string } | undefined = db @@ -30,7 +27,7 @@ export function getOurUuid(db: Database): string | undefined { export default function updateToSchemaVersion41( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 41) { @@ -92,8 +89,8 @@ export default function updateToSchemaVersion41( db.prepare('DELETE FROM preKeys').run().changes, ].reduce((a: number, b: number): number => a + b); - assertSync(removeById(db, 'items', 'identityKey')); - assertSync(removeById(db, 'items', 'registrationId')); + removeById(db, 'items', 'identityKey'); + removeById(db, 'items', 'registrationId'); return keyCount; }; @@ -104,36 +101,34 @@ export default function updateToSchemaVersion41( publicKey: string; }; - const identityKey = assertSync( - getById(db, 'items', 'identityKey') + const identityKey = getById( + db, + 'items', + 'identityKey' ); - type RegistrationId = number; - const registrationId = assertSync( - getById(db, 'items', 'registrationId') + const registrationId = getById( + db, + 'items', + 'registrationId' ); - if (identityKey) { - assertSync( - createOrUpdate(db, 'items', { - id: 'identityKeyMap', - value: { - [ourUuid]: identityKey.value, - }, - }) - ); + createOrUpdate(db, 'items', { + id: 'identityKeyMap', + value: { + [ourUuid]: identityKey.value, + }, + }); } if (registrationId) { - assertSync( - createOrUpdate(db, 'items', { - id: 'registrationIdMap', - value: { - [ourUuid]: registrationId.value, - }, - }) - ); + createOrUpdate(db, 'items', { + id: 'registrationIdMap', + value: { + [ourUuid]: registrationId.value, + }, + }); } db.exec( diff --git a/ts/sql/migrations/42-stale-reactions.ts b/ts/sql/migrations/42-stale-reactions.ts index f8c7cc38a..f0785a8ac 100644 --- a/ts/sql/migrations/42-stale-reactions.ts +++ b/ts/sql/migrations/42-stale-reactions.ts @@ -1,15 +1,14 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import { batchMultiVarQuery } from '../util'; import type { ArrayQuery } from '../util'; +import type { WritableDB } from '../Interface'; import type { LoggerType } from '../../types/Logging'; export default function updateToSchemaVersion42( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 42) { diff --git a/ts/sql/migrations/43-gv2-uuid.ts b/ts/sql/migrations/43-gv2-uuid.ts index 0a3fdfde7..c4f7b24dc 100644 --- a/ts/sql/migrations/43-gv2-uuid.ts +++ b/ts/sql/migrations/43-gv2-uuid.ts @@ -1,7 +1,6 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import { omit } from 'lodash'; import type { LoggerType } from '../../types/Logging'; @@ -16,6 +15,7 @@ import { objectToJSON, } from '../util'; import type { EmptyQuery, Query } from '../util'; +import type { WritableDB } from '../Interface'; type MessageType = Readonly<{ id: string; @@ -35,7 +35,7 @@ type ConversationType = Readonly<{ export default function updateToSchemaVersion43( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 43) { diff --git a/ts/sql/migrations/47-further-optimize.ts b/ts/sql/migrations/47-further-optimize.ts index ddc7b506d..d53307ff9 100644 --- a/ts/sql/migrations/47-further-optimize.ts +++ b/ts/sql/migrations/47-further-optimize.ts @@ -1,15 +1,14 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import type { LoggerType } from '../../types/Logging'; import { getOurUuid } from './41-uuid-keys'; +import type { WritableDB } from '../Interface'; import type { Query } from '../util'; export default function updateToSchemaVersion47( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 47) { diff --git a/ts/sql/migrations/51-centralize-conversation-jobs.ts b/ts/sql/migrations/51-centralize-conversation-jobs.ts index bbe8f720f..902cf4682 100644 --- a/ts/sql/migrations/51-centralize-conversation-jobs.ts +++ b/ts/sql/migrations/51-centralize-conversation-jobs.ts @@ -1,19 +1,14 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import type { LoggerType } from '../../types/Logging'; import { isRecord } from '../../util/isRecord'; -import { - getJobsInQueueSync, - getMessageByIdSync, - insertJobSync, -} from '../Server'; +import type { WritableDB } from '../Interface'; +import { getJobsInQueue, getMessageById, insertJob } from '../Server'; export default function updateToSchemaVersion51( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 51) { @@ -26,7 +21,7 @@ export default function updateToSchemaVersion51( ); // First, make sure that reactions job data has a type and conversationId - const reactionsJobs = getJobsInQueueSync(db, 'reactions'); + const reactionsJobs = getJobsInQueue(db, 'reactions'); deleteJobsInQueue.run({ queueType: 'reactions' }); reactionsJobs.forEach(job => { @@ -47,7 +42,7 @@ export default function updateToSchemaVersion51( return; } - const message = getMessageByIdSync(db, messageId); + const message = getMessageById(db, messageId); if (!message) { logger.warn( `updateToSchemaVersion51: Unable to find message for reaction job ${id}` @@ -73,11 +68,11 @@ export default function updateToSchemaVersion51( }, }; - insertJobSync(db, newJob); + insertJob(db, newJob); }); // Then make sure all normal send job data has a type - const normalSendJobs = getJobsInQueueSync(db, 'normal send'); + const normalSendJobs = getJobsInQueue(db, 'normal send'); deleteJobsInQueue.run({ queueType: 'normal send' }); normalSendJobs.forEach(job => { @@ -99,7 +94,7 @@ export default function updateToSchemaVersion51( }, }; - insertJobSync(db, newJob); + insertJob(db, newJob); }); db.pragma('user_version = 51'); diff --git a/ts/sql/migrations/55-report-message-aci.ts b/ts/sql/migrations/55-report-message-aci.ts index ed8e2237f..e9aba0bd7 100644 --- a/ts/sql/migrations/55-report-message-aci.ts +++ b/ts/sql/migrations/55-report-message-aci.ts @@ -1,15 +1,15 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import type { LoggerType } from '../../types/Logging'; -import { getJobsInQueueSync, insertJobSync } from '../Server'; +import { getJobsInQueue, insertJob } from '../Server'; +import type { WritableDB } from '../Interface'; import { isRecord } from '../../util/isRecord'; import { isIterable } from '../../util/iterables'; export default function updateToSchemaVersion55( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 55) { @@ -22,7 +22,7 @@ export default function updateToSchemaVersion55( ); // First, make sure that report spam job data has e164 and serverGuids - const reportSpamJobs = getJobsInQueueSync(db, 'report spam'); + const reportSpamJobs = getJobsInQueue(db, 'report spam'); deleteJobsInQueue.run({ queueType: 'report spam' }); reportSpamJobs.forEach(job => { @@ -59,7 +59,7 @@ export default function updateToSchemaVersion55( }, }; - insertJobSync(db, newJob); + insertJob(db, newJob); }); db.pragma('user_version = 55'); diff --git a/ts/sql/migrations/78-merge-receipt-jobs.ts b/ts/sql/migrations/78-merge-receipt-jobs.ts index 5210f9f05..04c2b4d40 100644 --- a/ts/sql/migrations/78-merge-receipt-jobs.ts +++ b/ts/sql/migrations/78-merge-receipt-jobs.ts @@ -1,19 +1,14 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; - import type { LoggerType } from '../../types/Logging'; import { isRecord } from '../../util/isRecord'; -import { - getJobsInQueueSync, - getMessageByIdSync, - insertJobSync, -} from '../Server'; +import type { WritableDB } from '../Interface'; +import { getJobsInQueue, getMessageById, insertJob } from '../Server'; export default function updateToSchemaVersion78( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 78) { @@ -47,7 +42,7 @@ export default function updateToSchemaVersion78( ]; for (const queue of queues) { - const prevJobs = getJobsInQueueSync(db, queue.queueType); + const prevJobs = getJobsInQueue(db, queue.queueType); deleteJobsInQueue.run({ queueType: queue.queueType }); prevJobs.forEach(job => { @@ -67,7 +62,7 @@ export default function updateToSchemaVersion78( return; } - const message = getMessageByIdSync(db, messageId); + const message = getMessageById(db, messageId); if (!message) { logger.warn( `updateToSchemaVersion78: Unable to find message for ${queue.queueType} job ${id}` @@ -121,7 +116,7 @@ export default function updateToSchemaVersion78( }, }; - insertJobSync(db, newJob); + insertJob(db, newJob); }); } diff --git a/ts/sql/migrations/89-call-history.ts b/ts/sql/migrations/89-call-history.ts index 0791f84ba..77956c8dd 100644 --- a/ts/sql/migrations/89-call-history.ts +++ b/ts/sql/migrations/89-call-history.ts @@ -1,7 +1,6 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import { callIdFromEra } from '@signalapp/ringrtc'; import Long from 'long'; import { v4 as generateUuid } from 'uuid'; @@ -20,7 +19,7 @@ import { callHistoryDetailsSchema, } from '../../types/CallDisposition'; import { CallMode } from '../../types/Calling'; -import type { MessageType, ConversationType } from '../Interface'; +import type { WritableDB, MessageType, ConversationType } from '../Interface'; import { strictAssert } from '../../util/assert'; import { missingCaseError } from '../../util/missingCaseError'; import { isAciString } from '../../util/isAciString'; @@ -188,7 +187,7 @@ function convertLegacyCallDetails( export default function updateToSchemaVersion89( currentVersion: number, - db: Database, + db: WritableDB, logger: LoggerType ): void { if (currentVersion >= 89) { diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index cb8e7cc1a..f1370d9e6 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -15,6 +15,7 @@ import { jsonToObject, } from '../util'; import type { Query, EmptyQuery } from '../util'; +import type { WritableDB } from '../Interface'; import updateToSchemaVersion41 from './41-uuid-keys'; import updateToSchemaVersion42 from './42-stale-reactions'; @@ -2075,7 +2076,7 @@ export function enableFTS5SecureDelete(db: Database, logger: LoggerType): void { } } -export function updateSchema(db: Database, logger: LoggerType): void { +export function updateSchema(db: WritableDB, logger: LoggerType): void { const sqliteVersion = getSQLiteVersion(db); const sqlcipherVersion = getSQLCipherVersion(db); const startingVersion = getUserVersion(db); diff --git a/ts/sql/server/callLinks.ts b/ts/sql/server/callLinks.ts index e0012f112..ce9e8e437 100644 --- a/ts/sql/server/callLinks.ts +++ b/ts/sql/server/callLinks.ts @@ -1,7 +1,6 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import { CallLinkRootKey } from '@signalapp/ringrtc'; import type { CallLinkStateType, CallLinkType } from '../../types/CallLink'; import { @@ -13,12 +12,12 @@ import { callLinkFromRecord, toAdminKeyBytes, } from '../../util/callLinks'; -import { getReadonlyInstance, getWritableInstance, prepare } from '../Server'; +import type { ReadableDB, WritableDB } from '../Interface'; +import { prepare } from '../Server'; import { sql } from '../util'; import { strictAssert } from '../../util/assert'; -export async function callLinkExists(roomId: string): Promise { - const db = getReadonlyInstance(); +export function callLinkExists(db: ReadableDB, roomId: string): boolean { const [query, params] = sql` SELECT 1 FROM callLinks @@ -27,10 +26,10 @@ export async function callLinkExists(roomId: string): Promise { return db.prepare(query).pluck(true).get(params) === 1; } -export async function getCallLinkByRoomId( +export function getCallLinkByRoomId( + db: ReadableDB, roomId: string -): Promise { - const db = getReadonlyInstance(); +): CallLinkType | undefined { const row = prepare(db, 'SELECT * FROM callLinks WHERE roomId = $roomId').get( { roomId, @@ -44,8 +43,7 @@ export async function getCallLinkByRoomId( return callLinkFromRecord(callLinkRecordSchema.parse(row)); } -export async function getAllCallLinks(): Promise> { - const db = getReadonlyInstance(); +export function getAllCallLinks(db: ReadableDB): ReadonlyArray { const [query] = sql` SELECT * FROM callLinks; `; @@ -55,7 +53,7 @@ export async function getAllCallLinks(): Promise> { .map(item => callLinkFromRecord(callLinkRecordSchema.parse(item))); } -function _insertCallLink(db: Database, callLink: CallLinkType): void { +function _insertCallLink(db: WritableDB, callLink: CallLinkType): void { const { roomId, rootKey } = callLink; assertRoomIdMatchesRootKey(roomId, rootKey); @@ -84,17 +82,16 @@ function _insertCallLink(db: Database, callLink: CallLinkType): void { ).run(data); } -export async function insertCallLink(callLink: CallLinkType): Promise { - const db = await getWritableInstance(); +export function insertCallLink(db: WritableDB, callLink: CallLinkType): void { _insertCallLink(db, callLink); } -export async function updateCallLinkState( +export function updateCallLinkState( + db: WritableDB, roomId: string, callLinkState: CallLinkStateType -): Promise { +): CallLinkType { const { name, restrictions, expiration, revoked } = callLinkState; - const db = await getWritableInstance(); const restrictionsValue = callLinkRestrictionsSchema.parse(restrictions); const [query, params] = sql` UPDATE callLinks @@ -111,11 +108,11 @@ export async function updateCallLinkState( return callLinkFromRecord(callLinkRecordSchema.parse(row)); } -export async function updateCallLinkAdminKeyByRoomId( +export function updateCallLinkAdminKeyByRoomId( + db: WritableDB, roomId: string, adminKey: string -): Promise { - const db = await getWritableInstance(); +): void { const adminKeyBytes = toAdminKeyBytes(adminKey); prepare( db, diff --git a/ts/sql/server/groupEndorsements.ts b/ts/sql/server/groupEndorsements.ts index 2916c61a0..51126c7e2 100644 --- a/ts/sql/server/groupEndorsements.ts +++ b/ts/sql/server/groupEndorsements.ts @@ -1,23 +1,23 @@ // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import type { GroupSendCombinedEndorsementRecord, GroupSendEndorsementsData, GroupSendMemberEndorsementRecord, } from '../../types/GroupSendEndorsements'; import { groupSendEndorsementExpirationSchema } from '../../types/GroupSendEndorsements'; -import { getReadonlyInstance, getWritableInstance, prepare } from '../Server'; +import { prepare } from '../Server'; +import type { ReadableDB, WritableDB } from '../Interface'; import { sql } from '../util'; /** * We don't need to store more than one endorsement per group or per member. */ -export async function replaceAllEndorsementsForGroup( +export function replaceAllEndorsementsForGroup( + db: WritableDB, data: GroupSendEndorsementsData -): Promise { - const db = await getWritableInstance(); +): void { db.transaction(() => { const { combinedEndorsement, memberEndorsements } = data; _replaceCombinedEndorsement(db, combinedEndorsement); @@ -26,7 +26,7 @@ export async function replaceAllEndorsementsForGroup( } function _replaceCombinedEndorsement( - db: Database, + db: WritableDB, combinedEndorsement: GroupSendCombinedEndorsementRecord ): void { const { groupId, expiration, endorsement } = combinedEndorsement; @@ -39,7 +39,7 @@ function _replaceCombinedEndorsement( } function _replaceMemberEndorsements( - db: Database, + db: WritableDB, memberEndorsements: ReadonlyArray ) { for (const memberEndorsement of memberEndorsements) { @@ -53,10 +53,10 @@ function _replaceMemberEndorsements( } } -export async function deleteAllEndorsementsForGroup( +export function deleteAllEndorsementsForGroup( + db: WritableDB, groupId: string -): Promise { - const db = await getWritableInstance(); +): void { db.transaction(() => { const [deleteCombined, deleteCombinedParams] = sql` DELETE FROM groupSendCombinedEndorsement @@ -71,10 +71,10 @@ export async function deleteAllEndorsementsForGroup( })(); } -export async function getGroupSendCombinedEndorsementExpiration( +export function getGroupSendCombinedEndorsementExpiration( + db: ReadableDB, groupId: string -): Promise { - const db = getReadonlyInstance(); +): number | null { const [selectGroup, selectGroupParams] = sql` SELECT expiration FROM groupSendCombinedEndorsement WHERE groupId = ${groupId}; diff --git a/ts/sql/util.ts b/ts/sql/util.ts index f00ca7504..9b29ab7c0 100644 --- a/ts/sql/util.ts +++ b/ts/sql/util.ts @@ -1,8 +1,8 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; import { isNumber, last } from 'lodash'; +import type { ReadableDB, WritableDB } from './Interface'; export type EmptyQuery = []; export type ArrayQuery = Array>; @@ -185,7 +185,7 @@ type QueryPlan = Readonly<{ * ``` */ export function explainQueryPlan( - db: Database, + db: ReadableDB, template: QueryTemplate ): QueryPlan { const [query, params] = template; @@ -197,7 +197,7 @@ export function explainQueryPlan( // Database helpers // -export function getSQLiteVersion(db: Database): string { +export function getSQLiteVersion(db: ReadableDB): string { const { sqlite_version: version } = db .prepare('select sqlite_version() AS sqlite_version') .get(); @@ -205,22 +205,22 @@ export function getSQLiteVersion(db: Database): string { return version; } -export function getSchemaVersion(db: Database): number { +export function getSchemaVersion(db: ReadableDB): number { return db.pragma('schema_version', { simple: true }); } -export function setUserVersion(db: Database, version: number): void { +export function setUserVersion(db: WritableDB, version: number): void { if (!isNumber(version)) { throw new Error(`setUserVersion: version ${version} is not a number`); } db.pragma(`user_version = ${version}`); } -export function getUserVersion(db: Database): number { +export function getUserVersion(db: ReadableDB): number { return db.pragma('user_version', { simple: true }); } -export function getSQLCipherVersion(db: Database): string | undefined { +export function getSQLCipherVersion(db: ReadableDB): string | undefined { return db.pragma('cipher_version', { simple: true }); } @@ -229,18 +229,18 @@ export function getSQLCipherVersion(db: Database): string | undefined { // export function batchMultiVarQuery( - db: Database, + db: ReadableDB, values: ReadonlyArray, query: (batch: ReadonlyArray) => void ): []; export function batchMultiVarQuery( - db: Database, + db: ReadableDB, values: ReadonlyArray, query: (batch: ReadonlyArray) => Array ): Array; export function batchMultiVarQuery( - db: Database, + db: ReadableDB, values: ReadonlyArray, query: | ((batch: ReadonlyArray) => void) @@ -265,7 +265,7 @@ export function batchMultiVarQuery( } export function createOrUpdate( - db: Database, + db: WritableDB, table: TableType, data: Record & { id: Key } ): void { @@ -291,7 +291,7 @@ export function createOrUpdate( } export function bulkAdd( - db: Database, + db: WritableDB, table: TableType, array: Array & { id: string | number }> ): void { @@ -303,7 +303,7 @@ export function bulkAdd( } export function getById( - db: Database, + db: ReadableDB, table: TableType, id: Key ): Result | undefined { @@ -327,7 +327,7 @@ export function getById( } export function removeById( - db: Database, + db: WritableDB, tableName: TableType, id: Key | Array ): number { @@ -359,11 +359,11 @@ export function removeById( return totalChanges; } -export function removeAllFromTable(db: Database, table: TableType): number { +export function removeAllFromTable(db: WritableDB, table: TableType): number { return db.prepare(`DELETE FROM ${table};`).run().changes; } -export function getAllFromTable(db: Database, table: TableType): Array { +export function getAllFromTable(db: ReadableDB, table: TableType): Array { const rows: JSONRows = db .prepare(`SELECT json FROM ${table};`) .all(); @@ -371,7 +371,7 @@ export function getAllFromTable(db: Database, table: TableType): Array { return rows.map(row => jsonToObject(row.json)); } -export function getCountFromTable(db: Database, table: TableType): number { +export function getCountFromTable(db: ReadableDB, table: TableType): number { const result: null | number = db .prepare(`SELECT count(*) from ${table};`) .pluck(true) @@ -384,7 +384,7 @@ export function getCountFromTable(db: Database, table: TableType): number { export class TableIterator { constructor( - private readonly db: Database, + private readonly db: ReadableDB, private readonly table: TableType, private readonly pageSize = 500 ) {} diff --git a/ts/state/ducks/badges.ts b/ts/state/ducks/badges.ts index 8093e381f..8f09ef95e 100644 --- a/ts/state/ducks/badges.ts +++ b/ts/state/ducks/badges.ts @@ -4,6 +4,7 @@ import type { ThunkAction } from 'redux-thunk'; import { isEqual, mapValues } from 'lodash'; import type { ReadonlyDeep } from 'type-fest'; +import { DataWriter } from '../../sql/Client'; import type { StateType as RootStateType } from '../reducer'; import type { BadgeType, BadgeImageType } from '../../badges/types'; import { getOwn } from '../../util/getOwn'; @@ -70,7 +71,7 @@ function updateOrCreate( // check (e.g., due to a crash), we won't download its image files. In the unlikely // event that this happens, we'll repair it the next time we check for undownloaded // image files. - await window.Signal.Data.updateOrCreateBadges(badges); + await DataWriter.updateOrCreateBadges(badges); dispatch({ type: UPDATE_OR_CREATE, diff --git a/ts/state/ducks/callHistory.ts b/ts/state/ducks/callHistory.ts index ec9498384..4a42248b9 100644 --- a/ts/state/ducks/callHistory.ts +++ b/ts/state/ducks/callHistory.ts @@ -13,6 +13,7 @@ import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; import type { ToastActionType } from './toast'; import { showToast } from './toast'; +import { DataReader, DataWriter } from '../../sql/Client'; import { ToastType } from '../../types/Toast'; import type { CallHistoryDetails } from '../../types/CallDisposition'; import * as log from '../../logging/log'; @@ -77,7 +78,7 @@ function updateCallHistoryUnreadCount(): ThunkAction< > { return async dispatch => { try { - const unreadCount = await window.Signal.Data.getCallHistoryUnreadCount(); + const unreadCount = await DataReader.getCallHistoryUnreadCount(); dispatch({ type: CALL_HISTORY_UPDATE_UNREAD, payload: unreadCount }); } catch (error) { log.error( @@ -94,7 +95,7 @@ function markCallHistoryRead( ): ThunkAction { return async dispatch => { try { - await window.Signal.Data.markCallHistoryRead(callId); + await DataWriter.markCallHistoryRead(callId); drop(window.ConversationController.get(conversationId)?.updateUnread()); } catch (error) { log.error( diff --git a/ts/state/ducks/calling.ts b/ts/state/ducks/calling.ts index a6255d5cc..104b63cbb 100644 --- a/ts/state/ducks/calling.ts +++ b/ts/state/ducks/calling.ts @@ -86,7 +86,7 @@ import type { ShowErrorModalActionType } from './globalModals'; import { SHOW_ERROR_MODAL } from './globalModals'; import { ButtonVariant } from '../../components/Button'; import { getConversationIdForLogging } from '../../util/idForLogging'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { isAciString } from '../../util/isAciString'; import type { CallHistoryDetails } from '../../types/CallDisposition'; import { @@ -1415,7 +1415,7 @@ function handleCallLinkUpdate( } const { callLinkState: freshCallLinkState } = readResult; - const existingCallLink = await dataInterface.getCallLinkByRoomId(roomId); + const existingCallLink = await DataReader.getCallLinkByRoomId(roomId); const existingCallLinkState = pick(existingCallLink, [ 'name', 'restrictions', @@ -1434,16 +1434,16 @@ function handleCallLinkUpdate( if (existingCallLink) { if (adminKey && adminKey !== existingCallLink.adminKey) { - await dataInterface.updateCallLinkAdminKeyByRoomId(roomId, adminKey); + await DataWriter.updateCallLinkAdminKeyByRoomId(roomId, adminKey); log.info(`${logId}: Updated existing call link with new adminKey`); } if (freshCallLinkState) { - await dataInterface.updateCallLinkState(roomId, freshCallLinkState); + await DataWriter.updateCallLinkState(roomId, freshCallLinkState); log.info(`${logId}: Updated existing call link state`); } } else { - await dataInterface.insertCallLink(callLink); + await DataWriter.insertCallLink(callLink); log.info(`${logId}: Saved new call link`); } @@ -1985,8 +1985,8 @@ function createCallLink( status: AdhocCallStatus.Pending, }; await Promise.all([ - dataInterface.insertCallLink(callLink), - dataInterface.saveCallHistory(callHistory), + DataWriter.insertCallLink(callLink), + DataWriter.saveCallHistory(callHistory), ]); dispatch({ type: HANDLE_CALL_LINK_UPDATE, @@ -2003,13 +2003,13 @@ function updateCallLinkName( name: string ): ThunkAction { return async dispatch => { - const prevCallLink = await dataInterface.getCallLinkByRoomId(roomId); + const prevCallLink = await DataReader.getCallLinkByRoomId(roomId); strictAssert( prevCallLink, `updateCallLinkName(${roomId}): call link not found` ); const callLinkState = await calling.updateCallLinkName(prevCallLink, name); - const callLink = await dataInterface.updateCallLinkState( + const callLink = await DataWriter.updateCallLinkState( roomId, callLinkState ); @@ -2025,7 +2025,7 @@ function updateCallLinkRestrictions( restrictions: CallLinkRestrictions ): ThunkAction { return async dispatch => { - const prevCallLink = await dataInterface.getCallLinkByRoomId(roomId); + const prevCallLink = await DataReader.getCallLinkByRoomId(roomId); strictAssert( prevCallLink, `updateCallLinkRestrictions(${roomId}): call link not found` @@ -2034,7 +2034,7 @@ function updateCallLinkRestrictions( prevCallLink, restrictions ); - const callLink = await dataInterface.updateCallLinkState( + const callLink = await DataWriter.updateCallLinkState( roomId, callLinkState ); @@ -2126,13 +2126,13 @@ const _startCallLinkLobby = async ({ } try { - const callLinkExists = await dataInterface.callLinkExists(roomId); + const callLinkExists = await DataReader.callLinkExists(roomId); if (callLinkExists) { - await dataInterface.updateCallLinkState(roomId, callLinkState); + await DataWriter.updateCallLinkState(roomId, callLinkState); log.info('startCallLinkLobby: Updated existing call link', roomId); } else { const { name, restrictions, expiration, revoked } = callLinkState; - await dataInterface.insertCallLink({ + await DataWriter.insertCallLink({ roomId, rootKey, adminKey: null, diff --git a/ts/state/ducks/composer.ts b/ts/state/ducks/composer.ts index 19d1242b2..b6e278513 100644 --- a/ts/state/ducks/composer.ts +++ b/ts/state/ducks/composer.ts @@ -18,6 +18,7 @@ import { isVideoAttachment, isImageAttachment, } from '../../types/Attachment'; +import { DataReader, DataWriter } from '../../sql/Client'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import type { DraftBodyRanges } from '../../types/BodyRange'; import type { LinkPreviewType } from '../../types/message/LinkPreviews'; @@ -336,7 +337,7 @@ function scrollToQuotedMessage({ ShowToastActionType | ScrollToMessageActionType > { return async (dispatch, getState) => { - const messages = await window.Signal.Data.getMessagesBySentAt(sentAt); + const messages = await DataReader.getMessagesBySentAt(sentAt); const message = messages.find(item => Boolean( item.conversationId === conversationId && @@ -765,7 +766,7 @@ export function setQuoteByMessageId( timestamp, }); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } if (message) { @@ -866,7 +867,7 @@ function addAttachment( }); } - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } }; } @@ -904,7 +905,7 @@ function addPendingAttachment( if (conversation) { conversation.attributes.draftAttachments = nextAttachments; conversation.attributes.draftChanged = true; - window.Signal.Data.updateConversation(conversation.attributes); + drop(DataWriter.updateConversation(conversation.attributes)); } }; } @@ -1201,7 +1202,7 @@ function removeAttachment( if (conversation) { conversation.attributes.draftAttachments = nextAttachments; conversation.attributes.draftChanged = true; - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } replaceAttachments(conversationId, nextAttachments)( @@ -1312,7 +1313,7 @@ function saveDraft( draftChanged: true, draftBodyRanges: [], }); - window.Signal.Data.updateConversation(conversation.attributes); + drop(DataWriter.updateConversation(conversation.attributes)); return; } @@ -1336,7 +1337,7 @@ function saveDraft( draftChanged: true, timestamp, }); - window.Signal.Data.updateConversation(conversation.attributes); + drop(DataWriter.updateConversation(conversation.attributes)); } } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index d154b2899..76f997742 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -17,6 +17,7 @@ import type { PhoneNumber } from 'google-libphonenumber'; import { clipboard } from 'electron'; import type { ReadonlyDeep } from 'type-fest'; +import { DataReader, DataWriter } from '../../sql/Client'; import type { AttachmentType } from '../../types/Attachment'; import type { StateType as RootStateType } from '../reducer'; import * as groups from '../../groups'; @@ -1480,7 +1481,7 @@ async function getAvatarsAndUpdateConversation( conversation.attributes.avatars = nextAvatars.map(avatarData => omit(avatarData, ['buffer']) ); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); return nextAvatars; } @@ -1750,21 +1751,20 @@ function deleteMessages({ let nearbyMessageId: string | null = null; if (nearbyMessageId == null && lastSelectedMessage != null) { - const foundMessageId = - await window.Signal.Data.getNearbyMessageFromDeletedSet({ - conversationId, - lastSelectedMessage, - deletedMessageIds: messageIds, - includeStoryReplies: false, - storyId: undefined, - }); + const foundMessageId = await DataReader.getNearbyMessageFromDeletedSet({ + conversationId, + lastSelectedMessage, + deletedMessageIds: messageIds, + includeStoryReplies: false, + storyId: undefined, + }); if (foundMessageId != null) { nearbyMessageId = foundMessageId; } } - await window.Signal.Data.removeMessages(messageIds, { + await DataWriter.removeMessages(messageIds, { singleProtoJobQueue, }); @@ -2189,7 +2189,7 @@ function removeCustomColorOnConversations( }); if (conversationsToUpdate.length) { - await window.Signal.Data.updateConversations(conversationsToUpdate); + await DataWriter.updateConversations(conversationsToUpdate); } dispatch({ @@ -2209,7 +2209,7 @@ function resetAllChatColors(): ThunkAction< > { return async dispatch => { // Calling this with no args unsets all the colors in the db - await window.Signal.Data.updateAllConversationColors(); + await DataWriter.updateAllConversationColors(); window.getConversations().forEach(conversation => { conversation.set({ @@ -2245,7 +2245,7 @@ function kickOffAttachmentDownload( if (didUpdateValues) { drop( - window.Signal.Data.saveMessage(message.attributes, { + DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }) ); @@ -2402,7 +2402,7 @@ export function setVoiceNotePlaybackRate({ conversationModel.set({ voiceNotePlaybackRate: rate === 1 ? undefined : rate, }); - window.Signal.Data.updateConversation(conversationModel.attributes); + await DataWriter.updateConversation(conversationModel.attributes); } const conversation = conversationModel?.format(); @@ -2456,7 +2456,7 @@ function colorSelected({ }); } - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } dispatch({ @@ -2779,20 +2779,17 @@ function toggleSelectMessage( message => message ); - const betweenIds = await window.Signal.Data.getMessagesBetween( - conversationId, - { - after: { - sent_at: after.sent_at, - received_at: after.received_at, - }, - before: { - sent_at: before.sent_at, - received_at: before.received_at, - }, - includeStoryReplies: !isGroup(conversation.attributes), - } - ); + const betweenIds = await DataReader.getMessagesBetween(conversationId, { + after: { + sent_at: after.sent_at, + received_at: after.received_at, + }, + before: { + sent_at: before.sent_at, + received_at: before.received_at, + }, + includeStoryReplies: !isGroup(conversation.attributes), + }); toggledMessageIds = [messageId, ...betweenIds]; } else { @@ -3434,7 +3431,7 @@ function reportSpam( addReportSpamJob({ conversation, getMessageServerGuidsForSpam: - window.Signal.Data.getMessageServerGuidsForSpam, + DataReader.getMessageServerGuidsForSpam, jobQueue: reportSpamJobQueue, }), ]); @@ -3484,7 +3481,7 @@ function blockAndReportSpam( addReportSpamJob({ conversation: conversationForSpam, getMessageServerGuidsForSpam: - window.Signal.Data.getMessageServerGuidsForSpam, + DataReader.getMessageServerGuidsForSpam, jobQueue: reportSpamJobQueue, }), ]); @@ -3654,12 +3651,9 @@ function loadRecentMediaItems( ): ThunkAction { return async dispatch => { const messages: Array = - await window.Signal.Data.getMessagesWithVisualMediaAttachments( - conversationId, - { - limit, - } - ); + await DataReader.getMessagesWithVisualMediaAttachments(conversationId, { + limit, + }); // Cache these messages in memory to ensure Lightbox can find them messages.forEach(message => { @@ -3839,7 +3833,7 @@ export function scrollToOldestUnreadMention( } const oldestUnreadMention = - await window.Signal.Data.getOldestUnreadMentionOfMeForConversation( + await DataReader.getOldestUnreadMentionOfMeForConversation( conversationId, { includeStoryReplies: !isGroup(conversation), @@ -4151,7 +4145,7 @@ function toggleGroupsForStorySend( conversation.set({ storySendMode: newStorySendMode, }); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); conversation.captureChange('storySendMode'); }) ); @@ -4413,7 +4407,7 @@ function onConversationClosed( }); } - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); drop(conversation.updateLastMessage()); } @@ -4784,7 +4778,7 @@ function updateNicknameAndNote( nicknameFamilyName: nickname?.familyName, note, }); - window.Signal.Data.updateConversation(conversationModel.attributes); + await DataWriter.updateConversation(conversationModel.attributes); const conversation = conversationModel.format(); dispatch(conversationChanged(conversationId, conversation)); conversationModel.captureChange('nicknameAndNote'); diff --git a/ts/state/ducks/emojis.ts b/ts/state/ducks/emojis.ts index f90e295da..18e75043b 100644 --- a/ts/state/ducks/emojis.ts +++ b/ts/state/ducks/emojis.ts @@ -5,11 +5,11 @@ import { take, uniq } from 'lodash'; import type { ThunkAction } from 'redux-thunk'; import type { ReadonlyDeep } from 'type-fest'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; -import dataInterface from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; -const { updateEmojiUsage } = dataInterface; +const { updateEmojiUsage } = DataWriter; // State diff --git a/ts/state/ducks/lightbox.ts b/ts/state/ducks/lightbox.ts index e3a6728b0..db7ac4f82 100644 --- a/ts/state/ducks/lightbox.ts +++ b/ts/state/ducks/lightbox.ts @@ -39,7 +39,7 @@ import { } from './conversations'; import { showStickerPackPreview } from './globalModals'; import { useBoundActions } from '../../hooks/useBoundActions'; -import dataInterface from '../../sql/Client'; +import { DataReader } from '../../sql/Client'; // eslint-disable-next-line local-rules/type-alias-readonlydeep export type LightboxStateType = @@ -349,7 +349,7 @@ function showLightbox(opts: { } const { older, newer } = - await dataInterface.getConversationRangeCenteredOnMessage({ + await DataReader.getConversationRangeCenteredOnMessage({ conversationId: message.get('conversationId'), messageId, receivedAt, @@ -436,8 +436,8 @@ function showLightboxForAdjacentMessage( const [adjacent] = direction === AdjacentMessageDirection.Previous - ? await dataInterface.getOlderMessagesByConversation(options) - : await dataInterface.getNewerMessagesByConversation(options); + ? await DataReader.getOlderMessagesByConversation(options) + : await DataReader.getNewerMessagesByConversation(options); if (!adjacent) { log.warn( diff --git a/ts/state/ducks/mediaGallery.ts b/ts/state/ducks/mediaGallery.ts index ec54e6834..457b798ec 100644 --- a/ts/state/ducks/mediaGallery.ts +++ b/ts/state/ducks/mediaGallery.ts @@ -15,7 +15,7 @@ import type { MIMEType } from '../../types/MIME'; import type { MediaItemType } from '../../types/MediaItem'; import type { StateType as RootStateType } from '../reducer'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { CONVERSATION_UNLOADED, MESSAGE_CHANGED, @@ -84,13 +84,13 @@ function loadMediaItems( const ourAci = window.textsecure.storage.user.getCheckedAci(); - const rawMedia = await dataInterface.getMessagesWithVisualMediaAttachments( + const rawMedia = await DataReader.getMessagesWithVisualMediaAttachments( conversationId, { limit: DEFAULT_MEDIA_FETCH_COUNT, } ); - const rawDocuments = await dataInterface.getMessagesWithFileAttachments( + const rawDocuments = await DataReader.getMessagesWithFileAttachments( conversationId, { limit: DEFAULT_DOCUMENTS_FETCH_COUNT, @@ -111,7 +111,7 @@ function loadMediaItems( const upgradedMsgAttributes = await upgradeMessageSchema(message); model.set(upgradedMsgAttributes); - await dataInterface.saveMessage(upgradedMsgAttributes, { ourAci }); + await DataWriter.saveMessage(upgradedMsgAttributes, { ourAci }); } }) ); diff --git a/ts/state/ducks/search.ts b/ts/state/ducks/search.ts index e532c5501..f6fba424f 100644 --- a/ts/state/ducks/search.ts +++ b/ts/state/ducks/search.ts @@ -7,11 +7,8 @@ import { debounce, omit, reject } from 'lodash'; import type { ReadonlyDeep } from 'type-fest'; import type { StateType as RootStateType } from '../reducer'; import { filterAndSortConversations } from '../../util/filterAndSortConversations'; -import type { - ClientSearchResultMessageType, - ClientInterface, -} from '../../sql/Interface'; -import dataInterface from '../../sql/Client'; +import type { ClientSearchResultMessageType } from '../../sql/Interface'; +import { DataReader } from '../../sql/Client'; import { makeLookup } from '../../util/makeLookup'; import { isNotNil } from '../../util/isNotNil'; import type { ServiceIdString } from '../../types/ServiceId'; @@ -44,7 +41,7 @@ import * as log from '../../logging/log'; import { searchConversationTitles } from '../../util/searchConversationTitles'; import { isDirectConversation } from '../../util/whatTypeOfConversation'; -const { searchMessages: dataSearchMessages }: ClientInterface = dataInterface; +const { searchMessages: dataSearchMessages } = DataReader; // State diff --git a/ts/state/ducks/stickers.ts b/ts/state/ducks/stickers.ts index 9ea7efb1b..5ea75084d 100644 --- a/ts/state/ducks/stickers.ts +++ b/ts/state/ducks/stickers.ts @@ -9,7 +9,7 @@ import type { StickerType as StickerDBType, StickerPackType as StickerPackDBType, } from '../../sql/Interface'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import type { RecentStickerType } from '../../types/Stickers'; import { downloadStickerPack as externalDownloadStickerPack, @@ -25,7 +25,8 @@ import type { NoopActionType } from './noop'; import type { BoundActionCreatorsMapObject } from '../../hooks/useBoundActions'; import { useBoundActions } from '../../hooks/useBoundActions'; -const { getRecentStickers, updateStickerLastUsed } = dataInterface; +const { getRecentStickers } = DataReader; +const { updateStickerLastUsed } = DataWriter; // State @@ -241,7 +242,7 @@ async function doInstallStickerPack( } = options; const timestamp = Date.now(); - await dataInterface.installStickerPack(packId, timestamp); + await DataWriter.installStickerPack(packId, timestamp); if (!fromSync && !fromStorageService && !fromBackup) { // Kick this off, but don't wait for it @@ -283,7 +284,7 @@ async function doUninstallStickerPack( const { fromSync = false, fromStorageService = false } = options; const timestamp = Date.now(); - await dataInterface.uninstallStickerPack(packId, timestamp); + await DataWriter.uninstallStickerPack(packId, timestamp); // If there are no more references, it should be removed await maybeDeletePack(packId); diff --git a/ts/state/ducks/stories.ts b/ts/state/ducks/stories.ts index 65a219b41..9306eb7fb 100644 --- a/ts/state/ducks/stories.ts +++ b/ts/state/ducks/stories.ts @@ -25,7 +25,7 @@ import { isAciString } from '../../util/isAciString'; import * as log from '../../logging/log'; import { TARGETED_CONVERSATION_CHANGED } from './conversations'; import { SIGNAL_ACI } from '../../types/SignalConversation'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SendStatus } from '../../messages/MessageSendState'; import { SafetyNumberChangeSource } from '../../components/SafetyNumberChangeDialog'; @@ -285,7 +285,7 @@ function deleteGroupStoryReply( messageId: string ): ThunkAction { return async dispatch => { - await window.Signal.Data.removeMessage(messageId, { singleProtoJobQueue }); + await DataWriter.removeMessage(messageId, { singleProtoJobQueue }); dispatch({ type: STORY_REPLY_DELETED, payload: messageId, @@ -337,7 +337,7 @@ function loadStoryReplies( ): ThunkAction { return async (dispatch, getState) => { const conversation = getConversationSelector(getState())(conversationId); - const replies = await dataInterface.getOlderMessagesByConversation({ + const replies = await DataReader.getOlderMessagesByConversation({ conversationId, limit: 9000, storyId: messageId, @@ -421,7 +421,7 @@ function markStoryRead( message.set(markViewed(message.attributes, storyReadDate)); drop( - dataInterface.saveMessage(message.attributes, { + DataWriter.saveMessage(message.attributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }) ); @@ -459,7 +459,7 @@ function markStoryRead( ); } - await dataInterface.addNewStoryRead({ + await DataWriter.addNewStoryRead({ authorId, conversationId: message.attributes.conversationId, storyId: messageId, @@ -1409,7 +1409,7 @@ function removeAllContactStories( log.info(`${logId}: removing ${messages.length} stories`); - await dataInterface.removeMessages(messageIds, { singleProtoJobQueue }); + await DataWriter.removeMessages(messageIds, { singleProtoJobQueue }); dispatch({ type: 'NOOP', diff --git a/ts/state/ducks/storyDistributionLists.ts b/ts/state/ducks/storyDistributionLists.ts index f2333089c..5eca889d3 100644 --- a/ts/state/ducks/storyDistributionLists.ts +++ b/ts/state/ducks/storyDistributionLists.ts @@ -10,7 +10,7 @@ import type { StoryDistributionWithMembersType } from '../../sql/Interface'; import type { StoryDistributionIdString } from '../../types/StoryDistributionId'; import type { ServiceIdString } from '../../types/ServiceId'; import * as log from '../../logging/log'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { MY_STORY_ID } from '../../types/Stories'; import { generateStoryDistributionId } from '../../types/StoryDistributionId'; import { deleteStoryForEveryone } from '../../util/deleteStoryForEveryone'; @@ -112,8 +112,9 @@ function allowsRepliesChanged( allowsReplies: boolean ): ThunkAction { return async dispatch => { - const storyDistribution = - await dataInterface.getStoryDistributionWithMembers(listId); + const storyDistribution = await DataReader.getStoryDistributionWithMembers( + listId + ); if (!storyDistribution) { log.warn( @@ -131,7 +132,7 @@ function allowsRepliesChanged( return; } - await dataInterface.modifyStoryDistribution({ + await DataWriter.modifyStoryDistribution({ ...storyDistribution, allowsReplies, storageNeedsSync: true, @@ -178,7 +179,7 @@ function createDistributionList( }; if (shouldSave) { - await dataInterface.createNewStoryDistribution(storyDistribution); + await DataWriter.createNewStoryDistribution(storyDistribution); } if (storyDistribution.storageNeedsSync) { @@ -207,15 +208,16 @@ function deleteDistributionList( return async (dispatch, getState) => { const deletedAtTimestamp = Date.now(); - const storyDistribution = - await dataInterface.getStoryDistributionWithMembers(listId); + const storyDistribution = await DataReader.getStoryDistributionWithMembers( + listId + ); if (!storyDistribution) { log.warn('No story distribution found for id', listId); return; } - await dataInterface.modifyStoryDistributionWithMembers( + await DataWriter.modifyStoryDistributionWithMembers( { ...storyDistribution, deletedAtTimestamp, @@ -266,7 +268,7 @@ function hideMyStoriesFrom( memberServiceIds: Array ): ThunkAction { return async dispatch => { - const myStories = await dataInterface.getStoryDistributionWithMembers( + const myStories = await DataReader.getStoryDistributionWithMembers( MY_STORY_ID ); @@ -279,7 +281,7 @@ function hideMyStoriesFrom( const toAdd = new Set(memberServiceIds); - await dataInterface.modifyStoryDistributionWithMembers( + await DataWriter.modifyStoryDistributionWithMembers( { ...myStories, isBlockList: true, @@ -315,8 +317,9 @@ function removeMembersFromDistributionList( return; } - const storyDistribution = - await dataInterface.getStoryDistributionWithMembers(listId); + const storyDistribution = await DataReader.getStoryDistributionWithMembers( + listId + ); if (!storyDistribution) { log.warn( @@ -343,7 +346,7 @@ function removeMembersFromDistributionList( await window.storage.put('hasSetMyStoriesPrivacy', true); } - await dataInterface.modifyStoryDistributionWithMembers( + await DataWriter.modifyStoryDistributionWithMembers( { ...storyDistribution, isBlockList, @@ -385,7 +388,7 @@ function setMyStoriesToAllSignalConnections(): ThunkAction< ResetMyStoriesActionType > { return async dispatch => { - const myStories = await dataInterface.getStoryDistributionWithMembers( + const myStories = await DataReader.getStoryDistributionWithMembers( MY_STORY_ID ); @@ -397,7 +400,7 @@ function setMyStoriesToAllSignalConnections(): ThunkAction< } if (myStories.isBlockList || myStories.members.length > 0) { - await dataInterface.modifyStoryDistributionWithMembers( + await DataWriter.modifyStoryDistributionWithMembers( { ...myStories, isBlockList: true, @@ -425,8 +428,9 @@ function updateStoryViewers( memberServiceIds: Array ): ThunkAction { return async dispatch => { - const storyDistribution = - await dataInterface.getStoryDistributionWithMembers(listId); + const storyDistribution = await DataReader.getStoryDistributionWithMembers( + listId + ); if (!storyDistribution) { log.warn( @@ -456,7 +460,7 @@ function updateStoryViewers( } }); - await dataInterface.modifyStoryDistributionWithMembers( + await DataWriter.modifyStoryDistributionWithMembers( { ...storyDistribution, isBlockList: false, @@ -489,7 +493,7 @@ function removeMemberFromAllDistributionLists( ): ThunkAction { return async dispatch => { const logId = `removeMemberFromAllDistributionLists(${member})`; - const lists = await dataInterface.getAllStoryDistributionsWithMembers(); + const lists = await DataReader.getAllStoryDistributionsWithMembers(); const listsWithMember = lists.filter(({ members }) => members.includes(member) diff --git a/ts/state/smart/App.tsx b/ts/state/smart/App.tsx index a06af5a33..1a59ce571 100644 --- a/ts/state/smart/App.tsx +++ b/ts/state/smart/App.tsx @@ -3,6 +3,7 @@ import React, { memo } from 'react'; import { useSelector } from 'react-redux'; import type { VerificationTransport } from '../../types/VerificationTransport'; +import { DataWriter } from '../../sql/Client'; import { App } from '../../components/App'; import OS from '../../util/os/osMain'; import { getConversation } from '../../util/getConversation'; @@ -101,7 +102,7 @@ async function uploadProfile({ us.set('profileName', firstName); us.set('profileFamilyName', lastName); us.captureChange('standaloneProfile'); - await window.Signal.Data.updateConversation(us.attributes); + await DataWriter.updateConversation(us.attributes); await writeProfile(getConversation(us), { keepAvatar: true, diff --git a/ts/state/smart/CallsTab.tsx b/ts/state/smart/CallsTab.tsx index 57f8ca0ef..a73416379 100644 --- a/ts/state/smart/CallsTab.tsx +++ b/ts/state/smart/CallsTab.tsx @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { memo, useCallback, useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; +import { DataReader } from '../../sql/Client'; import { useItemsActions } from '../ducks/items'; import { getNavTabsCollapsed, @@ -184,7 +185,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() { if (callHistoryFilter == null) { return 0; } - const count = await window.Signal.Data.getCallHistoryGroupsCount( + const count = await DataReader.getCallHistoryGroupsCount( callHistoryFilter ); return count; @@ -206,7 +207,7 @@ export const SmartCallsTab = memo(function SmartCallsTab() { if (callHistoryFilter == null) { return []; } - const results = await window.Signal.Data.getCallHistoryGroups( + const results = await DataReader.getCallHistoryGroups( callHistoryFilter, pagination ); diff --git a/ts/test-electron/ConversationController_test.ts b/ts/test-electron/ConversationController_test.ts index ceaa026be..78306cb3a 100644 --- a/ts/test-electron/ConversationController_test.ts +++ b/ts/test-electron/ConversationController_test.ts @@ -4,6 +4,7 @@ import { assert } from 'chai'; import { strictAssert } from '../util/assert'; +import { DataWriter } from '../sql/Client'; import type { ConversationModel } from '../models/conversations'; import type { AciString, PniString, ServiceIdString } from '../types/ServiceId'; @@ -32,7 +33,7 @@ describe('ConversationController', () => { ) => Promise; beforeEach(async () => { - await window.Signal.Data._removeAllConversations(); + await DataWriter._removeAllConversations(); window.ConversationController.reset(); await window.ConversationController.load(); diff --git a/ts/test-electron/MessageReceipts_test.ts b/ts/test-electron/MessageReceipts_test.ts index 458507ae3..1871161e7 100644 --- a/ts/test-electron/MessageReceipts_test.ts +++ b/ts/test-electron/MessageReceipts_test.ts @@ -6,6 +6,7 @@ import { assert } from 'chai'; import { type AciString, generateAci } from '../types/ServiceId'; import type { MessageAttributesType } from '../model-types'; +import { DataReader, DataWriter } from '../sql/Client'; import { SendStatus } from '../messages/MessageSendState'; import type { MessageReceiptAttributesType, @@ -77,7 +78,7 @@ describe('MessageReceipts', () => { }, }; - await window.Signal.Data.saveMessage(messageAttributes, { + await DataWriter.saveMessage(messageAttributes, { forceSave: true, ourAci, }); @@ -97,7 +98,7 @@ describe('MessageReceipts', () => { ), ]); - const messageFromDatabase = await window.Signal.Data.getMessageById(id); + const messageFromDatabase = await DataReader.getMessageById(id); const savedSendState = messageFromDatabase?.sendStateByConversationId; assert.equal(savedSendState?.aaaa.status, SendStatus.Read, 'aaaa'); @@ -154,11 +155,11 @@ describe('MessageReceipts', () => { ], }; - await window.Signal.Data.saveMessage(messageAttributes, { + await DataWriter.saveMessage(messageAttributes, { forceSave: true, ourAci, }); - await window.Signal.Data.saveEditedMessage(messageAttributes, ourAci, { + await DataWriter.saveEditedMessage(messageAttributes, ourAci, { conversationId: messageAttributes.conversationId, messageId: messageAttributes.id, readStatus: ReadStatus.Read, @@ -211,7 +212,7 @@ describe('MessageReceipts', () => { ), ]); - const messageFromDatabase = await window.Signal.Data.getMessageById(id); + const messageFromDatabase = await DataReader.getMessageById(id); const rootSendState = messageFromDatabase?.sendStateByConversationId; assert.deepEqual( diff --git a/ts/test-electron/SignalProtocolStore_test.ts b/ts/test-electron/SignalProtocolStore_test.ts index 4e1e7fdab..b293a8bc0 100644 --- a/ts/test-electron/SignalProtocolStore_test.ts +++ b/ts/test-electron/SignalProtocolStore_test.ts @@ -17,6 +17,7 @@ import { } from '@signalapp/libsignal-client'; import { v4 as generateUuid } from 'uuid'; +import { DataReader, DataWriter } from '../sql/Client'; import { signal } from '../protobuf/compiled'; import { sessionStructureToBytes } from '../util/sessionTranslation'; import * as durations from '../util/durations'; @@ -295,21 +296,21 @@ describe('SignalProtocolStore', () => { await store.saveIdentity(identifier, testKey.pubKey); }); it('marks the key firstUse', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert(identity.firstUse); }); it('sets the timestamp', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert(identity.timestamp); }); it('sets the verified status to DEFAULT', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -321,7 +322,7 @@ describe('SignalProtocolStore', () => { const oldTimestamp = Date.now(); before(async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, firstUse: true, @@ -334,14 +335,14 @@ describe('SignalProtocolStore', () => { await store.saveIdentity(identifier, newIdentity); }); it('marks the key not firstUse', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert(!identity.firstUse); }); it('updates the timestamp', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -350,7 +351,7 @@ describe('SignalProtocolStore', () => { describe('The previous verified status was DEFAULT', () => { before(async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, firstUse: true, @@ -363,9 +364,7 @@ describe('SignalProtocolStore', () => { await store.saveIdentity(identifier, newIdentity); }); it('sets the new key to default', async () => { - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -374,7 +373,7 @@ describe('SignalProtocolStore', () => { }); describe('The previous verified status was VERIFIED', () => { before(async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, firstUse: true, @@ -387,9 +386,7 @@ describe('SignalProtocolStore', () => { await store.saveIdentity(identifier, newIdentity); }); it('sets the new key to unverified', async () => { - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -401,7 +398,7 @@ describe('SignalProtocolStore', () => { }); describe('The previous verified status was UNVERIFIED', () => { before(async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, firstUse: true, @@ -414,9 +411,7 @@ describe('SignalProtocolStore', () => { await store.saveIdentity(identifier, newIdentity); }); it('sets the new key to unverified', async () => { - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -430,7 +425,7 @@ describe('SignalProtocolStore', () => { describe('When the key has not changed', () => { const oldTimestamp = Date.now(); before(async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: oldTimestamp, @@ -442,22 +437,18 @@ describe('SignalProtocolStore', () => { }); describe('If it is marked firstUse', () => { before(async () => { - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } identity.firstUse = true; - await window.Signal.Data.createOrUpdateIdentityKey(identity); + await DataWriter.createOrUpdateIdentityKey(identity); await store.hydrateCaches(); }); it('nothing changes', async () => { await store.saveIdentity(identifier, testKey.pubKey, true); - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -467,36 +458,30 @@ describe('SignalProtocolStore', () => { }); describe('If it is not marked firstUse', () => { before(async () => { - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } identity.firstUse = false; - await window.Signal.Data.createOrUpdateIdentityKey(identity); + await DataWriter.createOrUpdateIdentityKey(identity); await store.hydrateCaches(); }); describe('If nonblocking approval is required', () => { let now: number; before(async () => { now = Date.now(); - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } identity.timestamp = now; - await window.Signal.Data.createOrUpdateIdentityKey(identity); + await DataWriter.createOrUpdateIdentityKey(identity); await store.hydrateCaches(); }); it('sets non-blocking approval', async () => { await store.saveIdentity(identifier, testKey.pubKey, true); - const identity = await window.Signal.Data.getIdentityKeyById( - theirAci - ); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -532,35 +517,35 @@ describe('SignalProtocolStore', () => { }); it('publicKey is saved', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert.isTrue(constantTimeEqual(identity.publicKey, testKey.pubKey)); }); it('firstUse is saved', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert.strictEqual(identity.firstUse, true); }); it('timestamp is saved', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert.strictEqual(identity.timestamp, now); }); it('verified is saved', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED); }); it('nonblockingApproval is saved', async () => { - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -607,7 +592,7 @@ describe('SignalProtocolStore', () => { describe('setApproval', () => { it('sets nonblockingApproval', async () => { await store.setApproval(theirAci, true); - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -617,7 +602,7 @@ describe('SignalProtocolStore', () => { }); describe('setVerified', () => { async function saveRecordDefault() { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, firstUse: true, @@ -632,7 +617,7 @@ describe('SignalProtocolStore', () => { it('updates the verified status', async () => { await store.setVerified(theirAci, store.VerifiedStatus.VERIFIED); - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -646,7 +631,7 @@ describe('SignalProtocolStore', () => { it('updates the verified status', async () => { await store.setVerified(theirAci, store.VerifiedStatus.VERIFIED); - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -667,7 +652,7 @@ describe('SignalProtocolStore', () => { keychangeTriggered += 1; }); - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: Date.now() - 10 * 1000 * 60, @@ -693,7 +678,7 @@ describe('SignalProtocolStore', () => { assert.isFalse(needsNotification); assert.strictEqual(keychangeTriggered, 0); - const identity = await window.Signal.Data.getIdentityKeyById(newAci); + const identity = await DataReader.getIdentityKeyById(newAci); if (!identity) { throw new Error('Missing identity!'); } @@ -712,7 +697,7 @@ describe('SignalProtocolStore', () => { assert.isTrue(needsNotification); assert.strictEqual(keychangeTriggered, 0); - const identity = await window.Signal.Data.getIdentityKeyById(newAci); + const identity = await DataReader.getIdentityKeyById(newAci); if (!identity) { throw new Error('Missing identity!'); } @@ -729,7 +714,7 @@ describe('SignalProtocolStore', () => { assert.isFalse(needsNotification); assert.strictEqual(keychangeTriggered, 1); - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -746,7 +731,7 @@ describe('SignalProtocolStore', () => { assert.isTrue(needsNotification); assert.strictEqual(keychangeTriggered, 0); - const identity = await window.Signal.Data.getIdentityKeyById(theirAci); + const identity = await DataReader.getIdentityKeyById(theirAci); if (!identity) { throw new Error('Missing identity!'); } @@ -757,7 +742,7 @@ describe('SignalProtocolStore', () => { describe('isUntrusted', () => { it('returns false if identity key old enough', async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: Date.now() - 10 * 1000 * 60, @@ -772,7 +757,7 @@ describe('SignalProtocolStore', () => { }); it('returns false if new but nonblockingApproval is true', async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: Date.now(), @@ -787,7 +772,7 @@ describe('SignalProtocolStore', () => { }); it('returns false if new but firstUse is true', async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: Date.now(), @@ -802,7 +787,7 @@ describe('SignalProtocolStore', () => { }); it('returns true if new, and no flags are set', async () => { - await window.Signal.Data.createOrUpdateIdentityKey({ + await DataWriter.createOrUpdateIdentityKey({ id: theirAci, publicKey: testKey.pubKey, timestamp: Date.now(), diff --git a/ts/test-electron/backup/attachments_test.ts b/ts/test-electron/backup/attachments_test.ts index 15c5ae8b1..abd8254d2 100644 --- a/ts/test-electron/backup/attachments_test.ts +++ b/ts/test-electron/backup/attachments_test.ts @@ -10,7 +10,7 @@ import { assert } from 'chai'; import type { ConversationModel } from '../../models/conversations'; import * as Bytes from '../../Bytes'; -import Data from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import { type AciString, generateAci } from '../../types/ServiceId'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; @@ -39,8 +39,8 @@ describe('backup/attachments', () => { let contactA: ConversationModel; beforeEach(async () => { - await Data._removeAllMessages(); - await Data._removeAllConversations(); + await DataWriter._removeAllMessages(); + await DataWriter._removeAllConversations(); window.storage.reset(); await setupBasics(); diff --git a/ts/test-electron/backup/backup_groupv2_notifications_test.ts b/ts/test-electron/backup/backup_groupv2_notifications_test.ts index 87ea4d678..0dbcd2b36 100644 --- a/ts/test-electron/backup/backup_groupv2_notifications_test.ts +++ b/ts/test-electron/backup/backup_groupv2_notifications_test.ts @@ -3,7 +3,7 @@ import { v4 as generateGuid } from 'uuid'; -import Data from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import { SignalService as Proto } from '../../protobuf'; import { generateAci, generatePni } from '../../types/ServiceId'; @@ -73,8 +73,8 @@ function createMessage( describe('backup/groupv2/notifications', () => { beforeEach(async () => { - await Data._removeAllMessages(); - await Data._removeAllConversations(); + await DataWriter._removeAllMessages(); + await DataWriter._removeAllConversations(); window.storage.reset(); await setupBasics(); diff --git a/ts/test-electron/backup/bubble_test.ts b/ts/test-electron/backup/bubble_test.ts index 9a53bcbc3..17182ba80 100644 --- a/ts/test-electron/backup/bubble_test.ts +++ b/ts/test-electron/backup/bubble_test.ts @@ -7,7 +7,7 @@ import { SendStatus } from '../../messages/MessageSendState'; import type { ConversationModel } from '../../models/conversations'; import { GiftBadgeStates } from '../../components/conversation/Message'; -import Data from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import { getRandomBytes } from '../../Crypto'; import * as Bytes from '../../Bytes'; import { generateAci } from '../../types/ServiceId'; @@ -39,8 +39,8 @@ describe('backup/bubble messages', () => { let gv1: ConversationModel; beforeEach(async () => { - await Data._removeAllMessages(); - await Data._removeAllConversations(); + await DataWriter._removeAllMessages(); + await DataWriter._removeAllConversations(); window.storage.reset(); await setupBasics(); diff --git a/ts/test-electron/backup/filePointer_test.ts b/ts/test-electron/backup/filePointer_test.ts index f452c41bd..b65b9ec5a 100644 --- a/ts/test-electron/backup/filePointer_test.ts +++ b/ts/test-electron/backup/filePointer_test.ts @@ -5,6 +5,7 @@ import Long from 'long'; import { join } from 'path'; import * as sinon from 'sinon'; import { BackupLevel } from '@signalapp/libsignal-client/zkgroup'; +import { DataWriter } from '../../sql/Client'; import { Backups } from '../../protobuf'; import { getFilePointerForAttachment, @@ -552,7 +553,7 @@ describe('getBackupJobForAttachmentAndFilePointer', async () => { await window.storage.put('masterKey', Bytes.toBase64(getRandomBytes(32))); }); afterEach(async () => { - await window.Signal.Data.removeAll(); + await DataWriter.removeAll(); }); const attachment = composeAttachment(); diff --git a/ts/test-electron/backup/helpers.ts b/ts/test-electron/backup/helpers.ts index 0dc87d0b1..f29a6ed24 100644 --- a/ts/test-electron/backup/helpers.ts +++ b/ts/test-electron/backup/helpers.ts @@ -23,7 +23,7 @@ import type { import { backupsService } from '../../services/backups'; import { isUnsupportedMessage } from '../../state/selectors/message'; import { generateAci, generatePni } from '../../types/ServiceId'; -import Data from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { getRandomBytes } from '../../Crypto'; import * as Bytes from '../../Bytes'; @@ -145,7 +145,7 @@ export async function symmetricRoundtripHarness( } async function updateConvoIdToTitle() { - const all = await Data.getAllConversations(); + const all = await DataReader.getAllConversations(); for (const convo of all) { CONVO_ID_TO_STABLE_ID.set( convo.id, @@ -167,7 +167,7 @@ export async function asymmetricRoundtripHarness( try { const targetOutputFile = path.join(outDir, 'backup.bin'); - await Data.saveMessages(before, { forceSave: true, ourAci: OUR_ACI }); + await DataWriter.saveMessages(before, { forceSave: true, ourAci: OUR_ACI }); await backupsService.exportToDisk(targetOutputFile, options.backupLevel); @@ -177,7 +177,7 @@ export async function asymmetricRoundtripHarness( await backupsService.importBackup(() => createReadStream(targetOutputFile)); - const messagesFromDatabase = await Data._getAllMessages(); + const messagesFromDatabase = await DataReader._getAllMessages(); await updateConvoIdToTitle(); @@ -199,9 +199,9 @@ export async function asymmetricRoundtripHarness( } async function clearData() { - await Data._removeAllMessages(); - await Data._removeAllConversations(); - await Data.removeAllItems(); + await DataWriter._removeAllMessages(); + await DataWriter._removeAllConversations(); + await DataWriter.removeAllItems(); window.storage.reset(); window.ConversationController.reset(); diff --git a/ts/test-electron/backup/non_bubble_test.ts b/ts/test-electron/backup/non_bubble_test.ts index 063c9ef4c..45ccbeda4 100644 --- a/ts/test-electron/backup/non_bubble_test.ts +++ b/ts/test-electron/backup/non_bubble_test.ts @@ -9,7 +9,7 @@ import type { ConversationModel } from '../../models/conversations'; import { getRandomBytes } from '../../Crypto'; import * as Bytes from '../../Bytes'; import { SignalService as Proto, Backups } from '../../protobuf'; -import Data from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import { PaymentEventKind } from '../../types/Payment'; import { ContactFormType } from '../../types/EmbeddedContact'; @@ -32,8 +32,8 @@ describe('backup/non-bubble messages', () => { let group: ConversationModel; beforeEach(async () => { - await Data._removeAllMessages(); - await Data._removeAllConversations(); + await DataWriter._removeAllMessages(); + await DataWriter._removeAllConversations(); window.storage.reset(); await setupBasics(); diff --git a/ts/test-electron/models/conversations_test.ts b/ts/test-electron/models/conversations_test.ts index 707864aef..735f8a193 100644 --- a/ts/test-electron/models/conversations_test.ts +++ b/ts/test-electron/models/conversations_test.ts @@ -4,6 +4,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; +import { DataWriter } from '../../sql/Client'; import { SendStatus } from '../../messages/MessageSendState'; import { IMAGE_PNG } from '../../types/MIME'; import { generateAci, generatePni } from '../../types/ServiceId'; @@ -79,7 +80,7 @@ describe('Conversations', () => { }); // Saving to db and updating the convo's last message - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { forceSave: true, ourAci, }); @@ -88,7 +89,7 @@ describe('Conversations', () => { message, 'test' ); - await window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); await conversation.updateLastMessage(); // Should be set to bananas because that's the last message sent. diff --git a/ts/test-electron/models/messages_test.ts b/ts/test-electron/models/messages_test.ts index 32012c927..39152dd22 100644 --- a/ts/test-electron/models/messages_test.ts +++ b/ts/test-electron/models/messages_test.ts @@ -13,6 +13,7 @@ import type { MessageModel } from '../../models/messages'; import type { RawBodyRange } from '../../types/BodyRange'; import type { StorageAccessType } from '../../types/Storage.d'; import type { WebAPIType } from '../../textsecure/WebAPI'; +import { DataWriter } from '../../sql/Client'; import MessageSender from '../../textsecure/SendMessage'; import enMessages from '../../../_locales/en/messages.json'; import { SendStatus } from '../../messages/MessageSendState'; @@ -78,7 +79,7 @@ describe('Message', () => { }); after(async () => { - await window.Signal.Data.removeAll(); + await DataWriter.removeAll(); await window.storage.fetch(); await Promise.all( diff --git a/ts/test-electron/services/AttachmentBackupManager_test.ts b/ts/test-electron/services/AttachmentBackupManager_test.ts index 461e040f7..2d5f6f58b 100644 --- a/ts/test-electron/services/AttachmentBackupManager_test.ts +++ b/ts/test-electron/services/AttachmentBackupManager_test.ts @@ -19,7 +19,7 @@ import type { StandardAttachmentBackupJobType, ThumbnailAttachmentBackupJobType, } from '../../types/AttachmentBackup'; -import dataInterface from '../../sql/Client'; +import { DataWriter } from '../../sql/Client'; import { getRandomBytes } from '../../Crypto'; import { APPLICATION_OCTET_STREAM, VIDEO_MP4 } from '../../types/MIME'; import { createName, getRelativePath } from '../../util/attachmentPath'; @@ -98,7 +98,7 @@ describe('AttachmentBackupManager/JobManager', () => { } beforeEach(async () => { - await dataInterface.removeAll(); + await DataWriter.removeAll(); await window.storage.put('masterKey', Bytes.toBase64(getRandomBytes(32))); sandbox = sinon.createSandbox(); @@ -214,7 +214,7 @@ describe('AttachmentBackupManager/JobManager', () => { } async function getAllSavedJobs(): Promise> { - return dataInterface.getNextAttachmentBackupJobs({ + return DataWriter.getNextAttachmentBackupJobs({ limit: 1000, timestamp: Infinity, }); diff --git a/ts/test-electron/services/AttachmentDownloadManager_test.ts b/ts/test-electron/services/AttachmentDownloadManager_test.ts index c03b0ea75..736f2ad82 100644 --- a/ts/test-electron/services/AttachmentDownloadManager_test.ts +++ b/ts/test-electron/services/AttachmentDownloadManager_test.ts @@ -15,7 +15,7 @@ import { type NewAttachmentDownloadJobType, } from '../../jobs/AttachmentDownloadManager'; import type { AttachmentDownloadJobType } from '../../types/AttachmentDownload'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { MINUTE } from '../../util/durations'; import { type AciString } from '../../types/ServiceId'; import { type AttachmentType, AttachmentVariant } from '../../types/Attachment'; @@ -60,7 +60,7 @@ describe('AttachmentDownloadManager/JobManager', () => { let isInCall: sinon.SinonStub; beforeEach(async () => { - await dataInterface.removeAll(); + await DataWriter.removeAll(); sandbox = sinon.createSandbox(); clock = sandbox.useFakeTimers(); @@ -99,7 +99,7 @@ describe('AttachmentDownloadManager/JobManager', () => { urgency: AttachmentDownloadUrgency ) { // Save message first to satisfy foreign key constraint - await dataInterface.saveMessage( + await DataWriter.saveMessage( { id: job.messageId, type: 'incoming', @@ -162,13 +162,13 @@ describe('AttachmentDownloadManager/JobManager', () => { // first. In cases like maybeStartJobs where we prevent re-entrancy, without this, // prior (unfinished) invocations can prevent subsequent calls after the clock is // ticked forward and make tests unreliable - await dataInterface.getAllItems(); + await DataReader.getAllItems(); const now = Date.now(); while (Date.now() < now + ms) { // eslint-disable-next-line no-await-in-loop await clock.tickAsync(downloadManager?.tickInterval ?? 1000); // eslint-disable-next-line no-await-in-loop - await dataInterface.getAllItems(); + await DataReader.getAllItems(); } } @@ -187,7 +187,7 @@ describe('AttachmentDownloadManager/JobManager', () => { it('runs 3 jobs at a time in descending receivedAt order', async () => { const jobs = await addJobs(5); // Confirm they are saved to DB - const allJobs = await dataInterface.getNextAttachmentDownloadJobs({ + const allJobs = await DataWriter.getNextAttachmentDownloadJobs({ limit: 100, }); @@ -298,8 +298,8 @@ describe('AttachmentDownloadManager/JobManager', () => { assert.strictEqual(runJob.callCount, 2); assertRunJobCalledWith([jobs[1], jobs[0]]); - const retriedJob = await dataInterface.getAttachmentDownloadJob(jobs[1]); - const finishedJob = await dataInterface.getAttachmentDownloadJob(jobs[0]); + const retriedJob = await DataReader.getAttachmentDownloadJob(jobs[1]); + const finishedJob = await DataReader.getAttachmentDownloadJob(jobs[0]); assert.isUndefined(finishedJob); assert.strictEqual(retriedJob?.attempts, 1); @@ -332,7 +332,7 @@ describe('AttachmentDownloadManager/JobManager', () => { ]); // Ensure it's been removed after completed - assert.isUndefined(await dataInterface.getAttachmentDownloadJob(jobs[1])); + assert.isUndefined(await DataReader.getAttachmentDownloadJob(jobs[1])); }); it('will reset attempts if addJob is called again', async () => { @@ -382,7 +382,7 @@ describe('AttachmentDownloadManager/JobManager', () => { assert.strictEqual(runJob.callCount, 8); // Ensure it's been removed - assert.isUndefined(await dataInterface.getAttachmentDownloadJob(jobs[0])); + assert.isUndefined(await DataReader.getAttachmentDownloadJob(jobs[0])); }); }); diff --git a/ts/test-electron/services/MessageCache_test.ts b/ts/test-electron/services/MessageCache_test.ts index 7f8b8f83f..23ca61ac7 100644 --- a/ts/test-electron/services/MessageCache_test.ts +++ b/ts/test-electron/services/MessageCache_test.ts @@ -4,6 +4,7 @@ import { assert } from 'chai'; import { v4 as uuid } from 'uuid'; import type { MessageAttributesType } from '../../model-types.d'; +import { DataReader, DataWriter } from '../../sql/Client'; import { MessageModel } from '../../models/messages'; import { strictAssert } from '../../util/assert'; @@ -358,7 +359,7 @@ describe('MessageCache', () => { timestamp: Date.now(), type: 'incoming', }; - await window.Signal.Data.saveMessage(messageAttributes, { + await DataWriter.saveMessage(messageAttributes, { forceSave: true, ourAci, }); @@ -379,7 +380,7 @@ describe('MessageCache', () => { skipSaveToDatabase: false, }); - const messageFromDatabase = await window.Signal.Data.getMessageById(id); + const messageFromDatabase = await DataReader.getMessageById(id); assert.deepEqual(newAttributes, messageFromDatabase); }); diff --git a/ts/test-electron/sql/allMedia_test.ts b/ts/test-electron/sql/allMedia_test.ts index 8f30dc8f9..ead09f541 100644 --- a/ts/test-electron/sql/allMedia_test.ts +++ b/ts/test-electron/sql/allMedia_test.ts @@ -4,18 +4,17 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; const { - removeAll, _getAllMessages, - saveMessages, getMessagesWithVisualMediaAttachments, getMessagesWithFileAttachments, -} = dataInterface; +} = DataReader; +const { removeAll, saveMessages } = DataWriter; describe('sql/allMedia', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/conversationSummary_test.ts b/ts/test-electron/sql/conversationSummary_test.ts index e1c73a235..fec1be2af 100644 --- a/ts/test-electron/sql/conversationSummary_test.ts +++ b/ts/test-electron/sql/conversationSummary_test.ts @@ -4,18 +4,14 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import { DurationInSeconds } from '../../util/durations'; import type { MessageAttributesType } from '../../model-types.d'; -const { - removeAll, - _getAllMessages, - saveMessages, - getConversationMessageStats, -} = dataInterface; +const { _getAllMessages, getConversationMessageStats } = DataReader; +const { removeAll, saveMessages } = DataWriter; describe('sql/conversationSummary', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/fullTextSearch_test.ts b/ts/test-electron/sql/fullTextSearch_test.ts index de0c629d1..1dc7923c5 100644 --- a/ts/test-electron/sql/fullTextSearch_test.ts +++ b/ts/test-electron/sql/fullTextSearch_test.ts @@ -4,18 +4,13 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; -const { - removeAll, - _getAllMessages, - saveMessages, - saveMessage, - searchMessages, -} = dataInterface; +const { _getAllMessages, searchMessages } = DataReader; +const { removeAll, saveMessages, saveMessage } = DataWriter; describe('sql/searchMessages', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/getCallHistoryGroups_test.ts b/ts/test-electron/sql/getCallHistoryGroups_test.ts index cadf52f8f..c264c4a65 100644 --- a/ts/test-electron/sql/getCallHistoryGroups_test.ts +++ b/ts/test-electron/sql/getCallHistoryGroups_test.ts @@ -4,7 +4,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { CallMode } from '../../types/Calling'; import { generateAci } from '../../types/ServiceId'; @@ -28,14 +28,9 @@ import { FAKE_CALL_LINK_WITH_ADMIN_KEY, } from '../../test-both/helpers/fakeCallLink'; -const { - removeAll, - getCallHistoryGroups, - getCallHistoryGroupsCount, - insertCallLink, - saveCallHistory, - saveConversation, -} = dataInterface; +const { getCallHistoryGroups, getCallHistoryGroupsCount } = DataReader; +const { removeAll, insertCallLink, saveCallHistory, saveConversation } = + DataWriter; function toGroup(calls: Array): CallHistoryGroup { const firstCall = calls.at(0); diff --git a/ts/test-electron/sql/getCallHistoryMessageByCallId_test.ts b/ts/test-electron/sql/getCallHistoryMessageByCallId_test.ts index f786a6dc8..d2380ea48 100644 --- a/ts/test-electron/sql/getCallHistoryMessageByCallId_test.ts +++ b/ts/test-electron/sql/getCallHistoryMessageByCallId_test.ts @@ -4,17 +4,13 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; -const { - removeAll, - _getAllMessages, - saveMessages, - getCallHistoryMessageByCallId, -} = dataInterface; +const { _getAllMessages, getCallHistoryMessageByCallId } = DataReader; +const { removeAll, saveMessages } = DataWriter; describe('sql/getCallHistoryMessageByCallId', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/getMessagesBetween_test.ts b/ts/test-electron/sql/getMessagesBetween_test.ts index 9e504171c..c49306dc2 100644 --- a/ts/test-electron/sql/getMessagesBetween_test.ts +++ b/ts/test-electron/sql/getMessagesBetween_test.ts @@ -5,16 +5,12 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; import { generateAci } from '../../types/ServiceId'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import type { MessageAttributesType } from '../../model-types'; -const { - saveMessages, - _getAllMessages, - _removeAllMessages, - getMessagesBetween, -} = dataInterface; +const { _getAllMessages, getMessagesBetween } = DataReader; +const { saveMessages, _removeAllMessages } = DataWriter; describe('sql/getMessagesBetween', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/getNearbyMessageFromDeletedSet_test.ts b/ts/test-electron/sql/getNearbyMessageFromDeletedSet_test.ts index d3888ed8e..3b0343783 100644 --- a/ts/test-electron/sql/getNearbyMessageFromDeletedSet_test.ts +++ b/ts/test-electron/sql/getNearbyMessageFromDeletedSet_test.ts @@ -4,17 +4,13 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types'; -const { - saveMessages, - _getAllMessages, - _removeAllMessages, - getNearbyMessageFromDeletedSet, -} = dataInterface; +const { _getAllMessages, getNearbyMessageFromDeletedSet } = DataReader; +const { saveMessages, _removeAllMessages } = DataWriter; describe('sql/getNearbyMessageFromDeletedSet', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/getRecentStaleRingsAndMarkOlderMissed_test.ts b/ts/test-electron/sql/getRecentStaleRingsAndMarkOlderMissed_test.ts index 389b2d6e1..dd0bf72df 100644 --- a/ts/test-electron/sql/getRecentStaleRingsAndMarkOlderMissed_test.ts +++ b/ts/test-electron/sql/getRecentStaleRingsAndMarkOlderMissed_test.ts @@ -5,7 +5,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; import { times } from 'lodash'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { CallMode } from '../../types/Calling'; import { generateAci } from '../../types/ServiceId'; @@ -17,12 +17,9 @@ import { } from '../../types/CallDisposition'; import type { MaybeStaleCallHistory } from '../../sql/Server'; -const { - removeAll, - getRecentStaleRingsAndMarkOlderMissed, - saveCallHistory, - getAllCallHistory, -} = dataInterface; +const { getAllCallHistory } = DataReader; +const { getRecentStaleRingsAndMarkOlderMissed, removeAll, saveCallHistory } = + DataWriter; describe('sql/getRecentStaleRingsAndMarkOlderMissed', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/getRecentStoryReplies_test.ts b/ts/test-electron/sql/getRecentStoryReplies_test.ts index 4322fdf73..c0dc13792 100644 --- a/ts/test-electron/sql/getRecentStoryReplies_test.ts +++ b/ts/test-electron/sql/getRecentStoryReplies_test.ts @@ -4,13 +4,13 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; -const { _getAllMessages, getRecentStoryReplies, removeAll, saveMessages } = - dataInterface; +const { _getAllMessages, getRecentStoryReplies } = DataReader; +const { removeAll, saveMessages } = DataWriter; describe('sql/getRecentStoryReplies', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/markRead_test.ts b/ts/test-electron/sql/markRead_test.ts index 9d82bb9f2..d77a74958 100644 --- a/ts/test-electron/sql/markRead_test.ts +++ b/ts/test-electron/sql/markRead_test.ts @@ -4,7 +4,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { ReactionType } from '../../types/Reactions'; @@ -13,17 +13,16 @@ import { DurationInSeconds } from '../../util/durations'; import type { MessageAttributesType } from '../../model-types.d'; import { ReadStatus } from '../../messages/MessageReadStatus'; +const { _getAllReactions, _getAllMessages, getTotalUnreadForConversation } = + DataReader; const { _removeAllMessages, _removeAllReactions, - _getAllReactions, - _getAllMessages, addReaction, saveMessages, - getTotalUnreadForConversation, getUnreadByConversationAndMarkRead, getUnreadReactionsAndMarkRead, -} = dataInterface; +} = DataWriter; const UNREAD_REACTION = { readStatus: ReactionReadStatus.Unread }; diff --git a/ts/test-electron/sql/sendLog_test.ts b/ts/test-electron/sql/sendLog_test.ts index 4d87d726b..dffd2699d 100644 --- a/ts/test-electron/sql/sendLog_test.ts +++ b/ts/test-electron/sql/sendLog_test.ts @@ -4,7 +4,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import { constantTimeEqual, getRandomBytes } from '../../Crypto'; import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue'; @@ -12,17 +12,19 @@ import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue'; const { _getAllSentProtoMessageIds, _getAllSentProtoRecipients, + getAllSentProtos, +} = DataReader; +const { deleteSentProtoByMessageId, deleteSentProtoRecipient, deleteSentProtosOlderThan, - getAllSentProtos, getSentProtoByRecipient, insertProtoRecipients, insertSentProto, removeAllSentProtos, removeMessage, saveMessage, -} = dataInterface; +} = DataWriter; describe('sql/sendLog', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/stories_test.ts b/ts/test-electron/sql/stories_test.ts index 30edcb380..88a6cf7f5 100644 --- a/ts/test-electron/sql/stories_test.ts +++ b/ts/test-electron/sql/stories_test.ts @@ -4,13 +4,13 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; -const { removeAll, _getAllMessages, saveMessages, getAllStories } = - dataInterface; +const { _getAllMessages, getAllStories } = DataReader; +const { removeAll, saveMessages } = DataWriter; describe('sql/stories', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/storyDistribution_test.ts b/ts/test-electron/sql/storyDistribution_test.ts index 7e8912661..c482852f9 100644 --- a/ts/test-electron/sql/storyDistribution_test.ts +++ b/ts/test-electron/sql/storyDistribution_test.ts @@ -4,23 +4,26 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import { generateStoryDistributionId } from '../../types/StoryDistributionId'; import type { StoryDistributionWithMembersType } from '../../sql/Interface'; const { - _deleteAllStoryDistributions, _getAllStoryDistributionMembers, _getAllStoryDistributions, + getAllStoryDistributionsWithMembers, +} = DataReader; + +const { + _deleteAllStoryDistributions, createNewStoryDistribution, deleteStoryDistribution, - getAllStoryDistributionsWithMembers, modifyStoryDistribution, modifyStoryDistributionMembers, modifyStoryDistributionWithMembers, -} = dataInterface; +} = DataWriter; describe('sql/storyDistribution', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/storyReads_test.ts b/ts/test-electron/sql/storyReads_test.ts index ff1870143..11a8683c3 100644 --- a/ts/test-electron/sql/storyReads_test.ts +++ b/ts/test-electron/sql/storyReads_test.ts @@ -4,17 +4,14 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { StoryReadType } from '../../sql/Interface'; -const { - _getAllStoryReads, - _deleteAllStoryReads, - addNewStoryRead, - getLastStoryReadsForAuthor, -} = dataInterface; +const { _getAllStoryReads, getLastStoryReadsForAuthor } = DataReader; + +const { _deleteAllStoryReads, addNewStoryRead } = DataWriter; describe('sql/storyReads', () => { beforeEach(async () => { diff --git a/ts/test-electron/sql/timelineFetches_test.ts b/ts/test-electron/sql/timelineFetches_test.ts index ab036e4f6..e89e1e37f 100644 --- a/ts/test-electron/sql/timelineFetches_test.ts +++ b/ts/test-electron/sql/timelineFetches_test.ts @@ -4,22 +4,22 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; -import dataInterface from '../../sql/Client'; +import { DataReader, DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import type { MessageAttributesType } from '../../model-types.d'; import { ReadStatus } from '../../messages/MessageReadStatus'; const { - removeAll, _getAllMessages, - saveMessages, getMessageMetricsForConversation, getNewerMessagesByConversation, getOlderMessagesByConversation, getTotalUnreadMentionsOfMeForConversation, getOldestUnreadMentionOfMeForConversation, -} = dataInterface; +} = DataReader; + +const { removeAll, saveMessages } = DataWriter; describe('sql/timelineFetches', () => { beforeEach(async () => { diff --git a/ts/test-electron/state/ducks/stories_test.ts b/ts/test-electron/state/ducks/stories_test.ts index 4d9cbc17f..faa935717 100644 --- a/ts/test-electron/state/ducks/stories_test.ts +++ b/ts/test-electron/state/ducks/stories_test.ts @@ -5,6 +5,7 @@ import * as sinon from 'sinon'; import casual from 'casual'; import { v4 as generateUuid } from 'uuid'; +import { DataWriter } from '../../../sql/Client'; import type { DispatchableViewStoryType, StoryDataType, @@ -937,7 +938,7 @@ describe('both/state/ducks/stories', () => { }, ], }; - await window.Signal.Data.saveMessage(messageAttributes, { + await DataWriter.saveMessage(messageAttributes, { forceSave: true, ourAci: generateAci(), }); @@ -1003,7 +1004,7 @@ describe('both/state/ducks/stories', () => { preview: [preview], }; - await window.Signal.Data.saveMessage(messageAttributes, { + await DataWriter.saveMessage(messageAttributes, { forceSave: true, ourAci: generateAci(), }); diff --git a/ts/test-electron/textsecure/KeyChangeListener_test.ts b/ts/test-electron/textsecure/KeyChangeListener_test.ts index ea78279b0..dbbd4db08 100644 --- a/ts/test-electron/textsecure/KeyChangeListener_test.ts +++ b/ts/test-electron/textsecure/KeyChangeListener_test.ts @@ -3,6 +3,7 @@ import { assert } from 'chai'; +import { DataWriter } from '../../sql/Client'; import { getRandomBytes } from '../../Crypto'; import { Address } from '../../types/Address'; import { generateAci } from '../../types/ServiceId'; @@ -37,7 +38,7 @@ describe('KeyChangeListener', () => { }); after(async () => { - await window.Signal.Data.removeAll(); + await DataWriter.removeAll(); const { storage } = window.textsecure; await storage.fetch(); @@ -68,11 +69,11 @@ describe('KeyChangeListener', () => { }); afterEach(async () => { - await window.Signal.Data.removeMessagesInConversation(convo.id, { + await DataWriter.removeMessagesInConversation(convo.id, { logId: ourServiceIdWithKeyChange, singleProtoJobQueue, }); - await window.Signal.Data.removeConversation(convo.id); + await DataWriter.removeConversation(convo.id); await store.removeIdentityKey(ourServiceIdWithKeyChange); }); @@ -106,11 +107,11 @@ describe('KeyChangeListener', () => { }); afterEach(async () => { - await window.Signal.Data.removeMessagesInConversation(groupConvo.id, { + await DataWriter.removeMessagesInConversation(groupConvo.id, { logId: ourServiceIdWithKeyChange, singleProtoJobQueue, }); - await window.Signal.Data.removeConversation(groupConvo.id); + await DataWriter.removeConversation(groupConvo.id); }); it('generates a key change notice in the group conversation with this contact', async () => { diff --git a/ts/test-electron/updateConversationsWithUuidLookup_test.ts b/ts/test-electron/updateConversationsWithUuidLookup_test.ts index 9970f5d49..a0de713f4 100644 --- a/ts/test-electron/updateConversationsWithUuidLookup_test.ts +++ b/ts/test-electron/updateConversationsWithUuidLookup_test.ts @@ -6,6 +6,7 @@ import { assert } from 'chai'; import { v4 as generateUuid } from 'uuid'; import sinon from 'sinon'; +import { DataWriter } from '../sql/Client'; import { ConversationModel } from '../models/conversations'; import type { ConversationAttributesType } from '../model-types.d'; import type { WebAPIType } from '../textsecure/WebAPI'; @@ -151,7 +152,7 @@ describe('updateConversationsWithUuidLookup', () => { beforeEach(() => { sinonSandbox = sinon.createSandbox(); - sinonSandbox.stub(window.Signal.Data, 'updateConversation'); + sinonSandbox.stub(DataWriter, 'updateConversation'); fakeCdsLookup = sinonSandbox.stub().resolves({ entries: new Map(), diff --git a/ts/test-electron/util/downloadAttachment_test.ts b/ts/test-electron/util/downloadAttachment_test.ts index 90539dd0b..3fc085770 100644 --- a/ts/test-electron/util/downloadAttachment_test.ts +++ b/ts/test-electron/util/downloadAttachment_test.ts @@ -3,6 +3,7 @@ import { assert } from 'chai'; import * as sinon from 'sinon'; +import { DataWriter } from '../../sql/Client'; import { IMAGE_PNG } from '../../types/MIME'; import { AttachmentPermanentlyUndownloadableError, @@ -259,7 +260,7 @@ describe('getCdnNumberForBackupTier', () => { }); afterEach(async () => { - await window.Signal.Data.clearAllBackupCdnObjectMetadata(); + await DataWriter.clearAllBackupCdnObjectMetadata(); sandbox.restore(); }); @@ -282,7 +283,7 @@ describe('getCdnNumberForBackupTier', () => { assert.equal(result, 3); }); it('uses cdn number in DB if none on attachment', async () => { - await window.Signal.Data.saveBackupCdnObjectMetadata([ + await DataWriter.saveBackupCdnObjectMetadata([ { mediaId: getMediaIdFromMediaName('mediaName').string, cdnNumber: 42, diff --git a/ts/test-node/sql/helpers.ts b/ts/test-node/sql/helpers.ts index cb15c8fbc..0ea81e623 100644 --- a/ts/test-node/sql/helpers.ts +++ b/ts/test-node/sql/helpers.ts @@ -2,12 +2,17 @@ // SPDX-License-Identifier: AGPL-3.0-only import { noop } from 'lodash'; -import type { Database } from '@signalapp/better-sqlite3'; +import SQL from '@signalapp/better-sqlite3'; +import type { ReadableDB, WritableDB } from '../../sql/Interface'; import { SCHEMA_VERSIONS } from '../../sql/migrations'; import { consoleLogger } from '../../util/consoleLogger'; -export function updateToVersion(db: Database, version: number): void { +export function createDB(): WritableDB { + return new SQL(':memory:') as WritableDB; +} + +export function updateToVersion(db: WritableDB, version: number): void { const startVersion = db.pragma('user_version', { simple: true }); const silentLogger = { @@ -32,7 +37,11 @@ type TableRows = ReadonlyArray< Record> >; -export function insertData(db: Database, table: string, rows: TableRows): void { +export function insertData( + db: WritableDB, + table: string, + rows: TableRows +): void { for (const row of rows) { db.prepare( ` @@ -52,7 +61,7 @@ export function insertData(db: Database, table: string, rows: TableRows): void { } } -export function getTableData(db: Database, table: string): TableRows { +export function getTableData(db: ReadableDB, table: string): TableRows { return db .prepare(`SELECT * FROM ${table}`) .all() diff --git a/ts/test-node/sql/migrateConversationMessages_test.ts b/ts/test-node/sql/migrateConversationMessages_test.ts index 15705acef..22ade6e62 100644 --- a/ts/test-node/sql/migrateConversationMessages_test.ts +++ b/ts/test-node/sql/migrateConversationMessages_test.ts @@ -2,23 +2,21 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; -import data, { setupTests, teardownTests } from '../../sql/Server'; -import { insertData, getTableData } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { migrateConversationMessages, setupTests } from '../../sql/Server'; +import { createDB, insertData, getTableData } from './helpers'; describe('SQL/migrateConversationMessages', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); setupTests(db); }); afterEach(() => { db.close(); - teardownTests(); }); function compactify( @@ -33,7 +31,7 @@ describe('SQL/migrateConversationMessages', () => { }; } - it('should leave irrelevant messages intact', async () => { + it('should leave irrelevant messages intact', () => { insertData(db, 'messages', [ { id: 'irrelevant', @@ -44,7 +42,7 @@ describe('SQL/migrateConversationMessages', () => { }, ]); - await data.migrateConversationMessages('obsolete', 'current'); + migrateConversationMessages(db, 'obsolete', 'current'); assert.deepStrictEqual(getTableData(db, 'messages').map(compactify), [ { @@ -57,7 +55,7 @@ describe('SQL/migrateConversationMessages', () => { ]); }); - it('should update conversationId and send state', async () => { + it('should update conversationId and send state', () => { insertData(db, 'messages', [ { id: 'no-send-state', @@ -82,7 +80,7 @@ describe('SQL/migrateConversationMessages', () => { }, ]); - await data.migrateConversationMessages('obsolete', 'current'); + migrateConversationMessages(db, 'obsolete', 'current'); assert.deepStrictEqual(getTableData(db, 'messages').map(compactify), [ { diff --git a/ts/test-node/sql/migration_1000_test.ts b/ts/test-node/sql/migration_1000_test.ts index 84f49ff58..e4137d55c 100644 --- a/ts/test-node/sql/migration_1000_test.ts +++ b/ts/test-node/sql/migration_1000_test.ts @@ -2,21 +2,19 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { jsonToObject, sql } from '../../sql/util'; -import { updateToVersion } from './helpers'; -import type { MessageType } from '../../sql/Interface'; +import { createDB, updateToVersion } from './helpers'; +import type { WritableDB, MessageType } from '../../sql/Interface'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; describe('SQL/updateToSchemaVersion1000', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 990); }); diff --git a/ts/test-node/sql/migration_1020_test.ts b/ts/test-node/sql/migration_1020_test.ts index 5c97bea4a..65aeb4da2 100644 --- a/ts/test-node/sql/migration_1020_test.ts +++ b/ts/test-node/sql/migration_1020_test.ts @@ -2,15 +2,14 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { normalizeAci } from '../../util/normalizeAci'; -import { insertData, getTableData, updateToVersion } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, insertData, getTableData, updateToVersion } from './helpers'; describe('SQL/updateToSchemaVersion1020', () => { - let db: Database; + let db: WritableDB; const OUR_ACI = normalizeAci( generateGuid(), @@ -22,7 +21,7 @@ describe('SQL/updateToSchemaVersion1020', () => { ); beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1010); }); diff --git a/ts/test-node/sql/migration_1030_test.ts b/ts/test-node/sql/migration_1030_test.ts index 9dfdb62b4..89ace7363 100644 --- a/ts/test-node/sql/migration_1030_test.ts +++ b/ts/test-node/sql/migration_1030_test.ts @@ -2,19 +2,17 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { sql } from '../../sql/util'; -import { updateToVersion } from './helpers'; -import type { MessageType } from '../../sql/Interface'; +import { createDB, updateToVersion } from './helpers'; +import type { WritableDB, MessageType } from '../../sql/Interface'; import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; describe('SQL/updateToSchemaVersion1030', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1020); }); diff --git a/ts/test-node/sql/migration_1040_test.ts b/ts/test-node/sql/migration_1040_test.ts index 71d2d8aa6..7d207f0b7 100644 --- a/ts/test-node/sql/migration_1040_test.ts +++ b/ts/test-node/sql/migration_1040_test.ts @@ -2,17 +2,16 @@ // SPDX-License-Identifier: AGPL-3.0-only import { omit } from 'lodash'; import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; +import type { ReadableDB, WritableDB } from '../../sql/Interface'; import { jsonToObject, objectToJSON, sql, sqlJoin } from '../../sql/util'; -import { updateToVersion } from './helpers'; +import { createDB, updateToVersion } from './helpers'; import type { LegacyAttachmentDownloadJobType } from '../../sql/migrations/1040-undownloaded-backed-up-media'; import type { AttachmentType } from '../../types/Attachment'; import type { AttachmentDownloadJobType } from '../../types/AttachmentDownload'; import { IMAGE_JPEG } from '../../types/MIME'; -function getAttachmentDownloadJobs(db: Database) { +function getAttachmentDownloadJobs(db: ReadableDB) { const [query] = sql` SELECT * FROM attachment_downloads ORDER BY receivedAt DESC; `; @@ -31,7 +30,7 @@ type UnflattenedAttachmentDownloadJobType = Omit< 'digest' | 'contentType' | 'size' >; function insertNewJob( - db: Database, + db: WritableDB, job: UnflattenedAttachmentDownloadJobType, addMessageFirst: boolean = true ): void { @@ -82,10 +81,10 @@ function insertNewJob( describe('SQL/updateToSchemaVersion1040', () => { describe('Storing of new attachment jobs', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1040); }); @@ -338,10 +337,10 @@ describe('SQL/updateToSchemaVersion1040', () => { }); describe('existing jobs are transferred', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1030); }); @@ -462,7 +461,7 @@ describe('SQL/updateToSchemaVersion1040', () => { }); function insertLegacyJob( - db: Database, + db: WritableDB, job: Partial ): void { db.prepare('INSERT OR REPLACE INTO messages (id) VALUES ($id)').run({ diff --git a/ts/test-node/sql/migration_1060_test.ts b/ts/test-node/sql/migration_1060_test.ts index babcdb241..2110ff385 100644 --- a/ts/test-node/sql/migration_1060_test.ts +++ b/ts/test-node/sql/migration_1060_test.ts @@ -2,17 +2,16 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { - getAllSyncTasksSync, - getMostRecentAddressableMessagesSync, - removeSyncTaskByIdSync, - saveSyncTasksSync, + getAllSyncTasks, + getMostRecentAddressableMessages, + removeSyncTaskById, + saveSyncTasks, } from '../../sql/Server'; -import { insertData, updateToVersion } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { insertData, updateToVersion, createDB } from './helpers'; import { MAX_SYNC_TASK_ATTEMPTS } from '../../util/syncTasks.types'; import { WEEK } from '../../util/durations'; @@ -34,9 +33,9 @@ function generateMessage(json: MessageAttributesType) { } describe('SQL/updateToSchemaVersion1060', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1060); }); @@ -117,10 +116,7 @@ describe('SQL/updateToSchemaVersion1060', () => { }), ]); - const messages = getMostRecentAddressableMessagesSync( - db, - conversationId - ); + const messages = getMostRecentAddressableMessages(db, conversationId); assert.lengthOf(messages, 3); assert.deepEqual(messages, [ @@ -151,7 +147,7 @@ describe('SQL/updateToSchemaVersion1060', () => { ]); }); - it('ensures that index is used for getMostRecentAddressableMessagesSync, with storyId', () => { + it('ensures that index is used for getMostRecentAddressableMessages, with storyId', () => { const { detail } = db .prepare( ` @@ -219,14 +215,14 @@ describe('SQL/updateToSchemaVersion1060', () => { }, ]; - saveSyncTasksSync(db, expected); + saveSyncTasks(db, expected); - const actual = getAllSyncTasksSync(db); + const actual = getAllSyncTasks(db); assert.deepEqual(expected, actual, 'before delete'); - removeSyncTaskByIdSync(db, expected[1].id); + removeSyncTaskById(db, expected[1].id); - const actualAfterDelete = getAllSyncTasksSync(db); + const actualAfterDelete = getAllSyncTasks(db); assert.deepEqual( [ { ...expected[0], attempts: 2 }, @@ -291,9 +287,9 @@ describe('SQL/updateToSchemaVersion1060', () => { }, ]; - saveSyncTasksSync(db, expected); + saveSyncTasks(db, expected); - const actual = getAllSyncTasksSync(db); + const actual = getAllSyncTasks(db); assert.lengthOf(actual, 3); assert.deepEqual([expected[1], expected[2], expected[3]], actual); diff --git a/ts/test-node/sql/migration_1080_test.ts b/ts/test-node/sql/migration_1080_test.ts index 0212bc5de..2fcb60335 100644 --- a/ts/test-node/sql/migration_1080_test.ts +++ b/ts/test-node/sql/migration_1080_test.ts @@ -2,12 +2,11 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; -import { getMostRecentAddressableNondisappearingMessagesSync } from '../../sql/Server'; -import { insertData, updateToVersion } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { getMostRecentAddressableNondisappearingMessages } from '../../sql/Server'; +import { createDB, insertData, updateToVersion } from './helpers'; import type { MessageAttributesType } from '../../model-types'; import { DurationInSeconds } from '../../util/durations/duration-in-seconds'; @@ -28,9 +27,9 @@ function generateMessage(json: MessageAttributesType) { } describe('SQL/updateToSchemaVersion1080', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1080); }); @@ -111,7 +110,7 @@ describe('SQL/updateToSchemaVersion1080', () => { }), ]); - const messages = getMostRecentAddressableNondisappearingMessagesSync( + const messages = getMostRecentAddressableNondisappearingMessages( db, conversationId ); diff --git a/ts/test-node/sql/migration_1090_test.ts b/ts/test-node/sql/migration_1090_test.ts index 39cd25a8d..356af6e3a 100644 --- a/ts/test-node/sql/migration_1090_test.ts +++ b/ts/test-node/sql/migration_1090_test.ts @@ -2,14 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; -import { updateToVersion } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion } from './helpers'; describe('SQL/updateToSchemaVersion1090', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1090); }); diff --git a/ts/test-node/sql/migration_1100_test.ts b/ts/test-node/sql/migration_1100_test.ts index d1d71239b..7dfd03f12 100644 --- a/ts/test-node/sql/migration_1100_test.ts +++ b/ts/test-node/sql/migration_1100_test.ts @@ -2,11 +2,9 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { findLast } from 'lodash'; -import { insertData, updateToVersion } from './helpers'; -import { markAllCallHistoryReadSync } from '../../sql/Server'; +import type { WritableDB } from '../../sql/Interface'; +import { markAllCallHistoryRead } from '../../sql/Server'; import { SeenStatus } from '../../MessageSeenStatus'; import { CallMode } from '../../types/Calling'; import { @@ -15,11 +13,12 @@ import { DirectCallStatus, } from '../../types/CallDisposition'; import { strictAssert } from '../../util/assert'; +import { createDB, insertData, updateToVersion } from './helpers'; describe('SQL/updateToSchemaVersion1100', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1100); }); @@ -73,7 +72,7 @@ describe('SQL/updateToSchemaVersion1100', () => { }; const start = performance.now(); - markAllCallHistoryReadSync(db, target, true); + markAllCallHistoryRead(db, target, true); const end = performance.now(); assert.isBelow(end - start, 50); }); diff --git a/ts/test-node/sql/migration_1120_test.ts b/ts/test-node/sql/migration_1120_test.ts index 6ded43ba8..4d8e77485 100644 --- a/ts/test-node/sql/migration_1120_test.ts +++ b/ts/test-node/sql/migration_1120_test.ts @@ -2,14 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; -import { updateToVersion } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion } from './helpers'; describe('SQL/updateToSchemaVersion1120', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 1120); }); diff --git a/ts/test-node/sql/migration_87_test.ts b/ts/test-node/sql/migration_87_test.ts index fd21d18f8..54efc71bf 100644 --- a/ts/test-node/sql/migration_87_test.ts +++ b/ts/test-node/sql/migration_87_test.ts @@ -2,12 +2,10 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { range } from 'lodash'; -import { insertData, updateToVersion } from './helpers'; +import { createDB, insertData, updateToVersion } from './helpers'; import type { AciString, PniString, @@ -16,6 +14,7 @@ import type { import { normalizePni } from '../../types/ServiceId'; import { normalizeAci } from '../../util/normalizeAci'; import type { + WritableDB, KyberPreKeyType, PreKeyType, SignedPreKeyType, @@ -49,7 +48,7 @@ type TestingSignedKey = Omit< }; describe('SQL/updateToSchemaVersion87(cleanup)', () => { - let db: Database; + let db: WritableDB; const OUR_ACI = normalizeAci( generateGuid(), @@ -62,7 +61,7 @@ describe('SQL/updateToSchemaVersion87(cleanup)', () => { let idCount = 0; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 86); }); diff --git a/ts/test-node/sql/migration_88_test.ts b/ts/test-node/sql/migration_88_test.ts index df6d6ba48..a882cc3b2 100644 --- a/ts/test-node/sql/migration_88_test.ts +++ b/ts/test-node/sql/migration_88_test.ts @@ -2,11 +2,10 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; -import { updateToVersion, insertData, getTableData } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion, insertData, getTableData } from './helpers'; const CONVO_ID = generateGuid(); const GROUP_ID = generateGuid(); @@ -17,10 +16,10 @@ const OUR_PNI = generateGuid(); const THEIR_UUID = generateGuid(); describe('SQL/updateToSchemaVersion88', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 86); insertData(db, 'items', [ diff --git a/ts/test-node/sql/migration_89_test.ts b/ts/test-node/sql/migration_89_test.ts index ffb77ec19..573867b8b 100644 --- a/ts/test-node/sql/migration_89_test.ts +++ b/ts/test-node/sql/migration_89_test.ts @@ -2,8 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { jsonToObject, sql } from '../../sql/util'; @@ -22,14 +20,14 @@ import type { } from '../../sql/migrations/89-call-history'; import { getCallIdFromEra } from '../../util/callDisposition'; import { isValidUuid } from '../../util/isValidUuid'; -import { updateToVersion } from './helpers'; -import type { MessageType } from '../../sql/Interface'; +import { createDB, updateToVersion } from './helpers'; +import type { WritableDB, MessageType } from '../../sql/Interface'; describe('SQL/updateToSchemaVersion89', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 88); }); diff --git a/ts/test-node/sql/migration_90_test.ts b/ts/test-node/sql/migration_90_test.ts index 4e8a3ba5b..227a09ef1 100644 --- a/ts/test-node/sql/migration_90_test.ts +++ b/ts/test-node/sql/migration_90_test.ts @@ -1,17 +1,16 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { assert } from 'chai'; -import { updateToVersion, insertData, getTableData } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion, insertData, getTableData } from './helpers'; describe('SQL/updateToSchemaVersion90', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); }); afterEach(() => { diff --git a/ts/test-node/sql/migration_91_test.ts b/ts/test-node/sql/migration_91_test.ts index 970f818bc..b8714cbdb 100644 --- a/ts/test-node/sql/migration_91_test.ts +++ b/ts/test-node/sql/migration_91_test.ts @@ -2,16 +2,14 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { range } from 'lodash'; -import { getTableData, insertData, updateToVersion } from './helpers'; +import { createDB, getTableData, insertData, updateToVersion } from './helpers'; import type { ServiceIdString } from '../../types/ServiceId'; import { normalizePni } from '../../types/ServiceId'; import { normalizeAci } from '../../util/normalizeAci'; -import type { PreKeyType } from '../../sql/Interface'; +import type { WritableDB, PreKeyType } from '../../sql/Interface'; type TestingPreKey = Omit< PreKeyType, @@ -21,7 +19,7 @@ type TestingPreKey = Omit< }; describe('SQL/updateToSchemaVersion91', () => { - let db: Database; + let db: WritableDB; const OUR_ACI = normalizeAci(generateGuid(), 'updateToSchemaVersion91 test'); const OUR_PNI = normalizePni( @@ -31,7 +29,7 @@ describe('SQL/updateToSchemaVersion91', () => { let idCount = 0; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 90); }); diff --git a/ts/test-node/sql/migration_920_test.ts b/ts/test-node/sql/migration_920_test.ts index 53387fd56..b9e4fe3f7 100644 --- a/ts/test-node/sql/migration_920_test.ts +++ b/ts/test-node/sql/migration_920_test.ts @@ -2,16 +2,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; import { range } from 'lodash'; -import { insertData, updateToVersion } from './helpers'; +import { createDB, insertData, updateToVersion } from './helpers'; import type { ServiceIdString } from '../../types/ServiceId'; import { normalizePni } from '../../types/ServiceId'; import { normalizeAci } from '../../util/normalizeAci'; -import type { KyberPreKeyType, SignedPreKeyType } from '../../sql/Interface'; +import type { + WritableDB, + KyberPreKeyType, + SignedPreKeyType, +} from '../../sql/Interface'; type TestingKyberKey = Omit< KyberPreKeyType, @@ -27,7 +29,7 @@ type TestingSignedKey = Omit< }; describe('SQL/updateToSchemaVersion92', () => { - let db: Database; + let db: WritableDB; const OUR_ACI = normalizeAci(generateGuid(), 'updateToSchemaVersion92 test'); const OUR_PNI = normalizePni( @@ -37,7 +39,7 @@ describe('SQL/updateToSchemaVersion92', () => { let idCount = 0; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 91); }); diff --git a/ts/test-node/sql/migration_960_test.ts b/ts/test-node/sql/migration_960_test.ts index cf5acd5fd..17ced3a92 100644 --- a/ts/test-node/sql/migration_960_test.ts +++ b/ts/test-node/sql/migration_960_test.ts @@ -2,11 +2,10 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; -import { updateToVersion, insertData, getTableData } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion, insertData, getTableData } from './helpers'; const CONVO_ID = generateGuid(); const OUR_ACI = generateGuid(); @@ -14,10 +13,10 @@ const OUR_UNPREFIXED_PNI = generateGuid(); const OUR_PREFIXED_PNI = `PNI:${OUR_UNPREFIXED_PNI}`; describe('SQL/updateToSchemaVersion960', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 950); insertData(db, 'items', [ diff --git a/ts/test-node/sql/migration_990_test.ts b/ts/test-node/sql/migration_990_test.ts index 89801d94f..c4af41f10 100644 --- a/ts/test-node/sql/migration_990_test.ts +++ b/ts/test-node/sql/migration_990_test.ts @@ -2,16 +2,15 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; -import { updateToVersion, insertData, getTableData } from './helpers'; +import type { WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion, insertData, getTableData } from './helpers'; describe('SQL/updateToSchemaVersion990', () => { - let db: Database; + let db: WritableDB; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); updateToVersion(db, 980); }); diff --git a/ts/test-node/sql/migrations_test.ts b/ts/test-node/sql/migrations_test.ts index e39e48454..f054b0d17 100644 --- a/ts/test-node/sql/migrations_test.ts +++ b/ts/test-node/sql/migrations_test.ts @@ -2,27 +2,22 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import type { Database } from '@signalapp/better-sqlite3'; -import SQL from '@signalapp/better-sqlite3'; import { v4 as generateGuid } from 'uuid'; -import { - _storyIdPredicate, - getJobsInQueueSync, - insertJobSync, -} from '../../sql/Server'; +import { _storyIdPredicate, getJobsInQueue, insertJob } from '../../sql/Server'; +import type { WritableDB } from '../../sql/Interface'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; import { objectToJSON, sql, sqlJoin } from '../../sql/util'; import { BodyRange } from '../../types/BodyRange'; import type { AciString } from '../../types/ServiceId'; import { generateAci } from '../../types/ServiceId'; -import { updateToVersion } from './helpers'; +import { createDB, updateToVersion } from './helpers'; const OUR_UUID = generateGuid(); describe('SQL migrations test', () => { - let db: Database; + let db: WritableDB; const addOurUuid = () => { const value = { @@ -71,7 +66,7 @@ describe('SQL migrations test', () => { }; beforeEach(() => { - db = new SQL(':memory:'); + db = createDB(); }); afterEach(() => { @@ -1409,7 +1404,7 @@ describe('SQL migrations test', () => { const CONVERSATION_ID_1 = generateGuid(); const CONVERSATION_ID_2 = generateGuid(); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'reactions', @@ -1417,7 +1412,7 @@ describe('SQL migrations test', () => { messageId: MESSAGE_ID_1, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'reactions', @@ -1425,12 +1420,12 @@ describe('SQL migrations test', () => { messageId: MESSAGE_ID_2, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3-missing-data', timestamp: 3, queueType: 'reactions', }); - insertJobSync(db, { + insertJob(db, { id: 'id-4-non-string-messageId', timestamp: 1, queueType: 'reactions', @@ -1438,7 +1433,7 @@ describe('SQL migrations test', () => { messageId: 4, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-5-missing-message', timestamp: 5, queueType: 'reactions', @@ -1446,7 +1441,7 @@ describe('SQL migrations test', () => { messageId: 'missing', }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-6-missing-conversation', timestamp: 6, queueType: 'reactions', @@ -1490,7 +1485,7 @@ describe('SQL migrations test', () => { assert.strictEqual(reactionJobs.get(), 0, 'reaction jobs after'); assert.strictEqual(conversationJobs.get(), 2, 'conversation jobs after'); - const jobs = getJobsInQueueSync(db, 'conversation'); + const jobs = getJobsInQueue(db, 'conversation'); assert.deepEqual(jobs, [ { @@ -1525,7 +1520,7 @@ describe('SQL migrations test', () => { const CONVERSATION_ID_1 = generateGuid(); const CONVERSATION_ID_2 = generateGuid(); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'normal send', @@ -1534,7 +1529,7 @@ describe('SQL migrations test', () => { messageId: MESSAGE_ID_1, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'normal send', @@ -1543,7 +1538,7 @@ describe('SQL migrations test', () => { messageId: MESSAGE_ID_2, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3-missing-data', timestamp: 3, queueType: 'normal send', @@ -1567,7 +1562,7 @@ describe('SQL migrations test', () => { assert.strictEqual(normalSend.get(), 0, 'normal send jobs after'); assert.strictEqual(conversationJobs.get(), 2, 'conversation jobs after'); - const jobs = getJobsInQueueSync(db, 'conversation'); + const jobs = getJobsInQueue(db, 'conversation'); assert.deepEqual(jobs, [ { @@ -1742,7 +1737,7 @@ describe('SQL migrations test', () => { assert.strictEqual(totalJobs.get(), 2, 'after total'); assert.strictEqual(reportSpamJobs.get(), 1, 'after report spam'); - const jobs = getJobsInQueueSync(db, 'report spam'); + const jobs = getJobsInQueue(db, 'report spam'); assert.deepEqual(jobs, [ { @@ -2494,13 +2489,13 @@ describe('SQL migrations test', () => { ` ); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'random job', data: {}, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'delivery receipts', @@ -2509,7 +2504,7 @@ describe('SQL migrations test', () => { deliveryReceipts: [], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3', timestamp: 3, queueType: 'read receipts', @@ -2518,7 +2513,7 @@ describe('SQL migrations test', () => { readReceipts: [], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-4', timestamp: 4, queueType: 'viewed receipts', @@ -2527,7 +2522,7 @@ describe('SQL migrations test', () => { viewedReceipt: {}, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-5', timestamp: 5, queueType: 'conversation', @@ -2577,7 +2572,7 @@ describe('SQL migrations test', () => { const CONVERSATION_ID_1 = generateGuid(); const CONVERSATION_ID_2 = generateGuid(); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'delivery receipts', @@ -2591,7 +2586,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'delivery receipts', @@ -2605,12 +2600,12 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3-missing-data', timestamp: 3, queueType: 'delivery receipts', }); - insertJobSync(db, { + insertJob(db, { id: 'id-4-non-string-messageId', timestamp: 4, queueType: 'delivery receipts', @@ -2624,7 +2619,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-5-missing-message', timestamp: 5, queueType: 'delivery receipts', @@ -2638,7 +2633,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-6-missing-conversation', timestamp: 6, queueType: 'delivery receipts', @@ -2652,7 +2647,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-7-missing-delivery-receipts', timestamp: 7, queueType: 'delivery receipts', @@ -2698,7 +2693,7 @@ describe('SQL migrations test', () => { assert.strictEqual(conversationJobs.get(), 2, 'conversation jobs after'); assert.strictEqual(deliveryJobs.get(), 0, 'delivery jobs after'); - const jobs = getJobsInQueueSync(db, 'conversation'); + const jobs = getJobsInQueue(db, 'conversation'); assert.deepEqual(jobs, [ { @@ -2748,7 +2743,7 @@ describe('SQL migrations test', () => { const CONVERSATION_ID_1 = generateGuid(); const CONVERSATION_ID_2 = generateGuid(); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'read receipts', @@ -2762,7 +2757,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'read receipts', @@ -2776,12 +2771,12 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3-missing-data', timestamp: 3, queueType: 'read receipts', }); - insertJobSync(db, { + insertJob(db, { id: 'id-4-non-string-messageId', timestamp: 4, queueType: 'read receipts', @@ -2795,7 +2790,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-5-missing-message', timestamp: 5, queueType: 'read receipts', @@ -2809,7 +2804,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-6-missing-conversation', timestamp: 6, queueType: 'read receipts', @@ -2823,7 +2818,7 @@ describe('SQL migrations test', () => { ], }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-7-missing-read-receipts', timestamp: 7, queueType: 'read receipts', @@ -2867,7 +2862,7 @@ describe('SQL migrations test', () => { assert.strictEqual(conversationJobs.get(), 2, 'conversation jobs after'); assert.strictEqual(readJobs.get(), 0, 'read jobs after'); - const jobs = getJobsInQueueSync(db, 'conversation'); + const jobs = getJobsInQueue(db, 'conversation'); assert.deepEqual(jobs, [ { @@ -2917,7 +2912,7 @@ describe('SQL migrations test', () => { const CONVERSATION_ID_1 = generateGuid(); const CONVERSATION_ID_2 = generateGuid(); - insertJobSync(db, { + insertJob(db, { id: 'id-1', timestamp: 1, queueType: 'viewed receipts', @@ -2929,7 +2924,7 @@ describe('SQL migrations test', () => { }, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-2', timestamp: 2, queueType: 'viewed receipts', @@ -2941,12 +2936,12 @@ describe('SQL migrations test', () => { }, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-3-missing-data', timestamp: 3, queueType: 'viewed receipts', }); - insertJobSync(db, { + insertJob(db, { id: 'id-4-non-string-messageId', timestamp: 4, queueType: 'viewed receipts', @@ -2958,7 +2953,7 @@ describe('SQL migrations test', () => { }, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-5-missing-message', timestamp: 5, queueType: 'viewed receipts', @@ -2970,7 +2965,7 @@ describe('SQL migrations test', () => { }, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-6-missing-conversation', timestamp: 6, queueType: 'viewed receipts', @@ -2982,7 +2977,7 @@ describe('SQL migrations test', () => { }, }, }); - insertJobSync(db, { + insertJob(db, { id: 'id-7-missing-viewed-receipt', timestamp: 7, queueType: 'viewed receipts', @@ -3028,7 +3023,7 @@ describe('SQL migrations test', () => { assert.strictEqual(conversationJobs.get(), 2, 'conversation jobs after'); assert.strictEqual(viewedJobs.get(), 0, 'viewed jobs after'); - const jobs = getJobsInQueueSync(db, 'conversation'); + const jobs = getJobsInQueue(db, 'conversation'); assert.deepEqual(jobs, [ { diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 1cc0fb898..26d4a052b 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -15,6 +15,7 @@ import { SenderKeyDistributionMessage, } from '@signalapp/libsignal-client'; +import { DataWriter } from '../sql/Client'; import type { ConversationModel } from '../models/conversations'; import { GLOBAL_ZONE } from '../SignalProtocolStore'; import { assertDev, strictAssert } from '../util/assert'; @@ -2091,7 +2092,7 @@ export default class MessageSender { } if (initialSavePromise === undefined) { - initialSavePromise = window.Signal.Data.insertSentProto( + initialSavePromise = DataWriter.insertSentProto( { contentHint, proto, @@ -2107,7 +2108,7 @@ export default class MessageSender { await initialSavePromise; } else { const id = await initialSavePromise; - await window.Signal.Data.insertProtoRecipients({ + await DataWriter.insertProtoRecipients({ id, recipientServiceId, deviceIds, diff --git a/ts/textsecure/Storage.ts b/ts/textsecure/Storage.ts index 7e10f6d13..1808bd62c 100644 --- a/ts/textsecure/Storage.ts +++ b/ts/textsecure/Storage.ts @@ -9,7 +9,7 @@ import { User } from './storage/User'; import { Blocked } from './storage/Blocked'; import { assertDev } from '../util/assert'; -import Data from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import type { SignalProtocolStore } from '../SignalProtocolStore'; import * as log from '../logging/log'; @@ -81,7 +81,7 @@ export class Storage implements StorageInterface { } this.items[key] = value; - await window.Signal.Data.createOrUpdateItem({ id: key, value }); + await DataWriter.createOrUpdateItem({ id: key, value }); window.reduxActions?.items.putItemExternal(key, value); } @@ -92,7 +92,7 @@ export class Storage implements StorageInterface { } delete this.items[key]; - await Data.removeItemById(key); + await DataWriter.removeItemById(key); window.reduxActions?.items.removeItemExternal(key); } @@ -110,7 +110,7 @@ export class Storage implements StorageInterface { public async fetch(): Promise { this.reset(); - Object.assign(this.items, await Data.getAllItems()); + Object.assign(this.items, await DataReader.getAllItems()); this.ready = true; this.callListeners(); diff --git a/ts/types/Stickers.ts b/ts/types/Stickers.ts index 8599e2c42..1195a1d25 100644 --- a/ts/types/Stickers.ts +++ b/ts/types/Stickers.ts @@ -20,7 +20,7 @@ import type { StickerPackType, StickerPackStatusType, } from '../sql/Interface'; -import Data from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import { SignalService as Proto } from '../protobuf'; import * as log from '../logging/log'; import type { StickersStateType } from '../state/ducks/stickers'; @@ -277,8 +277,8 @@ function doesPackNeedDownload(pack?: StickerPackType): boolean { async function getPacksForRedux(): Promise> { const [packs, stickers] = await Promise.all([ - Data.getAllStickerPacks(), - Data.getAllStickers(), + DataReader.getAllStickerPacks(), + DataReader.getAllStickers(), ]); const stickersByPack = groupBy(stickers, sticker => sticker.packId); @@ -291,7 +291,7 @@ async function getPacksForRedux(): Promise> { } async function getRecentStickersForRedux(): Promise> { - const recent = await Data.getRecentStickers(); + const recent = await DataReader.getRecentStickers(); return recent.map(sticker => ({ packId: sticker.packId, stickerId: sticker.id, @@ -378,9 +378,9 @@ export async function savePackMetadata( }; stickerPackAdded(pack); - await Data.createOrUpdateStickerPack(pack); + await DataWriter.createOrUpdateStickerPack(pack); if (messageId) { - await Data.addStickerPackReference(messageId, packId); + await DataWriter.addStickerPackReference(messageId, packId); } } @@ -404,7 +404,7 @@ export async function removeEphemeralPack(packId: string): Promise { }); // Remove it from database in case it made it there - await Data.deleteStickerPack(packId); + await DataWriter.deleteStickerPack(packId); } export async function downloadEphemeralPack( @@ -626,7 +626,7 @@ async function doDownloadStickerPack( ); if (existing && existing.status !== 'error') { - await Data.updateStickerPackStatus(packId, 'error'); + await DataWriter.updateStickerPackStatus(packId, 'error'); stickerPackUpdated( packId, { @@ -713,11 +713,11 @@ async function doDownloadStickerPack( title: proto.title ?? '', author: proto.author ?? '', }; - await Data.createOrUpdateStickerPack(pack); + await DataWriter.createOrUpdateStickerPack(pack); stickerPackAdded(pack); if (messageId) { - await Data.addStickerPackReference(messageId, packId); + await DataWriter.addStickerPackReference(messageId, packId); } } catch (error) { log.error( @@ -734,7 +734,7 @@ async function doDownloadStickerPack( downloadAttempts, status: 'error' as const, }; - await Data.createOrUpdateStickerPack(pack); + await DataWriter.createOrUpdateStickerPack(pack); stickerPackAdded(pack, { suppressError }); return; @@ -757,7 +757,7 @@ async function doDownloadStickerPack( isCoverOnly: !coverIncludedInList && stickerInfo.id === coverStickerId, }; - await Data.createOrUpdateSticker(sticker); + await DataWriter.createOrUpdateSticker(sticker); stickerAdded(sticker); return true; } catch (error: unknown) { @@ -798,7 +798,7 @@ async function doDownloadStickerPack( }); } else { // Mark the pack as complete - await Data.updateStickerPackStatus(packId, finalStatus); + await DataWriter.updateStickerPackStatus(packId, finalStatus); stickerPackUpdated(packId, { status: finalStatus, }); @@ -810,7 +810,7 @@ async function doDownloadStickerPack( ); const errorStatus = 'error'; - await Data.updateStickerPackStatus(packId, errorStatus); + await DataWriter.updateStickerPackStatus(packId, errorStatus); if (stickerPackUpdated) { stickerPackUpdated( packId, @@ -921,7 +921,7 @@ export async function deletePackReference( // This call uses locking to prevent race conditions with other reference removals, // or an incoming message creating a new message->pack reference - const paths = await Data.deleteStickerPackReference(messageId, packId); + const paths = await DataWriter.deleteStickerPackReference(messageId, packId); // If we don't get a list of paths back, then the sticker pack was not deleted if (!paths) { @@ -945,7 +945,7 @@ async function deletePack(packId: string): Promise { // This call uses locking to prevent race conditions with other reference removals, // or an incoming message creating a new message->pack reference - const paths = await Data.deleteStickerPack(packId); + const paths = await DataWriter.deleteStickerPack(packId); const { removeStickerPack } = getReduxStickerActions(); removeStickerPack(packId); @@ -958,7 +958,7 @@ async function deletePack(packId: string): Promise { export async function encryptLegacyStickers(): Promise { const CONCURRENCY = 32; - const all = await Data.getAllStickers(); + const all = await DataReader.getAllStickers(); log.info(`encryptLegacyStickers: checking ${all.length}`); @@ -979,7 +979,9 @@ export async function encryptLegacyStickers(): Promise { ) ).filter(isNotNil); - await Data.createOrUpdateStickers(updated.map(({ sticker }) => sticker)); + await DataWriter.createOrUpdateStickers( + updated.map(({ sticker }) => sticker) + ); log.info(`encryptLegacyStickers: updated ${updated.length}`); diff --git a/ts/util/assert.ts b/ts/util/assert.ts index c4549be62..49a42eb20 100644 --- a/ts/util/assert.ts +++ b/ts/util/assert.ts @@ -67,11 +67,3 @@ export function strictAssert(condition: unknown, message: string): void { throw new Error(message); } } - -/** - * Asserts that the type of value is not a promise. - * (Useful for database modules) - */ -export function assertSync(value: T extends Promise ? never : T): T { - return value; -} diff --git a/ts/util/attachmentDownloadQueue.ts b/ts/util/attachmentDownloadQueue.ts index b15715c2d..ebe4c92be 100644 --- a/ts/util/attachmentDownloadQueue.ts +++ b/ts/util/attachmentDownloadQueue.ts @@ -3,6 +3,7 @@ import type { MessageModel } from '../models/messages'; import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import { isMoreRecentThan } from './timestamp'; import { isNotNil } from './isNotNil'; @@ -100,7 +101,7 @@ export async function flushAttachmentDownloadQueue(): Promise { .map(messageId => window.MessageCache.accessAttributes(messageId)) .filter(isNotNil); - await window.Signal.Data.saveMessages(messagesToSave, { + await DataWriter.saveMessages(messagesToSave, { ourAci: window.storage.user.getCheckedAci(), }); diff --git a/ts/util/callDisposition.ts b/ts/util/callDisposition.ts index 1595a11f8..4c48f66a4 100644 --- a/ts/util/callDisposition.ts +++ b/ts/util/callDisposition.ts @@ -14,6 +14,7 @@ import { import { v4 as generateGuid } from 'uuid'; import { isEqual } from 'lodash'; import { strictAssert } from './assert'; +import { DataReader, DataWriter } from '../sql/Client'; import { SignalService as Proto } from '../protobuf'; import { bytesToUuid, uuidToBytes } from './uuidToBytes'; import { missingCaseError } from './missingCaseError'; @@ -930,10 +931,8 @@ async function updateLocalCallHistory( ); const prevCallHistory = - (await window.Signal.Data.getCallHistory( - callEvent.callId, - callEvent.peerId - )) ?? null; + (await DataReader.getCallHistory(callEvent.callId, callEvent.peerId)) ?? + null; if (prevCallHistory != null) { log.info( @@ -987,10 +986,8 @@ export async function updateLocalAdhocCallHistory( ); const prevCallHistory = - (await window.Signal.Data.getCallHistory( - callEvent.callId, - callEvent.peerId - )) ?? null; + (await DataReader.getCallHistory(callEvent.callId, callEvent.peerId)) ?? + null; if (prevCallHistory != null) { log.info( @@ -1031,7 +1028,7 @@ export async function updateLocalAdhocCallHistory( 'updateAdhocCallHistory: Saving call history:', formatCallHistory(callHistory) ); - await window.Signal.Data.saveCallHistory(callHistory); + await DataWriter.saveCallHistory(callHistory); /* If we're not a call link admin and this is the first call history for this link, @@ -1040,9 +1037,7 @@ export async function updateLocalAdhocCallHistory( message refers to a valid call link. */ if (prevCallHistory == null) { - const callLink = await window.Signal.Data.getCallLinkByRoomId( - callEvent.peerId - ); + const callLink = await DataReader.getCallLinkByRoomId(callEvent.peerId); if (callLink) { log.info( `updateAdhocCallHistory: Syncing new observed call link ${callEvent.peerId}` @@ -1085,7 +1080,7 @@ async function saveCallHistory({ callHistory.status === DirectCallStatus.Deleted || callHistory.status === GroupCallStatus.Deleted; - await window.Signal.Data.saveCallHistory(callHistory); + await DataWriter.saveCallHistory(callHistory); if (isDeleted) { window.reduxActions.callHistory.removeCallHistory(callHistory.callId); @@ -1093,7 +1088,7 @@ async function saveCallHistory({ window.reduxActions.callHistory.addCallHistory(callHistory); } - const prevMessage = await window.Signal.Data.getCallHistoryMessageByCallId({ + const prevMessage = await DataReader.getCallHistoryMessageByCallId({ conversationId: conversation.id, callId: callHistory.callId, }); @@ -1112,7 +1107,7 @@ async function saveCallHistory({ if (isDeleted) { if (prevMessage != null) { - await window.Signal.Data.removeMessage(prevMessage.id, { + await DataWriter.removeMessage(prevMessage.id, { fromSync: true, singleProtoJobQueue, }); @@ -1156,7 +1151,7 @@ async function saveCallHistory({ callId: callHistory.callId, }; - const id = await window.Signal.Data.saveMessage(message, { + const id = await DataWriter.saveMessage(message, { ourAci: window.textsecure.storage.user.getCheckedAci(), // We don't want to force save if we're updating an existing message forceSave: prevMessage == null, @@ -1196,7 +1191,7 @@ async function saveCallHistory({ if (canConversationBeUnarchived(conversation.attributes)) { conversation.setArchived(false); } else { - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } window.reduxActions.callHistory.updateCallHistoryUnreadCount(); @@ -1300,7 +1295,7 @@ export async function clearCallHistoryDataAndSync( log.info( `clearCallHistory: Clearing call history before (${latestCall.callId}, ${latestCall.timestamp})` ); - const messageIds = await window.Signal.Data.clearCallHistory(latestCall); + const messageIds = await DataWriter.clearCallHistory(latestCall); updateDeletedMessages(messageIds); log.info('clearCallHistory: Queueing sync message'); await singleProtoJobQueue.add( @@ -1320,9 +1315,9 @@ export async function markAllCallHistoryReadAndSync( `markAllCallHistoryReadAndSync: Marking call history read before (${latestCall.callId}, ${latestCall.timestamp})` ); if (inConversation) { - await window.Signal.Data.markAllCallHistoryReadInConversation(latestCall); + await DataWriter.markAllCallHistoryReadInConversation(latestCall); } else { - await window.Signal.Data.markAllCallHistoryRead(latestCall); + await DataWriter.markAllCallHistoryRead(latestCall); } const ourAci = window.textsecure.storage.user.getCheckedAci(); @@ -1375,7 +1370,7 @@ export async function updateLocalGroupCallHistoryTimestamp( const peerId = getPeerIdFromConversation(conversation.attributes); const prevCallHistory = - (await window.Signal.Data.getCallHistory(callId, peerId)) ?? null; + (await DataReader.getCallHistory(callId, peerId)) ?? null; // We don't have all the details to add new call history here if (prevCallHistory != null) { diff --git a/ts/util/cleanup.ts b/ts/util/cleanup.ts index 8e354f94e..86ae51e31 100644 --- a/ts/util/cleanup.ts +++ b/ts/util/cleanup.ts @@ -5,6 +5,7 @@ import PQueue from 'p-queue'; import { batch } from 'react-redux'; import type { MessageAttributesType } from '../model-types.d'; +import { DataReader } from '../sql/Client'; import { deletePackReference } from '../types/Stickers'; import { isStory } from '../messages/helpers'; import { isDirectConversation } from './whatTypeOfConversation'; @@ -93,10 +94,7 @@ async function cleanupStoryReplies( parentConversation && !isDirectConversation(parentConversation.attributes) ); - const replies = await window.Signal.Data.getRecentStoryReplies( - storyId, - pagination - ); + const replies = await DataReader.getRecentStoryReplies(storyId, pagination); const logId = `cleanupStoryReplies(${storyId}/isGroup=${isGroupConversation})`; const lastMessage = replies[replies.length - 1]; diff --git a/ts/util/clearConversationDraftAttachments.ts b/ts/util/clearConversationDraftAttachments.ts index 522bb623b..bde56e224 100644 --- a/ts/util/clearConversationDraftAttachments.ts +++ b/ts/util/clearConversationDraftAttachments.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { AttachmentDraftType } from '../types/Attachment'; +import { DataWriter } from '../sql/Client'; import { strictAssert } from './assert'; import { deleteDraftAttachment } from './deleteDraftAttachment'; @@ -21,7 +22,7 @@ export async function clearConversationDraftAttachments( // We're fine doing this all at once; at most it should be 32 attachments await Promise.all([ - window.Signal.Data.updateConversation(conversation.attributes), + DataWriter.updateConversation(conversation.attributes), Promise.all( draftAttachments.map(attachment => deleteDraftAttachment(attachment)) ), diff --git a/ts/util/deleteForMe.ts b/ts/util/deleteForMe.ts index c1c27323e..6b6c61776 100644 --- a/ts/util/deleteForMe.ts +++ b/ts/util/deleteForMe.ts @@ -15,7 +15,7 @@ import { getMessageSentTimestampSet } from './getMessageSentTimestampSet'; import { getAuthor } from '../messages/helpers'; import { isPniString } from '../types/ServiceId'; import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue'; -import dataInterface, { deleteAndCleanup } from '../sql/Client'; +import { DataReader, DataWriter, deleteAndCleanup } from '../sql/Client'; import { deleteData } from '../types/Attachment'; import type { @@ -31,12 +31,9 @@ import type { AciString, PniString } from '../types/ServiceId'; import type { AttachmentType } from '../types/Attachment'; import type { MessageModel } from '../models/messages'; -const { - getMessagesBySentAt, - getMostRecentAddressableMessages, - removeMessagesInConversation, - saveMessage, -} = dataInterface; +const { getMessagesBySentAt, getMostRecentAddressableMessages } = DataReader; + +const { removeMessagesInConversation, saveMessage } = DataWriter; export function doesMessageMatch({ conversationId, diff --git a/ts/util/deleteStoryForEveryone.ts b/ts/util/deleteStoryForEveryone.ts index 27d4e0b43..541a67e5a 100644 --- a/ts/util/deleteStoryForEveryone.ts +++ b/ts/util/deleteStoryForEveryone.ts @@ -10,6 +10,7 @@ import type { StoryMessageRecipientsType } from '../types/Stories'; import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import type { ServiceIdString } from '../types/ServiceId'; import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import { DAY } from './durations'; import { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents'; import { @@ -190,7 +191,7 @@ export async function deleteStoryForEveryone( await conversationJobQueue.add(jobData, async jobToInsert => { log.info(`${logId}: Deleting message with job ${jobToInsert.id}`); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); diff --git a/ts/util/distributionListToSendTarget.ts b/ts/util/distributionListToSendTarget.ts index 3e75e4867..2d49c7887 100644 --- a/ts/util/distributionListToSendTarget.ts +++ b/ts/util/distributionListToSendTarget.ts @@ -3,7 +3,7 @@ import type { ServiceIdString } from '../types/ServiceId'; import type { SenderKeyInfoType } from '../model-types.d'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import type { StoryDistributionType } from '../sql/Interface'; import type { SenderKeyTargetType } from './sendToGroup'; import { isNotNil } from './isNotNil'; @@ -29,7 +29,7 @@ export function distributionListToSendTarget( getSenderKeyInfo: () => inMemorySenderKeyInfo, saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) => { inMemorySenderKeyInfo = senderKeyInfo; - await dataInterface.modifyStoryDistribution({ + await DataWriter.modifyStoryDistribution({ ...distributionList, senderKeyInfo, }); diff --git a/ts/util/encryptConversationAttachments.ts b/ts/util/encryptConversationAttachments.ts index c48c7b7d1..d6580189b 100644 --- a/ts/util/encryptConversationAttachments.ts +++ b/ts/util/encryptConversationAttachments.ts @@ -4,7 +4,7 @@ import pMap from 'p-map'; import * as log from '../logging/log'; -import Data from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import type { ConversationAttributesType } from '../model-types.d'; import { encryptLegacyAttachment } from './encryptLegacyAttachment'; import { AttachmentDisposition } from './getLocalAttachmentUrl'; @@ -17,7 +17,7 @@ const CONCURRENCY = 32; type CleanupType = Array<() => Promise>; export async function encryptConversationAttachments(): Promise { - const all = await Data.getAllConversations(); + const all = await DataReader.getAllConversations(); log.info(`encryptConversationAttachments: checking ${all.length}`); const updated = ( @@ -37,7 +37,9 @@ export async function encryptConversationAttachments(): Promise { if (updated.length !== 0) { log.info(`encryptConversationAttachments: updating ${updated.length}`); - await Data.updateConversations(updated.map(({ attributes }) => attributes)); + await DataWriter.updateConversations( + updated.map(({ attributes }) => attributes) + ); const cleanup = updated.map(entry => entry.cleanup).flat(); diff --git a/ts/util/findAndDeleteOnboardingStoryIfExists.ts b/ts/util/findAndDeleteOnboardingStoryIfExists.ts index 4a1faacc3..92fcb3048 100644 --- a/ts/util/findAndDeleteOnboardingStoryIfExists.ts +++ b/ts/util/findAndDeleteOnboardingStoryIfExists.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import { calculateExpirationTimestamp } from './expirationTimer'; import { DAY } from './durations'; import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue'; @@ -44,7 +45,7 @@ export async function findAndDeleteOnboardingStoryIfExists(): Promise { log.info('findAndDeleteOnboardingStoryIfExists: removing onboarding stories'); - await window.Signal.Data.removeMessages(existingOnboardingStoryMessageIds, { + await DataWriter.removeMessages(existingOnboardingStoryMessageIds, { singleProtoJobQueue, }); diff --git a/ts/util/findStoryMessage.ts b/ts/util/findStoryMessage.ts index 10e5137a6..b7b6148e3 100644 --- a/ts/util/findStoryMessage.ts +++ b/ts/util/findStoryMessage.ts @@ -5,6 +5,7 @@ import type { MessageAttributesType } from '../model-types.d'; import type { MessageModel } from '../models/messages'; import type { SignalService as Proto } from '../protobuf'; import type { AciString } from '../types/ServiceId'; +import { DataReader } from '../sql/Client'; import * as log from '../logging/log'; import { normalizeAci } from './normalizeAci'; import { filter } from './iterables'; @@ -50,7 +51,7 @@ export async function findStoryMessages( } log.info('findStoryMessages: db lookup needed', sentAt); - const messages = await window.Signal.Data.getMessagesBySentAt(sentAt); + const messages = await DataReader.getMessagesBySentAt(sentAt); const found = messages.filter(item => isStoryAMatch(item, conversationId, ourConversationId, authorAci, sentAt) ); diff --git a/ts/util/handleEditMessage.ts b/ts/util/handleEditMessage.ts index c18edfdc7..5f402fb36 100644 --- a/ts/util/handleEditMessage.ts +++ b/ts/util/handleEditMessage.ts @@ -13,7 +13,7 @@ import type { LinkPreviewType } from '../types/message/LinkPreviews'; import * as Edits from '../messageModifiers/Edits'; import * as log from '../logging/log'; import { ReadStatus } from '../messages/MessageReadStatus'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import { drop } from './drop'; import { getAttachmentSignature, isVoiceMessage } from '../types/Attachment'; import { isAciString } from './isAciString'; @@ -344,7 +344,7 @@ export async function handleEditMessage( // Save both the main message and the edited message for fast lookups drop( - dataInterface.saveEditedMessage( + DataWriter.saveEditedMessage( mainMessageModel.attributes, window.textsecure.storage.user.getCheckedAci(), { diff --git a/ts/util/handleMessageSend.ts b/ts/util/handleMessageSend.ts index bf027b700..5385efc61 100644 --- a/ts/util/handleMessageSend.ts +++ b/ts/util/handleMessageSend.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import { isBoolean, isNumber } from 'lodash'; import type { CallbackResultType } from '../textsecure/Types.d'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import * as log from '../logging/log'; import { OutgoingMessageError, @@ -14,8 +14,9 @@ import { } from '../textsecure/Errors'; import { SEALED_SENDER } from '../types/SealedSender'; import type { ServiceIdString } from '../types/ServiceId'; +import { drop } from './drop'; -const { insertSentProto, updateConversation } = dataInterface; +const { insertSentProto, updateConversation } = DataWriter; export const sendTypesEnum = z.enum([ // Core user interactions, default urgent @@ -121,7 +122,7 @@ function processError(error: unknown): void { `handleMessageSend: Got 401/403 for ${conversation.idForLogging()}, setting sealedSender = DISABLED` ); conversation.set('sealedSender', SEALED_SENDER.DISABLED); - updateConversation(conversation.attributes); + drop(updateConversation(conversation.attributes)); } } if (error.code === 404) { @@ -195,7 +196,7 @@ async function handleMessageSendResult( conversation.set({ sealedSender: SEALED_SENDER.DISABLED, }); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } }) ); @@ -223,7 +224,7 @@ async function handleMessageSendResult( sealedSender: SEALED_SENDER.UNRESTRICTED, }); } - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } }) ); diff --git a/ts/util/handleRetry.ts b/ts/util/handleRetry.ts index 2d9c8f0af..6c5eeb90a 100644 --- a/ts/util/handleRetry.ts +++ b/ts/util/handleRetry.ts @@ -8,7 +8,7 @@ import { import { isNumber } from 'lodash'; import * as Bytes from '../Bytes'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import { isProduction } from './version'; import { strictAssert } from './assert'; import { isGroupV2 } from './whatTypeOfConversation'; @@ -112,7 +112,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise { return; } - const sentProto = await window.Signal.Data.getSentProtoByRecipient({ + const sentProto = await DataWriter.getSentProtoByRecipient({ now: Date.now(), recipientServiceId: requesterAci, timestamp: sentAt, @@ -387,7 +387,7 @@ async function getRetryConversation({ } const [messageId] = messageIds; - const message = await window.Signal.Data.getMessageById(messageId); + const message = await DataReader.getMessageById(messageId); if (!message) { log.warn( `getRetryConversation/${logId}: Unable to find message ${messageId}` @@ -424,7 +424,7 @@ async function checkDistributionListAndAddSKDM({ listsById.set(list.id, list); }); - const messages = await dataInterface.getMessagesBySentAt(timestamp); + const messages = await DataReader.getMessagesBySentAt(timestamp); const isInAnyDistributionList = messages.some(message => { const listId = message.storyDistributionListId; if (!listId) { @@ -457,10 +457,9 @@ async function checkDistributionListAndAddSKDM({ distributionList, `checkDistributionListAndAddSKDM/${logId}: Should have a distribution list by this point` ); - const distributionDetails = - await window.Signal.Data.getStoryDistributionWithMembers( - distributionList.id - ); + const distributionDetails = await DataReader.getStoryDistributionWithMembers( + distributionList.id + ); const distributionId = distributionDetails?.senderKeyInfo?.distributionId; if (!distributionId) { log.warn( diff --git a/ts/util/incrementMessageCounter.ts b/ts/util/incrementMessageCounter.ts index bd2e347ff..0ddc17330 100644 --- a/ts/util/incrementMessageCounter.ts +++ b/ts/util/incrementMessageCounter.ts @@ -4,7 +4,7 @@ import { debounce, isNumber } from 'lodash'; import { strictAssert } from './assert'; -import Data from '../sql/Client'; +import { DataReader } from '../sql/Client'; import * as log from '../logging/log'; let receivedAtCounter: number | undefined; @@ -16,7 +16,7 @@ export async function initializeMessageCounter(): Promise { ); const storedCounter = Number(localStorage.getItem('lastReceivedAtCounter')); - const dbCounter = await Data.getMaxMessageCounter(); + const dbCounter = await DataReader.getMaxMessageCounter(); if (isNumber(dbCounter) && isNumber(storedCounter)) { log.info('initializeMessageCounter: picking max of db/stored counters'); diff --git a/ts/util/loadRecentEmojis.ts b/ts/util/loadRecentEmojis.ts index 5ebf14bbd..f863e3531 100644 --- a/ts/util/loadRecentEmojis.ts +++ b/ts/util/loadRecentEmojis.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { take } from 'lodash'; -import dataInterface from '../sql/Client'; +import { DataReader } from '../sql/Client'; type RecentEmojiObjectType = { recents: Array; @@ -11,7 +11,7 @@ type RecentEmojiObjectType = { let initialState: RecentEmojiObjectType; async function getRecentEmojisForRedux() { - const recent = await dataInterface.getRecentEmojis(); + const recent = await DataReader.getRecentEmojis(); return recent.map(e => e.shortName); } diff --git a/ts/util/markConversationRead.ts b/ts/util/markConversationRead.ts index 38c2b3bf5..806c846ea 100644 --- a/ts/util/markConversationRead.ts +++ b/ts/util/markConversationRead.ts @@ -4,6 +4,7 @@ import { isNumber, pick } from 'lodash'; import type { ConversationAttributesType } from '../model-types.d'; +import { DataWriter } from '../sql/Client'; import { hasErrors } from '../state/selectors/message'; import { readSyncJobQueue } from '../jobs/readSyncJobQueue'; import { notificationService } from '../services/notifications'; @@ -40,17 +41,17 @@ export async function markConversationRead( const [unreadMessages, unreadEditedMessages, unreadReactions] = await Promise.all([ - window.Signal.Data.getUnreadByConversationAndMarkRead({ + DataWriter.getUnreadByConversationAndMarkRead({ conversationId, newestUnreadAt, readAt: options.readAt, includeStoryReplies: !isGroup(conversationAttrs), }), - window.Signal.Data.getUnreadEditedMessagesAndMarkRead({ + DataWriter.getUnreadEditedMessagesAndMarkRead({ conversationId, newestUnreadAt, }), - window.Signal.Data.getUnreadReactionsAndMarkRead({ + DataWriter.getUnreadReactionsAndMarkRead({ conversationId, newestUnreadAt, }), diff --git a/ts/util/markOnboardingStoryAsRead.ts b/ts/util/markOnboardingStoryAsRead.ts index 86bccee8d..65fefcefc 100644 --- a/ts/util/markOnboardingStoryAsRead.ts +++ b/ts/util/markOnboardingStoryAsRead.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import { __DEPRECATED$getMessageById } from '../messages/getMessageById'; import { isNotNil } from './isNotNil'; import { DurationInSeconds } from './durations'; @@ -44,7 +45,7 @@ export async function markOnboardingStoryAsRead(): Promise { `markOnboardingStoryAsRead: marked ${messageAttributes.length} viewed` ); - await window.Signal.Data.saveMessages(messageAttributes, { + await DataWriter.saveMessages(messageAttributes, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); diff --git a/ts/util/messageBatcher.ts b/ts/util/messageBatcher.ts index 9d2e1cf74..15e28859e 100644 --- a/ts/util/messageBatcher.ts +++ b/ts/util/messageBatcher.ts @@ -4,6 +4,7 @@ import type { MessageAttributesType } from '../model-types.d'; import { createBatcher } from './batcher'; import { createWaitBatcher } from './waitBatcher'; +import { DataWriter } from '../sql/Client'; import * as log from '../logging/log'; const updateMessageBatcher = createBatcher({ @@ -18,7 +19,7 @@ const updateMessageBatcher = createBatcher({ message => window.MessageCache.accessAttributes(message.id) ?? message ); - await window.Signal.Data.saveMessages(messagesToSave, { + await DataWriter.saveMessages(messagesToSave, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); }, @@ -30,7 +31,7 @@ export function queueUpdateMessage(messageAttr: MessageAttributesType): void { if (shouldBatch) { updateMessageBatcher.add(messageAttr); } else { - void window.Signal.Data.saveMessage(messageAttr, { + void DataWriter.saveMessage(messageAttr, { ourAci: window.textsecure.storage.user.getCheckedAci(), }); } @@ -52,7 +53,7 @@ export const saveNewMessageBatcher = createWaitBatcher({ message => window.MessageCache.accessAttributes(message.id) ?? message ); - await window.Signal.Data.saveMessages(messagesToSave, { + await DataWriter.saveMessages(messagesToSave, { forceSave: true, ourAci: window.textsecure.storage.user.getCheckedAci(), }); diff --git a/ts/util/modifyTargetMessage.ts b/ts/util/modifyTargetMessage.ts index 1db61e844..124fdf752 100644 --- a/ts/util/modifyTargetMessage.ts +++ b/ts/util/modifyTargetMessage.ts @@ -9,6 +9,7 @@ import type { SendStateByConversationId } from '../messages/MessageSendState'; import * as Edits from '../messageModifiers/Edits'; import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import * as Deletes from '../messageModifiers/Deletes'; import * as DeletesForMe from '../messageModifiers/DeletesForMe'; import * as MessageReceipts from '../messageModifiers/MessageReceipts'; @@ -313,7 +314,7 @@ export async function modifyTargetMessage( // We save here before handling any edits because handleEditMessage does its own saves if (changed && !isFirstRun) { log.info(`${logId}: Changes in second run; saving.`); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { ourAci, }); } diff --git a/ts/util/onCallLogEventSync.ts b/ts/util/onCallLogEventSync.ts index e50f8e0e3..3ad5d3f59 100644 --- a/ts/util/onCallLogEventSync.ts +++ b/ts/util/onCallLogEventSync.ts @@ -3,6 +3,7 @@ import type { CallLogEventSyncEvent } from '../textsecure/messageReceiverEvents'; import * as log from '../logging/log'; +import { DataWriter } from '../sql/Client'; import type { CallLogEventTarget } from '../types/CallDisposition'; import { CallLogEvent } from '../types/CallDisposition'; import { missingCaseError } from './missingCaseError'; @@ -28,7 +29,7 @@ export async function onCallLogEventSync( if (type === CallLogEvent.Clear) { log.info('onCallLogEventSync: Clearing call history'); try { - const messageIds = await window.Signal.Data.clearCallHistory(target); + const messageIds = await DataWriter.clearCallHistory(target); updateDeletedMessages(messageIds); } finally { // We want to reset the call history even if the clear fails. @@ -38,7 +39,7 @@ export async function onCallLogEventSync( } else if (type === CallLogEvent.MarkedAsRead) { log.info('onCallLogEventSync: Marking call history read'); try { - await window.Signal.Data.markAllCallHistoryRead(target); + await DataWriter.markAllCallHistoryRead(target); } finally { window.reduxActions.callHistory.updateCallHistoryUnreadCount(); } @@ -47,7 +48,7 @@ export async function onCallLogEventSync( log.info('onCallLogEventSync: Marking call history read in conversation'); try { strictAssert(peerId, 'Missing peerId'); - await window.Signal.Data.markAllCallHistoryReadInConversation(target); + await DataWriter.markAllCallHistoryReadInConversation(target); } finally { window.reduxActions.callHistory.updateCallHistoryUnreadCount(); } diff --git a/ts/util/onStoryRecipientUpdate.ts b/ts/util/onStoryRecipientUpdate.ts index 6c46fe929..7de13cf56 100644 --- a/ts/util/onStoryRecipientUpdate.ts +++ b/ts/util/onStoryRecipientUpdate.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { isEqual } from 'lodash'; +import { DataReader } from '../sql/Client'; import type { StoryRecipientUpdateEvent } from '../textsecure/messageReceiverEvents'; import { normalizeServiceId } from '../types/ServiceId'; import { normalizeStoryDistributionId } from '../types/StoryDistributionId'; @@ -90,7 +91,7 @@ export async function onStoryRecipientUpdate( window.ConversationController.getOurConversationIdOrThrow(); const now = Date.now(); - const messages = await window.Signal.Data.getMessagesBySentAt(timestamp); + const messages = await DataReader.getMessagesBySentAt(timestamp); // Now we figure out who needs to be added and who needs to removed const handledMessages = messages.filter(item => { diff --git a/ts/util/queueAttachmentDownloads.ts b/ts/util/queueAttachmentDownloads.ts index adbfc3172..462f91ea9 100644 --- a/ts/util/queueAttachmentDownloads.ts +++ b/ts/util/queueAttachmentDownloads.ts @@ -10,7 +10,7 @@ import { savePackMetadata, getStickerPackStatus, } from '../types/Stickers'; -import dataInterface from '../sql/Client'; +import { DataWriter } from '../sql/Client'; import type { AttachmentType, ThumbnailType } from '../types/Attachment'; import type { EmbeddedContactType } from '../types/EmbeddedContact'; @@ -239,7 +239,7 @@ export async function queueAttachmentDownloads( // Save the packId/packKey for future download/install void savePackMetadata(packId, packKey, { messageId }); } else { - await dataInterface.addStickerPackReference(messageId, packId); + await DataWriter.addStickerPackReference(messageId, packId); } if (!data) { diff --git a/ts/util/sendDeleteForEveryoneMessage.ts b/ts/util/sendDeleteForEveryoneMessage.ts index c15b9f599..6c0e07ce4 100644 --- a/ts/util/sendDeleteForEveryoneMessage.ts +++ b/ts/util/sendDeleteForEveryoneMessage.ts @@ -3,6 +3,7 @@ import type { ConversationAttributesType } from '../model-types.d'; import type { ConversationQueueJobData } from '../jobs/conversationJobQueue'; +import { DataWriter } from '../sql/Client'; import * as Errors from '../types/errors'; import { DAY } from './durations'; import * as log from '../logging/log'; @@ -81,7 +82,7 @@ export async function sendDeleteForEveryoneMessage( `sendDeleteForEveryoneMessage: Deleting message ${idForLogging} ` + `in conversation ${conversationIdForLogging} with job ${jobToInsert.id}` ); - await window.Signal.Data.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); diff --git a/ts/util/sendEditedMessage.ts b/ts/util/sendEditedMessage.ts index 8ce57311d..ed24a161a 100644 --- a/ts/util/sendEditedMessage.ts +++ b/ts/util/sendEditedMessage.ts @@ -10,6 +10,7 @@ import type { QuotedMessageType, } from '../model-types.d'; import * as log from '../logging/log'; +import { DataReader, DataWriter } from '../sql/Client'; import type { AttachmentType } from '../types/Attachment'; import { ErrorWithToast } from '../types/ErrorWithToast'; import { SendStatus } from '../messages/MessageSendState'; @@ -129,9 +130,7 @@ export async function sendEditedMessage( if (quoteSentAt === existingQuote?.id) { quote = existingQuote; } else { - const messages = await window.Signal.Data.getMessagesBySentAt( - quoteSentAt - ); + const messages = await DataReader.getMessagesBySentAt(quoteSentAt); const matchingMessage = find(messages, item => isQuoteAMatch(item, conversationId, { id: quoteSentAt, @@ -224,7 +223,7 @@ export async function sendEditedMessage( log.info( `${idLog}: saving message ${targetMessageId} and job ${jobToInsert.id}` ); - await window.Signal.Data.saveMessage(targetMessage.attributes, { + await DataWriter.saveMessage(targetMessage.attributes, { jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -249,5 +248,5 @@ export async function sendEditedMessage( duration => `${idLog}: batchDispatch took ${duration}ms` ); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); } diff --git a/ts/util/sendStoryMessage.ts b/ts/util/sendStoryMessage.ts index b9ad69650..7787d9741 100644 --- a/ts/util/sendStoryMessage.ts +++ b/ts/util/sendStoryMessage.ts @@ -12,7 +12,7 @@ import type { import type { StoryDistributionIdString } from '../types/StoryDistributionId'; import type { ServiceIdString } from '../types/ServiceId'; import * as log from '../logging/log'; -import dataInterface from '../sql/Client'; +import { DataReader, DataWriter } from '../sql/Client'; import { MY_STORY_ID, StorySendMode } from '../types/Stories'; import { getStoriesBlocked } from './stories'; import { ReadStatus } from '../messages/MessageReadStatus'; @@ -54,9 +54,7 @@ export async function sendStoryMessage( const distributionLists = ( await Promise.all( - listIds.map(listId => - dataInterface.getStoryDistributionWithMembers(listId) - ) + listIds.map(listId => DataReader.getStoryDistributionWithMembers(listId)) ) ).filter(isNotNil); @@ -242,7 +240,7 @@ export async function sendStoryMessage( for (const group of groupsToUpdate) { group.set('storySendMode', StorySendMode.Always); } - void window.Signal.Data.updateConversations( + void DataWriter.updateConversations( groupsToUpdate.map(group => group.attributes) ); for (const group of groupsToUpdate) { @@ -322,7 +320,7 @@ export async function sendStoryMessage( log.info( `stories.sendStoryMessage: saving message ${messageAttributes.timestamp}` ); - return dataInterface.saveMessage(message.attributes, { + return DataWriter.saveMessage(message.attributes, { forceSave: true, ourAci: window.textsecure.storage.user.getCheckedAci(), }); @@ -377,7 +375,7 @@ export async function sendStoryMessage( log.info( `stories.sendStoryMessage: saving message ${messageAttributes.timestamp}` ); - await dataInterface.saveMessage(message.attributes, { + await DataWriter.saveMessage(message.attributes, { forceSave: true, jobToInsert, ourAci: window.textsecure.storage.user.getCheckedAci(), diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index efa4c58ae..d242a1c22 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -23,6 +23,7 @@ import { import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; import * as Errors from '../types/errors'; +import { DataWriter } from '../sql/Client'; import { getValue } from '../RemoteConfig'; import type { ServiceIdString } from '../types/ServiceId'; import { ServiceIdKind } from '../types/ServiceId'; @@ -558,7 +559,7 @@ export async function sendToGroupViaSenderKey(options: { } if (shouldSaveProto(sendType)) { - sendLogId = await window.Signal.Data.insertSentProto( + sendLogId = await DataWriter.insertSentProto( { contentHint, proto: Buffer.from(Proto.Content.encode(contentMessage).finish()), @@ -612,7 +613,7 @@ export async function sendToGroupViaSenderKey(options: { `sendToGroupViaSenderKey/${logId}: Disabling sealed sender for ${brokenAccount.idForLogging()}` ); brokenAccount.set({ sealedSender: SEALED_SENDER.DISABLED }); - window.Signal.Data.updateConversation(brokenAccount.attributes); + await DataWriter.updateConversation(brokenAccount.attributes); // Now that we've eliminate this problematic account, we can try the send again. return sendToGroupViaSenderKey({ @@ -682,7 +683,7 @@ export async function sendToGroupViaSenderKey(options: { return; } - await window.Signal.Data.insertProtoRecipients({ + await DataWriter.insertProtoRecipients({ id: sendLogId, recipientServiceId, deviceIds, @@ -923,7 +924,7 @@ async function markServiceIdUnregistered(serviceId: ServiceIdString) { ); conversation.setUnregistered(); - window.Signal.Data.updateConversation(conversation.attributes); + await DataWriter.updateConversation(conversation.attributes); await window.textsecure.storage.protocol.archiveAllSessions(serviceId); } @@ -1346,7 +1347,7 @@ async function fetchKeysForServiceId( emptyConversation.set({ sealedSender: SEALED_SENDER.DISABLED, }); - window.Signal.Data.updateConversation(emptyConversation.attributes); + await DataWriter.updateConversation(emptyConversation.attributes); } } catch (error: unknown) { if (error instanceof UnregisteredUserError) { diff --git a/ts/util/shouldDownloadStory.ts b/ts/util/shouldDownloadStory.ts index de62b5dd8..1b8578cb2 100644 --- a/ts/util/shouldDownloadStory.ts +++ b/ts/util/shouldDownloadStory.ts @@ -3,7 +3,7 @@ import type { ConversationAttributesType } from '../model-types.d'; -import dataInterface from '../sql/Client'; +import { DataReader } from '../sql/Client'; import { isMe } from './whatTypeOfConversation'; const MAX_NUM_STORIES_TO_PREFETCH = 5; @@ -21,8 +21,8 @@ export async function shouldDownloadStory( } const [storyReads, storyCounts] = await Promise.all([ - dataInterface.countStoryReadsByConversation(conversation.id), - dataInterface.getStoryCount(conversation.id), + DataReader.countStoryReadsByConversation(conversation.id), + DataReader.getStoryCount(conversation.id), ]); return storyReads > 0 && storyCounts <= MAX_NUM_STORIES_TO_PREFETCH; diff --git a/ts/util/shouldReplyNotifyUser.ts b/ts/util/shouldReplyNotifyUser.ts index ad97cedb8..3f511ab44 100644 --- a/ts/util/shouldReplyNotifyUser.ts +++ b/ts/util/shouldReplyNotifyUser.ts @@ -4,7 +4,7 @@ import type { ConversationModel } from '../models/conversations'; import type { MessageAttributesType } from '../model-types.d'; import * as log from '../logging/log'; -import dataInterface from '../sql/Client'; +import { DataReader } from '../sql/Client'; import { isGroup } from './whatTypeOfConversation'; import { isMessageUnread } from './isMessageUnread'; @@ -56,7 +56,7 @@ export async function shouldReplyNotifyUser( // If the story is from a different user, only notify if the user has // replied or reacted to the story - const replies = await dataInterface.getOlderMessagesByConversation({ + const replies = await DataReader.getOlderMessagesByConversation({ conversationId: conversation.id, limit: 9000, storyId, diff --git a/ts/window.d.ts b/ts/window.d.ts index fbb0258e9..d88f08ec8 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -33,7 +33,6 @@ import type { Receipt } from './types/Receipt'; import type { ConversationController } from './ConversationController'; import type { ReduxActions } from './state/types'; import type { createApp } from './state/roots/createApp'; -import type Data from './sql/Client'; import type { MessageModel } from './models/messages'; import type { ConversationModel } from './models/conversations'; import type { BatcherType } from './util/batcher'; @@ -138,7 +137,6 @@ export type SignalCoreType = { AboutWindowProps?: AboutWindowPropsType; Crypto: typeof Crypto; Curve: typeof Curve; - Data: typeof Data; DebugLogWindowProps?: DebugLogWindowPropsType; Groups: typeof Groups; PermissionsWindowProps?: PermissionsWindowPropsType; diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts index 51aa98099..45577c672 100644 --- a/ts/windows/main/phase1-ipc.ts +++ b/ts/windows/main/phase1-ipc.ts @@ -17,6 +17,7 @@ import * as Errors from '../../types/errors'; import { strictAssert } from '../../util/assert'; import { drop } from '../../util/drop'; +import { DataReader } from '../../sql/Client'; import type { NotificationClickData, WindowsNotificationData, @@ -193,7 +194,7 @@ ipc.on('additional-log-data-request', async event => { let statistics; try { - statistics = await window.Signal.Data.getStatisticsForLogging(); + statistics = await DataReader.getStatisticsForLogging(); } catch (error) { statistics = {}; } diff --git a/ts/windows/main/start.ts b/ts/windows/main/start.ts index 1c3808eb5..e378f2bb2 100644 --- a/ts/windows/main/start.ts +++ b/ts/windows/main/start.ts @@ -23,7 +23,6 @@ import { start as startConversationController } from '../../ConversationControll import { initMessageCleanup } from '../../services/messageStateCleanup'; import { Environment, getEnvironment } from '../../environment'; import { isProduction } from '../../util/version'; -import { ipcInvoke } from '../../sql/channels'; import { benchmarkConversationOpen } from '../../CI/benchmarkConversationOpen'; window.addEventListener('contextmenu', e => { @@ -89,8 +88,6 @@ if (!isProduction(window.SignalContext.getVersion())) { }, setRtcStatsInterval: (intervalMillis: number) => window.Signal.Services.calling.setAllRtcStatsInterval(intervalMillis), - sqlCall: (name: string, ...args: ReadonlyArray) => - ipcInvoke(name, args), ...(window.SignalContext.config.ciMode === 'benchmark' ? { benchmarkConversationOpen,