Skip to content

Commit 885266a

Browse files
committed
Setup sqlcipher and upgrade op-sqlite
1 parent f88a162 commit 885266a

File tree

3 files changed

+42
-19
lines changed

3 files changed

+42
-19
lines changed

packages/powersync-op-sqlite/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"access": "public"
6666
},
6767
"peerDependencies": {
68-
"@op-engineering/op-sqlite": "^9.1.3",
68+
"@op-engineering/op-sqlite": "^9.2.1",
6969
"@powersync/common": "workspace:^1.20.0",
7070
"react": "*",
7171
"react-native": "*"
@@ -75,7 +75,7 @@
7575
"async-lock": "^1.4.0"
7676
},
7777
"devDependencies": {
78-
"@op-engineering/op-sqlite": "^9.1.3",
78+
"@op-engineering/op-sqlite": "^9.2.1",
7979
"@react-native/eslint-config": "^0.73.1",
8080
"@types/async-lock": "^1.4.0",
8181
"@types/react": "^18.2.44",

packages/powersync-op-sqlite/src/db/OPSqliteAdapter.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import { ANDROID_DATABASE_PATH, IOS_LIBRARY_PATH, open, type DB } from '@op-engi
1111
import Lock from 'async-lock';
1212
import { OPSQLiteConnection } from './OPSQLiteConnection';
1313
import { NativeModules, Platform } from 'react-native';
14-
import { DEFAULT_SQLITE_OPTIONS, SqliteOptions } from './SqliteOptions';
14+
import { SqliteOptions } from './SqliteOptions';
15+
import Logger, { ILogger } from 'js-logger';
1516

1617
/**
1718
* Adapter for React Native Quick SQLite
@@ -39,6 +40,8 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
3940

4041
protected writeConnection: OPSQLiteConnection | null;
4142

43+
protected logger: ILogger = Logger.get('OPSQLiteAdapter');
44+
4245
constructor(protected options: OPSQLiteAdapterOptions) {
4346
super();
4447
this.name = this.options.name;
@@ -50,15 +53,10 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
5053
}
5154

5255
protected async init() {
53-
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous } = this.options.sqliteOptions;
54-
// const { dbFilename, dbLocation } = this.options;
56+
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous, encryptionKey } = this.options.sqliteOptions;
5557
const dbFilename = this.options.name;
56-
//This is needed because an undefined dbLocation will cause the open function to fail
57-
const location = this.getDbLocation(this.options.dbLocation);
58-
const DB: DB = open({
59-
name: dbFilename,
60-
location: location
61-
});
58+
59+
const DB: DB = this.openDatabase(dbFilename, encryptionKey);
6260

6361
const statements: string[] = [
6462
`PRAGMA busy_timeout = ${lockTimeoutMs}`,
@@ -90,7 +88,7 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
9088
for (let i = 0; i < READ_CONNECTIONS; i++) {
9189
// Workaround to create read-only connections
9290
let dbName = './'.repeat(i + 1) + dbFilename;
93-
const conn = await this.openConnection(location, dbName);
91+
const conn = await this.openConnection(dbName);
9492
await conn.execute('PRAGMA query_only = true');
9593
this.readConnections.push(conn);
9694
}
@@ -105,11 +103,8 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
105103
});
106104
}
107105

108-
protected async openConnection(dbLocation: string, filenameOverride?: string): Promise<OPSQLiteConnection> {
109-
const DB: DB = open({
110-
name: filenameOverride ?? this.options.name,
111-
location: dbLocation
112-
});
106+
protected async openConnection(filenameOverride?: string): Promise<OPSQLiteConnection> {
107+
const DB: DB = this.openDatabase(filenameOverride ?? this.options.name, this.options.sqliteOptions.encryptionKey);
113108

114109
//Load extension for all connections
115110
this.loadExtension(DB);
@@ -129,6 +124,27 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
129124
}
130125
}
131126

127+
private openDatabase(dbFilename: string, encryptionKey?: string): DB {
128+
//This is needed because an undefined/null dbLocation will cause the open function to fail
129+
const location = this.getDbLocation(this.options.dbLocation);
130+
if (encryptionKey && encryptionKey === '') {
131+
throw new Error('Encryption key cannot be empty when using SQLCipher');
132+
}
133+
//Simarlily if the encryption key is undefined/null when using SQLCipher it will cause the open function to fail
134+
if (encryptionKey) {
135+
return open({
136+
name: dbFilename,
137+
location: location,
138+
encryptionKey: encryptionKey
139+
});
140+
} else {
141+
return open({
142+
name: dbFilename,
143+
location: location
144+
});
145+
}
146+
}
147+
132148
private loadExtension(DB: DB) {
133149
if (Platform.OS === 'ios') {
134150
const bundlePath: string = NativeModules.PowerSyncOpSqlite.getBundlePath();

packages/powersync-op-sqlite/src/db/SqliteOptions.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export interface SqliteOptions {
2323
* Set to null or zero to fail immediately when the database is locked.
2424
*/
2525
lockTimeoutMs?: number;
26+
27+
/**
28+
* Encryption key for the database.
29+
* If set, the database will be encrypted using SQLCipher.
30+
*/
31+
encryptionKey?: string;
2632
}
2733

2834
// SQLite journal mode. Set on the primary connection.
@@ -36,19 +42,20 @@ enum SqliteJournalMode {
3642
truncate = 'TRUNCATE',
3743
persist = 'PERSIST',
3844
memory = 'MEMORY',
39-
off = 'OFF',
45+
off = 'OFF'
4046
}
4147

4248
// SQLite file commit mode.
4349
enum SqliteSynchronous {
4450
normal = 'NORMAL',
4551
full = 'FULL',
46-
off = 'OFF',
52+
off = 'OFF'
4753
}
4854

4955
export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
5056
journalMode: SqliteJournalMode.wal,
5157
synchronous: SqliteSynchronous.normal,
5258
journalSizeLimit: 6 * 1024 * 1024,
5359
lockTimeoutMs: 30000,
60+
encryptionKey: null
5461
};

0 commit comments

Comments
 (0)