Skip to content

Emit chat state changed event when chat state changes (e.g. one contact starts typing) #3374

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

lucas-almeida026
Copy link

PR Details

The PR adds the functionality requested on #82 and on related issues: #1137, #2636, #3372.
This feature introduces the ability to detect chat states such as 'available', 'unavailable', 'typing' and 'recording', addressing a longstanding request from the community

Description

3 main changes were made:

  1. addition of the CHAT_STATE_CHANGED event on the Events object from src/utiol/Contants.js
  2. addition of an exposed function to emit this particular event through the client object src/Client.js:249
  3. addition of 2 functions at the window level
    • injectPresenceModelInstance: this function wraps the underlying modelClass from the Store.Presence object. the modelClass is used to generate all the models that represents a presence for a given chat.
      This is done to "inject" the get and set properties on every single instance generate my the model class to be able to call the emit function every time the model chat state property is update
    • execPresenceModelInjection: this function is called when the Store.Presence object becomes available (it takes a couple of milliseconds for it to load) and calls the injectPresenceModelInstance to inject every model already created.
      The function also makes sure to maintain the prototype chain of every injected object to avoid breaking underlying functionality

Motivation and Context

The main objective is to have one more way to improve user experience of apps built using whatapp-web.js. By providing a way to handle events related to chat state changes developers will be able to create interaction flows with a finer level of control.

An example use case would be: prevent an automatic response from being sent if the target user is typing, so it doesn't interrupt the user.

How Has This Been Tested

I tested manually using the shell command from the package.json file.

Actions:

  • on the terminal: npm run shell
  • after the shell loads: client.on('chat_state_changed', console.log)
  • enter on my personal whatsapp account and open the chat with the connected account
  • start typing
  • observe the shell printing: { chatId: '*********@c.us', chatState: 'typing' }
  • stop typing on my phone
  • observe the shell printing: { chatId: '*********@c.us', chatState: 'unavailable' }

Environment

  • Machine OS: Linux - Ubuntu 24.04
  • Phone OS: Android 11
  • Library Version: 1.26.0
  • WhatsApp Web Version: 2.3000.1018163899
  • Puppeteer Version: ^18.2.1
  • Browser Type and Version: Chromium 107.0.5296.0 - developer build 64-bit
  • Node Version: 21.7.3
  • NPM Version: 10.5.0

Types of changes

  • Dependency change
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • I have updated the documentation accordingly (index.d.ts).
  • I have updated the usage example accordingly (example.js)

emits events when a chat state changes, possible states are: [available, unavailable, typing, recording]
@pedroapolloniufscar
Copy link

nice contribution

@tuyuribr
Copy link
Collaborator

The biggest problem with this function was beign reliable enough for production. Have you tested with changes on more than 10 chats at one time ? Does it auto subscribe for the chat "typping" event ?

@lucas-almeida026
Copy link
Author

@tuyuribr I did not test for multiple chats at the same time. I also only tested using headless: false option, maybe if headless is set to true it can impact the reliability (just speculating).

it does not auto subscribe for the "typing" event, instead it "subscribes" to changes on the __x_chatstate object, every time the property __x_type is changed it emits an event with the new state value

@TheStarkster
Copy link

@lucas-almeida026
Oh my god man, Nice contribution kuddos to you!

one question though, Does it also work when user who is connected starts typing?

@NewtonMiranda00
Copy link

NewtonMiranda00 commented May 2, 2025

@lucas-almeida026

Wouldn't it make sense to separate the statuses into two categories: 'Action' (Typing, Recording, Standby) and 'Status' (Available, Unavailable)?

Right now, we mix these states, which creates confusion—especially when trying to detect if the user is offline.

For example, during some tests with this PR, when Puppeteer navigates to another chat, it triggers an 'Unavailable' status even though the user is actually in 'Typing'. This leads to misleading behavior and makes it harder to handle presence accurately.

@lucas-almeida026
Copy link
Author

@lucas-almeida026

Wouldn't it make sense to separate the statuses into two categories: 'Action' (Typing, Recording, Standby) and 'Status' (Available, Unavailable)?

I totally agree with you, it makes sense to separate these two kinds of statuses.
To be 100% honest though, I initially did this modification as a "quick hack" for a proof of concept related to a prior project I was working on. Now this is not relevant to me anymore, so I don't plan on continue to work on this feature.

If anyone is interested in continue the work there is a couple of points I think is useful to share:

  • I did not test, but I'm pretty sure the status change is only captured if the chat is rendered on the side panel (where all chats are shown). While coding, I notice that not all chats are "know", it appears to have some sort of sliding window that determines which chats become actual DOM elements, and I suspect that if a chat that is not rendered changes its state, the event will not be triggered
  • I also did not test the feature while running with the headless option set to true, maybe it doesn't work in headless mode.

@NewtonMiranda00
Copy link

I totally agree with you, it makes sense to separate these two kinds of statuses. To be 100% honest though, I initially did this modification as a "quick hack" for a proof of concept related to a prior project I was working on. Now this is not relevant to me anymore, so I don't plan on continue to work on this feature.

If anyone is interested in continue the work there is a couple of points I think is useful to share:

  • I did not test, but I'm pretty sure the status change is only captured if the chat is rendered on the side panel (where all chats are shown). While coding, I notice that not all chats are "know", it appears to have some sort of sliding window that determines which chats become actual DOM elements, and I suspect that if a chat that is not rendered changes its state, the event will not be triggered
  • I also did not test the feature while running with the headless option set to true, maybe it doesn't work in headless mode.

I understand. I'm personally interested in this functionality because I'm working with WhatsApp integrated with AIs. My project focuses on humanizing the AI, and often the person chatting doesn't even realize they're not talking to a human due to several subtle details.

I noticed and tested the issues you mentioned about it not working.

Part of the rendering seems to depend on the user's activity. I might be biased, but I believe it's just a WhatsApp resource-saving mechanism to avoid constantly showing "recording..." or "typing..."

What I noticed is that recently clicked or opened chats (those that have been selected and opened) have this status change feature active.

I looked through part of the repository, and I'm really trying to overcome the laziness to push a modification using a getter that wraps the underlying class function to retrieve:

get isTyping() {
  // Logic...
}

get isRecording() {
  // Logic
}

All of this inside the PrivateChat class.

If I can finish up my current SaaS project, I’ll aim to build and submit a PR for this part by next weekend

@ReniDelonzek
Copy link
Contributor

Hello everyone, any updates here?

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.

7 participants