Skip to content

fix(chat): show typing only from participants of the current conversation - #6905

Open
ToteMeiSter wants to merge 13 commits into
nextcloud:masterfrom
ToteMeiSter:fix/typing-indicator-room-sessions-v3
Open

ToteMeiSter wants to merge 13 commits into
nextcloud:masterfrom
ToteMeiSter:fix/typing-indicator-room-sessions-v3

Conversation

@ToteMeiSter

@ToteMeiSter ToteMeiSter commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A conversation shows "Guest is typing…". The person who types is not a guest. They type in another conversation.

Cause

  • The signaling connection of an account is shared by all conversations. The session map in WebSocketInstance (usersHashMap) was filled by join events and cleared only by leave events. It was not cleared when the room changed, when the room was left, after a hello without resume, or on no_such_session / no_such_room. So it kept the sessions of earlier rooms.
  • MessageInputFragment sent startedTyping / stoppedTyping to every session in the map.
  • The signaling server (HPB) delivers a message with recipient session to that session in any room. In nextcloud-spreed-signaling, server/hub.go processMessageMsg checks only "same backend" and "not to self". It does not check the room of the recipient.
  • The receiver did not check that the sender is in the current room. The sender session was unknown, so its name was empty, and the app showed "Guest".
  • The web client already filters: src/utils/SignalingTypingHandler.js in spreed sends typing only to the participants of the current room and ignores typing from sessions which are not in that list.

Changes

  1. Track only the signaling sessions of the current room.
    • New RoomSessionRegistry replaces usersHashMap. It is thread safe. It is cleared when the server answers with another room, when the room is left, after a hello without resume, and on no_such_session / no_such_room. A resumed connection keeps it.
    • WebSocketInstance gives a snapshot of the room sessions without the own session, and checks if a session is in the current room.
    • MessageInputFragment sends typing only to the sessions of the current room.
    • ChatActivity accepts typing only from a session of the current room. A real guest still joins without a user id, stays in the registry and is still shown as "Guest".
  2. Drop late join events of a left room. After a leave the room is unknown, so the answer to the leave (an empty room) also clears the sessions. Join events of the left room which arrive between the leave and its answer are dropped.
  3. Hide the typing indicator of a session which left the room.
    • SignalingMessageReceiver and ConversationMessageNotifier pass the leave event to the chat listener (new onSessionsLeft, empty by default).
    • TypingParticipant stores the session and can remove the participants of left sessions.
    • ChatActivity changes the typing participants on the UI thread only, also for a stop message.
    • Without this, the indicator of a participant who left stayed until the 15 second timeout.
  4. Stop and expire typing per session, not per participant.
    • Every session of a typing user has its own 15 second timer. stoppedTyping and the timeout forget only that session; the participant is removed with its last session.
    • Before, a stop of one device hid the indicator while another device of the same user still typed.

Tests

  • RoomSessionRegistryTest
  • TypingParticipantTest
  • SignalingMessageReceiverSessionsLeftTest

How to test

  1. Use two accounts, X and Y, on a server with the high-performance backend. Conversation A has X and Y. Conversation B has Y and another person, not X.
  2. X and Y open A at the same time.
  3. Y opens B.
  4. X opens another conversation and starts to type there.
  5. Before: Y sees "Guest is typing…" in B. After: Y sees no typing indicator in B.
  6. A real guest who joins by a public link and types is still shown as "Guest".

Tested on a Huawei DEL-LX9 and a Samsung SM-S926B (Android 17): "Guest is typing…" from another conversation no longer appears.

🖼️ Screenshots

🏚️ Before 🏡 After
"Guest is typing…" from another conversation No typing indicator

No screenshots. The UI change is the absence of a wrong typing line.

🚧 TODO

  • Nothing left

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed (not needed: client side only, no new server feature)
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

The code and the tests were written by an AI agent (Claude Code, model claude-sonnet-5-5). I tested the change on a device and I am responsible for it. The commits carry the trailer Assisted-by: Claude-Code:claude-sonnet-5-5. This description was also prepared with AI help and proofread by me.

The signaling connection of an account is shared by all conversations,
but the session list was only filled by join events and never cleared
when the room changed. It kept the sessions of earlier rooms, so typing
messages went to sessions of other rooms, and typing messages from such
sessions were shown as "Guest" because their name is unknown.

The sessions are now held in RoomSessionRegistry. It is cleared when the
server answers with another room, when the room is left, after a hello
without resume, and on no_such_session / no_such_room. A resumed
connection keeps it. The registry is thread safe, the own session is not
a typing target, and a typing message is only accepted from a session of
the current room. A real guest still joins without a user id and stays
in the registry.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
…ssions

After the connection left a room the room is unknown, so the answer to
the leave (an empty room) clears the sessions as well. Join events of
the left room which arrive between the leave and its answer are dropped
with it. The registry tests are named after what they check.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
The leave event of the room now reaches the chat through the conversation
message listener. A session which left cannot send a stop message any
more (it is not a known session of the room), so its indicator stayed
until the 15 second timeout. The typing participants are changed on the
UI thread only, also when a stop message arrives.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: f4aa4ddd-1f82-4ecf-b619-7bc06b18b582
📥 Commits

Reviewing files that changed from the base of the PR and between 1211e20 and c7d2f34.

📒 Files selected for processing: 1
  • app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt
🚧 Files skipped from review as they are similar to previous changes: 1
  • app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

📝 Walkthrough

RoomSessionRegistry tracks sessions for the current room and provides session lookups. WebSocketInstance updates or resets that registry as room state changes. Signaling forwards room departure session IDs to conversation listeners. Chat typing behavior uses current-room sessions for typing messages and removes typing participants when their sessions leave.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to c7d2f

