nodebb-plugin-chat-read-receipts
v1.7.0
Published
Adds WhatsApp-style read receipts to NodeBB chat messages
Readme
nodebb-plugin-chat-read-receipts
Adds WhatsApp-style read receipts and an unread messages divider to NodeBB chat.
Features
Read receipts
A small indicator is shown next to each of your own outgoing messages (never on other people's messages — same as WhatsApp).
One-on-one rooms:
✓(grey) — Sent, not read yet.✓✓(blue) — Read by the other person.
Group rooms — based on each participant's last-seen time:
✓(grey) — nobody else has read it yet.👁 Read by k/N— read by some participants. Hover to see names.✓✓ Read by all(blue) — every other participant has read it.
Receipts update live as participants read messages, and also render correctly for messages loaded lazily when scrolling up.
Receipts in the chats list
The same indicator is shown in the chats dropdown (header chat button) and
in the chats page sidebar, next to each room's teaser — but only when the
room's last message is one of yours. It is pinned to the far edge of the
timestamp row (left in RTL, right in LTR) and uses a compact form: ✓, ✓✓,
or 👁 k/N, with the full text on hover.
Unread messages divider
When you open a chat that has unread messages, a dashed "New messages" divider bar is inserted before the first unread message and the view scrolls to it automatically (with a small amount of context shown above).
- Works in both popup (minimized) chat windows and the full-page
/user/:uid/chats/view, including sidebar room switching. - The divider disappears once you close and reopen the chat after reading.
- If a new message arrives while the chat is open but you are on a different tab/window, the divider repositions to that message.
- If all unread messages are above the initially-loaded batch, the plugin loads older messages until it finds the boundary (up to 50 batches).
How it works
- When you open a chat, the plugin fetches the per-participant read state
(
plugins.chatReadReceipts.getRoomReadState) before recording your visit, so the pre-visit "last seen" timestamp is used to locate the first unread message. On cores that fireaction:messaging.markRead, that timestamp is also captured server-side, which removes the dependency on the two requests arriving in that order. - After rendering the divider, your client sends the timestamp of the last
loaded message to the server (
plugins.chatReadReceipts.markSeen). The server broadcasts the update so other participants' receipts refresh live. - The
markSeencall uses the timestamp of the last message in the DOM — notDate.now()— so messages that arrive after the snapshot are not accidentally marked as read.
Compatibility
Works on NodeBB 4.x. Newer cores (4.5+) merged a few upstream changes this plugin used to work around, and it uses them automatically when they are available, falling back to the previous behaviour when they are not:
action:messaging.markReadsupplies the room's read timestamp from before core overwrote it. The plugin seeds its own per-room state with it, so rooms that already existed when the plugin was installed get a correct unread divider on the very first visit.- Live-received messages now keep the server's timestamp instead of being clamped to the receiving client's clock, so a reader whose clock lags no longer reports a "seen" time below the message's real timestamp.
Messaging.isRoomMember()provides the raw membership check the plugin needs (see the note below on whyMessaging.isUserInRoomis not used).
Membership is checked without the filter:messaging.isUserInRoom hook on
purpose: other plugins override that hook so admins and managers can view rooms
they are not part of, and honouring it here would record and broadcast a
lurking admin's view as a read receipt.
Privacy note
Read receipts are symmetric: if you can see whether others read your messages, they can see whether you read theirs.
