Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix/bad notif token + system push notifs - #925

Merged
coodos merged 5 commits into
mainfrom
fix/notification-fixes
Mar 16, 2026
Merged

Fix/bad notif token + system push notifs#925
coodos merged 5 commits into
mainfrom
fix/notification-fixes

Conversation

@coodos

@coodoscoodos commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixes notification issues

  • Bad token thing
  • Issue with system messages

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Added system message notifications with distinct title/body formatting.
  • Improvements

    • Improved device token management: stale tokens are automatically cleaned up and replaced per device.
    • More reliable push delivery with per-token processing, improved error handling for invalid tokens, and conditional registration of new tokens after verification.

@coodoscoodos changed the title Fix/notification fixesFix/bad notif token + system push notifsMar 16, 2026
@coderabbitai

coderabbitaiBot commented Mar 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR updates notification flows to manage DeviceToken storage: NotificationService and MessageNotificationService now accept a DeviceToken repository, device registration removes stale tokens, notifications are delivered sequentially with per-token error handling and bad-token removal, and system-message detection was added to message notifications.

Changes

Cohort / File(s)Summary
Notification core & device handling
infrastructure/evault-core/src/services/NotificationService.ts, infrastructure/evault-core/src/controllers/NotificationController.ts
NotificationService constructor accepts an optional DeviceToken repository. registerDevice now unregisters stale tokens for a (linkedEName, deviceId) pair and replaces them with the latest token. sendNotificationToEName sends to tokens sequentially, detects bad-token errors, and removes bad tokens from Verification and DeviceToken stores.
Message notification changes
infrastructure/evault-core/src/services/MessageNotificationService.ts
Constructor accepts an optional DeviceToken repository and passes it to NotificationService. Adds system-message detection via $$system-message$$ prefix, strips prefix for body, and adjusts notification title/body for system vs normal messages.
Integration wiring
infrastructure/evault-core/src/core/protocol/graphql-server.ts
Imported DeviceToken and passed AppDataSource.getRepository(DeviceToken) into MessageNotificationService initialization.

Sequence Diagram

