@reactor-team/js-sdk 2.3.2 → 2.4.0

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
@@ -31,7 +31,22 @@ interface ConnectOptions {
31
31
  /** Maximum number of SDP polling attempts before giving up. Default: 6. */
32
32
  maxAttempts?: number;
33
33
  }
34
- type ReactorEvent = "statusChanged" | "sessionIdChanged" | "newMessage" | "streamChanged" | "error" | "sessionExpirationChanged";
34
+ interface ConnectionStats {
35
+ /** ICE candidate-pair round-trip time in milliseconds */
36
+ rtt?: number;
37
+ /** ICE candidate type: "host", "srflx", "prflx", or "relay" (TURN) */
38
+ candidateType?: string;
39
+ /** Estimated available outgoing bitrate in bits/second */
40
+ availableOutgoingBitrate?: number;
41
+ /** Received video frames per second */
42
+ framesPerSecond?: number;
43
+ /** Ratio of packets lost (0-1) */
44
+ packetLossRatio?: number;
45
+ /** Network jitter in seconds (from inbound-rtp) */
46
+ jitter?: number;
47
+ timestamp: number;
48
+ }
49
+ type ReactorEvent = "statusChanged" | "sessionIdChanged" | "message" | "runtimeMessage" | "streamChanged" | "error" | "sessionExpirationChanged" | "statsUpdate";
35
50
 
36
51
  declare const PROD_COORDINATOR_URL = "https://api.reactor.inc";
