/* eslint-disable no-unused-expressions */
import * as BetterSqlite3 from '@signalapp/better-sqlite3' ;
import {
ContactInfoSet ,
ContactsConfigWrapperNode ,
ConvoInfoVolatileWrapperNode ,
DisappearingMessageConversationType ,
} from 'libsession_util_nodejs' ;
import { isEmpty , isEqual , isFinite , isNumber } from 'lodash' ;
import { CONVERSATION_PRIORITIES } from '../../../models/conversationAttributes' ;
import { MESSAGES_TABLE , toSqliteBoolean } from '../../database_utility' ;
import { fromHexToArray } from '../../../session/utils/String' ;
import { checkTargetMigration , hasDebugEnvVariable } from '../utils' ;
import { ConfigDumpRow , CONFIG_DUMP_TABLE } from '../../../types/sqlSharedTypes' ;
const targetVersion = 34 ;
function fetchConfigDumps (
db : BetterSqlite3.Database ,
version : number ,
userPubkeyhex : string ,
variant : 'UserConfig' | 'ContactsConfig' | 'UserGroupsConfig' | 'ConvoInfoVolatileConfig'
) : ConfigDumpRow | null {
checkTargetMigration ( version , targetVersion ) ;
const configWrapperDumps = db
. prepare (
` SELECT * FROM ${ CONFIG_DUMP_TABLE } WHERE variant = $ variant AND publicKey = $ publicKey; `
)
. all ( { variant , publicKey : userPubkeyhex } ) as Array < ConfigDumpRow > ;
if ( ! configWrapperDumps || ! configWrapperDumps . length ) {
return null ;
}
// we can only have one dump with the current variants and our pubkey
return configWrapperDumps [ 0 ] ;
}
function writeConfigDumps (
db : BetterSqlite3.Database ,
version : number ,
userPubkeyhex : string ,
variant : 'UserConfig' | 'ContactsConfig' | 'UserGroupsConfig' | 'ConvoInfoVolatileConfig' ,
dump : Uint8Array
) {
checkTargetMigration ( version , targetVersion ) ;
db . prepare (
` INSERT OR REPLACE INTO ${ CONFIG_DUMP_TABLE } (
publicKey ,
variant ,
data
) values (
$publicKey ,
$variant ,
$data
) ; `
) . run ( {
publicKey : userPubkeyhex ,
variant ,
data : dump ,
} ) ;
}
/ * *
* This function returns a contactInfo for the wrapper to understand from the DB values .
* Created in this file so we can reuse it during the migration ( node side ) , and from the renderer side
* /
function getContactInfoFromDBValues ( {
id ,
dbApproved ,
dbApprovedMe ,
dbBlocked ,
dbName ,
dbNickname ,
priority ,
dbProfileUrl ,
dbProfileKey ,
dbCreatedAtSeconds ,
expirationType ,
expireTimer ,
} : {
id : string ;
dbApproved : boolean ;
dbApprovedMe : boolean ;
dbBlocked : boolean ;
dbNickname : string | undefined ;
dbName : string | undefined ;
priority : number ;
dbProfileUrl : string | undefined ;
dbProfileKey : string | undefined ;
dbCreatedAtSeconds : number ;
expirationType : string | undefined ;
expireTimer : number | undefined ;
} ) : ContactInfoSet {
const wrapperContact : ContactInfoSet = {
id ,
approved : ! ! dbApproved ,
approvedMe : ! ! dbApprovedMe ,
blocked : ! ! dbBlocked ,
priority ,
nickname : dbNickname ,
name : dbName ,
createdAtSeconds : dbCreatedAtSeconds ,
expirationMode : expirationType
? ( expirationType as DisappearingMessageConversationType )
: undefined ,
expirationTimerSeconds :
! ! expireTimer && isFinite ( expireTimer ) && expireTimer > 0 ? expireTimer * 1000 : 0 ,
} ;
if (
wrapperContact . profilePicture ? . url !== dbProfileUrl ||
! isEqual ( wrapperContact . profilePicture ? . key , dbProfileKey )
) {
wrapperContact . profilePicture = {
url : dbProfileUrl || null ,
key : dbProfileKey && ! isEmpty ( dbProfileKey ) ? fromHexToArray ( dbProfileKey ) : null ,
} ;
}
return wrapperContact ;
}
function insertContactIntoContactWrapper (
contact : any ,
blockedNumbers : Array < string > ,
contactsConfigWrapper : ContactsConfigWrapperNode | null , // set this to null to only insert into the convo volatile wrapper (i.e. for ourConvo case)
volatileConfigWrapper : ConvoInfoVolatileWrapperNode ,
db : BetterSqlite3.Database ,
version : number
) {
checkTargetMigration ( version , targetVersion ) ;
if ( contactsConfigWrapper !== null ) {
const dbApproved = ! ! contact . isApproved || false ;
const dbApprovedMe = ! ! contact . didApproveMe || false ;
const dbBlocked = blockedNumbers . includes ( contact . id ) ;
const priority = contact . priority || CONVERSATION_PRIORITIES . default ;
const wrapperContact = getContactInfoFromDBValues ( {
id : contact.id ,
dbApproved ,
dbApprovedMe ,
dbBlocked ,
dbName : contact.displayNameInProfile || undefined ,
dbNickname : contact.nickname || undefined ,
dbProfileKey : contact.profileKey || undefined ,
dbProfileUrl : contact.avatarPointer || undefined ,
priority ,
dbCreatedAtSeconds : Math.floor ( ( contact . active_at || Date . now ( ) ) / 1000 ) ,
expirationType : contact.expirationType || 'off' ,
expireTimer : contact.expirationTimer || 0 ,
} ) ;
try {
hasDebugEnvVariable && console . info ( 'Inserting contact into wrapper: ' , wrapperContact ) ;
contactsConfigWrapper . set ( wrapperContact ) ;
} catch ( e ) {
console . error (
` contactsConfigWrapper.set during migration failed with ${ e . message } for id: ${ contact . id } `
) ;
// the wrapper did not like something. Try again with just the boolean fields as it's most likely the issue is with one of the strings (which could be recovered)
try {
hasDebugEnvVariable && console . info ( 'Inserting edited contact into wrapper: ' , contact . id ) ;
contactsConfigWrapper . set (
getContactInfoFromDBValues ( {
id : contact.id ,
dbApproved ,
dbApprovedMe ,
dbBlocked ,
dbName : undefined ,
dbNickname : undefined ,
dbProfileKey : undefined ,
dbProfileUrl : undefined ,
priority : CONVERSATION_PRIORITIES.default ,
dbCreatedAtSeconds : Math.floor ( Date . now ( ) / 1000 ) ,
expirationType : 'off' ,
expireTimer : 0 ,
} )
) ;
} catch ( err2 ) {
// there is nothing else we can do here
console . error (
` contactsConfigWrapper.set during migration failed with ${ err2 . message } for id: ${ contact . id } . Skipping contact entirely `
) ;
}
}
}
try {
const rows = db
. prepare (
`
SELECT MAX ( COALESCE ( sent_at , 0 ) ) AS max_sent_at
FROM $ { MESSAGES_TABLE } WHERE
conversationId = $conversationId AND
unread = $unread ;
`
)
. get ( {
conversationId : contact.id ,
unread : toSqliteBoolean ( false ) , // we want to find the message read with the higher sentAt timestamp
} ) ;
const maxRead = rows ? . max_sent_at ;
const lastRead = isNumber ( maxRead ) && isFinite ( maxRead ) ? maxRead : 0 ;
hasDebugEnvVariable &&
console . info ( ` Inserting contact into volatile wrapper maxread: ${ contact . id } : ${ lastRead } ` ) ;
volatileConfigWrapper . set1o1 ( contact . id , lastRead , false ) ;
} catch ( e ) {
console . error (
` volatileConfigWrapper.set1o1 during migration failed with ${ e . message } for id: ${ contact . id } . skipping `
) ;
}
}
export const V34 = {
fetchConfigDumps ,
writeConfigDumps ,
insertContactIntoContactWrapper ,
} ;