Skip to content

Commit ad65733

Browse files
committed
Support web encryption
1 parent a7e0b43 commit ad65733

File tree

12 files changed

+161
-28
lines changed

12 files changed

+161
-28
lines changed

demos/supabase-todolist/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*.swp
66
.DS_Store
77
.atom/
8+
.build/
89
.buildlog/
910
.history
1011
.svn/
12+
.swiftpm/
1113
migrate_working_dir/
1214

1315
# IntelliJ related

demos/supabase-todolist/macos/Podfile.lock

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ PODS:
55
- path_provider_foundation (0.0.1):
66
- Flutter
77
- FlutterMacOS
8-
- powersync-sqlite-core (0.3.4)
8+
- powersync-sqlite-core (0.3.9)
99
- powersync_flutter_libs (0.0.1):
1010
- FlutterMacOS
11-
- powersync-sqlite-core (~> 0.3.4)
11+
- powersync-sqlite-core (~> 0.3.8)
1212
- shared_preferences_foundation (0.0.1):
1313
- Flutter
1414
- FlutterMacOS
@@ -64,16 +64,16 @@ EXTERNAL SOURCES:
6464
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
6565

6666
SPEC CHECKSUMS:
67-
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
67+
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
6868
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
69-
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
70-
powersync-sqlite-core: d029aa444d33acbb05b47f9f9757b2650578e2d3
71-
powersync_flutter_libs: 44829eda70d4f87c9271e963a54126ce19408d7c
72-
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
69+
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
70+
powersync-sqlite-core: 7515d321eb8e3c08b5259cdadb9d19b1876fe13a
71+
powersync_flutter_libs: 330d8309223a121ec15a7334d9edc105053e5f82
72+
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
7373
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
74-
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
75-
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
74+
sqlite3_flutter_libs: 03311aede9d32fb2d24e32bebb8cd01c3b2e6239
75+
url_launcher_macos: de10e46d8d8b9e3a7b8a133e8de92b104379f05e
7676

7777
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
7878

79-
COCOAPODS: 1.15.2
79+
COCOAPODS: 1.16.2
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import Cocoa
22
import FlutterMacOS
33

4-
@NSApplicationMain
4+
@main
55
class AppDelegate: FlutterAppDelegate {
66
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
77
return true
88
}
9+
10+
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
11+
return true
12+
}
913
}

packages/powersync_core/lib/src/open_factory.dart

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// To conditionally export an implementation for either web or "native" platforms
33
// The sqlite library uses dart:ffi which is not supported on web
44

5-
export './open_factory/open_factory_stub.dart'
5+
export 'open_factory/abstract_powersync_open_factory.dart'
6+
show powerSyncDefaultSqliteOptions;
7+
8+
export 'open_factory/open_factory_stub.dart'
69
// ignore: uri_does_not_exist
7-
if (dart.library.io) './open_factory/native/native_open_factory.dart'
10+
if (dart.library.io) 'open_factory/native/native_open_factory.dart'
811
// ignore: uri_does_not_exist
9-
if (dart.library.html) './open_factory/web/web_open_factory.dart';
12+
if (dart.library.html) 'open_factory/web/web_open_factory.dart';

packages/powersync_core/lib/src/open_factory/web/web_open_factory.dart

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import '../../web/worker_utils.dart';
1111

