Skip to content

Commit

Permalink
[JS] fix: Fixed stuck typing indicator issue (#2286)
Browse files Browse the repository at this point in the history
## Linked issues

closes: #2285  

## Details

The stuck typing indicator appears to occur when we send a `message`
activity while we're in the middle of sending a `typing` activity. The
fix is to delay the outgoing `message` activity until after we finish
sending the `typing` activity.

#### Change details

> Created a separate promise variable so that we can have two threads
waiting for the same operation to complete. This lets us block the
outgoing message activity until after the typing activity finishes
delivering.

## Attestation Checklist

- [x] My code follows the style guidelines of this project

- I have checked for/fixed spelling, linting, and other errors
- I have commented my code for clarity
- I have made corresponding changes to the documentation (updating the
doc strings in the code is sufficient)
- My changes generate no new warnings
- I have added tests that validates my changes, and provides sufficient
test coverage. I have tested with:
  - Local testing
  - E2E testing in Teams
- New and existing unit tests pass locally with my changes
  • Loading branch information
Stevenic authored Jan 30, 2025
1 parent 44efd3f commit 951d079
Showing 1 changed file with 9 additions and 2 deletions.
11 changes: 9 additions & 2 deletions js/packages/teams-ai/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,14 +1021,18 @@ export class Application<TState extends TurnState = TurnState> {
public startTypingTimer(context: TurnContext): void {
if (context.activity.type == ActivityTypes.Message && !this._typingTimer) {
// Listen for outgoing activities
context.onSendActivities((context, activities, next) => {
context.onSendActivities(async (context, activities, next) => {
// Listen for any messages to be sent from the bot
if (timerRunning) {
for (let i = 0; i < activities.length; i++) {
if (activities[i].type == ActivityTypes.Message || activities[i].channelData?.streamType) {
// Stop the timer
this.stopTypingTimer();
timerRunning = false;

// Wait for the last "typing" activity to finish sending
// - This prevents a race condition that results in the typing indicator being stuck on.
await lastSend;
break;
}
}
Expand All @@ -1038,17 +1042,20 @@ export class Application<TState extends TurnState = TurnState> {
});

let timerRunning = true;
let lastSend: Promise<any> = Promise.resolve();
const onTimeout = async () => {
try {
// Send typing activity
await context.sendActivity({ type: ActivityTypes.Typing });
lastSend = context.sendActivity({ type: ActivityTypes.Typing });
await lastSend;
} catch (err) {
// Seeing a random proxy violation error from the context object. This is because
// we're in the middle of sending an activity on a background thread when the turn ends.
// The context object throws when we try to update "this.responded = true". We can just
// eat the error but lets make sure our states cleaned up a bit.
this._typingTimer = undefined;
timerRunning = false;
lastSend = Promise.resolve();
}

// Restart timer
Expand Down

0 comments on commit 951d079

Please sign in to comment.