chore: refactor db instance to outside its sql.ts file

pull/2620/head
Audric Ackermann 3 years ago
parent d1327fab5f
commit c8e76b17de

@ -1,10 +1 @@
export const channels = {} as Record<string, any>; export const channels = {} as Record<string, any>;
// export const addChannel = (id: string, action: any) => {
// (window as any).channels = (window as any).channels || {};
// (window as any).channels[id] = action;
// };
// export const getChannel = (id: string): ((...args: any) => Promise<any>) => {
// return (window as any).channels[id];
// };

@ -0,0 +1,26 @@
import {
AsyncWrapper,
ConfigDumpRow,
GetByPubkeyConfigDump,
GetByVariantAndPubkeyConfigDump,
SaveConfigDump,
SharedConfigSupportedVariant,
} from '../../types/sqlSharedTypes';
import { channels } from '../channels';
const getByVariantAndPubkey: AsyncWrapper<GetByVariantAndPubkeyConfigDump> = (
variant: SharedConfigSupportedVariant,
pubkey: string
) => {
return channels.getConfigDumpByVariantAndPubkey(variant, pubkey);
};
const getByPubkey: AsyncWrapper<GetByPubkeyConfigDump> = (pubkey: string) => {
return channels.getConfigDumpsByPk(pubkey);
};
const saveConfigDump: AsyncWrapper<SaveConfigDump> = (dump: ConfigDumpRow) => {
return channels.saveConfigDump(dump);
};
export const ConfigDumpData = { getByVariantAndPubkey, getByPubkey, saveConfigDump };

