feat: decrypted channel messages, scopes in ws

This commit is contained in:
MrAlders0n
2026-06-05 17:22:37 -04:00
parent 47c1813471
commit eb50f310db
4 changed files with 35 additions and 6 deletions
+5 -6
View File
@@ -301,16 +301,15 @@ function GroupTextPayload({ payload }: PayloadProps) {
<span className="text-text-normal font-semibold">{String(decrypted.sender)}</span>
</div>
)}
{decrypted.message != null && (
{decrypted.content != null && (
<div>
<span className="text-text-dim">Message </span>
<span className="text-text-bright break-all">{String(decrypted.message)}</span>
<span className="text-text-bright break-all">{String(decrypted.content)}</span>
</div>
)}
<div className="flex gap-x-4 text-text-dim">
{decrypted.timestamp != null && <span>{formatUnixTs(decrypted.timestamp as number)}</span>}
{decrypted.flags != null && <span>Flags {String(decrypted.flags)}</span>}
</div>
{decrypted.sentAt != null && (
<div className="text-text-dim">{formatTimestamp(decrypted.sentAt as number)}</div>
)}
</div>
</div>
) : (
+1
View File
@@ -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,
+1
View File
@@ -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;
@@ -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(<PayloadBreakdown payload={payload} />);
expect(screen.getByText("Alice")).toBeInTheDocument();
expect(screen.getByText("hello mesh")).toBeInTheDocument();
});
it("formats sentAt as epoch milliseconds, not seconds", () => {
render(<PayloadBreakdown payload={payload} />);
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();
});
});