@reactor-team/js-sdk 2.0.0 → 2.0.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/README.md CHANGED
@@ -11,24 +11,10 @@ There are two main ways to use the frontend SDK:
11
11
 
12
12
  ## Building the SDK
13
13
 
14
- Set up the environment variables:
15
-
16
- ```bash
17
- cp .env.example .env
18
- ```
19
-
20
- Then add your NPM_TOKEN to the .env file.
21
-
22
- Build the SDK:
23
-
24
14
  ```bash
25
15
  pnpm build
26
16
  ```
27
17
 
28
- ## Publishing
29
-
30
- To publish the SDK:
18
+ ## Documentation
31
19
 
32
- ```bash
33
- ./publish_package.sh
34
- ```
20
+ - [Getting Started](https://docs.reactor.inc)
package/dist/index.d.mts CHANGED
@@ -11,6 +11,9 @@ interface ReactorError {
11
11
  component: "coordinator" | "gpu" | "livekit";
12
12
  retryAfter?: number;
13
13
  }
14
+ declare class ConflictError extends Error {
15
+ constructor(message: string);
16
+ }
14
17
  interface ReactorState$1 {
15
18
  status: ReactorStatus;
16
19
  lastError?: ReactorError;
@@ -171,4 +174,17 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
171
174
  */
172
175
  declare function useReactorMessage(handler: (message: any) => void): void;
173
176
 
174
- export { 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, useReactor, useReactorMessage, useReactorStore };
177
+ /**
178
+ * ⚠️ INSECURE: Fetches a JWT token directly from the client.
179
+ *
180
+ * WARNING: This function exposes your API key in client-side code.
181
+ * Only use this for local development or testing purposes.
182
+ * In production, call /tokens from your server and pass the JWT to your frontend.
183
+ *
184
+ * @param apiKey - Your Reactor API key (will be exposed in client code!)
185
+ * @param coordinatorUrl - Optional coordinator URL, defaults to production
186
+ * @returns string containing the JWT token
187
+ */
188
+ declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
189
+
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 };
package/dist/index.d.ts CHANGED
@@ -11,6 +11,9 @@ interface ReactorError {
11
11
  component: "coordinator" | "gpu" | "livekit";
12
12
  retryAfter?: number;
13
13
  }
14
+ declare class ConflictError extends Error {
15
+ constructor(message: string);
16
+ }
14
17
  interface ReactorState$1 {
15
18
  status: ReactorStatus;
16
19
  lastError?: ReactorError;
@@ -171,4 +174,17 @@ declare function useReactor<T>(selector: (state: ReactorStore) => T): T;
171
174
  */
172
175
  declare function useReactorMessage(handler: (message: any) => void): void;
173
176
 
174
- export { 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, useReactor, useReactorMessage, useReactorStore };
177
+ /**
178
+ * ⚠️ INSECURE: Fetches a JWT token directly from the client.
179
+ *
180
+ * WARNING: This function exposes your API key in client-side code.
181
+ * Only use this for local development or testing purposes.
182
+ * In production, call /tokens from your server and pass the JWT to your frontend.
183
+ *
184
+ * @param apiKey - Your Reactor API key (will be exposed in client code!)
185
+ * @param coordinatorUrl - Optional coordinator URL, defaults to production
186
+ * @returns string containing the JWT token
187
+ */
188
+ declare function fetchInsecureJwtToken(apiKey: string, coordinatorUrl?: string): Promise<string>;
189
+
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 };
package/dist/index.js CHANGED
@@ -79,18 +79,27 @@ var __async = (__this, __arguments, generator) => {
79
79
  // src/index.ts
80
80
  var index_exports = {};
81
81
  __export(index_exports, {
82
+ ConflictError: () => ConflictError,
82
83
  PROD_COORDINATOR_URL: () => PROD_COORDINATOR_URL,
83
84
  Reactor: () => Reactor,
84
85
  ReactorController: () => ReactorController,
85
86
  ReactorProvider: () => ReactorProvider,
86
87
  ReactorView: () => ReactorView,
87
88
  WebcamStream: () => WebcamStream,
89
+ fetchInsecureJwtToken: () => fetchInsecureJwtToken,
88
90
  useReactor: () => useReactor,
89
91
  useReactorMessage: () => useReactorMessage,
90
92
  useReactorStore: () => useReactorStore
91
93
  });
92
94
  module.exports = __toCommonJS(index_exports);
93
95
 
96
+ // src/types.ts
97
+ var ConflictError = class extends Error {
98
+ constructor(message) {
99
+ super(message);
100
+ }
101
+ };
102
+
94
103
  // src/core/CoordinatorClient.ts
95
104
  var INITIAL_BACKOFF_MS = 500;
96
105
  var MAX_BACKOFF_MS = 3e4;
@@ -109,6 +118,11 @@ var CoordinatorClient = class {
109
118
  Authorization: `Bearer ${this.jwtToken}`
110
119
  };
111
120
  }
121
+ getIceServers() {
122
+ return __async(this, null, function* () {
123
+ return [{ urls: "stun:stun.l.google.com:19302" }];
124
+ });
125
+ }
112
126
  /**
113
127
  * Creates a new session with the coordinator.
114
128
  * Expects a 200 response and stores the session ID.
@@ -345,6 +359,27 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
345
359
  });
346
360
  this.localBaseUrl = baseUrl;
347
361
  }
362
+ /**
363
+ * Gets ICE servers from the local HTTP runtime.
364
+ * @returns The ICE server configuration
365
+ */
366
+ getIceServers() {
367
+ return __async(this, null, function* () {
368
+ console.debug("[LocalCoordinatorClient] Fetching ICE servers...");
369
+ const response = yield fetch(`${this.localBaseUrl}/ice_servers`, {
370
+ method: "GET"
371
+ });
372
+ if (!response.ok) {
373
+ throw new Error("Failed to get ICE servers from local coordinator.");
374
+ }
375
+ const data = yield response.json();
376
+ console.debug(
377
+ "[LocalCoordinatorClient] Received ICE servers:",
378
+ data.ice_servers
379
+ );
380
+ return data.ice_servers;
381
+ });
382
+ }
348
383
  /**
349
384
  * Creates a local session by posting to /start_session.
350
385
  * @returns always "local"
@@ -385,6 +420,9 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
385
420
  body: JSON.stringify(sdpBody)
386
421
  });
387
422
  if (!response.ok) {
423
+ if (response.status === 409) {
424
+ throw new ConflictError("Connection superseded by newer request");
425
+ }
388
426
  throw new Error("Failed to get SDP answer from local coordinator.");
389
427
  }
390
428
  const sdpAnswer = yield response.json();
@@ -403,15 +441,12 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
403
441
  };
404
442
 
405
443
  // src/utils/webrtc.ts
406
- var DEFAULT_ICE_SERVERS = [
407
- { urls: "stun:stun.l.google.com:19302" },
408
- { urls: "stun:stun1.l.google.com:19302" }
409
- ];
410
444
  var DEFAULT_DATA_CHANNEL_LABEL = "data";
445
+ var FORCE_RELAY_MODE = false;
411
446
  function createPeerConnection(config) {
412
- var _a;
413
447
  return new RTCPeerConnection({
414
- iceServers: (_a = config == null ? void 0 : config.iceServers) != null ? _a : DEFAULT_ICE_SERVERS
448
+ iceServers: config.iceServers,
449
+ iceTransportPolicy: FORCE_RELAY_MODE ? "relay" : "all"
415
450
  });
416
451
  }
417
452
  function createDataChannel(pc, label) {
@@ -492,7 +527,7 @@ var GPUMachineClient = class {
492
527
  constructor(config) {
493
528
  this.eventListeners = /* @__PURE__ */ new Map();
494
529
  this.status = "disconnected";
495
- this.config = config != null ? config : {};
530
+ this.config = config;
496
531
  }
