Skip to content

fix handleError on non-string error.code and serialize object/array data values as json#294

Open
anasmadrhar wants to merge 3 commits into
home-assistant:mainfrom
anasmadrhar:fix/handler-error-and-data-serialization
Open

fix handleError on non-string error.code and serialize object/array data values as json#294
anasmadrhar wants to merge 3 commits into
home-assistant:mainfrom
anasmadrhar:fix/handler-error-and-data-serialization

Conversation

@anasmadrhar
Copy link
Copy Markdown

two small bugs i hit while running this proxy on my own gcp project to handle pushes for an app fork.

1. handleError crashes when error.code isn't a string

in handlers.js, when messaging.send fails before reaching fcm (e.g. dns/network issue, or any non-fcm error path), the error doesn't always have a string code. it can be a number or undefined. the existing check goes straight to incomingError.code.startsWith('messaging/') which throws TypeError: incomingError.code.startsWith is not a function, and that throw escapes out of handleError itself — so the original error is masked and the response handler never returns. clients see a hung request / 500 with no useful info in logs.

guarded with typeof incomingError.code === 'string' before the .startsWith. behavior for real messaging/* codes is unchanged.

2. object/array data values become the literal string [object Object]

in android.js, the androidNotificationKeys loop does String(req.body.data[key]) to coerce each value to a string (fcm data values must be strings). that's fine for primitives but for arrays/objects it produces "[object Object]" or "a,b,c", neither of which the receiving app can decode.

this happens in practice when the calling integration's template engine auto-parses a json-string back into a native list/dict before posting. the sender thinks they sent [1,2,3], the proxy ships "1,2,3", the app can't JSON.parse it.

switched to JSON.stringify(v) when v is a non-null object, plain String(v) otherwise. no behavior change for primitive values.

both changes are independent and tiny, happy to split into two prs if preferred.

incomingError.code can be a non-string (e.g. when an upstream layer
fails before FCM is reached and surfaces a numeric HTTP status), in
which case .startsWith throws TypeError inside the error handler and
masks the original error. The 5xx response body became an unhelpful
stack trace from the handler itself, making the real problem hard to
trace from the calling app's logs.

Adding a typeof guard keeps the messaging-error fast path while letting
non-FCM errors fall through to the generic reportError + InternalError
response, which preserves the actual cause.

Reproducible by sending a request that triggers any non-FCM error in
sendNotification (e.g. a transient network failure surfaced with a
numeric code). With the guard, the handler returns the InternalError
JSON; without it, the request 500s with a TypeError.
FCM data fields must be strings, so the proxy coerces every value with
String(). For objects and arrays this produces "[object Object]" or
"[object Object],[object Object]" which the receiving app then can't
decode.

This bites whenever the calling integration's template renderer
auto-parses a JSON-shaped string back into a native list/object and
sends it as structured JSON in the request body. The proxy receives
the parsed value and stringifies it as JS rather than as JSON.

Switch to JSON.stringify for non-null objects/arrays. Strings, numbers,
and booleans still go through String() unchanged.

Reproducible from Home Assistant by sending notify.mobile_app_* with
a samples list inside the data field that originates from a Jinja
template — the template renders to a JSON-array string, HA parses it
back into a Python list before serializing the request body, and the
device-side parser receives "[object Object],[object Object]".
Copilot AI review requested due to automatic review settings May 2, 2026 20:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses two small runtime bugs in the Firebase push proxy: one in centralized error handling for non-FCM failures, and one in Android payload construction where structured data values were being flattened into unusable strings before being sent to FCM.

Changes:

  • Guard handleError against non-string incomingError.code values before calling .startsWith(...).
  • Serialize Android data values with JSON.stringify when they are arrays/objects, while preserving String(...) coercion for primitives.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
functions/handlers.js Hardens Firebase messaging error classification so non-string error codes do not crash error handling.
functions/android.js Changes Android payload data coercion so object/array values are preserved as JSON strings instead of lossy stringification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread functions/handlers.js
// `code` can be a non-string (e.g. numeric HTTP status from a network failure
// before reaching FCM); without the type guard, .startsWith throws and masks
// the underlying error.
if (typeof incomingError.code === 'string' && incomingError.code.startsWith('messaging/')) {
Comment thread functions/android.js
Comment on lines +116 to +121
const v = req.body.data[key];
// FCM data values must be strings. For arrays/objects (e.g. when the
// calling integration's template engine auto-parses a JSON-string
// back into a list), String(value) produces "[object Object]" which
// the receiving app then can't decode as JSON. Stringify properly.
payload.data[key] = (v !== null && typeof v === 'object') ? JSON.stringify(v) : String(v);
Per review feedback. Covers:

- handleError with non-string error.code (numeric, undefined) — repros
  the original TypeError-on-.startsWith crash, asserts the proxy still
  responds 500 instead of hanging.
- android.js whitelist serialization — array values produce a JSON
  string the receiver can parse, object values likewise, and null falls
  through to the primitive String() branch unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants