@adcops/autocore-react 3.0.10 → 3.0.13

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.
@@ -76,7 +76,7 @@ export interface EventEmitterContextType {
76
76
  * Invoke/send a message to the back end.
77
77
  * This does NOT get published to the front end.
78
78
  */
79
- invoke(fname: string, payload?: object): Promise<object>;
79
+ invoke(domain: string, fname: string, payload?: object): Promise<object>;
80
80
  /**
81
81
  * Subscribe to events identified by the topic.
82
82
  * @param topic The subscription topic.
@@ -1 +1 @@
1
- import{jsx as _jsx}from"react/jsx-runtime";import{createContext,useState,useMemo}from"react";import{createHub,Hub}from"../hub";export{Hub};let globalSubscriptionId=1;export const EventEmitterContext=createContext({state:{subscriptions:{},nextSubscriptionId:1},dispatch:()=>{},subscribe:()=>0,invoke:async(t,s)=>Promise.resolve({}),unsubscribe:t=>{},hub:null,getSubscriptions:()=>[]});export const EventEmitterProvider=({children:t})=>{const[s,e]=useState({subscriptions:{},nextSubscriptionId:1}),i=useMemo((()=>createHub()),[]),r=t=>{const{topic:s,payload:i}=t;e((t=>(t.subscriptions[s]?.forEach((t=>t.callback(i))),{...t,eventData:i})))},o=(t,s)=>{globalSubscriptionId+=1;const i=globalSubscriptionId;return e((e=>({...e,subscriptions:{...e.subscriptions,[t]:[...e.subscriptions[t]||[],{id:i,callback:s}]},nextSubscriptionId:globalSubscriptionId+1}))),i},n=t=>{e((s=>{const e={...s.subscriptions};for(const s in e)e.hasOwnProperty(s)&&(e[s]=e[s].filter((s=>s.id!==t)),0===e[s].length&&delete e[s]);return{...s,subscriptions:e}}))},c=t=>t?s.subscriptions[t]||[]:s.subscriptions,u=useMemo((()=>({state:s,dispatch:r,subscribe:o,unsubscribe:n,invoke:i.invoke,hub:i,getSubscriptions:c})),[s,i]);return i.setContext(u),_jsx(EventEmitterContext.Provider,{value:u,children:t})};
1
+ import{jsx as _jsx}from"react/jsx-runtime";import{createContext,useState,useMemo}from"react";import{createHub,Hub}from"../hub";export{Hub};let globalSubscriptionId=1;export const EventEmitterContext=createContext({state:{subscriptions:{},nextSubscriptionId:1},dispatch:()=>{},subscribe:()=>0,invoke:async(t,s,e)=>Promise.resolve({}),unsubscribe:t=>{},hub:null,getSubscriptions:()=>[]});export const EventEmitterProvider=({children:t})=>{const[s,e]=useState({subscriptions:{},nextSubscriptionId:1}),i=useMemo((()=>createHub()),[]),r=t=>{const{topic:s,payload:i}=t;e((t=>(t.subscriptions[s]?.forEach((t=>t.callback(i))),{...t,eventData:i})))},o=(t,s)=>{globalSubscriptionId+=1;const i=globalSubscriptionId;return e((e=>({...e,subscriptions:{...e.subscriptions,[t]:[...e.subscriptions[t]||[],{id:i,callback:s}]},nextSubscriptionId:globalSubscriptionId+1}))),i},n=t=>{e((s=>{const e={...s.subscriptions};for(const s in e)e.hasOwnProperty(s)&&(e[s]=e[s].filter((s=>s.id!==t)),0===e[s].length&&delete e[s]);return{...s,subscriptions:e}}))},c=t=>t?s.subscriptions[t]||[]:s.subscriptions,u=useMemo((()=>({state:s,dispatch:r,subscribe:o,unsubscribe:n,invoke:i.invoke,hub:i,getSubscriptions:c})),[s,i]);return i.setContext(u),_jsx(EventEmitterContext.Provider,{value:u,children:t})};
@@ -0,0 +1,12 @@
1
+ export interface CommandMessageResult {
2
+ data: any;
3
+ success: boolean;
4
+ error_message: string;
5
+ }
6
+ export interface CommandMessage {
7
+ request_id: number;
8
+ domain: string;
9
+ fname: string;
10
+ args?: any;
11
+ result?: CommandMessageResult;
12
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -1,4 +1,5 @@
1
1
  import { EventEmitterContextType } from '../core/EventEmitterContext';
2
+ import { CommandMessageResult } from "./CommandMessage";
2
3
  /**
3
4
  * Base class for the interface for the IPC to the backend,
4
5
  * which may be websockets, MQTT, the Tauri API,
@@ -98,16 +99,17 @@ export declare abstract class HubBase {
98
99
  * Invoke/send a message to the back end.
99
100
  * This does NOT get published to the front end.
100
101
  */
101
- abstract invoke(fname: string, payload?: object): Promise<object>;
102
+ abstract invoke(domain: string, fname: string, payload?: object): Promise<CommandMessageResult>;
102
103
  /**
103
104
  * Convenience function to invoke a command with an optional topic and optional value.
104
105
  * This will invoke the specified command in the backend. It does not broadcast the
105
106
  * value within the local frontend.
107
+ * @param domain string The domain of the command that will be invoked.
106
108
  * @param fname string Function name
107
109
  * @param topic string Topic
108
110
  * @param value any data payload
109
111
  */
110
- invokeTopic(fname: string, topic: string | null, value?: any): Promise<object>;
112
+ invokeTopic(domain: string, fname: string, topic: string | null, value?: any): Promise<CommandMessageResult>;
111
113
  /**
112
114
  * Pubish a topic throughout the web app using the
113
115
  * Global EventEmitterContext. This will broadcast within the local frontend.
@@ -1 +1 @@
1
- export class HubBase{constructor(){Object.defineProperty(this,"localTopicToBackendTopicMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"backendTopicToLocalMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"localFNameToBackendFNameMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"backendFNameToLocalFNameMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"context",{enumerable:!0,configurable:!0,writable:!0,value:null})}setContext(a){this.context=a}invokeTopic(a,e,o){const c={topic:e,data:o};return this.invoke(a,c)}publish(a,e){const o=this.toLocalTopic(a);this.context?.dispatch({topic:o,payload:e})}mapTopic(a,e){this.localTopicToBackendTopicMap.set(a,e),this.backendTopicToLocalMap.set(e,a)}unmapTopic(a){if(this.localTopicToBackendTopicMap.has(a)){const e=this.localTopicToBackendTopicMap.get(a);this.localTopicToBackendTopicMap.delete(a),null!=e&&this.backendTopicToLocalMap.has(e)&&this.backendTopicToLocalMap.delete(e)}}mapFName(a,e){this.localFNameToBackendFNameMap.set(a,e),this.backendFNameToLocalFNameMap.set(e,a)}unmapFName(a){if(this.localFNameToBackendFNameMap.has(a)){const e=this.localFNameToBackendFNameMap.get(a);this.localFNameToBackendFNameMap.delete(a),null!=e&&this.backendFNameToLocalFNameMap.has(e)&&this.backendFNameToLocalFNameMap.delete(e)}}toBackendTopic(a){if(this.localTopicToBackendTopicMap.has(a)){const e=this.localTopicToBackendTopicMap.get(a);return null!=e?e:a}return a}toBackendFName(a){if(this.localFNameToBackendFNameMap.has(a)){const e=this.localFNameToBackendFNameMap.get(a);return null!=e?e:a}return a}toLocalTopic(a){if(this.backendTopicToLocalMap.has(a)){const e=this.backendTopicToLocalMap.get(a);return null!=e?e:a}return a}toLocalFName(a){if(this.backendFNameToLocalFNameMap.has(a)){const e=this.backendFNameToLocalFNameMap.get(a);return null!=e?e:a}return a}}export default HubBase;
1
+ export class HubBase{constructor(){Object.defineProperty(this,"localTopicToBackendTopicMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"backendTopicToLocalMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"localFNameToBackendFNameMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"backendFNameToLocalFNameMap",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"context",{enumerable:!0,configurable:!0,writable:!0,value:null})}setContext(a){this.context=a}invokeTopic(a,e,o,c){const t={topic:o,data:c};return this.invoke(a,e,t)}publish(a,e){const o=this.toLocalTopic(a);this.context?.dispatch({topic:o,payload:e})}mapTopic(a,e){this.localTopicToBackendTopicMap.set(a,e),this.backendTopicToLocalMap.set(e,a)}unmapTopic(a){if(this.localTopicToBackendTopicMap.has(a)){const e=this.localTopicToBackendTopicMap.get(a);this.localTopicToBackendTopicMap.delete(a),null!=e&&this.backendTopicToLocalMap.has(e)&&this.backendTopicToLocalMap.delete(e)}}mapFName(a,e){this.localFNameToBackendFNameMap.set(a,e),this.backendFNameToLocalFNameMap.set(e,a)}unmapFName(a){if(this.localFNameToBackendFNameMap.has(a)){const e=this.localFNameToBackendFNameMap.get(a);this.localFNameToBackendFNameMap.delete(a),null!=e&&this.backendFNameToLocalFNameMap.has(e)&&this.backendFNameToLocalFNameMap.delete(e)}}toBackendTopic(a){if(this.localTopicToBackendTopicMap.has(a)){const e=this.localTopicToBackendTopicMap.get(a);return null!=e?e:a}return a}toBackendFName(a){if(this.localFNameToBackendFNameMap.has(a)){const e=this.localFNameToBackendFNameMap.get(a);return null!=e?e:a}return a}toLocalTopic(a){if(this.backendTopicToLocalMap.has(a)){const e=this.backendTopicToLocalMap.get(a);return null!=e?e:a}return a}toLocalFName(a){if(this.backendFNameToLocalFNameMap.has(a)){const e=this.backendFNameToLocalFNameMap.get(a);return null!=e?e:a}return a}}export default HubBase;
@@ -1,4 +1,5 @@
1
1
  import { HubBase } from './HubBase';
2
+ import { CommandMessageResult } from "./CommandMessage";
2
3
  /**
3
4
  * Hub for simulating functionality when no backend is present.
4
5
  */
@@ -15,6 +16,6 @@ export declare class HubSimulate extends HubBase {
15
16
  * @param timeout Timeout in milliseconds after which the promise is rejected if no response is received.
16
17
  * @returns A Promise that resolves to the response from the backend or rejects if a timeout occurs.
17
18
  */
18
- invoke(fname: string, payload?: object, timeout?: number): Promise<object>;
19
+ invoke(domain: string, fname: string, payload?: object, timeout?: number): Promise<CommandMessageResult>;
19
20
  }
20
21
  export default HubSimulate;
@@ -1 +1 @@
1
- import{HubBase}from"./HubBase";export class HubSimulate extends HubBase{constructor(){super()}invoke(e,t,u=20){return new Promise(((e,r)=>{setTimeout((()=>{e({data:t})}),u)}))}}export default HubSimulate;
1
+ import{HubBase}from"./HubBase";export class HubSimulate extends HubBase{constructor(){super()}invoke(e,s,r,t=20){return new Promise(((e,s)=>{setTimeout((()=>{e({data:r,error_message:"",success:!0})}),t)}))}}export default HubSimulate;
@@ -1,4 +1,5 @@
1
1
  import { HubBase } from './HubBase';
2
+ import { CommandMessageResult } from "./CommandMessage";
2
3
  /**
3
4
  * Hub for integrating with the Tauri backend.
4
5
  *
@@ -82,5 +83,5 @@ export declare class HubTauri extends HubBase {
82
83
  * @param payload Optional data payload
83
84
  * @returns The return value of the backend method.
84
85
  */
85
- invoke(fname: string, payload?: object | undefined): Promise<object>;
86
+ invoke(domain: string, fname: string, payload?: object | undefined): Promise<CommandMessageResult>;
86
87
  }
