2018-07-27 03:13:56 +02:00
const path = require ( 'path' ) ;
2021-12-08 04:15:54 +01:00
const fs = require ( 'fs' ) ;
2018-07-27 03:13:56 +02:00
const rimraf = require ( 'rimraf' ) ;
2021-07-08 05:00:20 +02:00
const SQL = require ( 'better-sqlite3' ) ;
2019-02-20 02:32:44 +01:00
const { app , dialog , clipboard } = require ( 'electron' ) ;
const { redactAll } = require ( '../js/modules/privacy' ) ;
const { remove : removeUserConfig } = require ( './user_config' ) ;
2021-07-08 05:00:20 +02:00
const { map , isString , fromPairs , forEach , last , isEmpty , isObject , isNumber } = require ( 'lodash' ) ;
2018-07-27 03:13:56 +02:00
2021-07-12 08:00:04 +02:00
/* eslint-disable camelcase */
2018-07-27 03:13:56 +02:00
module . exports = {
initialize ,
close ,
removeDB ,
2018-12-06 01:33:11 +01:00
setSQLPassword ,
getPasswordHash ,
savePasswordHash ,
removePasswordHash ,
2018-11-07 05:16:49 +01:00
getIdentityKeyById ,
createOrUpdateItem ,
getItemById ,
getAllItems ,
removeItemById ,
2020-07-06 07:28:22 +02:00
getSwarmNodesForPubkey ,
updateSwarmNodesForPubkey ,
2020-03-23 05:00:51 +01:00
getGuardNodes ,
updateGuardNodes ,
2019-01-09 02:33:21 +01:00
2018-09-21 03:47:19 +02:00
getConversationCount ,
saveConversation ,
getConversationById ,
updateConversation ,
removeConversation ,
getAllConversations ,
2021-04-23 06:59:08 +02:00
getAllOpenGroupV1Conversations ,
getAllOpenGroupV2Conversations ,
2019-11-13 05:52:17 +01:00
getPubkeysInPublicConversation ,
2018-09-21 03:47:19 +02:00
getAllGroupsInvolvingId ,
2019-09-02 06:24:02 +02:00
removeAllConversations ,
2019-01-14 22:47:19 +01:00
2018-09-21 03:47:19 +02:00
searchConversations ,
2019-01-14 22:47:19 +01:00
searchMessages ,
searchMessagesInConversation ,
2018-09-21 03:47:19 +02:00
2018-08-07 01:18:58 +02:00
getMessageCount ,
2018-07-27 03:13:56 +02:00
saveMessage ,
2018-11-19 07:57:20 +01:00
cleanSeenMessages ,
2019-04-15 05:19:06 +02:00
cleanLastHashes ,
2018-11-19 07:57:20 +01:00
saveSeenMessageHashes ,
saveSeenMessageHash ,
2019-04-15 05:19:06 +02:00
updateLastHash ,
2018-07-27 03:13:56 +02:00
saveMessages ,
removeMessage ,
getUnreadByConversation ,
2020-10-30 06:02:18 +01:00
getUnreadCountByConversation ,
2018-07-27 03:13:56 +02:00
getMessageBySender ,
2021-06-07 07:54:44 +02:00
getMessageBySenderAndServerTimestamp ,
2021-09-20 05:47:59 +02:00
getMessageBySenderAndTimestamp ,
2020-04-20 01:52:47 +02:00
getMessageIdsFromServerIds ,
2018-07-27 03:13:56 +02:00
getMessageById ,
getMessagesBySentAt ,
2018-11-19 07:57:20 +01:00
getSeenMessagesByHashList ,
2019-04-15 05:19:06 +02:00
getLastHashBySnode ,
2018-07-27 03:13:56 +02:00
getExpiredMessages ,
2018-08-07 21:33:56 +02:00
getOutgoingWithoutExpiresAt ,
2018-07-27 03:13:56 +02:00
getNextExpiringMessage ,
getMessagesByConversation ,
2021-07-22 02:20:09 +02:00
getFirstUnreadMessageIdInConversation ,
2021-12-08 04:15:54 +01:00
hasConversationOutgoingMessage ,
2018-07-27 03:13:56 +02:00
2018-09-29 00:51:26 +02:00
getUnprocessedCount ,
2018-07-27 03:13:56 +02:00
getAllUnprocessed ,
saveUnprocessed ,
2019-02-05 02:23:50 +01:00
updateUnprocessedAttempts ,
updateUnprocessedWithData ,
2018-07-27 03:13:56 +02:00
getUnprocessedById ,
removeUnprocessed ,
removeAllUnprocessed ,
2019-01-30 21:15:07 +01:00
getNextAttachmentDownloadJobs ,
saveAttachmentDownloadJob ,
setAttachmentDownloadJobPending ,
resetAttachmentDownloadPending ,
removeAttachmentDownloadJob ,
removeAllAttachmentDownloadJobs ,
2018-07-27 03:13:56 +02:00
removeAll ,
getMessagesWithVisualMediaAttachments ,
getMessagesWithFileAttachments ,
2018-08-07 01:18:58 +02:00
removeKnownAttachments ,
2020-05-04 05:24:53 +02:00
2021-01-28 02:06:51 +01:00
getAllEncryptionKeyPairsForGroup ,
2021-01-12 06:11:05 +01:00
getLatestClosedGroupEncryptionKeyPair ,
addClosedGroupEncryptionKeyPair ,
removeAllClosedGroupEncryptionKeyPairs ,
2021-04-15 10:14:04 +02:00
// open group v2
getV2OpenGroupRoom ,
saveV2OpenGroupRoom ,
getAllV2OpenGroupRooms ,
getV2OpenGroupRoomByRoomId ,
removeV2OpenGroupRoom ,
2021-06-11 07:14:52 +02:00
removeOneOpenGroupV1Message ,
2018-07-27 03:13:56 +02:00
} ;
2021-06-11 04:04:07 +02:00
const CONVERSATIONS _TABLE = 'conversations' ;
const MESSAGES _TABLE = 'messages' ;
2021-06-11 04:09:57 +02:00
const MESSAGES _FTS _TABLE = 'messages_fts' ;
2021-06-11 04:04:07 +02:00
const NODES _FOR _PUBKEY _TABLE = 'nodesForPubkey' ;
const OPEN _GROUP _ROOMS _V2 _TABLE = 'openGroupRoomsV2' ;
const IDENTITY _KEYS _TABLE = 'identityKeys' ;
const GUARD _NODE _TABLE = 'guardNodes' ;
const ITEMS _TABLE = 'items' ;
const ATTACHMENT _DOWNLOADS _TABLE = 'attachment_downloads' ;
const CLOSED _GROUP _V2 _KEY _PAIRS _TABLE = 'encryptionKeyPairsForClosedGroupV2' ;
2021-08-02 06:33:39 +02:00
const MAX _PUBKEYS _MEMBERS = 300 ;
2021-06-11 05:11:44 +02:00
2018-07-27 03:13:56 +02:00
function objectToJSON ( data ) {
return JSON . stringify ( data ) ;
}
function jsonToObject ( json ) {
return JSON . parse ( json ) ;
}
2021-07-08 05:00:20 +02:00
function getSQLiteVersion ( db ) {
const { sqlite _version } = db . prepare ( 'select sqlite_version() as sqlite_version' ) . get ( ) ;
return sqlite _version ;
}
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
function getSchemaVersion ( db ) {
return db . pragma ( 'schema_version' , { simple : true } ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function getSQLCipherVersion ( db ) {
return db . pragma ( 'cipher_version' , { simple : true } ) ;
}
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
function getSQLCipherIntegrityCheck ( db ) {
const rows = db . pragma ( 'cipher_integrity_check' ) ;
if ( rows . length === 0 ) {
return undefined ;
}
return rows . map ( row => row . cipher _integrity _check ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function keyDatabase ( db , key ) {
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#key
2021-07-28 09:25:20 +02:00
// If the password isn't hex then we need to derive a key from it
const deriveKey = HEX _KEY . test ( key ) ;
const value = deriveKey ? ` ' ${ key } ' ` : ` "x' ${ key } '" ` ;
const pragramToRun = ` key = ${ value } ` ;
db . pragma ( pragramToRun ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function switchToWAL ( db ) {
// https://sqlite.org/wal.html
db . pragma ( 'journal_mode = WAL' ) ;
db . pragma ( 'synchronous = FULL' ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function getSQLIntegrityCheck ( db ) {
const checkResult = db . pragma ( 'quick_check' , { simple : true } ) ;
if ( checkResult !== 'ok' ) {
return checkResult ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
return undefined ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
const HEX _KEY = /[^0-9A-Fa-f]/ ;
function migrateSchemaVersion ( db ) {
const userVersion = getUserVersion ( db ) ;
if ( userVersion > 0 ) {
return ;
2020-03-03 05:17:30 +01:00
}
2021-07-08 05:00:20 +02:00
const schemaVersion = getSchemaVersion ( db ) ;
2021-07-13 05:42:34 +02:00
2021-07-08 05:00:20 +02:00
const newUserVersion = schemaVersion > 18 ? 16 : schemaVersion ;
console . log (
'migrateSchemaVersion: Migrating from schema_version ' +
` ${ schemaVersion } to user_version ${ newUserVersion } `
) ;
setUserVersion ( db , newUserVersion ) ;
2020-03-03 05:17:30 +01:00
}
2021-07-08 05:00:20 +02:00
function getUserVersion ( db ) {
2021-07-13 05:42:34 +02:00
try {
return db . pragma ( 'user_version' , { simple : true } ) ;
} catch ( e ) {
console . warn ( 'getUserVersion error' , e ) ;
return 0 ;
}
2021-07-08 05:00:20 +02:00
}
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
function setUserVersion ( db , version ) {
if ( ! isNumber ( version ) ) {
throw new Error ( ` setUserVersion: version ${ version } is not a number ` ) ;
}
2021-07-13 05:42:34 +02:00
2021-07-08 05:00:20 +02:00
db . pragma ( ` user_version = ${ version } ` ) ;
}
function openAndMigrateDatabase ( filePath , key ) {
let db ;
// First, we try to open the database without any cipher changes
try {
2021-07-13 05:42:34 +02:00
db = new SQL ( filePath , { verbose : null } ) ;
2021-07-08 05:00:20 +02:00
keyDatabase ( db , key ) ;
switchToWAL ( db ) ;
migrateSchemaVersion ( db ) ;
2021-08-11 06:21:40 +02:00
db . pragma ( 'secure_delete = ON' ) ;
2021-07-08 05:00:20 +02:00
return db ;
} catch ( error ) {
if ( db ) {
db . close ( ) ;
}
2021-07-13 05:42:34 +02:00
console . log ( 'migrateDatabase: Migration without cipher change failed' , error ) ;
2021-07-08 05:00:20 +02:00
}
// 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).
2020-03-03 05:17:30 +01:00
2021-07-13 05:42:34 +02:00
let db1 ;
try {
db1 = new SQL ( filePath , { verbose : null } ) ;
keyDatabase ( db1 , key ) ;
2021-07-08 05:00:20 +02:00
2021-07-13 05:42:34 +02:00
// https://www.zetetic.net/blog/2018/11/30/sqlcipher-400-release/#compatability-sqlcipher-4-0-0
db1 . pragma ( 'cipher_compatibility = 3' ) ;
migrateSchemaVersion ( db1 ) ;
db1 . close ( ) ;
} catch ( error ) {
if ( db1 ) {
db1 . close ( ) ;
}
console . log ( 'migrateDatabase: migrateSchemaVersion failed' , error ) ;
return null ;
}
2021-07-08 05:00:20 +02:00
// After migrating user_version -> schema_version, we reopen database, because we can't
// migrate to the latest ciphers after we've modified the defaults.
2021-07-13 05:42:34 +02:00
let db2 ;
try {
db2 = new SQL ( filePath , { verbose : null } ) ;
keyDatabase ( db2 , key ) ;
2021-07-08 05:00:20 +02:00
2021-07-13 05:42:34 +02:00
db2 . pragma ( 'cipher_migrate' ) ;
switchToWAL ( db2 ) ;
2021-07-08 05:00:20 +02:00
2021-07-13 05:42:34 +02:00
// Because foreign key support is not enabled by default!
2021-07-27 02:29:18 +02:00
db2 . pragma ( 'foreign_keys = OFF' ) ;
2021-07-13 05:42:34 +02:00
return db2 ;
} catch ( error ) {
if ( db2 ) {
db2 . close ( ) ;
}
console . log ( 'migrateDatabase: switchToWAL failed' ) ;
return null ;
}
2018-12-06 01:33:11 +01:00
}
2021-07-08 05:00:20 +02:00
function openAndSetUpSQLCipher ( filePath , { key } ) {
2021-07-13 05:42:34 +02:00
return openAndMigrateDatabase ( filePath , key ) ;
2021-07-08 05:00:20 +02:00
}
function setSQLPassword ( password ) {
if ( ! globalInstance ) {
2018-12-06 01:33:11 +01:00
throw new Error ( 'setSQLPassword: db is not initialized' ) ;
}
// If the password isn't hex then we need to derive a key from it
const deriveKey = HEX _KEY . test ( password ) ;
2018-12-10 04:44:56 +01:00
const value = deriveKey ? ` ' ${ password } ' ` : ` "x' ${ password } '" ` ;
2021-07-08 05:00:20 +02:00
globalInstance . pragma ( ` rekey = ${ value } ` ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function vacuumDatabase ( db ) {
if ( ! db ) {
2021-05-27 02:13:59 +02:00
throw new Error ( 'vacuum: db is not initialized' ) ;
}
2021-07-08 05:00:20 +02:00
console . time ( 'vaccumming db' ) ;
2021-05-27 02:13:59 +02:00
console . warn ( 'Vacuuming DB. This might take a while.' ) ;
2021-07-08 05:00:20 +02:00
db . exec ( 'VACUUM;' ) ;
2021-06-03 03:37:45 +02:00
console . warn ( 'Vacuuming DB Finished' ) ;
2021-07-08 05:00:20 +02:00
console . timeEnd ( 'vaccumming db' ) ;
2021-05-27 02:13:59 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion1 ( currentVersion , db ) {
2018-07-27 03:13:56 +02:00
if ( currentVersion >= 1 ) {
return ;
}
console . log ( 'updateToSchemaVersion1: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec (
` CREATE TABLE ${ MESSAGES _TABLE } (
2018-08-07 21:33:56 +02:00
id STRING PRIMARY KEY ASC ,
json TEXT ,
unread INTEGER ,
expires _at INTEGER ,
2018-11-16 00:03:43 +01:00
sent BOOLEAN ,
2018-08-07 21:33:56 +02:00
sent _at INTEGER ,
schemaVersion INTEGER ,
conversationId STRING ,
received _at INTEGER ,
source STRING ,
sourceDevice STRING ,
hasAttachments INTEGER ,
hasFileAttachments INTEGER ,
hasVisualMediaAttachments INTEGER
2021-07-08 05:00:20 +02:00
) ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _unread ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
unread
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _expires _at ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
expires _at
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _receipt ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
sent _at
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _schemaVersion ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
schemaVersion
2021-07-08 05:00:20 +02:00
) ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _conversation ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
conversationId ,
received _at
2021-07-08 05:00:20 +02:00
) ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _duplicate _check ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
source ,
sourceDevice ,
sent _at
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _hasAttachments ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
conversationId ,
hasAttachments ,
received _at
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _hasFileAttachments ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
conversationId ,
hasFileAttachments ,
received _at
2021-07-08 05:00:20 +02:00
) ;
CREATE INDEX messages _hasVisualMediaAttachments ON $ { MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
conversationId ,
hasVisualMediaAttachments ,
received _at
2021-07-08 05:00:20 +02:00
) ;
CREATE TABLE unprocessed (
id STRING ,
timestamp INTEGER ,
json TEXT
) ;
CREATE INDEX unprocessed _id ON unprocessed (
id
) ;
CREATE INDEX unprocessed _timestamp ON unprocessed (
timestamp
) ;
`
) ;
db . pragma ( 'user_version = 1' ) ;
} ) ( ) ;
2018-07-27 03:13:56 +02:00
console . log ( 'updateToSchemaVersion1: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion2 ( currentVersion , db ) {
2018-08-07 21:33:56 +02:00
if ( currentVersion >= 2 ) {
return ;
}
console . log ( 'updateToSchemaVersion2: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( ` ALTER TABLE ${ MESSAGES _TABLE }
ADD COLUMN expireTimer INTEGER ;
2018-08-07 21:33:56 +02:00
2021-07-08 05:00:20 +02:00
ALTER TABLE $ { MESSAGES _TABLE }
ADD COLUMN expirationStartTimestamp INTEGER ;
2018-08-07 21:33:56 +02:00
2021-07-08 05:00:20 +02:00
ALTER TABLE $ { MESSAGES _TABLE }
ADD COLUMN type STRING ;
2018-08-07 21:33:56 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _expiring ON $ { MESSAGES _TABLE } (
2018-08-07 21:33:56 +02:00
expireTimer ,
expirationStartTimestamp ,
expires _at
2021-07-08 05:00:20 +02:00
) ;
2018-08-07 21:33:56 +02:00
2021-07-08 05:00:20 +02:00
UPDATE $ { MESSAGES _TABLE } SET
2018-08-07 21:33:56 +02:00
expirationStartTimestamp = json _extract ( json , '$.expirationStartTimestamp' ) ,
expireTimer = json _extract ( json , '$.expireTimer' ) ,
2021-07-08 05:00:20 +02:00
type = json _extract ( json , '$.type' ) ;
2018-08-07 21:33:56 +02:00
2021-07-08 05:00:20 +02:00
` );
db . pragma ( 'user_version = 2' ) ;
} ) ( ) ;
2018-08-07 21:33:56 +02:00
console . log ( 'updateToSchemaVersion2: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion3 ( currentVersion , db ) {
2018-08-09 03:32:10 +02:00
if ( currentVersion >= 3 ) {
return ;
}
console . log ( 'updateToSchemaVersion3: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
DROP INDEX messages _expiring ;
DROP INDEX messages _unread ;
2018-08-09 03:32:10 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _without _timer ON $ { MESSAGES _TABLE } (
2018-08-09 03:32:10 +02:00
expireTimer ,
expires _at ,
type
2021-07-08 05:00:20 +02:00
) WHERE expires _at IS NULL AND expireTimer IS NOT NULL ;
2018-08-09 03:32:10 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX messages _unread ON $ { MESSAGES _TABLE } (
2018-08-09 03:32:10 +02:00
conversationId ,
unread
2021-07-08 05:00:20 +02:00
) WHERE unread IS NOT NULL ;
ANALYZE ;
2018-08-09 03:32:10 +02:00
2021-07-08 05:00:20 +02:00
` );
db . pragma ( 'user_version = 3' ) ;
} ) ( ) ;
2018-08-09 03:32:10 +02:00
console . log ( 'updateToSchemaVersion3: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion4 ( currentVersion , db ) {
2018-09-21 03:47:19 +02:00
if ( currentVersion >= 4 ) {
return ;
}
console . log ( 'updateToSchemaVersion4: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
2018-09-21 03:47:19 +02:00
2021-07-08 05:00:20 +02:00
CREATE TABLE $ { CONVERSATIONS _TABLE } (
2018-09-21 03:47:19 +02:00
id STRING PRIMARY KEY ASC ,
json TEXT ,
active _at INTEGER ,
type STRING ,
members TEXT ,
name TEXT ,
profileName TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-09-21 03:47:19 +02:00
2021-07-08 05:00:20 +02:00
CREATE INDEX conversations _active ON $ { CONVERSATIONS _TABLE } (
2018-09-21 03:47:19 +02:00
active _at
2021-07-08 05:00:20 +02:00
) WHERE active _at IS NOT NULL ;
CREATE INDEX conversations _type ON $ { CONVERSATIONS _TABLE } (
2018-09-21 03:47:19 +02:00
type
2021-07-08 05:00:20 +02:00
) WHERE type IS NOT NULL ;
` );
2018-09-21 03:47:19 +02:00
2021-07-08 05:00:20 +02:00
db . pragma ( 'user_version = 4' ) ;
} ) ( ) ;
2018-09-21 03:47:19 +02:00
console . log ( 'updateToSchemaVersion4: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion6 ( currentVersion , db ) {
2018-11-07 05:16:49 +01:00
if ( currentVersion >= 6 ) {
return ;
}
console . log ( 'updateToSchemaVersion6: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE lastHashes (
2019-04-15 07:43:35 +02:00
snode TEXT PRIMARY KEY ,
hash TEXT ,
2019-04-15 05:19:06 +02:00
expiresAt INTEGER
2021-07-08 05:00:20 +02:00
) ;
2019-04-15 05:19:06 +02:00
2021-07-08 05:00:20 +02:00
CREATE TABLE seenMessages (
2019-04-15 07:43:35 +02:00
hash TEXT PRIMARY KEY ,
2018-11-19 07:57:20 +01:00
expiresAt INTEGER
2021-07-08 05:00:20 +02:00
) ;
2018-11-19 07:57:20 +01:00
2021-07-08 05:00:20 +02:00
CREATE TABLE sessions (
2018-11-07 05:16:49 +01:00
id STRING PRIMARY KEY ASC ,
number STRING ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-11-07 05:16:49 +01:00
2021-07-08 05:00:20 +02:00
CREATE INDEX sessions _number ON sessions (
number
) WHERE number IS NOT NULL ;
2018-11-07 05:16:49 +01:00
2021-07-08 05:00:20 +02:00
CREATE TABLE groups (
2018-11-07 05:16:49 +01:00
id STRING PRIMARY KEY ASC ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
CREATE TABLE $ { IDENTITY _KEYS _TABLE } (
2018-11-07 05:16:49 +01:00
id STRING PRIMARY KEY ASC ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
CREATE TABLE $ { ITEMS _TABLE } (
2018-11-07 05:16:49 +01:00
id STRING PRIMARY KEY ASC ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-11-07 05:16:49 +01:00
2021-07-08 05:00:20 +02:00
CREATE TABLE preKeys (
2018-11-07 05:16:49 +01:00
id INTEGER PRIMARY KEY ASC ,
2018-11-08 02:13:08 +01:00
recipient STRING ,
2018-11-07 05:16:49 +01:00
json TEXT
2021-07-08 05:00:20 +02:00
) ;
CREATE TABLE signedPreKeys (
2018-11-07 05:16:49 +01:00
id INTEGER PRIMARY KEY ASC ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-11-07 05:16:49 +01:00
2021-07-08 05:00:20 +02:00
CREATE TABLE contactPreKeys (
2018-11-07 23:49:10 +01:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
identityKeyString VARCHAR ( 255 ) ,
2018-11-07 22:52:03 +01:00
keyId INTEGER ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-11-07 22:52:03 +01:00
2021-07-08 05:00:20 +02:00
CREATE UNIQUE INDEX contact _prekey _identity _key _string _keyid ON contactPreKeys (
identityKeyString ,
keyId
) ;
2018-11-19 07:57:20 +01:00
2021-07-08 05:00:20 +02:00
CREATE TABLE contactSignedPreKeys (
2018-11-07 23:49:10 +01:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
identityKeyString VARCHAR ( 255 ) ,
2018-11-07 22:52:03 +01:00
keyId INTEGER ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2018-11-07 22:52:03 +01:00
2021-07-08 05:00:20 +02:00
CREATE UNIQUE INDEX contact _signed _prekey _identity _key _string _keyid ON contactSignedPreKeys (
identityKeyString ,
keyId
) ;
` );
db . pragma ( 'user_version = 6' ) ;
} ) ( ) ;
2018-11-19 07:57:20 +01:00
2018-11-08 02:13:08 +01:00
console . log ( 'updateToSchemaVersion6: success!' ) ;
2018-11-07 05:16:49 +01:00
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion7 ( currentVersion , db ) {
2018-11-20 02:38:55 +01:00
if ( currentVersion >= 7 ) {
return ;
}
console . log ( 'updateToSchemaVersion7: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
-- SQLite has been coercing our STRINGs into numbers , so we force it with TEXT
-- We create a new table then copy the data into it , since we can ' t modify columns
DROP INDEX sessions _number ;
ALTER TABLE sessions RENAME TO sessions _old ;
CREATE TABLE sessions (
id TEXT PRIMARY KEY ,
number TEXT ,
json TEXT
) ;
CREATE INDEX sessions _number ON sessions (
number
) WHERE number IS NOT NULL ;
INSERT INTO sessions ( id , number , json )
2018-11-20 02:38:55 +01:00
SELECT "+" || id , number , json FROM sessions _old ;
2021-07-08 05:00:20 +02:00
DROP TABLE sessions _old ;
` );
2018-11-20 02:38:55 +01:00
2021-07-08 05:00:20 +02:00
db . pragma ( 'user_version = 7' ) ;
} ) ( ) ;
2018-11-20 02:38:55 +01:00
console . log ( 'updateToSchemaVersion7: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion8 ( currentVersion , db ) {
2019-01-14 22:47:19 +01:00
if ( currentVersion >= 8 ) {
return ;
}
console . log ( 'updateToSchemaVersion8: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
-- First , we pull a new body field out of the message table ' s json blob
ALTER TABLE $ { MESSAGES _TABLE }
ADD COLUMN body TEXT ;
UPDATE $ { MESSAGES _TABLE } SET body = json _extract ( json , '$.body' ) ;
2019-01-14 22:47:19 +01:00
2021-07-08 05:00:20 +02:00
-- Then we create our full - text search table and populate it
2021-06-11 04:09:57 +02:00
CREATE VIRTUAL TABLE $ { MESSAGES _FTS _TABLE }
2021-07-08 05:00:20 +02:00
USING fts5 ( id UNINDEXED , body ) ;
2021-06-11 04:09:57 +02:00
INSERT INTO $ { MESSAGES _FTS _TABLE } ( id , body )
2021-07-08 05:00:20 +02:00
SELECT id , body FROM $ { MESSAGES _TABLE } ;
2019-01-14 22:47:19 +01:00
2021-07-08 05:00:20 +02:00
-- Then we set up triggers to keep the full - text search table up to date
2021-06-11 04:04:07 +02:00
CREATE TRIGGER messages _on _insert AFTER INSERT ON $ { MESSAGES _TABLE } BEGIN
2021-06-11 04:09:57 +02:00
INSERT INTO $ { MESSAGES _FTS _TABLE } (
2019-01-14 22:47:19 +01:00
id ,
body
) VALUES (
new . id ,
new . body
) ;
END ;
2021-06-11 04:04:07 +02:00
CREATE TRIGGER messages _on _delete AFTER DELETE ON $ { MESSAGES _TABLE } BEGIN
2021-06-11 04:09:57 +02:00
DELETE FROM $ { MESSAGES _FTS _TABLE } WHERE id = old . id ;
2019-01-14 22:47:19 +01:00
END ;
2021-06-11 04:04:07 +02:00
CREATE TRIGGER messages _on _update AFTER UPDATE ON $ { MESSAGES _TABLE } BEGIN
2021-06-11 04:09:57 +02:00
DELETE FROM $ { MESSAGES _FTS _TABLE } WHERE id = old . id ;
INSERT INTO $ { MESSAGES _FTS _TABLE } (
2019-01-14 22:47:19 +01:00
id ,
body
) VALUES (
new . id ,
new . body
) ;
END ;
2021-07-08 05:00:20 +02:00
` );
// For formatting search results:
// https://sqlite.org/fts5.html#the_highlight_function
// https://sqlite.org/fts5.html#the_snippet_function
db . pragma ( 'user_version = 8' ) ;
} ) ( ) ;
2019-01-14 22:47:19 +01:00
console . log ( 'updateToSchemaVersion8: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion9 ( currentVersion , db ) {
2019-01-30 21:15:07 +01:00
if ( currentVersion >= 9 ) {
return ;
}
console . log ( 'updateToSchemaVersion9: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE $ { ATTACHMENT _DOWNLOADS _TABLE } (
id STRING primary key ,
timestamp INTEGER ,
pending INTEGER ,
json TEXT
) ;
2019-01-30 21:15:07 +01:00
2021-07-08 05:00:20 +02:00
CREATE INDEX attachment _downloads _timestamp
ON $ { ATTACHMENT _DOWNLOADS _TABLE } (
timestamp
) WHERE pending = 0 ;
CREATE INDEX attachment _downloads _pending
ON $ { ATTACHMENT _DOWNLOADS _TABLE } (
pending
) WHERE pending != 0 ;
` );
db . pragma ( 'user_version = 9' ) ;
} ) ( ) ;
2019-01-30 21:15:07 +01:00
console . log ( 'updateToSchemaVersion9: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion10 ( currentVersion , db ) {
2019-02-05 02:23:50 +01:00
if ( currentVersion >= 10 ) {
return ;
}
console . log ( 'updateToSchemaVersion10: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
DROP INDEX unprocessed _id ;
DROP INDEX unprocessed _timestamp ;
ALTER TABLE unprocessed RENAME TO unprocessed _old ;
CREATE TABLE unprocessed (
id STRING ,
timestamp INTEGER ,
version INTEGER ,
attempts INTEGER ,
envelope TEXT ,
decrypted TEXT ,
source TEXT ,
sourceDevice TEXT ,
serverTimestamp INTEGER
) ;
CREATE INDEX unprocessed _id ON unprocessed (
id
) ;
CREATE INDEX unprocessed _timestamp ON unprocessed (
timestamp
) ;
INSERT INTO unprocessed (
id ,
timestamp ,
version ,
attempts ,
envelope ,
decrypted ,
source ,
sourceDevice ,
serverTimestamp
) SELECT
id ,
timestamp ,
json _extract ( json , '$.version' ) ,
json _extract ( json , '$.attempts' ) ,
json _extract ( json , '$.envelope' ) ,
json _extract ( json , '$.decrypted' ) ,
json _extract ( json , '$.source' ) ,
json _extract ( json , '$.sourceDevice' ) ,
json _extract ( json , '$.serverTimestamp' )
FROM unprocessed _old ;
DROP TABLE unprocessed _old ;
` );
db . pragma ( 'user_version = 10' ) ;
} ) ( ) ;
2019-02-05 02:23:50 +01:00
console . log ( 'updateToSchemaVersion10: success!' ) ;
}
2021-07-08 05:00:20 +02:00
function updateToSchemaVersion11 ( currentVersion , db ) {
2019-02-12 00:59:21 +01:00
if ( currentVersion >= 11 ) {
return ;
}
console . log ( 'updateToSchemaVersion11: starting...' ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
DROP TABLE groups ;
` );
2019-02-12 00:59:21 +01:00
2021-07-08 05:00:20 +02:00
db . pragma ( 'user_version = 11' ) ;
} ) ( ) ;
2019-02-12 00:59:21 +01:00
console . log ( 'updateToSchemaVersion11: success!' ) ;
}
2018-08-09 03:32:10 +02:00
const SCHEMA _VERSIONS = [
updateToSchemaVersion1 ,
updateToSchemaVersion2 ,
updateToSchemaVersion3 ,
2018-09-21 03:47:19 +02:00
updateToSchemaVersion4 ,
2018-11-20 02:38:55 +01:00
( ) => null , // version 5 was dropped
2018-11-07 05:16:49 +01:00
updateToSchemaVersion6 ,
2018-11-20 02:38:55 +01:00
updateToSchemaVersion7 ,
2019-01-14 22:47:19 +01:00
updateToSchemaVersion8 ,
2019-01-30 21:15:07 +01:00
updateToSchemaVersion9 ,
2019-02-05 02:23:50 +01:00
updateToSchemaVersion10 ,
2019-02-12 00:59:21 +01:00
updateToSchemaVersion11 ,
2018-08-09 03:32:10 +02:00
] ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
function updateSchema ( db ) {
const sqliteVersion = getSQLiteVersion ( db ) ;
const sqlcipherVersion = getSQLCipherVersion ( db ) ;
const userVersion = getUserVersion ( db ) ;
const maxUserVersion = SCHEMA _VERSIONS . length ;
const schemaVersion = getSchemaVersion ( db ) ;
console . log ( 'updateSchema:' ) ;
console . log ( ` Current user_version: ${ userVersion } ` ) ;
console . log ( ` Most recent db schema: ${ maxUserVersion } ` ) ;
console . log ( ` SQLite version: ${ sqliteVersion } ` ) ;
console . log ( ` SQLCipher version: ${ sqlcipherVersion } ` ) ;
console . log ( ` (deprecated) schema_version: ${ schemaVersion } ` ) ;
2018-07-27 03:13:56 +02:00
for ( let index = 0 , max = SCHEMA _VERSIONS . length ; index < max ; index += 1 ) {
const runSchemaUpdate = SCHEMA _VERSIONS [ index ] ;
2021-07-08 05:00:20 +02:00
runSchemaUpdate ( schemaVersion , db ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
updateLokiSchema ( db ) ;
2019-08-07 00:53:41 +02:00
}
2019-09-16 05:55:32 +02:00
const LOKI _SCHEMA _VERSIONS = [
updateToLokiSchemaVersion1 ,
updateToLokiSchemaVersion2 ,
2020-03-23 05:00:51 +01:00
updateToLokiSchemaVersion3 ,
2020-05-04 05:24:53 +02:00
updateToLokiSchemaVersion4 ,
2020-07-06 07:28:22 +02:00
updateToLokiSchemaVersion5 ,
2020-07-16 05:30:38 +02:00
updateToLokiSchemaVersion6 ,
2020-08-04 03:42:08 +02:00
updateToLokiSchemaVersion7 ,
2020-09-04 07:11:24 +02:00
updateToLokiSchemaVersion8 ,
2020-09-22 00:58:21 +02:00
updateToLokiSchemaVersion9 ,
2021-01-12 06:11:05 +01:00
updateToLokiSchemaVersion10 ,
updateToLokiSchemaVersion11 ,
2021-04-15 10:14:04 +02:00
updateToLokiSchemaVersion12 ,
2021-05-27 02:13:59 +02:00
updateToLokiSchemaVersion13 ,
2021-06-11 04:09:57 +02:00
updateToLokiSchemaVersion14 ,
2021-07-27 02:29:18 +02:00
updateToLokiSchemaVersion15 ,
2021-09-20 05:47:59 +02:00
updateToLokiSchemaVersion16 ,
2021-12-02 06:22:14 +01:00
updateToLokiSchemaVersion17 ,
2021-12-22 01:20:29 +01:00
updateToLokiSchemaVersion18 ,
2019-09-16 05:55:32 +02:00
] ;
2019-07-18 10:06:43 +02:00
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion1 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 1 ;
if ( currentVersion >= targetVersion ) {
2019-07-18 10:06:43 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
ALTER TABLE $ { MESSAGES _TABLE }
ADD COLUMN serverId INTEGER ;
2019-07-18 10:06:43 +02:00
2021-07-08 05:00:20 +02:00
CREATE TABLE servers (
2019-08-29 06:15:36 +02:00
serverUrl STRING PRIMARY KEY ASC ,
2019-08-28 09:00:48 +02:00
token TEXT
2021-07-08 05:00:20 +02:00
) ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2019-08-28 09:00:48 +02:00
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2019-07-18 10:06:43 +02:00
}
2019-08-07 00:53:41 +02:00
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion2 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 2 ;
if ( currentVersion >= targetVersion ) {
2019-08-07 00:53:41 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2019-08-07 00:53:41 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE pairingAuthorisations (
2019-08-07 00:53:41 +02:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
2019-08-23 06:57:07 +02:00
primaryDevicePubKey VARCHAR ( 255 ) ,
2019-08-07 00:53:41 +02:00
secondaryDevicePubKey VARCHAR ( 255 ) ,
2019-08-23 06:57:07 +02:00
isGranted BOOLEAN ,
2019-10-28 01:09:14 +01:00
json TEXT ,
UNIQUE ( primaryDevicePubKey , secondaryDevicePubKey )
2021-07-08 05:00:20 +02:00
) ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2019-08-07 00:53:41 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion3 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 3 ;
if ( currentVersion >= targetVersion ) {
2020-03-23 05:00:51 +01:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-03-23 05:00:51 +01:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE $ { GUARD _NODE _TABLE } (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
ed25519PubKey VARCHAR ( 64 )
) ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2020-03-23 05:00:51 +01:00
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-03-23 05:00:51 +01:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion4 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 4 ;
if ( currentVersion >= targetVersion ) {
2020-05-04 05:24:53 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-05-04 05:24:53 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
DROP TABLE lastHashes ;
CREATE TABLE lastHashes (
2020-05-04 05:24:53 +02:00
id TEXT ,
snode TEXT ,
hash TEXT ,
expiresAt INTEGER ,
PRIMARY KEY ( id , snode )
2021-07-08 05:00:20 +02:00
) ;
-- Add senderIdentity field to unprocessed needed for medium size groups
ALTER TABLE unprocessed ADD senderIdentity TEXT ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-05-04 05:24:53 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion5 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 5 ;
if ( currentVersion >= targetVersion ) {
2020-07-06 07:28:22 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-07-06 07:28:22 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE $ { NODES _FOR _PUBKEY _TABLE } (
2020-07-06 07:28:22 +02:00
pubkey TEXT PRIMARY KEY ,
json TEXT
2021-07-08 05:00:20 +02:00
) ;
2020-07-06 07:28:22 +02:00
2021-07-08 05:00:20 +02:00
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-07-06 07:28:22 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion6 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 6 ;
if ( currentVersion >= targetVersion ) {
2020-07-16 05:30:38 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-07-16 05:30:38 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
-- Remove RSS Feed conversations
DELETE FROM $ { CONVERSATIONS _TABLE } WHERE
type = 'group' AND
id LIKE 'rss://%' ;
2020-07-16 05:30:38 +02:00
2021-07-08 05:00:20 +02:00
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-07-16 05:30:38 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion7 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 7 ;
if ( currentVersion >= targetVersion ) {
2020-08-04 03:42:08 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-08-04 03:42:08 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
-- Remove multi device data
2020-08-04 03:42:08 +02:00
2021-07-08 05:00:20 +02:00
DELETE FROM pairingAuthorisations ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-08-04 03:42:08 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion8 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 8 ;
if ( currentVersion >= targetVersion ) {
2020-09-04 07:11:24 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2020-09-04 07:11:24 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
2020-09-04 07:11:24 +02:00
2021-07-08 05:00:20 +02:00
ALTER TABLE $ { MESSAGES _TABLE }
ADD COLUMN serverTimestamp INTEGER ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-09-04 07:11:24 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion9 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 9 ;
if ( currentVersion >= targetVersion ) {
2020-09-22 00:58:21 +02:00
return ;
}
2021-07-08 05:00:20 +02:00
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
const rows = db
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
type = 'group' AND
id LIKE '__textsecure_group__!%' ;
`
)
. all ( ) ;
2020-09-22 00:58:21 +02:00
2021-07-08 05:00:20 +02:00
const objs = map ( rows , row => jsonToObject ( row . json ) ) ;
const conversationIdRows = db
. prepare ( ` SELECT id FROM ${ CONVERSATIONS _TABLE } ORDER BY id ASC; ` )
. all ( ) ;
const allOldConversationIds = map ( conversationIdRows , row => row . id ) ;
objs . forEach ( o => {
const oldId = o . id ;
const newId = oldId . replace ( '__textsecure_group__!' , '' ) ;
console . log ( ` migrating conversation, ${ oldId } to ${ newId } ` ) ;
if ( allOldConversationIds . includes ( newId ) ) {
console . log (
'Found a duplicate conversation after prefix removing. We need to take care of it'
) ;
// We have another conversation with the same future name.
// We decided to keep only the conversation with the higher number of messages
const countMessagesOld = getMessagesCountByConversation ( db , oldId , {
limit : Number . MAX _VALUE ,
} ) ;
const countMessagesNew = getMessagesCountByConversation ( db , newId , {
limit : Number . MAX _VALUE ,
} ) ;
console . log ( ` countMessagesOld: ${ countMessagesOld } , countMessagesNew: ${ countMessagesNew } ` ) ;
const deleteId = countMessagesOld > countMessagesNew ? newId : oldId ;
db . prepare ( ` DELETE FROM ${ CONVERSATIONS _TABLE } WHERE id = $ deleteId; ` ) . run ( { deleteId } ) ;
}
const morphedObject = {
... o ,
id : newId ,
} ;
db . prepare (
` UPDATE ${ CONVERSATIONS _TABLE } SET
id = $newId ,
json = $json
WHERE id = $oldId ; `
) . run ( {
newId ,
json : objectToJSON ( morphedObject ) ,
oldId ,
} ) ;
} ) ;
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2020-09-22 00:58:21 +02:00
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2020-09-22 00:58:21 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion10 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 10 ;
if ( currentVersion >= targetVersion ) {
2021-01-12 06:11:05 +01:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-01-12 06:11:05 +01:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE $ { CLOSED _GROUP _V2 _KEY _PAIRS _TABLE } (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
groupPublicKey TEXT ,
timestamp NUMBER ,
json TEXT
) ;
2021-01-12 06:11:05 +01:00
2021-07-08 05:00:20 +02:00
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2021-01-12 06:11:05 +01:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion11 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 11 ;
if ( currentVersion >= targetVersion ) {
2021-01-12 06:11:05 +01:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-01-12 06:11:05 +01:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
updateExistingClosedGroupV1ToClosedGroupV2 ( db ) ;
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2021-01-12 06:11:05 +01:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion12 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 12 ;
if ( currentVersion >= targetVersion ) {
2021-04-15 10:14:04 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-04-15 10:14:04 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE $ { OPEN _GROUP _ROOMS _V2 _TABLE } (
2021-04-15 10:14:04 +02:00
serverUrl TEXT NOT NULL ,
roomId TEXT NOT NULL ,
conversationId TEXT ,
json TEXT ,
PRIMARY KEY ( serverUrl , roomId )
2021-07-08 05:00:20 +02:00
) ;
2021-04-15 10:14:04 +02:00
2021-07-08 05:00:20 +02:00
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2021-04-15 10:14:04 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion13 ( currentVersion , db ) {
2021-06-11 04:04:07 +02:00
const targetVersion = 13 ;
if ( currentVersion >= targetVersion ) {
2021-05-27 02:13:59 +02:00
return ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-05-27 02:13:59 +02:00
// Clear any already deleted db entries.
// secure_delete = ON will make sure next deleted entries are overwritten with 0 right away
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . pragma ( 'secure_delete = ON' ) ;
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:04:07 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
2021-05-27 02:13:59 +02:00
}
2021-07-08 05:00:20 +02:00
function updateToLokiSchemaVersion14 ( currentVersion , db ) {
2021-06-11 04:09:57 +02:00
const targetVersion = 14 ;
if ( currentVersion >= targetVersion ) {
return ;
}
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
2021-06-11 07:14:52 +02:00
2021-07-08 05:00:20 +02:00
db . transaction ( ( ) => {
db . exec ( `
DROP TABLE IF EXISTS servers ;
DROP TABLE IF EXISTS sessions ;
DROP TABLE IF EXISTS preKeys ;
DROP TABLE IF EXISTS contactPreKeys ;
DROP TABLE IF EXISTS contactSignedPreKeys ;
DROP TABLE IF EXISTS signedPreKeys ;
DROP TABLE IF EXISTS senderKeys ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
2021-06-11 04:09:57 +02:00
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
}
2021-07-27 02:29:18 +02:00
function updateToLokiSchemaVersion15 ( currentVersion , db ) {
const targetVersion = 15 ;
if ( currentVersion >= targetVersion ) {
return ;
}
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
db . transaction ( ( ) => {
db . exec ( `
DROP TABLE pairingAuthorisations ;
DROP TRIGGER messages _on _delete ;
DROP TRIGGER messages _on _update ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
}
2021-09-20 05:47:59 +02:00
function updateToLokiSchemaVersion16 ( currentVersion , db ) {
const targetVersion = 16 ;
if ( currentVersion >= targetVersion ) {
return ;
}
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
db . transaction ( ( ) => {
db . exec ( `
ALTER TABLE $ { MESSAGES _TABLE } ADD COLUMN serverHash TEXT ;
ALTER TABLE $ { MESSAGES _TABLE } ADD COLUMN isDeleted BOOLEAN ;
CREATE INDEX messages _serverHash ON $ { MESSAGES _TABLE } (
serverHash
) WHERE serverHash IS NOT NULL ;
CREATE INDEX messages _isDeleted ON $ { MESSAGES _TABLE } (
isDeleted
) WHERE isDeleted IS NOT NULL ;
ALTER TABLE unprocessed ADD serverHash TEXT ;
CREATE INDEX messages _messageHash ON unprocessed (
serverHash
) WHERE serverHash IS NOT NULL ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
}
2021-12-02 06:22:14 +01:00
function updateToLokiSchemaVersion17 ( currentVersion , db ) {
const targetVersion = 17 ;
if ( currentVersion >= targetVersion ) {
return ;
}
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
db . transaction ( ( ) => {
db . exec ( `
UPDATE $ { CONVERSATIONS _TABLE } SET
json = json _set ( json , '$.isApproved' , 1 )
` );
2021-12-08 04:15:54 +01:00
// remove the moderators field. As it was only used for opengroups a long time ago and whatever is there is probably unused
db . exec ( `
UPDATE $ { CONVERSATIONS _TABLE } SET
json = json _remove ( json , '$.moderators' , '$.dataMessage' , '$.accessKey' , '$.profileSharing' , '$.sessionRestoreSeen' )
` );
2021-12-02 06:22:14 +01:00
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
}
2021-12-22 01:20:29 +01:00
function updateToLokiSchemaVersion18 ( currentVersion , db ) {
const targetVersion = 18 ;
if ( currentVersion >= targetVersion ) {
return ;
}
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : starting... ` ) ;
// Dropping all pre-existing schema relating to message searching.
// Recreating the full text search and related triggers
db . transaction ( ( ) => {
db . exec ( `
DROP TRIGGER IF EXISTS messages _on _insert ;
DROP TRIGGER IF EXISTS messages _on _delete ;
DROP TRIGGER IF EXISTS messages _on _update ;
DROP TABLE IF EXISTS $ { MESSAGES _FTS _TABLE } ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
db . transaction ( ( ) => {
db . exec ( `
-- Then we create our full - text search table and populate it
CREATE VIRTUAL TABLE $ { MESSAGES _FTS _TABLE }
USING fts5 ( id UNINDEXED , body ) ;
INSERT INTO $ { MESSAGES _FTS _TABLE } ( id , body )
SELECT id , body FROM $ { MESSAGES _TABLE } ;
-- Then we set up triggers to keep the full - text search table up to date
CREATE TRIGGER messages _on _insert AFTER INSERT ON $ { MESSAGES _TABLE } BEGIN
INSERT INTO $ { MESSAGES _FTS _TABLE } (
id ,
body
) VALUES (
new . id ,
new . body
) ;
END ;
CREATE TRIGGER messages _on _delete AFTER DELETE ON $ { MESSAGES _TABLE } BEGIN
DELETE FROM $ { MESSAGES _FTS _TABLE } WHERE id = old . id ;
END ;
CREATE TRIGGER messages _on _update AFTER UPDATE ON $ { MESSAGES _TABLE } BEGIN
DELETE FROM $ { MESSAGES _FTS _TABLE } WHERE id = old . id ;
INSERT INTO $ { MESSAGES _FTS _TABLE } (
id ,
body
) VALUES (
new . id ,
new . body
) ;
END ;
` );
writeLokiSchemaVersion ( targetVersion , db ) ;
} ) ( ) ;
console . log ( ` updateToLokiSchemaVersion ${ targetVersion } : success! ` ) ;
}
2021-07-08 05:00:20 +02:00
function writeLokiSchemaVersion ( newVersion , db ) {
db . prepare (
` INSERT INTO loki_schema(
version
) values (
$newVersion
) `
) . run ( { newVersion } ) ;
}
function updateLokiSchema ( db ) {
const result = db
. prepare ( ` SELECT name FROM sqlite_master WHERE type = 'table' AND name='loki_schema'; ` )
. get ( ) ;
2019-08-07 00:53:41 +02:00
if ( ! result ) {
2021-07-08 05:00:20 +02:00
createLokiSchemaTable ( db ) ;
2019-08-07 00:53:41 +02:00
}
2021-07-08 05:00:20 +02:00
const lokiSchemaVersion = getLokiSchemaVersion ( db ) ;
2019-08-07 00:53:41 +02:00
console . log (
'updateLokiSchema:' ,
` Current loki schema version: ${ lokiSchemaVersion } ; ` ,
` Most recent schema version: ${ LOKI _SCHEMA _VERSIONS . length } ; `
) ;
2021-04-22 10:03:58 +02:00
for ( let index = 0 , max = LOKI _SCHEMA _VERSIONS . length ; index < max ; index += 1 ) {
2019-08-07 00:53:41 +02:00
const runSchemaUpdate = LOKI _SCHEMA _VERSIONS [ index ] ;
2021-07-08 05:00:20 +02:00
runSchemaUpdate ( lokiSchemaVersion , db ) ;
2019-08-07 00:53:41 +02:00
}
}
2021-07-08 05:00:20 +02:00
function getLokiSchemaVersion ( db ) {
const result = db
. prepare (
`
SELECT MAX ( version ) as version FROM loki _schema ;
`
)
. get ( ) ;
2019-07-19 03:19:16 +02:00
if ( ! result || ! result . version ) {
2019-08-07 00:53:41 +02:00
return 0 ;
}
return result . version ;
}
2021-07-08 05:00:20 +02:00
function createLokiSchemaTable ( db ) {
db . transaction ( ( ) => {
db . exec ( `
CREATE TABLE loki _schema (
2019-08-07 00:53:41 +02:00
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT ,
version INTEGER
2021-07-08 05:00:20 +02:00
) ;
INSERT INTO loki _schema (
2019-08-07 00:53:41 +02:00
version
) values (
0
2021-07-08 05:00:20 +02:00
) ;
` );
} ) ( ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
let globalInstance ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
let databaseFilePath ;
2018-07-27 03:13:56 +02:00
2019-01-29 02:12:39 +01:00
function _initializePaths ( configDir ) {
const dbDir = path . join ( configDir , 'sql' ) ;
2021-12-08 04:15:54 +01:00
fs . mkdirSync ( dbDir , { recursive : true } ) ;
2021-07-08 05:00:20 +02:00
databaseFilePath = path . join ( dbDir , 'db.sqlite' ) ;
2019-01-29 02:12:39 +01:00
}
2021-07-08 05:00:20 +02:00
function initialize ( { configDir , key , messages , passwordAttempt } ) {
if ( globalInstance ) {
2018-07-27 03:13:56 +02:00
throw new Error ( 'Cannot initialize more than once!' ) ;
}
if ( ! isString ( configDir ) ) {
throw new Error ( 'initialize: configDir is required!' ) ;
}
if ( ! isString ( key ) ) {
2019-02-20 02:32:44 +01:00
throw new Error ( 'initialize: key is required!' ) ;
}
if ( ! isObject ( messages ) ) {
throw new Error ( 'initialize: message is required!' ) ;
2018-07-27 03:13:56 +02:00
}
2019-01-29 02:12:39 +01:00
_initializePaths ( configDir ) ;
2018-11-07 05:16:49 +01:00
2021-07-08 05:00:20 +02:00
let db ;
2019-02-20 02:32:44 +01:00
try {
2021-07-08 05:00:20 +02:00
db = openAndSetUpSQLCipher ( databaseFilePath , { key } ) ;
updateSchema ( db ) ;
2019-02-20 02:32:44 +01:00
2019-02-27 19:17:22 +01:00
// test database
2020-03-03 05:17:30 +01:00
2021-07-08 05:00:20 +02:00
const cipherIntegrityResult = getSQLCipherIntegrityCheck ( db ) ;
if ( cipherIntegrityResult ) {
console . log ( 'Database cipher integrity check failed:' , cipherIntegrityResult ) ;
throw new Error ( ` Cipher integrity check failed: ${ cipherIntegrityResult } ` ) ;
}
const integrityResult = getSQLIntegrityCheck ( db ) ;
if ( integrityResult ) {
console . log ( 'Database integrity check failed:' , integrityResult ) ;
throw new Error ( ` Integrity check failed: ${ integrityResult } ` ) ;
2020-03-03 05:17:30 +01:00
}
2021-07-08 05:00:20 +02:00
// At this point we can allow general access to the database
globalInstance = db ;
2021-05-27 02:13:59 +02:00
// Clear any already deleted db entries on each app start.
2021-07-08 05:00:20 +02:00
vacuumDatabase ( db ) ;
2021-07-27 02:29:18 +02:00
const msgCount = getMessageCount ( ) ;
2021-12-08 04:15:54 +01:00
const convoCount = getConversationCount ( ) ;
console . info ( 'total message count: ' , msgCount ) ;
console . info ( 'total conversation count: ' , convoCount ) ;
2019-02-20 02:32:44 +01:00
} catch ( error ) {
2019-07-05 05:21:32 +02:00
if ( passwordAttempt ) {
throw error ;
}
2019-02-20 02:32:44 +01:00
console . log ( 'Database startup error:' , error . stack ) ;
const buttonIndex = dialog . showMessageBox ( {
2021-06-16 03:20:31 +02:00
buttons : [ messages . copyErrorAndQuit , messages . clearAllData ] ,
2019-02-20 02:32:44 +01:00
defaultId : 0 ,
detail : redactAll ( error . stack ) ,
2021-06-16 03:20:31 +02:00
message : messages . databaseError ,
2019-02-20 02:32:44 +01:00
noLink : true ,
type : 'error' ,
} ) ;
if ( buttonIndex === 0 ) {
2021-04-22 10:03:58 +02:00
clipboard . writeText ( ` Database startup error: \n \n ${ redactAll ( error . stack ) } ` ) ;
2019-02-20 02:32:44 +01:00
} else {
2021-07-08 05:00:20 +02:00
close ( ) ;
removeDB ( ) ;
2019-02-20 02:32:44 +01:00
removeUserConfig ( ) ;
app . relaunch ( ) ;
}
app . exit ( 1 ) ;
return false ;
}
return true ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function close ( ) {
if ( ! globalInstance ) {
2019-03-01 05:15:30 +01:00
return ;
}
2021-07-08 05:00:20 +02:00
const dbRef = globalInstance ;
globalInstance = null ;
// SQLLite documentation suggests that we run `PRAGMA optimize` right before
// closing the database connection.
dbRef . pragma ( 'optimize' ) ;
dbRef . close ( ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function removeDB ( configDir = null ) {
if ( globalInstance ) {
throw new Error ( 'removeDB: Cannot erase database when it is open!' ) ;
}
if ( globalInstance ) {
2018-07-27 03:13:56 +02:00
throw new Error ( 'removeDB: Cannot erase database when it is open!' ) ;
}
2021-07-08 05:00:20 +02:00
if ( ! databaseFilePath && configDir ) {
2019-01-29 02:12:39 +01:00
_initializePaths ( configDir ) ;
}
2021-07-08 05:00:20 +02:00
rimraf . sync ( databaseFilePath ) ;
rimraf . sync ( ` ${ databaseFilePath } -shm ` ) ;
rimraf . sync ( ` ${ databaseFilePath } -wal ` ) ;
2018-07-27 03:13:56 +02:00
}
2018-12-06 01:33:11 +01:00
// Password hash
2018-12-09 23:25:36 +01:00
const PASS _HASH _ID = 'passHash' ;
2021-07-08 05:00:20 +02:00
function getPasswordHash ( ) {
const item = getItemById ( PASS _HASH _ID ) ;
2018-12-06 01:33:11 +01:00
return item && item . value ;
}
2021-07-08 05:00:20 +02:00
function savePasswordHash ( hash ) {
2018-12-06 01:33:11 +01:00
if ( isEmpty ( hash ) ) {
return removePasswordHash ( ) ;
}
2018-12-09 23:25:36 +01:00
const data = { id : PASS _HASH _ID , value : hash } ;
2018-12-06 01:33:11 +01:00
return createOrUpdateItem ( data ) ;
}
2021-07-08 05:00:20 +02:00
function removePasswordHash ( ) {
2018-12-09 23:25:36 +01:00
return removeItemById ( PASS _HASH _ID ) ;
2018-12-06 01:33:11 +01:00
}
2021-07-08 05:00:20 +02:00
function getIdentityKeyById ( id , instance ) {
2021-01-12 06:11:05 +01:00
return getById ( IDENTITY _KEYS _TABLE , id , instance ) ;
2018-11-07 05:16:49 +01:00
}
2021-07-08 05:00:20 +02:00
function getGuardNodes ( ) {
const nodes = globalInstance . prepare ( ` SELECT ed25519PubKey FROM ${ GUARD _NODE _TABLE } ; ` ) . all ( ) ;
2020-03-23 05:00:51 +01:00
if ( ! nodes ) {
return null ;
}
return nodes ;
}
2021-07-08 05:00:20 +02:00
function updateGuardNodes ( nodes ) {
globalInstance . transaction ( ( ) => {
globalInstance . exec ( ` DELETE FROM ${ GUARD _NODE _TABLE } ` ) ;
2020-03-24 08:22:51 +01:00
nodes . map ( edkey =>
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT INTO ${ GUARD _NODE _TABLE } (
2020-03-23 05:00:51 +01:00
ed25519PubKey
2021-07-08 05:00:20 +02:00
) values ( $ed25519PubKey ) `
)
. run ( {
ed25519PubKey : edkey ,
} )
) ;
} ) ( ) ;
2020-03-23 05:00:51 +01:00
}
2021-07-08 05:00:20 +02:00
function createOrUpdateItem ( data , instance ) {
2021-04-14 02:33:52 +02:00
return createOrUpdate ( ITEMS _TABLE , data , instance ) ;
2018-11-07 05:16:49 +01:00
}
2021-07-08 05:00:20 +02:00
function getItemById ( id ) {
2018-11-07 05:16:49 +01:00
return getById ( ITEMS _TABLE , id ) ;
}
2021-07-08 05:00:20 +02:00
function getAllItems ( ) {
const rows = globalInstance . prepare ( ` SELECT json FROM ${ ITEMS _TABLE } ORDER BY id ASC; ` ) . all ( ) ;
2018-11-07 05:16:49 +01:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function removeItemById ( id ) {
2018-11-07 05:16:49 +01:00
return removeById ( ITEMS _TABLE , id ) ;
}
2021-07-08 05:00:20 +02:00
function createOrUpdate ( table , data , instance ) {
2018-11-07 05:16:49 +01:00
const { id } = data ;
if ( ! id ) {
throw new Error ( 'createOrUpdate: Provided data did not have a truthy id' ) ;
}
2021-07-08 05:00:20 +02:00
( globalInstance || instance )
. prepare (
` INSERT OR REPLACE INTO ${ table } (
2018-11-07 05:16:49 +01:00
id ,
json
) values (
$id ,
$json
2021-07-08 05:00:20 +02:00
) `
)
. run ( {
2021-07-12 08:00:04 +02:00
id ,
2021-07-08 05:00:20 +02:00
json : objectToJSON ( data ) ,
} ) ;
2018-11-07 05:16:49 +01:00
}
2021-07-08 05:00:20 +02:00
function getById ( table , id , instance ) {
const row = ( globalInstance || instance ) . prepare ( ` SELECT * FROM ${ table } WHERE id = $ id; ` ) . get ( {
id ,
2021-04-22 10:03:58 +02:00
} ) ;
2018-11-07 05:16:49 +01:00
if ( ! row ) {
return null ;
}
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function removeById ( table , id ) {
2018-11-07 05:16:49 +01:00
if ( ! Array . isArray ( id ) ) {
2021-07-08 05:00:20 +02:00
globalInstance . prepare ( ` DELETE FROM ${ table } WHERE id = $ id; ` ) . run ( { id } ) ;
2018-11-07 05:16:49 +01:00
return ;
}
if ( ! id . length ) {
throw new Error ( 'removeById: No ids to delete!' ) ;
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
2021-07-08 05:00:20 +02:00
globalInstance
. prepare ( ` DELETE FROM ${ table } WHERE id IN ( ${ id . map ( ( ) => '?' ) . join ( ', ' ) } ); ` )
. run ( { id } ) ;
2018-11-07 05:16:49 +01:00
}
// Conversations
2021-07-08 05:00:20 +02:00
function getSwarmNodesForPubkey ( pubkey ) {
const row = globalInstance
. prepare ( ` SELECT * FROM ${ NODES _FOR _PUBKEY _TABLE } WHERE pubkey = $ pubkey; ` )
. get ( {
pubkey ,
} ) ;
2019-01-09 02:33:21 +01:00
if ( ! row ) {
2019-01-30 04:23:36 +01:00
return [ ] ;
2019-01-09 02:33:21 +01:00
}
2020-07-06 07:28:22 +02:00
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function updateSwarmNodesForPubkey ( pubkey , snodeEdKeys ) {
globalInstance
. prepare (
` INSERT OR REPLACE INTO ${ NODES _FOR _PUBKEY _TABLE } (
2020-07-06 07:28:22 +02:00
pubkey ,
json
) values (
$pubkey ,
$json
2021-07-08 05:00:20 +02:00
) ; `
)
. run ( {
pubkey ,
json : objectToJSON ( snodeEdKeys ) ,
} ) ;
2019-01-09 02:33:21 +01:00
}
2021-07-08 05:00:20 +02:00
function getConversationCount ( ) {
const row = globalInstance . prepare ( ` SELECT count(*) from ${ CONVERSATIONS _TABLE } ; ` ) . get ( ) ;
2018-09-21 03:47:19 +02:00
if ( ! row ) {
2021-04-22 10:03:58 +02:00
throw new Error ( ` getConversationCount: Unable to get count of ${ CONVERSATIONS _TABLE } ` ) ;
2018-09-21 03:47:19 +02:00
}
return row [ 'count(*)' ] ;
}
2021-07-08 05:00:20 +02:00
function saveConversation ( data ) {
2021-07-12 08:00:04 +02:00
const { id , active _at , type , members , name , profileName } = data ;
2018-09-21 03:47:19 +02:00
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT INTO ${ CONVERSATIONS _TABLE } (
2018-09-21 03:47:19 +02:00
id ,
json ,
active _at ,
type ,
members ,
name ,
profileName
) values (
$id ,
$json ,
$active _at ,
$type ,
$members ,
$name ,
$profileName
2021-07-08 05:00:20 +02:00
) ; `
)
. run ( {
id ,
json : objectToJSON ( data ) ,
active _at ,
type ,
members : members ? members . join ( ' ' ) : null ,
name ,
profileName ,
} ) ;
2018-09-21 03:47:19 +02:00
}
2021-07-08 05:00:20 +02:00
function updateConversation ( data ) {
2019-01-16 05:44:13 +01:00
const {
id ,
// eslint-disable-next-line camelcase
active _at ,
type ,
members ,
name ,
profileName ,
} = data ;
2018-09-21 03:47:19 +02:00
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` UPDATE ${ CONVERSATIONS _TABLE } SET
2018-09-21 03:47:19 +02:00
json = $json ,
active _at = $active _at ,
type = $type ,
members = $members ,
name = $name ,
profileName = $profileName
2021-11-04 04:47:47 +01:00
WHERE id = $id ; `
2021-07-08 05:00:20 +02:00
)
. run ( {
id ,
json : objectToJSON ( data ) ,
active _at ,
type ,
members : members ? members . join ( ' ' ) : null ,
name ,
profileName ,
} ) ;
2018-09-21 03:47:19 +02:00
}
2021-07-08 05:00:20 +02:00
function removeConversation ( id ) {
2018-09-21 03:47:19 +02:00
if ( ! Array . isArray ( id ) ) {
2021-07-08 05:00:20 +02:00
globalInstance . prepare ( ` DELETE FROM ${ CONVERSATIONS _TABLE } WHERE id = $ id; ` ) . run ( {
id ,
2019-09-05 09:41:12 +02:00
} ) ;
2018-09-21 03:47:19 +02:00
return ;
}
if ( ! id . length ) {
throw new Error ( 'removeConversation: No ids to delete!' ) ;
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
2021-07-08 05:00:20 +02:00
globalInstance
. prepare ( ` DELETE FROM ${ CONVERSATIONS _TABLE } WHERE id IN ( ${ id . map ( ( ) => '?' ) . join ( ', ' ) } ); ` )
. run ( id ) ;
2018-09-21 03:47:19 +02:00
}
2021-07-08 05:00:20 +02:00
function getConversationById ( id ) {
const row = globalInstance . prepare ( ` SELECT * FROM ${ CONVERSATIONS _TABLE } WHERE id = $ id; ` ) . get ( {
id ,
2021-04-22 10:03:58 +02:00
} ) ;
2018-09-21 03:47:19 +02:00
if ( ! row ) {
return null ;
}
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function getAllConversations ( ) {
const rows = globalInstance
. prepare ( ` SELECT json FROM ${ CONVERSATIONS _TABLE } ORDER BY id ASC; ` )
. all ( ) ;
2018-09-21 03:47:19 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getAllOpenGroupV1Conversations ( ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
2019-07-22 08:40:17 +02:00
type = 'group' AND
2021-04-23 06:59:08 +02:00
id LIKE 'publicChat:1@%'
ORDER BY id ASC ; `
2021-07-08 05:00:20 +02:00
)
. all ( ) ;
2021-04-23 06:59:08 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getAllOpenGroupV2Conversations ( ) {
2021-04-23 06:59:08 +02:00
// first _ matches all opengroupv1,
// second _ force a second char to be there, so it can only be opengroupv2 convos
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
2021-04-23 06:59:08 +02:00
type = 'group' AND
id LIKE 'publicChat:__%@%'
2019-07-22 08:40:17 +02:00
ORDER BY id ASC ; `
2021-07-08 05:00:20 +02:00
)
. all ( ) ;
2019-07-22 08:40:17 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getPubkeysInPublicConversation ( conversationId ) {
const rows = globalInstance
. prepare (
` SELECT DISTINCT source FROM ${ MESSAGES _TABLE } WHERE
2019-11-13 05:52:17 +01:00
conversationId = $conversationId
2021-07-08 05:00:20 +02:00
ORDER BY received _at DESC LIMIT $ { MAX _PUBKEYS _MEMBERS } ; `
)
. all ( {
conversationId ,
} ) ;
2019-11-13 05:52:17 +01:00
return map ( rows , row => row . source ) ;
}
2021-07-08 05:00:20 +02:00
function getAllGroupsInvolvingId ( id ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
2018-09-21 03:47:19 +02:00
type = 'group' AND
members LIKE $id
2021-07-08 05:00:20 +02:00
ORDER BY id ASC ; `
)
. all ( {
id : ` % ${ id } % ` ,
} ) ;
2018-09-21 03:47:19 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function searchConversations ( query , { limit } = { } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
2019-01-14 22:47:19 +01:00
(
id LIKE $id OR
name LIKE $name OR
profileName LIKE $profileName
)
2019-01-14 22:49:58 +01:00
ORDER BY id ASC
2021-07-08 05:00:20 +02:00
LIMIT $limit `
)
. all ( {
id : ` % ${ query } % ` ,
name : ` % ${ query } % ` ,
profileName : ` % ${ query } % ` ,
limit : limit || 50 ,
} ) ;
2018-09-21 03:47:19 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function searchMessages ( query , { limit } = { } ) {
const rows = globalInstance
. prepare (
` SELECT
2019-01-14 22:47:19 +01:00
messages . json ,
snippet ( messages _fts , - 1 , '<<left>>' , '<<right>>' , '...' , 15 ) as snippet
2021-06-11 04:09:57 +02:00
FROM $ { MESSAGES _FTS _TABLE }
2021-06-11 04:04:07 +02:00
INNER JOIN $ { MESSAGES _TABLE } on messages _fts . id = messages . id
2019-01-14 22:47:19 +01:00
WHERE
messages _fts match $query
ORDER BY messages . received _at DESC
2021-07-08 05:00:20 +02:00
LIMIT $limit ; `
)
. all ( {
2021-07-12 08:00:04 +02:00
query ,
2021-07-08 05:00:20 +02:00
limit : limit || 100 ,
} ) ;
2019-01-14 22:47:19 +01:00
return map ( rows , row => ( {
... jsonToObject ( row . json ) ,
snippet : row . snippet ,
} ) ) ;
}
2021-07-08 05:00:20 +02:00
function searchMessagesInConversation ( query , conversationId , { limit } = { } ) {
const rows = globalInstance
. prepare (
` SELECT
2019-01-14 22:47:19 +01:00
messages . json ,
snippet ( messages _fts , - 1 , '<<left>>' , '<<right>>' , '...' , 15 ) as snippet
FROM messages _fts
2021-06-11 04:04:07 +02:00
INNER JOIN $ { MESSAGES _TABLE } on messages _fts . id = messages . id
2019-01-14 22:47:19 +01:00
WHERE
messages _fts match $query AND
messages . conversationId = $conversationId
ORDER BY messages . received _at DESC
2021-07-08 05:00:20 +02:00
LIMIT $limit ; `
)
. all ( {
query ,
conversationId ,
limit : limit || 100 ,
} ) ;
2019-01-14 22:47:19 +01:00
return map ( rows , row => ( {
... jsonToObject ( row . json ) ,
snippet : row . snippet ,
} ) ) ;
}
2021-07-08 05:00:20 +02:00
function getMessageCount ( ) {
const row = globalInstance . prepare ( ` SELECT count(*) from ${ MESSAGES _TABLE } ; ` ) . get ( ) ;
2018-08-07 01:18:58 +02:00
if ( ! row ) {
2021-04-22 10:03:58 +02:00
throw new Error ( ` getMessageCount: Unable to get count of ${ MESSAGES _TABLE } ` ) ;
2018-08-07 01:18:58 +02:00
}
return row [ 'count(*)' ] ;
}
2021-07-08 05:00:20 +02:00
function saveMessage ( data ) {
2018-07-27 03:13:56 +02:00
const {
2019-01-14 22:47:19 +01:00
body ,
2018-07-27 03:13:56 +02:00
conversationId ,
// eslint-disable-next-line camelcase
expires _at ,
hasAttachments ,
hasFileAttachments ,
hasVisualMediaAttachments ,
id ,
2019-08-09 05:34:48 +02:00
serverId ,
2020-09-04 07:11:24 +02:00
serverTimestamp ,
2018-07-27 03:13:56 +02:00
// eslint-disable-next-line camelcase
received _at ,
schemaVersion ,
2018-11-16 00:03:43 +01:00
sent ,
2018-11-19 07:57:20 +01:00
// eslint-disable-next-line camelcase
2018-07-27 03:13:56 +02:00
sent _at ,
source ,
sourceDevice ,
2018-08-07 21:33:56 +02:00
type ,
2018-07-27 03:13:56 +02:00
unread ,
2018-08-07 21:33:56 +02:00
expireTimer ,
expirationStartTimestamp ,
2018-07-27 03:13:56 +02:00
} = data ;
2021-02-19 03:01:24 +01:00
if ( ! id ) {
throw new Error ( 'id is required' ) ;
}
if ( ! conversationId ) {
throw new Error ( 'conversationId is required' ) ;
}
2018-08-07 21:33:56 +02:00
const payload = {
2021-07-08 05:00:20 +02:00
id ,
json : objectToJSON ( data ) ,
serverId ,
serverTimestamp ,
body ,
conversationId ,
expirationStartTimestamp ,
expires _at ,
expireTimer ,
hasAttachments ,
hasFileAttachments ,
hasVisualMediaAttachments ,
received _at ,
schemaVersion ,
sent ,
sent _at ,
source ,
sourceDevice ,
type : type || '' ,
unread ,
2018-08-07 21:33:56 +02:00
} ;
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT OR REPLACE INTO ${ MESSAGES _TABLE } (
2018-07-27 03:13:56 +02:00
id ,
json ,
2019-08-09 05:34:48 +02:00
serverId ,
2020-09-04 07:11:24 +02:00
serverTimestamp ,
2019-01-14 22:47:19 +01:00
body ,
2018-07-27 03:13:56 +02:00
conversationId ,
2018-08-07 21:33:56 +02:00
expirationStartTimestamp ,
2018-07-27 03:13:56 +02:00
expires _at ,
2018-08-07 21:33:56 +02:00
expireTimer ,
2018-07-27 03:13:56 +02:00
hasAttachments ,
hasFileAttachments ,
hasVisualMediaAttachments ,
received _at ,
schemaVersion ,
2018-11-16 00:03:43 +01:00
sent ,
2018-07-27 03:13:56 +02:00
sent _at ,
source ,
sourceDevice ,
2018-08-07 21:33:56 +02:00
type ,
2018-07-27 03:13:56 +02:00
unread
) values (
$id ,
$json ,
2019-08-09 05:34:48 +02:00
$serverId ,
2020-09-04 07:11:24 +02:00
$serverTimestamp ,
2019-01-14 22:47:19 +01:00
$body ,
2018-07-27 03:13:56 +02:00
$conversationId ,
2018-08-07 21:33:56 +02:00
$expirationStartTimestamp ,
2018-07-27 03:13:56 +02:00
$expires _at ,
2018-08-07 21:33:56 +02:00
$expireTimer ,
2018-07-27 03:13:56 +02:00
$hasAttachments ,
$hasFileAttachments ,
$hasVisualMediaAttachments ,
$received _at ,
$schemaVersion ,
2018-11-16 00:03:43 +01:00
$sent ,
2018-07-27 03:13:56 +02:00
$sent _at ,
$source ,
$sourceDevice ,
2018-08-07 21:33:56 +02:00
$type ,
2018-07-27 03:13:56 +02:00
$unread
2021-07-08 05:00:20 +02:00
) ; `
)
. run ( payload ) ;
2018-07-27 03:13:56 +02:00
2021-02-19 03:01:24 +01:00
return id ;
2018-07-27 03:13:56 +02:00
}
2021-08-16 03:14:39 +02:00
function saveSeenMessageHashes ( arrayOfHashes ) {
2021-07-08 05:00:20 +02:00
globalInstance . transaction ( ( ) => {
map ( arrayOfHashes , hashData => saveSeenMessageHash ( hashData ) ) ;
} ) ( ) ;
2018-11-19 07:57:20 +01:00
}
2021-07-08 05:00:20 +02:00
function updateLastHash ( data ) {
2020-05-04 05:24:53 +02:00
const { convoId , snode , hash , expiresAt } = data ;
const id = convoId ;
2019-04-15 05:19:06 +02:00
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT OR REPLACE INTO lastHashes (
2020-05-04 05:24:53 +02:00
id ,
2019-04-15 05:19:06 +02:00
snode ,
hash ,
expiresAt
) values (
2020-05-04 05:24:53 +02:00
$id ,
2019-04-15 05:19:06 +02:00
$snode ,
$hash ,
$expiresAt
2021-07-08 05:00:20 +02:00
) `
)
. run ( {
id ,
snode ,
hash ,
expiresAt ,
} ) ;
2019-04-15 05:19:06 +02:00
}
2021-07-08 05:00:20 +02:00
function saveSeenMessageHash ( data ) {
2019-01-16 05:44:13 +01:00
const { expiresAt , hash } = data ;
2021-07-30 02:36:25 +02:00
try {
globalInstance
. prepare (
` INSERT INTO seenMessages (
2018-11-19 07:57:20 +01:00
expiresAt ,
hash
2020-06-30 10:04:17 +02:00
) values (
$expiresAt ,
$hash
2021-07-08 05:00:20 +02:00
) ; `
2021-07-30 02:36:25 +02:00
)
. run ( {
expiresAt ,
hash ,
} ) ;
} catch ( e ) {
console . warn ( 'saveSeenMessageHash failed:' , e ) ;
}
2018-11-19 07:57:20 +01:00
}
2021-07-08 05:00:20 +02:00
function cleanLastHashes ( ) {
globalInstance . prepare ( 'DELETE FROM lastHashes WHERE expiresAt <= $now;' ) . run ( {
now : Date . now ( ) ,
2019-04-15 05:19:06 +02:00
} ) ;
}
2021-07-08 05:00:20 +02:00
function cleanSeenMessages ( ) {
globalInstance . prepare ( 'DELETE FROM seenMessages WHERE expiresAt <= $now;' ) . run ( {
now : Date . now ( ) ,
2018-11-19 07:57:20 +01:00
} ) ;
}
2021-08-16 03:14:39 +02:00
function saveMessages ( arrayOfMessages ) {
2021-07-08 05:00:20 +02:00
globalInstance . transaction ( ( ) => {
map ( arrayOfMessages , message => saveMessage ( message ) ) ;
} ) ( ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function removeMessage ( id , instance ) {
2018-07-27 03:13:56 +02:00
if ( ! Array . isArray ( id ) ) {
2021-07-08 05:00:20 +02:00
( globalInstance || instance )
. prepare ( ` DELETE FROM ${ MESSAGES _TABLE } WHERE id = $ id; ` )
. run ( { id } ) ;
2018-07-27 03:13:56 +02:00
return ;
}
if ( ! id . length ) {
throw new Error ( 'removeMessages: No ids to delete!' ) ;
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
2021-07-08 05:00:20 +02:00
( globalInstance || instance )
. prepare ( ` DELETE FROM ${ MESSAGES _TABLE } WHERE id IN ( ${ id . map ( ( ) => '?' ) . join ( ', ' ) } ); ` )
. run ( id ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function getMessageIdsFromServerIds ( serverIds , conversationId ) {
2020-04-20 01:52:47 +02:00
if ( ! Array . isArray ( serverIds ) ) {
return [ ] ;
}
// Sanitize the input as we're going to use it directly in the query
2021-04-22 10:03:58 +02:00
const validIds = serverIds . map ( id => Number ( id ) ) . filter ( n => ! Number . isNaN ( n ) ) ;
2020-04-20 01:52:47 +02:00
/ *
Sqlite3 doesn ' t have a good way to have ` IN ` query with another query .
See : https : //github.com/mapbox/node-sqlite3/issues/762.
So we have to use templating to insert the values .
* /
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT id FROM ${ MESSAGES _TABLE } WHERE
2020-04-20 01:52:47 +02:00
serverId IN ( $ { validIds . join ( ',' ) } ) AND
2021-07-08 05:00:20 +02:00
conversationId = $conversationId ; `
)
. all ( {
conversationId ,
} ) ;
2020-04-20 01:52:47 +02:00
return rows . map ( row => row . id ) ;
2019-08-09 05:34:48 +02:00
}
2021-07-08 05:00:20 +02:00
function getMessageById ( id ) {
const row = globalInstance . prepare ( ` SELECT * FROM ${ MESSAGES _TABLE } WHERE id = $ id; ` ) . get ( {
id ,
2018-07-27 03:13:56 +02:00
} ) ;
if ( ! row ) {
return null ;
}
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function getMessageBySender ( { source , sourceDevice , sentAt } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2018-07-27 03:13:56 +02:00
source = $source AND
sourceDevice = $sourceDevice AND
2021-07-08 05:00:20 +02:00
sent _at = $sent _at ; `
)
. all ( {
source ,
sourceDevice ,
sent _at : sentAt ,
} ) ;
2021-04-28 07:53:30 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
2020-09-08 02:23:37 +02:00
}
2021-09-20 05:47:59 +02:00
function getMessageBySenderAndTimestamp ( { source , timestamp } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
source = $source AND
sent _at = $timestamp ; `
)
. all ( {
source ,
timestamp ,
} ) ;
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getMessageBySenderAndServerTimestamp ( { source , serverTimestamp } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2021-06-07 07:54:44 +02:00
source = $source AND
2021-07-08 05:00:20 +02:00
serverTimestamp = $serverTimestamp ; `
)
. all ( {
source ,
serverTimestamp ,
} ) ;
2021-06-07 07:54:44 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getUnreadByConversation ( conversationId ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2018-08-09 03:32:10 +02:00
unread = $unread AND
conversationId = $conversationId
2021-07-08 05:00:20 +02:00
ORDER BY received _at DESC ; `
)
. all ( {
unread : 1 ,
conversationId ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getUnreadCountByConversation ( conversationId ) {
const row = globalInstance
. prepare (
` SELECT count(*) from ${ MESSAGES _TABLE } WHERE
2020-10-30 06:02:18 +01:00
unread = $unread AND
conversationId = $conversationId
2021-07-08 05:00:20 +02:00
ORDER BY received _at DESC ; `
)
. get ( {
unread : 1 ,
conversationId ,
} ) ;
2020-10-30 06:02:18 +01:00
if ( ! row ) {
throw new Error (
` getUnreadCountByConversation: Unable to get unread count of ${ conversationId } `
) ;
}
return row [ 'count(*)' ] ;
}
2020-01-13 05:37:55 +01:00
// Note: Sorting here is necessary for getting the last message (with limit 1)
2021-07-08 05:00:20 +02:00
// be sure to update the sorting order to sort messages on redux too (sortMessages)
2021-02-09 01:40:32 +01:00
2021-07-08 05:00:20 +02:00
function getMessagesByConversation (
2018-07-27 03:13:56 +02:00
conversationId ,
2018-11-13 05:35:45 +01:00
{ limit = 100 , receivedAt = Number . MAX _VALUE , type = '%' } = { }
2018-07-27 03:13:56 +02:00
) {
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
`
2020-10-30 03:19:57 +01:00
SELECT json FROM $ { MESSAGES _TABLE } WHERE
2018-11-13 05:35:45 +01:00
conversationId = $conversationId AND
received _at < $received _at AND
type LIKE $type
2021-02-09 01:40:32 +01:00
ORDER BY serverTimestamp DESC , serverId DESC , sent _at DESC , received _at DESC
2018-11-13 05:35:45 +01:00
LIMIT $limit ;
2021-07-08 05:00:20 +02:00
`
)
. all ( {
conversationId ,
received _at : receivedAt ,
limit ,
type ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-12-08 04:15:54 +01:00
function hasConversationOutgoingMessage ( conversationId ) {
const row = globalInstance
. prepare (
`
SELECT count ( * ) FROM $ { MESSAGES _TABLE } WHERE
conversationId = $conversationId AND
type IS 'outgoing'
`
)
. get ( {
conversationId ,
} ) ;
if ( ! row ) {
throw new Error ( 'hasConversationOutgoingMessage: Unable to get coun' ) ;
}
console . warn ( 'hasConversationOutgoingMessage' , row ) ;
return Boolean ( row [ 'count(*)' ] ) ;
}
2021-07-22 02:20:09 +02:00
function getFirstUnreadMessageIdInConversation ( conversationId ) {
const rows = globalInstance
. prepare (
`
SELECT id FROM $ { MESSAGES _TABLE } WHERE
conversationId = $conversationId AND
unread = $unread
ORDER BY serverTimestamp ASC , serverId ASC , sent _at ASC , received _at ASC
LIMIT 1 ;
`
)
. all ( {
conversationId ,
unread : 1 ,
} ) ;
if ( rows . length === 0 ) {
return undefined ;
}
return rows [ 0 ] . id ;
}
2021-12-22 01:20:29 +01:00
/ * *
* Deletes all but the 10 , 000 last received messages .
* /
function trimMessages ( ) {
globalInstance
. prepare (
`
DELETE FROM $ { MESSAGES _TABLE }
WHERE id NOT IN (
SELECT id FROM $ { MESSAGES _TABLE }
ORDER BY received _at DESC
LIMIT 10000
) ;
`
)
. run ( ) ;
const rows = globalInstance
. prepare (
` SELECT * FROM ${ MESSAGES _TABLE }
ORDER BY received _at DESC ; `
)
. all ( ) ;
return rows ;
// return map(rows, row => jsonToObject(row.json));
}
2021-07-08 05:00:20 +02:00
function getMessagesBySentAt ( sentAt ) {
const rows = globalInstance
. prepare (
` SELECT * FROM ${ MESSAGES _TABLE }
2018-07-27 03:13:56 +02:00
WHERE sent _at = $sent _at
2021-07-08 05:00:20 +02:00
ORDER BY received _at DESC ; `
)
. all ( {
sent _at : sentAt ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getLastHashBySnode ( convoId , snode ) {
const row = globalInstance
. prepare ( 'SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;' )
. get ( {
2021-07-12 08:00:04 +02:00
snode ,
2021-07-08 05:00:20 +02:00
id : convoId ,
} ) ;
2019-04-15 05:19:06 +02:00
if ( ! row ) {
return null ;
}
2020-05-04 05:24:53 +02:00
return row . hash ;
2019-04-15 05:19:06 +02:00
}
2021-07-08 05:00:20 +02:00
function getSeenMessagesByHashList ( hashes ) {
const rows = globalInstance
. prepare ( ` SELECT * FROM seenMessages WHERE hash IN ( ${ hashes . map ( ( ) => '?' ) . join ( ', ' ) } ); ` )
. all ( hashes ) ;
2018-11-19 07:57:20 +01:00
return map ( rows , row => row . hash ) ;
}
2021-07-08 05:00:20 +02:00
function getExpiredMessages ( ) {
2018-07-27 03:13:56 +02:00
const now = Date . now ( ) ;
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2018-07-27 03:13:56 +02:00
expires _at IS NOT NULL AND
expires _at <= $expires _at
2021-07-08 05:00:20 +02:00
ORDER BY expires _at ASC ; `
)
. all ( {
expires _at : now ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getOutgoingWithoutExpiresAt ( ) {
const rows = globalInstance
. prepare (
`
2020-10-30 03:19:57 +01:00
SELECT json FROM $ { MESSAGES _TABLE }
2018-08-07 21:33:56 +02:00
WHERE
2018-08-09 03:32:10 +02:00
expireTimer > 0 AND
expires _at IS NULL AND
type IS 'outgoing'
2018-08-07 21:33:56 +02:00
ORDER BY expires _at ASC ;
2021-07-08 05:00:20 +02:00
`
)
. all ( ) ;
2018-08-07 21:33:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getNextExpiringMessage ( ) {
const rows = globalInstance
. prepare (
`
2020-10-30 03:19:57 +01:00
SELECT json FROM $ { MESSAGES _TABLE }
2018-08-09 03:32:10 +02:00
WHERE expires _at > 0
2018-07-27 03:13:56 +02:00
ORDER BY expires _at ASC
LIMIT 1 ;
2021-07-08 05:00:20 +02:00
`
)
. all ( ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-02-19 01:57:23 +01:00
/* Unproccessed a received messages not yet processed */
2021-07-08 05:00:20 +02:00
function saveUnprocessed ( data ) {
2021-09-20 05:47:59 +02:00
const { id , timestamp , version , attempts , envelope , senderIdentity , messageHash } = data ;
2019-02-05 02:23:50 +01:00
if ( ! id ) {
2021-02-19 03:01:24 +01:00
throw new Error ( ` saveUnprocessed: id was falsey: ${ id } ` ) ;
2019-02-05 02:23:50 +01:00
}
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT OR REPLACE INTO unprocessed (
2021-02-19 07:46:13 +01:00
id ,
timestamp ,
version ,
attempts ,
envelope ,
2021-09-20 05:47:59 +02:00
senderIdentity ,
serverHash
2021-02-19 07:46:13 +01:00
) values (
$id ,
$timestamp ,
$version ,
$attempts ,
$envelope ,
2021-09-20 05:47:59 +02:00
$senderIdentity ,
$messageHash
2021-07-08 05:00:20 +02:00
) ; `
)
. run ( {
id ,
timestamp ,
version ,
attempts ,
envelope ,
senderIdentity ,
2021-09-20 05:47:59 +02:00
messageHash ,
2021-07-08 05:00:20 +02:00
} ) ;
2018-07-27 03:13:56 +02:00
return id ;
}
2021-07-08 05:00:20 +02:00
function updateUnprocessedAttempts ( id , attempts ) {
globalInstance . prepare ( 'UPDATE unprocessed SET attempts = $attempts WHERE id = $id;' ) . run ( {
2021-07-12 08:00:04 +02:00
id ,
attempts ,
2018-07-27 03:13:56 +02:00
} ) ;
2019-02-05 02:23:50 +01:00
}
2021-07-08 05:00:20 +02:00
function updateUnprocessedWithData ( id , data = { } ) {
2021-04-22 10:03:58 +02:00
const { source , sourceDevice , serverTimestamp , decrypted , senderIdentity } = data ;
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` UPDATE unprocessed SET
2019-02-05 02:23:50 +01:00
source = $source ,
sourceDevice = $sourceDevice ,
serverTimestamp = $serverTimestamp ,
2020-05-04 05:24:53 +02:00
decrypted = $decrypted ,
senderIdentity = $senderIdentity
2021-07-08 05:00:20 +02:00
WHERE id = $id ; `
)
. run ( {
id ,
source ,
sourceDevice ,
serverTimestamp ,
decrypted ,
senderIdentity ,
} ) ;
2019-02-05 02:23:50 +01:00
}
2018-07-27 03:13:56 +02:00
2021-07-08 05:00:20 +02:00
function getUnprocessedById ( id ) {
const row = globalInstance . prepare ( 'SELECT * FROM unprocessed WHERE id = $id;' ) . get ( {
id ,
2019-02-05 02:23:50 +01:00
} ) ;
return row ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function getUnprocessedCount ( ) {
const row = globalInstance . prepare ( 'SELECT count(*) from unprocessed;' ) . get ( ) ;
2018-09-29 00:51:26 +02:00
if ( ! row ) {
throw new Error ( 'getMessageCount: Unable to get count of unprocessed' ) ;
}
return row [ 'count(*)' ] ;
}
2021-07-08 05:00:20 +02:00
function getAllUnprocessed ( ) {
const rows = globalInstance . prepare ( 'SELECT * FROM unprocessed ORDER BY timestamp ASC;' ) . all ( ) ;
2018-07-27 03:13:56 +02:00
2019-02-05 02:23:50 +01:00
return rows ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function removeUnprocessed ( id ) {
2018-07-27 03:13:56 +02:00
if ( ! Array . isArray ( id ) ) {
2021-07-08 05:00:20 +02:00
globalInstance . prepare ( 'DELETE FROM unprocessed WHERE id = $id;' ) . run ( { id } ) ;
2018-07-27 03:13:56 +02:00
return ;
}
if ( ! id . length ) {
throw new Error ( 'removeUnprocessed: No ids to delete!' ) ;
}
// Our node interface doesn't seem to allow you to replace one single ? with an array
2021-07-08 05:00:20 +02:00
globalInstance
. prepare ( ` DELETE FROM unprocessed WHERE id IN ( ${ id . map ( ( ) => '?' ) . join ( ', ' ) } ); ` )
. run ( id ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function removeAllUnprocessed ( ) {
globalInstance . prepare ( 'DELETE FROM unprocessed;' ) . run ( ) ;
2018-07-27 03:13:56 +02:00
}
2021-07-08 05:00:20 +02:00
function getNextAttachmentDownloadJobs ( limit , options = { } ) {
2019-01-30 21:15:07 +01:00
const timestamp = options . timestamp || Date . now ( ) ;
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT json FROM ${ ATTACHMENT _DOWNLOADS _TABLE }
2019-01-30 21:15:07 +01:00
WHERE pending = 0 AND timestamp < $timestamp
ORDER BY timestamp DESC
2021-07-08 05:00:20 +02:00
LIMIT $limit ; `
)
. all ( {
limit ,
timestamp ,
} ) ;
2019-01-30 21:15:07 +01:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function saveAttachmentDownloadJob ( job ) {
2019-01-30 21:15:07 +01:00
const { id , pending , timestamp } = job ;
if ( ! id ) {
2021-04-22 10:03:58 +02:00
throw new Error ( 'saveAttachmentDownloadJob: Provided job did not have a truthy id' ) ;
2019-01-30 21:15:07 +01:00
}
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT OR REPLACE INTO ${ ATTACHMENT _DOWNLOADS _TABLE } (
2019-01-30 21:15:07 +01:00
id ,
pending ,
timestamp ,
json
) values (
$id ,
$pending ,
$timestamp ,
$json
2021-07-08 05:00:20 +02:00
) `
)
. run ( {
id ,
pending ,
timestamp ,
json : objectToJSON ( job ) ,
} ) ;
2019-01-30 21:15:07 +01:00
}
2021-07-08 05:00:20 +02:00
function setAttachmentDownloadJobPending ( id , pending ) {
globalInstance
. prepare ( ` UPDATE ${ ATTACHMENT _DOWNLOADS _TABLE } SET pending = $ pending WHERE id = $ id; ` )
. run ( {
id ,
pending ,
} ) ;
2019-01-30 21:15:07 +01:00
}
2021-07-08 05:00:20 +02:00
function resetAttachmentDownloadPending ( ) {
globalInstance
. prepare ( ` UPDATE ${ ATTACHMENT _DOWNLOADS _TABLE } SET pending = 0 WHERE pending != 0; ` )
. run ( ) ;
2019-01-30 21:15:07 +01:00
}
2021-07-08 05:00:20 +02:00
function removeAttachmentDownloadJob ( id ) {
2019-01-30 21:15:07 +01:00
return removeById ( ATTACHMENT _DOWNLOADS _TABLE , id ) ;
}
2021-07-08 05:00:20 +02:00
function removeAllAttachmentDownloadJobs ( ) {
globalInstance . exec ( ` DELETE FROM ${ ATTACHMENT _DOWNLOADS _TABLE } ; ` ) ;
2019-01-30 21:15:07 +01:00
}
2018-11-07 05:16:49 +01:00
// All data in database
2021-07-08 05:00:20 +02:00
function removeAll ( ) {
globalInstance . exec ( `
DELETE FROM $ { IDENTITY _KEYS _TABLE } ;
DELETE FROM $ { ITEMS _TABLE } ;
DELETE FROM unprocessed ;
DELETE FROM lastHashes ;
DELETE FROM $ { NODES _FOR _PUBKEY _TABLE } ;
DELETE FROM $ { CLOSED _GROUP _V2 _KEY _PAIRS _TABLE } ;
DELETE FROM seenMessages ;
DELETE FROM $ { CONVERSATIONS _TABLE } ;
DELETE FROM $ { MESSAGES _TABLE } ;
DELETE FROM $ { ATTACHMENT _DOWNLOADS _TABLE } ;
DELETE FROM $ { MESSAGES _FTS _TABLE } ;
` );
}
function removeAllConversations ( ) {
globalInstance . prepare ( ` DELETE FROM ${ CONVERSATIONS _TABLE } ; ` ) . run ( ) ;
}
function getMessagesWithVisualMediaAttachments ( conversationId , { limit } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2018-07-27 03:13:56 +02:00
conversationId = $conversationId AND
hasVisualMediaAttachments = 1
ORDER BY received _at DESC
2021-07-08 05:00:20 +02:00
LIMIT $limit ; `
)
. all ( {
conversationId ,
limit ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getMessagesWithFileAttachments ( conversationId , { limit } ) {
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE } WHERE
2018-07-27 03:13:56 +02:00
conversationId = $conversationId AND
hasFileAttachments = 1
ORDER BY received _at DESC
2021-07-08 05:00:20 +02:00
LIMIT $limit ; `
)
. all ( {
conversationId ,
limit ,
} ) ;
2018-07-27 03:13:56 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2018-08-07 01:18:58 +02:00
function getExternalFilesForMessage ( message ) {
2019-01-16 04:03:56 +01:00
const { attachments , contact , quote , preview } = message ;
2018-08-07 01:18:58 +02:00
const files = [ ] ;
forEach ( attachments , attachment => {
const { path : file , thumbnail , screenshot } = attachment ;
if ( file ) {
files . push ( file ) ;
}
if ( thumbnail && thumbnail . path ) {
files . push ( thumbnail . path ) ;
}
if ( screenshot && screenshot . path ) {
files . push ( screenshot . path ) ;
}
} ) ;
if ( quote && quote . attachments && quote . attachments . length ) {
forEach ( quote . attachments , attachment => {
const { thumbnail } = attachment ;
if ( thumbnail && thumbnail . path ) {
files . push ( thumbnail . path ) ;
}
} ) ;
}
if ( contact && contact . length ) {
forEach ( contact , item => {
const { avatar } = item ;
if ( avatar && avatar . avatar && avatar . avatar . path ) {
files . push ( avatar . avatar . path ) ;
}
} ) ;
}
2019-01-16 04:03:56 +01:00
if ( preview && preview . length ) {
forEach ( preview , item => {
const { image } = item ;
if ( image && image . path ) {
files . push ( image . path ) ;
}
} ) ;
}
2018-08-07 01:18:58 +02:00
return files ;
}
2018-09-21 03:47:19 +02:00
function getExternalFilesForConversation ( conversation ) {
const { avatar , profileAvatar } = conversation ;
const files = [ ] ;
if ( avatar && avatar . path ) {
files . push ( avatar . path ) ;
}
if ( profileAvatar && profileAvatar . path ) {
files . push ( profileAvatar . path ) ;
}
return files ;
}
2021-07-08 05:00:20 +02:00
function removeKnownAttachments ( allAttachments ) {
2018-08-07 01:18:58 +02:00
const lookup = fromPairs ( map ( allAttachments , file => [ file , true ] ) ) ;
const chunkSize = 50 ;
2021-07-08 05:00:20 +02:00
const total = getMessageCount ( ) ;
2021-04-22 10:03:58 +02:00
console . log ( ` removeKnownAttachments: About to iterate through ${ total } messages ` ) ;
2018-08-07 01:18:58 +02:00
let count = 0 ;
let complete = false ;
let id = '' ;
while ( ! complete ) {
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT json FROM ${ MESSAGES _TABLE }
2018-08-07 01:18:58 +02:00
WHERE id > $id
ORDER BY id ASC
2021-07-08 05:00:20 +02:00
LIMIT $chunkSize ; `
)
. all ( {
id ,
chunkSize ,
} ) ;
2018-08-07 01:18:58 +02:00
const messages = map ( rows , row => jsonToObject ( row . json ) ) ;
forEach ( messages , message => {
const externalFiles = getExternalFilesForMessage ( message ) ;
forEach ( externalFiles , file => {
delete lookup [ file ] ;
} ) ;
} ) ;
const lastMessage = last ( messages ) ;
if ( lastMessage ) {
( { id } = lastMessage ) ;
}
complete = messages . length < chunkSize ;
count += messages . length ;
}
2021-06-11 04:04:07 +02:00
console . log ( ` removeKnownAttachments: Done processing ${ count } ${ MESSAGES _TABLE } ` ) ;
2018-08-07 01:18:58 +02:00
2018-09-21 03:47:19 +02:00
complete = false ;
count = 0 ;
// Though conversations.id is a string, this ensures that, when coerced, this
// value is still a string but it's smaller than every other string.
id = 0 ;
2021-07-08 05:00:20 +02:00
const conversationTotal = getConversationCount ( ) ;
2018-09-21 03:47:19 +02:00
console . log (
2021-06-11 04:09:57 +02:00
` removeKnownAttachments: About to iterate through ${ conversationTotal } ${ CONVERSATIONS _TABLE } `
2018-09-21 03:47:19 +02:00
) ;
while ( ! complete ) {
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE }
2018-09-21 03:47:19 +02:00
WHERE id > $id
ORDER BY id ASC
2021-07-08 05:00:20 +02:00
LIMIT $chunkSize ; `
)
. all ( {
id ,
chunkSize ,
} ) ;
2018-09-21 03:47:19 +02:00
const conversations = map ( rows , row => jsonToObject ( row . json ) ) ;
forEach ( conversations , conversation => {
const externalFiles = getExternalFilesForConversation ( conversation ) ;
forEach ( externalFiles , file => {
delete lookup [ file ] ;
} ) ;
} ) ;
const lastMessage = last ( conversations ) ;
if ( lastMessage ) {
( { id } = lastMessage ) ;
}
complete = conversations . length < chunkSize ;
count += conversations . length ;
}
2021-06-11 04:09:57 +02:00
console . log ( ` removeKnownAttachments: Done processing ${ count } ${ CONVERSATIONS _TABLE } ` ) ;
2018-09-21 03:47:19 +02:00
2018-08-07 01:18:58 +02:00
return Object . keys ( lookup ) ;
}
2020-09-22 00:58:21 +02:00
2021-07-08 05:00:20 +02:00
function getMessagesCountByConversation ( instance , conversationId ) {
const row = instance
. prepare ( ` SELECT count(*) from ${ MESSAGES _TABLE } WHERE conversationId = $ conversationId; ` )
. get ( { conversationId } ) ;
2020-09-22 07:36:39 +02:00
return row ? row [ 'count(*)' ] : 0 ;
}
2021-07-08 05:00:20 +02:00
function getAllClosedGroupConversationsV1 ( instance ) {
const rows = ( globalInstance || instance )
. prepare (
` SELECT json FROM ${ CONVERSATIONS _TABLE } WHERE
2021-01-12 06:11:05 +01:00
type = 'group' AND
id NOT LIKE 'publicChat:%'
ORDER BY id ASC ; `
2021-07-08 05:00:20 +02:00
)
. all ( ) ;
2021-01-12 06:11:05 +01:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
function remove05PrefixFromStringIfNeeded ( str ) {
if ( str . length === 66 && str . startsWith ( '05' ) ) {
return str . substr ( 2 ) ;
}
return str ;
}
2021-07-08 05:00:20 +02:00
function updateExistingClosedGroupV1ToClosedGroupV2 ( db ) {
2021-01-12 06:11:05 +01:00
// the migration is called only once, so all current groups not being open groups are v1 closed group.
2021-07-08 05:00:20 +02:00
const allClosedGroupV1 = getAllClosedGroupConversationsV1 ( db ) || [ ] ;
2021-01-12 06:11:05 +01:00
2021-07-08 05:00:20 +02:00
allClosedGroupV1 . forEach ( groupV1 => {
const groupId = groupV1 . id ;
try {
console . log ( 'Migrating closed group v1 to v2: pubkey' , groupId ) ;
const groupV1IdentityKey = getIdentityKeyById ( groupId , db ) ;
const encryptionPubKeyWithoutPrefix = remove05PrefixFromStringIfNeeded ( groupV1IdentityKey . id ) ;
// Note:
// this is what we get from getIdentityKeyById:
// {
// id: string;
// secretKey?: string;
// }
// and this is what we want saved in db:
// {
// publicHex: string; // without prefix
// privateHex: string;
// }
const keyPair = {
publicHex : encryptionPubKeyWithoutPrefix ,
privateHex : groupV1IdentityKey . secretKey ,
} ;
addClosedGroupEncryptionKeyPair ( groupId , keyPair , db ) ;
} catch ( e ) {
console . warn ( e ) ;
}
} ) ;
2021-01-12 06:11:05 +01:00
}
/ * *
* The returned array is ordered based on the timestamp , the latest is at the end .
* @ param { * } groupPublicKey string | PubKey
* /
2021-07-08 05:00:20 +02:00
function getAllEncryptionKeyPairsForGroup ( groupPublicKey ) {
const rows = getAllEncryptionKeyPairsForGroupRaw ( groupPublicKey ) ;
2021-02-01 01:35:06 +01:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getAllEncryptionKeyPairsForGroupRaw ( groupPublicKey ) {
2021-04-22 10:03:58 +02:00
const pubkeyAsString = groupPublicKey . key ? groupPublicKey . key : groupPublicKey ;
2021-07-08 05:00:20 +02:00
const rows = globalInstance
. prepare (
` SELECT * FROM ${ CLOSED _GROUP _V2 _KEY _PAIRS _TABLE } WHERE groupPublicKey = $ groupPublicKey ORDER BY timestamp ASC; `
)
. all ( {
groupPublicKey : pubkeyAsString ,
} ) ;
2021-01-12 06:11:05 +01:00
2021-02-01 01:35:06 +01:00
return rows ;
2021-01-12 06:11:05 +01:00
}
2021-07-08 05:00:20 +02:00
function getLatestClosedGroupEncryptionKeyPair ( groupPublicKey ) {
const rows = getAllEncryptionKeyPairsForGroup ( groupPublicKey ) ;
2021-01-12 06:11:05 +01:00
if ( ! rows || rows . length === 0 ) {
return undefined ;
}
return rows [ rows . length - 1 ] ;
}
2021-07-08 05:00:20 +02:00
function addClosedGroupEncryptionKeyPair ( groupPublicKey , keypair , instance ) {
2021-01-12 06:11:05 +01:00
const timestamp = Date . now ( ) ;
2021-07-08 05:00:20 +02:00
( globalInstance || instance )
. prepare (
` INSERT OR REPLACE INTO ${ CLOSED _GROUP _V2 _KEY _PAIRS _TABLE } (
2021-01-12 06:11:05 +01:00
groupPublicKey ,
timestamp ,
json
) values (
$groupPublicKey ,
$timestamp ,
$json
2021-07-08 05:00:20 +02:00
) ; `
)
. run ( {
groupPublicKey ,
timestamp ,
json : objectToJSON ( keypair ) ,
} ) ;
2021-01-12 06:11:05 +01:00
}
2021-07-08 05:00:20 +02:00
function removeAllClosedGroupEncryptionKeyPairs ( groupPublicKey ) {
globalInstance
. prepare (
` DELETE FROM ${ CLOSED _GROUP _V2 _KEY _PAIRS _TABLE } WHERE groupPublicKey = $ groupPublicKey `
)
. run ( {
groupPublicKey ,
} ) ;
2021-01-12 06:11:05 +01:00
}
2021-04-15 10:14:04 +02:00
/ * *
* Related to Opengroup V2
* /
2021-07-08 05:00:20 +02:00
function getAllV2OpenGroupRooms ( ) {
const rows = globalInstance . prepare ( ` SELECT json FROM ${ OPEN _GROUP _ROOMS _V2 _TABLE } ; ` ) . all ( ) ;
2021-04-15 10:14:04 +02:00
return map ( rows , row => jsonToObject ( row . json ) ) ;
}
2021-07-08 05:00:20 +02:00
function getV2OpenGroupRoom ( conversationId ) {
const row = globalInstance
. prepare ( ` SELECT * FROM ${ OPEN _GROUP _ROOMS _V2 _TABLE } WHERE conversationId = $ conversationId; ` )
. get ( {
conversationId ,
} ) ;
2021-04-15 10:14:04 +02:00
if ( ! row ) {
return null ;
}
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function getV2OpenGroupRoomByRoomId ( serverUrl , roomId ) {
const row = globalInstance
. prepare (
` SELECT * FROM ${ OPEN _GROUP _ROOMS _V2 _TABLE } WHERE serverUrl = $ serverUrl AND roomId = $ roomId; `
)
. get ( {
serverUrl ,
roomId ,
} ) ;
2021-04-15 10:14:04 +02:00
if ( ! row ) {
return null ;
}
return jsonToObject ( row . json ) ;
}
2021-07-08 05:00:20 +02:00
function saveV2OpenGroupRoom ( opengroupsv2Room ) {
2021-04-15 10:14:04 +02:00
const { serverUrl , roomId , conversationId } = opengroupsv2Room ;
2021-07-08 05:00:20 +02:00
globalInstance
. prepare (
` INSERT OR REPLACE INTO ${ OPEN _GROUP _ROOMS _V2 _TABLE } (
2021-04-15 10:14:04 +02:00
serverUrl ,
roomId ,
conversationId ,
json
) values (
$serverUrl ,
$roomId ,
$conversationId ,
$json
2021-07-08 05:00:20 +02:00
) `
)
. run ( {
serverUrl ,
roomId ,
conversationId ,
json : objectToJSON ( opengroupsv2Room ) ,
} ) ;
2021-04-15 10:14:04 +02:00
}
2021-07-08 05:00:20 +02:00
function removeV2OpenGroupRoom ( conversationId ) {
globalInstance
. prepare ( ` DELETE FROM ${ OPEN _GROUP _ROOMS _V2 _TABLE } WHERE conversationId = $ conversationId ` )
. run ( {
conversationId ,
} ) ;
2021-04-15 10:14:04 +02:00
}
2021-06-11 07:14:52 +02:00
2021-07-08 05:00:20 +02:00
function removeOneOpenGroupV1Message ( ) {
const row = globalInstance
. prepare (
` SELECT count(*) from ${ MESSAGES _TABLE } WHERE
conversationId LIKE 'publicChat:1@%' ; `
)
. get ( ) ;
2021-06-11 07:14:52 +02:00
const toRemoveCount = row [ 'count(*)' ] ;
if ( toRemoveCount <= 0 ) {
return 0 ;
}
console . warn ( 'left opengroupv1 message to remove: ' , toRemoveCount ) ;
2021-07-08 05:00:20 +02:00
const rowMessageIds = globalInstance
. prepare (
` SELECT id from ${ MESSAGES _TABLE } WHERE conversationId LIKE 'publicChat:1@%' ORDER BY id LIMIT 1; `
)
. all ( ) ;
2021-06-11 07:14:52 +02:00
const messagesIds = map ( rowMessageIds , r => r . id ) [ 0 ] ;
console . time ( 'removeOneOpenGroupV1Message' ) ;
2021-07-08 05:00:20 +02:00
removeMessage ( messagesIds ) ;
2021-06-11 07:14:52 +02:00
console . timeEnd ( 'removeOneOpenGroupV1Message' ) ;
return toRemoveCount - 1 ;
}
2021-12-22 01:20:29 +01:00
/ * *
* Only using this for development . Populate conversation and message tables .
* @ param { * } numConvosToAdd
* @ param { * } numMsgsToAdd
* /
function fillWithTestData ( numConvosToAdd , numMsgsToAdd ) {
const convoBeforeCount = globalInstance
. prepare ( ` SELECT count(*) from ${ CONVERSATIONS _TABLE } ; ` )
. get ( ) [ 'count(*)' ] ;
const lipsum =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac ornare lorem, non suscipit purus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse cursus aliquet velit a dignissim. Integer at nisi sed velit consequat dictum. Phasellus congue tellus ante. Ut rutrum hendrerit dapibus. Fusce luctus, ante nec interdum molestie, purus urna volutpat turpis, eget mattis lectus velit at velit. Praesent vel tellus turpis. Praesent eget purus at nisl blandit pharetra. Cras dapibus sem vitae rutrum dapibus. Vivamus vitae mi ante. Donec aliquam porta nibh, vel scelerisque orci condimentum sed. Proin in mattis ipsum, ac euismod sem. Donec malesuada sem nisl, at vehicula ante efficitur sed. Curabitur in sapien eros. Morbi tempor ante ut metus scelerisque condimentum. Integer sit amet tempus nulla. Vivamus imperdiet dui ac luctus vulputate. Sed a accumsan risus. Nulla facilisi. Nulla mauris dui, luctus in sagittis at, sodales id mauris. Integer efficitur viverra ex, ut dignissim eros tincidunt placerat. Sed facilisis gravida mauris in luctus. Fusce dapibus, est vitae tincidunt eleifend, justo odio porta dui, sed ultrices mi arcu vitae ante. Mauris ut libero erat. Nam ut mi quis ante tincidunt facilisis sit amet id enim. Vestibulum in molestie mi. In ac felis est. Vestibulum vel blandit ex. Morbi vitae viverra augue. Ut turpis quam, cursus quis ex a, convallis ullamcorper purus. Nam eget libero arcu. Integer fermentum enim nunc, non consequat urna fermentum condimentum. Nulla vitae malesuada est. Donec imperdiet tortor interdum malesuada feugiat. Integer pulvinar dui ex, eget tristique arcu mattis at. Nam eu neque eget mauris varius suscipit. Quisque ac enim vitae mauris laoreet congue nec sed justo. Curabitur fermentum quam eget est tincidunt, at faucibus lacus maximus. Donec auctor enim dolor, faucibus egestas diam consectetur sed. Donec eget rutrum arcu, at tempus mi. Fusce quis volutpat sapien. In aliquet fringilla purus. Ut eu nunc non augue lacinia ultrices at eget tortor. Maecenas pulvinar odio sit amet purus elementum, a vehicula lorem maximus. Pellentesque eu lorem magna. Vestibulum ut facilisis lorem. Proin et enim cursus, vulputate neque sit amet, posuere enim. Praesent faucibus tellus vel mi tincidunt, nec malesuada nibh malesuada. In laoreet sapien vitae aliquet sollicitudin.' ;
const msgBeforeCount = globalInstance . prepare ( ` SELECT count(*) from ${ MESSAGES _TABLE } ; ` ) . get ( ) [
'count(*)'
] ;
console . warn ( '==== fillWithTestData ====' ) ;
console . warn ( {
convoBeforeCount ,
msgBeforeCount ,
convoToAdd : numConvosToAdd ,
msgToAdd : numMsgsToAdd ,
} ) ;
const convosIdsAdded = [ ] ;
// eslint-disable-next-line no-plusplus
for ( let index = 0 ; index < numConvosToAdd ; index ++ ) {
const activeAt = Date . now ( ) - index ;
const id = Date . now ( ) - 1000 * index ;
const convoObjToAdd = {
active _at : activeAt ,
members : [ ] ,
profileName : ` ${ activeAt } ` ,
name : ` ${ activeAt } ` ,
id : ` 05 ${ id } ` ,
type : 'group' ,
} ;
convosIdsAdded . push ( id ) ;
try {
saveConversation ( convoObjToAdd ) ;
// eslint-disable-next-line no-empty
} catch ( e ) { }
}
console . warn ( 'convosIdsAdded' , convosIdsAdded ) ;
// eslint-disable-next-line no-plusplus
for ( let index = 0 ; index < numMsgsToAdd ; index ++ ) {
const activeAt = Date . now ( ) - index ;
const id = Date . now ( ) - 1000 * index ;
const lipsumStartIdx = Math . floor ( Math . random ( ) * lipsum . length ) ;
const lipsumLength = Math . floor ( Math . random ( ) * lipsum . length - lipsumStartIdx ) ;
const fakeBodyText = lipsum . substring ( lipsumStartIdx , lipsumStartIdx + lipsumLength ) ;
const convoId = convosIdsAdded [ Math . floor ( Math . random ( ) * convosIdsAdded . length ) ] ;
const msgObjToAdd = {
// body: `fake body ${activeAt}`,
body : ` fakeMsgIdx-spongebob- ${ index } ${ fakeBodyText } ${ activeAt } ` ,
conversationId : ` ${ convoId } ` ,
// eslint-disable-next-line camelcase
expires _at : 0 ,
hasAttachments : 0 ,
hasFileAttachments : 0 ,
hasVisualMediaAttachments : 0 ,
id : ` ${ id } ` ,
serverId : 0 ,
serverTimestamp : 0 ,
// eslint-disable-next-line camelcase
received _at : Date . now ( ) ,
schemaVersion : 5 ,
sent : 0 ,
// eslint-disable-next-line camelcase
sent _at : Date . now ( ) ,
source : ` ${ convoId } ` ,
sourceDevice : 1 ,
type : '%' ,
unread : 1 ,
expireTimer : 0 ,
expirationStartTimestamp : 0 ,
} ;
if ( convoId % 10 === 0 ) {
console . info ( 'uyo , convoId ' , { index , convoId } ) ;
}
try {
saveMessage ( msgObjToAdd ) ;
// eslint-disable-next-line no-empty
} catch ( e ) {
console . warn ( e ) ;
}
}
const convoAfterCount = globalInstance
. prepare ( ` SELECT count(*) from ${ CONVERSATIONS _TABLE } ; ` )
. get ( ) [ 'count(*)' ] ;
const msgAfterCount = globalInstance . prepare ( ` SELECT count(*) from ${ MESSAGES _TABLE } ; ` ) . get ( ) [
'count(*)'
] ;
console . warn ( { convoAfterCount , msgAfterCount } ) ;
return convosIdsAdded ;
}