From 3ed514c23499ce20fc4f3964c673a59d65ea4a56 Mon Sep 17 00:00:00 2001 From: Zita Szupera Date: Tue, 3 Oct 2023 16:33:40 +0200 Subject: [PATCH] feat: add before send/update message hooks --- .../src/lib/channel.service.spec.ts | 70 +++++++++++++++++++ .../src/lib/channel.service.ts | 37 ++++++++-- projects/stream-chat-angular/src/lib/types.ts | 11 +++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/projects/stream-chat-angular/src/lib/channel.service.spec.ts b/projects/stream-chat-angular/src/lib/channel.service.spec.ts index 77ab0591..b4786b81 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.spec.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.spec.ts @@ -25,6 +25,7 @@ import { NotificationService } from './notification.service'; import { AttachmentUpload, DefaultStreamChatGenerics, + MessageInput, StreamMessage, } from './types'; @@ -1181,6 +1182,59 @@ describe('ChannelService', () => { expect(messageCount).toEqual(prevMessageCount + 1); }); + it('should send message - #beforeSendMessage hook is provided', async () => { + await init(); + let channel!: Channel; + service.activeChannel$.pipe(first()).subscribe((c) => (channel = c!)); + spyOn(channel, 'sendMessage').and.callThrough(); + spyOn(channel.state, 'addMessageSorted').and.callThrough(); + const text = 'Hi'; + const attachments = [{ fallback: 'image.png', url: 'http://url/to/image' }]; + const mentionedUsers = [{ id: 'sara', name: 'Sara' }]; + const spy = jasmine.createSpy(); + service.beforeSendMessage = spy; + spy.and.callFake((i: MessageInput) => { + i.customData = { custom: 'red' }; + return i; + }); + await service.sendMessage(text, attachments, mentionedUsers); + + expect(channel.sendMessage).toHaveBeenCalledWith({ + text, + attachments, + mentioned_users: ['sara'], + id: jasmine.any(String), + parent_id: undefined, + quoted_message_id: undefined, + custom: 'red', + }); + + expect(channel.state.addMessageSorted).toHaveBeenCalledWith( + jasmine.objectContaining({ custom: 'red' }), + true + ); + + spy.and.callFake((i: MessageInput) => { + i.text = 'censored'; + return Promise.resolve(i); + }); + await service.sendMessage(text, attachments, mentionedUsers); + + expect(channel.sendMessage).toHaveBeenCalledWith({ + text: 'censored', + attachments, + mentioned_users: ['sara'], + id: jasmine.any(String), + parent_id: undefined, + quoted_message_id: undefined, + }); + + expect(channel.state.addMessageSorted).toHaveBeenCalledWith( + jasmine.objectContaining({ text: 'censored' }), + true + ); + }); + it('should send action', async () => { await init(); let channel!: Channel; @@ -1230,6 +1284,22 @@ describe('ChannelService', () => { expect(mockChatClient.updateMessage).toHaveBeenCalledWith(message); }); + it('should update message - #beforeUpdateMessage is provided', async () => { + const message = mockMessage(); + mockChatClient.updateMessage.and.resolveTo({ message }); + const spy = jasmine.createSpy(); + service.beforeUpdateMessage = spy; + spy.and.callFake((m: StreamMessage) => { + m.text = 'Testing beforeUpdateMessage hook'; + return Promise.resolve(m); + }); + await service.updateMessage(message); + + expect(mockChatClient.updateMessage).toHaveBeenCalledWith( + jasmine.objectContaining({ text: 'Testing beforeUpdateMessage hook' }) + ); + }); + it('should remove translation object before updating message', () => { const message = mockMessage(); void service.updateMessage({ diff --git a/projects/stream-chat-angular/src/lib/channel.service.ts b/projects/stream-chat-angular/src/lib/channel.service.ts index 078be5db..a5415da2 100644 --- a/projects/stream-chat-angular/src/lib/channel.service.ts +++ b/projects/stream-chat-angular/src/lib/channel.service.ts @@ -31,6 +31,7 @@ import { AttachmentUpload, ChannelQueryState, DefaultStreamChatGenerics, + MessageInput, MessageReactionType, StreamMessage, } from './types'; @@ -277,6 +278,18 @@ export class ChannelService< messageDeleteConfirmationHandler?: ( message: StreamMessage ) => Promise; + /** + * The provided method will be called before a new message is sent to Stream's API. You can use this hook to tranfrom or enrich the message being sent. + */ + beforeSendMessage?: ( + input: MessageInput + ) => MessageInput | Promise>; + /** + * The provided method will be called before a message is sent to Stream's API for update. You can use this hook to tranfrom or enrich the message being updated. + */ + beforeUpdateMessage?: ( + message: StreamMessage + ) => StreamMessage | Promise>; private channelsSubject = new BehaviorSubject[] | undefined>( undefined ); @@ -723,19 +736,30 @@ export class ChannelService< quotedMessageId: string | undefined = undefined, customData: undefined | Partial = undefined ) { - const preview = createMessagePreview( - this.chatClientService.chatClient.user!, + let input: MessageInput = { text, attachments, mentionedUsers, parentId, quotedMessageId, - customData + customData, + }; + if (this.beforeSendMessage) { + input = await this.beforeSendMessage(input); + } + const preview = createMessagePreview( + this.chatClientService.chatClient.user!, + input.text, + input.attachments, + input.mentionedUsers, + input.parentId, + input.quotedMessageId, + input.customData ); const channel = this.activeChannelSubject.getValue()!; preview.readBy = []; channel.state.addMessageSorted(preview, true); - const response = await this.sendMessageRequest(preview, customData); + const response = await this.sendMessageRequest(preview, input.customData); return response; } @@ -761,8 +785,11 @@ export class ChannelService< * @param message Mesage to be updated */ async updateMessage(message: StreamMessage) { - const messageToUpdate = { ...message }; + let messageToUpdate = { ...message }; delete messageToUpdate.i18n; + if (this.beforeUpdateMessage) { + messageToUpdate = await this.beforeUpdateMessage(messageToUpdate); + } const response = await this.chatClientService.chatClient.updateMessage( messageToUpdate as any as UpdatedMessage ); diff --git a/projects/stream-chat-angular/src/lib/types.ts b/projects/stream-chat-angular/src/lib/types.ts index 3f531b18..5b0dece0 100644 --- a/projects/stream-chat-angular/src/lib/types.ts +++ b/projects/stream-chat-angular/src/lib/types.ts @@ -349,3 +349,14 @@ export type ChannelQueryState = { // No type def from stream-chat error?: unknown; }; + +export type MessageInput< + T extends DefaultStreamChatGenerics = DefaultStreamChatGenerics +> = { + text: string; + attachments: Attachment[]; + mentionedUsers: UserResponse[]; + parentId: string | undefined; + quotedMessageId: string | undefined; + customData: undefined | Partial; +};