@@ -1 +1 @@
1
- import{HubBase}from"./HubBase";import{event,invoke}from"@tauri-apps/api";export class HubTauri extends HubBase{constructor(){super(),event.listen("autocore://broadcast_event",(o=>{let e=JSON.parse(o.payload);void 0!==e.topic&&null!==e.topic&&(void 0!==e.payload&&null!==e.payload?this.publish(e.topic,e.payload):this.publish(e.topic,void 0))}))}invoke(o,e){return null!=e?invoke(o,e):invoke(o)}}
1
+ import{HubBase}from"./HubBase";import{event,invoke}from"@tauri-apps/api";export class HubTauri extends HubBase{constructor(){super(),event.listen("autocore://broadcast_event",(o=>{let e=JSON.parse(o.payload);void 0!==e.topic&&null!==e.topic&&(void 0!==e.payload&&null!==e.payload?this.publish(e.topic,e.payload):this.publish(e.topic,void 0))}))}invoke(o,e,i){if(null!=i){return invoke(`${o}_${e}`,i)}return invoke(e)}}
@@ -0,0 +1,25 @@
1
+ /** @file
2
+ *
3
+ * ## broadcast messages
4
+ * The result field of CommandMessages used for broadcast must have
5
+ * success set to true, and must contain the field "topic." The topic field, along
6
+ * with the domain field of the CommandMessage will be combined for the topic used
7
+ * by the websocket client.
8
+ *
9
+ * broadcast topic = `${CommandMessage.domain}/${CommandMessage.result["topic"]}
10
+ *
11
+ * So, to subscribe to a broadcast topic for GM.fReal that is served by the ADS client, the
12
+ * topic will be: "ADS/GM.fReal"
13
+ */
14
+ import { HubBase } from './HubBase';
15
+ import { CommandMessage, CommandMessageResult } from "./CommandMessage";
16
+ export declare class HubWebSocket extends HubBase {
17
+ private socket;
18
+ private requestId;
19
+ private pendingRequests;
20
+ constructor();
21
+ invoke: (domain: string, fname: string, payload?: object) => Promise<CommandMessageResult>;
22
+ handleUnsolicitedMessage: (msg: CommandMessage) => void;
23
+ disconnect: () => void;
24
+ protected emit: (eventName: string, payload?: object) => void;
25
+ }
@@ -0,0 +1 @@
1
+ import{HubBase}from"./HubBase";export class HubWebSocket extends HubBase{constructor(){super(),Object.defineProperty(this,"socket",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),Object.defineProperty(this,"requestId",{enumerable:!0,configurable:!0,writable:!0,value:0}),Object.defineProperty(this,"pendingRequests",{enumerable:!0,configurable:!0,writable:!0,value:new Map}),Object.defineProperty(this,"invoke",{enumerable:!0,configurable:!0,writable:!0,value:(e,t,s)=>new Promise(((i,r)=>{const n=++this.requestId;this.pendingRequests.set(n,{resolve:i,reject:r});let o={request_id:n,domain:e,fname:t,args:s,result:void 0};this.socket.send(JSON.stringify(o))}))}),Object.defineProperty(this,"handleUnsolicitedMessage",{enumerable:!0,configurable:!0,writable:!0,value:e=>{if("BROADCAST"===e.fname&&e.domain.length>=1&&void 0!==e.result&&null!==e.result&&void 0!==e.result.data&&null!==e.result.data){let t=`${e.domain}/${e.result.data.topic}`;this.publish(t,e.result.data)}}}),Object.defineProperty(this,"disconnect",{enumerable:!0,configurable:!0,writable:!0,value:()=>{this.socket.close()}}),Object.defineProperty(this,"emit",{enumerable:!0,configurable:!0,writable:!0,value:(e,t)=>{this.socket.send(JSON.stringify({eventName:e,payload:t}))}});const e=window.location.hostname,t=window.location.port,s=`${"https:"===window.location.protocol?"wss://":"ws://"}${e}${t?":"+t:""}/ws/`;this.socket=new WebSocket(s);let i=this;this.socket.onopen=function(){i.publish("HUB/connected",!0)},this.socket.onmessage=e=>{const t=JSON.parse(e.data);if(t.request_id&&this.pendingRequests.has(t.request_id)){const{resolve:e,reject:s}=this.pendingRequests.get(t.request_id);t.result?.success?e(t.result):s(new Error(t.result?.error_message)),this.pendingRequests.delete(t.request_id)}else this.handleUnsolicitedMessage(t)},this.socket.onerror=e=>{},this.socket.onclose=()=>{}}}
@@ -1,7 +1,8 @@
1
1
  import { HubBase as Hub } from './HubBase';
