In the evolving world of app development, ensuring real-time engagement with users is crucial. Apple Push Notification Service (APNs) enables developers to send notifications to iOS devices, and with the introduction of Live Activities in iOS, keeping users updated about ongoing tasks is easier than ever. This guide demonstrates how to use .NET to send Live Activity push notifications using APNs.

Prerequisites

Before diving into the code, ensure you have the following:

  1. Apple Developer Account with access to APNs.
  2. P8 Certificate downloaded from the Apple Developer Portal.
  3. Your Team ID, Key ID, and Bundle ID of the iOS application.
  4. .NET SDK installed on your system.

Overview of the Code

The provided ApnsService class encapsulates the logic to interact with APNs for sending push notifications, including Live Activities. Let’s break it down step-by-step:

1. Initializing APNs Service

The constructor sets up the base URI for APNs:

  • Use https://api.push.apple.com for production.
  • Use https://api.development.push.apple.com for the development environment.
_httpClient = new HttpClient { BaseAddress = new Uri("https://api.development.push.apple.com:443") };

2. Generating the JWT Token

APNs requires a JWT token for authentication. This token is generated using:

  • Team ID: Unique identifier for your Apple Developer account.
  • Key ID: Associated with the P8 certificate.
  • ES256 Algorithm: Uses the private key in the P8 certificate to sign the token.
private string GetProviderToken()
{
    double epochNow = (int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
    Dictionary<string, object> payload = new Dictionary<string, object>
    {
        { "iss", _teamId },
        { "iat", epochNow }
    };
    var extraHeaders = new Dictionary<string, object>
    {
        { "kid", _keyId },
        { "alg", "ES256" }
    };

    CngKey privateKey = GetPrivateKey();

    return JWT.Encode(payload, privateKey, JwsAlgorithm.ES256, extraHeaders);
}

3. Loading the Private Key

The private key is extracted from the .p8 file using BouncyCastle.

private CngKey GetPrivateKey()
{
    using (var reader = File.OpenText(_p8CertificateFileLocation))
    {
        ECPrivateKeyParameters ecPrivateKeyParameters = (ECPrivateKeyParameters)new PemReader(reader).ReadObject();
        var x = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded();
        var y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded();
        var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned();

        return EccKey.New(x, y, d);
    }
}

4. Sending the Notification

The SendApnsNotificationAsync method handles:

  • Building the request with headers and payload.
  • Adding apns-push-type as liveactivity for Live Activity notifications.
  • Adding a unique topic for Live Activities by appending .push-type.liveactivity to the Bundle ID.
public async Task SendApnsNotificationAsync<T>(string deviceToken, string pushType, T payload) where T : class
    {
        var jwtToken = GetProviderToken();
        var jsonPayload = JsonSerializer.Serialize(payload);
        // Prepare HTTP request
        var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}")
        {
            Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json")
        };
        request.Headers.Add("authorization", $"Bearer {jwtToken}");
        request.Headers.Add("apns-push-type", pushType);
        if (pushType == "liveactivity")
        {
            request.Headers.Add("apns-topic", _bundleId + ".push-type.liveactivity");
            request.Headers.Add("apns-priority", "10");
        }
        else
        {
            request.Headers.Add("apns-topic", _bundleId);
        }
        request.Version = new Version(2, 0);
        // Send the request
        var response = await _httpClient.SendAsync(request);
        if (response.IsSuccessStatusCode)
        {
            Console.WriteLine("Push notification sent successfully!");
        }
        else
        {
            var responseBody = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"Failed to send push notification: {response.StatusCode} - {responseBody}");
        }
    }

Sample Usage

Here’s how you can use the ApnsService class to send a Live Activity notification:

var apnsService = new ApnsService();
 // Example device token (replace with a real one)
 var pushDeviceToken = "808f63xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 // Create the payload for the Live Activity
 var notificationPayload = new PushNotification
 {
     Aps = new Aps
     {
         Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
         Event = "update",
         ContentState = new ContentState
         {
             Status = "Charging",
             ChargeAmount = "65 Kw",
             DollarAmount = "$11.80",
             timeDuration = "00:28",
             Percentage = 80
         },
     }
 };
 await apnsService.SendApnsNotificationAsync(pushDeviceToken, "liveactivity", notificationPayload);

Key Points to Remember

  1. JWT Token Validity: Tokens expire after 1 hour. Ensure you regenerate tokens periodically.
  2. APNs Endpoint: Use the correct environment (production or development) based on your app stage.
  3. Error Handling: Handle HTTP responses carefully. Common issues include invalid tokens or expired certificates.

Debugging Tips

  • Ensure your device token is correct and valid.
  • Double-check your .p8 file, Team ID, Key ID, and Bundle ID.
  • Use tools like Postman to test your APNs requests independently.

Conclusion

Sending Live Activity push notifications using .NET involves integrating APNs with proper authentication and payload setup. The ApnsService class demonstrated here provides a robust starting point for developers looking to enhance user engagement with real-time updates.🚀