Fixing wireformat for SSE

This commit is contained in:
Daniel Pupius
2025-04-22 13:37:07 -07:00
parent 6844d575c4
commit 71e914b05d
5 changed files with 80 additions and 54 deletions

View File

@@ -178,7 +178,7 @@ func (s *Server) handleStream(w http.ResponseWriter, r *http.Request) {
// Create a marshaler with pretty printing enabled
marshaler := protojson.MarshalOptions{
EmitUnpopulated: true,
Indent: " ",
Multiline: false,
UseProtoNames: false, // Use camelCase names
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
interface InfoMessageProps {
message: string;
type?: 'info' | 'warning' | 'error';
}
export const InfoMessage: React.FC<InfoMessageProps> = ({
message,
type = 'info'
}) => {
const getBgColor = () => {
switch (type) {
case 'warning':
return 'bg-yellow-50 border-yellow-200';
case 'error':
return 'bg-red-50 border-red-200';
case 'info':
default:
return 'bg-blue-50 border-blue-200';
}
};
const getTextColor = () => {
switch (type) {
case 'warning':
return 'text-yellow-800';
case 'error':
return 'text-red-800';
case 'info':
default:
return 'text-blue-800';
}
};
return (
<div className={`p-3 mb-4 rounded border ${getBgColor()}`}>
<p className={`text-sm ${getTextColor()}`}>{message}</p>
</div>
);
};

View File

@@ -2,3 +2,4 @@ export * from './PacketList';
export * from './MessageDisplay';
export * from './PacketDetails';
export * from './Filter';
export * from './InfoMessage';

View File

@@ -1,7 +1,7 @@
/**
* API client functions for interacting with the Meshstream server
*/
import { API_ENDPOINTS } from './config';
import { API_ENDPOINTS } from "./config";
export interface ApiResponse<T> {
data?: T;
@@ -28,16 +28,21 @@ export async function fetchRecentPackets(): Promise<ApiResponse<any[]>> {
* Type definitions for SSE events
*/
export interface InfoEvent {
type: 'info';
type: "info";
data: string;
}
export interface MessageEvent {
type: 'message';
type: "message";
data: any; // Will be the parsed JSON message
}
export type StreamEvent = InfoEvent | MessageEvent;
export interface BadDataEvent {
type: "bad_data";
data: string; // Raw data that failed to parse
}
export type StreamEvent = InfoEvent | MessageEvent | BadDataEvent;
export type StreamEventHandler = (event: StreamEvent) => void;
@@ -45,40 +50,35 @@ export type StreamEventHandler = (event: StreamEvent) => void;
* Establish a Server-Sent Events connection to receive real-time packets
*/
export function streamPackets(
onEvent: StreamEventHandler,
onEvent: StreamEventHandler,
onError?: (error: Event) => void
): () => void {
const evtSource = new EventSource(API_ENDPOINTS.STREAM);
// Handle general messages (fallback)
evtSource.onmessage = (event) => {
handleEventData('message', event.data, onEvent);
};
// Handle info events specifically
evtSource.addEventListener('info', (event) => {
evtSource.addEventListener("info", (event) => {
// Info events are just strings
onEvent({
type: 'info',
data: event.data
type: "info",
data: event.data,
});
});
// Handle message events specifically
evtSource.addEventListener('message', (event) => {
handleEventData('message', event.data, onEvent);
evtSource.addEventListener("message", (event) => {
handleEventData(event.data, onEvent);
});
// Handle errors
if (onError) {
evtSource.onerror = onError;
} else {
evtSource.onerror = () => {
console.error('EventSource failed');
console.error("EventSource failed");
evtSource.close();
};
}
// Return cleanup function
return () => evtSource.close();
}
@@ -86,36 +86,19 @@ export function streamPackets(
/**
* Helper to handle event data based on type
*/
function handleEventData(
type: 'info' | 'message',
data: string,
callback: StreamEventHandler
): void {
function handleEventData(data: string, callback: StreamEventHandler): void {
try {
if (type === 'info') {
// Info events are plain text
callback({
type: 'info',
data
});
} else {
// Message events are JSON
try {
const parsedData = JSON.parse(data);
callback({
type: 'message',
data: parsedData
});
} catch (error) {
// If JSON parsing fails, treat it as a plain text message
console.warn('Failed to parse message as JSON:', error);
callback({
type: 'info',
data
});
}
}
const parsedData = JSON.parse(data);
callback({
type: "message",
data: parsedData,
});
} catch (error) {
console.error('Error handling event data:', error);
console.warn("Failed to parse message as JSON:", error, "Raw data:", data);
callback({
type: "bad_data",
data,
});
}
}

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useAppDispatch } from '../hooks';
import { PacketList } from '../components/PacketList';
import { InfoMessage } from '../components/InfoMessage';
import { addPacket } from '../store/slices/packetSlice';
import { streamPackets, StreamEvent } from '../lib/api';
@@ -37,10 +38,10 @@ export function PacketsRoute() {
<h2 className="text-2xl font-bold mb-4">Mesh Network Packets</h2>
{/* Connection status indicator */}
<div className="mb-4 p-2 bg-blue-50 border border-blue-200 rounded">
<span className="font-medium">Status: </span>
<span>{connectionStatus}</span>
</div>
<InfoMessage
message={`Status: ${connectionStatus}`}
type={connectionStatus.includes('error') ? 'error' : 'info'}
/>
<p className="mb-4">
This page displays real-time packets from the Meshtastic mesh network.