37
52
  declare const OptionsSchema: z.ZodObject<{
@@ -114,6 +129,7 @@ declare class Reactor {
114
129
  * Get the last error that occurred
115
130
  */
116
131
  getLastError(): ReactorError | undefined;
132
+ getStats(): ConnectionStats | undefined;
117
133
  /**
118
134
  * Create and store an error
119
135
  */
@@ -195,15 +211,30 @@ declare function WebcamStream({ className, style, videoConstraints, showWebcam,
195
211
  */
196
212
  declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
197
213
  /**
198
- * Hook for handling message subscriptions with proper React lifecycle management.
214
+ * Hook for receiving model application messages.
215
+ *
216
+ * Only fires for messages sent by the model via `get_ctx().send()`.
217
+ * Internal platform-level messages (e.g. capabilities) are NOT delivered here.
218
+ *
219
+ * @param handler - Callback invoked with each application message payload.
220
+ */
221
+ declare function useReactorMessage(handler: (message: any) => void): void;
222
+ /**
223
+ * Hook for receiving internal platform-level (runtime) messages.
199
224
  *
200
- * The handler receives the message payload and the scope it arrived on:
201
- * - "application" for model-defined messages (via get_ctx().send())
202
- * - "runtime" for platform-level messages (e.g., capabilities response)
225
+ * This is intended for advanced use cases that need access to the runtime
226
+ * control layer, such as capabilities negotiation. Model application messages
227
+ * sent via `get_ctx().send()` are NOT delivered through this hook — use
228
+ * {@link useReactorMessage} for those.
203
229
  *
204
- * @param handler - The message handler function (message, scope)
230
+ * @param handler - Callback invoked with each runtime message payload.
231
+ */
232
+ declare function useReactorInternalMessage(handler: (message: any) => void): void;
233
+ /**
234
+ * Hook that returns the current connection stats (RTT, etc.).
235
+ * Updates every ~2s while connected. Returns undefined when disconnected.
205
236
  */
206
- declare function useReactorMessage(handler: (message: any, scope: MessageScope) => void): void;
237
+ declare function useStats(): ConnectionStats | undefined;
207
238
 
208
239
  /**
209
240
  * ⚠️ INSECURE: Fetches a JWT token directly from the client.
@@ -218,4 +249,4 @@ declare function useReactorMessage(handler: (message: any, scope: MessageScope)
218
249
  */
219
250
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
220
251
 
221
- export { ConflictError, type ConnectOptions, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
252
+ export { ConflictError, type ConnectOptions, type ConnectionStats, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorInternalMessage, useReactorMessage, useReactorStore, useStats };
package/dist/index.d.ts CHANGED
@@ -31,7 +31,22 @@ interface ConnectOptions {
31
31
  /** Maximum number of SDP polling attempts before giving up. Default: 6. */
32
32
  maxAttempts?: number;
33
33
  }
34
- type ReactorEvent = "statusChanged" | "sessionIdChanged" | "newMessage" | "streamChanged" | "error" | "sessionExpirationChanged";
34
+ interface ConnectionStats {
35
+ /** ICE candidate-pair round-trip time in milliseconds */
36
+ rtt?: number;
37
+ /** ICE candidate type: "host", "srflx", "prflx", or "relay" (TURN) */
38
+ candidateType?: string;
39
+ /** Estimated available outgoing bitrate in bits/second */
40
+ availableOutgoingBitrate?: number;
41
+ /** Received video frames per second */
42
+ framesPerSecond?: number;
43
+ /** Ratio of packets lost (0-1) */
44
+ packetLossRatio?: number;
45
+ /** Network jitter in seconds (from inbound-rtp) */
46
+ jitter?: number;
47
+ timestamp: number;
48
+ }
49
+ type ReactorEvent = "statusChanged" | "sessionIdChanged" | "message" | "runtimeMessage" | "streamChanged" | "error" | "sessionExpirationChanged" | "statsUpdate";
35
50
 
36
51
  declare const PROD_COORDINATOR_URL = "https://api.reactor.inc";
37
52
  declare const OptionsSchema: z.ZodObject<{
@@ -114,6 +129,7 @@ declare class Reactor {
114
129
  * Get the last error that occurred
115
130
  */
116
131
  getLastError(): ReactorError | undefined;
132
+ getStats(): ConnectionStats | undefined;
117
133
  /**
118
134
  * Create and store an error
119
135
  */
@@ -195,15 +211,30 @@ declare function WebcamStream({ className, style, videoConstraints, showWebcam,
195
211
  */
196
212
  declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
197
213
  /**
198
- * Hook for handling message subscriptions with proper React lifecycle management.
214
+ * Hook for receiving model application messages.
215
+ *
216
+ * Only fires for messages sent by the model via `get_ctx().send()`.
217
+ * Internal platform-level messages (e.g. capabilities) are NOT delivered here.
218
+ *
219
+ * @param handler - Callback invoked with each application message payload.
220
+ */
221
+ declare function useReactorMessage(handler: (message: any) => void): void;
222
+ /**
223
+ * Hook for receiving internal platform-level (runtime) messages.
199
224
  *
200
- * The handler receives the message payload and the scope it arrived on:
201
- * - "application" for model-defined messages (via get_ctx().send())
202
- * - "runtime" for platform-level messages (e.g., capabilities response)
225
+ * This is intended for advanced use cases that need access to the runtime
226
+ * control layer, such as capabilities negotiation. Model application messages
227
+ * sent via `get_ctx().send()` are NOT delivered through this hook — use
228
+ * {@link useReactorMessage} for those.
203
229
  *
204
- * @param handler - The message handler function (message, scope)
230
+ * @param handler - Callback invoked with each runtime message payload.
231
+ */
232
+ declare function useReactorInternalMessage(handler: (message: any) => void): void;
233
+ /**
234
+ * Hook that returns the current connection stats (RTT, etc.).
235
+ * Updates every ~2s while connected. Returns undefined when disconnected.
205
236
  */
206
- declare function useReactorMessage(handler: (message: any, scope: MessageScope) => void): void;
237
+ declare function useStats(): ConnectionStats | undefined;
207
238
 
208
239
  /**
209
240
  * ⚠️ INSECURE: Fetches a JWT token directly from the client.
@@ -218,4 +249,4 @@ declare function useReactorMessage(handler: (message: any, scope: MessageScope)
218
249
  */
219
250
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
220
251
 
221
- export { ConflictError, type ConnectOptions, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorMessage, useReactorStore };
252
+ export { ConflictError, type ConnectOptions, type ConnectionStats, type MessageScope, type Options, PROD_COORDINATOR_URL, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, WebcamStream, fetchInsecureJwtToken, useReactor, useReactorInternalMessage, useReactorMessage, useReactorStore, useStats };
package/dist/index.js CHANGED
@@ -88,8 +88,10 @@ __export(index_exports, {
88
88
  WebcamStream: () => WebcamStream,
89
89
  fetchInsecureJwtToken: () => fetchInsecureJwtToken,
90
90
  useReactor: () => useReactor,
91
+ useReactorInternalMessage: () => useReactorInternalMessage,
91
92
  useReactorMessage: () => useReactorMessage,
92
- useReactorStore: () => useReactorStore
93
+ useReactorStore: () => useReactorStore,
94
+ useStats: () => useStats
93
95
  });
94
96
  module.exports = __toCommonJS(index_exports);
95
97
 
@@ -249,6 +251,52 @@ function parseMessage(data) {
249
251
  function closePeerConnection(pc) {
250
252
  pc.close();
251
253
  }
254
+ function extractConnectionStats(report) {
255
+ let rtt;
256
+ let availableOutgoingBitrate;
257
+ let localCandidateId;
258
+ let framesPerSecond;
259
+ let jitter;
260
+ let packetLossRatio;
261
+ report.forEach((stat) => {
262
+ if (stat.type === "candidate-pair" && stat.state === "succeeded") {
263
+ if (stat.currentRoundTripTime !== void 0) {
264
+ rtt = stat.currentRoundTripTime * 1e3;
265
+ }
266
+ if (stat.availableOutgoingBitrate !== void 0) {
267
+ availableOutgoingBitrate = stat.availableOutgoingBitrate;
268
+ }
269
+ localCandidateId = stat.localCandidateId;
270
+ }
271
+ if (stat.type === "inbound-rtp" && stat.kind === "video") {
272
+ if (stat.framesPerSecond !== void 0) {
273
+ framesPerSecond = stat.framesPerSecond;
274
+ }
275
+ if (stat.jitter !== void 0) {
276
+ jitter = stat.jitter;
277
+ }
278
+ if (stat.packetsReceived !== void 0 && stat.packetsLost !== void 0 && stat.packetsReceived + stat.packetsLost > 0) {
279
+ packetLossRatio = stat.packetsLost / (stat.packetsReceived + stat.packetsLost);
280
+ }
281
+ }
282
+ });
283
+ let candidateType;
284
+ if (localCandidateId) {
285
+ const localCandidate = report.get(localCandidateId);
286
+ if (localCandidate == null ? void 0 : localCandidate.candidateType) {
287
+ candidateType = localCandidate.candidateType;
288
+ }
289
+ }
290
+ return {
291
+ rtt,
292
+ candidateType,
293
+ availableOutgoingBitrate,
294
+ framesPerSecond,
295
+ packetLossRatio,
296
+ jitter,
297
+ timestamp: Date.now()
298
+ };
299
+ }
252
300
 
253
301
  // src/core/CoordinatorClient.ts
254
302
  var INITIAL_BACKOFF_MS = 500;
@@ -624,6 +672,7 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
624
672
 
625
673
  // src/core/GPUMachineClient.ts
626
674
  var PING_INTERVAL_MS = 5e3;
675
+ var STATS_INTERVAL_MS = 2e3;
627
676
  var GPUMachineClient = class {
628
677
  constructor(config) {
629
678
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -707,6 +756,7 @@ var GPUMachineClient = class {
707
756
  disconnect() {
708
757
  return __async(this, null, function* () {
709
758
  this.stopPing();
759
+ this.stopStatsPolling();
710
760
  if (this.publishedTrack) {
711
761
  yield this.unpublishTrack();
712
762
  }
@@ -863,6 +913,31 @@ var GPUMachineClient = class {
863
913
  }
864
914
  }
865
915
  // ─────────────────────────────────────────────────────────────────────────────
916
+ // Stats Polling (RTT)
917
+ // ─────────────────────────────────────────────────────────────────────────────
918
+ getStats() {
919
+ return this.stats;
920
+ }
921
+ startStatsPolling() {
922
+ this.stopStatsPolling();
923
+ this.statsInterval = setInterval(() => __async(this, null, function* () {
924
+ if (!this.peerConnection) return;
925
+ try {
926
+ const report = yield this.peerConnection.getStats();
927
+ this.stats = extractConnectionStats(report);
928
+ this.emit("statsUpdate", this.stats);
929
+ } catch (e) {
930
+ }
931
+ }), STATS_INTERVAL_MS);
932
+ }
933
+ stopStatsPolling() {
934
+ if (this.statsInterval !== void 0) {
935
+ clearInterval(this.statsInterval);
936
+ this.statsInterval = void 0;
937
+ }
938
+ this.stats = void 0;
939
+ }
940
+ // ─────────────────────────────────────────────────────────────────────────────
866
941
  // Private Helpers
867
942
  // ─────────────────────────────────────────────────────────────────────────────
868
943
  setStatus(newStatus) {
@@ -881,6 +956,7 @@ var GPUMachineClient = class {
881
956
  switch (state) {
882
957
  case "connected":
883
958
  this.setStatus("connected");
959
+ this.startStatsPolling();
884
960
  break;
885
961
  case "disconnected":
886
962
  case "closed":
@@ -1170,7 +1246,11 @@ var Reactor = class {
1170
1246
  setupMachineClientHandlers() {
1171
1247
  if (!this.machineClient) return;
1172
1248
  this.machineClient.on("message", (message, scope) => {
1173
- this.emit("newMessage", message, scope);
1249
+ if (scope === "application") {
1250
+ this.emit("message", message);
1251
+ } else if (scope === "runtime") {
1252
+ this.emit("runtimeMessage", message);
1253
+ }
1174
1254
  });
1175
1255
  this.machineClient.on("statusChanged", (status) => {
1176
1256
  switch (status) {
@@ -1197,6 +1277,9 @@ var Reactor = class {
1197
1277
  this.emit("streamChanged", track, stream);
1198
1278
  }
1199
1279
  );
1280
+ this.machineClient.on("statsUpdate", (stats) => {
1281
+ this.emit("statsUpdate", stats);
1282
+ });
1200
1283
  }
1201
1284
  /**
1202
1285
  * Disconnects from the coordinator and the gpu machine.
@@ -1283,6 +1366,10 @@ var Reactor = class {
1283
1366
  getLastError() {
1284
1367
  return this.lastError;
1285
1368
  }
1369
+ getStats() {
1370
+ var _a;
1371
+ return (_a = this.machineClient) == null ? void 0 : _a.getStats();
1372
+ }
1286
1373
  /**
1287
1374
  * Create and store an error
1288
1375
  */
@@ -1371,10 +1458,10 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
1371
1458
  // actions
1372
1459
  onMessage: (handler) => {
1373
1460
  console.debug("[ReactorStore] Registering message handler");
1374
- get().internal.reactor.on("newMessage", handler);
1461
+ get().internal.reactor.on("message", handler);
1375
1462
  return () => {
1376
1463
  console.debug("[ReactorStore] Cleaning up message handler");
1377
- get().internal.reactor.off("newMessage", handler);
1464
+ get().internal.reactor.off("message", handler);
1378
1465
  };
1379
1466
  },
1380
1467
  sendCommand: (command, data, scope) => __async(null, null, function* () {
@@ -1588,21 +1675,45 @@ function useReactorMessage(handler) {
1588
1675
  handlerRef.current = handler;
1589
1676
  }, [handler]);
1590
1677
  (0, import_react4.useEffect)(() => {
1591
- console.debug("[useReactorMessage] Setting up message subscription");
1592
- const stableHandler = (message, scope) => {
1593
- console.debug("[useReactorMessage] Message received", {
1594
- message,
1595
- scope
1596
- });
1597
- handlerRef.current(message, scope);
1678
+ const stableHandler = (message) => {
1679
+ handlerRef.current(message);
1680
+ };
1681
+ reactor.on("message", stableHandler);
1682
+ return () => {
1683
+ reactor.off("message", stableHandler);
1684
+ };
1685
+ }, [reactor]);
1686
+ }
1687
+ function useReactorInternalMessage(handler) {
1688
+ const reactor = useReactor((state) => state.internal.reactor);
1689
+ const handlerRef = (0, import_react4.useRef)(handler);
1690
+ (0, import_react4.useEffect)(() => {
1691
+ handlerRef.current = handler;
1692
+ }, [handler]);
1693
+ (0, import_react4.useEffect)(() => {
1694
+ const stableHandler = (message) => {
1695
+ handlerRef.current(message);
1696
+ };
1697
+ reactor.on("runtimeMessage", stableHandler);
1698
+ return () => {
1699
+ reactor.off("runtimeMessage", stableHandler);
1700
+ };
1701
+ }, [reactor]);
1702
+ }
1703
+ function useStats() {
1704
+ const reactor = useReactor((state) => state.internal.reactor);
1705
+ const [stats, setStats] = (0, import_react4.useState)(void 0);
1706
+ (0, import_react4.useEffect)(() => {
1707
+ const handler = (newStats) => {
1708
+ setStats(newStats);
1598
1709
  };
1599
- reactor.on("newMessage", stableHandler);
1600
- console.debug("[useReactorMessage] Message handler registered");
1710
+ reactor.on("statsUpdate", handler);
1601
1711
  return () => {
1602
- console.debug("[useReactorMessage] Cleaning up message subscription");
1603
- reactor.off("newMessage", stableHandler);
1712
+ reactor.off("statsUpdate", handler);
1713
+ setStats(void 0);
1604
1714
  };
1605
1715
  }, [reactor]);
1716
+ return stats;
1606
1717
  }
1607
1718
 
1608
1719
  // src/react/ReactorView.tsx
@@ -1740,8 +1851,8 @@ function ReactorController({
1740
1851
  }, 5e3);
1741
1852
  return () => clearInterval(interval);
1742
1853
  }, [status, commands, requestCapabilities]);
1743
- useReactorMessage((message, scope) => {
1744
- if (scope === "runtime" && message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
1854
+ useReactorInternalMessage((message) => {
1855
+ if (message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
1745
1856
  const commandsMessage = message.data;
1746
1857
  setCommands(commandsMessage.commands);
1747
1858
  const initialValues = {};
@@ -2374,7 +2485,9 @@ function fetchInsecureJwtToken(_0) {
2374
2485
  WebcamStream,
2375
2486
  fetchInsecureJwtToken,
2376
2487
  useReactor,
2488
+ useReactorInternalMessage,
2377
2489
  useReactorMessage,
2378
- useReactorStore
2490
+ useReactorStore,
2491
+ useStats
2379
2492
  });
2380
2493
  //# sourceMappingURL=index.js.map