diff --git a/src/ChatApi.ts b/src/ChatApi.ts index 5931d4d9..4353eb54 100644 --- a/src/ChatApi.ts +++ b/src/ChatApi.ts @@ -99,4 +99,14 @@ export class ChatApi { if (!response.ok) throw new ErrorInfo(response.statusText, response.status, 4000); return response.json(); } + + async deleteMessage(conversationId: string, messageId: string): Promise { + const response = await fetch(`${this.baseUrl}/v1/conversations/${conversationId}/messages/${messageId}`, { + method: 'DELETE', + headers: { + 'ably-clientId': this.clientId, + }, + }); + if (!response.ok) throw new ErrorInfo(response.statusText, response.status, 4000); + } } diff --git a/src/Messages.test.ts b/src/Messages.test.ts index 233b33fc..913a3c68 100644 --- a/src/Messages.test.ts +++ b/src/Messages.test.ts @@ -128,4 +128,59 @@ describe('Messages', () => { }); }); }); + + describe('deleting message', () => { + it('should delete message by message object', async (context) => { + const { chatApi, realtime } = context; + + vi.spyOn(chatApi, 'deleteMessage').mockImplementation(async (conversationId, messageId) => { + context.emulateBackendPublish({ + clientId: 'clientId', + data: { + id: messageId, + client_id: 'clientId', + content: 'text', + deleted_at: 1111, + }, + }); + }); + + const conversation = new Conversation('conversationId', realtime, chatApi); + const message = await conversation.messages.delete({ id: 'messageId', content: 'text' } as any); + + expect(message).toContain({ + id: 'messageId', + client_id: 'clientId', + content: 'text', + deleted_at: 1111, + }); + }); + + it('should delete message by messageId', async (context) => { + const { chatApi, realtime } = context; + vi.spyOn(chatApi, 'deleteMessage').mockResolvedValue(undefined); + + const conversation = new Conversation('conversationId', realtime, chatApi); + const messagePromise = conversation.messages.delete('messageId'); + + context.emulateBackendPublish({ + clientId: 'clientId', + data: { + id: 'messageId', + client_id: 'clientId', + content: 'text', + deleted_at: 1111, + }, + }); + + const message = await messagePromise; + + expect(message).toContain({ + id: 'messageId', + client_id: 'clientId', + content: 'text', + deleted_at: 1111, + }); + }); + }); }); diff --git a/src/Messages.ts b/src/Messages.ts index 47a450e9..fc63c6e0 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -43,63 +43,28 @@ export class Messages { } async send(text: string): Promise { - const createdMessages: Record = {}; - - let waitingMessageId: string | null = null; - let resolver: ((message: Message) => void) | null = null; - - const waiter = ({ data }: Types.Message) => { - const message: Message = data; - if (waitingMessageId == null) createdMessages[message.id] = message; - if (waitingMessageId == message.id) resolver?.(message); - }; - - await this.channel.subscribe(MessageEvents.created, waiter); - - try { + return this.makeApiCallAndWaitForRealtimeResult(MessageEvents.created, async () => { const { id } = await this.chatApi.sendMessage(this.conversationId, text); - if (createdMessages[id]) { - this.channel.unsubscribe(MessageEvents.created, waiter); - return createdMessages[id]; - } - waitingMessageId = id; - } catch (e) { - this.channel.unsubscribe(MessageEvents.created, waiter); - throw e; - } - - return new Promise((resolve) => { - resolver = (message) => { - this.channel.unsubscribe(MessageEvents.created, waiter); - resolve(message); - }; + return id; }); } async edit(messageId: string, text: string): Promise { - let resolver: ((message: Message) => void) | null = null; - const waiter = ({ data }: Types.Message) => { - const message: Message = data; - if (messageId == message.id) resolver?.(message); - }; - - const promise: Promise = new Promise((resolve) => { - resolver = (message) => { - this.channel.unsubscribe(MessageEvents.updated, waiter); - resolve(message); - }; + return this.makeApiCallAndWaitForRealtimeResult(MessageEvents.deleted, async () => { + await this.chatApi.editMessage(this.conversationId, messageId, text); + return messageId; }); + } - await this.channel.subscribe(MessageEvents.updated, waiter); + async delete(message: Message): Promise; + async delete(messageId: string): Promise; + async delete(messageIdOrMessage: string | Message): Promise { + const messageId = typeof messageIdOrMessage === 'string' ? messageIdOrMessage : messageIdOrMessage.id; - try { - await this.chatApi.editMessage(this.conversationId, messageId, text); - } catch (e) { - this.channel.unsubscribe(MessageEvents.updated, waiter); - throw e; - } - - return promise; + return this.makeApiCallAndWaitForRealtimeResult(MessageEvents.deleted, async () => { + await this.chatApi.deleteMessage(this.conversationId, messageId); + return messageId; + }); } async subscribe(event: MessageEvents, listener: MessageListener) { @@ -118,4 +83,42 @@ export class Messages { if (!channelListener) return; this.channel.unsubscribe(event, channelListener); } + + private async makeApiCallAndWaitForRealtimeResult(event: MessageEvents, apiCall: () => Promise) { + const queuedMessages: Record = {}; + + let waitingMessageId: string | null = null; + let resolver: ((message: Message) => void) | null = null; + + const waiter = ({ data }: Types.Message) => { + const message: Message = data; + if (waitingMessageId === null) { + queuedMessages[message.id] = message; + } else if (waitingMessageId === message.id) { + resolver?.(message); + resolver = null; + } + }; + + await this.channel.subscribe(event, waiter); + + try { + const messageId = await apiCall(); + if (queuedMessages[messageId]) { + this.channel.unsubscribe(event, waiter); + return queuedMessages[messageId]; + } + waitingMessageId = messageId; + } catch (e) { + this.channel.unsubscribe(event, waiter); + throw e; + } + + return new Promise((resolve) => { + resolver = (message) => { + this.channel.unsubscribe(event, waiter); + resolve(message); + }; + }); + } }