You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
overridefunonFailure(call:Call, e:IOException) {
Log.d(tag, "FAILURE TO CONNECT")
e.printStackTrace()
val jsobj =JSObject()
jsobj.put("error", "Failed to connect")
cb(jsobj)
}
Log.d — debug level, commonly filtered out.
printStackTrace() — goes to System.err, not AbsLogger, so it never reaches the in-app log viewer. onResponse right below this uses AbsLogger.info for the 401 case, so the omission looks like an oversight rather than a choice.
"Failed to connect" — discards the exception type and message before the UI ever sees them.
Any ApiHandler call — playback, download, sync — reports a bad cert the same as an unreachable server. That's common and reproduces trivially.
A narrower, costlier case: login/browse (CapacitorHttp) succeeds or fails informatively against a server, while ApiHandler fails opaquely against what looks like the same server, because the two paths actually resolved to different endpoints (split-horizon DNS, path-based reverse proxy, or the NAT64/DNAT case in #1901). Not everyday, but it's what turns a five-minute check into a long investigation, since nothing distinguishes "wrong cert" from "no route" until you pull logcat.
Steps to Reproduce the Issue
Put an untrusted cert in front of an ABS server — e.g. openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout key.pem -out cert.pem -subj "/CN=abs-test.local", not installed on the test device. (A DNAT misconfiguration like [Bug]: Certain app functions fail over IPv6 #1901 produces the same failure; a self-signed cert is just easier to set up.)
Add the server and log in fresh (Settings → Server → Add Server) — goes through CapacitorHttp, not ApiHandler.
handleLoginFormError surfaces something reasonably specific here: "Failed to contact server. (${error})". Not the code path this issue is about, but it shows the app is capable of a real message.
With the server added, trigger playback, a download, or progress sync — all go through ApiHandler.makeRequest (OkHttp).
All three fail with the same bare "Failed to connect", no matter the actual cause. Settings → Logs shows nothing.
adb logcat -v time System.err:V *:S shows what the app doesn't:
W/System.err: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:379)
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:337)
at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:209)
...
D/ApiHandler: FAILURE TO CONNECT
E/ApiHandler: Failed to connect
The app can report a real error — step 2 proves that. ApiHandler.makeRequest just doesn't.
What was Expected?
A certificate/TLS failure should be reported as one, not folded into a generic connectivity error, in both the UI and the in-app logs.
Phone Model
OnePlus 11
Phone OS
Android 16
Audiobookshelf App Version
Android App - 0.13.0
Installation Source
Google Play Store
Additional Notes
Why this matters
I filed #1901 as an IPv6 bug. The real cause was a misscoped DNAT rule on my firewall: NAT64 traffic wasn't being translated, so the phone was hitting the firewall's own GUI listener and its self-signed bootstrap cert instead of the real server.
The app was right to refuse that cert. But the only signal was Failed to connect, so the misdiagnosis stood for seven weeks.
Confirmed with curl on the device — same hostname, two different certs depending on which address gets used:
"certificate trust failure: CN=opnsense-primary.home.bishop.be" would have gotten me to the firewall rule in about ninety seconds.
Related
InternalDownloadManager.kt#L42-L45 has a milder version — logs at Log.e with the exception attached, better, but still bypasses AbsLogger and reports upstream as a bare onComplete(true):
A failed download gives the user no indication why.
AI involvement
Claude was used to explore the app's source, form and discard several hypotheses (Happy Eyeballs, an Android DNS-resolution bug, sequential-connect timeouts, CLAT/RFC 6724 address selection), and draft this writeup. I directed the investigation, ran all adb/openssl commands against my own devices and network, and confirmed the actual root cause (the DNAT scope on my firewall, and the two differing certificates) myself before accepting it. The suggested code change is untested — I haven't built or run it.
I have verified that the bug is not already awaiting release
Yes
What was the Problem?
ApiHandler.makeRequestcollapses everyIOExceptioninto"Failed to connect", logged atLog.dwithprintStackTrace(), never throughAbsLogger. So:adb logcatonSystem.err.ApiHandler.kt#L113-L120:Log.d— debug level, commonly filtered out.printStackTrace()— goes toSystem.err, notAbsLogger, so it never reaches the in-app log viewer.onResponseright below this usesAbsLogger.infofor the 401 case, so the omission looks like an oversight rather than a choice."Failed to connect"— discards the exception type and message before the UI ever sees them.Any
ApiHandlercall — playback, download, sync — reports a bad cert the same as an unreachable server. That's common and reproduces trivially.A narrower, costlier case: login/browse (
CapacitorHttp) succeeds or fails informatively against a server, whileApiHandlerfails opaquely against what looks like the same server, because the two paths actually resolved to different endpoints (split-horizon DNS, path-based reverse proxy, or the NAT64/DNAT case in #1901). Not everyday, but it's what turns a five-minute check into a long investigation, since nothing distinguishes "wrong cert" from "no route" until you pull logcat.Steps to Reproduce the Issue
openssl req -x509 -newkey rsa:2048 -days 365 -nodes -keyout key.pem -out cert.pem -subj "/CN=abs-test.local", not installed on the test device. (A DNAT misconfiguration like [Bug]: Certain app functions fail over IPv6 #1901 produces the same failure; a self-signed cert is just easier to set up.)CapacitorHttp, notApiHandler.handleLoginFormErrorsurfaces something reasonably specific here:"Failed to contact server. (${error})". Not the code path this issue is about, but it shows the app is capable of a real message.ApiHandler.makeRequest(OkHttp)."Failed to connect", no matter the actual cause. Settings → Logs shows nothing.adb logcat -v time System.err:V *:Sshows what the app doesn't:The app can report a real error — step 2 proves that.
ApiHandler.makeRequestjust doesn't.What was Expected?
A certificate/TLS failure should be reported as one, not folded into a generic connectivity error, in both the UI and the in-app logs.
Phone Model
OnePlus 11
Phone OS
Android 16
Audiobookshelf App Version
Android App - 0.13.0
Installation Source
Google Play Store
Additional Notes
Why this matters
I filed #1901 as an IPv6 bug. The real cause was a misscoped DNAT rule on my firewall: NAT64 traffic wasn't being translated, so the phone was hitting the firewall's own GUI listener and its self-signed bootstrap cert instead of the real server.
The app was right to refuse that cert. But the only signal was
Failed to connect, so the misdiagnosis stood for seven weeks.Confirmed with
curlon the device — same hostname, two different certs depending on which address gets used:"certificate trust failure: CN=opnsense-primary.home.bishop.be"would have gotten me to the firewall rule in about ninety seconds.Related
InternalDownloadManager.kt#L42-L45has a milder version — logs atLog.ewith the exception attached, better, but still bypassesAbsLoggerand reports upstream as a bareonComplete(true):A failed download gives the user no indication why.
AI involvement
Claude was used to explore the app's source, form and discard several hypotheses (Happy Eyeballs, an Android DNS-resolution bug, sequential-connect timeouts, CLAT/RFC 6724 address selection), and draft this writeup. I directed the investigation, ran all
adb/opensslcommands against my own devices and network, and confirmed the actual root cause (the DNAT scope on my firewall, and the two differing certificates) myself before accepting it. The suggested code change is untested — I haven't built or run it.