1212
/// Web implementation for [AbstractPowerSyncOpenFactory]
1313
class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
14-
implements WebSqliteOpenFactory {
14+
with WebSqliteOpenFactory {
1515
PowerSyncOpenFactory({
1616
required super.path,
1717
super.sqliteOptions,
@@ -26,6 +26,12 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
2626
);
2727
}
2828

29+
@override
30+
Future<ConnectToRecommendedResult> connectToWorker(
31+
WebSqlite sqlite, String name) {
32+
return sqlite.connectToRecommended(name);
33+
}
34+
2935
@override
3036
void enableExtension() {
3137
// No op for web
@@ -34,11 +40,11 @@ class PowerSyncOpenFactory extends AbstractPowerSyncOpenFactory
3440
@override
3541
Future<SqliteConnection> openConnection(SqliteOpenOptions options) async {
3642
var conn = await super.openConnection(options);
37-
for (final statement in super.pragmaStatements(options)) {
43+
for (final statement in pragmaStatements(options)) {
3844
await conn.execute(statement);
3945
}
4046

41-
return super.openConnection(options);
47+
return conn;
4248
}
4349

4450
@override

packages/powersync_core/lib/src/web/worker_utils.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,45 @@ import 'package:uuid/uuid.dart';
88

99
final class PowerSyncAsyncSqliteController extends AsyncSqliteController {
1010
@override
11-
Future<WorkerDatabase> openDatabase(
12-
WasmSqlite3 sqlite3, String path, String vfs) async {
13-
final asyncDb = await super.openDatabase(sqlite3, path, vfs);
11+
Future<WorkerDatabase> openDatabase(WasmSqlite3 sqlite3, String path,
12+
String vfs, JSAny? additionalData) async {
13+
final asyncDb =
14+
await super.openDatabase(sqlite3, path, vfs, additionalData);
1415
setupPowerSyncDatabase(asyncDb.database);
1516
return asyncDb;
1617
}
1718

19+
@override
20+
CommonDatabase openUnderlying(
21+
WasmSqlite3 sqlite3, String path, String vfs, JSAny? additionalData) {
22+
final options = additionalData == null
23+
? null
24+
: additionalData as PowerSyncAdditionalOpenOptions;
25+
if (options != null && options.useMultipleCiphersVfs) {
26+
vfs = 'multipleciphers-$vfs';
27+
}
28+
29+
return sqlite3.open(path, vfs: vfs);
30+
}
31+
1832
@override
1933
Future<JSAny?> handleCustomRequest(
2034
ClientConnection connection, JSAny? request) {
2135
throw UnimplementedError();
2236
}
2337
}
2438

39+
@JS()
40+
@anonymous
41+
extension type PowerSyncAdditionalOpenOptions._(JSObject _)
42+
implements JSObject {
43+
external factory PowerSyncAdditionalOpenOptions({
44+
required bool useMultipleCiphersVfs,
45+
});
46+
47+
external bool get useMultipleCiphersVfs;
48+
}
49+
2550
// Registers custom SQLite functions for the SQLite connection
2651
void setupPowerSyncDatabase(CommonDatabase database) {
2752
setupCommonDBFunctions(database);

packages/powersync_core/lib/web.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// Internal options used to customize how PowerSync opens databases on the web.
2+
library;
3+
4+
export 'src/web/worker_utils.dart' show PowerSyncAdditionalOpenOptions;
5+
export 'package:sqlite_async/sqlite3_web.dart';
6+
export 'package:sqlite_async/web.dart';

packages/powersync_sqlcipher/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.1.4-dev
2+
3+
- Support encryption on the web (through SQLite3 Multiple Ciphers).
4+
15
## 0.1.3
26

37
- Fix `statusStream` emitting the same sync status multiple times.

packages/powersync_sqlcipher/lib/powersync.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,27 @@
33
/// Use [PowerSyncSQLCipherOpenFactory] to open an encrypted database.
44
library;
55

6-
export 'src/sqlcipher.dart';
6+
import 'package:powersync_core/sqlite_async.dart';
7+
import 'package:powersync_sqlcipher/powersync.dart';
8+
79
export 'package:powersync_core/powersync_core.dart';
10+
11+
import 'src/stub.dart'
12+
if (dart.library.js_interop) 'src/web_encryption.dart'
13+
if (dart.library.ffi) 'src/sqlcipher.dart';
14+
15+
/// A factory for opening a database with SQLCipher encryption.
16+
/// An encryption [key] is required to open the database.
17+
abstract base class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
18+
PowerSyncSQLCipherOpenFactory.internal(
19+
{required super.path, required this.key, super.sqliteOptions});
20+
21+
factory PowerSyncSQLCipherOpenFactory(
22+
{required String path,
23+
required String key,
24+
SqliteOptions sqliteOptions = powerSyncDefaultSqliteOptions}) {
25+
return cipherFactory(path: path, key: key, options: sqliteOptions);
26+
}
27+
28+
final String key;
29+
}

packages/powersync_sqlcipher/lib/src/sqlcipher.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import 'package:powersync_core/powersync_core.dart';
21
import 'package:powersync_core/sqlite3_common.dart';
32
import 'package:powersync_core/sqlite3_open.dart' as sqlite3_open;
43
import 'package:powersync_core/sqlite_async.dart';
54
import 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';
65

7-
/// A factory for opening a database with SQLCipher encryption.
8-
/// An encryption [key] is required to open the database.
9-
class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
10-
PowerSyncSQLCipherOpenFactory(
11-
{required super.path, required this.key, super.sqliteOptions});
6+
import '../powersync.dart';
127

13-
final String key;
8+
final class _NativeCipherOpenFactory extends PowerSyncSQLCipherOpenFactory {
9+
_NativeCipherOpenFactory({
10+
required super.path,
11+
required super.key,
12+
super.sqliteOptions,
13+
}) : super.internal();
1414

1515
@override
1616
List<String> pragmaStatements(SqliteOpenOptions options) {
@@ -37,3 +37,11 @@ class PowerSyncSQLCipherOpenFactory extends PowerSyncOpenFactory {
3737
return db;
3838
}
3939
}
40+
41+
PowerSyncSQLCipherOpenFactory cipherFactory({
42+
required String path,
43+
required String key,
44+
required SqliteOptions options,
45+
}) {
46+
return _NativeCipherOpenFactory(path: path, key: key, sqliteOptions: options);
47+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import '../powersync.dart';
2+
import '../sqlite_async.dart';
3+
4+
PowerSyncSQLCipherOpenFactory cipherFactory({
5+
required String path,
6+
required String key,
7+
required SqliteOptions options,
8+
}) {
9+
throw UnsupportedError('Unsupported platform for powersync_sqlcipher');
10+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import 'package:powersync_core/sqlite_async.dart';
2+
import 'package:powersync_core/web.dart';
3+
4+
import '../powersync.dart';
5+
6+
final class _WebEncryptionFactory extends PowerSyncSQLCipherOpenFactory
7+
with WebSqliteOpenFactory {
8+
_WebEncryptionFactory({
9+
required super.path,
10+
required super.key,
11+
super.sqliteOptions,
12+
}) : super.internal();
13+
14+
@override
15+
List<String> pragmaStatements(SqliteOpenOptions options) {
16+
final basePragmaStatements = super.pragmaStatements(options);
17+
return [
18+
// Set the encryption key as the first statement
19+
"PRAGMA KEY = ${quoteString(key)}",
20+
// Include the default statements afterwards
21+
for (var statement in basePragmaStatements) statement
22+
];
23+
}
24+
25+
@override
26+
Future<ConnectToRecommendedResult> connectToWorker(
27+
WebSqlite sqlite, String name) async {
28+
return sqlite.connectToRecommended(
29+
name,
30+
additionalOptions: PowerSyncAdditionalOpenOptions(
31+
useMultipleCiphersVfs: true,
32+
),
33+
);
34+
}
35+
}
36+
37+
PowerSyncSQLCipherOpenFactory cipherFactory({
38+
required String path,
39+
required String key,
40+
required SqliteOptions options,
41+
}) {
42+
return _WebEncryptionFactory(path: path, key: key, sqliteOptions: options);
43+
}

0 commit comments

Comments
 (0)