import { Observable } from "@noya-app/observable"; import { ClientToServerMessage } from "./multiplayer"; type ActivityEventStream = { activityEvents: Observable; isInitialized: Observable; }; export type StreamFilter = "all" | { resourceId: string }; export class ActivityEventsManager { activityEventStreams: Record = {}; constructor( private sendMessage?: (message: ClientToServerMessage) => void ) {} subscribe(filter: StreamFilter) { const streamId = filter === "all" ? "all" : `resource-${filter.resourceId}`; if (!this.activityEventStreams[streamId]) { this.activityEventStreams[streamId] = { activityEvents: new Observable([]), isInitialized: new Observable(false), }; const cursor = undefined as string | undefined; this.sendMessage?.({ type: "activityEvents.subscribe", streamId, ...(cursor && { cursor }), }); } return streamId; } getStream(streamId: string): Observable { return this.activityEventStreams[streamId]?.activityEvents; } unsubscribe(streamId: string) { this.sendMessage?.({ type: "activityEvents.unsubscribe", streamId, }); delete this.activityEventStreams[streamId]; } setStreamEvents(streamId: string, activityEvents: unknown[]) { if (!this.activityEventStreams[streamId]) { this.activityEventStreams[streamId] = { activityEvents: new Observable([]), isInitialized: new Observable(false), }; } const current = this.activityEventStreams[streamId].activityEvents.get(); // Merge by id if possible, otherwise just concatenate new events first const merged = mergeActivityEvents(current, activityEvents); this.activityEventStreams[streamId].activityEvents.set(merged); this.activityEventStreams[streamId].isInitialized.set(true); } } function mergeActivityEvents(existing: unknown[], incoming: unknown[]) { const byId = new Map(); const getId = (e: any) => (e && typeof e === "object" ? e.id : undefined); for (const ev of [...(incoming || []), ...(existing || [])]) { const id = getId(ev); if (typeof id === "string") byId.set(id, ev); } return Array.from(byId.values()); }