Files
paseo/packages/app/src/plugins/surface-runtime.ts
T
Mohamed Boudra 6f2195904b fix(subscriptions): isolate observers and silence unrequested feeds (#4596)
Give each observation a server-issued ID owned by its physical connection. Preserve snapshot-only reads and combined list-and-subscribe, source-bound replies, lifecycle cleanup and legacy wire compatibility.
2026-09-11 08:07:35 +02:00

45 lines
1.6 KiB
TypeScript

import { useEffect, useState } from "react";
import type { InstalledPlugin } from "./types";
import { createPaseoApi, type PaseoApi } from "@getpaseo/client";
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
export interface PluginSurfaceRuntime {
paseo: PaseoApi;
invoke(method: string, input: unknown): Promise<unknown>;
}
export function createPluginSurfaceRuntime(
client: DaemonClient | null,
plugin: Pick<InstalledPlugin, "id" | "lifetime">,
): PluginSurfaceRuntime | null {
if (!client || plugin.lifetime.signal.aborted) return null;
return {
paseo: createPaseoApi(client, { signal: plugin.lifetime.signal }),
invoke: (method, input) => client.invokePluginRpc(plugin.id, method, input),
};
}
/** A mounted surface owns its API; creating a React element creates no server demand. */
export function usePluginSurfaceRuntime(
client: DaemonClient | null,
plugin: InstalledPlugin | null | undefined,
): PluginSurfaceRuntime | null {
const [mounted, setMounted] = useState<{
client: DaemonClient;
plugin: InstalledPlugin;
runtime: PluginSurfaceRuntime;
} | null>(null);
useEffect(() => {
if (!client || !plugin) return;
const runtime = createPluginSurfaceRuntime(client, plugin);
if (!runtime) return;
setMounted({ client, plugin, runtime });
return () => {
void runtime.paseo
.dispose()
.catch((error) => console.warn(`[Plugins] Surface cleanup failed for ${plugin.id}`, error));
};
}, [client, plugin]);
return mounted?.client === client && mounted.plugin === plugin ? mounted.runtime : null;
}