Forward whole message to FE on resend so the browser updates

This commit is contained in:
Jack Kingsman
2026-03-13 21:57:19 -07:00
parent f41c7756d3
commit 39a687da58
8 changed files with 149 additions and 6 deletions
+7 -1
View File
@@ -34,6 +34,12 @@ import type {
UnreadCounts,
} from './types';
export interface ResendChannelMessageResponse {
status: string;
message_id: number;
message?: Message;
}
const API_BASE = '/api';
async function fetchJson<T>(url: string, options?: RequestInit): Promise<T> {
@@ -238,7 +244,7 @@ export const api = {
body: JSON.stringify({ channel_key: channelKey, text }),
}),
resendChannelMessage: (messageId: number, newTimestamp?: boolean) =>
fetchJson<{ status: string; message_id: number }>(
fetchJson<ResendChannelMessageResponse>(
`/messages/channel/${messageId}/resend${newTimestamp ? '?new_timestamp=true' : ''}`,
{ method: 'POST' }
),
+11 -2
View File
@@ -69,7 +69,16 @@ export function useConversationActions({
const handleResendChannelMessage = useCallback(
async (messageId: number, newTimestamp?: boolean) => {
try {
await api.resendChannelMessage(messageId, newTimestamp);
const resent = await api.resendChannelMessage(messageId, newTimestamp);
const resentMessage = resent.message;
if (
newTimestamp &&
resentMessage &&
activeConversationRef.current?.type === 'channel' &&
activeConversationRef.current.id === resentMessage.conversation_key
) {
addMessageIfNew(resentMessage);
}
toast.success(newTimestamp ? 'Message resent with new timestamp' : 'Message resent');
} catch (err) {
toast.error('Failed to resend', {
@@ -77,7 +86,7 @@ export function useConversationActions({
});
}
},
[]
[activeConversationRef, addMessageIfNew]
);
const handleSetChannelFloodScopeOverride = useCallback(
@@ -125,6 +125,74 @@ describe('useConversationActions', () => {
expect(args.messageInputRef.current?.appendText).toHaveBeenCalledWith('@[Alice] ');
});
it('appends a new-timestamp resend immediately for the active channel', async () => {
const resentMessage: Message = {
...sentMessage,
id: 99,
sender_timestamp: 1700000100,
received_at: 1700000100,
};
mocks.api.resendChannelMessage.mockResolvedValue({
status: 'ok',
message_id: resentMessage.id,
message: resentMessage,
});
const args = createArgs();
const { result } = renderHook(() => useConversationActions(args));
await act(async () => {
await result.current.handleResendChannelMessage(sentMessage.id, true);
});
expect(mocks.api.resendChannelMessage).toHaveBeenCalledWith(sentMessage.id, true);
expect(args.addMessageIfNew).toHaveBeenCalledWith(resentMessage);
});
it('does not append a byte-perfect resend locally', async () => {
mocks.api.resendChannelMessage.mockResolvedValue({
status: 'ok',
message_id: sentMessage.id,
});
const args = createArgs();
const { result } = renderHook(() => useConversationActions(args));
await act(async () => {
await result.current.handleResendChannelMessage(sentMessage.id, false);
});
expect(args.addMessageIfNew).not.toHaveBeenCalled();
});
it('does not append a resend if the user has switched conversations', async () => {
const resentMessage: Message = {
...sentMessage,
id: 100,
sender_timestamp: 1700000200,
received_at: 1700000200,
};
mocks.api.resendChannelMessage.mockResolvedValue({
status: 'ok',
message_id: resentMessage.id,
message: resentMessage,
});
const args = createArgs();
const { result } = renderHook(() => useConversationActions(args));
await act(async () => {
const resendPromise = result.current.handleResendChannelMessage(sentMessage.id, true);
args.activeConversationRef.current = {
type: 'channel',
id: 'AA'.repeat(16),
name: 'Other',
};
await resendPromise;
});
expect(args.addMessageIfNew).not.toHaveBeenCalled();
});
it('merges returned contact data after path discovery', async () => {
const contactKey = 'aa'.repeat(32);
const discoveredContact: Contact = {