Search code examples
azureasp.net-coreazure-notificationhubweb-push

Web Push via Azure Notification Hubs: JSON value could not be converted to Microsoft.NotificationHubs.Contracts.LegacyModels.BrowserPushSubscription


I'm having a very difficult time making Web Push notifications work via Azure Notification Hubs.

Browser notifications support has been announced in March 2024, but it looks like any kind of proper availability is still missing, despite this and this pull requests waiting for several months to be merged.

I spent a decent amount of time trying some esoteric suggestions like this one, but I'm constantly failing at creating a new Installation for a Browser-based push notification target.

The error is:

Microsoft.Azure.NotificationHubs.Messaging.BadRequestException:  The provided request body is invalid. Error: The JSON value could not be converted to Microsoft.NotificationHubs.Contracts.LegacyModels.BrowserPushSubscription. Path: $.pushChannel | LineNumber: 0 | BytePositionInLine: 487.. TrackingId:dacca0ce-6af8-45b5-9ab5-ea1d1f78a57e,TimeStamp:9/21/2024 6:02:33 PM +00:00
   at Microsoft.Azure.NotificationHubs.NotificationHubClient.SendRequestAsync(HttpRequestMessage request, String trackingId, HttpStatusCode[] successfulResponseStatuses, CancellationToken cancellationToken)
   at Microsoft.Azure.NotificationHubs.NotificationHubClient.SendRequestAsync(HttpRequestMessage request, String trackingId, HttpStatusCode successfulResponseStatus, CancellationToken cancellationToken)
   at Microsoft.Azure.NotificationHubs.NotificationHubClient.<>c__DisplayClass91_0.<<CreateOrUpdateInstallationAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Azure.NotificationHubs.NotificationHubRetryPolicy.RunOperation[T](Func`2 operation, CancellationToken cancellationToken)
   at Microsoft.Azure.NotificationHubs.NotificationHubRetryPolicy.RunOperation[T](Func`2 operation, CancellationToken cancellationToken)
   at Microsoft.Azure.NotificationHubs.NotificationHubClient.CreateOrUpdateInstallationAsync(Installation installation, CancellationToken cancellationToken)

It looks like under the hood the Notification Hubs SDK is Serializing the BrowserPushSubscription instance a JSON string to be then saved into the PushChannel property.

Unfortunately, the Notification Hubs endpoint doesn't like it at all, because it's not capable of properly de-serializing it.

I tried to manually serialize it into different formats (IE: a common way to represent the browser subscription JSON is by grouping P256DH and Auth properties into a Keys subproperty), with no luck.

Is there anybody that got it working? Thanks


Solution

  • Construct a manual JSON payload by yourself and send it via the Notification Hub REST API, bypassing the SDK.

    JSON Payload :

    {
      "installationId": "your-installation-id",
      "platform": "browser",
      "pushChannel": {
        "endpoint": "https://your-endpoint-url.com",
        "keys": {
          "p256dh": "your-p256dh-key",
          "auth": "your-auth-key"
        }
      },
      "tags": ["your-tag"]
    }
    

    Use REST API Directly:

    curl -X PUT \
      https://<your-notification-hub-namespace>.servicebus.windows.net/<your-hub-name>/installations/<your-installation-id>?api-version=2020-06 \
      -H "Content-Type: application/json" \
      -H "Authorization: SharedAccessSignature sr=<resource-uri>&sig=<signature>&se=<expiry>&skn=<policy-name>" \
      -d '{
        "installationId": "your-installation-id",
        "platform": "browser",
        "pushChannel": {
          "endpoint": "https://your-endpoint-url.com",
          "keys": {
            "p256dh": "your-p256dh-key",
            "auth": "your-auth-key"
          }
        },
        "tags": ["your-tag"]
      }'
    

    Generate the Shared Access Signature (SAS), you can use Azure Portal or a script like this in C#

    Code:

    string resourceUri = "<namespace>.servicebus.windows.net";
    string keyName = "<your-key-name>";
    string key = "<your-primary-or-secondary-key>";
    
    TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
    int expiry = Convert.ToInt32(sinceEpoch.TotalSeconds) + 3600; // 1 hour expiration
    
    string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;
    HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
    string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
    
    string sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
        WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry, keyName);
    
    Console.WriteLine(sasToken);
    

    enter image description here

    Response (Success):

    HTTP/1.1 200 OK
    Date: Tue, 24 Sep 2024 14:02:33 GMT
    Content-Length: 0
    Server: Microsoft-HTTPAPI/2.0
    TrackingId: 3429bafa-212b-44ab-9b7e-caa3f217b7f5
    x-ms-request-id: 7db24283-22c7-4e93-b865-9f6f063d5c7f
    x-ms-version: 2020-02-09