2
2
  import { HubTauri } from './HubTauri';
3
- import { HubSocketIo } from "./HubSocketIo";
3
+ import { HubWebSocket } from "./HubWebSocket";
4
4
  import { HubSimulate } from "./HubSimulate";
5
+ import { CommandMessage, CommandMessageResult } from './CommandMessage';
5
6
  /**
6
7
  * Creates a connection to the backend.
7
8
  * @returns Hub
@@ -9,5 +10,6 @@ import { HubSimulate } from "./HubSimulate";
9
10
  export declare function createHub(): Hub;
10
11
  export { HubBase as Hub } from './HubBase';
11
12
  export { HubTauri };
12
- export { HubSocketIo };
13
+ export { HubWebSocket };
13
14
  export { HubSimulate };
15
+ export type { CommandMessage, CommandMessageResult };
package/dist/hub/index.js CHANGED
@@ -1 +1 @@
1
- import{HubTauri}from"./HubTauri";import{HubSocketIo}from"./HubSocketIo";import{HubSimulate}from"./HubSimulate";export function createHub(){return window.__TAURI__?new HubTauri:"80"!==window.location.port&&"443"!==window.location.port?new HubSimulate:new HubSocketIo}export{HubBase as Hub}from"./HubBase";export{HubTauri};export{HubSocketIo};export{HubSimulate};
1
+ import{HubTauri}from"./HubTauri";import{HubWebSocket}from"./HubWebSocket";import{HubSimulate}from"./HubSimulate";export function createHub(){return void 0!==window.__TAURI__&&null!==window.__TAURI__?new HubTauri:void 0!==window.location.port&&window.location.port.length>0&&"80"!==window.location.port&&"8080"!==window.location.port&&"443"!==window.location.port?new HubSimulate:new HubWebSocket}export{HubBase as Hub}from"./HubBase";export{HubTauri};export{HubWebSocket};export{HubSimulate};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcops/autocore-react",
3
- "version": "3.0.10",
3
+ "version": "3.0.13",
4
4
  "description": "A React component library for industrial user interfaces.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2024-01-17 11:45:10
4
4
  * -----
5
- * Last Modified: 2024-03-22 14:25:08
5
+ * Last Modified: 2024-04-23 11:53:43
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -103,7 +103,7 @@ export interface EventEmitterContextType {
103
103
  * Invoke/send a message to the back end.
104
104
  * This does NOT get published to the front end.
105
105
  */