497
532
  // ─────────────────────────────────────────────────────────────────────────────
498
533
  // Event Emitter API
@@ -889,9 +924,14 @@ var Reactor = class {
889
924
  console.warn("[Reactor] No active session to reconnect to.");
890
925
  return;
891
926
  }
927
+ if (this.status === "ready") {
928
+ console.warn("[Reactor] Already connected, no need to reconnect.");
929
+ return;
930
+ }
892
931
  this.setStatus("connecting");
893
932
  if (!this.machineClient) {
894
- this.machineClient = new GPUMachineClient();
933
+ const iceServers = yield this.coordinatorClient.getIceServers();
934
+ this.machineClient = new GPUMachineClient({ iceServers });
895
935
  this.setupMachineClientHandlers();
896
936
  }
897
937
  const sdpOffer = yield this.machineClient.createOffer();
@@ -903,8 +943,12 @@ var Reactor = class {
903
943
  yield this.machineClient.connect(sdpAnswer);
904
944
  this.setStatus("ready");
905
945
  } catch (error) {
946
+ let recoverable = false;
947
+ if (error instanceof ConflictError) {
948
+ recoverable = true;
949
+ }
906
950
  console.error("[Reactor] Failed to reconnect:", error);
907
- this.disconnect(false);
951
+ this.disconnect(recoverable);
908
952
  this.createError(
909
953
  "RECONNECTION_FAILED",
910
954
  `Failed to reconnect: ${error}`,
@@ -939,7 +983,8 @@ var Reactor = class {
939
983
  // Safe: validated on line 186-188
940
984
  model: this.model
941
985
  });
942
- this.machineClient = new GPUMachineClient();
986
+ const iceServers = yield this.coordinatorClient.getIceServers();
987
+ this.machineClient = new GPUMachineClient({ iceServers });
943
988
  this.setupMachineClientHandlers();
944
989
  const sdpOffer = yield this.machineClient.createOffer();
945
990
  const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
@@ -2128,14 +2173,37 @@ function WebcamStream({
2128
2173
  }
2129
2174
  );
2130
2175
  }
2176
+
2177
+ // src/utils/tokens.ts
2178
+ function fetchInsecureJwtToken(_0) {
2179
+ return __async(this, arguments, function* (apiKey, coordinatorUrl = PROD_COORDINATOR_URL) {
2180
+ console.warn(
2181
+ "[Reactor] \u26A0\uFE0F SECURITY WARNING: fetchInsecureJwtToken() exposes your API key in client-side code. This should ONLY be used for local development or testing. In production, fetch tokens from your server instead."
2182
+ );
2183
+ const response = yield fetch(`${coordinatorUrl}/tokens`, {
2184
+ method: "GET",
2185
+ headers: {
2186
+ "X-API-Key": apiKey
2187
+ }
2188
+ });
2189
+ if (!response.ok) {
2190
+ const error = yield response.text();
2191
+ throw new Error(`Failed to create token: ${response.status} ${error}`);
2192
+ }
2193
+ const { jwt } = yield response.json();
2194
+ return jwt;
2195
+ });
2196
+ }
2131
2197
  // Annotate the CommonJS export names for ESM import in node:
2132
2198
  0 && (module.exports = {
2199
+ ConflictError,
2133
2200
  PROD_COORDINATOR_URL,
2134
2201
  Reactor,
2135
2202
  ReactorController,
2136
2203
  ReactorProvider,
2137
2204
  ReactorView,
2138
2205
  WebcamStream,
2206
+ fetchInsecureJwtToken,
2139
2207
  useReactor,
2140
2208
  useReactorMessage,
2141
2209
  useReactorStore