Commit Graph

74 Commits

Author SHA1 Message Date
Huang Xin 712d564e9d feat(sync): encrypted OPDS credentials + Tauri keychain (PR 4c + 4d) (#4090)
* feat(sync): encrypt opds_catalog credentials end-to-end (TS path)

Wires encrypted-credential sync for opds_catalog via the CryptoSession
shipped in PR 4a (#4084) plus a new publish/pull crypto middleware.
TS-only — native still uses ephemeral storage (re-enter passphrase per
launch); PR 4d wires the OS keychain.

- ReplicaAdapter gains optional `encryptedFields: readonly string[]`.
  Adapters stay sync; the middleware handles the crypto round trip.
- replicaCryptoMiddleware.ts: encryptPackedFields drops the named
  fields from the push when the session is locked (no plaintext
  leak); decryptRowFields drops them on pull failure (local
  plaintext preserved by the store merge).
- replicaPublish / replicaPullAndApply invoke the middleware.
- OPDS adapter declares encryptedFields = [username, password] and
  now pack/unpack them as plaintext.
- passphraseGate.ts: ensurePassphraseUnlocked coalesces concurrent
  calls, prompts via the registered prompter with kind=setup|unlock,
  throws NO_PASSPHRASE on cancel.
- PassphrasePromptModal mounted at the Providers root; registers
  itself as the gate prompter.
- CryptoSession.forget() wipes server-side envelopes + salts.
- Migration 010 + replica_keys_forget RPC; DELETE
  /api/sync/replica-keys + client wrapper.
- SyncPassphraseSection on the user page: status / Set / Unlock /
  Lock / Forgot.
- CatalogManager pre-save: ensurePassphraseUnlocked when credentials
  are present; user cancel saves locally without sync.

Plan updated: PR 4 split documented as 4a/4b/4c/4d.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sync): persist sync passphrase via OS keychain (Tauri)

Replaces the EphemeralPassphraseStore stub on native with real
OS-keychain storage so users don't re-enter their sync passphrase
every launch. Web stays on the in-memory ephemeral store by design.

Native bridge plugin gains 4 commands wired across all platforms:

- Rust desktop (`keyring` crate): macOS Keychain on apple-native,
  Windows Credential Manager on windows-native, Linux libsecret/
  Secret Service on sync-secret-service. Per-target features so each
  platform compiles only the backend it needs.
- iOS Swift: Security framework Keychain (kSecClassGenericPassword,
  SecItemAdd / Copy / Delete).
- Android Kotlin: androidx.security EncryptedSharedPreferences
  (AndroidKeystore-derived AES-GCM master key,
  AES256_SIV / AES256_GCM key/value encryption).

TS layer:

- TauriPassphraseStore wraps the bridge calls. set is fail-loud
  (surfaces keychain rejection); get is fail-soft (returns null on
  any error so the gate prompts).
- createPassphraseStore returns ephemeral synchronously;
  upgradeToKeychainIfAvailable swaps the singleton to
  TauriPassphraseStore on Tauri after probing the bridge. CryptoSession
  resolves the store via createPassphraseStore() each touch so the
  swap is transparent.
- CryptoSession.tryRestoreFromStore: silent unlock at boot. Stale-
  entry recovery clears the store when the account has no salt
  server-side. unlock/setup persist; forget also clears the store.
- Providers boot effect: upgrade keychain → silent restore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sync): make encrypted-credential pull actually decrypt + UX polish

PR 4c shipped the encrypt path but the pull side silently dropped
ciphers when locked, the modal was busy with double-rings, and web
re-prompted on every page refresh. This rolls up the post-test
fixes + UX polish:

Pull-side decrypt:
- decryptRowFields takes an `onLocked` callback the orchestrator
  wires to the passphrase gate; encountering a cipher field with a
  locked session now triggers the lazy-prompt path instead of
  dropping the field.
