Skip to content

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Nov 9, 2025

Summary by CodeRabbit

  • Improvements
    • Enhanced push notification handling with support for both iOS (APNS) and Android (FCM) device identifiers and a fallback path between them.
    • Expanded notification payloads with richer metadata (badges, structured sound settings, type, category, event code) and improved nested APNS/FCM override structures for more precise client behavior.
  • Bug Fixes
    • Sending is aborted when no valid device identifier is available to avoid empty sends.

@request-info
Copy link

request-info bot commented Nov 9, 2025

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 9, 2025

Walkthrough

Update to NovuProvider: UpdateSubscriberApns signature now accepts apnsId and fcmId; it builds an APNS payload when apnsId is present, else an FCM payload when fcmId is present and returns false if neither; callers updated; SendNotification adds APNS fields under overrides.fcm.android.

Changes

Cohort / File(s) Summary
NovuProvider updates
Providers/Resgrid.Providers.Messaging/NovuProvider.cs
Changed UpdateSubscriberApns signature to (string id, string token, string apnsId, string fcmId); payload construction now: build APNS payload when apnsId provided, else build FCM payload when fcmId provided, early-return false if no payload; updated callers to pass appropriate nulls; expanded SendNotification to include APNS fields (badge, sound {name, critical, volume}, type, category, eventCode) under overrides.fcm.android while retaining existing android/apns structures.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant NovuProvider
    participant PayloadBuilder
    participant NovuAPI

    Caller->>NovuProvider: UpdateSubscriberApns(id, token, apnsId?, fcmId?)
    alt apnsId provided
        NovuProvider->>PayloadBuilder: build credential { providerId: "apns", integrationIdentifier: apnsId }
    else fcmId provided
        NovuProvider->>PayloadBuilder: build credential { providerId: "fcm", integrationIdentifier: fcmId }
    else neither provided
        NovuProvider-->>Caller: return false (no payload)
    end

    PayloadBuilder-->>NovuProvider: jsonContent
    NovuProvider->>NovuAPI: POST /subscribers/update with jsonContent
    NovuAPI-->>NovuProvider: Response (success/failure)
    NovuProvider-->>Caller: Task<bool> result

    Note right of NovuProvider: SendNotification now injects APNS fields under overrides.fcm.android (badge, sound, type, category, eventCode)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify all caller updates pass correct nulls and no overloads remain.
  • Confirm early-return behavior when no credential is provided.
  • Validate JSON shapes for the new APNS fields under overrides.fcm.android align with Novu API.

Possibly Related PRs

Poem

🐰
I hopped through code with careful paws,
split APNS and FCM by cause,
badges, sounds, and fields in tow,
subscribers set — away they go! 🎶

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: evaluating FCM integration to forward APNS messages within the Novu provider implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84356ce and f157b83.

📒 Files selected for processing (1)
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Prefer functional patterns and immutable data where appropriate in C#
Use modern C# features appropriately
Use meaningful, descriptive names for types, methods, and parameters; avoid unclear abbreviations
Separate state from behavior (e.g., use records for state and place operations in separate static classes)
Prefer pure methods over methods with side effects
Use extension methods appropriately for domain-specific operations
Design for testability; avoid hidden dependencies inside methods and prefer explicit, pure functions
Minimize constructor injection; keep the number of injected dependencies small
Prefer composition with interfaces to extend behavior

Files:

  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs
🧬 Code graph analysis (1)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (3)
Core/Resgrid.Services/PushService.cs (5)
  • Task (31-48)
  • Task (50-55)
  • Task (57-75)
  • Task (77-82)
  • Task (89-120)
Core/Resgrid.Model/Providers/Models/INovuProvider.cs (11)
  • Task (20-20)
  • Task (31-31)
  • Task (40-40)
  • Task (49-49)
  • Task (58-58)
  • Task (67-67)
  • Task (82-83)
  • Task (90-90)
  • Task (92-92)
  • Task (94-94)
  • Task (96-96)
