Pinqponq.Push.Fcm
1.1.0
Prefix Reserved
dotnet add package Pinqponq.Push.Fcm --version 1.1.0
NuGet\Install-Package Pinqponq.Push.Fcm -Version 1.1.0
<PackageReference Include="Pinqponq.Push.Fcm" Version="1.1.0" />
<PackageVersion Include="Pinqponq.Push.Fcm" Version="1.1.0" />
<PackageReference Include="Pinqponq.Push.Fcm" />
paket add Pinqponq.Push.Fcm --version 1.1.0
#r "nuget: Pinqponq.Push.Fcm, 1.1.0"
#:package Pinqponq.Push.Fcm@1.1.0
#addin nuget:?package=Pinqponq.Push.Fcm&version=1.1.0
#tool nuget:?package=Pinqponq.Push.Fcm&version=1.1.0
Pinqponq.Push.Fcm
Firebase Cloud Messaging (FCM) sender for backends that push to mobile apps. You give it a
Firebase service account, and it gives you an IFcmSender that sends notifications and data-only
pushes to device tokens, sends to topics and manages topic subscriptions. Every send to tokens
returns one result per token that says whether the token is invalid (delete it) or the push
provider credentials are broken (fix them, every push to that platform fails).
The client side is in deveng-core-kmp (pinqponq/deveng-core-kmp#116): it registers the device with FCM and hands the token to your backend.
Install
dotnet add package Pinqponq.Push.Fcm
Requirements
- .NET 8.0, 9.0, or 10.0
- A Firebase service account JSON (Firebase console → Project settings → Service accounts)
- An
ILogServiceregistration, for example fromPinqponq.Observability
Quick start
{
"Fcm": {
"CredentialPath": "firebase-service-account.json"
}
}
builder.Services.AddPinqponqObservability(/* … */);
builder.Services.AddPinqponqFcm(builder.Configuration.GetSection(FcmOptions.SectionName));
public sealed class ReminderNotifier(IFcmSender fcmSender)
{
public Task<IReadOnlyList<FcmSendResult>> Notify(IReadOnlyCollection<string> deviceTokens) =>
fcmSender.SendToTokens(
new FcmMessage { Title = "Reminder", Body = "Your session starts in 10 minutes" },
deviceTokens);
}
The service account JSON is a secret: keep the file out of the repository, or read the JSON
from Vault (Pinqponq.Configuration.Vault) into Fcm:CredentialJson.
Options
| Option | Default | Meaning |
|---|---|---|
IsEnabled |
true |
false sends nothing and needs no credential (developer machines). |
CredentialPath |
— | Service account JSON file. A relative path is resolved against the app directory. |
CredentialJson |
— | The service account JSON itself. Set this or CredentialPath, not both. |
DataOnlyAndroidPriority |
Normal |
Android priority of SendDataOnlyToTokens. High wakes the device at once. Use it only when the app shows a notification for every such push, or Android lowers the app's priority. |
IsSendFailureLoggingEnabled |
true |
false turns off the fcm_send_partial_failure and fcm_provider_credential_invalid log records. Use it when your application logs the returned results itself, so each failure is logged once. Topic log records stay on. |
The options are validated at startup: when IsEnabled is true, exactly one credential must
be set, the file must exist and the JSON must be a service account.
Sending
| Method | Sends |
|---|---|
SendToTokens(message, tokens) |
A notification (title, body, optional image and data) to device tokens. |
SendDataOnlyToTokens(data, tokens) |
Data without a notification block. The app decides what to show. |
SendToTopic(topic, message) |
A notification to every device subscribed to the topic. |
SubscribeToTopic(tokens, topic) / UnsubscribeFromTopic(tokens, topic) |
Topic membership. Rejected tokens are logged. |
- More than 500 tokens are split into several FCM calls (1,000 for topic changes), so you can pass any number.
- No tokens, or
IsEnabledset tofalse, sends nothing and returns an empty list. FcmMessage.AndroidandFcmMessage.Apnstake FirebaseAdmin'sAndroidConfigandApnsConfigas they are. The package sets nothing there by default: no sound, badge or content-available.
Results and invalid tokens
SendToTokens and SendDataOnlyToTokens return an FcmSendResult per token, in the order the
tokens were given:
| Field | Meaning |
|---|---|
IsSuccess |
FCM accepted the push. Acceptance is not proof of delivery. |
ErrorCode / ErrorMessage |
FCM's error code (Unregistered, ThirdPartyAuthError, …), or the general Firebase code when the request failed below the messaging layer. |
IsTokenInvalid |
FCM answered Unregistered: the token will never work again. |
IsProviderCredentialInvalid |
FCM answered ThirdPartyAuthError: the APNs key (or another provider credential) is invalid. Not the token's fault. |
InvalidArgument and SenderIdMismatch are not treated as invalid tokens: a bad payload or the
wrong Firebase project gives the same answer for every token, and deleting them all would be
wrong. They come back as failures with their ErrorCode.
To delete invalid tokens, implement IFcmInvalidTokenHandler and register it. The sender calls it
after each send with the tokens FCM reported as Unregistered:
public sealed class DeviceTokenCleaner(AppDbContext dbContext) : IFcmInvalidTokenHandler
{
public Task HandleInvalidTokens(IReadOnlyList<string> invalidTokens, CancellationToken cancellationToken = default) =>
dbContext.Devices.Where(device => invalidTokens.Contains(device.PushToken)).ExecuteDeleteAsync(cancellationToken);
}
builder.Services.AddScoped<IFcmInvalidTokenHandler, DeviceTokenCleaner>();
Logging
Failures are written through ILogService with the event names in FcmLogEvents. Tokens are
credentials, so a record holds only the last 6 characters of each token.
| Event | Level | When |
|---|---|---|
fcm_send_partial_failure |
Warning | Some tokens of a send failed. Off when IsSendFailureLoggingEnabled is false. |
fcm_provider_credential_invalid |
Error | A send failed with ThirdPartyAuthError. Off when IsSendFailureLoggingEnabled is false. |
fcm_topic_subscribe_partial_failure / fcm_topic_unsubscribe_partial_failure |
Warning | FCM rejected tokens of a topic change. Each failure has FCM's reason. |
fcm_disabled |
Debug | IsEnabled is false and a send was skipped. |
Firebase app
The package creates its own named Firebase app (Pinqponq.Push.Fcm) on the first send and
reuses it afterwards. A default Firebase app your application creates for something else is
left alone.
What stays in your application
Device token storage, notification texts and their translations, topic names and segments, user preferences, scheduling, and platform-specific pushes such as APNs widget updates.
Repository
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- FirebaseAdmin (>= 3.4.0)
- Google.Apis.Auth (>= 1.77.0)
- Microsoft.Extensions.Configuration (>= 10.0.10)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Microsoft.Extensions.Options (>= 10.0.10)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.10)
- Pinqponq.Observability.Abstractions (>= 1.0.0)
-
net8.0
- FirebaseAdmin (>= 3.4.0)
- Google.Apis.Auth (>= 1.77.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- Pinqponq.Observability.Abstractions (>= 1.0.0)
-
net9.0
- FirebaseAdmin (>= 3.4.0)
- Google.Apis.Auth (>= 1.77.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
- Microsoft.Extensions.Options (>= 9.0.18)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.18)
- Pinqponq.Observability.Abstractions (>= 1.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.