@rainbow-robotics/plugin-ui 0.1.2-dev.6 → 0.1.2-dev.7

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.
@@ -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-3DOWEHFU.js";import"./chunks/chunk-HJVATDC6.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
+ 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-3DOWEHFU.js";import"./chunks/chunk-HJVATDC6.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
+ 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-HJVATDC6.js";export{a as RbContext,b as useRb};
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.6",
3
+ "version": "0.1.2-dev.7",
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 Zenoh config
6
- * (url + pluginId). We open a Zenoh session and wire up the rb object.
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 { RbZenoh } from "./zenoh";
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
- zenohUrl: string;
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, zenohUrl, pluginId }) => {
44
- const zenoh = await RbZenoh.open(zenohUrl, pluginId);
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) => zenoh.pub(topic, payload),
48
- sub: (topic, handler) => zenoh.sub(topic, handler),
49
- action: (topic, payload) => zenoh.action(topic, payload),
50
- serve: (topic, handler) => zenoh.serve(topic, handler),
51
- toast: (message, type) => toast(message, type),
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 { RbSdk } from "./sdk";
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
- export type RbSdkImpl = RbSdk;
270
-
271
- /** Shape the host exposes to the sandbox over the thread (see ./connect). */
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
- action: (id: string, payload?: unknown) => Promise<unknown>;
279
- notify: (message: string, type?: string) => void;
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
- /** Host SDK implementation handed to the plugin. */
287
- rb: RbSdkImpl;
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({ sandboxUrl, rb }: PluginContentProps) {
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 rbRef = useRef(rb);
298
- rbRef.current = rb;
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
- action: (id: string, payload?: unknown) =>
320
- rbRef.current.action(id, payload),
321
- notify: (message: string, type?: string) =>
322
- rbRef.current.notify(message, type as never),
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 "./zenoh";
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
@@ -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
- }