@rainbow-robotics/plugin-ui 0.1.2-dev.6 → 0.1.2-dev.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-browser.mjs +1 -39
- package/dist/connect.d.ts.map +1 -1
- package/dist/connect.js +7 -7
- package/dist/connect.js.map +2 -2
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/ws.d.ts +20 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist-browser/@rainbow-robotics/plugin-ui/chunks/chunk-37HVFFJB.js +1 -0
- package/dist-browser/@rainbow-robotics/plugin-ui/chunks/{chunk-3DOWEHFU.js → chunk-4MGSRUAS.js} +1 -1
- package/dist-browser/@rainbow-robotics/plugin-ui/connect.js +1 -1
- package/dist-browser/@rainbow-robotics/plugin-ui/elements.js +1 -1
- package/dist-browser/@rainbow-robotics/plugin-ui/react.js +1 -1
- package/dist-browser/@rainbow-robotics/plugin-ui/sdk.js +1 -1
- package/package.json +4 -9
- package/src/connect.ts +11 -13
- package/src/host.tsx +29 -16
- package/src/sdk.ts +1 -1
- package/src/ws.ts +120 -0
- package/dist/zenoh.d.ts +0 -29
- package/dist/zenoh.d.ts.map +0 -1
- package/dist-browser/@rainbow-robotics/plugin-ui/chunks/chunk-HJVATDC6.js +0 -1
- package/src/zenoh.ts +0 -122
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as a,c as b,d as c,e as d,f as e,g as f,h as g,i as h,j as i}from"./chunks/chunk-
|
|
1
|
+
import{b as a,c as b,d as c,e as d,f as e,g as f,h as g,i as h,j as i}from"./chunks/chunk-4MGSRUAS.js";import"./chunks/chunk-37HVFFJB.js";export{h as RB_ELEMENT_NAMES,b as RbButtonElement,c as RbIconButtonElement,g as RbSelectElement,a as RbSliderElement,f as RbSwitchElement,d as RbTextElement,e as RbTextInputElement,i as defineElements};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as A}from"./chunks/chunk-Q6XGCIPE.js";import{b as d,c as x,d as h,e as S,f as C,g as y,h as g}from"./chunks/chunk-
|
|
1
|
+
import{b as A}from"./chunks/chunk-Q6XGCIPE.js";import{b as d,c as x,d as h,e as S,f as C,g as y,h as g}from"./chunks/chunk-4MGSRUAS.js";import"./chunks/chunk-37HVFFJB.js";import{forwardRef as L,useRef as T,isValidElement as k,cloneElement as M,createElement as I,useLayoutEffect as O}from"react";function o(n,w=customElements.get(n),{slotProps:m=!0,eventProps:B={}}={}){let N=!!m,c=(typeof m=="object"?m.wrapper:void 0)??!0,E=typeof c=="string"?c:c?"remote-fragment":!1,u=L(function(l,i){let a=T(),v=T(),r={},R=V(l.children);for(let e in l){let t=l[e];if(e==="slot"){r.slot=t;continue}if(N&&w.remoteSlotDefinitions.has(e)&&k(t)){E?R.push(I(E,{slot:e},t)):R.push(M(t,{slot:e}));continue}r[e]=t}return O(()=>{let e=a.current;if(e!=null){for(let t in r){if(t==="children")continue;let b=v.current?.[t],s=r[t];if(b===s)continue;let P=B[t];if(P){let f=P.event;b&&e.removeEventListener(f,b),s&&e.addEventListener(f,s);continue}if(t in e){e[t]=r[t];continue}s==null?e.removeAttribute(t):e.setAttribute(t,String(s))}v.current=r}}),I(n,{ref:e=>{a.current=e,typeof i=="function"?i(e):i!=null&&(i.current=e)}},...R)});return u.displayName=`RemoteComponent(${n})`,u}function V(n){return n==null?[]:Array.isArray(n)?[...n]:[n]}var p=n=>({eventProps:n}),G=o("rb-slider",d,p({onChange:{event:"change"}})),H=o("rb-button",x,p({onPress:{event:"press"}})),J=o("rb-icon-button",h,p({onPress:{event:"press"}})),K=o("rb-text",S),Q=o("rb-text-input",C,p({onInput:{event:"input"}})),U=o("rb-switch",y,p({onChange:{event:"change"}})),X=o("rb-select",g,p({onChange:{event:"change"}}));export{H as Button,J as IconButton,X as Select,G as Slider,U as Switch,K as Text,Q as TextInput,A as useRb};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a,b}from"./chunks/chunk-Q6XGCIPE.js";import"./chunks/chunk-
|
|
1
|
+
import{a,b}from"./chunks/chunk-Q6XGCIPE.js";import"./chunks/chunk-37HVFFJB.js";export{a as RbContext,b as useRb};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rainbow-robotics/plugin-ui",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20"
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@repo/eslint-config": "*",
|
|
51
|
+
"@repo/rb-components": "*",
|
|
51
52
|
"@repo/typescript-config": "*",
|
|
52
53
|
"@types/node": "^22.13.9",
|
|
53
54
|
"@types/react": "^19.0.10",
|
|
@@ -56,19 +57,13 @@
|
|
|
56
57
|
"typescript": "^5.8.0"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
|
-
"@eclipse-zenoh/zenoh-ts": "^1.9.0",
|
|
60
60
|
"@quilted/threads": "^4.0.3",
|
|
61
61
|
"@remote-dom/core": "^1.11.1",
|
|
62
|
-
"@remote-dom/react": "^1.2.2"
|
|
62
|
+
"@remote-dom/react": "^1.2.2",
|
|
63
|
+
"socket.io-client": "^4.8.0"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
|
-
"@repo/rb-components": "*",
|
|
66
66
|
"react": ">=18",
|
|
67
67
|
"react-dom": ">=18"
|
|
68
|
-
},
|
|
69
|
-
"peerDependenciesMeta": {
|
|
70
|
-
"@repo/rb-components": {
|
|
71
|
-
"optional": true
|
|
72
|
-
}
|
|
73
68
|
}
|
|
74
69
|
}
|
package/src/connect.ts
CHANGED
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
* Sandbox bootstrap — runs *inside* the plugin iframe.
|
|
3
3
|
*
|
|
4
4
|
* The sandbox initiates the handshake: imports the host's `connect()` and calls
|
|
5
|
-
* it. The host responds with the RemoteConnection, toast(), and
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
8
|
-
* Verified against @remote-dom/core@1.11.x + @quilted/threads@4.x.
|
|
5
|
+
* it. The host responds with the RemoteConnection, toast(), and Socket.IO config
|
|
6
|
+
* (socketUrl + pluginId). We open a Socket.IO connection and wire up the rb object.
|
|
9
7
|
*/
|
|
10
8
|
import { RemoteMutationObserver } from "@remote-dom/core/elements";
|
|
11
9
|
import type { RemoteConnection } from "@remote-dom/core";
|
|
@@ -14,13 +12,13 @@ import { createElement, type ComponentType } from "react";
|
|
|
14
12
|
import { createRoot } from "react-dom/client";
|
|
15
13
|
import { defineElements } from "./elements";
|
|
16
14
|
import { RbContext, type RbSdk } from "./sdk";
|
|
17
|
-
import {
|
|
15
|
+
import { RbWs } from "./ws";
|
|
18
16
|
|
|
19
17
|
interface HostExports {
|
|
20
18
|
connect: () => Promise<{
|
|
21
19
|
connection: RemoteConnection;
|
|
22
20
|
toast: (message: string, type?: string) => void;
|
|
23
|
-
|
|
21
|
+
socketUrl: string;
|
|
24
22
|
pluginId: string;
|
|
25
23
|
}>;
|
|
26
24
|
}
|
|
@@ -40,15 +38,15 @@ export function connect(App: ComponentType): void {
|
|
|
40
38
|
targetOrigin: "*",
|
|
41
39
|
});
|
|
42
40
|
|
|
43
|
-
void host.connect().then(async ({ connection, toast,
|
|
44
|
-
const
|
|
41
|
+
void host.connect().then(async ({ connection, toast, socketUrl, pluginId }) => {
|
|
42
|
+
const ws = await RbWs.connect(socketUrl, pluginId);
|
|
45
43
|
|
|
46
44
|
const rb: RbSdk = {
|
|
47
|
-
pub: (topic, payload)
|
|
48
|
-
sub: (topic, handler)
|
|
49
|
-
action: (topic, payload)
|
|
50
|
-
serve: (topic, handler)
|
|
51
|
-
toast:
|
|
45
|
+
pub: (topic, payload) => ws.pub(topic, payload),
|
|
46
|
+
sub: (topic, handler) => ws.sub(topic, handler),
|
|
47
|
+
action: (topic, payload) => ws.action(topic, payload),
|
|
48
|
+
serve: (topic, handler) => ws.serve(topic, handler),
|
|
49
|
+
toast: (message, type) => toast(message, type),
|
|
52
50
|
};
|
|
53
51
|
|
|
54
52
|
const root = document.createElement("remote-root");
|
package/src/host.tsx
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
RBIcons,
|
|
40
40
|
} from "@repo/rb-components/new_components";
|
|
41
41
|
import type { RbSelectOption } from "./elements";
|
|
42
|
-
import type {
|
|
42
|
+
import type { RbNotifyType } from "./sdk";
|
|
43
43
|
|
|
44
44
|
type IconComponent = ComponentType<{ title?: string }>;
|
|
45
45
|
|
|
@@ -266,36 +266,49 @@ export const RB_COMPONENT_MAP: RemoteComponentRendererMap = new Map([
|
|
|
266
266
|
/* Host SDK contract + mount component */
|
|
267
267
|
/* -------------------------------------------------------------------------- */
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
/**
|
|
270
|
+
* Shape the host exposes to the sandbox over the thread (see ./connect).
|
|
271
|
+
* The sandbox opens its own Socket.IO connection from `socketUrl` + `pluginId`
|
|
272
|
+
* and implements pub/sub/action/serve directly; the host only forwards the
|
|
273
|
+
* remote-dom connection, the Socket.IO config, and toast().
|
|
274
|
+
*/
|
|
272
275
|
interface HostThreadExports {
|
|
273
276
|
connect: () => {
|
|
274
277
|
connection: {
|
|
275
278
|
mutate: (records: unknown) => void;
|
|
276
279
|
call: (id: string, method: string, ...args: unknown[]) => unknown;
|
|
277
280
|
};
|
|
278
|
-
|
|
279
|
-
|
|
281
|
+
toast: (message: string, type?: RbNotifyType) => void;
|
|
282
|
+
socketUrl: string;
|
|
283
|
+
pluginId: string;
|
|
280
284
|
};
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
export interface PluginContentProps {
|
|
284
288
|
/** URL of the plugin sandbox document (e.g. /plugin/:id/sandbox.html). */
|
|
285
289
|
sandboxUrl: string;
|
|
286
|
-
/**
|
|
287
|
-
|
|
290
|
+
/** Zenoh router URL the sandbox connects to. */
|
|
291
|
+
socketUrl: string;
|
|
292
|
+
/** Plugin id — used for Zenoh topic namespacing inside the sandbox. */
|
|
293
|
+
pluginId: string;
|
|
294
|
+
/** Host toast notification handed to the plugin. */
|
|
295
|
+
toast: (message: string, type?: RbNotifyType) => void;
|
|
288
296
|
}
|
|
289
297
|
|
|
290
298
|
/**
|
|
291
299
|
* Renders a sandboxed plugin: spins up a hidden iframe running the plugin's
|
|
292
300
|
* remote tree, and mirrors that tree with real rb-components in the host.
|
|
293
301
|
*/
|
|
294
|
-
export function PluginContent({
|
|
302
|
+
export function PluginContent({
|
|
303
|
+
sandboxUrl,
|
|
304
|
+
socketUrl,
|
|
305
|
+
pluginId,
|
|
306
|
+
toast,
|
|
307
|
+
}: PluginContentProps) {
|
|
295
308
|
const receiver = useMemo(() => new RemoteReceiver(), []);
|
|
296
309
|
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
297
|
-
const
|
|
298
|
-
|
|
310
|
+
const toastRef = useRef(toast);
|
|
311
|
+
toastRef.current = toast;
|
|
299
312
|
|
|
300
313
|
useEffect(() => {
|
|
301
314
|
const iframe = iframeRef.current;
|
|
@@ -316,17 +329,17 @@ export function PluginContent({ sandboxUrl, rb }: PluginContentProps) {
|
|
|
316
329
|
call: (id: string, method: string, ...args: unknown[]) =>
|
|
317
330
|
receiver.connection.call(id, method, ...args),
|
|
318
331
|
},
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
332
|
+
toast: (message: string, type?: RbNotifyType) =>
|
|
333
|
+
toastRef.current(message, type),
|
|
334
|
+
socketUrl,
|
|
335
|
+
pluginId,
|
|
323
336
|
};
|
|
324
337
|
},
|
|
325
338
|
},
|
|
326
339
|
});
|
|
327
340
|
|
|
328
341
|
return () => controller.abort();
|
|
329
|
-
}, [receiver]);
|
|
342
|
+
}, [receiver, socketUrl, pluginId]);
|
|
330
343
|
|
|
331
344
|
return (
|
|
332
345
|
<>
|
package/src/sdk.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* notify() is still host-mediated (goes through the Muscat shell).
|
|
9
9
|
*/
|
|
10
10
|
import { createContext, useContext } from "react";
|
|
11
|
-
import type { Subscriber, Queryable } from "./
|
|
11
|
+
import type { WsSubscriber as Subscriber, WsQueryable as Queryable } from "./ws";
|
|
12
12
|
|
|
13
13
|
export type RbNotifyType = "info" | "success" | "warning" | "error";
|
|
14
14
|
|
package/src/ws.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Socket.IO communication client for plugin frontends.
|
|
3
|
+
*
|
|
4
|
+
* Topics are automatically namespaced to prevent cross-plugin collisions:
|
|
5
|
+
* "sensor/data" → "plugin/{pluginId}/sensor/data"
|
|
6
|
+
* "/broadcast/x" → "broadcast/x" (leading slash = absolute)
|
|
7
|
+
*
|
|
8
|
+
* Browser ←→ Python backend protocol:
|
|
9
|
+
* browser emits : sub, unsub, pub, action (with ack), serve_reply
|
|
10
|
+
* server emits : msg, serve_call
|
|
11
|
+
*/
|
|
12
|
+
import { io, type Socket } from "socket.io-client";
|
|
13
|
+
|
|
14
|
+
export interface WsHandle {
|
|
15
|
+
undeclare(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type WsSubscriber = WsHandle;
|
|
19
|
+
export type WsQueryable = WsHandle;
|
|
20
|
+
|
|
21
|
+
function resolveTopic(pluginId: string, topic: string): string {
|
|
22
|
+
return topic.startsWith("/") ? topic.slice(1) : `plugin/${pluginId}/${topic}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class RbWs {
|
|
26
|
+
private readonly _socket: Socket;
|
|
27
|
+
private readonly _pluginId: string;
|
|
28
|
+
private readonly _subHandlers = new Map<string, Set<(payload: unknown) => void>>();
|
|
29
|
+
private readonly _serveHandlers = new Map<string, (payload?: unknown) => unknown | Promise<unknown>>();
|
|
30
|
+
|
|
31
|
+
private constructor(socket: Socket, pluginId: string) {
|
|
32
|
+
this._socket = socket;
|
|
33
|
+
this._pluginId = pluginId;
|
|
34
|
+
|
|
35
|
+
// Deliver incoming pub/sub messages to registered handlers
|
|
36
|
+
socket.on("msg", ({ topic, payload }: { topic: string; payload: unknown }) => {
|
|
37
|
+
this._subHandlers.get(topic)?.forEach((h) => h(payload));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Python calls a browser-side serve handler
|
|
41
|
+
socket.on("serve_call", async ({ id, topic, payload }: { id: string; topic: string; payload: unknown }) => {
|
|
42
|
+
const handler = this._serveHandlers.get(topic);
|
|
43
|
+
const result = handler ? await handler(payload) : undefined;
|
|
44
|
+
socket.emit("serve_reply", { id, payload: result });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static connect(url: string, pluginId: string): Promise<RbWs> {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const socket = io(url, { transports: ["websocket"] });
|
|
51
|
+
const onConnect = () => { cleanup(); resolve(new RbWs(socket, pluginId)); };
|
|
52
|
+
const onError = (err: Error) => { cleanup(); reject(err); };
|
|
53
|
+
const cleanup = () => { socket.off("connect", onConnect); socket.off("connect_error", onError); };
|
|
54
|
+
socket.once("connect", onConnect);
|
|
55
|
+
socket.once("connect_error", onError);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _key(topic: string): string {
|
|
60
|
+
return resolveTopic(this._pluginId, topic);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
async pub<P = any>(topic: string, payload: P): Promise<void> {
|
|
65
|
+
this._socket.emit("pub", { topic: this._key(topic), payload });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
async sub<P = any>(topic: string, handler: (payload: P) => void): Promise<WsSubscriber> {
|
|
70
|
+
const key = this._key(topic);
|
|
71
|
+
if (!this._subHandlers.has(key)) {
|
|
72
|
+
this._subHandlers.set(key, new Set());
|
|
73
|
+
this._socket.emit("sub", { topic: key });
|
|
74
|
+
}
|
|
75
|
+
const h = handler as (payload: unknown) => void;
|
|
76
|
+
this._subHandlers.get(key)!.add(h);
|
|
77
|
+
return {
|
|
78
|
+
undeclare: () => {
|
|
79
|
+
const set = this._subHandlers.get(key);
|
|
80
|
+
if (!set) return;
|
|
81
|
+
set.delete(h);
|
|
82
|
+
if (set.size === 0) {
|
|
83
|
+
this._subHandlers.delete(key);
|
|
84
|
+
this._socket.emit("unsub", { topic: key });
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
91
|
+
async action<R = any, P = any>(topic: string, payload?: P): Promise<R> {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
this._socket
|
|
94
|
+
.timeout(10_000)
|
|
95
|
+
.emit("action", { topic: this._key(topic), payload }, (_err: Error | null, result: R) => {
|
|
96
|
+
resolve(_err ? (undefined as R) : result);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
|
+
async serve<P = any, R = any>(
|
|
103
|
+
topic: string,
|
|
104
|
+
handler: (payload?: P) => R | Promise<R>,
|
|
105
|
+
): Promise<WsQueryable> {
|
|
106
|
+
const key = this._key(topic);
|
|
107
|
+
this._serveHandlers.set(key, handler as (payload?: unknown) => unknown);
|
|
108
|
+
this._socket.emit("serve_register", { topic: key });
|
|
109
|
+
return {
|
|
110
|
+
undeclare: () => {
|
|
111
|
+
this._serveHandlers.delete(key);
|
|
112
|
+
this._socket.emit("serve_unregister", { topic: key });
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
close(): void {
|
|
118
|
+
this._socket.disconnect();
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/zenoh.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { Subscriber, Queryable } from "@eclipse-zenoh/zenoh-ts";
|
|
2
|
-
export type { Subscriber, Queryable };
|
|
3
|
-
export interface ZenohHandle {
|
|
4
|
-
undeclare(): Promise<void>;
|
|
5
|
-
}
|
|
6
|
-
export declare class RbZenoh {
|
|
7
|
-
private readonly _session;
|
|
8
|
-
private readonly _pluginId;
|
|
9
|
-
private readonly _pubs;
|
|
10
|
-
private constructor();
|
|
11
|
-
static open(url: string, pluginId: string): Promise<RbZenoh>;
|
|
12
|
-
private _key;
|
|
13
|
-
/** Publish JSON payload to a topic. Publisher is cached per topic. */
|
|
14
|
-
pub<P = any>(topic: string, payload: P): Promise<void>;
|
|
15
|
-
/** Subscribe to a topic. Returns a handle with undeclare(). */
|
|
16
|
-
sub<P = any>(topic: string, handler: (payload: P) => void): Promise<Subscriber>;
|
|
17
|
-
/**
|
|
18
|
-
* Send a request and await the first reply (Zenoh query).
|
|
19
|
-
* Equivalent to a remote procedure call.
|
|
20
|
-
*/
|
|
21
|
-
action<R = any, P = any>(topic: string, payload?: P): Promise<R>;
|
|
22
|
-
/**
|
|
23
|
-
* Register a queryable handler (Zenoh queryable).
|
|
24
|
-
* The backend calls action(), this side responds.
|
|
25
|
-
*/
|
|
26
|
-
serve<P = any, R = any>(topic: string, handler: (payload?: P) => R | Promise<R>): Promise<Queryable>;
|
|
27
|
-
close(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
//# sourceMappingURL=zenoh.d.ts.map
|
package/dist/zenoh.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"zenoh.d.ts","sourceRoot":"","sources":["../src/zenoh.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAa,UAAU,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEhF,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AAEtC,MAAM,WAAW,WAAW;IAC1B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAmBD,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgC;IAEtD,OAAO;WAKM,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKlE,OAAO,CAAC,IAAI;IAIZ,sEAAsE;IAEhE,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5D,+DAA+D;IAEzD,GAAG,CAAC,CAAC,GAAG,GAAG,EACf,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,GAC5B,OAAO,CAAC,UAAU,CAAC;IAQtB;;;OAGG;IAEG,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IActE;;;OAGG;IAEG,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC,SAAS,CAAC;IAaf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var i=Object.create;var f=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty;var h=b=>{throw TypeError(b)};var n=(b,a,c)=>a in b?f(b,a,{enumerable:!0,configurable:!0,writable:!0,value:c}):b[a]=c;var p=(b,a)=>()=>(a||b((a={exports:{}}).exports,a),a.exports),q=(b,a)=>{for(var c in a)f(b,c,{get:a[c],enumerable:!0})},o=(b,a,c,d)=>{if(a&&typeof a=="object"||typeof a=="function")for(let e of k(a))!m.call(b,e)&&e!==c&&f(b,e,{get:()=>a[e],enumerable:!(d=j(a,e))||d.enumerable});return b};var r=(b,a,c)=>(c=b!=null?i(l(b)):{},o(a||!b||!b.__esModule?f(c,"default",{value:b,enumerable:!0}):c,b));var s=(b,a,c)=>n(b,typeof a!="symbol"?a+"":a,c),g=(b,a,c)=>a.has(b)||h("Cannot "+c);var t=(b,a,c)=>(g(b,a,"read from private field"),c?c.call(b):a.get(b)),u=(b,a,c)=>a.has(b)?h("Cannot add the same private member more than once"):a instanceof WeakSet?a.add(b):a.set(b,c),v=(b,a,c,d)=>(g(b,a,"write to private field"),d?d.call(b,c):a.set(b,c),c),w=(b,a,c)=>(g(b,a,"access private method"),c);export{p as a,q as b,r as c,s as d,t as e,u as f,v as g,w as h};
|
package/src/zenoh.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Zenoh communication client for plugin frontends.
|
|
3
|
-
* All topics are automatically namespaced to prevent cross-plugin collisions:
|
|
4
|
-
* "sensor/data" → "plugin/{pluginId}/sensor/data"
|
|
5
|
-
* "/broadcast/x" → "broadcast/x" (leading slash = absolute)
|
|
6
|
-
*/
|
|
7
|
-
import { Session, Config } from "@eclipse-zenoh/zenoh-ts";
|
|
8
|
-
import type { Publisher, Subscriber, Queryable } from "@eclipse-zenoh/zenoh-ts";
|
|
9
|
-
|
|
10
|
-
export type { Subscriber, Queryable };
|
|
11
|
-
|
|
12
|
-
export interface ZenohHandle {
|
|
13
|
-
undeclare(): Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function resolveTopic(pluginId: string, topic: string): string {
|
|
17
|
-
return topic.startsWith("/") ? topic.slice(1) : `plugin/${pluginId}/${topic}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function encode(payload: unknown): string {
|
|
21
|
-
return JSON.stringify(payload);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function decode(raw: string | Uint8Array): unknown {
|
|
25
|
-
const str = typeof raw === "string" ? raw : new TextDecoder().decode(raw);
|
|
26
|
-
try {
|
|
27
|
-
return JSON.parse(str);
|
|
28
|
-
} catch {
|
|
29
|
-
return str;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class RbZenoh {
|
|
34
|
-
private readonly _session: Session;
|
|
35
|
-
private readonly _pluginId: string;
|
|
36
|
-
private readonly _pubs = new Map<string, Publisher>();
|
|
37
|
-
|
|
38
|
-
private constructor(session: Session, pluginId: string) {
|
|
39
|
-
this._session = session;
|
|
40
|
-
this._pluginId = pluginId;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static async open(url: string, pluginId: string): Promise<RbZenoh> {
|
|
44
|
-
const session = await Session.open(new Config(url));
|
|
45
|
-
return new RbZenoh(session, pluginId);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private _key(topic: string): string {
|
|
49
|
-
return resolveTopic(this._pluginId, topic);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Publish JSON payload to a topic. Publisher is cached per topic. */
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
-
async pub<P = any>(topic: string, payload: P): Promise<void> {
|
|
55
|
-
const key = this._key(topic);
|
|
56
|
-
let publisher = this._pubs.get(key);
|
|
57
|
-
if (!publisher) {
|
|
58
|
-
publisher = await this._session.declarePublisher(key);
|
|
59
|
-
this._pubs.set(key, publisher);
|
|
60
|
-
}
|
|
61
|
-
await publisher.put(encode(payload));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Subscribe to a topic. Returns a handle with undeclare(). */
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
-
async sub<P = any>(
|
|
67
|
-
topic: string,
|
|
68
|
-
handler: (payload: P) => void,
|
|
69
|
-
): Promise<Subscriber> {
|
|
70
|
-
return this._session.declareSubscriber(this._key(topic), {
|
|
71
|
-
handler: (sample) => {
|
|
72
|
-
handler(decode(sample.payload().toBytes()) as P);
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Send a request and await the first reply (Zenoh query).
|
|
79
|
-
* Equivalent to a remote procedure call.
|
|
80
|
-
*/
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
82
|
-
async action<R = any, P = any>(topic: string, payload?: P): Promise<R> {
|
|
83
|
-
const opts =
|
|
84
|
-
payload !== undefined ? { payload: encode(payload) } : undefined;
|
|
85
|
-
const receiver = await this._session.get(this._key(topic), opts);
|
|
86
|
-
if (!receiver) return undefined;
|
|
87
|
-
for await (const reply of receiver) {
|
|
88
|
-
const result = reply.result();
|
|
89
|
-
if (result && "payload" in result) {
|
|
90
|
-
return decode(result.payload().toBytes()) as R;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Register a queryable handler (Zenoh queryable).
|
|
98
|
-
* The backend calls action(), this side responds.
|
|
99
|
-
*/
|
|
100
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
-
async serve<P = any, R = any>(
|
|
102
|
-
topic: string,
|
|
103
|
-
handler: (payload?: P) => R | Promise<R>,
|
|
104
|
-
): Promise<Queryable> {
|
|
105
|
-
const key = this._key(topic);
|
|
106
|
-
return this._session.declareQueryable(key, {
|
|
107
|
-
handler: async (query) => {
|
|
108
|
-
const reqPayload = query.payload()
|
|
109
|
-
? (decode(query.payload()!.toBytes()) as P)
|
|
110
|
-
: undefined;
|
|
111
|
-
const result = await handler(reqPayload);
|
|
112
|
-
await query.reply(key, encode(result));
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async close(): Promise<void> {
|
|
118
|
-
for (const pub of this._pubs.values()) await pub.undeclare();
|
|
119
|
-
this._pubs.clear();
|
|
120
|
-
await this._session.close();
|
|
121
|
-
}
|
|
122
|
-
}
|