2026-05-19 13:16:47 +03:00
|
|
|
import React from 'react';
|
|
|
|
|
import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
2026-05-19 14:32:07 +03:00
|
|
|
import { isDesktopShell, isWebRuntime } from '@/lib/desktop';
|
2026-06-02 00:43:05 +03:00
|
|
|
import { getRuntimeUrlResolver } from '@/lib/runtime-url';
|
2026-05-19 13:16:47 +03:00
|
|
|
import { useUIStore } from '@/stores/useUIStore';
|
|
|
|
|
import type { NotificationPayload } from '@/lib/api/types';
|
|
|
|
|
|
|
|
|
|
const NOTIFICATION_STREAM_PATH = '/api/notifications/stream';
|
|
|
|
|
|
|
|
|
|
const isFocused = () => {
|
|
|
|
|
if (typeof document === 'undefined') return true;
|
|
|
|
|
return document.visibilityState === 'visible' && document.hasFocus();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toNotificationPayload = (value: unknown): NotificationPayload | null => {
|
|
|
|
|
if (!value || typeof value !== 'object') return null;
|
|
|
|
|
const record = value as Record<string, unknown>;
|
|
|
|
|
const properties = record.properties && typeof record.properties === 'object'
|
|
|
|
|
? record.properties as Record<string, unknown>
|
|
|
|
|
: null;
|
|
|
|
|
if (record.type !== 'openchamber:notification' || !properties) return null;
|
|
|
|
|
return {
|
|
|
|
|
title: typeof properties.title === 'string' ? properties.title : undefined,
|
|
|
|
|
body: typeof properties.body === 'string' ? properties.body : undefined,
|
|
|
|
|
tag: typeof properties.tag === 'string' ? properties.tag : undefined,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useWebNotificationStream = (options?: { enabled?: boolean }) => {
|
|
|
|
|
const enabled = options?.enabled ?? true;
|
|
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
2026-05-19 14:32:07 +03:00
|
|
|
if (!enabled || isDesktopShell() || !isWebRuntime() || typeof window === 'undefined' || typeof EventSource === 'undefined') {
|
2026-05-19 13:16:47 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 00:43:05 +03:00
|
|
|
const source = new EventSource(getRuntimeUrlResolver().sse(NOTIFICATION_STREAM_PATH));
|
2026-05-19 13:16:47 +03:00
|
|
|
source.onmessage = (event) => {
|
|
|
|
|
let data: unknown;
|
|
|
|
|
try {
|
|
|
|
|
data = JSON.parse(event.data) as unknown;
|
|
|
|
|
} catch {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const settings = useUIStore.getState();
|
|
|
|
|
if (!settings.nativeNotificationsEnabled) return;
|
|
|
|
|
if (settings.notificationMode !== 'always' && isFocused()) return;
|
|
|
|
|
|
|
|
|
|
const payload = toNotificationPayload(data);
|
|
|
|
|
if (!payload) return;
|
|
|
|
|
|
|
|
|
|
const apis = getRegisteredRuntimeAPIs();
|
|
|
|
|
void apis?.notifications?.notifyAgentCompletion(payload);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
source.close();
|
|
|
|
|
};
|
|
|
|
|
}, [enabled]);
|
|
|
|
|
};
|