Skip to content

Commit 6c327bb

Browse files
committed
Update README documentation for v5.0.0
1 parent bda66be commit 6c327bb

1 file changed

Lines changed: 185 additions & 65 deletions

File tree

README.md

Lines changed: 185 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,25 @@
33

44
# .NET Core Push Notifications for Web, Android and iOS
55
Send notifications to:
6-
- **iOS** - Apple Push Notifications (via Latest Apple Push Notifications HTTP2 JWT API)
7-
- **Android** - via Firebase Cloud Messaging (via Latest Firebase HTTP v1 API)
8-
- **Web** - via Firebase Cloud Messaging (via Latest Firebase HTTP v1 API)
6+
- **iOS** - Apple Push Notifications (via Latest Apple Push Notifications HTTP2 JWT API)
7+
- **Android** - via Firebase Cloud Messaging (via Latest Firebase HTTP v1 API)
8+
- **Web** - via Firebase Cloud Messaging (via Latest Firebase HTTP v1 API)
99

10-
CorePush is a simple lightweight library with minimal overhead. Send notifications to Android and Web using Firebase Cloud Messaging and iOS APN with JWT HTTP/2 API.
10+
CorePush is a simple lightweight library with **no external dependencies**. It uses built-in .NET cryptography for JWT token generation and signing. Send notifications to Android and Web using Firebase Cloud Messaging and iOS APN with JWT HTTP/2 API.
11+
12+
Both `ApnSender` and `FirebaseSender` are thread safe.
1113

1214
# Installation - NuGet
1315