@ -1,6 +1,7 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import _ from 'lodash'; import _ from 'lodash';
import { channels } from './channels'; import { channels } from './channels';
import { ConfigDumpData } from './configDump/configDump';
const channelsToMakeForOpengroupV2 = [ const channelsToMakeForOpengroupV2 = [
'getAllV2OpenGroupRooms', 'getAllV2OpenGroupRooms',
@ -10,6 +11,8 @@ const channelsToMakeForOpengroupV2 = [
'getAllOpenGroupV2Conversations', 'getAllOpenGroupV2Conversations',
]; ];
const channelsToMakeForConfigDumps = [...Object.keys(ConfigDumpData)];
const channelsToMake = new Set([ const channelsToMake = new Set([
'shutdown', 'shutdown',
'close', 'close',
@ -89,6 +92,7 @@ const channelsToMake = new Set([
'removeAllClosedGroupEncryptionKeyPairs', 'removeAllClosedGroupEncryptionKeyPairs',
'fillWithTestData', 'fillWithTestData',
...channelsToMakeForOpengroupV2, ...channelsToMakeForOpengroupV2,
...channelsToMakeForConfigDumps,
]); ]);
const SQL_CHANNEL_KEY = 'sql-channel'; const SQL_CHANNEL_KEY = 'sql-channel';

@ -80,6 +80,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToSessionSchemaVersion28, updateToSessionSchemaVersion28,
updateToSessionSchemaVersion29, updateToSessionSchemaVersion29,
updateToSessionSchemaVersion30, updateToSessionSchemaVersion30,
updateToSessionSchemaVersion31,
]; ];
function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) { function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -1223,9 +1224,33 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`); console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
} }
// function printTableColumns(table: string, db: BetterSqlite3.Database) { function updateToSessionSchemaVersion31(currentVersion: number, db: BetterSqlite3.Database) {
// console.info(db.pragma(`table_info('${table}');`)); const targetVersion = 31;
// } if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
/**
* Create a table to store our sharedConfigMessage dumps
**/
db.transaction(() => {
db.exec(`CREATE TABLE configDump(
variant TEXT NOT NULL,
publicKey TEXT NOT NULL,
data BLOB,
combinedMessageHashes TEXT);
`);
throw null;
writeSessionSchemaVersion(targetVersion, db);
})();
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}
export function printTableColumns(table: string, db: BetterSqlite3.Database) {
console.info(db.pragma(`table_info('${table}');`));
}
function writeSessionSchemaVersion(newVersion: number, db: BetterSqlite3.Database) { function writeSessionSchemaVersion(newVersion: number, db: BetterSqlite3.Database) {
db.prepare( db.prepare(

@ -46,7 +46,12 @@ import {
toSqliteBoolean, toSqliteBoolean,
} from './database_utility'; } from './database_utility';
import { UpdateLastHashType } from '../types/sqlSharedTypes'; import {
ConfigDumpDataNode,
ConfigDumpRow,
SharedConfigSupportedVariant,
UpdateLastHashType,
} from '../types/sqlSharedTypes';
import { OpenGroupV2Room } from '../data/opengroups'; import { OpenGroupV2Room } from '../data/opengroups';
import { import {
@ -55,6 +60,13 @@ import {
updateSchema, updateSchema,
} from './migration/signalMigrations'; } from './migration/signalMigrations';
import { SettingsKey } from '../data/settings-key'; import { SettingsKey } from '../data/settings-key';
import {
assertGlobalInstance,
assertGlobalInstanceOrInstance,
closeDbInstance,
initDbInstanceWith,
isInstanceInitialized,
} from './sqlInstance';
// tslint:disable: no-console function-name non-literal-fs-path // tslint:disable: no-console function-name non-literal-fs-path
@ -74,14 +86,14 @@ function openAndSetUpSQLCipher(filePath: string, { key }: { key: string }) {
} }
function setSQLPassword(password: string) { function setSQLPassword(password: string) {
if (!globalInstance) { if (!assertGlobalInstance()) {
throw new Error('setSQLPassword: db is not initialized'); throw new Error('setSQLPassword: db is not initialized');
} }
// If the password isn't hex then we need to derive a key from it // If the password isn't hex then we need to derive a key from it
const deriveKey = HEX_KEY.test(password); const deriveKey = HEX_KEY.test(password);
const value = deriveKey ? `'${password}'` : `"x'${password}'"`; const value = deriveKey ? `'${password}'` : `"x'${password}'"`;
globalInstance.pragma(`rekey = ${value}`); assertGlobalInstance().pragma(`rekey = ${value}`);
} }
function vacuumDatabase(db: BetterSqlite3.Database) { function vacuumDatabase(db: BetterSqlite3.Database) {
@ -94,26 +106,6 @@ function vacuumDatabase(db: BetterSqlite3.Database) {
console.info(`Vacuuming DB Finished in ${Date.now() - start}ms.`); console.info(`Vacuuming DB Finished in ${Date.now() - start}ms.`);
} }
let globalInstance: BetterSqlite3.Database | null = null;
function assertGlobalInstance(): BetterSqlite3.Database {
if (!globalInstance) {
throw new Error('globalInstance is not initialized.');
}
return globalInstance;
}
function assertGlobalInstanceOrInstance(
instance?: BetterSqlite3.Database | null
): BetterSqlite3.Database {
// if none of them are initialized, throw
if (!globalInstance && !instance) {
throw new Error('neither globalInstance nor initialized is initialized.');
}
// otherwise, return which ever is true, priority to the global one
return globalInstance || (instance as BetterSqlite3.Database);
}
let databaseFilePath: string | undefined; let databaseFilePath: string | undefined;
function _initializePaths(configDir: string) { function _initializePaths(configDir: string) {
@ -143,7 +135,7 @@ async function initializeSql({
passwordAttempt: boolean; passwordAttempt: boolean;
}) { }) {
console.info('initializeSql sqlnode'); console.info('initializeSql sqlnode');
if (globalInstance) { if (isInstanceInitialized()) {
throw new Error('Cannot initialize more than once!'); throw new Error('Cannot initialize more than once!');
} }
@ -184,7 +176,7 @@ async function initializeSql({
} }
// At this point we can allow general access to the database // At this point we can allow general access to the database
globalInstance = db; initDbInstanceWith(db);
console.info('total message count before cleaning: ', getMessageCount()); console.info('total message count before cleaning: ', getMessageCount());
console.info('total conversation count before cleaning: ', getConversationCount()); console.info('total conversation count before cleaning: ', getConversationCount());
@ -215,7 +207,7 @@ async function initializeSql({
if (button.response === 0) { if (button.response === 0) {
clipboard.writeText(`Database startup error:\n\n${redactAll(error.stack)}`); clipboard.writeText(`Database startup error:\n\n${redactAll(error.stack)}`);
} else { } else {
close(); closeDbInstance();
showFailedToStart(); showFailedToStart();
} }
@ -226,20 +218,8 @@ async function initializeSql({
return true; return true;
} }
function close() {
if (!globalInstance) {
return;
}
const dbRef = globalInstance;
globalInstance = null;
// SQLLite documentation suggests that we run `PRAGMA optimize` right before
// closing the database connection.
dbRef.pragma('optimize');
dbRef.close();
}
function removeDB(configDir = null) { function removeDB(configDir = null) {
if (globalInstance) { if (isInstanceInitialized()) {
throw new Error('removeDB: Cannot erase database when it is open!'); throw new Error('removeDB: Cannot erase database when it is open!');
} }
@ -1232,7 +1212,7 @@ function getMessagesByConversation(conversationId: string, { messageId = null }
// If messageId is null, it means we are just opening the convo to the last unread message, or at the bottom // If messageId is null, it means we are just opening the convo to the last unread message, or at the bottom
const firstUnread = getFirstUnreadMessageIdInConversation(conversationId); const firstUnread = getFirstUnreadMessageIdInConversation(conversationId);
const numberOfMessagesInConvo = getMessagesCountByConversation(conversationId, globalInstance); const numberOfMessagesInConvo = getMessagesCountByConversation(conversationId);
const floorLoadAllMessagesInConvo = 70; const floorLoadAllMessagesInConvo = 70;
if (messageId || firstUnread) { if (messageId || firstUnread) {
@ -2046,6 +2026,70 @@ function removeV2OpenGroupRoom(conversationId: string) {
}); });
} }
/**
* Config dumps sql calls
*/
const configDumpData: ConfigDumpDataNode = {
getConfigDumpByVariantAndPubkey: (variant: SharedConfigSupportedVariant, pubkey: string) => {
const rows = assertGlobalInstance()
.prepare(`SELECT * from configDump WHERE variant = $variant AND pubkey = $pubkey;`)
.get({
pubkey,
variant,
});
if (!rows) {
return [];
}
throw new Error(`getConfigDumpByVariantAndPubkey: rows: ${JSON.stringify(rows)} `);
return rows;
},
getConfigDumpsByPubkey: (pubkey: string) => {
const rows = assertGlobalInstance()
.prepare(`SELECT * from configDump WHERE pubkey = $pubkey;`)
.get({
pubkey,
});
if (!rows) {
return [];
}
throw new Error(`getConfigDumpsByPubkey: rows: ${JSON.stringify(rows)} `);
return rows;
},
saveConfigDump: ({ data, pubkey, variant, combinedMessageHashes }: ConfigDumpRow) => {
assertGlobalInstance()
.prepare(
`INSERT OR REPLACE INTO configDump (
pubkey,
variant,
combinedMessageHashes,
data
) values (
$pubkey,
$variant,
$combinedMessageHashes,
$data,
);`
)
.run({
pubkey,
variant,
combinedMessageHashes,
data,
});
},
};
/**
* Others
*/
function getEntriesCountInTable(tbl: string) { function getEntriesCountInTable(tbl: string) {
try { try {
const row = assertGlobalInstance() const row = assertGlobalInstance()
@ -2424,6 +2468,10 @@ function fillWithTestData(numConvosToAdd: number, numMsgsToAdd: number) {
export type SqlNodeType = typeof sqlNode; export type SqlNodeType = typeof sqlNode;
export function close() {
closeDbInstance();
}
export const sqlNode = { export const sqlNode = {
initializeSql, initializeSql,
close, close,
@ -2528,4 +2576,7 @@ export const sqlNode = {
saveV2OpenGroupRoom, saveV2OpenGroupRoom,
getAllV2OpenGroupRooms, getAllV2OpenGroupRooms,
removeV2OpenGroupRoom, removeV2OpenGroupRoom,
// config dumps
...configDumpData,
}; };

@ -0,0 +1,44 @@
import * as BetterSqlite3 from 'better-sqlite3';
let globalInstance: BetterSqlite3.Database | null = null;
export function assertGlobalInstance(): BetterSqlite3.Database {
if (!globalInstance) {
throw new Error('globalInstance is not initialized.');
}
return globalInstance;
}
export function isInstanceInitialized(): boolean {
return !!globalInstance;
}
export function assertGlobalInstanceOrInstance(
instance?: BetterSqlite3.Database | null
): BetterSqlite3.Database {
// if none of them are initialized, throw
if (!globalInstance && !instance) {
throw new Error('neither globalInstance nor initialized is initialized.');
}
// otherwise, return which ever is true, priority to the global one
return globalInstance || (instance as BetterSqlite3.Database);
}
export function initDbInstanceWith(instance: BetterSqlite3.Database) {
if (globalInstance) {
throw new Error('already init');
}
globalInstance = instance;
}
export function closeDbInstance() {
if (!globalInstance) {
return;
}
const dbRef = globalInstance;
globalInstance = null;
// SQLLite documentation suggests that we run `PRAGMA optimize` right before
// closing the database connection.
dbRef.pragma('optimize');
dbRef.close();
}

@ -90,7 +90,6 @@ async function retrieveNextMessages(
// let exceptions bubble up // let exceptions bubble up
// no retry for this one as this a call we do every few seconds while polling for messages // no retry for this one as this a call we do every few seconds while polling for messages
console.warn(`fetching messages associatedWith:${associatedWith} namespaces:${namespaces}`);
const results = await doSnodeBatchRequest(retrieveRequestsParams, targetNode, 4000); const results = await doSnodeBatchRequest(retrieveRequestsParams, targetNode, 4000);
if (!results || !results.length) { if (!results || !results.length) {

@ -1,4 +1,3 @@
import { to_string } from 'libsodium-wrappers-sumo';
import { getSodiumRenderer } from '../../crypto'; import { getSodiumRenderer } from '../../crypto';
import { UserUtils, StringUtils } from '../../utils'; import { UserUtils, StringUtils } from '../../utils';
import { fromHexToArray, fromUInt8ArrayToBase64 } from '../../utils/String'; import { fromHexToArray, fromUInt8ArrayToBase64 } from '../../utils/String';
@ -40,9 +39,6 @@ async function getSnodeSignatureParams(params: {
try { try {
const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes); const signature = sodium.crypto_sign_detached(message, edKeyPrivBytes);
const signatureBase64 = fromUInt8ArrayToBase64(signature); const signatureBase64 = fromUInt8ArrayToBase64(signature);
console.warn(
`signing: "${to_string(new Uint8Array(verificationData))}" signature:"${signatureBase64}"`
);
return { return {
timestamp: signatureTimestamp, timestamp: signatureTimestamp,

@ -236,8 +236,8 @@ export class SwarmPolling {
); );
} }
console.warn(`received userConfigMessagesMerged: ${userConfigMessagesMerged.length}`); console.info(`received userConfigMessagesMerged: ${userConfigMessagesMerged.length}`);
console.warn( console.info(
`received allNamespacesWithoutUserConfigIfNeeded: ${allNamespacesWithoutUserConfigIfNeeded.length}` `received allNamespacesWithoutUserConfigIfNeeded: ${allNamespacesWithoutUserConfigIfNeeded.length}`
); );

@ -125,7 +125,7 @@ export async function send(
: SnodeNamespaces.UserMessages; : SnodeNamespaces.UserMessages;
} }
let timestamp = networkTimestamp; let timestamp = networkTimestamp;
// the user config namespacesm requires a signature to be added // the user config namespaces requires a signature to be added
let signOpts: SnodeSignatureResult | undefined; let signOpts: SnodeSignatureResult | undefined;
if (SnodeNamespace.isUserConfigNamespace(namespace)) { if (SnodeNamespace.isUserConfigNamespace(namespace)) {
signOpts = await SnodeSignature.getSnodeSignatureParams({ signOpts = await SnodeSignature.getSnodeSignatureParams({

@ -1,3 +1,11 @@
/**
* This wrapper can be used to make a function type not async, asynced.
* We use it in the typing of the database communication, because the data calls (renderer side) have essentially the same signature of the sql calls (node side), with an added `await`
*/
export type AsyncWrapper<T extends (...args: any) => any> = (
...args: Parameters<T>
) => Promise<ReturnType<T>>;
export type MsgDuplicateSearchOpenGroup = Array<{ export type MsgDuplicateSearchOpenGroup = Array<{
sender: string; sender: string;
serverTimestamp: number; serverTimestamp: number;
@ -11,3 +19,30 @@ export type UpdateLastHashType = {
expiresAt: number; expiresAt: number;
namespace: number; namespace: number;
}; };
/**
* Shared config dump types
*/
export type SharedConfigSupportedVariant = 'user-profile' | 'contacts';
export type ConfigDumpRow = {
variant: SharedConfigSupportedVariant; // the variant this entry is about. (user-config, contacts, ...)
pubkey: string; // either our pubkey if a dump for our own swarm or the closed group pubkey
data: Uint8Array; // the blob returned by libsession.dump() call
combinedMessageHashes?: string; // array of lastHashes to keep track of, stringified
// we might need to add a `seqno` field here.
};
export type GetByVariantAndPubkeyConfigDump = (
variant: SharedConfigSupportedVariant,
pubkey: string
) => Array<ConfigDumpRow>;
export type GetByPubkeyConfigDump = (pubkey: string) => Array<ConfigDumpRow>;
export type SaveConfigDump = (dump: ConfigDumpRow) => void;
export type ConfigDumpDataNode = {
getConfigDumpByVariantAndPubkey: GetByVariantAndPubkeyConfigDump;
getConfigDumpsByPubkey: GetByPubkeyConfigDump;
saveConfigDump: SaveConfigDump;
};

@ -8,8 +8,6 @@ import { ConfigWrapperObjectTypes } from '../../browser/libsession_worker_functi
let userConfig: UserConfigWrapper; let userConfig: UserConfigWrapper;
/* eslint-disable strict */
// async function getSodiumWorker() { // async function getSodiumWorker() {
// await sodiumWrappers.ready; // await sodiumWrappers.ready;
@ -50,8 +48,8 @@ function initUserConfigWrapper(options: Array<any>) {
userConfig = new UserConfigWrapper(edSecretKey, dump); userConfig = new UserConfigWrapper(edSecretKey, dump);
} }
// tslint:disable: function-name // tslint:disable: function-name no-console
//tslint-disable no-console
onmessage = async (e: { data: [number, ConfigWrapperObjectTypes, string, ...any] }) => { onmessage = async (e: { data: [number, ConfigWrapperObjectTypes, string, ...any] }) => {
const [jobId, config, action, ...args] = e.data; const [jobId, config, action, ...args] = e.data;

Loading…
Cancel
Save