Skip to content

Commit 3133398

Browse files
Use dynamic extension loading for Android.
1 parent 2bea72c commit 3133398

File tree

11 files changed

+59
-89
lines changed

11 files changed

+59
-89
lines changed

demos/example-capacitor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"preview": "vite preview"
2020
},
2121
"dependencies": {
22-
"@capacitor-community/sqlite": "^7.0.1",
22+
"@capacitor-community/sqlite": "^7.0.2",
2323
"@capacitor/android": "^7.4.3",
2424
"@capacitor/core": "latest",
2525
"@capacitor/ios": "^7.4.3",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
require 'json'
2+
3+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4+
5+
Pod::Spec.new do |s|
6+
s.name = 'PowerSyncCapacitor'
7+
version = package['version']
8+
if version.include?('-dev')
9+
s.version = '0.0.0'
10+
else
11+
s.version = version
12+
end
13+
s.summary = package['description']
14+
s.license = package['license']
15+
s.homepage = package['repository']['url']
16+
s.author = package['author']
17+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
18+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
19+
s.dependency "SQLCipher", "~> 4.0"
20+
s.public_header_files = 'ios/Sources/CPowerSyncPlugin/include/*.h'
21+
s.ios.deployment_target = '14.0'
22+
s.dependency 'Capacitor'
23+
s.swift_version = '5.1'
24+
s.dependency "powersync-sqlite-core", "~> 0.4.6"
25+
s.xcconfig = {
26+
'OTHER_CFLAGS' => '$(inherited) -DSQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION=1',
27+
'HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_ROOT)/SQLCipher"'
28+
}
29+
end

packages/capacitor/PowersyncCapacitor.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ require 'json'
33
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
44

55
Pod::Spec.new do |s|
6-
s.name = 'PowersyncCapacitor'
6+
s.name = 'PowerSyncCapacitor'
77
version = package['version']
88
if version.include?('-dev')
99
s.version = '0.0.0'
@@ -21,7 +21,7 @@ Pod::Spec.new do |s|
2121
s.ios.deployment_target = '14.0'
2222
s.dependency 'Capacitor'
2323
s.swift_version = '5.1'
24-
s.dependency "powersync-sqlite-core", "~> 0.4.4"
24+
s.dependency "powersync-sqlite-core", "~> 0.4.6"
2525
s.xcconfig = {
2626
'OTHER_CFLAGS' => '$(inherited) -DSQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION=1',
2727
'HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_ROOT)/SQLCipher"'

packages/capacitor/android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ ext {
33
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
44
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
55
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6-
powerSyncCoreVersion = project.hasProperty('powerSyncCoreVersion') ? rootProject.ext.powerSyncCoreVersion : '0.4.5'
6+
powerSyncCoreVersion = project.hasProperty('powerSyncCoreVersion') ? rootProject.ext.powerSyncCoreVersion : '0.4.6'
77
}
88

99
buildscript {

packages/capacitor/android/src/main/cpp/powersync_capacitor_jni.c

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,8 @@
11
#include <jni.h>
2-
#include <dlfcn.h>
3-
#include <android/log.h>
4-
5-
#define LOG_TAG "PowerSyncNative"
6-
7-
// Forward declarations (no need for full struct definitions)
8-
typedef struct sqlite3 sqlite3;
9-
typedef struct sqlite3_api_routines sqlite3_api_routines;
10-
11-
typedef int (*sqlite3_auto_extension_fn)(void (*xEntryPoint)(void));
12-
13-
extern int sqlite3_powersync_init(
14-
sqlite3 *db, // Database handle
15-
const char **pzErrMsg, // Error message out parameter
16-
const struct sqlite3_api_routines *pThunk // SQLite API routines
17-
);
182

3+
// SQLCipher enables dynamic loading of extensions. We don't need to do anything here.
194
int register_powersync(void) {
20-
void *handle = dlopen("libsqlcipher.so", RTLD_LAZY);
21-
if (!handle) {
22-
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Failed to dlopen libsqlcipher.so, trying process handle");
23-
// Try loading from the process itself
24-
handle = dlopen(NULL, RTLD_LAZY);
25-
if (!handle) {
26-
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to dlopen process handle. SQLCipher symbols not found.");
27-
return -1;
28-
}
29-
} else {
30-
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Successfully loaded libsqlcipher.so");
31-
}
32-
33-
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Resolving sqlite3_auto_extension symbol");
34-
sqlite3_auto_extension_fn auto_ext = (sqlite3_auto_extension_fn)dlsym(handle, "sqlite3_auto_extension");
35-
if (!auto_ext) {
36-
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to resolve sqlite3_auto_extension symbol");
37-
}
38-
39-
40-
if (!auto_ext) {
41-
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Required symbols not found. Aborting registration.");
42-
dlclose(handle);
43-
return -2;
44-
}
45-
46-
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Registering powersync extension");
47-
int result = auto_ext((void(*)(void))sqlite3_powersync_init);
48-
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "register_powersync result: %d", result);
49-
dlclose(handle);
50-
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Completed register_powersync");
51-
return result;
5+
return 0;
526
}
537

548
// JNI wrapper

packages/capacitor/android/src/main/java/com/powersync/capacitor/PowerSync.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
package com.powersync.capacitor;
22

3-
import com.getcapacitor.Logger;
4-
53
public class PowerSync {
64
static {
75
System.loadLibrary("powersync_capacitor");
8-
// Ensures we load this early before registering.
9-
System.loadLibrary("sqlcipher");
106
}
117

128
public static native int registerPowersync();

packages/capacitor/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"ios/Sources",
1818
"ios/Tests",
1919
"Package.swift",
20-
"PowersyncCapacitor.podspec"
20+
"PowerSyncCapacitor.podspec"
2121
],
2222
"author": "",
2323
"license": "Apache-2.0",
@@ -48,6 +48,7 @@
4848
"build:prod": "pnpm build",
4949
"clean": "rimraf ./dist",
5050
"watch": "tsc --watch",
51+
"test": "pnpm verify:android",
5152
"prepublishOnly": "pnpm build",
5253
"test:exports": "attw --pack ."
5354
},

packages/capacitor/src/adapter/CapacitorSQLiteAdapter.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,26 @@ export class CapacitorSQLiteAdapter extends BaseObserver<DBAdapterListener> impl
8989
await this._writeConnection.open();
9090

9191
const { cacheSizeKb, journalSizeLimit, synchronous } = { ...DEFAULT_SQLITE_OPTIONS, ...this.options.sqliteOptions };
92-
await this.writeConnection.query("SELECT powersync_update_hooks('install')");
92+
9393
await this.writeConnection.query('PRAGMA journal_mode = WAL');
9494
await this.writeConnection.query(`PRAGMA journal_size_limit = ${journalSizeLimit}`);
9595
await this.writeConnection.query(`PRAGMA temp_store = memory`);
9696
await this.writeConnection.query(`PRAGMA synchronous = ${synchronous}`);
9797
await this.writeConnection.query(`PRAGMA cache_size = -${cacheSizeKb}`);
9898

9999
await this._readConnection.open();
100+
101+
const platform = Capacitor.getPlatform();
102+
if (platform == 'android') {
103+
/**
104+
* SQLCipher for Android enables dynamic loading of extensions.
105+
* On iOS we use a static auto extension registration.
106+
*/
107+
const extensionQuery = "SELECT load_extension('libpowersync.so', 'sqlite3_powersync_init')";
108+
await this.writeConnection.query(extensionQuery);
109+
await this.readConnection.query(extensionQuery);
110+
}
111+
await this.writeConnection.query("SELECT powersync_update_hooks('install')");
100112
}
101113

