[PR #1768] feat(android): add custom headers to all HTTP request paths #1691

Open
opened 2026-04-25 00:00:38 +02:00 by adam · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/advplyr/audiobookshelf-app/pull/1768
Author: @yaker-gh
Created: 12/30/2025
Status: 🔄 Open

Base: masterHead: add-custom-headers


📝 Commits (5)

  • 1345676 feat(android): support custom HTTP headers for reverse proxy auth
  • e48043f fix: add CF Access custom headers to all HTTP paths
  • b863f21 fix: show edit form immediately without re-validating server connection
  • 8e6fee1 fix: enable CapacitorHttp to route ebook requests through native HTTP
  • 1a3ff3a Merge branch 'advplyr:master' into add-custom-headers

📊 Changes

16 files changed (+133 additions, -47 deletions)

View changed files

📝 android/app/src/main/assets/capacitor.config.json (+1 -1)
📝 android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt (+14 -10)
📝 android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt (+2 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt (+1 -1)
📝 android/app/src/main/java/com/audiobookshelf/app/managers/InternalDownloadManager.kt (+9 -3)
📝 android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt (+5 -0)
📝 android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt (+8 -1)
📝 android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt (+26 -9)
📝 capacitor.config.json (+1 -1)
📝 components/connection/ServerConnectForm.vue (+18 -6)
📝 components/readers/PdfReader.vue (+9 -3)
📝 layouts/default.vue (+1 -1)
📝 plugins/axios.js (+7 -0)
📝 plugins/nativeHttp.js (+16 -6)
📝 plugins/server.js (+5 -1)
📝 store/user.js (+10 -4)

📄 Description

Brief summary

Completes the custom HTTP headers implementation for the Android app. The app already had UI scaffolding (CustomHeadersModal.vue) and a customHeaders field on ServerConnectionConfig, but headers were never actually sent with requests. This PR wires them through every HTTP path so the app works behind reverse proxies that require authentication headers (Cloudflare Access, Authelia, Authentik, etc.).

Which issue is fixed?

Related to discussion: https://github.com/advplyr/audiobookshelf-app/discussions/1313
Closes: https://github.com/advplyr/audiobookshelf-app/issues/254

Pull Request Type

  • Platform: Android only
  • Scope: Frontend (Vue/JS) and Backend (Kotlin)

In-depth Description

Commit 1: Core plumbing (feat(android): support custom HTTP headers for reverse proxy auth)

Android (Kotlin):

File Change Why
ApiHandler.kt Inject custom headers into getRequest(), postRequest(), patchRequest() All API calls funnel through these methods — single injection point covers all endpoints
DeviceManager.kt Add customHeaders getter property Exposes headers from current ServerConnectionConfig to all Kotlin code
AbsDatabase.kt Sync customHeaders when updating existing server configs Previously only set on new configs — editing headers on an existing server had no effect
DownloadItemPart.kt Add custom headers to Android DownloadManager requests External-storage downloads use the system DownloadManager, not OkHttp
InternalDownloadManager.kt Accept + apply custom headers to OkHttp download requests Internal-storage downloads go through a separate OkHttp client
DownloadItemManager.kt Pass DeviceManager.customHeaders to InternalDownloadManager Wiring between header source and download pipeline
PlaybackSession.kt try-catch around local cover bitmap decoding Prevents crash when cover image decode fails (encountered during testing)

Frontend (Vue/JS):

File Change Why
ServerConnectForm.vue Add "Custom Headers" button to UI; pass headers to ping, status check, and socket connect Users need to configure headers AND they need to be used during initial connection
plugins/nativeHttp.js Add custom headers to all CapacitorHttp requests JS-layer native HTTP calls were missing headers entirely

Commit 2: Fix remaining HTTP paths (fix: add CF Access custom headers to all HTTP paths)

During real-world testing behind Cloudflare Access, several HTTP paths were found to be missing custom headers, causing:

  • Constant logouts — token refresh requests were blocked by CF Access, so the app thought the token was invalid
  • Broken ebook/PDF rendering — pdfjs fetches content through its own HTTP layer without custom headers
  • Broken real-time sync — Socket.IO WebSocket connections were blocked by CF Access
File Change Why
plugins/axios.js Request interceptor injects custom headers into all axios requests Axios is used alongside nativeHttp for many API calls — was completely missing headers
plugins/nativeHttp.js Add headers to refreshAccessToken(); pass serverConnectionConfig through call chain Token refresh hitting /auth/refresh without CF headers → blocked → forced logout
store/user.js Add headers to Vuex refreshToken action Duplicate token refresh code path (Vuex store action) — same logout bug
components/readers/PdfReader.vue Add custom headers to pdfjs httpHeaders config PDF/ebook viewer has its own HTTP layer that doesn't go through axios or nativeHttp
plugins/server.js Accept customHeaders in connect(), pass as Socket.IO extraHeaders WebSocket was connecting without auth headers — blocked by proxy
layouts/default.vue Pass customHeaders when reconnecting socket on app resume Auto-reconnect used old connect() signature without headers

Commit 3: UX fix (fix: show edit form immediately without re-validating server connection)

File Change Why
ServerConnectForm.vue editServerConfig() now shows the form directly instead of calling submit(true) first Previously, editing a saved server config required the server to be reachable. If the server was down or the address changed, you couldn't edit the config. Now matches newServerConfigClick() behavior.

Commit 4: Ebook reader fix (fix: enable CapacitorHttp to route ebook requests through native HTTP)

The ebook readers (epub, comic, mobi, PDF) use axios/XHR for binary data fetches (responseType: 'arraybuffer'/'blob'). With CapacitorHttp.enabled: false, these XHR requests execute in the WebView and are subject to CORS enforcement. Behind reverse proxies like Cloudflare Access, the CORS preflight OPTIONS request is blocked because preflight requests cannot carry custom authentication headers per the CORS spec.

File Change Why
capacitor.config.json Set CapacitorHttp.enabled: true Patches global XMLHttpRequest/fetch to route through native HTTP, bypassing CORS while preserving binary response types
android/.../capacitor.config.json Same change in Android assets copy Android build reads from this copy

Why this is safe now: This was previously disabled because Capacitor 4's native HTTP broke binary responses (returning base64 strings instead of ArrayBuffer/Blob). Capacitor 5.7+ (PR #6818) fixed this for GET requests by using a proxy URL approach — the native layer handles the HTTP request but the WebView's real XHR processes the response, preserving proper binary types. The app is on Capacitor 7.

Known limitations

  • iOS: No custom header support at all — ServerConnectionConfig.swift doesn't have a customHeaders property. Out of scope for this PR.
  • ExoPlayer HLS streaming: Only sends Authorization header, not custom headers. Direct play uses token-in-URL so it works regardless.
  • Cover images via <img> tags: Browser limitation — <img> elements can't carry custom headers. Works if server doesn't require auth for cover endpoints.

How have you tested this?

  • Configured Cloudflare Access with Service Token authentication (CF-Access-Client-Id + CF-Access-Client-Secret) in front of audiobookshelf server
  • Added headers via the Custom Headers modal in the app
  • Verified: server ping, login, library browsing, audiobook streaming, downloads (both internal and external storage), ebook/PDF reading, real-time sync via Socket.IO
  • Verified token refresh works without triggering logout
  • Verified headers persist across app restarts
  • Verified headers update correctly when editing an existing server connection
  • Verified ebook readers (epub, comic, mobi, PDF) load correctly behind CF Access after enabling CapacitorHttp
  • Verified no regression in audio playback, navigation, or socket connections after enabling CapacitorHttp

Screenshots

Screenshot_20251229-231947 Screenshot_20251229-231904 Screenshot_20251229-231856 Screenshot_20251229-231816

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/advplyr/audiobookshelf-app/pull/1768 **Author:** [@yaker-gh](https://github.com/yaker-gh) **Created:** 12/30/2025 **Status:** 🔄 Open **Base:** `master` ← **Head:** `add-custom-headers` --- ### 📝 Commits (5) - [`1345676`](https://github.com/advplyr/audiobookshelf-app/commit/1345676bb9fb6b37e1f3f2586f0197c1f80ecb02) feat(android): support custom HTTP headers for reverse proxy auth - [`e48043f`](https://github.com/advplyr/audiobookshelf-app/commit/e48043f0eec0fa12063f9c88507df3df599d4dd7) fix: add CF Access custom headers to all HTTP paths - [`b863f21`](https://github.com/advplyr/audiobookshelf-app/commit/b863f21e7019d8538190e179ac4408c7a7242011) fix: show edit form immediately without re-validating server connection - [`8e6fee1`](https://github.com/advplyr/audiobookshelf-app/commit/8e6fee10637608dfb95fd23b98df292430545b1c) fix: enable CapacitorHttp to route ebook requests through native HTTP - [`1a3ff3a`](https://github.com/advplyr/audiobookshelf-app/commit/1a3ff3a52d8c70277c31b5a8e80f0a58db321cce) Merge branch 'advplyr:master' into add-custom-headers ### 📊 Changes **16 files changed** (+133 additions, -47 deletions) <details> <summary>View changed files</summary> 📝 `android/app/src/main/assets/capacitor.config.json` (+1 -1) 📝 `android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt` (+14 -10) 📝 `android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt` (+2 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt` (+1 -1) 📝 `android/app/src/main/java/com/audiobookshelf/app/managers/InternalDownloadManager.kt` (+9 -3) 📝 `android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt` (+5 -0) 📝 `android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt` (+8 -1) 📝 `android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt` (+26 -9) 📝 `capacitor.config.json` (+1 -1) 📝 `components/connection/ServerConnectForm.vue` (+18 -6) 📝 `components/readers/PdfReader.vue` (+9 -3) 📝 `layouts/default.vue` (+1 -1) 📝 `plugins/axios.js` (+7 -0) 📝 `plugins/nativeHttp.js` (+16 -6) 📝 `plugins/server.js` (+5 -1) 📝 `store/user.js` (+10 -4) </details> ### 📄 Description ## Brief summary Completes the custom HTTP headers implementation for the Android app. The app already had UI scaffolding (`CustomHeadersModal.vue`) and a `customHeaders` field on `ServerConnectionConfig`, but headers were never actually sent with requests. This PR wires them through every HTTP path so the app works behind reverse proxies that require authentication headers (Cloudflare Access, Authelia, Authentik, etc.). ## Which issue is fixed? Related to discussion: https://github.com/advplyr/audiobookshelf-app/discussions/1313 Closes: https://github.com/advplyr/audiobookshelf-app/issues/254 ## Pull Request Type - **Platform:** Android only - **Scope:** Frontend (Vue/JS) and Backend (Kotlin) ## In-depth Description ### Commit 1: Core plumbing (`feat(android): support custom HTTP headers for reverse proxy auth`) **Android (Kotlin):** | File | Change | Why | |------|--------|-----| | `ApiHandler.kt` | Inject custom headers into `getRequest()`, `postRequest()`, `patchRequest()` | All API calls funnel through these methods — single injection point covers all endpoints | | `DeviceManager.kt` | Add `customHeaders` getter property | Exposes headers from current `ServerConnectionConfig` to all Kotlin code | | `AbsDatabase.kt` | Sync `customHeaders` when updating *existing* server configs | Previously only set on new configs — editing headers on an existing server had no effect | | `DownloadItemPart.kt` | Add custom headers to Android `DownloadManager` requests | External-storage downloads use the system DownloadManager, not OkHttp | | `InternalDownloadManager.kt` | Accept + apply custom headers to OkHttp download requests | Internal-storage downloads go through a separate OkHttp client | | `DownloadItemManager.kt` | Pass `DeviceManager.customHeaders` to `InternalDownloadManager` | Wiring between header source and download pipeline | | `PlaybackSession.kt` | try-catch around local cover bitmap decoding | Prevents crash when cover image decode fails (encountered during testing) | **Frontend (Vue/JS):** | File | Change | Why | |------|--------|-----| | `ServerConnectForm.vue` | Add "Custom Headers" button to UI; pass headers to ping, status check, and socket connect | Users need to configure headers AND they need to be used during initial connection | | `plugins/nativeHttp.js` | Add custom headers to all `CapacitorHttp` requests | JS-layer native HTTP calls were missing headers entirely | ### Commit 2: Fix remaining HTTP paths (`fix: add CF Access custom headers to all HTTP paths`) During real-world testing behind Cloudflare Access, several HTTP paths were found to be missing custom headers, causing: - **Constant logouts** — token refresh requests were blocked by CF Access, so the app thought the token was invalid - **Broken ebook/PDF rendering** — pdfjs fetches content through its own HTTP layer without custom headers - **Broken real-time sync** — Socket.IO WebSocket connections were blocked by CF Access | File | Change | Why | |------|--------|-----| | `plugins/axios.js` | Request interceptor injects custom headers into all axios requests | Axios is used alongside nativeHttp for many API calls — was completely missing headers | | `plugins/nativeHttp.js` | Add headers to `refreshAccessToken()`; pass `serverConnectionConfig` through call chain | Token refresh hitting `/auth/refresh` without CF headers → blocked → forced logout | | `store/user.js` | Add headers to Vuex `refreshToken` action | Duplicate token refresh code path (Vuex store action) — same logout bug | | `components/readers/PdfReader.vue` | Add custom headers to pdfjs `httpHeaders` config | PDF/ebook viewer has its own HTTP layer that doesn't go through axios or nativeHttp | | `plugins/server.js` | Accept `customHeaders` in `connect()`, pass as Socket.IO `extraHeaders` | WebSocket was connecting without auth headers — blocked by proxy | | `layouts/default.vue` | Pass `customHeaders` when reconnecting socket on app resume | Auto-reconnect used old `connect()` signature without headers | ### Commit 3: UX fix (`fix: show edit form immediately without re-validating server connection`) | File | Change | Why | |------|--------|-----| | `ServerConnectForm.vue` | `editServerConfig()` now shows the form directly instead of calling `submit(true)` first | Previously, editing a saved server config required the server to be reachable. If the server was down or the address changed, you couldn't edit the config. Now matches `newServerConfigClick()` behavior. | ### Commit 4: Ebook reader fix (`fix: enable CapacitorHttp to route ebook requests through native HTTP`) The ebook readers (epub, comic, mobi, PDF) use axios/XHR for binary data fetches (`responseType: 'arraybuffer'`/`'blob'`). With `CapacitorHttp.enabled: false`, these XHR requests execute in the WebView and are subject to CORS enforcement. Behind reverse proxies like Cloudflare Access, the CORS preflight `OPTIONS` request is blocked because preflight requests cannot carry custom authentication headers per the CORS spec. | File | Change | Why | |------|--------|-----| | `capacitor.config.json` | Set `CapacitorHttp.enabled: true` | Patches global `XMLHttpRequest`/`fetch` to route through native HTTP, bypassing CORS while preserving binary response types | | `android/.../capacitor.config.json` | Same change in Android assets copy | Android build reads from this copy | **Why this is safe now:** This was previously disabled because Capacitor 4's native HTTP broke binary responses (returning base64 strings instead of ArrayBuffer/Blob). Capacitor 5.7+ (PR [#6818](https://github.com/ionic-team/capacitor/pull/6818)) fixed this for GET requests by using a proxy URL approach — the native layer handles the HTTP request but the WebView's real XHR processes the response, preserving proper binary types. The app is on Capacitor 7. ### Known limitations - **iOS:** No custom header support at all — `ServerConnectionConfig.swift` doesn't have a `customHeaders` property. Out of scope for this PR. - **ExoPlayer HLS streaming:** Only sends `Authorization` header, not custom headers. Direct play uses token-in-URL so it works regardless. - **Cover images via `<img>` tags:** Browser limitation — `<img>` elements can't carry custom headers. Works if server doesn't require auth for cover endpoints. ## How have you tested this? - Configured Cloudflare Access with Service Token authentication (`CF-Access-Client-Id` + `CF-Access-Client-Secret`) in front of audiobookshelf server - Added headers via the Custom Headers modal in the app - Verified: server ping, login, library browsing, audiobook streaming, downloads (both internal and external storage), ebook/PDF reading, real-time sync via Socket.IO - Verified token refresh works without triggering logout - Verified headers persist across app restarts - Verified headers update correctly when editing an existing server connection - Verified ebook readers (epub, comic, mobi, PDF) load correctly behind CF Access after enabling CapacitorHttp - Verified no regression in audio playback, navigation, or socket connections after enabling CapacitorHttp ## Screenshots <img width="864" height="1939" alt="Screenshot_20251229-231947" src="https://github.com/user-attachments/assets/bd1325e0-90c5-47b5-8b23-6d965992adad" /> <img width="864" height="1939" alt="Screenshot_20251229-231904" src="https://github.com/user-attachments/assets/1f8dba01-8486-453d-b8fc-7546f046e9cc" /> <img width="864" height="1939" alt="Screenshot_20251229-231856" src="https://github.com/user-attachments/assets/331b04ca-7df9-4a06-a1ed-1516c5cd8802" /> <img width="864" height="1939" alt="Screenshot_20251229-231816" src="https://github.com/user-attachments/assets/830ca520-0b5c-4e2c-be39-4b6abcf50763" /> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
adam added the pull-request label 2026-04-25 00:00:38 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/audiobookshelf-app#1691