mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Tidy up message dedupe logic
This commit is contained in:
1
frontend/dist/assets/index-70eOpM-W.js.map
vendored
1
frontend/dist/assets/index-70eOpM-W.js.map
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index-CfOMUIYi.js.map
vendored
Normal file
1
frontend/dist/assets/index-CfOMUIYi.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
frontend/dist/index.html
vendored
2
frontend/dist/index.html
vendored
@@ -13,7 +13,7 @@
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/assets/index-70eOpM-W.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CfOMUIYi.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DZ67iE5i.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -174,20 +174,10 @@ export function MessageList({
|
||||
return <div className="flex-1 overflow-y-auto p-5 text-center text-muted-foreground">No messages yet</div>;
|
||||
}
|
||||
|
||||
// Deduplicate messages by content + timestamp (same message via different paths)
|
||||
const deduplicatedMessages = messages.reduce<Message[]>((acc, msg) => {
|
||||
const key = `${msg.type}-${msg.conversation_key}-${msg.text}-${msg.sender_timestamp}`;
|
||||
const existing = acc.find(m =>
|
||||
`${m.type}-${m.conversation_key}-${m.text}-${m.sender_timestamp}` === key
|
||||
);
|
||||
if (!existing) {
|
||||
acc.push(msg);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// Sort messages by received_at ascending (oldest first)
|
||||
const sortedMessages = [...deduplicatedMessages].sort(
|
||||
// Note: Deduplication is handled by useConversationMessages.addMessageIfNew()
|
||||
// and the database UNIQUE constraint on (type, conversation_key, text, sender_timestamp)
|
||||
const sortedMessages = [...messages].sort(
|
||||
(a, b) => a.received_at - b.received_at
|
||||
);
|
||||
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* Tests for message deduplication in MessageList.
|
||||
*
|
||||
* Messages arriving via different packet paths should be deduplicated
|
||||
* based on (type, conversation_key, text, sender_timestamp).
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import type { Message } from '../types';
|
||||
|
||||
/**
|
||||
* Deduplication logic extracted from MessageList for testing.
|
||||
* Same message via different paths = same (type, conversation_key, text, timestamp)
|
||||
*/
|
||||
function deduplicateMessages(messages: Message[]): Message[] {
|
||||
return messages.reduce<Message[]>((acc, msg) => {
|
||||
const key = `${msg.type}-${msg.conversation_key}-${msg.text}-${msg.sender_timestamp}`;
|
||||
const existing = acc.find(m =>
|
||||
`${m.type}-${m.conversation_key}-${m.text}-${m.sender_timestamp}` === key
|
||||
);
|
||||
if (!existing) {
|
||||
acc.push(msg);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
function createMessage(overrides: Partial<Message>): Message {
|
||||
return {
|
||||
id: 1,
|
||||
type: 'CHAN',
|
||||
conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', // 32-char hex channel key
|
||||
text: 'Test message',
|
||||
sender_timestamp: 1700000000,
|
||||
received_at: 1700000001,
|
||||
path_len: null,
|
||||
txt_type: 0,
|
||||
signature: null,
|
||||
outgoing: false,
|
||||
acked: 0,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('Message Deduplication', () => {
|
||||
it('keeps unique messages', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, text: 'Message 1', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, text: 'Message 2', sender_timestamp: 2000 }),
|
||||
createMessage({ id: 3, text: 'Message 3', sender_timestamp: 3000 }),
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('deduplicates same channel message via different paths', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', text: 'Hello', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', text: 'Hello', sender_timestamp: 1000 }), // duplicate
|
||||
createMessage({ id: 3, conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', text: 'Hello', sender_timestamp: 1000 }), // duplicate
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].id).toBe(1); // keeps first occurrence
|
||||
});
|
||||
|
||||
it('keeps messages with same text but different timestamps', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, text: 'Hello', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, text: 'Hello', sender_timestamp: 2000 }), // different timestamp
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('keeps messages with same text but different channels', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', text: 'Hello', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, conversation_key: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', text: 'Hello', sender_timestamp: 1000 }), // different channel
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('deduplicates same DM via different paths', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, type: 'PRIV', conversation_key: 'abc123def456789012345678901234567890123456789012345678901234', text: 'Hi', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, type: 'PRIV', conversation_key: 'abc123def456789012345678901234567890123456789012345678901234', text: 'Hi', sender_timestamp: 1000 }), // duplicate
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('keeps DMs from different senders with same text', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, type: 'PRIV', conversation_key: 'abc123def456789012345678901234567890123456789012345678901234', text: 'Hi', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, type: 'PRIV', conversation_key: 'def456789012345678901234567890123456789012345678901234567890', text: 'Hi', sender_timestamp: 1000 }),
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('keeps channel message and DM with same text', () => {
|
||||
const messages = [
|
||||
createMessage({ id: 1, type: 'CHAN', conversation_key: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0', text: 'Hello', sender_timestamp: 1000 }),
|
||||
createMessage({ id: 2, type: 'PRIV', conversation_key: 'abc123def456789012345678901234567890123456789012345678901234', text: 'Hello', sender_timestamp: 1000 }),
|
||||
];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('handles empty array', () => {
|
||||
const result = deduplicateMessages([]);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('handles single message', () => {
|
||||
const messages = [createMessage({ id: 1 })];
|
||||
|
||||
const result = deduplicateMessages(messages);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user