Skip to content

Commit 1f457d1

Browse files
committed
Fix #288 Vite hot module reloading creating multiple SSE connections
- move SSE (EventSource) connection to module level - manage EventSource as a singleton, closing open connection before reopening a new one
1 parent f58c8c8 commit 1f457d1

File tree

1 file changed

+13
-11
lines changed

1 file changed

+13
-11
lines changed

ui/src/contexts/APIProvider.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useRef, createContext, useState, useContext, useEffect, useCallback, useMemo, type ReactNode } from "react";
1+
import { createContext, useState, useContext, useEffect, useCallback, useMemo, type ReactNode } from "react";
22
import type { ConnectionState } from "../lib/types";
33

44
type ModelStatus = "ready" | "starting" | "stopping" | "stopped" | "shutdown" | "unknown";
@@ -51,12 +51,14 @@ type APIProviderProps = {
5151
autoStartAPIEvents?: boolean;
5252
};
5353

54+
let apiEventSource: EventSource | null = null;
55+
5456
export function APIProvider({ children, autoStartAPIEvents = true }: APIProviderProps) {
5557
const [proxyLogs, setProxyLogs] = useState("");
5658
const [upstreamLogs, setUpstreamLogs] = useState("");
5759
const [metrics, setMetrics] = useState<Metrics[]>([]);
5860
const [connectionStatus, setConnectionState] = useState<ConnectionState>("disconnected");
59-
const apiEventSource = useRef<EventSource | null>(null);
61+
//const apiEventSource = useRef<EventSource | null>(null);
6062

6163
const [models, setModels] = useState<Model[]>([]);
6264

@@ -69,8 +71,8 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
6971

7072
const enableAPIEvents = useCallback((enabled: boolean) => {
7173
if (!enabled) {
72-
apiEventSource.current?.close();
73-
apiEventSource.current = null;
74+
apiEventSource?.close();
75+
apiEventSource = null;
7476
setMetrics([]);
7577
return;
7678
}
@@ -79,22 +81,22 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
7981
const initialDelay = 1000; // 1 second
8082

8183
const connect = () => {
82-
apiEventSource.current = null;
83-
const eventSource = new EventSource("/api/events");
84+
apiEventSource?.close();
85+
apiEventSource = new EventSource("/api/events");
86+
8487
setConnectionState("connecting");
8588

86-
eventSource.onopen = () => {
89+
apiEventSource.onopen = () => {
8790
// clear everything out on connect to keep things in sync
8891
setProxyLogs("");
8992
setUpstreamLogs("");
9093
setMetrics([]); // clear metrics on reconnect
9194
setModels([]); // clear models on reconnect
92-
apiEventSource.current = eventSource;
9395
retryCount = 0;
9496
setConnectionState("connected");
9597
};
9698

97-
eventSource.onmessage = (e: MessageEvent) => {
99+
apiEventSource.onmessage = (e: MessageEvent) => {
98100
try {
99101
const message = JSON.parse(e.data) as APIEventEnvelope;
100102
switch (message.type) {
@@ -137,8 +139,8 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
137139
}
138140
};
139141

140-
eventSource.onerror = () => {
141-
eventSource.close();
142+
apiEventSource.onerror = () => {
143+
apiEventSource?.close();
142144
retryCount++;
143145
const delay = Math.min(initialDelay * Math.pow(2, retryCount - 1), 5000);
144146
setConnectionState("disconnected");

0 commit comments

Comments
 (0)