- replicaPullAndApply re-applies the unpacked row for metadata-only
  kinds even when a local copy exists, so the now-decrypted creds
  reach the store (the binary-kind skip-if-local optimization
  doesn't apply).
- Cipher fingerprint comparison: capture the row's `cipher.c` for
  each encrypted field, compare against the local record's
  lastSeenCipher. Same → skip prompt + decrypt entirely. Different
  (rotation / value change on another device) → prompt to
  re-decrypt. Fingerprint persists via OPDSCatalog.lastSeenCipher.

Web persistence:
- SessionStoragePassphraseStore: passphrase survives page refresh
  within the same tab, dies on tab close. Replaces
  EphemeralPassphraseStore as the default on web. Avoids
  localStorage / IndexedDB to keep the tab-scoped trust boundary.

UI:
- Renamed PassphrasePromptModal → PassphrasePrompt; modernized: filled
  input style with single subtle focus border, btn-primary +
  btn-ghost replaced with leaner custom buttons. eink-bordered +
  btn-primary classes give the dialog correct e-paper rendering.
- globals.css: suppress redundant outline/box-shadow on focused text
  inputs / textareas (the element's own border is the focus
  indicator).
- AGENTS.md: documents the e-ink convention (`eink-bordered`,
  `btn-primary` for inverted CTAs, etc.) so future widgets ship with
  e-paper support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 13:22:49 +02:00
Huang Xin 011ad18a02 fix(android): use stable safe area insets to avoid unnecessary layout shift, closes #3670 (#3859) 2026-04-13 12:04:41 +02:00
Huang Xin ff962a1f02 fix(android): auto-shutdown native TTS engine after 30 min idle to save battery (#3728)
When the Android native TTS engine is paused or stopped but not shut down,
it holds resources and drains battery. This adds a 30-minute idle timer that
automatically shuts down the TextToSpeech engine and MediaPlaybackService
after inactivity. The engine transparently re-initializes on next use.

Also adds missing androidx.lifecycle:lifecycle-process dependency to fix
ProcessLifecycleOwner build error.

Closes #3713

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:00:21 +02:00
Huang Xin bbfc82e50d feat(android): add foss flavor build without gms services (#3666) 2026-03-28 05:36:09 +01:00
Huang Xin 26fec924fc chore: bump turso to the latest version (#3650) 2026-03-27 06:28:59 +01:00
Huang Xin af9cf33936 fix(android): never try to fight with the navigation bar on Android ever, closes #3618 (#3646) 2026-03-26 17:40:49 +01:00
Huang Xin b2bb92036b fix(ios): decode percent-encoded file URIs in copy_uri_to_path (#3553) 2026-03-17 13:17:42 +01:00
Huang Xin fad27d74a0 compat(android): show button navigation bar on Android, closes #3466 (#3501) 2026-03-09 17:36:13 +01:00
Huang Xin dac4f2e8ec chore: experimental vinext build (#3486) 2026-03-06 18:04:28 +01:00
Huang Xin 97cab2d70b feat(database): support turso fts in tauri apps (#3484) 2026-03-05 21:00:15 +01:00
Huang Xin 1e9bd1d821 chore(database): unit testing and feature detect for fts and vector search (#3476) 2026-03-05 07:41:16 +01:00
Huang Xin 5273ef75dc feat(database): add database service abstraction with libsql/turso backend (#3472) 2026-03-05 02:38:23 +08:00
Huang Xin 6ad549d13c fix(iOS): correct sidebar insets on iPad and resolve occasional stale safe area inset on iOS (#3395) 2026-02-27 05:43:45 +01:00
Huang Xin 48920a87bf chore(opds): disable popular online opds catalogs in certain regions on App Store (#3031) 2026-01-22 11:42:16 +01:00
Huang Xin ed476a4fce fix(ios): fix wakelock on iOS, closes #2453 (#2948) 2026-01-14 13:06:16 +01:00
Huang Xin 462ca46fee feat(eink): optimize color and layout for e-ink mode (#2887) 2026-01-08 17:29:33 +01:00
Huang Xin f4587663b5 chore(ios): drop support for iOS below 15.0 (#2804) 2025-12-29 03:41:12 +01:00
Huang Xin 7063d62b13 fix(settings): screen brightness setting only applies to the reader page, closes #2717 (#2720) 2025-12-15 06:04:29 +01:00
Huang Xin c1530cc5c4 feat(iOS): support open file with Readest in Files App, closes #2334 (#2705) 2025-12-13 13:28:03 +01:00
Huang Xin 5141be1c3f fix(iap): don't initialize billing on Android without google play service, closes #2630 (#2677) 2025-12-10 15:34:23 +01:00
Huang Xin ba3f060cc4 feat: add support for importing from a directory recursively, closes #179 (#2642) 2025-12-08 07:16:57 +01:00
Huang Xin 4e6f146b8f feat(android): support opening shared files from other apps, closes #2484 (#2628) 2025-12-05 18:13:06 +01:00
Huang Xin b08b7de8e9 fix(tts): fixed highlighting of current sentence for native tts on Android, closes #2620 (#2621) 2025-12-05 04:05:18 +01:00
Huang Xin 8f22a5c570 feat: select directory to save last book cover image (#2521) 2025-11-23 17:00:49 +01:00
Huang Xin 6d5b16ea8b fix(iap): open external app for payment (#2465) 2025-11-18 12:07:43 +01:00
Huang Xin 5a3114de48 fix(android): get brightness of the current window on Android, closes #2389 (#2409) 2025-11-05 19:36:45 +01:00
Huang Xin 78e61060d2 fix(tts): fixed listener of tts events (#2349) 2025-10-28 18:36:57 +01:00
Huang Xin 31d6f44ff5 fix(iOS): restore last page if app is terminated in the background (#2312) 2025-10-24 05:42:52 +02:00
Huang Xin a1f6030e75 fix(iOS): detect and recover from WebContent process termination (#2302) 2025-10-23 10:37:07 +02:00
Huang Xin 50d7f9a9dd feat(android): support custom data location on external sdcard (#2292) 2025-10-22 10:14:41 +02:00
Huang Xin 8737535b90 feat: support Android IAP (#2286) 2025-10-21 18:24:50 +02:00
Huang Xin 5773e31990 compat(tts): fix crash with TTS on some versions of Android systems (#2244) 2025-10-16 10:10:04 +02:00
Huang Xin ada427b134 feat(settings): add settings to adjust screen brightness when reading, closes #1532 (#2197) 2025-10-10 19:02:35 +02:00
Huang Xin b69d9ed69f tts: improve media session control compatibility across more Android systems (#2185) 2025-10-09 17:53:07 +02:00
Huang Xin 0e6950a60f i18n: localization for notification title and text (#2143) 2025-09-30 10:03:33 +02:00
Huang Xin 1d4541e353 feat: request manage external storage permission when changing data directory to sdcard root on Android (#2142) 2025-09-30 08:59:16 +02:00
Huang Xin 0a1e0212e2 feat: supported background TTS with media session controls, closes #2099 and closes #964 (#2138) 2025-09-29 19:45:17 +02:00
Huang Xin e8a96fba95 compat: more compat with window insets on Android (#1931) 2025-08-30 06:18:06 +02:00
Huang Xin 5ee860f1e8 refactor: use safe insets from native for Android, closes #1793 and closes #1886 (#1897) 2025-08-25 09:38:49 +02:00
Huang Xin f46be89036 fix: apply system color scheme for iOS in auto theme mode, closes #1762 (#1767) 2025-08-08 13:45:04 +02:00
Huang Xin f3e9983742 feat: add IAP in native bridge plugin for iOS (#1673) 2025-07-24 14:54:17 +02:00
Huang Xin 93071e727f linux: fixed multiple instances created in OAuth, closes #1654 (#1659) 2025-07-22 09:00:19 +02:00
Huang Xin 3e4b7ebb13 css: remove unintended indent for images, closes #1567 (#1568)
* chore: target Android SDK to version 36

* css: remove unintended indent for images, closes #1567
2025-07-10 12:05:07 +02:00
Huang Xin 630025c123 chore: update Android target sdk to 35 (#1534) 2025-07-05 08:49:41 +02:00
Huang Xin cb1ddaeedb fix: smoother orientation change in iOS, closes #1434 (#1441) 2025-06-21 17:11:45 +02:00
Huang Xin 84328dcfb2 hotfix: resolve compatibility issues with Android Text-to-Speech API (#1394) 2025-06-14 15:39:12 +02:00
Huang Xin 658969e97a feat: support native tts engine on Android (#1387) 2025-06-12 17:56:14 +02:00
Huang Xin f8ac30adf1 tts: add native tts plugin for Android (#1376) 2025-06-09 16:54:12 +02:00
Huang Xin 9700942e24 fix: transient navigation bar should work now for Android 8-15, closes #1199 (#1225) 2025-05-22 16:12:59 +02:00
Huang Xin da49526f4c fix: now auto orientation follows system settings, closes #1093 and closes #1098 (#1122) 2025-05-11 14:57:05 +02:00