ui/api: init message bus subscriber hooks

This commit is contained in:
Johannes Kirschbauer
2025-08-11 13:48:34 +02:00
parent bb6fab1168
commit c70c588c1c
3 changed files with 90 additions and 12 deletions

View File

@@ -1,4 +1,4 @@
import { ProcessMessage } from "./src/hooks/api"; import { ProcessMessage } from "./src/hooks/notify";
export {}; export {};

View File

@@ -99,14 +99,3 @@ export const callApi = <K extends OperationNames>(
}, },
}; };
}; };
export interface ProcessMessage {
topic: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any;
origin: string | null;
}
window.notifyBus = (data) => {
console.debug("Channel function called with data:", data);
};

View File

@@ -0,0 +1,89 @@
import { createSignal, onCleanup } from "solid-js";
import { OperationNames } from "./api";
export interface ProcessMessage<
TData = unknown,
TTopic extends string = string,
> {
topic: TTopic;
data: TData;
origin: string | null;
}
interface Subscriber<T extends ProcessMessage> {
filter: (msg: T) => boolean;
callback: (msg: T) => void;
}
const subscribers: Subscriber<ProcessMessage>[] = [];
// Declare the global function on the window type
declare global {
interface Window {
notifyBus: (msg: ProcessMessage) => void;
}
}
window.notifyBus = (msg: ProcessMessage) => {
console.debug("notifyBus called with:", msg);
for (const sub of subscribers) {
try {
if (sub.filter(msg)) {
sub.callback(msg);
}
} catch (e) {
console.error("Subscriber threw an error:", e);
}
}
};
/**
* Subscribe to any message
*
* Returns a function to unsubsribe itself
*
* consider using useNotify for reactive usage on solidjs
*/
export function _subscribeNotify<T extends ProcessMessage>(
filter: (msg: T) => boolean,
callback: (msg: T) => void,
) {
// Cast to shared subscriber type for storage
const sub: Subscriber<ProcessMessage> = {
filter: filter as (msg: ProcessMessage) => boolean,
callback: callback as (msg: ProcessMessage) => void,
};
subscribers.push(sub);
return () => {
const idx = subscribers.indexOf(sub);
if (idx >= 0) subscribers.splice(idx, 1);
};
}
/**
* Returns a reactive signal that tracks a message by the given filter predicate
* The signal has the value of the last message where filter was true
* null in case no message was recieved yet
*/
export function useNotify<T extends ProcessMessage = ProcessMessage>(
filter: (msg: T) => boolean = () => true as boolean,
) {
const [message, setMessage] = createSignal<T | null>(null);
const unsubscribe = _subscribeNotify(filter, (msg) => setMessage(() => msg));
onCleanup(unsubscribe);
return message;
}
/**
* Tracks any message that was sent from this api origin
*
*/
export function useNotifyOrigin<
T extends ProcessMessage = ProcessMessage,
K extends OperationNames = OperationNames,
>(origin: K) {
return useNotify<T>((m) => m.origin === origin);
}