Core/Resgrid.Config/ChatConfig.cs (1)
  • ChatConfig (3-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f157b83 and 2292217.

📒 Files selected for processing (1)
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs (5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Prefer functional patterns and immutable data where appropriate in C#
Use modern C# features appropriately
Use meaningful, descriptive names for types, methods, and parameters; avoid unclear abbreviations
Separate state from behavior (e.g., use records for state and place operations in separate static classes)
Prefer pure methods over methods with side effects
Use extension methods appropriately for domain-specific operations
Design for testability; avoid hidden dependencies inside methods and prefer explicit, pure functions
Minimize constructor injection; keep the number of injected dependencies small
Prefer composition with interfaces to extend behavior

Files:

  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs
🧬 Code graph analysis (1)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (3)
Core/Resgrid.Services/PushService.cs (5)
  • Task (31-48)
  • Task (50-55)
  • Task (57-75)
  • Task (77-82)
  • Task (89-120)
Core/Resgrid.Model/Providers/Models/INovuProvider.cs (11)
  • Task (20-20)
  • Task (31-31)
  • Task (40-40)
  • Task (49-49)
  • Task (58-58)
  • Task (67-67)
  • Task (82-83)
  • Task (90-90)
  • Task (92-92)
  • Task (94-94)
  • Task (96-96)
Core/Resgrid.Config/ChatConfig.cs (1)
  • ChatConfig (3-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines 232 to 236
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);
}
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.

Comment on lines 287 to 299
apns = new
{
badge = count,
sound = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
type = type,
category = channelName,
eventCode = eventCode,
},
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (1)

136-194: Method name misleading: Consider refactoring for clarity.

The method is named UpdateSubscriberApns but now handles both APNS and FCM registration paths. This violates single responsibility and makes the code harder to understand. The conditional branching based on which provider ID is present adds cognitive load.

Consider one of these approaches:

  • Extract into two separate methods: UpdateSubscriberApns and UpdateSubscriberFcm (note: a separate UpdateSubscriberFcm already exists at line 100)
  • Rename to UpdateSubscriberPushCredentials to reflect its dual purpose
  • Keep the existing UpdateSubscriberFcm (line 100) for FCM-only paths and restrict UpdateSubscriberApns to APNS-only

As per coding guidelines: "Separate state from behavior" and "Use meaningful, descriptive names for types, methods, and parameters."

♻️ Duplicate comments (2)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (2)

235-236: Critical: iOS unit tokens incorrectly routed to FCM provider.

Line 236 registers iOS device tokens with the FCM integration (ChatConfig.NovuUnitFcmProviderId) instead of APNS. This is a critical issue because:

  • APNS device tokens are 64-character hex strings that must be registered with an APNS integration
  • FCM expects its own registration tokens (longer base64 format) and will reject APNS tokens with INVALID_ARGUMENT or UNREGISTERED errors
  • From PushService.cs lines 56-74: iOS platform (PlatformType == Platforms.iOS) calls UpdateUnitSubscriberApns, which now routes to FCM
  • Result: iOS unit push notifications will fail to deliver

The commented-out line 235 shows the correct implementation using ChatConfig.NovuUnitApnsProviderId.

-		//return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, ChatConfig.NovuUnitApnsProviderId);
-		return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, null, ChatConfig.NovuUnitFcmProviderId);
+		return await UpdateSubscriberApns($"{code}_Unit_{unitId}", token, ChatConfig.NovuUnitApnsProviderId, null);

Note: If the PR objective is to test FCM passthrough for APNS, the caller must provide FCM registration tokens (not APNS tokens) and the platform check in PushService.RegisterUnit must be updated accordingly.


288-316: Fix FCM APNS override: Remove duplicate direct fields and correct payload structure.

The current structure includes APNS fields both directly on the apns object (lines 290-299) and nested under payload.aps (lines 302-314). FCM will silently ignore the direct fields. Additionally, custom fields like eventCode and customType inside aps (lines 312-313) are non-standard and may be dropped by Apple.

Issues:

  • Duplicate field definitions (badge, sound appear twice)
  • Direct fields (lines 290-299) are not part of FCM's APNS config schema
  • Custom fields should be outside the aps object for Apple to deliver them as custom data
 							apns = new
 							{
-								badge = count,
-								sound = new
-								{
-									name = sound,
-									critical = channelName == "calls" ? 1 : 0,
-									volume = 1.0f
-								},
-								type = type,
-								category = channelName,
-								eventCode = eventCode,
 								payload = new
 								{
 									aps = new
 									{
 										badge = count,
 										sound = new
 										{
 											name = sound,
 											critical = channelName == "calls" ? 1 : 0,
 											volume = 1.0f
 										},
 										category = channelName,
-										eventCode = eventCode,
-										customType = type
 									},
+									eventCode = eventCode,
+									type = type
 								},
 							},

This moves custom fields (eventCode, type) outside aps where they belong and removes the duplicate direct fields.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2292217 and 6306057.

📒 Files selected for processing (1)
  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Prefer functional patterns and immutable data where appropriate in C#
