@reactor-team/js-sdk 2.2.2 → 2.3.1

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/dist/index.d.mts CHANGED
@@ -3,6 +3,12 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React, { ReactNode } from 'react';
4
4
 
5
5
  type ReactorStatus = "disconnected" | "connecting" | "waiting" | "ready";
6
+ /**
7
+ * The message scope identifies the envelope layer a data channel message belongs to.
8
+ * - "application": model-defined commands (client->runtime) and model-emitted payloads (runtime->client).
9
+ * - "runtime": platform-level control messages (e.g., capabilities exchange).
10
+ */
11
+ type MessageScope = "application" | "runtime";
6
12
  interface ReactorError {
7
13
  code: string;
8
14
  message: string;
@@ -45,11 +51,14 @@ declare class Reactor {
45
51
  emit(event: ReactorEvent, ...args: any[]): void;
46
52
  /**
47
53
  * Public method to send a message to the machine.
48
- * Automatically wraps the message in an application message.
49
- * @param message The message to send to the machine.
54
+ * Wraps the message in the specified channel envelope (defaults to "application").
55
+ * @param command The command name to send.
56
+ * @param data The command payload.
57
+ * @param scope The envelope scope – "application" (default) for model commands,
58
+ * "runtime" for platform-level messages (e.g. requestCapabilities).
50
59
  * @throws Error if not in ready state
51
60
  */
52
- sendCommand(command: string, data: any): Promise<void>;
61
+ sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
53
62
  /**
54
63
  * Public method to publish a track to the machine.
55
64
  * @param track The track to send to the machine.
@@ -111,7 +120,7 @@ interface ReactorState {
111
120
  jwtToken?: string;
112
121
  }
113
122
  interface ReactorActions {
114
- sendCommand(command: string, data: any): Promise<void>;
123
+ sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
115
124
  connect(jwtToken?: string): Promise<void>;
116
125
  disconnect(recoverable?: boolean): Promise<void>;
117
126
  publishVideoStream(stream: MediaStream): Promise<void>;
@@ -170,9 +179,13 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
170
179
  /**
171
180
  * Hook for handling message subscriptions with proper React lifecycle management.
172
181
  *
173
- * @param handler - The message handler function
182
+ * The handler receives the message payload and the scope it arrived on:
183
+ * - "application" for model-defined messages (via get_ctx().send())
184
+ * - "runtime" for platform-level messages (e.g., capabilities response)
185
+ *
186
+ * @param handler - The message handler function (message, scope)
174
187
  */
175
- declare function useReactorMessage(handler: (message: any) => void): void;
188
+ declare function useReactorMessage(handler: (message: any, scope: MessageScope) => void): void;
176
189
 
177
190
  /**
178
191
  * ⚠️ INSECURE: Fetches a JWT token directly from the client.
@@ -187,4 +200,4 @@ declare function useReactorMessage(handler: (message: any) => void): void;
187
200
  */
188
201
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
189
202
 
190
- export { ConflictError, type Options, PROD_COORDINATOR_URL, Reactor, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
203
+ export { ConflictError, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,12 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React, { ReactNode } from 'react';
4
4
 
5
5
  type ReactorStatus = "disconnected" | "connecting" | "waiting" | "ready";
6
+ /**
7
+ * The message scope identifies the envelope layer a data channel message belongs to.
8
+ * - "application": model-defined commands (client->runtime) and model-emitted payloads (runtime->client).
9
+ * - "runtime": platform-level control messages (e.g., capabilities exchange).
10
+ */
11
+ type MessageScope = "application" | "runtime";
6
12
  interface ReactorError {
7
13
  code: string;
8
14
  message: string;
@@ -45,11 +51,14 @@ declare class Reactor {
45
51
  emit(event: ReactorEvent, ...args: any[]): void;
46
52
  /**
47
53
  * Public method to send a message to the machine.
48
- * Automatically wraps the message in an application message.
49
- * @param message The message to send to the machine.
54
+ * Wraps the message in the specified channel envelope (defaults to "application").
55
+ * @param command The command name to send.
56
+ * @param data The command payload.
57
+ * @param scope The envelope scope – "application" (default) for model commands,
58
+ * "runtime" for platform-level messages (e.g. requestCapabilities).
50
59
  * @throws Error if not in ready state
51
60
  */
52
- sendCommand(command: string, data: any): Promise<void>;
61
+ sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
53
62
  /**
54
63
  * Public method to publish a track to the machine.
55
64
  * @param track The track to send to the machine.
@@ -111,7 +120,7 @@ interface ReactorState {
111
120
  jwtToken?: string;
112
121
  }
113
122
  interface ReactorActions {
114
- sendCommand(command: string, data: any): Promise<void>;
123
+ sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
115
124
  connect(jwtToken?: string): Promise<void>;
116
125
  disconnect(recoverable?: boolean): Promise<void>;
117
126
  publishVideoStream(stream: MediaStream): Promise<void>;
@@ -170,9 +179,13 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
170
179
  /**
171
180
  * Hook for handling message subscriptions with proper React lifecycle management.
172
181
  *
173
- * @param handler - The message handler function
182
+ * The handler receives the message payload and the scope it arrived on:
183
+ * - "application" for model-defined messages (via get_ctx().send())
184
+ * - "runtime" for platform-level messages (e.g., capabilities response)
185
+ *
186
+ * @param handler - The message handler function (message, scope)
174
187
  */
175
- declare function useReactorMessage(handler: (message: any) => void): void;
188
+ declare function useReactorMessage(handler: (message: any, scope: MessageScope) => void): void;
176
189
 
177
190
  /**
178
191
  * ⚠️ INSECURE: Fetches a JWT token directly from the client.
@@ -187,4 +200,4 @@ declare function useReactorMessage(handler: (message: any) => void): void;
187
200
  */
188
201
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
189
202
 
190
- export { ConflictError, type Options, PROD_COORDINATOR_URL, Reactor, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
203
+ export { ConflictError, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
package/dist/index.js CHANGED
@@ -227,12 +227,13 @@ function waitForIceGathering(pc, timeoutMs = 5e3) {
227
227
  }, timeoutMs);
228
228
  });
229
229
  }
230
- function sendMessage(channel, command, data) {
230
+ function sendMessage(channel, command, data, scope = "application") {
231
231
  if (channel.readyState !== "open") {
232
232
  throw new Error(`Data channel not open: ${channel.readyState}`);
233
233
  }
234
234
  const jsonData = typeof data === "string" ? JSON.parse(data) : data;
235
- const payload = { type: command, data: jsonData };
235
+ const inner = { type: command, data: jsonData };
236
+ const payload = { scope, data: inner };
236
237
  channel.send(JSON.stringify(payload));
237
238
  }
238
239
  function parseMessage(data) {
@@ -614,6 +615,7 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
614
615
  };
615
616
 
616
617
  // src/core/GPUMachineClient.ts
618
+ var PING_INTERVAL_MS = 5e3;
617
619
  var GPUMachineClient = class {
618
620
  constructor(config) {
619
621
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -696,6 +698,7 @@ var GPUMachineClient = class {
696
698
  */
697
699
  disconnect() {
698
700
  return __async(this, null, function* () {
701
+ this.stopPing();
699
702
  if (this.publishedTrack) {
700
703
  yield this.unpublishTrack();
701
704
  }
@@ -736,13 +739,14 @@ var GPUMachineClient = class {
736
739
  * Sends a command to the GPU machine via the data channel.
737
740
  * @param command The command to send
738
741
  * @param data The data to send with the command. These are the parameters for the command, matching the scheme in the capabilities dictionary.
742
+ * @param scope The message scope – "application" (default) for model commands, "runtime" for platform-level messages.
739
743
  */
740
- sendCommand(command, data) {
744
+ sendCommand(command, data, scope = "application") {
741
745
  if (!this.dataChannel) {
742
746
  throw new Error("[GPUMachineClient] Data channel not available");
743
747
  }
744
748
  try {
745
- sendMessage(this.dataChannel, command, data);
749
+ sendMessage(this.dataChannel, command, data, scope);
746
750
  } catch (error) {
747
751
  console.warn("[GPUMachineClient] Failed to send message:", error);
748
752
  }
@@ -823,6 +827,34 @@ var GPUMachineClient = class {
823
827
  return new MediaStream(tracks);
824
828
  }
825
829
  // ─────────────────────────────────────────────────────────────────────────────
830
+ // Ping (Client Liveness)
831
+ // ─────────────────────────────────────────────────────────────────────────────
832
+ /**
833
+ * Starts sending periodic "ping" messages on the runtime channel so the
834
+ * server can detect stale connections quickly.
835
+ */
836
+ startPing() {
837
+ this.stopPing();
838
+ this.pingInterval = setInterval(() => {
839
+ var _a;
840
+ if (((_a = this.dataChannel) == null ? void 0 : _a.readyState) === "open") {
841
+ try {
842
+ sendMessage(this.dataChannel, "ping", {}, "runtime");
843
+ } catch (e) {
844
+ }
845
+ }
846
+ }, PING_INTERVAL_MS);
847
+ }
848
+ /**
849
+ * Stops the periodic ping.
850
+ */
851
+ stopPing() {
852
+ if (this.pingInterval !== void 0) {
853
+ clearInterval(this.pingInterval);
854
+ this.pingInterval = void 0;
855
+ }
856
+ }
857
+ // ─────────────────────────────────────────────────────────────────────────────
826
858
  // Private Helpers
827
859
  // ─────────────────────────────────────────────────────────────────────────────
828
860
  setStatus(newStatus) {
@@ -876,18 +908,29 @@ var GPUMachineClient = class {
876
908
  if (!this.dataChannel) return;
877
909
  this.dataChannel.onopen = () => {
878
910
  console.debug("[GPUMachineClient] Data channel open");
911
+ this.startPing();
879
912
  };
880
913
  this.dataChannel.onclose = () => {
881
914
  console.debug("[GPUMachineClient] Data channel closed");
915
+ this.stopPing();
882
916
  };
883
917
  this.dataChannel.onerror = (error) => {
884
918
  console.error("[GPUMachineClient] Data channel error:", error);
885
919
  };
886
920
  this.dataChannel.onmessage = (event) => {
887
- const data = parseMessage(event.data);
888
- console.debug("[GPUMachineClient] Received message:", data);
921
+ const rawData = parseMessage(event.data);
922
+ console.debug("[GPUMachineClient] Received message:", rawData);
889
923
  try {
890
- this.emit("application", data);
924
+ if ((rawData == null ? void 0 : rawData.scope) === "application" && (rawData == null ? void 0 : rawData.data) !== void 0) {
925
+ this.emit("message", rawData.data, "application");
926
+ } else if ((rawData == null ? void 0 : rawData.scope) === "runtime" && (rawData == null ? void 0 : rawData.data) !== void 0) {
927
+ this.emit("message", rawData.data, "runtime");
928
+ } else {
929
+ console.warn(
930
+ "[GPUMachineClient] Received message without envelope, treating as application"
931
+ );
932
+ this.emit("message", rawData, "application");
933
+ }
891
934
  } catch (error) {
892
935
  console.error(
893
936
  "[GPUMachineClient] Failed to parse/validate message:",
@@ -937,11 +980,14 @@ var Reactor = class {
937
980
  }
938
981
  /**
939
982
  * Public method to send a message to the machine.
940
- * Automatically wraps the message in an application message.
941
- * @param message The message to send to the machine.
983
+ * Wraps the message in the specified channel envelope (defaults to "application").
984
+ * @param command The command name to send.
985
+ * @param data The command payload.
986
+ * @param scope The envelope scope – "application" (default) for model commands,
987
+ * "runtime" for platform-level messages (e.g. requestCapabilities).
942
988
  * @throws Error if not in ready state
943
989
  */
944
- sendCommand(command, data) {
990
+ sendCommand(command, data, scope = "application") {
945
991
  return __async(this, null, function* () {
946
992
  var _a;
947
993
  if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
@@ -950,7 +996,7 @@ var Reactor = class {
950
996
  return;
951
997
  }
952
998
  try {
953
- (_a = this.machineClient) == null ? void 0 : _a.sendCommand(command, data);
999
+ (_a = this.machineClient) == null ? void 0 : _a.sendCommand(command, data, scope);
954
1000
  } catch (error) {
955
1001
  console.error("[Reactor] Failed to send message:", error);
956
1002
  this.createError(
@@ -1100,12 +1146,8 @@ var Reactor = class {
1100
1146
  */
1101
1147
  setupMachineClientHandlers() {
1102
1148
  if (!this.machineClient) return;
1103
- this.machineClient.on("application", (message) => {
1104
- if ((message == null ? void 0 : message.type) === "application" && (message == null ? void 0 : message.data) !== void 0) {
1105
- this.emit("newMessage", message.data);
1106
- } else {
1107
- this.emit("newMessage", message);
1108
- }
1149
+ this.machineClient.on("message", (message, scope) => {
1150
+ this.emit("newMessage", message, scope);
1109
1151
  });
1110
1152
  this.machineClient.on("statusChanged", (status) => {
1111
1153
  switch (status) {
@@ -1312,10 +1354,14 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
1312
1354
  get().internal.reactor.off("newMessage", handler);
1313
1355
  };
1314
1356
  },
1315
- sendCommand: (command, data) => __async(null, null, function* () {
1316
- console.debug("[ReactorStore] Sending command", { command, data });
1357
+ sendCommand: (command, data, scope) => __async(null, null, function* () {
1358
+ console.debug("[ReactorStore] Sending command", {
1359
+ command,
1360
+ data,
1361
+ scope
1362
+ });
1317
1363
  try {
1318
- yield get().internal.reactor.sendCommand(command, data);
1364
+ yield get().internal.reactor.sendCommand(command, data, scope);
1319
1365
  console.debug("[ReactorStore] Command sent successfully");
1320
1366
  } catch (error) {
1321
1367
  console.error("[ReactorStore] Failed to send command:", error);
@@ -1518,9 +1564,12 @@ function useReactorMessage(handler) {
1518
1564
  }, [handler]);
1519
1565
  (0, import_react4.useEffect)(() => {
1520
1566
  console.debug("[useReactorMessage] Setting up message subscription");
1521
- const stableHandler = (message) => {
1522
- console.debug("[useReactorMessage] Message received", { message });
1523
- handlerRef.current(message);
1567
+ const stableHandler = (message, scope) => {
1568
+ console.debug("[useReactorMessage] Message received", {
1569
+ message,
1570
+ scope
1571
+ });
1572
+ handlerRef.current(message, scope);
1524
1573
  };
1525
1574
  reactor.on("newMessage", stableHandler);
1526
1575
  console.debug("[useReactorMessage] Message handler registered");
@@ -1649,7 +1698,7 @@ function ReactorController({
1649
1698
  }, [status]);
1650
1699
  const requestCapabilities = (0, import_react6.useCallback)(() => {
1651
1700
  if (status === "ready") {
1652
- sendCommand("requestCapabilities", {});
1701
+ sendCommand("requestCapabilities", {}, "runtime");
1653
1702
  }
1654
1703
  }, [status, sendCommand]);
1655
1704
  import_react6.default.useEffect(() => {
@@ -1666,9 +1715,9 @@ function ReactorController({
1666
1715
  }, 5e3);
1667
1716
  return () => clearInterval(interval);
1668
1717
  }, [status, commands, requestCapabilities]);
1669
- useReactorMessage((message) => {
1670
- if (message && typeof message === "object" && "commands" in message) {
1671
- const commandsMessage = message;
1718
+ useReactorMessage((message, scope) => {
1719
+ if (scope === "runtime" && message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
1720
+ const commandsMessage = message.data;
1672
1721
  setCommands(commandsMessage.commands);
1673
1722
  const initialValues = {};
1674
1723
  const initialExpanded = {};