@reactor-team/js-sdk 2.5.1 → 2.6.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 +31 -6
- package/dist/index.d.ts +31 -6
- package/dist/index.js +116 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +114 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -2
package/dist/index.d.mts
CHANGED
|
@@ -89,7 +89,7 @@ interface ReactorError {
|
|
|
89
89
|
message: string;
|
|
90
90
|
timestamp: number;
|
|
91
91
|
recoverable: boolean;
|
|
92
|
-
component: "
|
|
92
|
+
component: "api" | "gpu";
|
|
93
93
|
retryAfter?: number;
|
|
94
94
|
}
|
|
95
95
|
declare class ConflictError extends Error {
|
|
@@ -111,6 +111,25 @@ interface ConnectOptions {
|
|
|
111
111
|
/** Maximum number of SDP polling attempts before giving up. Default: 6. */
|
|
112
112
|
maxAttempts?: number;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* One-shot timing breakdown of the connect() handshake, recorded once per
|
|
116
|
+
* connection and included in every subsequent {@link ConnectionStats} update.
|
|
117
|
+
* All durations are in milliseconds (from `performance.now()`).
|
|
118
|
+
*/
|
|
119
|
+
interface ConnectionTimings {
|
|
120
|
+
/** POST /sessions round-trip time */
|
|
121
|
+
sessionCreationMs: number;
|
|
122
|
+
/** Total time spent polling for the SDP answer */
|
|
123
|
+
sdpPollingMs: number;
|
|
124
|
+
/** Number of SDP poll requests made (1 = answered on first try) */
|
|
125
|
+
sdpPollingAttempts: number;
|
|
126
|
+
/** setRemoteDescription → RTCPeerConnection connectionState "connected" */
|
|
127
|
+
iceNegotiationMs: number;
|
|
128
|
+
/** setRemoteDescription → RTCDataChannel "open" */
|
|
129
|
+
dataChannelMs: number;
|
|
130
|
+
/** End-to-end: connect() invocation → status "ready" */
|
|
131
|
+
totalMs: number;
|
|
132
|
+
}
|
|
114
133
|
interface ConnectionStats {
|
|
115
134
|
/** ICE candidate-pair round-trip time in milliseconds */
|
|
116
135
|
rtt?: number;
|
|
@@ -124,13 +143,15 @@ interface ConnectionStats {
|
|
|
124
143
|
packetLossRatio?: number;
|
|
125
144
|
/** Network jitter in seconds (from inbound-rtp) */
|
|
126
145
|
jitter?: number;
|
|
146
|
+
/** Timing breakdown of the initial connection handshake (set once, persisted until disconnect) */
|
|
147
|
+
connectionTimings?: ConnectionTimings;
|
|
127
148
|
timestamp: number;
|
|
128
149
|
}
|
|
129
150
|
type ReactorEvent = "statusChanged" | "sessionIdChanged" | "message" | "runtimeMessage" | "trackReceived" | "error" | "sessionExpirationChanged" | "statsUpdate";
|
|
130
151
|
|
|
131
|
-
declare const
|
|
152
|
+
declare const DEFAULT_BASE_URL = "https://api.reactor.inc";
|
|
132
153
|
declare const OptionsSchema: z.ZodObject<{
|
|
133
|
-
|
|
154
|
+
apiUrl: z.ZodDefault<z.ZodString>;
|
|
134
155
|
modelName: z.ZodString;
|
|
135
156
|
local: z.ZodDefault<z.ZodBoolean>;
|
|
136
157
|
receive: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -164,6 +185,8 @@ declare class Reactor {
|
|
|
164
185
|
/** Tracks the client SENDS to the model (client → model). */
|
|
165
186
|
private send;
|
|
166
187
|
private sessionId?;
|
|
188
|
+
private connectStartTime?;
|
|
189
|
+
private connectionTimings?;
|
|
167
190
|
constructor(options: Options);
|
|
168
191
|
private eventListeners;
|
|
169
192
|
on(event: ReactorEvent, handler: EventHandler): void;
|
|
@@ -235,6 +258,8 @@ declare class Reactor {
|
|
|
235
258
|
*/
|
|
236
259
|
getLastError(): ReactorError | undefined;
|
|
237
260
|
getStats(): ConnectionStats | undefined;
|
|
261
|
+
private resetConnectionTimings;
|
|
262
|
+
private finalizeConnectionTimings;
|
|
238
263
|
/**
|
|
239
264
|
* Create and store an error
|
|
240
265
|
*/
|
|
@@ -372,9 +397,9 @@ declare function useStats(): ConnectionStats | undefined;
|
|
|
372
397
|
* In production, call /tokens from your server and pass the JWT to your frontend.
|
|
373
398
|
*
|
|
374
399
|
* @param apiKey - Your Reactor API key (will be exposed in client code!)
|
|
375
|
-
* @param
|
|
400
|
+
* @param apiUrl - Optional API URL, defaults to production
|
|
376
401
|
* @returns string containing the JWT token
|
|
377
402
|
*/
|
|
378
|
-
declare function
|
|
403
|
+
declare function fetchInsecureToken(apiKey: string, apiUrl?: string): Promise<string>;
|
|
379
404
|
|
|
380
|
-
export { AbortError, type AudioTrackOptions, ConflictError, type ConnectOptions, type ConnectionStats, type MessageScope, type Options,
|
|
405
|
+
export { AbortError, type AudioTrackOptions, ConflictError, type ConnectOptions, type ConnectionStats, type ConnectionTimings, DEFAULT_BASE_URL, type MessageScope, type Options, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type TrackConfig, type VideoTrackOptions, WebcamStream, type WebcamStreamProps, audio, fetchInsecureToken, isAbortError, useReactor, useReactorInternalMessage, useReactorMessage, useReactorStore, useStats, video };
|
package/dist/index.d.ts
CHANGED
|
@@ -89,7 +89,7 @@ interface ReactorError {
|
|
|
89
89
|
message: string;
|
|
90
90
|
timestamp: number;
|
|
91
91
|
recoverable: boolean;
|
|
92
|
-
component: "
|
|
92
|
+
component: "api" | "gpu";
|
|
93
93
|
retryAfter?: number;
|
|
94
94
|
}
|
|
95
95
|
declare class ConflictError extends Error {
|
|
@@ -111,6 +111,25 @@ interface ConnectOptions {
|
|
|
111
111
|
/** Maximum number of SDP polling attempts before giving up. Default: 6. */
|
|
112
112
|
maxAttempts?: number;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* One-shot timing breakdown of the connect() handshake, recorded once per
|
|
116
|
+
* connection and included in every subsequent {@link ConnectionStats} update.
|
|
117
|
+
* All durations are in milliseconds (from `performance.now()`).
|
|
118
|
+
*/
|
|
119
|
+
interface ConnectionTimings {
|
|
120
|
+
/** POST /sessions round-trip time */
|
|
121
|
+
sessionCreationMs: number;
|
|
122
|
+
/** Total time spent polling for the SDP answer */
|
|
123
|
+
sdpPollingMs: number;
|
|
124
|
+
/** Number of SDP poll requests made (1 = answered on first try) */
|
|
125
|
+
sdpPollingAttempts: number;
|
|
126
|
+
/** setRemoteDescription → RTCPeerConnection connectionState "connected" */
|
|
127
|
+
iceNegotiationMs: number;
|
|
128
|
+
/** setRemoteDescription → RTCDataChannel "open" */
|
|
129
|
+
dataChannelMs: number;
|
|
130
|
+
/** End-to-end: connect() invocation → status "ready" */
|
|
131
|
+
totalMs: number;
|
|
132
|
+
}
|
|
114
133
|
interface ConnectionStats {
|
|
115
134
|
/** ICE candidate-pair round-trip time in milliseconds */
|
|
116
135
|
rtt?: number;
|
|
@@ -124,13 +143,15 @@ interface ConnectionStats {
|
|
|
124
143
|
packetLossRatio?: number;
|
|
125
144
|
/** Network jitter in seconds (from inbound-rtp) */
|
|
126
145
|
jitter?: number;
|
|
146
|
+
/** Timing breakdown of the initial connection handshake (set once, persisted until disconnect) */
|
|
147
|
+
connectionTimings?: ConnectionTimings;
|
|
127
148
|
timestamp: number;
|
|
128
149
|
}
|
|
129
150
|
type ReactorEvent = "statusChanged" | "sessionIdChanged" | "message" | "runtimeMessage" | "trackReceived" | "error" | "sessionExpirationChanged" | "statsUpdate";
|
|
130
151
|
|
|
131
|
-
declare const
|
|
152
|
+
declare const DEFAULT_BASE_URL = "https://api.reactor.inc";
|
|
132
153
|
declare const OptionsSchema: z.ZodObject<{
|
|
133
|
-
|
|
154
|
+
apiUrl: z.ZodDefault<z.ZodString>;
|
|
134
155
|
modelName: z.ZodString;
|
|
135
156
|
local: z.ZodDefault<z.ZodBoolean>;
|
|
136
157
|
receive: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
@@ -164,6 +185,8 @@ declare class Reactor {
|
|
|
164
185
|
/** Tracks the client SENDS to the model (client → model). */
|
|
165
186
|
private send;
|
|
166
187
|
private sessionId?;
|
|
188
|
+
private connectStartTime?;
|
|
189
|
+
private connectionTimings?;
|
|
167
190
|
constructor(options: Options);
|
|
168
191
|
private eventListeners;
|
|
169
192
|
on(event: ReactorEvent, handler: EventHandler): void;
|
|
@@ -235,6 +258,8 @@ declare class Reactor {
|
|
|
235
258
|
*/
|
|
236
259
|
getLastError(): ReactorError | undefined;
|
|
237
260
|
getStats(): ConnectionStats | undefined;
|
|
261
|
+
private resetConnectionTimings;
|
|
262
|
+
private finalizeConnectionTimings;
|
|
238
263
|
/**
|
|
239
264
|
* Create and store an error
|
|
240
265
|
*/
|
|
@@ -372,9 +397,9 @@ declare function useStats(): ConnectionStats | undefined;
|
|
|
372
397
|
* In production, call /tokens from your server and pass the JWT to your frontend.
|
|
373
398
|
*
|
|
374
399
|
* @param apiKey - Your Reactor API key (will be exposed in client code!)
|
|
375
|
-
* @param
|
|
400
|
+
* @param apiUrl - Optional API URL, defaults to production
|
|
376
401
|
* @returns string containing the JWT token
|
|
377
402
|
*/
|
|
378
|
-
declare function
|
|
403
|
+
declare function fetchInsecureToken(apiKey: string, apiUrl?: string): Promise<string>;
|
|
379
404
|
|
|
380
|
-
export { AbortError, type AudioTrackOptions, ConflictError, type ConnectOptions, type ConnectionStats, type MessageScope, type Options,
|
|
405
|
+
export { AbortError, type AudioTrackOptions, ConflictError, type ConnectOptions, type ConnectionStats, type ConnectionTimings, DEFAULT_BASE_URL, type MessageScope, type Options, Reactor, type ReactorConnectOptions, ReactorController, type ReactorControllerProps, type ReactorError, type ReactorEvent, ReactorProvider, type ReactorState$1 as ReactorState, type ReactorStatus, ReactorView, type ReactorViewProps, type TrackConfig, type VideoTrackOptions, WebcamStream, type WebcamStreamProps, audio, fetchInsecureToken, isAbortError, useReactor, useReactorInternalMessage, useReactorMessage, useReactorStore, useStats, video };
|
package/dist/index.js
CHANGED
|
@@ -81,14 +81,14 @@ var index_exports = {};
|
|
|
81
81
|
__export(index_exports, {
|
|
82
82
|
AbortError: () => AbortError,
|
|
83
83
|
ConflictError: () => ConflictError,
|
|
84
|
-
|
|
84
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
85
85
|
Reactor: () => Reactor,
|
|
86
86
|
ReactorController: () => ReactorController,
|
|
87
87
|
ReactorProvider: () => ReactorProvider,
|
|
88
88
|
ReactorView: () => ReactorView,
|
|
89
89
|
WebcamStream: () => WebcamStream,
|
|
90
90
|
audio: () => audio,
|
|
91
|
-
|
|
91
|
+
fetchInsecureToken: () => fetchInsecureToken,
|
|
92
92
|
isAbortError: () => isAbortError,
|
|
93
93
|
useReactor: () => useReactor,
|
|
94
94
|
useReactorInternalMessage: () => useReactorInternalMessage,
|
|
@@ -178,6 +178,7 @@ var IceServersResponseSchema = import_zod.z.object({
|
|
|
178
178
|
// src/utils/webrtc.ts
|
|
179
179
|
var DEFAULT_DATA_CHANNEL_LABEL = "data";
|
|
180
180
|
var FORCE_RELAY_MODE = false;
|
|
181
|
+
var DEFAULT_MAX_MESSAGE_BYTES = 256 * 1024;
|
|
181
182
|
function createPeerConnection(config) {
|
|
182
183
|
return new RTCPeerConnection({
|
|
183
184
|
iceServers: config.iceServers,
|
|
@@ -334,14 +335,21 @@ function waitForIceGathering(pc, timeoutMs = 5e3) {
|
|
|
334
335
|
}, timeoutMs);
|
|
335
336
|
});
|
|
336
337
|
}
|
|
337
|
-
function sendMessage(channel, command, data, scope = "application") {
|
|
338
|
+
function sendMessage(channel, command, data, scope = "application", maxBytes = DEFAULT_MAX_MESSAGE_BYTES) {
|
|
338
339
|
if (channel.readyState !== "open") {
|
|
339
340
|
throw new Error(`Data channel not open: ${channel.readyState}`);
|
|
340
341
|
}
|
|
341
342
|
const jsonData = typeof data === "string" ? JSON.parse(data) : data;
|
|
342
343
|
const inner = { type: command, data: jsonData };
|
|
343
344
|
const payload = { scope, data: inner };
|
|
344
|
-
|
|
345
|
+
const serialized = JSON.stringify(payload);
|
|
346
|
+
const byteLength = new TextEncoder().encode(serialized).byteLength;
|
|
347
|
+
if (byteLength > maxBytes) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Data channel message too large: ${byteLength} bytes exceeds limit of ${maxBytes} bytes (command: "${command}")`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
channel.send(serialized);
|
|
345
353
|
}
|
|
346
354
|
function parseMessage(data) {
|
|
347
355
|
if (typeof data === "string") {
|
|
@@ -662,7 +670,7 @@ var CoordinatorClient = class {
|
|
|
662
670
|
if (response.status === 200) {
|
|
663
671
|
const answerData = yield response.json();
|
|
664
672
|
console.debug("[CoordinatorClient] Received SDP answer via polling");
|
|
665
|
-
return answerData.sdp_answer;
|
|
673
|
+
return { sdpAnswer: answerData.sdp_answer, attempts: attempt };
|
|
666
674
|
}
|
|
667
675
|
if (response.status === 202) {
|
|
668
676
|
console.warn(
|
|
@@ -686,7 +694,7 @@ var CoordinatorClient = class {
|
|
|
686
694
|
* @param sessionId - The session ID to connect to
|
|
687
695
|
* @param sdpOffer - Optional SDP offer from the local WebRTC peer connection
|
|
688
696
|
* @param maxAttempts - Optional maximum number of polling attempts before giving up
|
|
689
|
-
* @returns The SDP answer
|
|
697
|
+
* @returns The SDP answer and the number of polling attempts made (0 if answered immediately via PUT)
|
|
690
698
|
*/
|
|
691
699
|
connect(sessionId, sdpOffer, maxAttempts) {
|
|
692
700
|
return __async(this, null, function* () {
|
|
@@ -694,10 +702,11 @@ var CoordinatorClient = class {
|
|
|
694
702
|
if (sdpOffer) {
|
|
695
703
|
const answer = yield this.sendSdpOffer(sessionId, sdpOffer);
|
|
696
704
|
if (answer !== null) {
|
|
697
|
-
return answer;
|
|
705
|
+
return { sdpAnswer: answer, sdpPollingAttempts: 0 };
|
|
698
706
|
}
|
|
699
707
|
}
|
|
700
|
-
|
|
708
|
+
const result = yield this.pollSdpAnswer(sessionId, maxAttempts);
|
|
709
|
+
return { sdpAnswer: result.sdpAnswer, sdpPollingAttempts: result.attempts };
|
|
701
710
|
});
|
|
702
711
|
}
|
|
703
712
|
/**
|
|
@@ -779,9 +788,10 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
|
|
|
779
788
|
}
|
|
780
789
|
/**
|
|
781
790
|
* Connects to the local session by posting SDP params to /sdp_params.
|
|
791
|
+
* Local connections are always immediate (no polling).
|
|
782
792
|
* @param sessionId - The session ID (ignored for local)
|
|
783
793
|
* @param sdpMessage - The SDP offer from the local WebRTC peer connection
|
|
784
|
-
* @returns The SDP answer
|
|
794
|
+
* @returns The SDP answer and polling attempts (always 0 for local)
|
|
785
795
|
*/
|
|
786
796
|
connect(sessionId, sdpMessage) {
|
|
787
797
|
return __async(this, null, function* () {
|
|
@@ -807,7 +817,7 @@ var LocalCoordinatorClient = class extends CoordinatorClient {
|
|
|
807
817
|
}
|
|
808
818
|
const sdpAnswer = yield response.json();
|
|
809
819
|
console.debug("[LocalCoordinatorClient] Received SDP answer");
|
|
810
|
-
return sdpAnswer.sdp;
|
|
820
|
+
return { sdpAnswer: sdpAnswer.sdp, sdpPollingAttempts: 0 };
|
|
811
821
|
});
|
|
812
822
|
}
|
|
813
823
|
terminateSession() {
|
|
@@ -952,6 +962,9 @@ var GPUMachineClient = class {
|
|
|
952
962
|
);
|
|
953
963
|
}
|
|
954
964
|
this.setStatus("connecting");
|
|
965
|
+
this.iceStartTime = performance.now();
|
|
966
|
+
this.iceNegotiationMs = void 0;
|
|
967
|
+
this.dataChannelMs = void 0;
|
|
955
968
|
try {
|
|
956
969
|
let answer = sdpAnswer;
|
|
957
970
|
if (this.midMapping) {
|
|
@@ -991,6 +1004,7 @@ var GPUMachineClient = class {
|
|
|
991
1004
|
this.midMapping = void 0;
|
|
992
1005
|
this.peerConnected = false;
|
|
993
1006
|
this.dataChannelOpen = false;
|
|
1007
|
+
this.resetConnectionTimings();
|
|
994
1008
|
this.setStatus("disconnected");
|
|
995
1009
|
console.debug("[GPUMachineClient] Disconnected");
|
|
996
1010
|
});
|
|
@@ -1015,6 +1029,14 @@ var GPUMachineClient = class {
|
|
|
1015
1029
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1016
1030
|
// Messaging
|
|
1017
1031
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1032
|
+
/**
|
|
1033
|
+
* Returns the negotiated SCTP max message size (bytes) if available,
|
|
1034
|
+
* otherwise `undefined` so `sendMessage` falls back to its built-in default.
|
|
1035
|
+
*/
|
|
1036
|
+
get maxMessageBytes() {
|
|
1037
|
+
var _a, _b, _c;
|
|
1038
|
+
return (_c = (_b = (_a = this.peerConnection) == null ? void 0 : _a.sctp) == null ? void 0 : _b.maxMessageSize) != null ? _c : void 0;
|
|
1039
|
+
}
|
|
1018
1040
|
/**
|
|
1019
1041
|
* Sends a command to the GPU machine via the data channel.
|
|
1020
1042
|
* @param command The command to send
|
|
@@ -1026,7 +1048,13 @@ var GPUMachineClient = class {
|
|
|
1026
1048
|
throw new Error("[GPUMachineClient] Data channel not available");
|
|
1027
1049
|
}
|
|
1028
1050
|
try {
|
|
1029
|
-
sendMessage(
|
|
1051
|
+
sendMessage(
|
|
1052
|
+
this.dataChannel,
|
|
1053
|
+
command,
|
|
1054
|
+
data,
|
|
1055
|
+
scope,
|
|
1056
|
+
this.maxMessageBytes
|
|
1057
|
+
);
|
|
1030
1058
|
} catch (error) {
|
|
1031
1059
|
console.warn("[GPUMachineClient] Failed to send message:", error);
|
|
1032
1060
|
}
|
|
@@ -1154,6 +1182,24 @@ var GPUMachineClient = class {
|
|
|
1154
1182
|
getStats() {
|
|
1155
1183
|
return this.stats;
|
|
1156
1184
|
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Returns the ICE/data-channel durations recorded during the last connect(),
|
|
1187
|
+
* or undefined if no connection has completed yet.
|
|
1188
|
+
*/
|
|
1189
|
+
getConnectionTimings() {
|
|
1190
|
+
if (this.iceNegotiationMs == null || this.dataChannelMs == null) {
|
|
1191
|
+
return void 0;
|
|
1192
|
+
}
|
|
1193
|
+
return {
|
|
1194
|
+
iceNegotiationMs: this.iceNegotiationMs,
|
|
1195
|
+
dataChannelMs: this.dataChannelMs
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
resetConnectionTimings() {
|
|
1199
|
+
this.iceStartTime = void 0;
|
|
1200
|
+
this.iceNegotiationMs = void 0;
|
|
1201
|
+
this.dataChannelMs = void 0;
|
|
1202
|
+
}
|
|
1157
1203
|
startStatsPolling() {
|
|
1158
1204
|
this.stopStatsPolling();
|
|
1159
1205
|
this.statsInterval = setInterval(() => __async(this, null, function* () {
|
|
@@ -1197,6 +1243,9 @@ var GPUMachineClient = class {
|
|
|
1197
1243
|
if (state) {
|
|
1198
1244
|
switch (state) {
|
|
1199
1245
|
case "connected":
|
|
1246
|
+
if (this.iceStartTime != null && this.iceNegotiationMs == null) {
|
|
1247
|
+
this.iceNegotiationMs = performance.now() - this.iceStartTime;
|
|
1248
|
+
}
|
|
1200
1249
|
this.peerConnected = true;
|
|
1201
1250
|
this.checkFullyConnected();
|
|
1202
1251
|
break;
|
|
@@ -1246,6 +1295,9 @@ var GPUMachineClient = class {
|
|
|
1246
1295
|
if (!this.dataChannel) return;
|
|
1247
1296
|
this.dataChannel.onopen = () => {
|
|
1248
1297
|
console.debug("[GPUMachineClient] Data channel open");
|
|
1298
|
+
if (this.iceStartTime != null && this.dataChannelMs == null) {
|
|
1299
|
+
this.dataChannelMs = performance.now() - this.iceStartTime;
|
|
1300
|
+
}
|
|
1249
1301
|
this.dataChannelOpen = true;
|
|
1250
1302
|
this.startPing();
|
|
1251
1303
|
this.checkFullyConnected();
|
|
@@ -1285,13 +1337,13 @@ var GPUMachineClient = class {
|
|
|
1285
1337
|
// src/core/Reactor.ts
|
|
1286
1338
|
var import_zod2 = require("zod");
|
|
1287
1339
|
var LOCAL_COORDINATOR_URL = "http://localhost:8080";
|
|
1288
|
-
var
|
|
1340
|
+
var DEFAULT_BASE_URL = "https://api.reactor.inc";
|
|
1289
1341
|
var TrackConfigSchema = import_zod2.z.object({
|
|
1290
1342
|
name: import_zod2.z.string(),
|
|
1291
1343
|
kind: import_zod2.z.enum(["audio", "video"])
|
|
1292
1344
|
});
|
|
1293
1345
|
var OptionsSchema = import_zod2.z.object({
|
|
1294
|
-
|
|
1346
|
+
apiUrl: import_zod2.z.string().default(DEFAULT_BASE_URL),
|
|
1295
1347
|
modelName: import_zod2.z.string(),
|
|
1296
1348
|
local: import_zod2.z.boolean().default(false),
|
|
1297
1349
|
/**
|
|
@@ -1316,12 +1368,12 @@ var Reactor = class {
|
|
|
1316
1368
|
// Generic event map
|
|
1317
1369
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1318
1370
|
const validatedOptions = OptionsSchema.parse(options);
|
|
1319
|
-
this.coordinatorUrl = validatedOptions.
|
|
1371
|
+
this.coordinatorUrl = validatedOptions.apiUrl;
|
|
1320
1372
|
this.model = validatedOptions.modelName;
|
|
1321
1373
|
this.local = validatedOptions.local;
|
|
1322
1374
|
this.receive = validatedOptions.receive;
|
|
1323
1375
|
this.send = validatedOptions.send;
|
|
1324
|
-
if (this.local && options.
|
|
1376
|
+
if (this.local && options.apiUrl === void 0) {
|
|
1325
1377
|
this.coordinatorUrl = LOCAL_COORDINATOR_URL;
|
|
1326
1378
|
}
|
|
1327
1379
|
}
|
|
@@ -1442,7 +1494,7 @@ var Reactor = class {
|
|
|
1442
1494
|
receive: this.receive
|
|
1443
1495
|
});
|
|
1444
1496
|
try {
|
|
1445
|
-
const sdpAnswer = yield this.coordinatorClient.connect(
|
|
1497
|
+
const { sdpAnswer } = yield this.coordinatorClient.connect(
|
|
1446
1498
|
this.sessionId,
|
|
1447
1499
|
sdpOffer,
|
|
1448
1500
|
options == null ? void 0 : options.maxAttempts
|
|
@@ -1459,7 +1511,7 @@ var Reactor = class {
|
|
|
1459
1511
|
this.createError(
|
|
1460
1512
|
"RECONNECTION_FAILED",
|
|
1461
1513
|
`Failed to reconnect: ${error}`,
|
|
1462
|
-
"
|
|
1514
|
+
"api",
|
|
1463
1515
|
true
|
|
1464
1516
|
);
|
|
1465
1517
|
}
|
|
@@ -1482,6 +1534,7 @@ var Reactor = class {
|
|
|
1482
1534
|
throw new Error("Already connected or connecting");
|
|
1483
1535
|
}
|
|
1484
1536
|
this.setStatus("connecting");
|
|
1537
|
+
this.connectStartTime = performance.now();
|
|
1485
1538
|
try {
|
|
1486
1539
|
console.debug(
|
|
1487
1540
|
"[Reactor] Connecting to coordinator with authenticated URL"
|
|
@@ -1499,13 +1552,25 @@ var Reactor = class {
|
|
|
1499
1552
|
send: this.send,
|
|
1500
1553
|
receive: this.receive
|
|
1501
1554
|
});
|
|
1555
|
+
const tSession = performance.now();
|
|
1502
1556
|
const sessionId = yield this.coordinatorClient.createSession(sdpOffer);
|
|
1557
|
+
const sessionCreationMs = performance.now() - tSession;
|
|
1503
1558
|
this.setSessionId(sessionId);
|
|
1504
|
-
const
|
|
1559
|
+
const tSdp = performance.now();
|
|
1560
|
+
const { sdpAnswer, sdpPollingAttempts } = yield this.coordinatorClient.connect(
|
|
1505
1561
|
sessionId,
|
|
1506
1562
|
void 0,
|
|
1507
1563
|
options == null ? void 0 : options.maxAttempts
|
|
1508
1564
|
);
|
|
1565
|
+
const sdpPollingMs = performance.now() - tSdp;
|
|
1566
|
+
this.connectionTimings = {
|
|
1567
|
+
sessionCreationMs,
|
|
1568
|
+
sdpPollingMs,
|
|
1569
|
+
sdpPollingAttempts,
|
|
1570
|
+
iceNegotiationMs: 0,
|
|
1571
|
+
dataChannelMs: 0,
|
|
1572
|
+
totalMs: 0
|
|
1573
|
+
};
|
|
1509
1574
|
yield this.machineClient.connect(sdpAnswer);
|
|
1510
1575
|
} catch (error) {
|
|
1511
1576
|
if (isAbortError(error)) return;
|
|
@@ -1513,7 +1578,7 @@ var Reactor = class {
|
|
|
1513
1578
|
this.createError(
|
|
1514
1579
|
"CONNECTION_FAILED",
|
|
1515
1580
|
`Connection failed: ${error}`,
|
|
1516
|
-
"
|
|
1581
|
+
"api",
|
|
1517
1582
|
true
|
|
1518
1583
|
);
|
|
1519
1584
|
try {
|
|
@@ -1551,6 +1616,7 @@ var Reactor = class {
|
|
|
1551
1616
|
if (this.machineClient !== client) return;
|
|
1552
1617
|
switch (status) {
|
|
1553
1618
|
case "connected":
|
|
1619
|
+
this.finalizeConnectionTimings(client);
|
|
1554
1620
|
this.setStatus("ready");
|
|
1555
1621
|
break;
|
|
1556
1622
|
case "disconnected":
|
|
@@ -1576,7 +1642,9 @@ var Reactor = class {
|
|
|
1576
1642
|
);
|
|
1577
1643
|
client.on("statsUpdate", (stats) => {
|
|
1578
1644
|
if (this.machineClient !== client) return;
|
|
1579
|
-
this.emit("statsUpdate", stats)
|
|
1645
|
+
this.emit("statsUpdate", __spreadProps(__spreadValues({}, stats), {
|
|
1646
|
+
connectionTimings: this.connectionTimings
|
|
1647
|
+
}));
|
|
1580
1648
|
});
|
|
1581
1649
|
}
|
|
1582
1650
|
/**
|
|
@@ -1610,6 +1678,7 @@ var Reactor = class {
|
|
|
1610
1678
|
}
|
|
1611
1679
|
}
|
|
1612
1680
|
this.setStatus("disconnected");
|
|
1681
|
+
this.resetConnectionTimings();
|
|
1613
1682
|
if (!recoverable) {
|
|
1614
1683
|
this.setSessionExpiration(void 0);
|
|
1615
1684
|
this.setSessionId(void 0);
|
|
@@ -1672,7 +1741,23 @@ var Reactor = class {
|
|
|
1672
1741
|
}
|
|
1673
1742
|
getStats() {
|
|
1674
1743
|
var _a;
|
|
1675
|
-
|
|
1744
|
+
const stats = (_a = this.machineClient) == null ? void 0 : _a.getStats();
|
|
1745
|
+
if (!stats) return void 0;
|
|
1746
|
+
return __spreadProps(__spreadValues({}, stats), { connectionTimings: this.connectionTimings });
|
|
1747
|
+
}
|
|
1748
|
+
resetConnectionTimings() {
|
|
1749
|
+
this.connectStartTime = void 0;
|
|
1750
|
+
this.connectionTimings = void 0;
|
|
1751
|
+
}
|
|
1752
|
+
finalizeConnectionTimings(client) {
|
|
1753
|
+
var _a, _b;
|
|
1754
|
+
if (!this.connectionTimings || this.connectStartTime == null) return;
|
|
1755
|
+
const webrtcTimings = client.getConnectionTimings();
|
|
1756
|
+
this.connectionTimings.iceNegotiationMs = (_a = webrtcTimings == null ? void 0 : webrtcTimings.iceNegotiationMs) != null ? _a : 0;
|
|
1757
|
+
this.connectionTimings.dataChannelMs = (_b = webrtcTimings == null ? void 0 : webrtcTimings.dataChannelMs) != null ? _b : 0;
|
|
1758
|
+
this.connectionTimings.totalMs = performance.now() - this.connectStartTime;
|
|
1759
|
+
this.connectStartTime = void 0;
|
|
1760
|
+
console.debug("[Reactor] Connection timings:", this.connectionTimings);
|
|
1676
1761
|
}
|
|
1677
1762
|
/**
|
|
1678
1763
|
* Create and store an error
|
|
@@ -1712,7 +1797,7 @@ var initReactorStore = (props) => {
|
|
|
1712
1797
|
};
|
|
1713
1798
|
var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
1714
1799
|
console.debug("[ReactorStore] Creating store", {
|
|
1715
|
-
|
|
1800
|
+
apiUrl: initProps.apiUrl,
|
|
1716
1801
|
jwtToken: initProps.jwtToken,
|
|
1717
1802
|
initialState: publicState
|
|
1718
1803
|
});
|
|
@@ -1880,7 +1965,7 @@ function ReactorProvider(_a) {
|
|
|
1880
1965
|
console.debug("[ReactorProvider] Reactor store created successfully");
|
|
1881
1966
|
}
|
|
1882
1967
|
const _a2 = connectOptions != null ? connectOptions : {}, { autoConnect = false } = _a2, pollingOptions = __objRest(_a2, ["autoConnect"]);
|
|
1883
|
-
const {
|
|
1968
|
+
const { apiUrl, modelName, local, receive, send } = props;
|
|
1884
1969
|
const maxAttempts = pollingOptions.maxAttempts;
|
|
1885
1970
|
(0, import_react3.useEffect)(() => {
|
|
1886
1971
|
const handleBeforeUnload = () => {
|
|
@@ -1933,7 +2018,7 @@ function ReactorProvider(_a) {
|
|
|
1933
2018
|
console.debug("[ReactorProvider] Updating reactor store");
|
|
1934
2019
|
storeRef.current = createReactorStore(
|
|
1935
2020
|
initReactorStore({
|
|
1936
|
-
|
|
2021
|
+
apiUrl,
|
|
1937
2022
|
modelName,
|
|
1938
2023
|
local,
|
|
1939
2024
|
receive,
|
|
@@ -1965,7 +2050,7 @@ function ReactorProvider(_a) {
|
|
|
1965
2050
|
});
|
|
1966
2051
|
};
|
|
1967
2052
|
}, [
|
|
1968
|
-
|
|
2053
|
+
apiUrl,
|
|
1969
2054
|
modelName,
|
|
1970
2055
|
autoConnect,
|
|
1971
2056
|
local,
|
|
@@ -2794,12 +2879,12 @@ function WebcamStream({
|
|
|
2794
2879
|
}
|
|
2795
2880
|
|
|
2796
2881
|
// src/utils/tokens.ts
|
|
2797
|
-
function
|
|
2798
|
-
return __async(this, arguments, function* (apiKey,
|
|
2882
|
+
function fetchInsecureToken(_0) {
|
|
2883
|
+
return __async(this, arguments, function* (apiKey, apiUrl = DEFAULT_BASE_URL) {
|
|
2799
2884
|
console.warn(
|
|
2800
|
-
"[Reactor] \u26A0\uFE0F SECURITY WARNING:
|
|
2885
|
+
"[Reactor] \u26A0\uFE0F SECURITY WARNING: fetchInsecureToken() 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."
|
|
2801
2886
|
);
|
|
2802
|
-
const response = yield fetch(`${
|
|
2887
|
+
const response = yield fetch(`${apiUrl}/tokens`, {
|
|
2803
2888
|
method: "GET",
|
|
2804
2889
|
headers: {
|
|
2805
2890
|
"X-API-Key": apiKey
|
|
@@ -2817,14 +2902,14 @@ function fetchInsecureJwtToken(_0) {
|
|
|
2817
2902
|
0 && (module.exports = {
|
|
2818
2903
|
AbortError,
|
|
2819
2904
|
ConflictError,
|
|
2820
|
-
|
|
2905
|
+
DEFAULT_BASE_URL,
|
|
2821
2906
|
Reactor,
|
|
2822
2907
|
ReactorController,
|
|
2823
2908
|
ReactorProvider,
|
|
2824
2909
|
ReactorView,
|
|
2825
2910
|
WebcamStream,
|
|
2826
2911
|
audio,
|
|
2827
|
-
|
|
2912
|
+
fetchInsecureToken,
|
|
2828
2913
|
isAbortError,
|
|
2829
2914
|
useReactor,
|
|
2830
2915
|
useReactorInternalMessage,
|