Skip to content

tauri: let the app supply the HttpClient used by the SDK (Android TLS makes the sync stream unusable otherwise) #1071

Description

@jaimeshalom

Problem

tauri-plugin-powersync hardcodes the HTTP client it hands to the SDK:

// packages/tauri/src/lib.rs — open_database
let env = PowerSyncEnvironment::custom(
    reqwest::Client::new(),
    pool,
    PowerSyncEnvironment::tokio_timer(),
);

PowerSyncEnvironment::custom already accepts any HttpClient, and the powersync
crate documents that as the extension point — but the Tauri plugin does not
re-expose it, and init() takes no options. So an application can configure its
own requests and not the sync stream the SDK opens internally.

Why it matters (the case that led us here)

On Android, the default reqwest client verifies TLS with
rustls-platform-verifier, and that rejects certificates which carry no OCSP
responder. Android's TrustManager mandates OCSP for revocation checking, so it
raises CertPathValidatorException: Certificate does not specify OCSP responder,
which the crate maps to Revoked:

WARN  rustls_platform_verifier::verification::android] certificate was revoked:
      java.security.cert.CertPathValidatorException: Certificate does not specify OCSP responder
ERROR rustls_platform_verifier::verification::android] failed to verify TLS certificate:
      invalid peer certificate: Revoked
WARN  powersync::sync::download::actor] Sync iteration failed, HTTP error:
      error sending request for url (https://sync-…)

Modern certificates no longer ship an OCSP responder — Let's Encrypt ended OCSP
in August 2025 and Google Trust Services followed. We verified ours is genuinely
not revoked: its CRL is empty and does not contain the serial, and the edge sends
OCSP response: no response sent because there is nothing to staple. Upstream
issue: rustls/rustls-platform-verifier#221 (open).

The application can work around it for its own requests by building a client
with a bundled root store, but the sync stream stays broken — so the app looks
healthy (API reachable, UI fine) while sync silently retries forever. That is a
much harder failure to diagnose than a plain TLS error.

Suggested change (additive, backwards compatible)

Store the client on PowerSync and add an init_with_client:

pub fn init<R: Runtime>() -> TauriPlugin<R> {
    init_with_client(reqwest::Client::new())
}

pub fn init_with_client<R: Runtime>(client: reqwest::Client) -> TauriPlugin<R> {}

init() keeps its signature and behaviour. Beyond this TLS case it also lets
apps set timeouts, proxies or a user agent for the sync connection.

I have this running against 0.0.4 here, and I'm happy to open a PR against main
if you'd take it: https://github.com/jaimeshalom/powersync-js/tree/listto/injectable-http-client

Environment

  • tauri-plugin-powersync 0.0.4 (same hardcoding in 0.0.5 and 0.0.6)
  • powersync 0.0.5, reqwest 0.13, rustls-platform-verifier 0.7.0
  • Android 16 (Xiaomi), release build, universal APK

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions