Skip to content

fix(js): ensure notification cache entries are proper Notification instances#10585

Draft
cursor[bot] wants to merge 1 commit intonextfrom
cursor/sentry-error-investigation-7ecf
Draft

fix(js): ensure notification cache entries are proper Notification instances#10585
cursor[bot] wants to merge 1 commit intonextfrom
cursor/sentry-error-investigation-7ecf

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor bot commented Apr 5, 2026

What changed and why

Fixes DASHBOARD-2B5 (TypeError: notification.read is not a function — 11 events, 3 users) and DASHBOARD-2M6 (TypeError: notification.archive is not a function — 1 event, 1 user).

Root cause

When multiple browser tabs are open, @novu/js uses BroadcastChannel to share websocket events (like new notification received) across tabs. Only one tab (the "leader") holds the actual websocket connection; other tabs receive events via BroadcastChannel.postMessage().

The problem: BroadcastChannel uses the browser's structured clone algorithm, which strips class prototypes. A Notification class instance (with read(), archive(), unread(), etc. on its prototype) becomes a plain object on the receiving tab — it has all the data properties but none of the methods.

In CountContext.tsx, the received notification was inserted directly into the notifications cache via notificationsCache.update(), bypassing the createNotification() wrapper that constructs proper Notification class instances. When the user then clicked "mark as read" or "archive" in the inbox, the code called notification.read() on this plain object, producing the TypeError.

Changes

  1. CountContext.tsx: Changed from notificationsCache.update() (which accepts raw objects) to notificationsCache.unshift() (which wraps via createNotification(), ensuring a proper Notification instance).

  2. notifications-cache.ts: Added ensureNotificationInstance() guard in handleNotificationEvent() — any notification data entering the cache from events is validated and re-wrapped if it lacks the Notification prototype. This is a defense-in-depth measure for the same BroadcastChannel scenario on notification update/remove events.

  3. createNotification.ts: Added ensureNotificationInstance() utility that checks instanceof Notification and only wraps when needed (no-op for already-valid instances).

  4. Tests: Added 2 tests to notifications-cache.test.ts verifying that plain objects (simulating BroadcastChannel deserialization) are properly wrapped into Notification instances.

Open in Web View Automation 

…stances

Fixes DASHBOARD-2B5, DASHBOARD-2M6

When multiple tabs are open, BroadcastChannel.postMessage() uses structured
cloning which strips class prototypes from Notification objects. This causes
'notification.read is not a function' and 'notification.archive is not a
function' errors when users interact with inbox notifications on non-leader
tabs.

Two fixes applied:
- CountContext: use notificationsCache.unshift() instead of direct
  cache.update() so incoming notifications are wrapped via createNotification
- notifications-cache: add ensureNotificationInstance() guard in
  handleNotificationEvent to re-wrap plain objects that lost their prototype

Co-authored-by: Dima Grossman <dima@grossman.io>
@netlify
Copy link
Copy Markdown

netlify bot commented Apr 5, 2026

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit 1c1e92f
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69d2552a4a115600086a31ec

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 5, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: fix(js): ensure notification cache entries are proper Notification instances

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant