Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion ts/components/conversation/composition/CompositionBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { updateOutgoingLightBoxOptions } from '../../../state/ducks/modalDialog'
import { isEnterKey, isEscapeKey } from '../../../util/keyboardShortcuts';
import type { CommunityInvitation } from '../../../session/messages/outgoing/visibleMessage/VisibleMessage';
import { SessionGifPanel } from './gif/SessionGifPanel';
import { createMessageSendGuard } from './messageSendGuard';

export interface ReplyingToMessageProps {
convoId: string;
Expand Down Expand Up @@ -243,6 +244,7 @@ class CompositionBoxInner extends Component<Props, State> {
private readonly emojiPanel: RefObject<HTMLDivElement | null>;
private readonly emojiPanelButton: any;
private readonly showGifsButtonRef: RefObject<HTMLButtonElement | null>;
private readonly messageSendGuard = createMessageSendGuard();
private linkPreviewAbortController?: AbortController;

constructor(props: Props) {
Expand Down Expand Up @@ -640,7 +642,16 @@ class CompositionBoxInner extends Component<Props, State> {
return text.length > 0;
}

private async onSendMessage() {
private onSendMessage() {
const { selectedConversationKey } = this.props;
if (!selectedConversationKey) {
return this.sendCurrentMessage();
}

return this.messageSendGuard(selectedConversationKey, () => this.sendCurrentMessage());
}

private async sendCurrentMessage() {
const {
selectedConversationKey,
selectedConversation,
Expand Down
16 changes: 16 additions & 0 deletions ts/components/conversation/composition/messageSendGuard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function createMessageSendGuard() {
const conversationsSending = new Set<string>();

return async (conversationKey: string, sendMessage: () => Promise<void>) => {
if (conversationsSending.has(conversationKey)) {
return;
}

conversationsSending.add(conversationKey);
try {
await sendMessage();
} finally {
conversationsSending.delete(conversationKey);
}
};
}
Comment on lines +1 to +16

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way this is setup, I think

  • if a message is being sent to conversationA and takes a while,
  • you switch to conversationB while the one above is still being sent,
  • and try to send a message in conversationB, it wouldn't work

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, the guard is now scoped per conversation so a slow send in one conversation doesn't block a send in another one

i added a regression test for that case and pnpm ready passes locally

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import chai from 'chai';

import { createMessageSendGuard } from '../../../../../components/conversation/composition/messageSendGuard';

const { expect } = chai;

describe('createMessageSendGuard', () => {
it('ignores another send to the same conversation while one is in progress', async () => {
const sendMessage = createMessageSendGuard();
let resolveFirstSend!: () => void;
let sendCount = 0;

const firstSend = sendMessage(
'conversation-a',
() =>
new Promise<void>(resolve => {
sendCount += 1;
resolveFirstSend = resolve;
})
);
await sendMessage('conversation-a', async () => {
sendCount += 1;
});

expect(sendCount).to.equal(1);

resolveFirstSend();
await firstSend;
await sendMessage('conversation-a', async () => {
sendCount += 1;
});

expect(sendCount).to.equal(2);
});

it('allows sends to different conversations at the same time', async () => {
const sendMessage = createMessageSendGuard();
let resolveFirstSend!: () => void;
let secondSendCompleted = false;

const firstSend = sendMessage(
'conversation-a',
() =>
new Promise<void>(resolve => {
resolveFirstSend = resolve;
})
);
await sendMessage('conversation-b', async () => {
secondSendCompleted = true;
});

expect(secondSendCompleted).to.equal(true);

resolveFirstSend();
await firstSend;
});

it('allows another send after a failure', async () => {
const sendMessage = createMessageSendGuard();
const failure = new Error('send failed');
let caught: unknown;

try {
await sendMessage('conversation-a', async () => {
throw failure;
});
} catch (error) {
caught = error;
}

expect(caught).to.equal(failure);

let didRetry = false;
await sendMessage('conversation-a', async () => {
didRetry = true;
});

expect(didRetry).to.equal(true);
});
});
Loading