14-
Version 4.0.0+ requires .NET7.0. For earlier versions please use v3.1.1 of the library as it's targeting netstandard2.0, though please note, it uses legacy FCM send API.
15-
The easiest way to get started with CorePush is to use [nuget](https://www.nuget.org/packages/CorePush) package.
16+
| Package Version | .NET Version |
17+
|---|---|
18+
| v5.0.0+ | .NET 10 |
19+
| v4.4.0 | .NET 9 |
20+
| v4.2.1 – v4.3.0 | .NET 8 |
21+
22+
For earlier versions please use v3.1.1 of the library as it targets netstandard2.0, though please note, it uses the legacy FCM send API.
23+
24+
The easiest way to get started with CorePush is to use [NuGet](https://www.nuget.org/packages/CorePush) package.
1625

1726
dotnet cli:
1827
```
@@ -24,110 +33,222 @@ Package Manager Console:
2433
Install-Package CorePush
2534
```
2635

27-
Check out Tester project [Program.cs](https://github.com/andrei-m-code/net-core-push-notifications/blob/master/CorePush.Tester/Program.cs) for a quick getting started.
36+
Check out the Tester project [Program.cs](https://github.com/andrei-m-code/net-core-push-notifications/blob/master/CorePush.Tester/Program.cs) for a quick getting started example.
2837

2938
# Firebase Cloud Messages for Android, iOS and Web
3039

31-
To start sending Firebase messages you need to have Google Project ID and JWT Bearer token. Steps to generate JWT bearer token:
32-
1. Enable HTTP v1 API if you haven't done it yet. Go here for instructions: https://console.firebase.google.com/project/YOUR-GOOGLE-PROJECT-ID/settings/cloudmessaging/ Your project ID looks like this: my-project-123456.
33-
2. From that page you can also go to "Manage Service Accounts". Here is the link: https://console.cloud.google.com/iam-admin/serviceaccounts and select your project.
34-
3. Create Service Account with "Firebase Service Management Service Agent" role.
35-
4. Download Service Account JSON file and use it to configure FirebaseSender either by deserializing it into FirebaseSettings or by directly passing json string into the constructor.
40+
To start sending Firebase messages you need a Service Account JSON key file from your Google project:
41+
1. Go to [Firebase Console](https://console.firebase.google.com) > Project Settings > Service Accounts.
42+
2. Click "Generate new private key" to download the JSON key file.
43+
3. Use the JSON file contents to configure `FirebaseSender` either by deserializing it into `FirebaseSettings` or by passing the JSON string directly into the constructor.
3644

37-
Sending messages is very simple so long as you know the format:
45+
Sending messages:
3846

3947
```csharp
40-
var firebaseSettingsJson = await File.ReadAllTextAsync('./link/to/my-project-123345-e12345.json');
48+
var firebaseSettingsJson = await File.ReadAllTextAsync("./link/to/my-project-123345-e12345.json");
4149
var fcm = new FirebaseSender(firebaseSettingsJson, httpClient);
42-
await fcm.SendAsync(payload);
50+
var result = await fcm.SendAsync(payload);
51+
52+
if (!result.IsSuccessStatusCode)
53+
{
54+
Console.WriteLine($"Firebase error: {result.Error} - {result.Message}");
55+
}
4356
```
57+
4458
Useful links:
4559
- Message formats: https://firebase.google.com/docs/cloud-messaging/customize-messages/set-message-type
4660
- Migrating from legacy API: https://firebase.google.com/docs/cloud-messaging/migrate-v1
4761

4862
## Firebase iOS notifications
49-
If you want to use Firebase to send iOS notifications, please checkout this article: https://firebase.google.com/docs/cloud-messaging/ios/certs.
50-
The library serializes notification object to JSON and sends it to Google cloud. Here is more details on the expected payloads for FCM https://firebase.google.com/docs/cloud-messaging/concept-options#notifications.
63+
If you want to use Firebase to send iOS notifications, please check out this article: https://firebase.google.com/docs/cloud-messaging/ios/certs.
64+
The library serializes the notification object to JSON and sends it to Google Cloud. See the expected payload formats for FCM here: https://firebase.google.com/docs/cloud-messaging/concept-options#notifications.
5165

5266
## Firebase Notification Payload Example
5367

5468
```json
5569
{
56-
"message":{
57-
"token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvD this is DEVICE_TOKEN",
58-
"notification":{
59-
"title":"Match update",
60-
"body":"Arsenal goal in added time, score is now 3-0"
61-
},
62-
"android":{
63-
"ttl":"86400s",
64-
"notification"{
65-
"click_action":"OPEN_ACTIVITY_1"
66-
}
67-
},
68-
"apns": {
69-
"headers": {
70-
"apns-priority": "5",
71-
},
72-
"payload": {
73-
"aps": {
74-
"category": "NEW_MESSAGE_CATEGORY"
75-
}
76-
}
77-
},
78-
"webpush":{
79-
"headers":{
80-
"TTL":"86400"
81-
}
82-
}
83-
}
84-
}
70+
"message": {
71+
"token": "DEVICE_TOKEN",
72+
"notification": {
73+
"title": "Match update",
74+
"body": "Arsenal goal in added time, score is now 3-0"
75+
},
76+
"android": {
77+
"ttl": "86400s",
78+
"notification": {
79+
"click_action": "OPEN_ACTIVITY_1"
80+
}
81+
},
82+
"apns": {
83+
"headers": {
84+
"apns-priority": "5"
85+
},
86+
"payload": {
87+
"aps": {
88+
"category": "NEW_MESSAGE_CATEGORY"
89+
}
90+
}
91+
},
92+
"webpush": {
93+
"headers": {
94+
"TTL": "86400"
95+
}
96+
}
97+
}
98+
}
8599
```
86100

87101
# Apple Push Notifications
88102

89-
To send notifications to Apple devices you have to create a publisher profile and pass settings object with necessary parameters to ApnSender constructor. Apn Sender will create and sign JWT token and attach it to every request to Apple servers:
90-
1. P8 private key - p8 certificate generated in itunes. Just 1 line string without spaces, ----- or line breaks.
91-
2. Private key id - 10 digit p8 certificate id. Usually a part of a downloadable certificate filename e.g. AuthKey_IDOFYOURCR.p8</param>
92-
3. Team id - Apple 10 digit team id from itunes
93-
4. App bundle identifier - App slug / bundle name e.g.com.mycompany.myapp
94-
5. Server type - Development or Production APN server
103+
To send notifications to Apple devices you need to create a push notification key in the Apple Developer portal and pass the settings to the `ApnSender` constructor. `ApnSender` will create and sign a JWT token and attach it to every request to Apple servers:
104+
1. **P8 private key** - generated in the Apple Developer portal. Just the base64 content without headers, spaces, or line breaks.
105+
2. **Private key id** - 10 digit p8 certificate id. Usually part of the downloadable certificate filename, e.g. AuthKey_IDOFYOURCR.p8
106+
3. **Team id** - Apple 10 digit team id
107+
4. **App bundle identifier** - e.g. com.mycompany.myapp
108+
5. **Server type** - Development or Production APN server
95109

96110
```csharp
111+
var settings = new ApnSettings
112+
{
113+
AppBundleIdentifier = "com.mycompany.myapp",
114+
P8PrivateKey = "YOUR_P8_KEY_CONTENT",
115+
P8PrivateKeyId = "IDOFYOURCR",
116+
TeamId = "YOURTEAMID",
117+
ServerType = ApnServerType.Production
118+
};
119+
97120
var apn = new ApnSender(settings, httpClient);
98-
await apn.SendAsync(notification, deviceToken);
121+
var result = await apn.SendAsync(notification, deviceToken);
122+
123+
if (!result.IsSuccessStatusCode)
124+
{
125+
Console.WriteLine($"APN error: {result.Error} - {result.Message}");
126+
}
99127
```
128+
100129
Please see Apple notification payload examples here: https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW1.
101-
Tip: To send properties like {"content-available": true} you can use System.Text.Json attributes over C# properties like `[JsonPropertyName("content-available")]`.
130+
Tip: To send properties like `{"content-available": true}` you can use `System.Text.Json` attributes over C# properties like `[JsonPropertyName("content-available")]`.
131+
132+
## SendAsync Options
133+
134+
`SendAsync` supports optional parameters for fine-grained control:
135+
136+
```csharp
137+
var result = await apn.SendAsync(
138+
notification,
139+
deviceToken,
140+
apnsId: "unique-notification-id", // optional unique ID for the notification
141+
apnsExpiration: 0, // 0 = immediate delivery or discard
142+
apnsPriority: 10, // 10 = immediate, 5 = power-saving
143+
apnPushType: ApnPushType.Alert, // Alert, Background, or Voip
144+
cancellationToken: ct);
145+
```
146+
147+
For silent background notifications, use `ApnPushType.Background` with priority `5` and include `"content-available": 1` in your payload.
102148

103149
## Example of notification payload
104-
You can find expected notification formats for different types of notifications in the documentation. To make it easier to get started, here is a simple example of visible notification (the one that you'll see in phone's notification center) for iOS:
150+
You can find expected notification formats for different types of notifications in the Apple documentation. Here is a simple example of a visible notification (the one that appears in the phone's notification center):
105151

106152
```csharp
107153
public class AppleNotification
108154
{
109155
public class ApsPayload
110156
{
157+
public class Alert
158+
{
159+
[JsonPropertyName("title")]
160+
public string Title { get; set; }
161+
162+
[JsonPropertyName("body")]
163+
public string Body { get; set; }
164+
}
165+
111166
[JsonPropertyName("alert")]
112-
public string AlertBody { get; set; }
113-
}
167+
public Alert AlertBody { get; set; }
114168

115-
// Your custom properties as needed
169+
[JsonPropertyName("badge")]
170+
public int Badge { get; set; }
171+
}
116172

117173
[JsonPropertyName("aps")]
118174
public ApsPayload Aps { get; set; }
119175
}
120176
```
177+
121178
Use `[JsonPropertyName("alert-type")]` attribute to serialize C# properties into JSON properties with dashes.
122179

123-
# Azure Functions and Azure App Service
124-
You may be getting this error when running in Azure Functions or Azure App Service:
180+
## Error Handling
181+
182+
Both `ApnSender` and `FirebaseSender` return a `PushResult`:
183+
184+
```csharp
185+
public record PushResult(
186+
int StatusCode,
187+
bool IsSuccessStatusCode,
188+
string Message,
189+
string Error);
125190
```
126-
System.Security.Cryptography.CryptographicException: The system cannot find the file specified. at
127-
System.Security.Cryptography.NCryptNative.ImportKey(SafeNCryptProviderHandle provider, Byte[] keyBlob, String format) at
128-
System.Security.Cryptography.CngKey.Import(Byte[] keyBlob, String curveName, CngKeyBlobFormat format, CngProvider provider)
191+
192+
For APN errors, you can compare against `ApnsErrorReasons` constants:
193+
194+
```csharp
195+
var result = await apn.SendAsync(notification, deviceToken);
196+
197+
if (result.Error == ApnsErrorReasons.BadDeviceToken)
198+
{
199+
// Remove invalid token from your database
200+
}
201+
else if (result.Error == ApnsErrorReasons.TooManyRequests)
202+
{
203+
// Retry after a delay
204+
}
129205
```
130-
The solution is to add this in the Environment Variables of your service: `WEBSITE_LOAD_USER_PROFILE: 1`. More info on the issue can be found [here](https://stackoverflow.com/questions/66367406/cngkey-system-security-cryptography-cryptographicexception-the-system-cannot-fin) and [here](https://stackoverflow.com/questions/46114264/x509certificate2-on-azure-app-services-azure-websites-since-mid-2017).
206+
207+
# HttpClient and Dependency Injection
208+
209+
Each `ApnSender` requires a **dedicated** `HttpClient` instance because it sets `BaseAddress` on the client. Do not share an `HttpClient` between multiple senders or other services. However, the underlying `HttpClientHandler` can be shared.
210+
211+
`IApnSender` and `IFirebaseSender` interfaces are provided for dependency injection:
212+
213+
```csharp
214+
// Using IHttpClientFactory (recommended)
215+
services.AddHttpClient<IApnSender, ApnSender>();
216+
services.AddHttpClient<IFirebaseSender, FirebaseSender>();
217+
```
218+
219+
If you send many APN messages at once, Apple may occasionally respond with HTTP 429. Wrap your calls in try/catch and retry as needed.
220+
221+
# Custom JSON Serializer
222+
223+
Both senders use `System.Text.Json` with camelCase naming by default. You can provide a custom serializer by implementing `IJsonSerializer`:
224+
225+
```csharp
226+
public interface IJsonSerializer
227+
{
228+
string Serialize(object obj);
229+
TObject Deserialize<TObject>(string json);
230+
}
231+
```
232+
233+
Pass your implementation to the sender constructor:
234+
235+
```csharp
236+
var apn = new ApnSender(settings, httpClient, myCustomSerializer);
237+
var fcm = new FirebaseSender(firebaseSettingsJson, httpClient, myCustomSerializer);
238+
```
239+
240+
# Migrating from v4 to v5
241+
242+
v5.0.0 removes the BouncyCastle dependency. JWT token generation and signing now use built-in .NET cryptography (`System.Security.Cryptography`). No code changes are required on your end — the public API is unchanged.
243+
244+
Key changes:
245+
- **Removed** BouncyCastle.Cryptography NuGet dependency
246+
- **Target framework** upgraded from .NET 9 to .NET 10
247+
- **Fixed** Base64URL encoding for JWT tokens
248+
- **Fixed** potential null reference in APN error deserialization
249+
- **Fixed** APN token cache key collision when using multiple key IDs
250+
- **Added** device token validation in `ApnSender`
251+
- **Added** `CancellationToken` propagation in `FirebaseSender`
131252

132253
# MIT License
133254

@@ -138,4 +259,3 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
138259
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
139260

140261
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
141-

0 commit comments

Comments
 (0)