mirror of
https://github.com/dpup/meshstream.git
synced 2026-03-28 17:42:37 +01:00
Fixing wireformat for SSE
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
41
web/src/components/InfoMessage.tsx
Normal file
41
web/src/components/InfoMessage.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -2,3 +2,4 @@ export * from './PacketList';
|
||||
export * from './MessageDisplay';
|
||||
export * from './PacketDetails';
|
||||
export * from './Filter';
|
||||
export * from './InfoMessage';
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user