Fix export of SSEManager for SSE connection
This commit is contained in:
parent
cee76ddd53
commit
303b4b826b
4 changed files with 163 additions and 7 deletions
|
|
@ -12,11 +12,13 @@
|
|||
{{ block "extraheader" . }}{{ end }}
|
||||
<script>
|
||||
const USER = {{ .User.AsJSON|json }};
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
SSEManager.subscribe("*", function (e) {
|
||||
if (e.type != "heartbeat") {
|
||||
updateUserState();
|
||||
}
|
||||
});
|
||||
});
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.store("user", USER);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -97,10 +97,15 @@ func fileServer(r chi.Router, path string, root http.FileSystem, embeddedFS embe
|
|||
// Cache for 1 week (604800 seconds)
|
||||
crw.Header().Set("Cache-Control", "public, max-age=604800, stale-while-revalidate=86400")
|
||||
default:
|
||||
// If it's a generated file, cache it essentially forever (1 year)
|
||||
if strings.HasPrefix(requestedPath, "gen/") {
|
||||
crw.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
} else {
|
||||
// Other files, 1 hour
|
||||
crw.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Serve the file
|
||||
http.ServeContent(crw, r, requestedPath, startedTime, fileToServe)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Alpine from './vendor/alpinejs-3.15.8.js';
|
||||
import bootstrap from '../static/vendor/bootstrap-5.3.8/bootstrap.bundle.min.js'
|
||||
import bootstrap from '../static/vendor/bootstrap-5.3.8/bootstrap.bundle.min.js';
|
||||
import { SSEManager } from './sse-manager';
|
||||
|
||||
// Make Alpine available on window for inline Alpine
|
||||
window.Alpine = Alpine;
|
||||
|
|
@ -7,9 +8,13 @@ window.Alpine = Alpine;
|
|||
// Make bootstrap available on window for various scripts
|
||||
window.bootstrap = bootstrap;
|
||||
|
||||
// Make SSEManager available to all the JavaScript
|
||||
window.SSEManager = SSEManager;
|
||||
|
||||
// Wait for DOM to be ready, then initialize Alpine
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
Alpine.start();
|
||||
SSEManager.connect("/api/events");
|
||||
});
|
||||
interface GreetingComponent {
|
||||
message: string;
|
||||
|
|
|
|||
144
ts/sse-manager.ts
Normal file
144
ts/sse-manager.ts
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// Define types for the SSE data structure
|
||||
interface SSEMessage {
|
||||
type: string;
|
||||
count?: number;
|
||||
[key: string]: any; // Allow additional properties
|
||||
}
|
||||
|
||||
type SSEHandler = (data: SSEMessage) => void;
|
||||
|
||||
interface SSEManagerType {
|
||||
connect: (url: string) => Promise<EventSource>;
|
||||
disconnect: () => void;
|
||||
subscribe: (eventType: string, handler: SSEHandler) => void;
|
||||
unsubscribe: (eventType: string, handler: SSEHandler) => void;
|
||||
ready: (callback: (eventSource: EventSource) => void) => void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
SSEManager: SSEManagerType;
|
||||
}
|
||||
}
|
||||
|
||||
export const SSEManager: SSEManagerType = (function (): SSEManagerType {
|
||||
let eventSource: EventSource | null = null;
|
||||
let subscribers: Map<string, SSEHandler[]> = new Map();
|
||||
let isConnected: boolean = false;
|
||||
let connectionPromise: Promise<EventSource> | null = null;
|
||||
|
||||
function subscribe(eventType: string, handler: SSEHandler): void {
|
||||
if (!subscribers.has(eventType)) {
|
||||
subscribers.set(eventType, []);
|
||||
}
|
||||
subscribers.get(eventType)!.push(handler);
|
||||
|
||||
// If already connected, attach the listener immediately
|
||||
if (isConnected && eventSource) {
|
||||
eventSource.addEventListener(eventType, handler as EventListener);
|
||||
}
|
||||
}
|
||||
|
||||
function unsubscribe(eventType: string, handler: SSEHandler): void {
|
||||
if (subscribers.has(eventType)) {
|
||||
const handlers = subscribers.get(eventType)!;
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index > -1) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
if (eventSource) {
|
||||
eventSource.removeEventListener(eventType, handler as EventListener);
|
||||
}
|
||||
}
|
||||
|
||||
function connect(url: string): Promise<EventSource> {
|
||||
if (connectionPromise) {
|
||||
return connectionPromise;
|
||||
}
|
||||
|
||||
connectionPromise = new Promise((resolve, reject) => {
|
||||
eventSource = new EventSource(url);
|
||||
|
||||
eventSource.onopen = function (): void {
|
||||
isConnected = true;
|
||||
|
||||
// Attach all pre-registered handlers
|
||||
subscribers.forEach((handlers: SSEHandler[], eventType: string) => {
|
||||
handlers.forEach((handler: SSEHandler) => {
|
||||
eventSource!.addEventListener("message", (message: MessageEvent) => {
|
||||
const data: SSEMessage = JSON.parse(message.data);
|
||||
if (eventType === "*" || eventType === data.type) {
|
||||
handler(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log("SSE connected");
|
||||
resolve(eventSource!);
|
||||
};
|
||||
|
||||
eventSource.onerror = function (err: Event): void {
|
||||
console.error("SSE error:", err);
|
||||
isConnected = false;
|
||||
|
||||
// Close old connection
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
}
|
||||
|
||||
// Reconnect after delay
|
||||
setTimeout(() => {
|
||||
connectionPromise = null;
|
||||
connect(url);
|
||||
}, 5000);
|
||||
|
||||
if (!isConnected) {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return connectionPromise;
|
||||
}
|
||||
|
||||
function disconnect(): void {
|
||||
if (eventSource) {
|
||||
eventSource.close();
|
||||
eventSource = null;
|
||||
isConnected = false;
|
||||
connectionPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
function ready(callback: (eventSource: EventSource) => void): void {
|
||||
if (connectionPromise) {
|
||||
connectionPromise.then(callback);
|
||||
} else {
|
||||
// If connect hasn't been called yet, queue it
|
||||
const checkInterval = setInterval(() => {
|
||||
if (connectionPromise) {
|
||||
clearInterval(checkInterval);
|
||||
connectionPromise.then(callback);
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
connect,
|
||||
disconnect,
|
||||
subscribe,
|
||||
unsubscribe,
|
||||
ready,
|
||||
};
|
||||
})();
|
||||
|
||||
function updateNotificationBadge(data: SSEMessage): void {
|
||||
const badge = document.querySelector<HTMLElement>(".notification-badge");
|
||||
if (badge) {
|
||||
badge.textContent = String(data.count || 0);
|
||||
badge.style.display = (data.count || 0) > 0 ? "block" : "none";
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue