Class FcmTransport
- All Implemented Interfaces:
IPushTransport
POST https://fcm.googleapis.com/v1/projects/{project}/messages:send with an OAuth 2.0 bearer token minted from the application's service account.The subscription is the registration token the Cordova client obtained from the FirebaseX plugin. Both Android and iOS devices are reached here: with the plugin in FCM mode the iOS token is an FCM registration token and FCM relays through APNs (ruling on the iOS transport, 2026-09-06). An PushTransport.APNS row, a raw APNs token registered by the legacy client branch, is not this transport's.
Message mapping, PUSH-WIRE-CONTRACT section 6.5: notification.title, body and image from the message; data from options.data, every value a string as FCM requires, plus the actions as JSON text and every member that has no native FCM field (navigate, badge, renotify, requireInteraction, lang, dir), so the device can rebuild the object the Service Worker receives; android.ttl and apns-expiration from the TTL; android.priority and apns-priority from the urgency; android.notification.tag and the apns-collapse-id header from options.tag; android.notification.icon from options.icon, as given; android.notification.event_time from options.timestamp as RFC 3339; android.notification.vibrate_timings from options.vibrate as durations, "0.2s", after a leading "0s" because Android's first element is the delay before the vibrator starts where the Notification API's first element is the first vibration; android.notification.notification_priority PRIORITY_MIN from options.silent when true. A member the message does not carry produces no field, and no sound field is written for either platform.
data.iizi (PUSH-WIRE-CONTRACT section 6.2) is rebuilt here as one JSON string: the members the server put in it - the message ID, the application, the Topic, the send time - and, on this leg only, notification, the whole content object without its data. FirebaseX hands the app's JavaScript nothing but data, so the members mapped to native fields above would otherwise never reach it; the Web Push leg carries no such copy, since its Service Worker decrypts the whole object (ruled 2026-09-23, CC-PUSH-P3 RR-11, landed by CC-PUSH-FCM-DATA-IIZI). FCM refuses a payload over MAX_PAYLOAD; payloadSize(PushMessage) measures it so that push refuses up front, and send(SubscriptionRecord, PushMessage, long, DeliveryUrgency) refuses it again before any request, the backstop.
The target seam (CC-PUSH-CORDOVA-FIX C1e, ruling R-4 A of 2026-09-26): buildMessage(String, PushMessage, long, DeliveryUrgency) addresses a FcmTransport.Target, a registration token or an FCM Topic, and can ask FCM to validate the request without delivering it (validate_only, a member of the request beside message). send(SubscriptionRecord, PushMessage, long, DeliveryUrgency) always addresses the subscription's token and never validates only. The Topic and the validation serve the P4-13 measurement of MAX_PAYLOAD against a Topic with no subscribers, no device needed (P4-13 rulings P13-2 and P13-4); no server path sends to an FCM Topic (PUSH-WIRE-CONTRACT section 2.4).
The subscribe check (CC-PUSHDEV-VALIDATE-ONLY, D-2 (c) of CC-FIX-PUSHDEV-PROJECT, ruled 2026-09-28): checkToken(String, String) posts a validate_only body to a registration token with the application's credentials, so that a device whose token names no Firebase project is told apart at subscribe time - FCM answers 200 for a token of the application's project and SENDER_ID_MISMATCH for another project's, and delivers nothing either way. The verdict, a FcmTransport.TokenCheck, is the value a later caller reuses; the definitive ones are kept per application, project and token, since a token's project never changes. A subscribe path whose token another project issued asks again with *'s credentials, checkToken("*",...), and a match records the row under *'s project (B-10b, RR-21 C).
The credentials are chosen by the row (PUSH-CLUSTER-DESIGN 3.3, B-10b, AppPushConfigs.signer(SubscriptionRecord)): a row made under the server's own * project - a device inside iiziRun Developer subscribed for an application (RR-21 C, ruled PC-9) - is posted to *'s project with *'s service account; a row made under a project this server holds for neither is DeliveryOutcome.KEY_MISMATCH, nothing is posted, and the row is kept.
Status mapping: 200 delivered; 404 with UNREGISTERED, or 400 whose error names the registration token, endpoint stale; 429, 500, 503 transient with Retry-After; 401 and 403 the credentials, permanent and the cached token dropped - except a 403 whose error code is SENDER_ID_MISMATCH, the registration token's fault and not the credentials': the token was issued by another Firebase project than the one the row is signed through - since B-10b only a row that records no project, stored before B-10a or by a device that named none and FCM could not check - so the row is refused with a detail that says so and the OAuth token FCM accepted is kept (ruled 2026-09-26, CC-PUSH-ROADMAP row 4); other 4xx permanent.
- Author:
- Christopher Mindus
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumWhatcheckToken(String, String)learnt about the Firebase project of a registration token.static final recordThe target of a message: a registration token (message.token) or an FCM Topic (message.topic, the name without the/topics/prefix).static final recordThe outcome ofcheckToken(String, String): the verdict, the application's Firebase project the token was checked against, and what FCM answered.Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe largest message payload FCM accepts, in bytes: the notification and the data together (Firebase: "maximum payload for both message types is 4096 bytes").Constructor Summary
ConstructorsMethod Summary
Modifier and TypeMethodDescriptionstatic JSONObjectbuildMessage(FcmTransport.Target target, PushMessage message, long ttlMillis, DeliveryUrgency urgency, boolean validateOnly) Builds the FCM v1 request body for a target, a registration token or a Topic, delivering or validating only.static JSONObjectbuildMessage(String registrationToken, PushMessage message, long ttlMillis, DeliveryUrgency urgency) Builds the FCM v1 request body for a registration token, assend(SubscriptionRecord, PushMessage, long, DeliveryUrgency)posts it:buildMessage(Target,PushMessage,long,DeliveryUrgency,boolean)withFcmTransport.Target.token(String)and novalidate_only.checkToken(String appId, String registrationToken) Checks the Firebase project of a registration token at subscribe time (CC-PUSHDEV-VALIDATE-ONLY): postsbuildMessage(Target,PushMessage,long,DeliveryUrgency,boolean)to the token withvalidate_onlyand the application's credentials, so FCM validates the request and delivers nothing.voiddispose()Releases the transport's resources.getKind()Gets the transport kind.Gets whether this transport can send for an application.static intpayloadSize(PushMessage message) Measures the FCM payload a notification has on this leg,data.iizi.notificationincluded, for a refusal before anything is sent (PUSH-WIRE-CONTRACT section 6.3).send(SubscriptionRecord subscription, PushMessage message, long ttlMillis, DeliveryUrgency urgency) Sends a notification to one subscription.
Field Details
MAX_PAYLOAD
public static final int MAX_PAYLOADThe largest message payload FCM accepts, in bytes: the notification and the data together (Firebase: "maximum payload for both message types is 4096 bytes"). How FCM counts is not published;payloadSize(JSONObject)counts the compact JSON text of the two objects, which is what FCM is given.- See Also:
Constructor Details
FcmTransport
Constructor.- Parameters:
client- The started Jetty client.configs- The application configurations.
Method Details
getKind
Description copied from interface:IPushTransportGets the transport kind.- Specified by:
getKindin interfaceIPushTransport- Returns:
- The kind.
getState
Description copied from interface:IPushTransportGets whether this transport can send for an application.- Specified by:
getStatein interfaceIPushTransport- Parameters:
appId- The application ID,*for iiziRun.- Returns:
- The state, never null.
send
public TransportResult send(SubscriptionRecord subscription, PushMessage message, long ttlMillis, DeliveryUrgency urgency) Description copied from interface:IPushTransportSends a notification to one subscription. Blocks the calling (virtual) thread until the push service has answered or the request timed out.- Specified by:
sendin interfaceIPushTransport- Parameters:
subscription- The subscription.message- The notification.ttlMillis- The effective time-to-live in milliseconds, already defaulted and clamped by the engine; converted to seconds at the header only.urgency- The effective urgency.- Returns:
- The result, never null; a failure is an outcome, never an exception.
checkToken
Checks the Firebase project of a registration token at subscribe time (CC-PUSHDEV-VALIDATE-ONLY): postsbuildMessage(Target,PushMessage,long,DeliveryUrgency,boolean)to the token withvalidate_onlyand the application's credentials, so FCM validates the request and delivers nothing. 200 isFcmTransport.ProjectVerdict.MATCHES;SENDER_ID_MISMATCHisFcmTransport.ProjectVerdict.ANOTHER, with the detailsend(SubscriptionRecord, PushMessage, long, DeliveryUrgency)would report; anything else isFcmTransport.ProjectVerdict.UNKNOWN- an application without a configured transport, no OAuth token, FCM unreachable or slower thanCHECK_TIMEOUT_SECONDS, a stale or malformed token, a credential refusal (which drops the cached OAuth token, as a send does) or a transient error.The two definitive verdicts are kept per application, project and token, at most
VERDICTS, so that a device registering one token for many rows costs one request; an unknown one is asked again next time. Blocking: call it off any worker or connection thread.- Parameters:
appId- The application.registrationToken- The device's FCM token.- Returns:
- The outcome, never null.
buildMessage
public static JSONObject buildMessage(String registrationToken, PushMessage message, long ttlMillis, DeliveryUrgency urgency) Builds the FCM v1 request body for a registration token, assend(SubscriptionRecord, PushMessage, long, DeliveryUrgency)posts it:buildMessage(Target,PushMessage,long,DeliveryUrgency,boolean)withFcmTransport.Target.token(String)and novalidate_only.- Parameters:
registrationToken- The device token.message- The notification.ttlMillis- The TTL.urgency- The urgency.- Returns:
- The
{"message":{...}}object.
buildMessage
public static JSONObject buildMessage(FcmTransport.Target target, PushMessage message, long ttlMillis, DeliveryUrgency urgency, boolean validateOnly) Builds the FCM v1 request body for a target, a registration token or a Topic, delivering or validating only.The
datavalues, ruled 2026-09-22 (CC-PUSH-FCM-MAP ruling 3): every value is the text FCM requires and the receiver cannot tell the type back. A number is its decimal text (7to"7"), a boolean"true"or"false", an object or an array its JSON text, a JSON null the empty string, a string itself. The members carried indata(DATA_MEMBERSandactions) follow the same rule and, ruled the same day (ruling 6), overwrite an applicationdatakey of the same name; PUSH-WIRE-CONTRACT section 6.2 lists those names as reserved.data.iiziis the JSON text of the server's object withnotificationadded: the content object without itsdata, carried on this leg only (CC-PUSH-FCM-DATA-IIZI).validate_only(P4-13, ruling P13-2): FCM'smessages:sendtakes it as a member of the request besidemessage, never inside it, and validates the message without delivering it. It is written only when asked, so a body that does not ask is the bodysend(SubscriptionRecord, PushMessage, long, DeliveryUrgency)posts.- Parameters:
target- The registration token or the Topic.message- The notification.ttlMillis- The TTL.urgency- The urgency.validateOnly- true to have FCM validate the request and deliver nothing.- Returns:
- The
{"message":{...}}object, with"validate_only":truebesidemessagewhen asked.
payloadSize
Measures the FCM payload a notification has on this leg,data.iizi.notificationincluded, for a refusal before anything is sent (PUSH-WIRE-CONTRACT section 6.3). The token, the TTL and the urgency do not count and are stood in for.- Parameters:
message- The notification.- Returns:
- The size in bytes.
dispose
public void dispose()Description copied from interface:IPushTransportReleases the transport's resources.- Specified by:
disposein interfaceIPushTransport