Use numerical acks

This commit is contained in:
Jack Kingsman
2026-01-10 00:51:54 -08:00
parent e262bd677a
commit 2798b551f8
17 changed files with 79 additions and 58 deletions
+10 -10
View File
@@ -305,13 +305,13 @@ export function App() {
return updated;
});
},
onMessageAcked: (messageId: number) => {
// Update message acked status
onMessageAcked: (messageId: number, ackCount: number) => {
// Update message acked count
setMessages((prev) => {
const idx = prev.findIndex((m) => m.id === messageId);
if (idx >= 0) {
const updated = [...prev];
updated[idx] = { ...prev[idx], acked: true };
updated[idx] = { ...prev[idx], acked: ackCount };
return updated;
}
return prev;
@@ -639,7 +639,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false, // Show as incoming (from the repeater)
acked: true, // Mark as acked since it's a response
acked: 1, // Mark as acked since it's a response
};
// Create a second message for neighbors
@@ -654,7 +654,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false,
acked: true,
acked: 1,
};
// Create a third message for ACL
@@ -669,7 +669,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false,
acked: true,
acked: 1,
};
// Add all messages to the list
@@ -690,7 +690,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false,
acked: true,
acked: 1,
};
setMessages((prev) => [...prev, errorMessage]);
}
@@ -718,7 +718,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: true,
acked: true,
acked: 1,
};
setMessages((prev) => [...prev, commandMessage]);
@@ -740,7 +740,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false,
acked: true,
acked: 1,
};
setMessages((prev) => [...prev, responseMessage]);
@@ -756,7 +756,7 @@ export function App() {
txt_type: 0,
signature: null,
outgoing: false,
acked: true,
acked: 1,
};
setMessages((prev) => [...prev, errorMessage]);
}
+1 -1
View File
@@ -295,7 +295,7 @@ export function MessageList({
{formatTime(msg.sender_timestamp || msg.received_at)}
</span>
)}
{msg.outgoing && (msg.acked ? ' ✓' : ' ?')}
{msg.outgoing && (msg.acked > 0 ? `${msg.acked > 1 ? msg.acked : ''}` : ' ?')}
</div>
</div>
</div>
@@ -37,7 +37,7 @@ function createMessage(overrides: Partial<Message>): Message {
txt_type: 0,
signature: null,
outgoing: false,
acked: false,
acked: 0,
...overrides,
};
}
+3 -3
View File
@@ -52,7 +52,7 @@ describe('Repeater message sender parsing', () => {
it('non-repeater messages still get sender parsed', () => {
const channelMessage = 'Alice: Hello everyone!';
const contactType = CONTACT_TYPE_CLIENT;
const contactType: number = CONTACT_TYPE_CLIENT;
const isRepeater = contactType === CONTACT_TYPE_REPEATER;
const { sender, content } = isRepeater
@@ -107,7 +107,7 @@ describe('Repeater password handling', () => {
});
it('normal password is passed through unchanged', () => {
const trimmed = 'mySecretPassword';
const trimmed: string = 'mySecretPassword';
const password = trimmed === '.' ? '' : trimmed;
expect(password).toBe('mySecretPassword');
@@ -123,7 +123,7 @@ describe('Repeater password handling', () => {
});
it('".." is NOT converted (only single dot)', () => {
const trimmed = '..';
const trimmed: string = '..';
const password = trimmed === '.' ? '' : trimmed;
// Double dot is passed through as-is (it's a valid password)
+1 -1
View File
@@ -73,7 +73,7 @@ describe('shouldIncrementUnread', () => {
txt_type: 0,
signature: null,
outgoing: false,
acked: false,
acked: 0,
...overrides,
});
+9 -7
View File
@@ -21,7 +21,7 @@ function parseWebSocketMessage(
onMessage?: (message: Message) => void;
onContact?: (contact: Contact) => void;
onRawPacket?: (packet: RawPacket) => void;
onMessageAcked?: (messageId: number) => void;
onMessageAcked?: (messageId: number, ackCount: number) => void;
}
): { type: string; handled: boolean } {
try {
@@ -46,9 +46,11 @@ function parseWebSocketMessage(
case 'raw_packet':
handlers.onRawPacket?.(msg.data as RawPacket);
return { type: msg.type, handled: !!handlers.onRawPacket };
case 'message_acked':
handlers.onMessageAcked?.((msg.data as { message_id: number }).message_id);
case 'message_acked': {
const ackData = msg.data as { message_id: number; ack_count: number };
handlers.onMessageAcked?.(ackData.message_id, ackData.ack_count);
return { type: msg.type, handled: !!handlers.onMessageAcked };
}
case 'pong':
return { type: msg.type, handled: true };
default:
@@ -77,18 +79,18 @@ describe('parseWebSocketMessage', () => {
});
});
it('routes message_acked to onMessageAcked with message ID', () => {
it('routes message_acked to onMessageAcked with message ID and ack count', () => {
const onMessageAcked = vi.fn();
const data = JSON.stringify({
type: 'message_acked',
data: { message_id: 42 },
data: { message_id: 42, ack_count: 3 },
});
const result = parseWebSocketMessage(data, { onMessageAcked });
expect(result.type).toBe('message_acked');
expect(result.handled).toBe(true);
expect(onMessageAcked).toHaveBeenCalledWith(42);
expect(onMessageAcked).toHaveBeenCalledWith(42, 3);
});
it('routes new message to onMessage handler', () => {
@@ -100,7 +102,7 @@ describe('parseWebSocketMessage', () => {
text: 'Hello',
received_at: 1700000000,
outgoing: false,
acked: false,
acked: 0,
};
const data = JSON.stringify({ type: 'message', data: messageData });
+4 -2
View File
@@ -76,7 +76,8 @@ export interface Message {
txt_type: number;
signature: string | null;
outgoing: boolean;
acked: boolean;
/** ACK count: 0 = not acked, 1+ = number of acks/flood echoes received */
acked: number;
}
export type ConversationType = 'contact' | 'channel' | 'raw';
@@ -110,7 +111,8 @@ export interface AppSettingsUpdate {
max_radio_contacts?: number;
}
/** Contact type constant for repeaters */
/** Contact type constants */
export const CONTACT_TYPE_CLIENT = 1;
export const CONTACT_TYPE_REPEATER = 2;
export interface NeighborInfo {
+5 -3
View File
@@ -18,7 +18,7 @@ interface UseWebSocketOptions {
onMessage?: (message: Message) => void;
onContact?: (contact: Contact) => void;
onRawPacket?: (packet: RawPacket) => void;
onMessageAcked?: (messageId: number) => void;
onMessageAcked?: (messageId: number, ackCount: number) => void;
onError?: (error: ErrorEvent) => void;
}
@@ -82,9 +82,11 @@ export function useWebSocket(options: UseWebSocketOptions) {
case 'raw_packet':
options.onRawPacket?.(msg.data as RawPacket);
break;
case 'message_acked':
options.onMessageAcked?.((msg.data as { message_id: number }).message_id);
case 'message_acked': {
const ackData = msg.data as { message_id: number; ack_count: number };
options.onMessageAcked?.(ackData.message_id, ackData.ack_count);
break;
}
case 'error':
options.onError?.(msg.data as ErrorEvent);
break;