From 1cf482f3ab3f989b1788f416dc5748eaaf0a48f2 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 03:37:10 +0200 Subject: [PATCH 1/8] fix(apple-network): fix port handling and error messages --- Makefile | 18 ++++++++++++++---- src/network.m | 15 +++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0820b4a..eaa5af9 100644 --- a/Makefile +++ b/Makefile @@ -136,6 +136,16 @@ endif T_LDFLAGS += -fprofile-arcs -ftest-coverage endif +# Native network support only for Apple platforms +ifdef NATIVE_NETWORK + RELEASE_OBJ += $(patsubst %.m, $(BUILD_RELEASE)/%_m.o, $(notdir $(wildcard $(SRC_DIR)/*.m))) + LDFLAGS += -framework Foundation + CFLAGS += -DCLOUDSYNC_OMIT_CURL + +$(BUILD_RELEASE)/%_m.o: %.m + $(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@ +endif + # Windows .def file generation $(DEF_FILE): ifeq ($(PLATFORM),windows) @@ -314,15 +324,15 @@ clean: help: @echo "SQLite Sync Extension Makefile" @echo "Usage:" - @echo " make [PLATFORM=platform] [ARCH=arch] [ANDROID_NDK=\$$ANDROID_HOME/ndk/26.1.10909125] [target]" + @echo " make [PLATFORM=platform] [ARCH=arch] [ANDROID_NDK=\$$ANDROID_HOME/ndk/26.1.10909125] [NATIVE_NETWORK=ON] [target]" @echo "" @echo "Platforms:" @echo " linux (default on Linux)" - @echo " macos (default on macOS)" + @echo " macos (default on macOS - can be compiled with native network support)" @echo " windows (default on Windows)" @echo " android (needs ARCH to be set to x86_64 or arm64-v8a and ANDROID_NDK to be set)" - @echo " ios (only on macOS)" - @echo " isim (only on macOS)" + @echo " ios (only on macOS - can be compiled with native network support)" + @echo " isim (only on macOS - can be compiled with native network support)" @echo " wasm (needs wabt[brew install wabt/sudo apt install wabt])" @echo "" @echo "Targets:" diff --git a/src/network.m b/src/network.m index c9a9dc9..1c5cee0 100644 --- a/src/network.m +++ b/src/network.m @@ -58,7 +58,7 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co } char *site_id = network_data_get_siteid(data); - char *port_or_default = (port) ? (char *)port.UTF8String : CLOUDSYNC_DEFAULT_ENDPOINT_PORT; + char *port_or_default = (port && strcmp(port.UTF8String, "8860") != 0) ? (char *)port.UTF8String : CLOUDSYNC_DEFAULT_ENDPOINT_PORT; NSString *check_endpoint = [NSString stringWithFormat:@"%s://%s:%s/%s%s/%s", scheme.UTF8String, host.UTF8String, port_or_default, CLOUDSYNC_ENDPOINT_PREFIX, database.UTF8String, site_id]; NSString *upload_endpoint = [NSString stringWithFormat: @"%s://%s:%s/%s%s/%s/%s", scheme.UTF8String, host.UTF8String, port_or_default, CLOUDSYNC_ENDPOINT_PREFIX, database.UTF8String, site_id, CLOUDSYNC_ENDPOINT_UPLOAD]; @@ -188,7 +188,18 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, // return error NETWORK_RESULT result = {}; - NSString *msg = (responseError) ? [responseError localizedDescription] : [NSString stringWithCString:"Unknown network URL" encoding:NSUTF8StringEncoding]; + NSString *msg; + if (responseError) { + msg = [responseError localizedDescription]; + } else if (responseData && [responseData length] > 0) { + // Use the actual response body as the error message + msg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; + if (!msg) { + msg = [NSString stringWithCString:"Invalid error response encoding" encoding:NSUTF8StringEncoding]; + } + } else { + msg = [NSString stringWithFormat:@"HTTP %ld error", (long)statusCode]; + } result.code = CLOUDSYNC_NETWORK_ERROR; result.buffer = (char *)msg.UTF8String; result.xdata = (void *)CFBridgingRetain(msg); From 805699abb5813ba2c017bd9deb6eb0462d3962d2 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 03:58:21 +0200 Subject: [PATCH 2/8] fix header comments error/typos --- src/dbutils.c | 2 +- src/network.m | 2 +- src/wasm.c | 7 +++++++ test/main.c | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/dbutils.c b/src/dbutils.c index dd7e13a..9edd10f 100644 --- a/src/dbutils.c +++ b/src/dbutils.c @@ -1,6 +1,6 @@ // // dbutils.c -// cloudsync_test +// cloudsync // // Created by Marco Bambini on 23/09/24. // diff --git a/src/network.m b/src/network.m index 1c5cee0..0bbc1e1 100644 --- a/src/network.m +++ b/src/network.m @@ -1,6 +1,6 @@ // // network.m -// cloudsync_network_test +// cloudsync // // Created by Marco Bambini on 23/05/25. // diff --git a/src/wasm.c b/src/wasm.c index 9c53835..5f60ee3 100644 --- a/src/wasm.c +++ b/src/wasm.c @@ -1,3 +1,10 @@ +// +// wasm.c +// cloudsync +// +// Created by Gioele Cantoni on 25/06/25. +// + #ifdef SQLITE_WASM_EXTRA_INIT #define CLOUDSYNC_OMIT_CURL diff --git a/test/main.c b/test/main.c index 2ed92a2..b285f7c 100644 --- a/test/main.c +++ b/test/main.c @@ -1,6 +1,6 @@ // // main.c -// sqlite-sync +// cloudsync // // Created by Gioele Cantoni on 05/06/25. // Set CONNECTION_STRING, APIKEY and WEBLITE environment variables before running this test. From 05c2f13af09738e1bb8ffe59150b1e2c318b8c07 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 04:48:28 +0200 Subject: [PATCH 3/8] fix(apple-network): retain response to avoid it getting deallocated --- src/network.m | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/network.m b/src/network.m index 0bbc1e1..eb9c504 100644 --- a/src/network.m +++ b/src/network.m @@ -147,15 +147,20 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, } __block NSData *responseData = nil; - __block NSError *responseError = nil; + __block NSString *responseError = nil; __block NSInteger statusCode = 0; + __block NSInteger errorCode = 0; dispatch_semaphore_t sema = dispatch_semaphore_create(0); NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - responseData = data; - responseError = error; + // Retain data to not get it deallocated, release it before returning + responseData = [data retain]; + if (error) { + responseError = [error localizedDescription]; + errorCode = [error code]; + } if ([response isKindOfClass:[NSHTTPURLResponse class]]) { statusCode = [(NSHTTPURLResponse *)response statusCode]; } @@ -168,7 +173,10 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, if (!responseError && (statusCode >= 200 && statusCode < 300)) { size_t len = [responseData length]; // check if OK should be returned - if (len == 0) return (NETWORK_RESULT){CLOUDSYNC_NETWORK_OK, NULL, 0, NULL, NULL}; + if (len == 0) { + [responseData release]; + return (NETWORK_RESULT){CLOUDSYNC_NETWORK_OK, NULL, 0, NULL, NULL}; + } // otherwise return a buffer NETWORK_RESULT result = {}; @@ -183,19 +191,21 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, } result.blen = len; result.xfree = network_buffer_cleanup; + + [responseData release]; return result; } // return error NETWORK_RESULT result = {}; - NSString *msg; + NSString *msg = nil; if (responseError) { - msg = [responseError localizedDescription]; + msg = responseError; } else if (responseData && [responseData length] > 0) { // Use the actual response body as the error message msg = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]; if (!msg) { - msg = [NSString stringWithCString:"Invalid error response encoding" encoding:NSUTF8StringEncoding]; + msg = [NSString stringWithFormat:@"HTTP %ld error", (long)statusCode]; } } else { msg = [NSString stringWithFormat:@"HTTP %ld error", (long)statusCode]; @@ -204,6 +214,8 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, result.buffer = (char *)msg.UTF8String; result.xdata = (void *)CFBridgingRetain(msg); result.xfree = network_buffer_cleanup; - result.blen = (responseError) ? (size_t)responseError.code : (size_t)statusCode; + result.blen = responseError ? (size_t)errorCode : (size_t)statusCode; + + [responseData release]; return result; } From bfa103606bc6cce8ffc3e7320f5a1bf46d6d32d4 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 05:01:19 +0200 Subject: [PATCH 4/8] fix(wasm-network): get error message from response body --- src/wasm.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/wasm.c b/src/wasm.c index 5f60ee3..2fc324f 100644 --- a/src/wasm.c +++ b/src/wasm.c @@ -103,7 +103,6 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint, } if (fetch->status >= 200 && fetch->status < 300) { - if (blen > 0 && buffer) { char *buf = (char*)malloc(blen + 1); if (buf) { @@ -119,9 +118,18 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint, result.code = CLOUDSYNC_NETWORK_ERROR; if (fetch->statusText && fetch->statusText[0]) { result.buffer = strdup(fetch->statusText); + result.blen = sizeof(fetch->statusText); + result.xfree = free; + } else if (blen > 0 && buffer) { + char *buf = (char*)malloc(blen + 1); + if (buf) { + memcpy(buf, buffer, blen); + buf[blen] = 0; + result.buffer = buf; + result.blen = blen; + result.xfree = free; + } } - result.blen = sizeof(fetch->statusText); - result.xfree = free; } // cleanup From 9d8d05328a9c6290627fd47c9b2e5c5ae35d0b6b Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 05:02:35 +0200 Subject: [PATCH 5/8] fix(integration-test): remove db if it exists --- test/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/test/main.c b/test/main.c index b285f7c..d8f9b1b 100644 --- a/test/main.c +++ b/test/main.c @@ -358,6 +358,7 @@ void* worker(void* arg) { int main (void) { int rc = SQLITE_OK; + remove(DB_PATH); // remove the database file if it exists cloudsync_memory_init(1); From 8f48bb3bdad8816fb90b0d800bfa3bfcedafb946 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 15:11:00 +0200 Subject: [PATCH 6/8] fix(wasm-example): add error handling for login process and display error messages --- examples/sport-tracker-app/src/App.tsx | 11 +++++++++++ examples/sport-tracker-app/src/SQLiteSync.ts | 6 +----- .../sport-tracker-app/src/components/UserLogin.tsx | 3 +++ examples/sport-tracker-app/src/db/database.ts | 1 - 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/sport-tracker-app/src/App.tsx b/examples/sport-tracker-app/src/App.tsx index 2cd6603..e315942 100644 --- a/examples/sport-tracker-app/src/App.tsx +++ b/examples/sport-tracker-app/src/App.tsx @@ -36,6 +36,7 @@ const AppContent: React.FC = () => { const [refreshTrigger, setRefreshTrigger] = useState(0); const [sqliteSyncVersion, setSqliteSyncVersion] = useState(""); const [sqliteVersion, setSqliteVersion] = useState(""); + const [loginError, setLoginError] = useState(""); // Coach mode - true when logged in user is named "coach" const isCoachMode = (session: UserSession | null): boolean => { @@ -392,6 +393,15 @@ const AppContent: React.FC = () => { ); } + if (loginError) { + return ( +
+

