Files
kestrelos/server/utils/cotSubscribers.js
T
Madison Grubb aa8a0bd83f
PR / lint (pull_request) Failing after 31s
PR / test (pull_request) Successful in 45s
PR / docker-build (pull_request) Successful in 1m3s
PR / e2e (pull_request) Successful in 1m33s
Add ADS-B, AIS, and ALPR map layers with live CoT streaming.
Ingest aircraft and vessel tracks via OSINT feeds and tactical CoT, expose viewport-filtered SSE to the map, and add an OSM ALPR layer with tiled caching and performant marker sync.
2026-06-24 16:24:41 -04:00

71 lines
2.2 KiB
JavaScript

/** SSE subscriber registry; bbox union drives OSINT feeds. */
import { getActiveEntitiesInBbox } from './cotStore.js'
import { isInBbox, matchesLayerFilter, unionBboxes } from './cotEntityUtils.js'
/** @typedef {{ west: number, south: number, east: number, north: number }} Bbox */
/** @typedef {(event: string, data: string) => Promise<void> | void} PushFn */
/** @type {Map<string, { bbox: Bbox | null, layers: Set<string>, push: PushFn }>} */
const subscribers = new Map()
let nextId = 1
/**
* @param {{ bbox: Bbox | null, layers: Set<string>, push: PushFn }} sub
* @returns {() => void} Unregister function.
*/
export function registerSubscriber(sub) {
const id = String(nextId++)
subscribers.set(id, sub)
return () => subscribers.delete(id)
}
/** @returns {Bbox | null} Union of all subscriber bboxes. */
export function getSubscriberBboxUnion() {
return unionBboxes([...subscribers.values()].map(s => s.bbox))
}
export function getSubscriberCount() {
return subscribers.size
}
export function clearSubscribers() {
subscribers.clear()
}
export async function notifySubscribersForEntity(event, payload, entity) {
const data = JSON.stringify(payload)
const tasks = []
for (const sub of subscribers.values()) {
if (sub.bbox && !isInBbox(entity, sub.bbox)) continue
if (!matchesLayerFilter(sub.layers, entity)) continue
tasks.push(Promise.resolve(sub.push(event, data)))
}
await Promise.all(tasks)
}
export async function notifySubscribersRemove(id) {
const data = JSON.stringify({ id })
await Promise.all(
[...subscribers.values()].map(sub => Promise.resolve(sub.push('remove', data))),
)
}
/**
* Push a filtered snapshot to each active SSE subscriber.
* @param {{ ttlMs?: number, osintTtlMs?: number, takFilterBbox?: boolean, maxEntities?: number }} snapshotOpts
*/
export async function broadcastSubscriberSnapshots(snapshotOpts) {
const tasks = []
for (const sub of subscribers.values()) {
tasks.push((async () => {
const entities = await getActiveEntitiesInBbox(sub.bbox, {
...snapshotOpts,
layers: sub.layers,
})
await sub.push('snapshot', JSON.stringify({ entities }))
})())
}
await Promise.all(tasks)
}