106
- invoke(fname: string, payload? : object) : Promise<object>;
106
+ invoke( domain: string, fname: string, payload? : object) : Promise<object>;
107
107
 
108
108
  /**
109
109
  * Subscribe to events identified by the topic.
@@ -264,7 +264,8 @@ export const EventEmitterContext = createContext<EventEmitterContextType>({
264
264
  state: { subscriptions: {}, nextSubscriptionId : 1 },
265
265
  dispatch: () => { },
266
266
  subscribe: () => { return 0; }, // Placeholder for subscription logic
267
- invoke: async (fname: string, payload?: object) => {
267
+ invoke: async (domain: string, fname: string, payload?: object) => {
268
+ domain;
268
269
  fname;
269
270
  payload;
270
271
  // Placeholder for invoke logic
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
+ * Created Date: 2024-04-24 11:47:13
4
+ * -----
5
+ * Last Modified: 2024-04-24 11:48:08
6
+ * -----
7
+ *
8
+ */
9
+
10
+
11
+
12
+ /// The result portion of a CommandMessage. The server will
13
+ /// place the result of a command in this portion of the message.
14
+ export interface CommandMessageResult {
15
+ /// The JSON object of the result.
16
+ data: any;
17
+ /// If true, the command was processed successfully.
18
+ success : boolean,
19
+ /// If success is false, this should contain a corresponding error message.
20
+ error_message: string;
21
+ }
22
+
23
+ /// CommandMessage is the object passed between the client and server for making requests and
24
+ /// responses.
25
+ export interface CommandMessage {
26
+ /// An id to identify the request. The request ID is managed by the client and will be reflected back.
27
+ /// The server will never change the request ID.
28
+ request_id: number;
29
+ /// The command domain/servelet in which this command/function to invoke belongs.
30
+ domain: string;
31
+ /// The name of the function to invoke.
32
+ fname: string;
33
+ /// A JSON object of the arguments to the function.
34
+ args?: any;
35
+ /// The result of the command, reflected back from the server
36
+ result?: CommandMessageResult;
37
+ }
@@ -3,14 +3,14 @@
3
3
  * Created Date: 2023-12-15 14:21:33
4
4
  * Author: Thomas C. Bitsky Jr.
5
5
  * -----
6
- * Last Modified: 2024-03-08 07:09:11
6
+ * Last Modified: 2024-04-24 11:56:16
7
7
  * Modified By: ADC
8
8
  * -----
9
9
  *
10
10
  */
11
11
 
12
12
  import { EventEmitterContextType} from '../core/EventEmitterContext';