102114
async close(): Promise<void> {

packages/capacitor/src/plugin/PowerSyncPlugin.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
export type RegistrationResponse = {
22
/**
33
* Zero for successful registration, non-zero for failure.
4-
* - [Android] -1: SQLCipher library not found.
5-
* - [Android] -2: Required symbols not found.
6-
* - The result of sqlite3_auto_extension
74
*/
85
responseCode: number;
96
};
107

118
export const messageForErrorCode = (code: number): string => {
129
switch (code) {
13-
case -1:
14-
return '[Android] SQLCipher library not found';
15-
case -2:
16-
return '[Android] Required symbols not found';
1710
case 0:
1811
return 'Success';
1912
default:

packages/capacitor/src/sync/CapacitorSyncImplementation.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
1-
import {
2-
AbstractStreamingSyncImplementation,
3-
AbstractStreamingSyncImplementationOptions,
4-
LockOptions
5-
} from '@powersync/web';
1+
import { AbstractStreamingSyncImplementation, LockOptions } from '@powersync/web';
62
import Lock from 'async-lock';
73

84
export class CapacitorStreamingSyncImplementation extends AbstractStreamingSyncImplementation {
9-
protected lock: Lock;
10-
constructor(options: AbstractStreamingSyncImplementationOptions) {
11-
// Super will store and provide default values for options
12-
super(options);
13-
this.lock = new Lock();
14-
}
5+
static GLOBAL_LOCK = new Lock();
156

167
async obtainLock<T>(lockOptions: LockOptions<T>): Promise<T> {
178
const identifier = `streaming-sync-${lockOptions.type}-${this.options.identifier}`;
18-
return this.lock.acquire(identifier, async () => {
9+
return CapacitorStreamingSyncImplementation.GLOBAL_LOCK.acquire(identifier, async () => {
1910
if (lockOptions.signal?.aborted) {
2011
throw new Error('Aborted');
2112
}

0 commit comments

Comments
 (0)