openclaw/apps/web/lib/app-registry.ts
kumarabhirup ea8bab6179
feat(apps): expand platform API with objects, chat, store, cron, and webhooks
Enables apps to access the full DenchClaw platform — CRUD on workspace objects, AI chat with streaming, inter-app messaging, KV store, HTTP proxy, webhooks, cron scheduling, and widget display mode.
2026-03-17 14:42:01 -07:00

119 lines
2.9 KiB
TypeScript

/**
* Global registry for active Dench App instances.
* Enables inter-app messaging, tool discovery, and app listing.
*/
export type ToolDef = {
name: string;
description: string;
inputSchema?: unknown;
};
export type AppInstance = {
appName: string;
iframe: HTMLIFrameElement;
tools: Map<string, ToolDef>;
};
type AppMessageHandler = (event: {
from: string;
message: unknown;
}) => void;
const g = (typeof globalThis !== "undefined" ? globalThis : window) as unknown as {
__denchAppRegistry?: Map<string, Set<AppInstance>>;
__denchAppMessageHandlers?: Set<AppMessageHandler>;
};
if (!g.__denchAppRegistry) g.__denchAppRegistry = new Map();
if (!g.__denchAppMessageHandlers) g.__denchAppMessageHandlers = new Set();
const registry = g.__denchAppRegistry;
const messageHandlers = g.__denchAppMessageHandlers;
export function registerApp(instance: AppInstance): void {
let instances = registry.get(instance.appName);
if (!instances) {
instances = new Set();
registry.set(instance.appName, instances);
}
instances.add(instance);
}
export function unregisterApp(instance: AppInstance): void {
const instances = registry.get(instance.appName);
if (instances) {
instances.delete(instance);
if (instances.size === 0) registry.delete(instance.appName);
}
}
export function sendToApp(
targetApp: string,
message: unknown,
sourceApp: string,
): boolean {
const instances = registry.get(targetApp);
if (!instances || instances.size === 0) return false;
for (const inst of instances) {
inst.iframe.contentWindow?.postMessage(
{
type: "dench:event",
channel: "apps.message",
data: { from: sourceApp, message },
},
"*",
);
}
return true;
}
export function listActiveApps(): Array<{
name: string;
instanceCount: number;
}> {
const result: Array<{ name: string; instanceCount: number }> = [];
for (const [name, instances] of registry) {
result.push({ name, instanceCount: instances.size });
}
return result;
}
export function getAppTools(
appName: string,
): ToolDef[] {
const instances = registry.get(appName);
if (!instances) return [];
const tools: ToolDef[] = [];
for (const inst of instances) {
for (const tool of inst.tools.values()) {
tools.push(tool);
}
}
return tools;
}
export function getAllTools(): Array<ToolDef & { appName: string }> {
const result: Array<ToolDef & { appName: string }> = [];
for (const [appName, instances] of registry) {
for (const inst of instances) {
for (const tool of inst.tools.values()) {
result.push({ ...tool, appName });
}
}
}
return result;
}
export function onAppMessage(handler: AppMessageHandler): () => void {
messageHandlers.add(handler);
return () => messageHandlers.delete(handler);
}
export function emitAppMessage(from: string, message: unknown): void {
for (const handler of messageHandlers) {
handler({ from, message });
}
}