Login Error

+

{loginError}

+
+ ); + } + if (!isInitialized || loading) { return (
@@ -425,6 +435,7 @@ const AppContent: React.FC = () => { onLogout={handleLogout} onUsersLoad={loadUsers} onRefresh={handleRefreshData} + onError={setLoginError} /> Promise; onUsersLoad: () => void; onRefresh: () => void; + onError?: (error: string) => void; // Optional error handler } const UserLogin: React.FC = ({ @@ -24,6 +25,7 @@ const UserLogin: React.FC = ({ onLogout, onUsersLoad, onRefresh, + onError }) => { const { db } = useDatabase(); const [selectedUserId, setSelectedUserId] = useState(""); @@ -113,6 +115,7 @@ const UserLogin: React.FC = ({ error ); console.warn("SQLite Sync: Falling back to local refresh only"); + if(onError) onError("SQLite Sync - Failed to sync with SQLite Cloud: " + error); } } else { console.log( diff --git a/examples/sport-tracker-app/src/db/database.ts b/examples/sport-tracker-app/src/db/database.ts index ca71e17..39b06d0 100644 --- a/examples/sport-tracker-app/src/db/database.ts +++ b/examples/sport-tracker-app/src/db/database.ts @@ -97,7 +97,6 @@ export class Database { return this.sendMessage("sqliteSyncSetToken", token); } - async sqliteSyncNetworkSync(): Promise { return this.sendMessage("sqliteSyncNetworkSync"); } From a31b013a0931f2c530252e784d6dd6dc0ecbe546 Mon Sep 17 00:00:00 2001 From: Gioele Cantoni Date: Sat, 2 Aug 2025 15:13:04 +0200 Subject: [PATCH 7/8] bump version --- examples/sport-tracker-app/package.json | 2 +- src/cloudsync.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/sport-tracker-app/package.json b/examples/sport-tracker-app/package.json index 5897d20..f8bc85d 100644 --- a/examples/sport-tracker-app/package.json +++ b/examples/sport-tracker-app/package.json @@ -13,7 +13,7 @@ "vite": "^7.0.0" }, "dependencies": { - "@sqliteai/sqlite-sync-wasm": "^3.49.2-sync-0.8.9", + "@sqliteai/sqlite-sync-wasm": "^3.49.2-sync-0.8.20", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", diff --git a/src/cloudsync.h b/src/cloudsync.h index 0b33ede..fe9b87f 100644 --- a/src/cloudsync.h +++ b/src/cloudsync.h @@ -16,7 +16,7 @@ #include "sqlite3.h" #endif -#define CLOUDSYNC_VERSION "0.8.12" +#define CLOUDSYNC_VERSION "0.8.20" int sqlite3_cloudsync_init (sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); From ba20582a434af6a2d111c072a46310d2fb3b2190 Mon Sep 17 00:00:00 2001 From: Andrea Donetti Date: Mon, 4 Aug 2025 10:55:57 +0200 Subject: [PATCH 8/8] fix(apple-network): use Objective-C Automatic Reference Counting (ARC) for NSData --- Makefile | 2 +- src/network.m | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index eaa5af9..c5970df 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ ifdef NATIVE_NETWORK CFLAGS += -DCLOUDSYNC_OMIT_CURL $(BUILD_RELEASE)/%_m.o: %.m - $(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@ + $(CC) $(CFLAGS) -fobjc-arc -O3 -fPIC -c $< -o $@ endif # Windows .def file generation diff --git a/src/network.m b/src/network.m index eb9c504..3bba42d 100644 --- a/src/network.m +++ b/src/network.m @@ -155,8 +155,7 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - // Retain data to not get it deallocated, release it before returning - responseData = [data retain]; + responseData = data; if (error) { responseError = [error localizedDescription]; errorCode = [error code]; @@ -171,10 +170,8 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (!responseError && (statusCode >= 200 && statusCode < 300)) { - size_t len = [responseData length]; // check if OK should be returned - if (len == 0) { - [responseData release]; + if (responseData == nil || [responseData length] == 0) { return (NETWORK_RESULT){CLOUDSYNC_NETWORK_OK, NULL, 0, NULL, NULL}; } @@ -189,10 +186,9 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, result.buffer = (char *)responseData.bytes; result.xdata = (void *)CFBridgingRetain(responseData); } - result.blen = len; + result.blen = [responseData length]; result.xfree = network_buffer_cleanup; - [responseData release]; return result; } @@ -216,6 +212,5 @@ NETWORK_RESULT network_receive_buffer(network_data *data, const char *endpoint, result.xfree = network_buffer_cleanup; result.blen = responseError ? (size_t)errorCode : (size_t)statusCode; - [responseData release]; return result; }