Skip to content
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

chatClient.connectUser() before previous websocket connection is disconnected causes wrong current user #679

Closed
JayAhn2 opened this issue Jan 14, 2025 · 5 comments

Comments

@JayAhn2
Copy link

JayAhn2 commented Jan 14, 2025

When users navigate away from the chat component and then quickly return, the previously active websocket connection may not have fully closed before the new ws connection is established. As a result, the current user occasionally appears as if they are another participant in the conversation. In the browser’s network panel, you can observe the old WebSocket still terminating while the new session is starting. This leads to chat messages from the “current user” being displayed on the left side (as though they belong to a different user).

Steps to Reproduce

  • Navigate to a route that uses a Stream Chat-based component.
  • Navigate away from that route, triggering ngOnDestroy().
  • Quickly navigate back, before disconnectUser() has fully completed.
  • Observe that the chat now shows the current user as if they are a different user.

Correct state
image
When visit the component before closing previous ws connection
image

Code

import {
  ChangeDetectionStrategy,
  Component,
  inject,
  OnDestroy,
  OnInit,
} from '@angular/core';
import { FuseConfigService } from '@fuse/services/config';
import { streamChatTranslationKo } from '@libs/shared/stream-chat/translation';
import { TranslateModule } from '@ngx-translate/core';
import {
  injectExpertProgramStreamChatService,
  provideExpertProgramStreamChatService,
} from '@sprint-expert/app/modules/expert-program-stream-chat/expert-program-stream-chat.service';
import { environment } from '@sprint-expert/environments/environment';
import {
  ChannelService,
  ChatClientService,
  StreamAutocompleteTextareaModule,
  StreamChatModule,
  StreamI18nService,
  ThemeService,
} from 'stream-chat-angular';

@Component({
  selector: 'app-expert-program-stream-chat',
  standalone: true,
  imports: [
    TranslateModule,
    StreamAutocompleteTextareaModule,
    StreamChatModule,
  ],
  template: `
    <div class="flex h-full w-full flex-row">
      <!-- Channel list container -->
      <div
        class="h-full w-64 max-w-80 basis-1/3 overflow-y-auto border-r border-gray-200">
        <stream-channel-list />
      </div>

      <!-- Channel (messages) area -->
      <div class="flex h-full flex-1 basis-2/3 flex-col">
        <stream-channel class="flex h-full flex-1 flex-col">
          <!-- Header (fixed) -->
          <div class="shrink-0 border-b border-gray-200">
            <stream-channel-header />
          </div>

          <!-- Message list (scrollable) -->
          <div class="flex-1 overflow-y-hidden">
            <stream-message-list />
            <stream-notification-list />
          </div>

          <!-- Message input (fixed) -->
          <div class="shrink-0 border-t border-gray-200">
            <stream-message-input />
          </div>

          <!-- Thread view (scrollable) -->
          <stream-thread
            name="thread"
            class="flex-1 overflow-y-auto border-t border-gray-200">
            <stream-message-list mode="thread" />
            <stream-message-input mode="thread" />
          </stream-thread>
        </stream-channel>
      </div>
    </div>
  `,
  styles: ``,
  providers: [provideExpertProgramStreamChatService()],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExpertProgramStreamChatComponent implements OnInit, OnDestroy {
  readonly vm = injectExpertProgramStreamChatService();
  readonly chatClient = inject(ChatClientService);
  readonly channelService = inject(ChannelService);
  readonly streamI18nService = inject(StreamI18nService);
  readonly fuseConfigService = inject(FuseConfigService);
  readonly themeService = inject(ThemeService);

  constructor() {
    this.fuseConfigService.config$.subscribe(config => {
      this.themeService.theme$.next(config.scheme);
    });
  }

  async ngOnInit() {
    this.connectUser();
    this.streamI18nService.setTranslation('ko', streamChatTranslationKo);
  }

  ngOnDestroy(): void {
    this.disconnectUser();
  }

  private connectUser() {
    const apiKey = environment.streamChat.apiKey;
    this.chatClient.init(apiKey, this.vm.currentUserId$$(), async () => {
      return await this.vm.getStreamChatToken();
    });

    this.channelService.init({
      type: 'messaging',
      members: { $in: [this.vm.currentUserId$$()] },
      disabled: false,
    });
  }

  private async disconnectUser() {
    if (!this.chatClient.chatClient?.user) {
      return;
    }
    this.channelService.reset();
    await this.chatClient.disconnectUser();
  }
}
@JayAhn2 JayAhn2 changed the title chatClient.connectUser() before previous websocket connection is disconnected cause wrong current user chatClient.connectUser() before previous websocket connection is disconnected causes wrong current user Jan 14, 2025
@szuperaz
Copy link
Contributor

You must wait for the previous disconnect to finish before connecting with another user.

private connectUser() {
  const apiKey = environment.streamChat.apiKey;
  // TODO: wait for disconnect to finish
  await this.chatClient.init(apiKey, this.vm.currentUserId$$(), async () => {
    return await this.vm.getStreamChatToken();
  });

  await this.channelService.init({
    type: 'messaging',
    members: { $in: [this.vm.currentUserId$$()] },
    disabled: false,
  });
}

private async disconnectUser() {
  if (!this.chatClient.chatClient?.user) {
    return;
  }
  this.channelService.reset();
  await this.chatClient.disconnectUser();
}

Since there is no way to wait for ngOnDestory to finish before a new component instance is created you can use a service to coordinate between connect and disconnect operations.

Feel free to reopen the ticket if you have further questions.

@JayAhn2
Copy link
Author

JayAhn2 commented Jan 14, 2025

@szuperaz I tried your suggestion, but it doesn't work.

I suspect that isSentByCurrentUser logic is not properly updated when we initialize new ws connection before previous ws connection is closed completely.

@szuperaz
Copy link
Contributor

This could also be if you don't wait for user connect before querying channels, if you look at my code, I've added await here:

await this.chatClient.init(apiKey, this.vm.currentUserId$$(), async () => {
   return await this.vm.getStreamChatToken();
});
await this.channelService.init({
    type: 'messaging',
    members: { $in: [this.vm.currentUserId$$()] },
    disabled: false,
});

@JayAhn2
Copy link
Author

JayAhn2 commented Jan 14, 2025

@szuperaz Thank you for your quick response.

I've tried, but still the problem persists.

Here is the recording of this symptom, which that the current user is not properly set.

Screen.Recording.2025-01-14.at.6.01.12.PM.mov

Hope this helps to fix this problem.

@szuperaz
Copy link
Contributor

If you believe the issue is inside the SDK please create a reproduction by forking our sample app

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

No branches or pull requests

2 participants