13
-
13
+ import {CommandMessageResult} from "./CommandMessage";
14
14
 
15
15
 
16
16
  /**
@@ -122,23 +122,24 @@ export abstract class HubBase {
122
122
  * Invoke/send a message to the back end.
123
123
  * This does NOT get published to the front end.
124
124
  */
125
- abstract invoke(fname: string, payload? : object) : Promise<object>;
125
+ abstract invoke(domain: string, fname: string, payload? : object) : Promise<CommandMessageResult>;
126
126
 
127
127
 
128
128
  /**
129
129
  * Convenience function to invoke a command with an optional topic and optional value.
130
130
  * This will invoke the specified command in the backend. It does not broadcast the
131
131
  * value within the local frontend.
132
+ * @param domain string The domain of the command that will be invoked.
132
133
  * @param fname string Function name
133
134
  * @param topic string Topic
134
135
  * @param value any data payload
135
136
  */
136
- invokeTopic( fname : string, topic : string | null, value? : any ) : Promise<object> {
137
+ invokeTopic( domain: string, fname : string, topic : string | null, value? : any ) : Promise<CommandMessageResult> {
137
138
  const msg = {
138
139
  topic : topic,
139
140
  data : value
140
141
  };
141
- return this.invoke(fname, msg);
142
+ return this.invoke(domain, fname, msg);
142
143
  }
143
144
 
144
145
  /**
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2023 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2023-12-17 10:38:21
4
4
  * -----
5
- * Last Modified: 2024-03-08 09:35:51
5
+ * Last Modified: 2024-04-24 11:56:26
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
  import { HubBase } from './HubBase';
13
-
13
+ import { CommandMessageResult } from "./CommandMessage";
14
14
  /**
15
15
  * Hub for simulating functionality when no backend is present.
16
16
  */
@@ -31,13 +31,21 @@ export class HubSimulate extends HubBase {
31
31
  * @param timeout Timeout in milliseconds after which the promise is rejected if no response is received.
32
32
  * @returns A Promise that resolves to the response from the backend or rejects if a timeout occurs.
33
33
  */
34
- invoke(fname: string, payload?: object, timeout: number = 20): Promise<object> {
34
+ invoke(domain: string, fname: string, payload?: object, timeout: number = 20): Promise<CommandMessageResult> {
35
+ domain; // Not using for simulator
35
36
  fname; // Not using for simulator
36
37
  return new Promise((resolve, reject) => {
37
38
  reject; // not using for simulator
38
39
  // Set a timeout to echo a response
39
40
  setTimeout(() => {
40
- resolve({data: payload});
41
+
42
+ let ret: CommandMessageResult = {
43
+ data: payload,
44
+ error_message: "",
45
+ success: true
46
+ };
47
+
48
+ resolve(ret);
41
49
  }, timeout);
42
50
  });
43
51
  }
@@ -3,7 +3,7 @@
3
3
  * Created Date: 2023-12-17 09:50:23
4
4
  * Author: Thomas C. Bitsky Jr.
5
5
  * -----
6
- * Last Modified: 2024-03-07 13:20:46
6
+ * Last Modified: 2024-04-24 11:56:32
7
7
  * Modified By: ADC
8
8
  * -----
9
9
  *
@@ -12,7 +12,7 @@
12
12
  import { InvokeArgs } from '@tauri-apps/api/tauri';
13
13
  import { HubBase } from './HubBase';
14
14
  import {event, invoke} from '@tauri-apps/api';
15
-
15
+ import {CommandMessageResult} from "./CommandMessage";
16
16
 
17
17
  /**
18
18
  * Hub for integrating with the Tauri backend.
@@ -128,13 +128,17 @@ export class HubTauri extends HubBase {
128
128
  * @param payload Optional data payload
129
129
  * @returns The return value of the backend method.
130
130
  */
131
- invoke(fname: string, payload?: object | undefined): Promise<object> {
131
+ invoke(domain : string, fname: string, payload?: object | undefined): Promise<CommandMessageResult> {
132
132
 
133
133
  console.log(JSON.stringify(event));
134
134
 
135
135
  if (payload !== undefined && payload !== null) {
136
136
  console.log(`Payload: ${JSON.stringify(payload)}`);
137
- return invoke(fname, payload as InvokeArgs);
137
+
138
+ // todo! This should probably always been the same function, and then that function
139
+ // in the backend routes the command.
140
+ let topic = `${domain}_${fname}`;
141
+ return invoke(topic, payload as InvokeArgs);
138
142
  }
139
143
  else {
140
144
  return invoke(fname);
@@ -0,0 +1,127 @@
1
+ /*
2
+ * Copyright (C) 2024 Automated Design Corp.. All Rights Reserved.
3
+ * Created Date: 2024-04-17 09:13:07
4
+ * -----
5
+ * Last Modified: 2024-04-24 11:56:51
6
+ * -----
7
+ *
8
+ */
9
+
10
+ /** @file
11
+ *
12
+ * ## broadcast messages
13
+ * The result field of CommandMessages used for broadcast must have
14
+ * success set to true, and must contain the field "topic." The topic field, along
15
+ * with the domain field of the CommandMessage will be combined for the topic used
16
+ * by the websocket client.
17
+ *
18
+ * broadcast topic = `${CommandMessage.domain}/${CommandMessage.result["topic"]}
19
+ *
20
+ * So, to subscribe to a broadcast topic for GM.fReal that is served by the ADS client, the
21
+ * topic will be: "ADS/GM.fReal"
22
+ */
23
+
24
+ import { HubBase } from './HubBase';
25
+ import {CommandMessage, CommandMessageResult} from "./CommandMessage";
26
+
27
+ // Function to parse the JSON string received from the server into a CommandMessage object
28
+ // function parseCommandMessage(jsonString: string): CommandMessage {
29
+ // const parsed: CommandMessage = JSON.parse(jsonString);
30
+ // // Assuming the JSON structure directly matches the TypeScript interfaces
31
+ // return parsed;
32
+ // }
33
+
34
+
35
+ interface RequestRecord {
36
+ resolve: (value?: any) => void;
37
+ reject: (reason?: any) => void;
38
+ }
39
+
40
+
41
+ export class HubWebSocket extends HubBase {
42
+ private socket: WebSocket;
43
+ private requestId = 0;
44
+ private pendingRequests = new Map<number, RequestRecord>();
45
+
46
+ constructor() {
47
+ super();
48
+
49
+ const host = window.location.hostname; // Get the hostname from the address bar
50
+ const port = window.location.port; // Get the port from the address bar, if any
51
+ const proto = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; // Determine the protocol
52
+ const wsUrl = `${proto}${host}${port ? ':' + port : ''}/ws/`; // Construct WebSocket URL
53
+
54
+ this.socket = new WebSocket(wsUrl);
55
+
56
+
57
+ let self = this;
58
+ this.socket.onopen = function() {
59
+ console.log("WebSocket connection established.");
60
+ self.publish("HUB/connected", true);
61
+ //ws.send("Hello, server!"); // Send a message to the server
62
+ };
63
+
64
+
65
+ this.socket.onmessage = (event) => {
66
+ const data: CommandMessage = JSON.parse(event.data);
67
+ if (data.request_id && this.pendingRequests.has(data.request_id)) {
68
+ const { resolve, reject } = this.pendingRequests.get(data.request_id)!;
69
+ if (!data.result?.success) {
70
+ reject(new Error(data.result?.error_message));
71
+ } else {
72
+ resolve(data.result);
73
+ }
74
+ this.pendingRequests.delete(data.request_id);
75
+ } else {
76
+ this.handleUnsolicitedMessage(data);
77
+ }
78
+ };
79
+
80
+ this.socket.onerror = (error: Event) => {
81
+ console.error('WebSocket error:', error);
82
+ };
83
+
84
+ this.socket.onclose = () => {
85
+ console.log('WebSocket connection closed.');
86
+ };
87
+ }
88
+
89
+ invoke = (domain : string, fname: string, payload?: object): Promise<CommandMessageResult> => {
90
+
91
+ return new Promise((resolve, reject) => {
92
+ const id = ++this.requestId; // Increment and use the request ID
93
+ this.pendingRequests.set(id, { resolve, reject });
94
+
95
+ let cm : CommandMessage = {
96
+ request_id: id,
97
+ domain: domain,
98
+ fname: fname,
99
+ args: payload,
100
+ result: undefined
101
+ };
102
+
103
+ this.socket.send(JSON.stringify(cm));
104
+ });
105
+ }
106
+
107
+ handleUnsolicitedMessage = (msg: CommandMessage) => {
108
+ // Handle messages that do not correspond to a pending request
109
+
110
+ if (msg.fname === "BROADCAST"
111
+ && msg.domain.length >= 1
112
+ && msg.result !== undefined && msg.result !== null
113
+ && msg.result.data !== undefined && msg.result.data !== null
114
+ ) {
115
+ let topic = `${msg.domain}/${msg.result.data["topic"]}`;
116
+ this.publish(topic, msg.result.data);
117
+ }
118
+ }
119
+
120
+ disconnect= (): void => {
121
+ this.socket.close();
122
+ }
123
+
124
+ protected emit = (eventName: string, payload?: object): void => {
125
+ this.socket.send(JSON.stringify({ eventName, payload }));
126
+ }
127
+ }
package/src/hub/index.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Copyright (C) 2024 Automated Design Corp. All Rights Reserved.
3
3
  * Created Date: 2024-01-16 21:07:29
4
4
  * -----
5
- * Last Modified: 2024-03-08 10:20:06
5
+ * Last Modified: 2024-04-24 11:52:21
6
6
  * Modified By: ADC
7
7
  * -----
8
8
  *
@@ -10,25 +10,38 @@
10
10
 
11
11
  import { HubBase as Hub } from './HubBase';
12
12
  import { HubTauri } from './HubTauri';
13
- import { HubSocketIo } from "./HubSocketIo";
13
+ import { HubWebSocket } from "./HubWebSocket";
14
14
  import {HubSimulate} from "./HubSimulate"
15
+ import { CommandMessage, CommandMessageResult } from './CommandMessage';
15
16
 
16
17
  /**
17
18
  * Creates a connection to the backend.
18
19
  * @returns Hub
19
20
  */
20
21
  export function createHub(): Hub {
21
- if (window.__TAURI__) {
22
+
23
+ if (window.__TAURI__ !== undefined && window.__TAURI__ !== null) {
24
+ // Standalone Tauri application
25
+ console.log("HUB: Starting link to Tauri backend.");
22
26
  return new HubTauri();
23
27
  }
24
- else if (window.location.port !== '80' && window.location.port !== '443' ) {
28
+ else if (
29
+ window.location.port !== undefined
30
+ && window.location.port.length > 0
31
+ && window.location.port !== '80'
32
+ && window.location.port !== '8080'
33
+ && window.location.port !== '443'
34
+ ) {
25
35
 
26
36
  // We're loaded in some development environment
37
+ console.log("HUB: Starting HUB SIMULATOR.");
27
38
  return new HubSimulate();
28
39
 
29
40
  }
30
41
  else {
31
- return new HubSocketIo();
42
+ // A web-app that must communicate with a backend in another process.
43
+ console.log("HUB: Starting websocket connection.");
44
+ return new HubWebSocket();
32
45
  }
33
46
  }
34
47
 
@@ -37,5 +50,6 @@ export function createHub(): Hub {
37
50
  // application.
38
51
  export {HubBase as Hub} from './HubBase';
39
52
  export {HubTauri}
40
- export {HubSocketIo}
53
+ export {HubWebSocket}
41
54
  export {HubSimulate}
55
+ export type {CommandMessage, CommandMessageResult}
@@ -1,101 +0,0 @@
1
- import { HubBase } from './HubBase';
2
- /**
3
- * Hub for integrating with a webserver/backend using Socket.IO as the pipeline.
4
- *
5
- * The socket-io connection is expected to broadcast messages on the event
6
- * name: 'autocore://broadcast_event'
7
- *
8
- * This hub will capture those messages and dispatch them globally to any
9
- * listeners in the web app.
10
- *
11
- *
12
- * Example: Listen to an event 'xarm-position':
13
- * ```
14
- * const {subscribe, unsubscribe} = useContext(EventEmitterContext);
15
- * useEffect(() => {
16
- * const unsubscripeMp = subscribe('xarm-position', (value) => {
17
- * // The rust backend sent a JSON object of 3D position values.
18
- * setX(value.x);
19
- * setY(value.y);
20
- * setZ(value.z);
21
- * setA(value.roll);
22
- * setB(value.yaw);
23
- * setC(value.pitch);
24
- * });
25
- *
26
- * return () => {
27
- * unsubscribe(unsubscripeMp);
28
- * }
29
- *
30
- * }, [] );
31
- *
32
- * ```
33
- *
34
- * The hub should also be used for invoking events in the Tauri backend.
35
- * This example will call the function "update_count" in the rust backend, passing
36
- * the expected argument "count".
37
- * ```
38
- * const {invoke} = useContext(EventEmitterContext);
39
- * const incrementCount = () => {
40
- * count += 1;
41
- * invoke('update_count', {"count": count});
42
- * };
43
- * ```
44
- *
45
- * Of course, like any class derived from HubBase, the hub can be used to publish and subscribe to
46
- * topics in the front-end, without need of interacting with the Tauri backed.
47
- *
48
- * ```
49
- * const {dispatch, subscribe, unsubscribe} = useContext(EventEmitterContext);
50
- * const [controlPower, setControlPower] = useState(false);
51
- * useEffect(() => {
52
- * const unsubscribeControlPower = subscribe('value-simulator-bBit1', (value) => {
53
- * setControlPower(value);
54
- * });
55
- *
56
- *
57
- * return () => {
58
- * unsubscribe(unsubscribeControlPower);
59
- * }
60
- * }, [] );
61
- *
62
- * const onPbPressed = () => {
63
- * let count = 1;
64
- * dispatch({
65
- * topic: "my-awesome-topic",
66
- * payload: count
67
- * });
68
- * }
69
- *
70
- * ```
71
- *
72
- */
73
- export declare class HubSocketIo extends HubBase {
74
- private socket;
75
- /**
76
- * Constructor
77
- */
78
- constructor();
79
- /**
80
- * Invoke a method in the Socket.IO backend and wait for acknowledgment.
81
- *
82
- * @param fname method name
83
- * @param payload Optional data payload
84
- * @param timeout Timeout in milliseconds after which the promise is rejected if no response is received.
85
- * @returns A Promise that resolves to the response from the backend or rejects if a timeout occurs.
86
- */
87
- invoke(fname: string, payload?: object, timeout?: number): Promise<object>;
88
- /**
89
- * Disconnects the Socket.IO client.
90
- */
91
- disconnect(): void;
92
- /**
93
- * Emit an event to the Socket.IO server.
94
- * The intention is that the invoke method is used instead.
95
- *
96
- * @param eventName Name of the event to emit
97
- * @param payload Optional data payload
98
- */
99
- protected emit(eventName: string, payload?: object): void;
100
- }
101
- export default HubSocketIo;
@@ -1 +0,0 @@
1
- import{HubBase}from"./HubBase";import io from"socket.io-client";export class HubSocketIo extends HubBase{constructor(){super(),Object.defineProperty(this,"socket",{enumerable:!0,configurable:!0,writable:!0,value:void 0});const e=window.location.origin;this.socket=io(e),this.socket.on("autocore://broadcast_event",(e=>{let o=JSON.parse(e.payload);void 0!==o.topic&&null!==o.topic&&(void 0!==o.payload&&null!==o.payload?this.publish(o.topic,o.payload):this.publish(o.topic,void 0))}))}invoke(e,o,t=5e3){return new Promise(((i,s)=>{let c=!1;this.socket.emit(e,o,(e=>{c=!0,i(e)})),setTimeout((()=>{c||s(new Error(`Timeout: No response received within ${t} ms`))}),t)}))}disconnect(){this.socket.disconnect()}emit(e,o){this.socket.emit(e,o)}}export default HubSocketIo;
@@ -1,166 +0,0 @@
1
- /*
2
- * Copyright (C) 2023 Automated Design Corp. All Rights Reserved.
3
- * Created Date: 2023-12-17 10:38:21
4
- * Author: Thomas C. Bitsky Jr.
5
- * -----
6
- * Last Modified: 2024-03-08 09:46:20
7
- * Modified By: ADC
8
- * -----
9
- *
10
- */
11
-
12
- import { HubBase } from './HubBase';
13
- import io, { Socket } from 'socket.io-client';
14
-
15
- /**
16
- * Hub for integrating with a webserver/backend using Socket.IO as the pipeline.
17
- *
18
- * The socket-io connection is expected to broadcast messages on the event
19
- * name: 'autocore://broadcast_event'
20
- *
21
- * This hub will capture those messages and dispatch them globally to any
22
- * listeners in the web app.
23
- *
24
- *
25
- * Example: Listen to an event 'xarm-position':
26
- * ```
27
- * const {subscribe, unsubscribe} = useContext(EventEmitterContext);
28
- * useEffect(() => {
29
- * const unsubscripeMp = subscribe('xarm-position', (value) => {
30
- * // The rust backend sent a JSON object of 3D position values.
31
- * setX(value.x);
32
- * setY(value.y);
33
- * setZ(value.z);
34
- * setA(value.roll);
35
- * setB(value.yaw);
36
- * setC(value.pitch);
37
- * });
38
- *
39
- * return () => {
40
- * unsubscribe(unsubscripeMp);
41
- * }
42
- *
43
- * }, [] );
44
- *
45
- * ```
46
- *
47
- * The hub should also be used for invoking events in the Tauri backend.
48
- * This example will call the function "update_count" in the rust backend, passing
49
- * the expected argument "count".
50
- * ```
51
- * const {invoke} = useContext(EventEmitterContext);
52
- * const incrementCount = () => {
53
- * count += 1;
54
- * invoke('update_count', {"count": count});
55
- * };
56
- * ```
57
- *
58
- * Of course, like any class derived from HubBase, the hub can be used to publish and subscribe to
59
- * topics in the front-end, without need of interacting with the Tauri backed.
60
- *
61
- * ```
62
- * const {dispatch, subscribe, unsubscribe} = useContext(EventEmitterContext);
63
- * const [controlPower, setControlPower] = useState(false);
64
- * useEffect(() => {
65
- * const unsubscribeControlPower = subscribe('value-simulator-bBit1', (value) => {
66
- * setControlPower(value);
67
- * });
68
- *
69
- *
70
- * return () => {
71
- * unsubscribe(unsubscribeControlPower);
72
- * }
73
- * }, [] );
74
- *
75
- * const onPbPressed = () => {
76
- * let count = 1;
77
- * dispatch({
78
- * topic: "my-awesome-topic",
79
- * payload: count
80
- * });
81
- * }
82
- *
83
- * ```
84
- *
85
- */
86
- export class HubSocketIo extends HubBase {
87
- private socket: Socket;
88
-
89
- /**
90
- * Constructor
91
- */
92
- constructor() {
93
- super();
94
-
95
- // Initialize the Socket.IO client to connect to the same domain and protocol
96
- const socketUrl = window.location.origin;
97
-
98
- this.socket = io(socketUrl);
99
-
100
- // Listen for a custom event from the backend
101
- this.socket.on('autocore://broadcast_event', (ev: any) => {
102
- let objPayload = JSON.parse(ev.payload);
103
-
104
- if (objPayload.topic !== undefined && objPayload.topic !== null ) {
105
-
106
- if ( objPayload.payload !== undefined && objPayload.payload !== null ) {
107
- this.publish(objPayload.topic, objPayload.payload);
108
- }
109
- else {
110
- this.publish(objPayload.topic, undefined);
111
- }
112
-
113
- }
114
- });
115
-
116
- }
117
-
118
- /**
119
- * Invoke a method in the Socket.IO backend and wait for acknowledgment.
120
- *
121
- * @param fname method name
122
- * @param payload Optional data payload
123
- * @param timeout Timeout in milliseconds after which the promise is rejected if no response is received.
124
- * @returns A Promise that resolves to the response from the backend or rejects if a timeout occurs.
125
- */
126
- invoke(fname: string, payload?: object, timeout: number = 5000): Promise<object> {
127
- return new Promise((resolve, reject) => {
128
- // Flag to track if the response was received
129
- let responseReceived = false;
130
-
131
- this.socket.emit(fname, payload, (response: object) => {
132
- responseReceived = true;
133
- resolve(response);
134
- });
135
-
136
- // Set a timeout to reject the promise if no response is received
137
- setTimeout(() => {
138
- if (!responseReceived) {
139
- reject(new Error(`Timeout: No response received within ${timeout} ms`));
140
- }
141
- }, timeout);
142
- });
143
- }
144
-
145
- /**
146
- * Disconnects the Socket.IO client.
147
- */
148
- disconnect(): void {
149
- this.socket.disconnect();
150
- }
151
-
152
-
153
- /**
154
- * Emit an event to the Socket.IO server.
155
- * The intention is that the invoke method is used instead.
156
- *
157
- * @param eventName Name of the event to emit
158
- * @param payload Optional data payload
159
- */
160
- protected emit(eventName: string, payload?: object): void {
161
- this.socket.emit(eventName, payload);
162
- }
163
-
164
- }
165
-
166
- export default HubSocketIo;