diff --git a/src/features/packets/payload-renderers.tsx b/src/features/packets/payload-renderers.tsx index e5575f7..b5bcc6f 100644 --- a/src/features/packets/payload-renderers.tsx +++ b/src/features/packets/payload-renderers.tsx @@ -301,16 +301,15 @@ function GroupTextPayload({ payload }: PayloadProps) { {String(decrypted.sender)} )} - {decrypted.message != null && ( + {decrypted.content != null && (
Message - {String(decrypted.message)} + {String(decrypted.content)}
)} -
- {decrypted.timestamp != null && {formatUnixTs(decrypted.timestamp as number)}} - {decrypted.flags != null && Flags {String(decrypted.flags)}} -
+ {decrypted.sentAt != null && ( +
{formatTimestamp(decrypted.sentAt as number)}
+ )} ) : ( diff --git a/src/features/packets/usePackets.ts b/src/features/packets/usePackets.ts index 79a90f6..5fc99a9 100644 --- a/src/features/packets/usePackets.ts +++ b/src/features/packets/usePackets.ts @@ -143,6 +143,7 @@ export function usePackets() { firstHeardAt: data.observation.heardAt, lastHeardAt: data.observation.heardAt, observationCount: data.packet.observationCount, + scope: data.packet.scope, latestObserver: { id: data.observation.observerId, displayName: data.observation.observerName, diff --git a/src/types/ws.ts b/src/types/ws.ts index 9349243..2212b3a 100644 --- a/src/types/ws.ts +++ b/src/types/ws.ts @@ -41,6 +41,7 @@ export interface WsPacketObservation { routeTypeName: string; isFirstObservation: boolean; observationCount: number; + scope?: string; // matched transport scope name; omitted when none matched }; observation: { observerId: string; diff --git a/tests/features/packets/payload-renderers.test.tsx b/tests/features/packets/payload-renderers.test.tsx index 3f8d20c..f5acacb 100644 --- a/tests/features/packets/payload-renderers.test.tsx +++ b/tests/features/packets/payload-renderers.test.tsx @@ -1,6 +1,7 @@ import { describe, it, expect, vi } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import { PayloadBreakdown } from "../../../src/features/packets/payload-renderers"; +import { formatTimestamp } from "../../../src/lib/formatters"; import type { ResolvedHop } from "../../../src/types/api"; const tracePayload = { @@ -44,3 +45,30 @@ describe("PayloadBreakdown — trace resolvedRoute overlay", () => { expect(screen.getByText("-")).toBeInTheDocument(); }); }); + +describe("PayloadBreakdown — GROUP_TEXT decrypted channel message", () => { + // Backend GetPacket enrichment nests decrypted:{sender,content,sentAt} (sentAt is epoch ms). + // See tower-server db/packets.go + internal/ingest/side_effects.go. + // chosen so the ms reading (5:43 p.m.) and the wrong seconds reading (1:13 p.m.) differ + const sentAt = 1_700_001_800_000; // epoch ms + const payload = { + type: "GROUP_TEXT", + channelHash: "ab", + cipherMac: "00112233", + ciphertext: "deadbeef", + decrypted: { sender: "Alice", content: "hello mesh", sentAt }, + }; + + it("renders the decrypted sender and message body from content", () => { + render(); + expect(screen.getByText("Alice")).toBeInTheDocument(); + expect(screen.getByText("hello mesh")).toBeInTheDocument(); + }); + + it("formats sentAt as epoch milliseconds, not seconds", () => { + render(); + expect(screen.getByText(formatTimestamp(sentAt))).toBeInTheDocument(); + // the seconds interpretation (×1000) would be a far-future date — must not appear + expect(screen.queryByText(formatTimestamp(sentAt * 1000))).not.toBeInTheDocument(); + }); +});