Skip to content

Commit

Permalink
feat: add delete method
Browse files Browse the repository at this point in the history
  • Loading branch information
ttypic committed Dec 18, 2023
1 parent 356b8f7 commit 7544fc6
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 49 deletions.
10 changes: 10 additions & 0 deletions src/ChatApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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);
}
}
55 changes: 55 additions & 0 deletions src/Messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,59 @@ describe('Messages', () => {
});
});
});

describe('deleting message', () => {
it<TestContext>('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<TestContext>('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,
});
});
});
});
101 changes: 52 additions & 49 deletions src/Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,63 +43,28 @@ export class Messages {
}

async send(text: string): Promise<Message> {
const createdMessages: Record<string, Message> = {};

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<Message> {
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<Message> = 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<Message>;
async delete(messageId: string): Promise<Message>;
async delete(messageIdOrMessage: string | Message): Promise<Message> {
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) {
Expand All @@ -118,4 +83,42 @@ export class Messages {
if (!channelListener) return;
this.channel.unsubscribe(event, channelListener);
}

private async makeApiCallAndWaitForRealtimeResult(event: MessageEvents, apiCall: () => Promise<string>) {
const queuedMessages: Record<string, Message> = {};

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<Message>((resolve) => {
resolver = (message) => {
this.channel.unsubscribe(event, waiter);
resolve(message);
};
});
}
}

0 comments on commit 7544fc6

Please sign in to comment.