(feat): surface transport error details in FCM push results#136
Conversation
Greptile SummaryThis PR improves FCM push delivery error reporting by surfacing transport-layer failures (timeouts, DNS errors, TLS issues) that previously produced an opaque
Confidence Score: 5/5Safe to merge — changes are additive, backward-compatible, and confined to error-reporting paths that have no effect on successful deliveries. The transport-error capture is straightforward: No files require special attention. Important Files Changed
Reviews (1): Last reviewed commit: "(feat): surface transport error details ..." | Re-trigger Greptile |
What
Improves FCM push delivery error reporting. Previously, when a request failed at the transport layer (timeout, DNS failure, TLS error) or returned a non-JSON body, the per-recipient result was just
"Unknown error"— with no way to tell why delivery failed.This PR:
errorCode(cURL errno) to the result shape returned byAdapter::request()andAdapter::requestMulti().getError()helper that falls back to transport-level details when Firebase doesn't provide an error message.Why
A failed FCM send with
statusCode: 0andresponse: null(e.g. connection timeout) produced:{ "recipient": "token123", "error": "Unknown error" }With this change, the same failure produces:
{ "recipient": "token123", "error": "Connection timed out after 10000 milliseconds (HTTP status 0; cURL error code 28)" }This makes production delivery failures actually diagnosable from message logs.
How
Adapter.php— capture cURL error codesSingle requests use
curl_errno(), multi requests use the per-handleresultfromcurl_multi_info_read():The
requestMulti()docblock is also corrected:responsecan bestring|nullas well asarray(it already could at runtime — the annotation was wrong).FCM.php— layered error resolutionThe inline ternary chain in
process()is replaced with agetError()helper that resolves errors in priority order:This also hardens against non-array responses (e.g. an HTML error page from a proxy), which previously relied on null coalescing over a string.
Tests
FCMTestgains a data-provider unit test covering all four resolution paths (runs without FCM credentials, unlike the existing integration tests):response.error.messagesetInvalid registration tokenerror.status = NOT_FOUNDExpired device tokenConnection timed out after 10000 milliseconds (HTTP status 0; cURL error code 28)Request failed (HTTP status 503; cURL error code 0)Existing test stubs (
ResendRoutingTest,SESRoutingTest,Msg91Test) are updated to includeerrorCodein their stubbed result shapes.Breaking changes
None for consumers of adapters. Subclasses that override
request()/requestMulti()and rely on the documented return shape should adderrorCode(see updated test stubs for reference).