diff --git a/public/index.html b/public/index.html index 55cb6ca..aa8c313 100644 --- a/public/index.html +++ b/public/index.html @@ -92,7 +92,7 @@ - + diff --git a/public/live.js b/public/live.js index 201fc55..66b4fc3 100644 --- a/public/live.js +++ b/public/live.js @@ -1470,6 +1470,9 @@ if (showOnlyFavorites && !packets.some(p => packetInvolvesFavorite(p))) return; if (window.MeshAudio) MeshAudio.sonifyPacket(first); + // Add single consolidated feed item for the group + const allHops = (decoded.path?.hops) || []; + addFeedItem(icon, typeName, payload, allHops, color, Object.assign({}, first, { observation_count: packets.length })); // Rain drop per observation in the group packets.forEach((p, i) => setTimeout(() => addRainDrop(p), i * 150)); diff --git a/server.js b/server.js index ae8e1b9..55623a5 100644 --- a/server.js +++ b/server.js @@ -796,7 +796,7 @@ for (const source of mqttSources) { }; const packetId = pktStore.insert(advertPktData); _updateHashSizeForPacket(advertPktData); try { db.insertTransmission(advertPktData); } catch (e) { console.error('[dual-write] transmission insert error:', e.message); } - broadcast({ type: 'packet', data: { id: packetId, decoded: { header: { payloadTypeName: 'ADVERT' }, payload: advert } } }); + broadcast({ type: 'packet', data: { id: packetId, hash: advertPktData.hash, raw: advertPktData.raw_hex, decoded: { header: { payloadTypeName: 'ADVERT' }, payload: advert } } }); } return; } @@ -829,8 +829,8 @@ for (const source of mqttSources) { }; const packetId = pktStore.insert(chPktData); _updateHashSizeForPacket(chPktData); try { db.insertTransmission(chPktData); } catch (e) { console.error('[dual-write] transmission insert error:', e.message); } - broadcast({ type: 'packet', data: { id: packetId, decoded: { header: { payloadTypeName: 'GRP_TXT' }, payload: channelMsg } } }); - broadcast({ type: 'message', data: { id: packetId, decoded: { header: { payloadTypeName: 'GRP_TXT' }, payload: channelMsg } } }); + broadcast({ type: 'packet', data: { id: packetId, hash: chPktData.hash, raw: chPktData.raw_hex, decoded: { header: { payloadTypeName: 'GRP_TXT' }, payload: channelMsg } } }); + broadcast({ type: 'message', data: { id: packetId, hash: chPktData.hash, decoded: { header: { payloadTypeName: 'GRP_TXT' }, payload: channelMsg } } }); return; } @@ -852,7 +852,7 @@ for (const source of mqttSources) { }; const packetId = pktStore.insert(dmPktData); _updateHashSizeForPacket(dmPktData); try { db.insertTransmission(dmPktData); } catch (e) { console.error('[dual-write] transmission insert error:', e.message); } - broadcast({ type: 'packet', data: { id: packetId, decoded: { header: { payloadTypeName: 'TXT_MSG' }, payload: dm } } }); + broadcast({ type: 'packet', data: { id: packetId, hash: dmPktData.hash, raw: dmPktData.raw_hex, decoded: { header: { payloadTypeName: 'TXT_MSG' }, payload: dm } } }); return; } @@ -874,7 +874,7 @@ for (const source of mqttSources) { }; const packetId = pktStore.insert(tracePktData); _updateHashSizeForPacket(tracePktData); try { db.insertTransmission(tracePktData); } catch (e) { console.error('[dual-write] transmission insert error:', e.message); } - broadcast({ type: 'packet', data: { id: packetId, decoded: { header: { payloadTypeName: 'TRACE' }, payload: trace } } }); + broadcast({ type: 'packet', data: { id: packetId, hash: tracePktData.hash, raw: tracePktData.raw_hex, decoded: { header: { payloadTypeName: 'TRACE' }, payload: trace } } }); return; } @@ -1112,7 +1112,7 @@ app.post('/api/packets', requireApiKey, (req, res) => { // Invalidate caches on new data cache.debouncedInvalidateAll(); - broadcast({ type: 'packet', data: { id: packetId, decoded } }); + broadcast({ type: 'packet', data: { id: packetId, hash: apiPktData.hash, raw: apiPktData.raw_hex, decoded } }); res.json({ id: packetId, decoded }); } catch (e) {