Skip to content

Commit 4873621

Browse files
committed
fix: throttle mark read API requests
1 parent 164b676 commit 4873621

File tree

4 files changed

+126
-6
lines changed

4 files changed

+126
-6
lines changed

projects/stream-chat-angular/src/lib/channel.service.spec.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
1+
import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
22
import { Subject } from 'rxjs';
33
import { first, take } from 'rxjs/operators';
44
import {
@@ -208,6 +208,7 @@ describe('ChannelService', () => {
208208
events$.next({ eventType: 'connection.recovered' } as ClientEvent);
209209

210210
tick();
211+
flush();
211212

212213
expect(spy).toHaveBeenCalledWith(channels);
213214
expect(activeChannelSpy).toHaveBeenCalledWith(channels[0]);
@@ -577,6 +578,10 @@ describe('ChannelService', () => {
577578

578579
it('should watch for new message events', async () => {
579580
await init();
581+
// wait for mark read throttle time
582+
await new Promise((resolve) => {
583+
setTimeout(resolve, service['markReadThrottleTime']);
584+
});
580585
const spy = jasmine.createSpy();
581586
service.activeChannelMessages$.subscribe(spy);
582587
const prevCount = (spy.calls.mostRecent().args[0] as Channel[]).length;
@@ -991,6 +996,7 @@ describe('ChannelService', () => {
991996

992997
it('should add the new channel to the top of the list, and start watching it, if user is added to a channel', fakeAsync(async () => {
993998
await init();
999+
flush();
9941000
const newChannel = generateMockChannels()[0];
9951001
newChannel.cid = 'newchannel';
9961002
newChannel.id = 'newchannel';
@@ -1030,6 +1036,7 @@ describe('ChannelService', () => {
10301036
event: { channel: channel } as any as Event<DefaultStreamChatGenerics>,
10311037
});
10321038
tick();
1039+
flush();
10331040

10341041
const channels = spy.calls.mostRecent().args[0] as Channel[];
10351042
const firstChannel = channels[0];
@@ -1059,6 +1066,7 @@ describe('ChannelService', () => {
10591066
event: { channel: channel } as any as Event<DefaultStreamChatGenerics>,
10601067
});
10611068
tick();
1069+
flush();
10621070

10631071
const channels = spy.calls.mostRecent().args[0] as Channel[];
10641072

@@ -2242,6 +2250,7 @@ describe('ChannelService', () => {
22422250

22432251
it('should relaod active channel if active channel is not present after state reconnect', fakeAsync(async () => {
22442252
await init();
2253+
flush();
22452254
let activeChannel!: Channel<DefaultStreamChatGenerics>;
22462255
service.activeChannel$.subscribe((c) => (activeChannel = c!));
22472256
let channels!: Channel<DefaultStreamChatGenerics>[];
@@ -2251,6 +2260,7 @@ describe('ChannelService', () => {
22512260
mockChatClient.queryChannels.and.resolveTo(channels);
22522261
events$.next({ eventType: 'connection.recovered' } as ClientEvent);
22532262
tick();
2263+
flush();
22542264
const spy = jasmine.createSpy();
22552265
service.activeChannel$.subscribe(spy);
22562266

@@ -2276,6 +2286,7 @@ describe('ChannelService', () => {
22762286
activeChannel.state.messages.push(newMessage);
22772287
events$.next({ eventType: 'connection.recovered' } as ClientEvent);
22782288
tick();
2289+
flush();
22792290

22802291
expect(spy).not.toHaveBeenCalled();
22812292
expect(service.deselectActiveChannel).not.toHaveBeenCalled();
@@ -2639,4 +2650,76 @@ describe('ChannelService', () => {
26392650
expect(customQuery).toHaveBeenCalledWith('next-page');
26402651
expect(hasMoreSpy).toHaveBeenCalledWith(false);
26412652
});
2653+
2654+
it('should throttle mark read API calls', async () => {
2655+
await init();
2656+
// wait for mark read throttle time
2657+
await new Promise((resolve) => {
2658+
setTimeout(resolve, service['markReadThrottleTime']);
2659+
});
2660+
2661+
const activeChannel = service.activeChannel!;
2662+
spyOn(activeChannel, 'markRead');
2663+
2664+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2665+
2666+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2667+
2668+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2669+
2670+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2671+
2672+
// wait for mark read throttle time
2673+
await new Promise((resolve) => {
2674+
setTimeout(resolve, service['markReadThrottleTime']);
2675+
});
2676+
2677+
expect(activeChannel.markRead).toHaveBeenCalledTimes(2);
2678+
});
2679+
2680+
it('should throttle mark read API calls - channel change', async () => {
2681+
await init();
2682+
// wait for mark read throttle time
2683+
await new Promise((resolve) => {
2684+
setTimeout(resolve, service['markReadThrottleTime']);
2685+
});
2686+
2687+
const activeChannel = service.activeChannel!;
2688+
spyOn(activeChannel, 'markRead');
2689+
2690+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2691+
2692+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2693+
2694+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2695+
2696+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2697+
2698+
service.setAsActiveChannel(service.channels[1]);
2699+
2700+
expect(activeChannel.markRead).toHaveBeenCalledTimes(2);
2701+
});
2702+
2703+
it('should throttle mark read API calls - reset', async () => {
2704+
await init();
2705+
// wait for mark read throttle time
2706+
await new Promise((resolve) => {
2707+
setTimeout(resolve, service['markReadThrottleTime']);
2708+
});
2709+
2710+
const activeChannel = service.activeChannel!;
2711+
spyOn(activeChannel, 'markRead');
2712+
2713+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2714+
2715+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2716+
2717+
(activeChannel as MockChannel).handleEvent('message.new', mockMessage());
2718+
2719+
expect(activeChannel.markRead).toHaveBeenCalledTimes(1);
2720+
2721+
service.reset();
2722+
2723+
expect(activeChannel.markRead).toHaveBeenCalledTimes(2);
2724+
});
26422725
});

projects/stream-chat-angular/src/lib/channel.service.thread.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
1+
import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
22
import { Subject } from 'rxjs';
33
import { first } from 'rxjs/operators';
44
import {
@@ -235,6 +235,7 @@ describe('ChannelService - threads', () => {
235235
spy.calls.reset();
236236
events$.next({ eventType: 'connection.recovered' } as ClientEvent);
237237
tick();
238+
flush();
238239

239240
expect(spy).toHaveBeenCalledWith(undefined);
240241
}));
@@ -314,6 +315,10 @@ describe('ChannelService - threads', () => {
314315

315316
it('should watch for new message events', async () => {
316317
await init();
318+
// wait for mark read throttle time
319+
await new Promise((resolve) => {
320+
setTimeout(resolve, service['markReadThrottleTime']);
321+
});
317322
const spy = jasmine.createSpy();
318323
const parentMessage = mockMessage();
319324
await service.setAsActiveParentMessage(parentMessage);

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,9 @@ export class ChannelService<
428428
};
429429
private dismissErrorNotification?: () => void;
430430
private areReadEventsPaused = false;
431+
private markReadThrottleTime = 1050;
432+
private markReadTimeout?: ReturnType<typeof setTimeout>;
433+
private scheduledMarkReadRequest?: () => void;
431434

432435
constructor(
433436
private chatClientService: ChatClientService<T>,
@@ -563,6 +566,7 @@ export class ChannelService<
563566
return;
564567
}
565568
this.stopWatchForActiveChannelEvents(prevActiveChannel);
569+
this.flushMarkReadQueue();
566570
this.areReadEventsPaused = false;
567571
const readState =
568572
channel.state.read[this.chatClientService.chatClient.user?.id || ''];
@@ -595,6 +599,7 @@ export class ChannelService<
595599
return;
596600
}
597601
this.stopWatchForActiveChannelEvents(activeChannel);
602+
this.flushMarkReadQueue();
598603
this.activeChannelMessagesSubject.next([]);
599604
this.activeChannelSubject.next(undefined);
600605
this.activeParentMessageIdSubject.next(undefined);
@@ -2220,16 +2225,43 @@ export class ChannelService<
22202225
this.usersTypingInThreadSubject.next([]);
22212226
}
22222227

2223-
private markRead(channel: Channel<T>) {
2228+
private markRead(channel: Channel<T>, isThrottled = true) {
22242229
if (
22252230
this.canSendReadEvents &&
22262231
this.shouldMarkActiveChannelAsRead &&
2227-
!this.areReadEventsPaused
2232+
!this.areReadEventsPaused &&
2233+
channel.countUnread() > 0
22282234
) {
2229-
void channel.markRead();
2235+
if (isThrottled) {
2236+
this.markReadThrottled(channel);
2237+
} else {
2238+
void channel.markRead();
2239+
}
2240+
}
2241+
}
2242+
2243+
private markReadThrottled(channel: Channel<T>) {
2244+
if (!this.markReadTimeout) {
2245+
this.markRead(channel, false);
2246+
this.markReadTimeout = setTimeout(() => {
2247+
this.flushMarkReadQueue();
2248+
}, this.markReadThrottleTime);
2249+
} else {
2250+
clearTimeout(this.markReadTimeout);
2251+
this.scheduledMarkReadRequest = () => this.markRead(channel, false);
2252+
this.markReadTimeout = setTimeout(() => {
2253+
this.flushMarkReadQueue();
2254+
}, this.markReadThrottleTime);
22302255
}
22312256
}
22322257

2258+
private flushMarkReadQueue() {
2259+
this.scheduledMarkReadRequest?.();
2260+
this.scheduledMarkReadRequest = undefined;
2261+
clearTimeout(this.markReadTimeout);
2262+
this.markReadTimeout = undefined;
2263+
}
2264+
22332265
private async _init(settings: {
22342266
shouldSetActiveChannel: boolean;
22352267
messagePageSize: number;

projects/stream-chat-angular/src/lib/mocks/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export const generateMockChannels = (length = 25) => {
9898
sendAction: () => {},
9999
deleteImage: () => {},
100100
deleteFile: () => {},
101-
countUnread: () => {},
101+
countUnread: () => 3,
102102
markRead: () => {},
103103
getReplies: () => {},
104104
keystroke: () => {},

0 commit comments

Comments
 (0)