@ -62,6 +62,30 @@
import { CapacitorSQLite } from '@capacitor-community/sqlite/electron/dist/plugin.js' ;
import { CapacitorSQLite } from '@capacitor-community/sqlite/electron/dist/plugin.js' ;
import { logger } from './logger' ;
import { logger } from './logger' ;
import * as crypto from 'crypto' ;
// Constants
const DEFAULT_ENDORSER_API_SERVER = 'https://api.timesafari.app' ;
// Utility function to delay execution
const delay = ( ms : number ) : Promise < void > = > {
return new Promise ( resolve = > setTimeout ( resolve , ms ) ) ;
} ;
// Utility function to convert Buffer to base64
const bufferToBase64 = ( buffer : Buffer ) : string = > {
return buffer . toString ( 'base64' ) ;
} ;
// Generate a random secret for the secret table
// Note: This is a temporary solution until better secure storage is implemented
const generateSecret = ( ) : string = > {
const randomBytes = crypto . randomBytes ( 32 ) ;
return bufferToBase64 ( randomBytes ) ;
} ;
// Constants for initial data
const INITIAL_SECRET = generateSecret ( ) ;
// Types for migration system
// Types for migration system
interface Migration {
interface Migration {
@ -116,18 +140,10 @@ const MAX_RETRY_ATTEMPTS = 3;
const RETRY_DELAY_MS = 1000 ;
const RETRY_DELAY_MS = 1000 ;
const LOCK_TIMEOUT_MS = 10000 ; // 10 seconds total timeout for locks
const LOCK_TIMEOUT_MS = 10000 ; // 10 seconds total timeout for locks
/ * *
* Utility function to delay execution
* @param ms Milliseconds to delay
* @returns Promise that resolves after the delay
* /
const delay = ( ms : number ) : Promise < void > = > {
return new Promise ( resolve = > setTimeout ( resolve , ms ) ) ;
} ;
// SQL Parsing Utilities
// SQL Parsing Utilities
interface ParsedSQL {
interface ParsedSQL {
statements : string [ ] ;
statements : string [ ] ;
parameters : any [ ] [ ] ;
errors : string [ ] ;
errors : string [ ] ;
warnings : string [ ] ;
warnings : string [ ] ;
}
}
@ -276,6 +292,7 @@ const validateSQLStatement = (statement: string): string[] => {
const parseSQL = ( sql : string ) : ParsedSQL = > {
const parseSQL = ( sql : string ) : ParsedSQL = > {
const result : ParsedSQL = {
const result : ParsedSQL = {
statements : [ ] ,
statements : [ ] ,
parameters : [ ] ,
errors : [ ] ,
errors : [ ] ,
warnings : [ ]
warnings : [ ]
} ;
} ;
@ -290,13 +307,16 @@ const parseSQL = (sql: string): ParsedSQL => {
. map ( s = > formatSQLStatement ( s ) )
. map ( s = > formatSQLStatement ( s ) )
. filter ( s = > s . length > 0 ) ;
. filter ( s = > s . length > 0 ) ;
// Validate each statement
// Process each statement
for ( const statement of rawStatements ) {
for ( const statement of rawStatements ) {
const errors = validateSQLStatement ( statement ) ;
const errors = validateSQLStatement ( statement ) ;
if ( errors . length > 0 ) {
if ( errors . length > 0 ) {
result . errors . push ( . . . errors . map ( e = > ` ${ e } in statement: ${ statement . substring ( 0 , 50 ) } ... ` ) ) ;
result . errors . push ( . . . errors . map ( e = > ` ${ e } in statement: ${ statement . substring ( 0 , 50 ) } ... ` ) ) ;
} else {
} else {
result . statements . push ( statement ) ;
// Extract any parameterized values (e.g., from INSERT statements)
const { processedStatement , params } = extractParameters ( statement ) ;
result . statements . push ( processedStatement ) ;
result . parameters . push ( params ) ;
}
}
}
}
@ -323,6 +343,35 @@ const parseSQL = (sql: string): ParsedSQL => {
return result ;
return result ;
} ;
} ;
// Helper to extract parameters from SQL statements
const extractParameters = ( statement : string ) : { processedStatement : string ; params : any [ ] } = > {
const params : any [ ] = [ ] ;
let processedStatement = statement ;
// Handle INSERT statements with VALUES
if ( statement . toLowerCase ( ) . includes ( 'insert into' ) && statement . includes ( 'values' ) ) {
const valuesMatch = statement . match ( /values\s*\((.*?)\)/i ) ;
if ( valuesMatch ) {
const values = valuesMatch [ 1 ] . split ( ',' ) . map ( v = > v . trim ( ) ) ;
const placeholders = values . map ( ( _ , i ) = > '?' ) . join ( ', ' ) ;
processedStatement = statement . replace ( /values\s*\(.*?\)/i , ` VALUES ( ${ placeholders } ) ` ) ;
// Extract actual values
values . forEach ( v = > {
if ( v . startsWith ( "'" ) && v . endsWith ( "'" ) ) {
params . push ( v . slice ( 1 , - 1 ) ) ; // Remove quotes
} else if ( ! isNaN ( Number ( v ) ) ) {
params . push ( Number ( v ) ) ;
} else {
params . push ( v ) ;
}
} ) ;
}
}
return { processedStatement , params } ;
} ;
// Add version conflict detection
// Add version conflict detection
const validateMigrationVersions = ( migrations : Migration [ ] ) : void = > {
const validateMigrationVersions = ( migrations : Migration [ ] ) : void = > {
const versions = new Set < number > ( ) ;
const versions = new Set < number > ( ) ;
@ -384,7 +433,7 @@ const MIGRATIONS: Migration[] = [
{
{
version : 2 ,
version : 2 ,
name : '002_secret_and_settings' ,
name : '002_secret_and_settings' ,
description : 'Add secret, settings, contacts, logs, and temp tables' ,
description : 'Add secret, settings, contacts, logs, and temp tables with initial data ' ,
sql : `
sql : `
-- Secret table for storing encryption keys
-- Secret table for storing encryption keys
-- Note : This is a temporary solution until better secure storage is implemented
-- Note : This is a temporary solution until better secure storage is implemented
@ -393,6 +442,9 @@ const MIGRATIONS: Migration[] = [
secretBase64 TEXT NOT NULL
secretBase64 TEXT NOT NULL
) ;
) ;
-- Insert initial secret
INSERT INTO secret ( id , secretBase64 ) VALUES ( 1 , '${INITIAL_SECRET}' ) ;
-- Settings table for user preferences and app state
-- Settings table for user preferences and app state
CREATE TABLE IF NOT EXISTS settings (
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT ,
id INTEGER PRIMARY KEY AUTOINCREMENT ,
@ -426,6 +478,9 @@ const MIGRATIONS: Migration[] = [
webPushServer TEXT
webPushServer TEXT
) ;
) ;
-- Insert default API server setting
INSERT INTO settings ( id , apiServer ) VALUES ( 1 , '${DEFAULT_ENDORSER_API_SERVER}' ) ;
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings ( accountDid ) ;
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings ( accountDid ) ;
-- Contacts table for user connections
-- Contacts table for user connections
@ -458,11 +513,15 @@ const MIGRATIONS: Migration[] = [
) ;
) ;
` ,
` ,
rollback : `
rollback : `
DROP TABLE IF EXISTS secret ;
-- Drop tables in reverse order to avoid foreign key issues
DROP TABLE IF EXISTS settings ;
DROP TABLE IF EXISTS contacts ;
DROP TABLE IF EXISTS logs ;
DROP TABLE IF EXISTS temp ;
DROP TABLE IF EXISTS temp ;
DROP TABLE IF EXISTS logs ;
DROP INDEX IF EXISTS idx_contacts_name ;
DROP INDEX IF EXISTS idx_contacts_did ;
DROP TABLE IF EXISTS contacts ;
DROP INDEX IF EXISTS idx_settings_accountDid ;
DROP TABLE IF EXISTS settings ;
DROP TABLE IF EXISTS secret ;
`
`
}
}
] ;
] ;
@ -705,8 +764,8 @@ const ensureMigrationsTable = async (
}
}
} ;
} ;
// Update the parseMigrationStatements function to use the new parser
// Update parseMigrationStatements to return ParsedSQL type
const parseMigrationStatements = ( sql : string ) : string [ ] = > {
const parseMigrationStatements = ( sql : string ) : ParsedSQL = > {
const parsed = parseSQL ( sql ) ;
const parsed = parseSQL ( sql ) ;
if ( parsed . errors . length > 0 ) {
if ( parsed . errors . length > 0 ) {
@ -717,7 +776,7 @@ const parseMigrationStatements = (sql: string): string[] => {
logger . warn ( 'SQL parsing warnings:' , parsed . warnings ) ;
logger . warn ( 'SQL parsing warnings:' , parsed . warnings ) ;
}
}
return parsed . statements ;
return parsed ;
} ;
} ;
// Add debug helper function
// Add debug helper function
@ -780,7 +839,9 @@ const executeMigration = async (
migration : Migration
migration : Migration
) : Promise < MigrationResult > = > {
) : Promise < MigrationResult > = > {
const startTime = Date . now ( ) ;
const startTime = Date . now ( ) ;
const statements = parseMigrationStatements ( migration . sql ) ;
// Parse SQL and extract any parameterized values
const { statements , parameters } = parseMigrationStatements ( migration . sql ) ;
let transactionStarted = false ;
let transactionStarted = false ;
logger . info ( ` Starting migration ${ migration . version } : ${ migration . name } ` , {
logger . info ( ` Starting migration ${ migration . version } : ${ migration . name } ` , {
@ -788,7 +849,8 @@ const executeMigration = async (
version : migration.version ,
version : migration.version ,
name : migration.name ,
name : migration.name ,
description : migration.description ,
description : migration.description ,
statementCount : statements.length
statementCount : statements.length ,
hasParameters : parameters.length > 0
}
}
} ) ;
} ) ;
@ -822,13 +884,15 @@ const executeMigration = async (
) ;
) ;
try {
try {
// Execute each statement with retry
// Execute each statement with retry and parameters if any
for ( let i = 0 ; i < statements . length ; i ++ ) {
for ( let i = 0 ; i < statements . length ; i ++ ) {
const statement = statements [ i ] ;
const statement = statements [ i ] ;
const statementParams = parameters [ i ] || [ ] ;
await executeWithRetry (
await executeWithRetry (
plugin ,
plugin ,
database ,
database ,
( ) = > executeSingleStatement ( plugin , database , statement ) ,
( ) = > executeSingleStatement ( plugin , database , statement , statementParams ) ,
` executeStatement_ ${ i + 1 } `
` executeStatement_ ${ i + 1 } `
) ;
) ;
}
}