Use modern C# features appropriately
Use meaningful, descriptive names for types, methods, and parameters; avoid unclear abbreviations
Separate state from behavior (e.g., use records for state and place operations in separate static classes)
Prefer pure methods over methods with side effects
Use extension methods appropriately for domain-specific operations
Design for testability; avoid hidden dependencies inside methods and prefer explicit, pure functions
Minimize constructor injection; keep the number of injected dependencies small
Prefer composition with interfaces to extend behavior

Files:

  • Providers/Resgrid.Providers.Messaging/NovuProvider.cs
🧬 Code graph analysis (1)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (3)
Core/Resgrid.Services/PushService.cs (5)
  • Task (31-48)
  • Task (50-55)
  • Task (57-75)
  • Task (77-82)
  • Task (89-120)
Core/Resgrid.Model/Providers/Models/INovuProvider.cs (11)
  • Task (20-20)
  • Task (31-31)
  • Task (40-40)
  • Task (49-49)
  • Task (58-58)
  • Task (67-67)
  • Task (82-83)
  • Task (90-90)
  • Task (92-92)
  • Task (94-94)
  • Task (96-96)
Core/Resgrid.Config/ChatConfig.cs (1)
  • ChatConfig (3-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (2)
Providers/Resgrid.Providers.Messaging/NovuProvider.cs (2)

178-181: Guard logic correctly implemented.

The early return when neither apnsId nor fcmId is provided properly prevents sending an empty payload to Novu, addressing the previous review concern.


225-225: Correct routing for iOS user registrations.

Passing null for fcmId ensures iOS user tokens are registered to the APNS integration via ChatConfig.NovuResponderApnsProviderId, which is the correct behavior.

using Resgrid.Model;
using Resgrid.Model.Providers;
using Resgrid.Providers.Bus.Models;
using SharpCompress.Common;
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 | 🟡 Minor

Unused import: Remove if not needed.

The SharpCompress.Common namespace doesn't appear to be used anywhere in this file. Please remove it unless it's required for functionality outside the shown code.

-using SharpCompress.Common;
📝 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
using SharpCompress.Common;
// Line 10 and the using SharpCompress.Common; statement removed
🤖 Prompt for AI Agents
In Providers/Resgrid.Providers.Messaging/NovuProvider.cs around line 10, the
using directive for SharpCompress.Common is unused; remove the 'using
SharpCompress.Common;' line to clean up imports and ensure no references to that
namespace remain in the file (if any are later added, re-add the using where
needed).

Comment on lines +318 to 331
apns = new Dictionary<string, object>
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
["badge"] = count,
["sound"] = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
["type"] = type,
["category"] = channelName,
["eventCode"] = eventCode,
["gcm.message_id"] = "123"
},
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

Remove or clarify gcm.message_id in APNS override.

Line 330 adds ["gcm.message_id"] = "123" to the APNS override dictionary. This is problematic:

  • gcm.message_id is a GCM (Google Cloud Messaging, FCM's predecessor) concept and doesn't belong in APNS payloads
  • The hardcoded value "123" suggests placeholder code
  • APNS has no concept of GCM message IDs and will likely ignore or reject this field

If this field is required for some FCM-APNS passthrough scenario, please:

  1. Document why it's needed
  2. Generate a meaningful value instead of hardcoding "123"
  3. Verify that Novu/APNS doesn't reject this unexpected field

Otherwise, remove it:

 							["type"] = type,
 							["category"] = channelName,
 							["eventCode"] = eventCode,
-							["gcm.message_id"] = "123"
 						},
📝 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 Dictionary<string, object>
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
["badge"] = count,
["sound"] = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
["type"] = type,
["category"] = channelName,
["eventCode"] = eventCode,
["gcm.message_id"] = "123"
},
apns = new Dictionary<string, object>
{
["badge"] = count,
["sound"] = new
{
name = sound,
critical = channelName == "calls" ? 1 : 0,
volume = 1.0f
},
["type"] = type,
["category"] = channelName,
["eventCode"] = eventCode,
},
🤖 Prompt for AI Agents
In Providers/Resgrid.Providers.Messaging/NovuProvider.cs around lines 318 to
331, the APNS override currently contains a hardcoded GCM field
["gcm.message_id"] = "123", which is inappropriate for APNS; remove that key
from the APNS dictionary, or if you truly need to pass an FCM/Android message
id, move it to the FCM/GCM-specific payload branch (not APNS), generate a
meaningful unique id (e.g., guid or provider message id) instead of "123", and
add a short comment documenting why and where that field is used and a check to
ensure Novu/APNS won’t reject it.

@ucswift
Copy link
Member Author

ucswift commented Nov 9, 2025

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 9b5e9eb into master Nov 9, 2025
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants