@reactor-team/js-sdk 2.3.1 → 2.3.2

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
@@ -24,6 +24,13 @@ interface ReactorState$1 {
24
24
  status: ReactorStatus;
25
25
  lastError?: ReactorError;
26
26
  }
27
+ /**
28
+ * Options for configuring the connect polling behavior.
29
+ */
30
+ interface ConnectOptions {
31
+ /** Maximum number of SDP polling attempts before giving up. Default: 6. */
32
+ maxAttempts?: number;
33
+ }
27
34
  type ReactorEvent = "statusChanged" | "sessionIdChanged" | "newMessage" | "streamChanged" | "error" | "sessionExpirationChanged";
28
35
 
29
36
  declare const PROD_COORDINATOR_URL = "https://api.reactor.inc";
@@ -70,14 +77,17 @@ declare class Reactor {
70
77
  unpublishTrack(): Promise<void>;
71
78
  /**
72
79
  * Public method for reconnecting to an existing session, that may have been interrupted but can be recovered.
80
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
73
81
  */
74
- reconnect(): Promise<void>;
82
+ reconnect(options?: ConnectOptions): Promise<void>;
75
83
  /**
76
84
  * Connects to the coordinator and waits for a GPU to be assigned.
77
85
  * Once a GPU is assigned, the Reactor will connect to the gpu machine via WebRTC.
78
86
  * If no authentication is provided and not in local mode, an error is thrown.
87
+ * @param jwtToken Optional JWT token for authentication
88
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
79
89
  */
80
- connect(jwtToken?: string): Promise<void>;
90
+ connect(jwtToken?: string, options?: ConnectOptions): Promise<void>;
81
91
  /**
82
92
  * Sets up event handlers for the machine client.
83
93
  */
@@ -121,11 +131,11 @@ interface ReactorState {
121
131
  }
122
132
  interface ReactorActions {
123
133
  sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
124
- connect(jwtToken?: string): Promise<void>;
134
+ connect(jwtToken?: string, options?: ConnectOptions): Promise<void>;
125
135
  disconnect(recoverable?: boolean): Promise<void>;
126
136
  publishVideoStream(stream: MediaStream): Promise<void>;
127
137
  unpublishVideoStream(): Promise<void>;
128
- reconnect(): Promise<void>;
138
+ reconnect(options?: ConnectOptions): Promise<void>;
129
139
  }
130
140
  interface ReactorInternalState {
131
141
  reactor: Reactor;
@@ -137,12 +147,20 @@ interface ReactorInitializationProps extends Options {
137
147
  jwtToken?: string;
138
148
  }
139
149
 
140
- interface ReactorProviderProps extends ReactorInitializationProps {
150
+ /**
151
+ * Options for the React provider's connect behavior.
152
+ * Extends the core ConnectOptions with autoConnect for the React lifecycle.
153
+ */
154
+ interface ReactorConnectOptions extends ConnectOptions {
155
+ /** Whether to automatically connect when the provider mounts. Default: false. */
141
156
  autoConnect?: boolean;
157
+ }
158
+ interface ReactorProviderProps extends ReactorInitializationProps {
159
+ connectOptions?: ReactorConnectOptions;
142
160
  jwtToken?: string;
143
161
  children: ReactNode;
144
162
  }
145
- declare function ReactorProvider({ children, autoConnect, jwtToken, ...props }: ReactorProviderProps): react_jsx_runtime.JSX.Element;
163
+ declare function ReactorProvider({ children, connectOptions, jwtToken, ...props }: ReactorProviderProps): react_jsx_runtime.JSX.Element;
146
164
  declare function useReactorStore<T = ReactorStore>(selector: (state: ReactorStore) => T): T;
147
165
 
148
166
  interface ReactorViewProps {
@@ -200,4 +218,4 @@ declare function useReactorMessage(handler: (message: any, scope: MessageScope)
200
218
  */
201
219
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
202
220
 
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 };
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 };
package/dist/index.d.ts CHANGED
@@ -24,6 +24,13 @@ interface ReactorState$1 {
24
24
  status: ReactorStatus;
25
25
  lastError?: ReactorError;
26
26
  }
27
+ /**
28
+ * Options for configuring the connect polling behavior.
29
+ */
30
+ interface ConnectOptions {
31
+ /** Maximum number of SDP polling attempts before giving up. Default: 6. */
32
+ maxAttempts?: number;
33
+ }
27
34
  type ReactorEvent = "statusChanged" | "sessionIdChanged" | "newMessage" | "streamChanged" | "error" | "sessionExpirationChanged";
28
35
 
29
36
  declare const PROD_COORDINATOR_URL = "https://api.reactor.inc";
@@ -70,14 +77,17 @@ declare class Reactor {
70
77
  unpublishTrack(): Promise<void>;
71
78
  /**
72
79
  * Public method for reconnecting to an existing session, that may have been interrupted but can be recovered.
80
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
73
81
  */
74
- reconnect(): Promise<void>;
82
+ reconnect(options?: ConnectOptions): Promise<void>;
75
83
  /**
76
84
  * Connects to the coordinator and waits for a GPU to be assigned.
77
85
  * Once a GPU is assigned, the Reactor will connect to the gpu machine via WebRTC.
78
86
  * If no authentication is provided and not in local mode, an error is thrown.
87
+ * @param jwtToken Optional JWT token for authentication
88
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
79
89
  */
80
- connect(jwtToken?: string): Promise<void>;
90
+ connect(jwtToken?: string, options?: ConnectOptions): Promise<void>;
81
91
  /**
82
92
  * Sets up event handlers for the machine client.
83
93
  */
@@ -121,11 +131,11 @@ interface ReactorState {
121
131
  }
122
132
  interface ReactorActions {
123
133
  sendCommand(command: string, data: any, scope?: MessageScope): Promise<void>;
124
- connect(jwtToken?: string): Promise<void>;
134
+ connect(jwtToken?: string, options?: ConnectOptions): Promise<void>;
125
135
  disconnect(recoverable?: boolean): Promise<void>;
126
136
  publishVideoStream(stream: MediaStream): Promise<void>;
127
137
  unpublishVideoStream(): Promise<void>;
128
- reconnect(): Promise<void>;
138
+ reconnect(options?: ConnectOptions): Promise<void>;
129
139
  }
130
140
  interface ReactorInternalState {
131
141
  reactor: Reactor;
@@ -137,12 +147,20 @@ interface ReactorInitializationProps extends Options {
137
147
  jwtToken?: string;
138
148
  }
139
149
 
140
- interface ReactorProviderProps extends ReactorInitializationProps {
150
+ /**
151
+ * Options for the React provider's connect behavior.
152
+ * Extends the core ConnectOptions with autoConnect for the React lifecycle.
153
+ */
154
+ interface ReactorConnectOptions extends ConnectOptions {
155
+ /** Whether to automatically connect when the provider mounts. Default: false. */
141
156
  autoConnect?: boolean;
157
+ }
158
+ interface ReactorProviderProps extends ReactorInitializationProps {
159
+ connectOptions?: ReactorConnectOptions;
142
160
  jwtToken?: string;
143
161
  children: ReactNode;
144
162
  }
145
- declare function ReactorProvider({ children, autoConnect, jwtToken, ...props }: ReactorProviderProps): react_jsx_runtime.JSX.Element;
163
+ declare function ReactorProvider({ children, connectOptions, jwtToken, ...props }: ReactorProviderProps): react_jsx_runtime.JSX.Element;
146
164
  declare function useReactorStore<T = ReactorStore>(selector: (state: ReactorStore) => T): T;
147
165
 
148
166
  interface ReactorViewProps {
@@ -200,4 +218,4 @@ declare function useReactorMessage(handler: (message: any, scope: MessageScope)
200
218
  */
201
219
  declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
202
220
 
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 };
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 };
package/dist/index.js CHANGED
@@ -252,8 +252,9 @@ function closePeerConnection(pc) {
252
252
 
253
253
  // src/core/CoordinatorClient.ts
254
254
  var INITIAL_BACKOFF_MS = 500;
255
- var MAX_BACKOFF_MS = 3e4;
255
+ var MAX_BACKOFF_MS = 15e3;
256
256
  var BACKOFF_MULTIPLIER = 2;
257
+ var DEFAULT_MAX_ATTEMPTS = 6;
257
258
  var CoordinatorClient = class {
258
259
  constructor(options) {
259
260
  this.baseUrl = options.baseUrl;
@@ -446,13 +447,14 @@ var CoordinatorClient = class {
446
447
  });
447
448
  }
448
449
  /**
449
- * Polls for the SDP answer with geometric backoff.
450
+ * Polls for the SDP answer with exponential backoff.
450
451
  * Used for async reconnection when the answer is not immediately available.
451
452
  * @param sessionId - The session ID to poll for
453
+ * @param maxAttempts - Optional maximum number of polling attempts before giving up
452
454
  * @returns The SDP answer from the server
453
455
  */
454
- pollSdpAnswer(sessionId) {
455
- return __async(this, null, function* () {
456
+ pollSdpAnswer(_0) {
457
+ return __async(this, arguments, function* (sessionId, maxAttempts = DEFAULT_MAX_ATTEMPTS) {
456
458
  console.debug(
457
459
  "[CoordinatorClient] Polling for SDP answer for session:",
458
460
  sessionId
@@ -460,9 +462,14 @@ var CoordinatorClient = class {
460
462
  let backoffMs = INITIAL_BACKOFF_MS;
461
463
  let attempt = 0;
462
464
  while (true) {
465
+ if (attempt >= maxAttempts) {
466
+ throw new Error(
467
+ `SDP polling exceeded maximum attempts (${maxAttempts}) for session ${sessionId}`
468
+ );
469
+ }
463
470
  attempt++;
464
471
  console.debug(
465
- `[CoordinatorClient] SDP poll attempt ${attempt} for session ${sessionId}`
472
+ `[CoordinatorClient] SDP poll attempt ${attempt}/${maxAttempts} for session ${sessionId}`
466
473
  );
467
474
  const response = yield fetch(
468
475
  `${this.baseUrl}/sessions/${sessionId}/sdp_params`,
@@ -499,9 +506,10 @@ var CoordinatorClient = class {
499
506
  * falls back to polling. If no sdpOffer is provided, goes directly to polling.
500
507
  * @param sessionId - The session ID to connect to
501
508
  * @param sdpOffer - Optional SDP offer from the local WebRTC peer connection
509
+ * @param maxAttempts - Optional maximum number of polling attempts before giving up
502
510
  * @returns The SDP answer from the server
503
511
  */
504
- connect(sessionId, sdpOffer) {
512
+ connect(sessionId, sdpOffer, maxAttempts) {
505
513
  return __async(this, null, function* () {
506
514
  console.debug("[CoordinatorClient] Connecting to session:", sessionId);
507
515
  if (sdpOffer) {
@@ -510,7 +518,7 @@ var CoordinatorClient = class {
510
518
  return answer;
511
519
  }
512
520
  }
513
- return this.pollSdpAnswer(sessionId);
521
+ return this.pollSdpAnswer(sessionId, maxAttempts);
514
522
  });
515
523
  }
516
524
  /**
@@ -1054,8 +1062,9 @@ var Reactor = class {
1054
1062
  }
1055
1063
  /**
1056
1064
  * Public method for reconnecting to an existing session, that may have been interrupted but can be recovered.
1065
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
1057
1066
  */
1058
- reconnect() {
1067
+ reconnect(options) {
1059
1068
  return __async(this, null, function* () {
1060
1069
  if (!this.sessionId || !this.coordinatorClient) {
1061
1070
  console.warn("[Reactor] No active session to reconnect to.");
@@ -1075,7 +1084,8 @@ var Reactor = class {
1075
1084
  try {
1076
1085
  const sdpAnswer = yield this.coordinatorClient.connect(
1077
1086
  this.sessionId,
1078
- sdpOffer
1087
+ sdpOffer,
1088
+ options == null ? void 0 : options.maxAttempts
1079
1089
  );
1080
1090
  yield this.machineClient.connect(sdpAnswer);
1081
1091
  this.setStatus("ready");
@@ -1099,8 +1109,10 @@ var Reactor = class {
1099
1109
  * Connects to the coordinator and waits for a GPU to be assigned.
1100
1110
  * Once a GPU is assigned, the Reactor will connect to the gpu machine via WebRTC.
1101
1111
  * If no authentication is provided and not in local mode, an error is thrown.
1112
+ * @param jwtToken Optional JWT token for authentication
1113
+ * @param options Optional connect options (e.g. maxAttempts for SDP polling)
1102
1114
  */
1103
- connect(jwtToken) {
1115
+ connect(jwtToken, options) {
1104
1116
  return __async(this, null, function* () {
1105
1117
  console.debug("[Reactor] Connecting, status:", this.status);
1106
1118
  if (jwtToken == void 0 && !this.local) {
@@ -1117,7 +1129,7 @@ var Reactor = class {
1117
1129
  this.coordinatorClient = this.local ? new LocalCoordinatorClient(this.coordinatorUrl) : new CoordinatorClient({
1118
1130
  baseUrl: this.coordinatorUrl,
1119
1131
  jwtToken,
1120
- // Safe: validated on line 186-188
1132
+ // Safe: validated above
1121
1133
  model: this.model
1122
1134
  });
1123
1135
  const iceServers = yield this.coordinatorClient.getIceServers();
@@ -1126,7 +1138,11 @@ var Reactor = class {
1126
1138
  const sdpOffer = yield this.machineClient.createOffer();
1127
1139
  const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
1128
1140
  this.setSessionId(sessionId);
1129
- const sdpAnswer = yield this.coordinatorClient.connect(sessionId);
1141
+ const sdpAnswer = yield this.coordinatorClient.connect(
1142
+ sessionId,
1143
+ void 0,
1144
+ options == null ? void 0 : options.maxAttempts
1145
+ );
1130
1146
  yield this.machineClient.connect(sdpAnswer);
1131
1147
  } catch (error) {
1132
1148
  console.error("[Reactor] Connection failed:", error);
@@ -1136,7 +1152,14 @@ var Reactor = class {
1136
1152
  "coordinator",
1137
1153
  true
1138
1154
  );
1139
- this.setStatus("disconnected");
1155
+ try {
1156
+ yield this.disconnect(false);
1157
+ } catch (disconnectError) {
1158
+ console.error(
1159
+ "[Reactor] Failed to clean up after connection failure:",
1160
+ disconnectError
1161
+ );
1162
+ }
1140
1163
  throw error;
1141
1164
  }
1142
1165
  });
@@ -1368,13 +1391,13 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
1368
1391
  throw error;
1369
1392
  }
1370
1393
  }),
1371
- connect: (jwtToken) => __async(null, null, function* () {
1394
+ connect: (jwtToken, options) => __async(null, null, function* () {
1372
1395
  if (jwtToken === void 0) {
1373
1396
  jwtToken = get().jwtToken;
1374
1397
  }
1375
1398
  console.debug("[ReactorStore] Connect called.");
1376
1399
  try {
1377
- yield get().internal.reactor.connect(jwtToken);
1400
+ yield get().internal.reactor.connect(jwtToken, options);
1378
1401
  console.debug("[ReactorStore] Connect completed successfully");
1379
1402
  } catch (error) {
1380
1403
  console.error("[ReactorStore] Connect failed:", error);
@@ -1419,10 +1442,10 @@ var createReactorStore = (initProps, publicState = defaultInitState) => {
1419
1442
  throw error;
1420
1443
  }
1421
1444
  }),
1422
- reconnect: () => __async(null, null, function* () {
1445
+ reconnect: (options) => __async(null, null, function* () {
1423
1446
  console.debug("[ReactorStore] Reconnecting");
1424
1447
  try {
1425
- yield get().internal.reactor.reconnect();
1448
+ yield get().internal.reactor.reconnect(options);
1426
1449
  console.debug("[ReactorStore] Reconnect completed successfully");
1427
1450
  } catch (error) {
1428
1451
  console.error("[ReactorStore] Failed to reconnect:", error);
@@ -1439,11 +1462,11 @@ var import_jsx_runtime = require("react/jsx-runtime");
1439
1462
  function ReactorProvider(_a) {
1440
1463
  var _b = _a, {
1441
1464
  children,
1442
- autoConnect = true,
1465
+ connectOptions,
1443
1466
  jwtToken
1444
1467
  } = _b, props = __objRest(_b, [
1445
1468
  "children",
1446
- "autoConnect",
1469
+ "connectOptions",
1447
1470
  "jwtToken"
1448
1471
  ]);
1449
1472
  const storeRef = (0, import_react3.useRef)(void 0);
@@ -1458,14 +1481,16 @@ function ReactorProvider(_a) {
1458
1481
  );
1459
1482
  console.debug("[ReactorProvider] Reactor store created successfully");
1460
1483
  }
1484
+ const _a2 = connectOptions != null ? connectOptions : {}, { autoConnect = false } = _a2, pollingOptions = __objRest(_a2, ["autoConnect"]);
1461
1485
  const { coordinatorUrl, modelName, local } = props;
1486
+ const maxAttempts = pollingOptions.maxAttempts;
1462
1487
  (0, import_react3.useEffect)(() => {
1463
1488
  const handleBeforeUnload = () => {
1464
- var _a2;
1489
+ var _a3;
1465
1490
  console.debug(
1466
1491
  "[ReactorProvider] Page unloading, performing non-recoverable disconnect"
1467
1492
  );
1468
- (_a2 = storeRef.current) == null ? void 0 : _a2.getState().internal.reactor.disconnect(false);
1493
+ (_a3 = storeRef.current) == null ? void 0 : _a3.getState().internal.reactor.disconnect(false);
1469
1494
  };
1470
1495
  window.addEventListener("beforeunload", handleBeforeUnload);
1471
1496
  return () => {
@@ -1480,7 +1505,7 @@ function ReactorProvider(_a) {
1480
1505
  console.debug(
1481
1506
  "[ReactorProvider] Starting autoconnect in first render..."
1482
1507
  );
1483
- current2.getState().connect(jwtToken).then(() => {
1508
+ current2.getState().connect(jwtToken, pollingOptions).then(() => {
1484
1509
  console.debug(
1485
1510
  "[ReactorProvider] Autoconnect successful in first render"
1486
1511
  );
@@ -1523,7 +1548,7 @@ function ReactorProvider(_a) {
1523
1548
  );
1524
1549
  if (autoConnect && current.getState().status === "disconnected" && jwtToken) {
1525
1550
  console.debug("[ReactorProvider] Starting autoconnect...");
1526
- current.getState().connect(jwtToken).then(() => {
1551
+ current.getState().connect(jwtToken, pollingOptions).then(() => {
1527
1552
  console.debug("[ReactorProvider] Autoconnect successful");
1528
1553
  }).catch((error) => {
1529
1554
  console.error("[ReactorProvider] Failed to autoconnect:", error);
@@ -1539,7 +1564,7 @@ function ReactorProvider(_a) {
1539
1564
  console.error("[ReactorProvider] Failed to disconnect:", error);
1540
1565
  });
1541
1566
  };
1542
- }, [coordinatorUrl, modelName, autoConnect, local, jwtToken]);
1567
+ }, [coordinatorUrl, modelName, autoConnect, local, jwtToken, maxAttempts]);
1543
1568
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReactorContext.Provider, { value: storeRef.current, children });
1544
1569
  }
1545
1570
  function useReactorStore(selector) {