sequenceDiagram
participant App as Client/App
participant NS as NotificationService
participant DT as DeviceToken Repo
participant Verif as Verification Repo
participant PP as Push Provider
App->>NS: registerDevice(linkedEName, deviceId, pushToken)
NS->>Verif: Ensure verification exists
NS->>DT: Query tokens by (linkedEName, deviceId)
DT-->>NS: existingTokens
alt existingTokens contain others
loop For each stale token (not current)
NS->>DT: remove stale token
NS->>Verif: remove token reference
end
end
NS->>DT: store current pushToken
App->>NS: sendNotificationToEName(payload)
NS->>NS: detect $$system-message$$ prefix (if any)
loop For each token (sequential)
NS->>PP: send push to token (HTTP)
alt success
PP-->>NS: 200 OK
NS->>NS: mark delivered, stop if desired
else failure
PP-->>NS: error
NS->>NS: classify bad token
NS->>DT: remove bad token
NS->>Verif: remove bad token reference
end
end
NS-->>App: delivery result (true/false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: push notifs #820: Adds DeviceToken entity support and updates NotificationService and controller flows to integrate device-token lifecycle and new endpoints.
  • feat: multi device notifs support #913: Changes NotificationService constructor signature and adjusts device-token/verification cleanup logic for token lifecycle.

Poem

🐰 Old tokens hop away, one by one,
New tokens nest where the warm sun shone,
I prune the bad with gentle paws,
System notes arrive without a pause—
Hooray for tidy feeds and cleaned-up bones!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Title check✅ PassedThe title specifically references the two main fixes: bad notification tokens and system push notifications, which directly align with the core changes in the changeset.
Description check✅ PassedThe description covers the main fixes and follows the template structure, but critical sections like 'How the change has been tested' are left blank, reducing clarity on validation approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/notification-fixes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@coodos
coodos marked this pull request as ready for review March 16, 2026 08:58

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
infrastructure/evault-core/src/services/MessageNotificationService.ts (1)

80-104: ⚠️ Potential issue | 🟠 Major

Don't infer a trusted system notification from user-controlled text.

Any sender can prefix content with $$system-message$$ and get a push titled as a system message. That makes system notifications spoofable; this should come from a server-set flag/type or a trusted-sender check, not from the message body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts` around
lines 80 - 104, The code currently treats any message whose payload.content
starts with "$$system-message$$" (rawText and isSystemMessage) as a system
notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
infrastructure/evault-core/src/services/NotificationService.ts (1)

52-64: ⚠️ Potential issue | 🟡 Minor

Clean the previous owner's device_token row here too.

This removes a transferred token from other users' Verification.pushTokens, but it leaves the old device_token.tokens entry behind. After an account switch, the same token can still show up under both eNames in the DeviceToken-backed listing endpoints unless this path updates both stores.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
52 - 64, The current NotificationService flow removes the token from other
Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/controllers/NotificationController.ts (1)

80-100: Consider making the Verification + DeviceToken updates atomic.

This path now does unregister(old)registerDevice(...)register(new) as separate writes. If the request fails in the middle, Verification.pushTokens and device_token.tokens can diverge and the endpoint may return 500 after a partial success. Moving the token sync into one service/transaction would make this much safer.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/controllers/NotificationController.ts` around
lines 80 - 100, The current flow in NotificationController does separate writes
(unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 225-231: The method in NotificationService.ts currently returns a
boolean (delivered) which is ambiguous; change the method (e.g., sendPush /
sendPushNotification) to return a discriminated result (string enum or object)
that distinguishes at least: delivered, no_active_devices, and
all_attempts_failed (include counts/tokens as needed). Update the method logic
where delivered is computed to set the appropriate status and return that result
instead of a bare boolean, adjust logging to include the status, and update
callers (NotificationController.ts) to switch on the new status rather than
treating false as "no active devices."
- Around line 26-39: The BAD_TOKEN_ERRORS list in NotificationService.ts
incorrectly includes provider/auth errors (ExpiredProviderToken,
InvalidProviderToken, messaging/mismatched-credential) which are not
device-token failures and lead removeBadTokens to delete valid registrations;
remove those three entries from the BAD_TOKEN_ERRORS constant so isBadTokenError
only matches actual device/token errors (e.g.,
messaging/registration-token-not-valid, Unregistered, BadDeviceToken,
DeviceTokenNotForTopic) and verify any callers of isBadTokenError (such as
removeBadTokens) still behave correctly.
---
Outside diff comments:
In `@infrastructure/evault-core/src/services/MessageNotificationService.ts`:
- Around line 80-104: The code currently treats any message whose
payload.content starts with "$$system-message$$" (rawText and isSystemMessage)
as a system notification, which is spoofable; change the logic in the
MessageNotificationService where rawText/isSystemMessage/messageText are used so
system notifications come from trusted metadata instead of user-controlled
content — e.g., check a server-set flag on the payload (payload.type ===
'system' or payload.isSystem) or validate the sender via a trusted-sender check
(use senderEName against a trusted list or call an isTrustedSender helper)
before setting title/body as a system message; retain removing the marker from
display text only for legacy compatibility if and only if the message is
validated as a system message by the trusted flag/sender check.
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 52-64: The current NotificationService flow removes the token from
other Verification.pushTokens but doesn't remove it from the DeviceToken store,
leaving device_token.tokens entries that still reference the old owner; update
the same path in NotificationService (after the loop that updates
verificationRepository entries) to query deviceTokenRepository for DeviceToken
rows where :token = ANY(device_token.tokens) and linkedEName !=
registration.eName (or owner/ename field used on DeviceToken), remove the token
from each DeviceToken.tokens array, update updatedAt, and save via
deviceTokenRepository.save so the token is removed from the device_token.tokens
store as well; reference NotificationService, verificationRepository,
deviceTokenRepository, registration.eName, pushTokens, and device_token.tokens
to locate changes.
---
Nitpick comments:
In `@infrastructure/evault-core/src/controllers/NotificationController.ts`:
- Around line 80-100: The current flow in NotificationController does separate
writes (unregister old tokens via deviceTokenService, then
notificationService.registerDevice which updates Verification, then
deviceTokenService.register), risking divergence on failure; change this so the
Verification pushTokens update and device_token register/unregister occur inside
a single transaction (e.g., use AppDataSource.manager.transaction) by moving the
token-sync logic into a single service method (or extend
notificationService.registerDevice) that accepts a transactional EntityManager
and performs the AppDataSource.getRepository("Verification") update plus the
device token register/unregister operations under that manager; update
deviceTokenService.unregister/register to accept an optional manager or provide
transactional counterparts, and ensure external side-effects are performed only
after the DB transaction commits or are compensatable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ff2d11be-4743-4ca4-a02c-68bd6d2b3690

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce20f5 and 83edaa7.

📒 Files selected for processing (4)
  • infrastructure/evault-core/src/controllers/NotificationController.ts
  • infrastructure/evault-core/src/core/protocol/graphql-server.ts
  • infrastructure/evault-core/src/services/MessageNotificationService.ts
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos marked this pull request as draft March 16, 2026 09:11
@coodos
coodos marked this pull request as ready for review March 16, 2026 09:19

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

171-236: ⚠️ Potential issue | 🟠 Major

This fan-out is now serialized behind a 10s timeout per token.

If an eName has several slow or stale tokens, the caller waits for each network attempt in sequence, so one notification can easily turn into a long-running request. Please keep the per-token cleanup, but move the send path to bounded concurrency or a background job.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
171 - 236, The current sequential loop in NotificationService.ts (iterating over
allTokens and awaiting fetch per token) serializes sends behind a 10s timeout
and can make a single notification block for many seconds; refactor by
extracting the per-token logic into a sendToToken(token, platform) helper (reuse
pushPayload, triggerUrl, isBadTokenError, and logging) and run those helpers
with bounded concurrency (e.g., a simple semaphore/pool or p-limit) instead of
awaiting each in sequence; collect results from Promise.allSettled to aggregate
delivered and badTokens (mark delivered true if any task succeeded and push
token to badTokens when the helper indicates a bad-token result), or
alternatively dispatch sendToToken tasks to a background worker/queue and return
immediately—preserve existing logging and error handling inside sendToToken and
ensure the code updates delivered and badTokens only after tasks complete or via
the background job processing.
♻️ Duplicate comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

244-250: ⚠️ Potential issue | 🟠 Major

The boolean result is still overloaded.

false covers both “no active devices” and “all push attempts failed”, while true can also mean “saved for polling but no push tokens existed”. Callers cannot react correctly unless this becomes an explicit status/result type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
244 - 250, The current return value (delivered boolean) from NotificationService
conflates multiple outcomes (no active devices, saved-for-polling, push attempts
failed, delivered); change it to an explicit result type (e.g., a discriminated
union or enum like NotificationResult with variants such as NoActiveDevices,
Delivered, SavedForPolling, AllPushFailed) and return that instead of the
boolean; update the code that references delivered, allTokens and eName to set
the correct variant (inspect token list length, whether messages were queued for
polling, and whether any push succeeded) and update all callers to handle the
new result type.
🧹 Nitpick comments (1)
infrastructure/evault-core/src/services/NotificationService.ts (1)

42-43: Don't let bad-token cleanup silently degrade to one table.

With deviceTokenRepository?, this service can be constructed in a state where removeBadTokens only mutates Verification and leaves device_token stale. Making the dependency required, or failing fast when cleanup needs it, would keep this fix from becoming configuration-dependent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infrastructure/evault-core/src/services/NotificationService.ts` around lines
42 - 43, NotificationService currently allows deviceTokenRepository to be
optional which lets removeBadTokens run without touching the device_token table;
make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 171-236: The current sequential loop in NotificationService.ts
(iterating over allTokens and awaiting fetch per token) serializes sends behind
a 10s timeout and can make a single notification block for many seconds;
refactor by extracting the per-token logic into a sendToToken(token, platform)
helper (reuse pushPayload, triggerUrl, isBadTokenError, and logging) and run
those helpers with bounded concurrency (e.g., a simple semaphore/pool or
p-limit) instead of awaiting each in sequence; collect results from
Promise.allSettled to aggregate delivered and badTokens (mark delivered true if
any task succeeded and push token to badTokens when the helper indicates a
bad-token result), or alternatively dispatch sendToToken tasks to a background
worker/queue and return immediately—preserve existing logging and error handling
inside sendToToken and ensure the code updates delivered and badTokens only
after tasks complete or via the background job processing.
---
Duplicate comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 244-250: The current return value (delivered boolean) from
NotificationService conflates multiple outcomes (no active devices,
saved-for-polling, push attempts failed, delivered); change it to an explicit
result type (e.g., a discriminated union or enum like NotificationResult with
variants such as NoActiveDevices, Delivered, SavedForPolling, AllPushFailed) and
return that instead of the boolean; update the code that references delivered,
allTokens and eName to set the correct variant (inspect token list length,
whether messages were queued for polling, and whether any push succeeded) and
update all callers to handle the new result type.
---
Nitpick comments:
In `@infrastructure/evault-core/src/services/NotificationService.ts`:
- Around line 42-43: NotificationService currently allows deviceTokenRepository
to be optional which lets removeBadTokens run without touching the device_token
table; make the dependency required or fail fast: update the NotificationService
constructor to require deviceTokenRepository (remove the ? from the field) so
it's always injected, or if you prefer runtime checking, have removeBadTokens
explicitly throw a clear error if this.deviceTokenRepository is undefined before
any mutations; reference NotificationService, removeBadTokens,
deviceTokenRepository and ensure device_token cleanup is performed (or fails)
rather than silently skipping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fae4f0d8-e307-461b-9831-3a6a17b0f3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 83edaa7 and dd64ce2.

📒 Files selected for processing (1)
  • infrastructure/evault-core/src/services/NotificationService.ts

@coodos
coodos merged commit 3d61412 into mainMar 16, 2026
6 checks passed
@coodos
coodos deleted the fix/notification-fixes branch March 16, 2026 10:39
Sign up for freeto 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.

1 participant

@coodos