@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.mjs CHANGED
@@ -206,6 +206,52 @@ function parseMessage(data) {
206
206
  function closePeerConnection(pc) {
207
207
  pc.close();
208
208
  }
209
+ function extractConnectionStats(report) {
210
+ let rtt;
211
+ let availableOutgoingBitrate;
212
+ let localCandidateId;
213
+ let framesPerSecond;
214
+ let jitter;
215
+ let packetLossRatio;
216
+ report.forEach((stat) => {
217
+ if (stat.type === "candidate-pair" && stat.state === "succeeded") {
218
+ if (stat.currentRoundTripTime !== void 0) {
219
+ rtt = stat.currentRoundTripTime * 1e3;
220
+ }
221
+ if (stat.availableOutgoingBitrate !== void 0) {
222
+ availableOutgoingBitrate = stat.availableOutgoingBitrate;
223
+ }
224
+ localCandidateId = stat.localCandidateId;
225
+ }
226
+ if (stat.type === "inbound-rtp" && stat.kind === "video") {
227
+ if (stat.framesPerSecond !== void 0) {
228
+ framesPerSecond = stat.framesPerSecond;
229
+ }
230
+ if (stat.jitter !== void 0) {
231
+ jitter = stat.jitter;
232
+ }
233
+ if (stat.packetsReceived !== void 0 && stat.packetsLost !== void 0 && stat.packetsReceived + stat.packetsLost > 0) {
234
+ packetLossRatio = stat.packetsLost / (stat.packetsReceived + stat.packetsLost);
235
+ }
236
+ }
237
+ });
238
+ let candidateType;
239
+ if (localCandidateId) {
240
+ const localCandidate = report.get(localCandidateId);
241
+ if (localCandidate == null ? void 0 : localCandidate.candidateType) {
242
+ candidateType = localCandidate.candidateType;
243
+ }
244
+ }
245
+ return {
246
+ rtt,
247
+ candidateType,
248
+ availableOutgoingBitrate,
249
+ framesPerSecond,
250
+ packetLossRatio,
251
+ jitter,
252
+ timestamp: Date.now()
253
+ };
254
+ }
209
255
 
210
256
  // src/core/CoordinatorClient.ts
211
257
  var INITIAL_BACKOFF_MS = 500;
@@ -581,6 +627,7 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
581
627
 
582
628
  // src/core/GPUMachineClient.ts
583
629
  var PING_INTERVAL_MS = 5e3;
630
+ var STATS_INTERVAL_MS = 2e3;
584
631
  var GPUMachineClient = class {
585
632
  constructor(config) {
586
633
  this.eventListeners = /* @__PURE__ */ new Map();
@@ -664,6 +711,7 @@ var GPUMachineClient = class {
664
711
  disconnect() {
665
712
  return __async(this, null, function* () {
666
713
  this.stopPing();
714
+ this.stopStatsPolling();
667
715
  if (this.publishedTrack) {
668
716
  yield this.unpublishTrack();
669
717
  }
@@ -820,6 +868,31 @@ var GPUMachineClient = class {
820
868
  }
821
869
  }
822
870
  // ─────────────────────────────────────────────────────────────────────────────
871
+ // Stats Polling (RTT)
872
+ // ─────────────────────────────────────────────────────────────────────────────
873
+ getStats() {
874
+ return this.stats;
875
+ }
876
+ startStatsPolling() {
877
+ this.stopStatsPolling();
878
+ this.statsInterval = setInterval(() => __async(this, null, function* () {
879
+ if (!this.peerConnection) return;
880
+ try {
881
+ const report = yield this.peerConnection.getStats();
882
+ this.stats = extractConnectionStats(report);
883
+ this.emit("statsUpdate", this.stats);
884
+ } catch (e) {
885
+ }
886
+ }), STATS_INTERVAL_MS);
887
+ }
888
+ stopStatsPolling() {
889
+ if (this.statsInterval !== void 0) {
890
+ clearInterval(this.statsInterval);
891
+ this.statsInterval = void 0;
892
+ }
893
+ this.stats = void 0;
894
+ }
895
+ // ─────────────────────────────────────────────────────────────────────────────
823
896
  // Private Helpers
824
897
  // ─────────────────────────────────────────────────────────────────────────────
825
898
  setStatus(newStatus) {
@@ -838,6 +911,7 @@ var GPUMachineClient = class {
838
911
  switch (state) {
839
912
  case "connected":
840
913
  this.setStatus("connected");
914
+ this.startStatsPolling();
841
915
  break;
842
916
  case "disconnected":
843
917
  case "closed":
@@ -1127,7 +1201,11 @@ var Reactor = class {
1127
1201
  setupMachineClientHandlers() {
1128
1202
  if (!this.machineClient) return;
1129
1203
  this.machineClient.on("message", (message, scope) => {
1130
- this.emit("newMessage", message, scope);
1204
+ if (scope === "application") {
1205
+ this.emit("message", message);
1206
+ } else if (scope === "runtime") {
1207
+ this.emit("runtimeMessage", message);
1208
+ }
1131
1209
  });
1132
1210
  this.machineClient.on("statusChanged", (status) => {
1133
1211
  switch (status) {
@@ -1154,6 +1232,9 @@ var Reactor = class {
1154
1232
  this.emit("streamChanged", track, stream);
1155
1233
  }
1156
1234
  );
1235
+ this.machineClient.on("statsUpdate", (stats) => {
1236
+ this.emit("statsUpdate", stats);
1237
+ });
1157
1238
  }
1158
1239
  /**
1159
1240
  * Disconnects from the coordinator and the gpu machine.
@@ -1240,6 +1321,10 @@ var Reactor = class {
1240
1321
  getLastError() {
1241
1322
  return this.lastError;
1242
1323
  }
1324
+ getStats() {
1325
+ var _a;
1326
+ return (_a = this.machineClient) == null ? void 0 : _a.getStats();
1327
+ }
1243
1328
  /**
1244
1329
  * Create and store an error
1245
1330
  */
@@ -1328,10 +1413,10 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
1328
1413
  // actions
1329
1414
  onMessage: (handler) => {
1330
1415
  console.debug("[ReactorStore] Registering message handler");
1331
- get().internal.reactor.on("newMessage", handler);
1416
+ get().internal.reactor.on("message", handler);
1332
1417
  return () => {
1333
1418
  console.debug("[ReactorStore] Cleaning up message handler");
1334
- get().internal.reactor.off("newMessage", handler);
1419
+ get().internal.reactor.off("message", handler);
1335
1420
  };
1336
1421
  },
1337
1422
  sendCommand: (command, data, scope) => __async(null, null, function* () {
@@ -1534,7 +1619,7 @@ function useReactorStore(selector) {
1534
1619
 
1535
1620
  // src/react/hooks.ts
1536
1621
  import { useShallow } from "zustand/shallow";
1537
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
1622
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1538
1623
  function useReactor(selector) {
1539
1624
  return useReactorStore(useShallow(selector));
1540
1625
  }
@@ -1545,21 +1630,45 @@ function useReactorMessage(handler) {
1545
1630
  handlerRef.current = handler;
1546
1631
  }, [handler]);
1547
1632
  useEffect2(() => {
1548
- console.debug("[useReactorMessage] Setting up message subscription");
1549
- const stableHandler = (message, scope) => {
1550
- console.debug("[useReactorMessage] Message received", {
1551
- message,
1552
- scope
1553
- });
1554
- handlerRef.current(message, scope);
1633
+ const stableHandler = (message) => {
1634
+ handlerRef.current(message);
1635
+ };
1636
+ reactor.on("message", stableHandler);
1637
+ return () => {
1638
+ reactor.off("message", stableHandler);
1639
+ };
1640
+ }, [reactor]);
1641
+ }
1642
+ function useReactorInternalMessage(handler) {
1643
+ const reactor = useReactor((state) => state.internal.reactor);
1644
+ const handlerRef = useRef2(handler);
1645
+ useEffect2(() => {
1646
+ handlerRef.current = handler;
1647
+ }, [handler]);
1648
+ useEffect2(() => {
1649
+ const stableHandler = (message) => {
1650
+ handlerRef.current(message);
1651
+ };
1652
+ reactor.on("runtimeMessage", stableHandler);
1653
+ return () => {
1654
+ reactor.off("runtimeMessage", stableHandler);
1655
+ };
1656
+ }, [reactor]);
1657
+ }
1658
+ function useStats() {
1659
+ const reactor = useReactor((state) => state.internal.reactor);
1660
+ const [stats, setStats] = useState2(void 0);
1661
+ useEffect2(() => {
1662
+ const handler = (newStats) => {
1663
+ setStats(newStats);
1555
1664
  };
1556
- reactor.on("newMessage", stableHandler);
1557
- console.debug("[useReactorMessage] Message handler registered");
1665
+ reactor.on("statsUpdate", handler);
1558
1666
  return () => {
1559
- console.debug("[useReactorMessage] Cleaning up message subscription");
1560
- reactor.off("newMessage", stableHandler);
1667
+ reactor.off("statsUpdate", handler);
1668
+ setStats(void 0);
1561
1669
  };
1562
1670
  }, [reactor]);
1671
+ return stats;
1563
1672
  }
1564
1673
 
1565
1674
  // src/react/ReactorView.tsx
@@ -1658,7 +1767,7 @@ function ReactorView({
1658
1767
  }
1659
1768
 
1660
1769
  // src/react/ReactorController.tsx
1661
- import React, { useState as useState2, useCallback } from "react";
1770
+ import React, { useState as useState3, useCallback } from "react";
1662
1771
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1663
1772
  function ReactorController({
1664
1773
  className,
@@ -1668,9 +1777,9 @@ function ReactorController({
1668
1777
  sendCommand: state.sendCommand,
1669
1778
  status: state.status
1670
1779
  }));
1671
- const [commands, setCommands] = useState2({});
1672
- const [formValues, setFormValues] = useState2({});
1673
- const [expandedCommands, setExpandedCommands] = useState2({});
1780
+ const [commands, setCommands] = useState3({});
1781
+ const [formValues, setFormValues] = useState3({});
1782
+ const [expandedCommands, setExpandedCommands] = useState3({});
1674
1783
  React.useEffect(() => {
1675
1784
  if (status === "disconnected") {
1676
1785
  setCommands({});
@@ -1697,8 +1806,8 @@ function ReactorController({
1697
1806
  }, 5e3);
1698
1807
  return () => clearInterval(interval);
1699
1808
  }, [status, commands, requestCapabilities]);
1700
- useReactorMessage((message, scope) => {
1701
- if (scope === "runtime" && message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
1809
+ useReactorInternalMessage((message) => {
1810
+ if (message && typeof message === "object" && message.type === "modelCapabilities" && message.data && "commands" in message.data) {
1702
1811
  const commandsMessage = message.data;
1703
1812
  setCommands(commandsMessage.commands);
1704
1813
  const initialValues = {};
@@ -2111,7 +2220,7 @@ function ReactorController({
2111
2220
  }
2112
2221
 
2113
2222
  // src/react/WebcamStream.tsx
2114
- import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
2223
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
2115
2224
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2116
2225
  function WebcamStream({
2117
2226
  className,
@@ -2123,9 +2232,9 @@ function WebcamStream({
2123
2232
  showWebcam = true,
2124
2233
  videoObjectFit = "contain"
2125
2234
  }) {
2126
- const [stream, setStream] = useState3(null);
2127
- const [isPublishing, setIsPublishing] = useState3(false);
2128
- const [permissionDenied, setPermissionDenied] = useState3(false);
2235
+ const [stream, setStream] = useState4(null);
2236
+ const [isPublishing, setIsPublishing] = useState4(false);
2237
+ const [permissionDenied, setPermissionDenied] = useState4(false);
2129
2238
  const { status, publishVideoStream, unpublishVideoStream, reactor } = useReactor((state) => ({
2130
2239
  status: state.status,
2131
2240
  publishVideoStream: state.publishVideoStream,
@@ -2330,7 +2439,9 @@ export {
2330
2439
  WebcamStream,
2331
2440
  fetchInsecureJwtToken,
2332
2441
  useReactor,
2442
+ useReactorInternalMessage,
2333
2443
  useReactorMessage,
2334
- useReactorStore
2444
+ useReactorStore,
2445
+ useStats
2335
2446
  };
2336
2447
  //# sourceMappingURL=index.mjs.map