Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 49 additions & 11 deletions Providers/Resgrid.Providers.Messaging/NovuProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ private async Task<bool> UpdateSubscriberFcm(string id, string token, string fcm
}
}

private async Task<bool> UpdateSubscriberApns(string id, string token, string apnsId)
private async Task<bool> UpdateSubscriberApns(string id, string token, string apnsId, string fcmId)
{
try
{
Expand All @@ -144,16 +144,40 @@ private async Task<bool> UpdateSubscriberApns(string id, string token, string ap
request.Headers.Add("idempotency-key", Guid.NewGuid().ToString());
request.Headers.Add("Authorization", $"ApiKey {ChatConfig.NovuSecretKey}");

var payload = new
string jsonContent = string.Empty;
if (!string.IsNullOrWhiteSpace(apnsId))
{
providerId = "apns",
credentials = new
var payload = new
{
deviceTokens = new string[] { token }
},
integrationIdentifier = apnsId
};
string jsonContent = JsonConvert.SerializeObject(payload, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
providerId = "apns",
credentials = new
{
deviceTokens = new string[] { token }
},
integrationIdentifier = apnsId
};

jsonContent = JsonConvert.SerializeObject(payload, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}
else if (!string.IsNullOrWhiteSpace(fcmId))
{
var payload = new
{
providerId = "fcm",
credentials = new
{
deviceTokens = new string[] { token }
},
integrationIdentifier = fcmId
};

jsonContent = JsonConvert.SerializeObject(payload, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
}

if (string.IsNullOrWhiteSpace(jsonContent))
{
return false;
}

request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.SendAsync(request);
Expand Down Expand Up @@ -197,7 +221,7 @@ public async Task<bool> UpdateUserSubscriberFcm(string userId, string code, stri

public async Task<bool> UpdateUserSubscriberApns(string userId, string code, string token)
{
return await UpdateSubscriberApns($"{code}_User_{userId}", token, ChatConfig.NovuResponderApnsProviderId);
return await UpdateSubscriberApns($"{code}_User_{userId}", token, ChatConfig.NovuResponderApnsProviderId, null);
}

public async Task<bool> UpdateUnitSubscriberFcm(int unitId, string code, string token)
Expand All @@ -207,7 +231,8 @@ public async Task<bool> UpdateUnitSubscriberFcm(int unitId, string code, string

public async Task<bool> UpdateUnitSubscriberApns(int unitId, string code, string token)
{
return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, ChatConfig.NovuUnitApnsProviderId);
//return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, ChatConfig.NovuUnitApnsProviderId);
return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, null, ChatConfig.NovuUnitFcmProviderId);
}
Comment on lines 233 to 237
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

iOS unit registrations are now pushed into the wrong provider

UpdateUnitSubscriberApns no longer stores iOS tokens under the APNS integration and instead pushes them into the FCM provider. APNS device tokens are 64‑char hex strings that must be registered against an APNS integration; feeding them to FCM results in INVALID_ARGUMENT/UNREGISTERED responses because FCM expects its own registration tokens (longer base64 strings) and rejects APNS tokens outright. The net effect is that unit notifications on iOS will stop delivering. Please keep iOS registrations on the APNS integration (as before) or ensure the caller passes real FCM registration tokens before switching providers. (docs.novu.co)

🤖 Prompt for AI Agents
In Providers/Resgrid.Providers.Messaging/NovuProvider.cs around lines 232-236,
UpdateUnitSubscriberApns is registering iOS APNS tokens against the FCM
provider; change the call to register tokens with the APNS integration instead.
Replace the current call that passes the FCM provider id with the APNS provider
id (e.g. use ChatConfig.NovuUnitApnsProviderId) so iOS 64‑char hex device tokens
are stored under APNS, not FCM.


private async Task<bool> SendNotification(string title, string body, string recipientId, string eventCode,
Expand Down Expand Up @@ -259,6 +284,19 @@ private async Task<bool> SendNotification(string title, string body, string reci
type = type,
}
},
apns = new
{
badge = count,
sound = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
type = type,
category = channelName,
eventCode = eventCode,
},
Comment on lines 288 to 316
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix the FCM APNS override payload structure

The new overrides.fcm.apns block sets badge, sound, etc. directly on the APNS object, but FCM expects an ApnsConfig with payload.aps.* fields. With the current shape, FCM silently drops these keys, so iOS devices never receive the badge or custom sound. Please wrap the fields under payload.aps (and keep headers if needed) to match the documented structure.

-               apns = new
-               {
-                   badge = count,
-                   sound = new
-                   {
-                       name = sound,
-                       critical = channelName == "calls" ? 1 : 0,
-                       volume = 1.0f
-                   },
-                   type = type,
-                   category = channelName,
-                   eventCode = eventCode,
-               },
+               apns = new
+               {
+                   payload = new
+                   {
+                       aps = new
+                       {
+                           badge = count,
+                           sound = new
+                           {
+                               name = sound,
+                               critical = channelName == "calls" ? 1 : 0,
+                               volume = 1.0f
+                           },
+                           category = channelName,
+                           @event = eventCode,
+                           customType = type
+                       }
+                   }
+               },

(Adjust the exact keys you need under aps; Apple expects them inside payload.aps.) (novu-preview.mintlify.app)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
apns = new
{
badge = count,
sound = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
type = type,
category = channelName,
eventCode = eventCode,
},
apns = new
{
payload = new
{
aps = new
{
badge = count,
sound = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
category = channelName,
@event = eventCode,
customType = type
}
}
},
🤖 Prompt for AI Agents
In Providers/Resgrid.Providers.Messaging/NovuProvider.cs around lines 287 to
299, the APNS fields (badge, sound, type, category, eventCode) are being placed
directly under overrides.fcm.apns which FCM will ignore; wrap these keys under
payload.aps (e.g. payload: { aps: { badge: ..., sound: ..., ... } }) so the APNS
payload matches FCM/Apple expectations, and preserve any headers (apns.headers)
if required; adjust the object shape accordingly so badge and custom sound are
delivered to iOS devices.

},
apns = new Dictionary<string, object>
{
Expand Down
Loading