No actionable merge-blocking issue is established for this change; it is mergeable after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 16.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 74 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: restricting typing indicators to participants in the current conversation.
Description check Passed The description is complete and follows the repository template. It explains the problem, cause, changes, tests, manual verification, TODO status, and checklist. The backport and milestone items remai…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description is complete and follows the repository template. It explains the problem, cause, changes, tests, manual verification, TODO status, and checklist. The backport and milestone items remain unchecked, but these are non-critical metadata items.

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1c7b5586-699b-423c-b95c-264054a80196
📥 Commits

Reviewing files that changed from the base of the PR and between da0a3d9 and f199745.

📒 Files selected for processing: 10
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
  • app/src/main/java/com/nextcloud/talk/chat/TypingParticipant.kt
  • app/src/main/java/com/nextcloud/talk/signaling/ConversationMessageNotifier.kt
  • app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.kt
  • app/src/main/java/com/nextcloud/talk/webrtc/RoomSessionRegistry.kt
  • app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt
  • app/src/test/java/com/nextcloud/talk/chat/TypingParticipantTest.kt
  • app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverSessionsLeftTest.kt
  • app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Comment thread app/src/main/java/com/nextcloud/talk/webrtc/RoomSessionRegistry.kt
…r types

A user can be in the room with several signaling sessions (phone and
laptop). The typing participant kept only the first session, so the
leave event of that session hid the indicator although the second
session still typed. The participant now holds all sessions which sent
a typing message and is removed when the last of them left.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
… room

After the connection left a room (or lost its session) the registry
accepted a late join event of that room until the answer to the leave
arrived. Such a session received our typing messages and passed the
check of incoming typing messages. The server sends the answer to a
join before the list of the sessions, so a join without a known room
is always a late event of a room which was left.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
A user may type from several devices. A stoppedTyping message of one
session removed the whole participant, and the single timer of the
participant removed it 15 s after the last start of any session, so the
indicator vanished while another session still typed.

Every session now has its own 15 s timer and stoppedTyping forgets only
its session. The participant is removed with its last session. Events
without a session id are ignored, as they are not accepted anyway.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: da5969f1-13d9-4629-8c8f-b3b1801fc10f
📥 Commits

Reviewing files that changed from the base of the PR and between f199745 and 1517c84.

📒 Files selected for processing: 5
  • app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
  • app/src/main/java/com/nextcloud/talk/chat/TypingParticipant.kt
  • app/src/main/java/com/nextcloud/talk/webrtc/RoomSessionRegistry.kt
  • app/src/test/java/com/nextcloud/talk/chat/TypingParticipantTest.kt
  • app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread app/src/main/java/com/nextcloud/talk/webrtc/RoomSessionRegistry.kt
@ToteMeiSter

Copy link
Copy Markdown
Contributor Author

I added one more commit: 1517c84 "fix(chat): stop and expire typing per session, not per participant".

de29297 keeps all sessions of a typing user, but only the leave event used them. A stoppedTyping of one session still removed the whole participant, so the indicator vanished while the other device of the same user still typed. The single timer of the participant had a similar effect: it was restarted by any session, so after one session stopped, the other one stayed visible up to 15 s longer than its own last startedTyping.

Now every session has its own 15 s timer, and stoppedTyping forgets only the sending session. The participant is removed with its last session. Typing events without a session id are ignored, because isSessionInCurrentRoom(null) is always false and they were never shown anyway.

Tests: TypingParticipantTest covers stop of one of two sessions, expiry of each session on its own, and a repeated start of the same session.

How to test: log in with the same user on two devices, open the conversation on a third device. Type on both devices, then stop typing on one of them. "… is typing" stays visible. Stop on the second device: the indicator disappears.

…nRegistry

reset() runs on the UI thread when a room is left, onJoin() on the
OkHttp thread. onJoin() could pass its check of the room, reset() then
cleared the registry, and onJoin() still stored its session. The session
of the left room stayed until the answer to the leave arrived, so it
received our typing messages and passed the check of incoming ones.

onRoomResponse(), reset() and onJoin() now share one lock. Reads stay
lock-free.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
The new test makes the class reach the detekt limit of functions, as in
the other large test classes of the project.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2320d593-5685-463c-9573-ca56421955b7
📥 Commits

Reviewing files that changed from the base of the PR and between 1517c84 and b403c07.

📒 Files selected for processing: 2
  • app/src/main/java/com/nextcloud/talk/webrtc/RoomSessionRegistry.kt
  • app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Comment thread app/src/test/java/com/nextcloud/talk/webrtc/RoomSessionRegistryTest.kt Outdated
…yTest

Without the lock on onRoomResponse() no test failed: a join of the previous
room could store its session after the answer for the next room cleared the
registry. Share the paused-join setup between the reset() and the
onRoomResponse() race tests.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
…istryTest fails

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
The constructor takes an optional client, event bus and connection helper;
when the client and the bus are given, the injection through the application
component is skipped. This lets a unit test run WebSocketInstance against a
mock web server.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
…g frames

The tests feed the frames of a signaling server into a real
WebSocketInstance: switching the room, leaving, resume, a new session
without resume, already_joined, no_such_session, no_such_room, the leave
event and two accounts. They fail when the instance stops clearing the
sessions on a room change or on a new session.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
…tInstance

A join event of the room being left, sent before the answer to the leave,
must neither make that session a typing sender nor a typing target; after
the answer and the join of the next room only its sessions are known.

Also fix four tests of the class which waited for a barrier join event: the
registry ignores join events while the connection is in no room (bcb9d83),
so they timed out. They wait for the leave event of an unknown session now.

Assisted-by: Claude-Code:claude-sonnet-5-5
Signed-off-by: Krainov Gleb <krajnov.g@kontentplus.ru>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant