@reactor-team/js-sdk 1.0.1

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 ADDED
@@ -0,0 +1,971 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+ var __async = (__this, __arguments, generator) => {
33
+ return new Promise((resolve, reject) => {
34
+ var fulfilled = (value) => {
35
+ try {
36
+ step(generator.next(value));
37
+ } catch (e) {
38
+ reject(e);
39
+ }
40
+ };
41
+ var rejected = (value) => {
42
+ try {
43
+ step(generator.throw(value));
44
+ } catch (e) {
45
+ reject(e);
46
+ }
47
+ };
48
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
49
+ step((generator = generator.apply(__this, __arguments)).next());
50
+ });
51
+ };
52
+
53
+ // src/core/types.ts
54
+ import { z } from "zod";
55
+ var ApplicationMessageSchema = z.object({
56
+ type: z.literal("application"),
57
+ data: z.any()
58
+ // Can be any JSON-serializable data
59
+ });
60
+ var FPSMessageSchema = z.object({
61
+ type: z.literal("fps"),
62
+ data: z.number()
63
+ });
64
+ var GPUMachineReceiveMessageSchema = z.discriminatedUnion("type", [
65
+ ApplicationMessageSchema,
66
+ FPSMessageSchema
67
+ ]);
68
+ var WelcomeMessageSchema = z.object({
69
+ type: z.literal("welcome"),
70
+ data: z.record(z.string(), z.any())
71
+ });
72
+ var GPUMachineAssignmentDataSchema = z.object({
73
+ livekitWsUrl: z.string(),
74
+ livekitJwtToken: z.string()
75
+ });
76
+ var GPUMachineAssignmentMessageSchema = z.object({
77
+ type: z.literal("gpu-machine-assigned"),
78
+ data: GPUMachineAssignmentDataSchema
79
+ });
80
+ var EchoMessageSchema = z.object({
81
+ type: z.literal("echo"),
82
+ data: z.record(z.string(), z.any())
83
+ });
84
+ var WaitingInfoDataSchema = z.object({
85
+ position: z.number().optional(),
86
+ estimatedWaitTime: z.number().optional(),
87
+ averageWaitTime: z.number().optional()
88
+ });
89
+ var WaitingInfoMessageSchema = z.object({
90
+ type: z.literal("waiting-info"),
91
+ data: WaitingInfoDataSchema
92
+ });
93
+ var CoordinatorMessageSchema = z.discriminatedUnion("type", [
94
+ WelcomeMessageSchema,
95
+ GPUMachineAssignmentMessageSchema,
96
+ EchoMessageSchema,
97
+ WaitingInfoMessageSchema
98
+ ]);
99
+ var GPUMachineSendMessageSchema = z.discriminatedUnion("type", [
100
+ ApplicationMessageSchema
101
+ ]);
102
+ var SessionSetupMessageSchema = z.object({
103
+ type: z.literal("sessionSetup"),
104
+ data: z.object({
105
+ modelName: z.string(),
106
+ modelVersion: z.string().default("latest")
107
+ })
108
+ });
109
+
110
+ // src/core/CoordinatorClient.ts
111
+ import { z as z2 } from "zod";
112
+ var OptionsSchema = z2.object({
113
+ wsUrl: z2.string().nonempty(),
114
+ jwtToken: z2.string().optional(),
115
+ insecureApiKey: z2.string().optional(),
116
+ modelName: z2.string(),
117
+ modelVersion: z2.string().default("latest")
118
+ }).refine((data) => data.jwtToken || data.insecureApiKey, {
119
+ message: "At least one of jwtToken or insecureApiKey must be provided."
120
+ });
121
+ var CoordinatorClient = class {
122
+ constructor(options) {
123
+ this.eventListeners = /* @__PURE__ */ new Map();
124
+ const validatedOptions = OptionsSchema.parse(options);
125
+ this.wsUrl = validatedOptions.wsUrl;
126
+ this.protocols = validatedOptions.jwtToken ? [`jwt-token.${validatedOptions.jwtToken}`] : [`insecure-api-key.${validatedOptions.insecureApiKey}`];
127
+ this.modelName = validatedOptions.modelName;
128
+ this.modelVersion = validatedOptions.modelVersion;
129
+ }
130
+ // Event Emitter API
131
+ on(event, handler) {
132
+ if (!this.eventListeners.has(event)) {
133
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
134
+ }
135
+ this.eventListeners.get(event).add(handler);
136
+ }
137
+ off(event, handler) {
138
+ var _a;
139
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
140
+ }
141
+ emit(event, ...args) {
142
+ var _a;
143
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
144
+ }
145
+ sendMessage(message) {
146
+ var _a;
147
+ try {
148
+ const messageStr = typeof message === "string" ? message : JSON.stringify(message);
149
+ (_a = this.websocket) == null ? void 0 : _a.send(messageStr);
150
+ } catch (error) {
151
+ console.error("[CoordinatorClient] Failed to send message:", error);
152
+ this.emit("statusChanged", "error");
153
+ }
154
+ }
155
+ connect() {
156
+ return __async(this, null, function* () {
157
+ console.debug("[CoordinatorClient] Connecting to", this.wsUrl);
158
+ this.websocket = new WebSocket(this.wsUrl, this.protocols);
159
+ this.websocket.onopen = () => {
160
+ console.debug("[CoordinatorClient] WebSocket opened");
161
+ this.emit("statusChanged", "connected");
162
+ };
163
+ this.websocket.onmessage = (event) => {
164
+ try {
165
+ let parsedData;
166
+ if (typeof event.data === "string") {
167
+ parsedData = JSON.parse(event.data);
168
+ } else {
169
+ parsedData = event.data;
170
+ }
171
+ console.debug(
172
+ "[CoordinatorClient] Received message from coordinator:",
173
+ parsedData
174
+ );
175
+ const validatedData = CoordinatorMessageSchema.parse(parsedData);
176
+ this.emit(validatedData.type, validatedData.data);
177
+ } catch (error) {
178
+ console.error(
179
+ "[CoordinatorClient] Failed to parse WebSocket message from coordinator:",
180
+ error,
181
+ "message",
182
+ event.data
183
+ );
184
+ this.emit("statusChanged", "error");
185
+ }
186
+ };
187
+ this.websocket.onclose = (event) => {
188
+ console.debug("[CoordinatorClient] WebSocket closed", event);
189
+ this.websocket = void 0;
190
+ this.emit("statusChanged", "disconnected");
191
+ };
192
+ this.websocket.onerror = (error) => {
193
+ console.error("[CoordinatorClient] WebSocket error:", error);
194
+ this.websocket = void 0;
195
+ this.emit("statusChanged", "error");
196
+ };
197
+ yield new Promise((resolve, reject) => {
198
+ var _a, _b;
199
+ const onOpen = () => {
200
+ var _a2;
201
+ (_a2 = this.websocket) == null ? void 0 : _a2.removeEventListener("error", onError);
202
+ resolve();
203
+ };
204
+ const onError = (error) => {
205
+ var _a2;
206
+ (_a2 = this.websocket) == null ? void 0 : _a2.removeEventListener("open", onOpen);
207
+ reject(error);
208
+ };
209
+ (_a = this.websocket) == null ? void 0 : _a.addEventListener("open", onOpen);
210
+ (_b = this.websocket) == null ? void 0 : _b.addEventListener("error", onError);
211
+ });
212
+ console.log("[CoordinatorClient] WebSocket connected");
213
+ this.sendMessage({
214
+ type: "sessionSetup",
215
+ data: {
216
+ modelName: this.modelName,
217
+ modelVersion: this.modelVersion
218
+ }
219
+ });
220
+ console.debug("[CoordinatorClient] Setup session message sent");
221
+ });
222
+ }
223
+ /**
224
+ * Closes the WebSocket connection if it exists.
225
+ * This will trigger the onclose event handler.
226
+ */
227
+ disconnect() {
228
+ if (this.websocket) {
229
+ console.debug("[CoordinatorClient] Closing WebSocket connection");
230
+ this.websocket.close();
231
+ this.websocket = void 0;
232
+ }
233
+ }
234
+ };
235
+
236
+ // src/core/GPUMachineClient.ts
237
+ import { Room, RoomEvent, Track } from "livekit-client";
238
+ var GPUMachineClient = class {
239
+ constructor(token, liveKitUrl) {
240
+ this.eventListeners = /* @__PURE__ */ new Map();
241
+ this.token = token;
242
+ this.liveKitUrl = liveKitUrl;
243
+ }
244
+ // Event Emitter API
245
+ on(event, handler) {
246
+ if (!this.eventListeners.has(event)) {
247
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
248
+ }
249
+ this.eventListeners.get(event).add(handler);
250
+ }
251
+ off(event, handler) {
252
+ var _a;
253
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
254
+ }
255
+ emit(event, ...args) {
256
+ var _a;
257
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
258
+ }
259
+ sendMessage(message) {
260
+ try {
261
+ if (this.roomInstance) {
262
+ const messageStr = JSON.stringify(message);
263
+ this.roomInstance.localParticipant.sendText(messageStr, {
264
+ topic: "application"
265
+ });
266
+ } else {
267
+ console.warn(
268
+ "[GPUMachineClient] Cannot send message - not connected to room"
269
+ );
270
+ }
271
+ } catch (error) {
272
+ console.error("[GPUMachineClient] Failed to send message:", error);
273
+ this.emit("statusChanged", "error");
274
+ }
275
+ }
276
+ connect() {
277
+ return __async(this, null, function* () {
278
+ this.roomInstance = new Room({
279
+ adaptiveStream: true,
280
+ dynacast: true
281
+ });
282
+ this.roomInstance.on(RoomEvent.Connected, () => {
283
+ console.debug("[GPUMachineClient] Connected to room");
284
+ this.emit("statusChanged", "connected");
285
+ });
286
+ this.roomInstance.on(RoomEvent.Disconnected, () => {
287
+ console.debug("[GPUMachineClient] Disconnected from room");
288
+ this.emit("statusChanged", "disconnected");
289
+ });
290
+ this.roomInstance.on(
291
+ RoomEvent.TrackSubscribed,
292
+ (track, _publication, participant) => {
293
+ console.debug(
294
+ "[GPUMachineClient] Track subscribed:",
295
+ track.kind,
296
+ participant.identity
297
+ );
298
+ if (track.kind === Track.Kind.Video) {
299
+ const videoTrack = track;
300
+ this.emit("streamChanged", videoTrack);
301
+ }
302
+ }
303
+ );
304
+ this.roomInstance.on(
305
+ RoomEvent.TrackUnsubscribed,
306
+ (track, _publication, participant) => {
307
+ console.debug(
308
+ "[GPUMachineClient] Track unsubscribed:",
309
+ track.kind,
310
+ participant.identity
311
+ );
312
+ if (track.kind === Track.Kind.Video) {
313
+ this.emit("streamChanged", null);
314
+ }
315
+ }
316
+ );
317
+ this.roomInstance.registerTextStreamHandler(
318
+ "application",
319
+ (reader, participant) => __async(this, null, function* () {
320
+ const text = yield reader.readAll();
321
+ console.log("[GPUMachineClient] Received message:", text);
322
+ try {
323
+ const parsedData = JSON.parse(text);
324
+ const validatedMessage = GPUMachineReceiveMessageSchema.parse(parsedData);
325
+ if (validatedMessage.type === "fps") {
326
+ this.machineFPS = validatedMessage.data;
327
+ }
328
+ this.emit(validatedMessage.type, validatedMessage.data);
329
+ } catch (error) {
330
+ console.error(
331
+ "[GPUMachineClient] Failed to parse/validate message:",
332
+ error
333
+ );
334
+ this.emit("statusChanged", "error");
335
+ }
336
+ })
337
+ );
338
+ yield this.roomInstance.connect(this.liveKitUrl, this.token);
339
+ console.log("[GPUMachineClient] Room connected");
340
+ });
341
+ }
342
+ /**
343
+ * Closes the LiveKit connection if it exists.
344
+ * This will trigger the onclose event handler.
345
+ */
346
+ disconnect() {
347
+ return __async(this, null, function* () {
348
+ if (this.roomInstance) {
349
+ console.debug("[GPUMachineClient] Closing LiveKit connection");
350
+ yield this.roomInstance.disconnect();
351
+ this.roomInstance = void 0;
352
+ }
353
+ this.machineFPS = void 0;
354
+ });
355
+ }
356
+ /**
357
+ * Returns the current fps rate of the machine.
358
+ * @returns The current fps rate of the machine.
359
+ */
360
+ getFPS() {
361
+ return this.machineFPS;
362
+ }
363
+ /**
364
+ * Returns the current video stream from the GPU machine.
365
+ * @returns The current video stream or undefined if not available.
366
+ */
367
+ getVideoStream() {
368
+ return this.videoStream;
369
+ }
370
+ };
371
+
372
+ // src/core/Reactor.ts
373
+ import { z as z3 } from "zod";
374
+ var OptionsSchema2 = z3.object({
375
+ directConnection: z3.object({
376
+ livekitJwtToken: z3.string(),
377
+ livekitWsUrl: z3.string()
378
+ }).optional(),
379
+ insecureApiKey: z3.string().optional(),
380
+ jwtToken: z3.string().optional(),
381
+ coordinatorUrl: z3.string().optional(),
382
+ modelName: z3.string()
383
+ }).refine(
384
+ (data) => data.directConnection || data.insecureApiKey || data.jwtToken,
385
+ {
386
+ message: "At least one of directConnection, insecureApiKey, or jwtToken must be provided."
387
+ }
388
+ );
389
+ var Reactor = class {
390
+ constructor(options) {
391
+ //client for the machine instance
392
+ this.status = "disconnected";
393
+ // Generic event map
394
+ this.eventListeners = /* @__PURE__ */ new Map();
395
+ const validatedOptions = OptionsSchema2.parse(options);
396
+ this.coordinatorUrl = validatedOptions.coordinatorUrl || "ws://localhost:8080/ws";
397
+ this.jwtToken = validatedOptions.jwtToken;
398
+ this.insecureApiKey = validatedOptions.insecureApiKey;
399
+ this.directConnection = validatedOptions.directConnection;
400
+ this.modelName = validatedOptions.modelName;
401
+ this.modelVersion = "1.0.0";
402
+ }
403
+ // Event Emitter API
404
+ on(event, handler) {
405
+ if (!this.eventListeners.has(event)) {
406
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
407
+ }
408
+ this.eventListeners.get(event).add(handler);
409
+ }
410
+ off(event, handler) {
411
+ var _a;
412
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
413
+ }
414
+ emit(event, ...args) {
415
+ var _a;
416
+ (_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
417
+ }
418
+ /**
419
+ * Public method to send a message to the machine.
420
+ * Automatically wraps the message in an application message.
421
+ * @param message The message to send to the machine.
422
+ * @throws Error if not in ready state
423
+ */
424
+ sendMessage(message) {
425
+ var _a;
426
+ if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
427
+ const errorMessage = `Cannot send message, status is ${this.status}`;
428
+ console.error("[Reactor] Not ready, cannot send message");
429
+ throw new Error(errorMessage);
430
+ }
431
+ try {
432
+ const applicationMessage = {
433
+ type: "application",
434
+ data: message
435
+ };
436
+ (_a = this.machineClient) == null ? void 0 : _a.sendMessage(applicationMessage);
437
+ } catch (error) {
438
+ console.error("[Reactor] Failed to send message:", error);
439
+ this.createError(
440
+ "MESSAGE_SEND_FAILED",
441
+ `Failed to send message: ${error}`,
442
+ "gpu",
443
+ true
444
+ );
445
+ }
446
+ }
447
+ /**
448
+ * Connects to the machine via LiveKit and waits for the gpu machine to be ready.
449
+ * Once the machine is ready, the Reactor will establish the LiveKit connection.
450
+ * @param livekitJwtToken The JWT token for LiveKit authentication
451
+ * @param livekitWsUrl The WebSocket URL for LiveKit connection
452
+ */
453
+ connectToGPUMachine(livekitJwtToken, livekitWsUrl) {
454
+ return __async(this, null, function* () {
455
+ console.debug("[Reactor] Connecting to machine room...");
456
+ try {
457
+ this.machineClient = new GPUMachineClient(livekitJwtToken, livekitWsUrl);
458
+ this.machineClient.on("application", (message) => {
459
+ this.emit("newMessage", message);
460
+ });
461
+ this.machineClient.on(
462
+ "statusChanged",
463
+ (status) => {
464
+ switch (status) {
465
+ case "connected":
466
+ this.setStatus("ready");
467
+ break;
468
+ case "disconnected":
469
+ this.disconnect();
470
+ break;
471
+ case "error":
472
+ this.createError(
473
+ "GPU_CONNECTION_ERROR",
474
+ "GPU machine connection failed",
475
+ "gpu",
476
+ true
477
+ );
478
+ this.disconnect();
479
+ break;
480
+ }
481
+ }
482
+ );
483
+ this.machineClient.on("fps", (fps) => {
484
+ this.emit("fps", fps);
485
+ });
486
+ this.machineClient.on("streamChanged", (videoTrack) => {
487
+ this.emit("streamChanged", videoTrack);
488
+ });
489
+ console.debug("[Reactor] About to connect to machine");
490
+ yield this.machineClient.connect();
491
+ } catch (error) {
492
+ throw error;
493
+ }
494
+ });
495
+ }
496
+ /**
497
+ * Connects to the coordinator and waits for a GPU to be assigned.
498
+ * Once a GPU is assigned, the Reactor will connect to the gpu machine via LiveKit.
499
+ */
500
+ connect() {
501
+ return __async(this, null, function* () {
502
+ console.debug("[Reactor] Connecting, status:", this.status);
503
+ if (this.status !== "disconnected")
504
+ throw new Error("Already connected or connecting");
505
+ if (this.directConnection) {
506
+ return this.connectToGPUMachine(
507
+ this.directConnection.livekitJwtToken,
508
+ this.directConnection.livekitWsUrl
509
+ );
510
+ }
511
+ this.setStatus("connecting");
512
+ try {
513
+ console.debug(
514
+ "[Reactor] Connecting to coordinator with authenticated URL"
515
+ );
516
+ this.coordinatorClient = new CoordinatorClient({
517
+ wsUrl: this.coordinatorUrl,
518
+ jwtToken: this.jwtToken,
519
+ insecureApiKey: this.insecureApiKey,
520
+ modelName: this.modelName,
521
+ modelVersion: this.modelVersion
522
+ });
523
+ this.coordinatorClient.on(
524
+ "gpu-machine-assigned",
525
+ (assignmentData) => __async(this, null, function* () {
526
+ console.debug(
527
+ "[Reactor] GPU machine assigned by coordinator:",
528
+ assignmentData
529
+ );
530
+ try {
531
+ yield this.connectToGPUMachine(
532
+ assignmentData.livekitJwtToken,
533
+ assignmentData.livekitWsUrl
534
+ );
535
+ } catch (error) {
536
+ console.error("[Reactor] Failed to connect to GPU machine:", error);
537
+ this.createError(
538
+ "GPU_CONNECTION_FAILED",
539
+ `Failed to connect to GPU machine: ${error}`,
540
+ "gpu",
541
+ true
542
+ );
543
+ this.disconnect();
544
+ }
545
+ })
546
+ );
547
+ this.coordinatorClient.on(
548
+ "waiting-info",
549
+ (waitingData) => {
550
+ console.debug("[Reactor] Waiting info update received:", waitingData);
551
+ this.setWaitingInfo(__spreadValues(__spreadValues({}, this.waitingInfo), waitingData));
552
+ }
553
+ );
554
+ this.coordinatorClient.on(
555
+ "statusChanged",
556
+ (newStatus) => {
557
+ switch (newStatus) {
558
+ case "connected":
559
+ this.setStatus("waiting");
560
+ this.setWaitingInfo({
561
+ position: void 0,
562
+ estimatedWaitTime: void 0,
563
+ averageWaitTime: void 0
564
+ });
565
+ break;
566
+ case "disconnected":
567
+ this.disconnect();
568
+ break;
569
+ case "error":
570
+ this.createError(
571
+ "COORDINATOR_CONNECTION_ERROR",
572
+ "Coordinator connection failed",
573
+ "coordinator",
574
+ true
575
+ );
576
+ this.disconnect();
577
+ break;
578
+ }
579
+ }
580
+ );
581
+ yield this.coordinatorClient.connect();
582
+ } catch (error) {
583
+ console.error("[Reactor] Authentication failed:", error);
584
+ this.createError(
585
+ "AUTHENTICATION_FAILED",
586
+ `Authentication failed: ${error}`,
587
+ "coordinator",
588
+ true
589
+ );
590
+ this.setStatus("disconnected");
591
+ throw error;
592
+ }
593
+ });
594
+ }
595
+ /**
596
+ * Disconnects from the coordinator and the gpu machine.
597
+ * Ensures cleanup completes even if individual disconnections fail.
598
+ */
599
+ disconnect() {
600
+ return __async(this, null, function* () {
601
+ if (this.status === "disconnected") return;
602
+ if (this.coordinatorClient) {
603
+ try {
604
+ this.coordinatorClient.disconnect();
605
+ } catch (error) {
606
+ console.error("[Reactor] Error disconnecting from coordinator:", error);
607
+ }
608
+ this.coordinatorClient = void 0;
609
+ }
610
+ if (this.machineClient) {
611
+ try {
612
+ yield this.machineClient.disconnect();
613
+ } catch (error) {
614
+ console.error("[Reactor] Error disconnecting from GPU machine:", error);
615
+ }
616
+ this.machineClient = void 0;
617
+ }
618
+ this.setStatus("disconnected");
619
+ this.waitingInfo = void 0;
620
+ });
621
+ }
622
+ setStatus(newStatus) {
623
+ console.debug("[Reactor] Setting status:", newStatus, "from", this.status);
624
+ if (this.status !== newStatus) {
625
+ this.status = newStatus;
626
+ this.emit("statusChanged", newStatus);
627
+ }
628
+ }
629
+ setWaitingInfo(newWaitingInfo) {
630
+ console.debug(
631
+ "[Reactor] Setting waiting info:",
632
+ newWaitingInfo,
633
+ "from",
634
+ this.waitingInfo
635
+ );
636
+ if (this.waitingInfo !== newWaitingInfo) {
637
+ this.waitingInfo = newWaitingInfo;
638
+ this.emit("waitingInfoChanged", newWaitingInfo);
639
+ }
640
+ }
641
+ getStatus() {
642
+ return this.status;
643
+ }
644
+ /**
645
+ * Get the current state including status, error, and waiting info
646
+ */
647
+ getState() {
648
+ return {
649
+ status: this.status,
650
+ waitingInfo: this.waitingInfo,
651
+ lastError: this.lastError
652
+ };
653
+ }
654
+ /**
655
+ * Get waiting information when status is 'waiting'
656
+ */
657
+ getWaitingInfo() {
658
+ return this.waitingInfo;
659
+ }
660
+ /**
661
+ * Get the last error that occurred
662
+ */
663
+ getLastError() {
664
+ return this.lastError;
665
+ }
666
+ /**
667
+ * Create and store an error
668
+ */
669
+ createError(code, message, component, recoverable, retryAfter) {
670
+ this.lastError = {
671
+ code,
672
+ message,
673
+ timestamp: Date.now(),
674
+ recoverable,
675
+ component,
676
+ retryAfter
677
+ };
678
+ this.emit("error", this.lastError);
679
+ }
680
+ };
681
+
682
+ // src/react/ReactorProvider.tsx
683
+ import { useContext, useEffect, useRef } from "react";
684
+
685
+ // src/core/store.ts
686
+ import { create } from "zustand/react";
687
+ import { createContext } from "react";
688
+ var ReactorContext = createContext(
689
+ void 0
690
+ );
691
+ var defaultInitState = {
692
+ status: "disconnected",
693
+ videoTrack: null,
694
+ fps: void 0,
695
+ waitingInfo: void 0,
696
+ lastError: void 0
697
+ };
698
+ var initReactorStore = (props) => {
699
+ return __spreadValues(__spreadValues({}, defaultInitState), props);
700
+ };
701
+ var createReactorStore = (initProps, publicState = defaultInitState) => {
702
+ console.debug("[ReactorStore] Creating store", {
703
+ coordinatorUrl: initProps.coordinatorUrl,
704
+ initialState: publicState
705
+ });
706
+ return create()((set, get) => {
707
+ const reactor = new Reactor(initProps);
708
+ console.debug("[ReactorStore] Setting up event listeners");
709
+ reactor.on("statusChanged", (newStatus) => {
710
+ console.debug("[ReactorStore] Status changed", {
711
+ oldStatus: get().status,
712
+ newStatus
713
+ });
714
+ set({ status: newStatus });
715
+ });
716
+ reactor.on("waitingInfoChanged", (newWaitingInfo) => {
717
+ console.debug("[ReactorStore] Waiting info changed", {
718
+ oldWaitingInfo: get().waitingInfo,
719
+ newWaitingInfo
720
+ });
721
+ set({ waitingInfo: newWaitingInfo });
722
+ });
723
+ reactor.on("streamChanged", (videoTrack) => {
724
+ console.debug("[ReactorStore] Stream changed", {
725
+ hasVideoTrack: !!videoTrack,
726
+ videoTrackKind: videoTrack == null ? void 0 : videoTrack.kind,
727
+ videoTrackSid: videoTrack == null ? void 0 : videoTrack.sid
728
+ });
729
+ set({ videoTrack });
730
+ });
731
+ reactor.on("fps", (fps) => {
732
+ console.debug("[ReactorStore] FPS updated", { fps });
733
+ set({ fps });
734
+ });
735
+ reactor.on("error", (error) => {
736
+ console.debug("[ReactorStore] Error occurred", error);
737
+ set({ lastError: error });
738
+ });
739
+ return __spreadProps(__spreadValues({}, publicState), {
740
+ internal: { reactor },
741
+ // actions
742
+ onMessage: (handler) => {
743
+ console.debug("[ReactorStore] Registering message handler");
744
+ get().internal.reactor.on("newMessage", handler);
745
+ return () => {
746
+ console.debug("[ReactorStore] Cleaning up message handler");
747
+ get().internal.reactor.off("newMessage", handler);
748
+ };
749
+ },
750
+ sendMessage: (mess) => {
751
+ console.debug("[ReactorStore] Sending message", { message: mess });
752
+ try {
753
+ get().internal.reactor.sendMessage(mess);
754
+ console.debug("[ReactorStore] Message sent successfully");
755
+ } catch (error) {
756
+ console.error("[ReactorStore] Failed to send message:", error);
757
+ throw error;
758
+ }
759
+ },
760
+ connect: () => __async(null, null, function* () {
761
+ console.debug("[ReactorStore] Connect called");
762
+ try {
763
+ yield get().internal.reactor.connect();
764
+ console.debug("[ReactorStore] Connect completed successfully");
765
+ } catch (error) {
766
+ console.error("[ReactorStore] Connect failed:", error);
767
+ throw error;
768
+ }
769
+ }),
770
+ disconnect: () => __async(null, null, function* () {
771
+ console.debug("[ReactorStore] Disconnect called", {
772
+ currentStatus: get().status
773
+ });
774
+ try {
775
+ yield get().internal.reactor.disconnect();
776
+ console.debug("[ReactorStore] Disconnect completed successfully");
777
+ } catch (error) {
778
+ console.error("[ReactorStore] Disconnect failed:", error);
779
+ throw error;
780
+ }
781
+ })
782
+ });
783
+ });
784
+ };
785
+
786
+ // src/react/ReactorProvider.tsx
787
+ import { useStore } from "zustand";
788
+ import { jsx } from "react/jsx-runtime";
789
+ function ReactorProvider(_a) {
790
+ var _b = _a, {
791
+ children,
792
+ autoConnect = true
793
+ } = _b, props = __objRest(_b, [
794
+ "children",
795
+ "autoConnect"
796
+ ]);
797
+ const storeRef = useRef(void 0);
798
+ const firstRender = useRef(true);
799
+ function attemptAutoConnect() {
800
+ var _a2, _b2;
801
+ const status = (_a2 = storeRef.current) == null ? void 0 : _a2.getState().status;
802
+ if (autoConnect && status === "disconnected") {
803
+ console.debug("[ReactorProvider] Starting autoconnect...");
804
+ (_b2 = storeRef.current) == null ? void 0 : _b2.getState().connect().then(() => {
805
+ console.debug("[ReactorProvider] Autoconnect successful");
806
+ }).catch((error) => {
807
+ console.error("[ReactorProvider] Failed to autoconnect:", error);
808
+ });
809
+ }
810
+ }
811
+ if (storeRef.current === void 0) {
812
+ console.debug("[ReactorProvider] Creating new reactor store");
813
+ storeRef.current = createReactorStore(initReactorStore(props));
814
+ console.debug("[ReactorProvider] Reactor store created successfully");
815
+ attemptAutoConnect();
816
+ }
817
+ useEffect(() => {
818
+ if (firstRender.current) {
819
+ firstRender.current = false;
820
+ return;
821
+ }
822
+ console.debug("[ReactorProvider] Updating reactor store");
823
+ storeRef.current = createReactorStore(initReactorStore(props));
824
+ console.debug("[ReactorProvider] Reactor store updated successfully");
825
+ attemptAutoConnect();
826
+ }, [props, autoConnect]);
827
+ return /* @__PURE__ */ jsx(ReactorContext.Provider, { value: storeRef.current, children });
828
+ }
829
+ function useReactorStore(selector) {
830
+ const ctx = useContext(ReactorContext);
831
+ if (!ctx) {
832
+ throw new Error("useReactor must be used within a ReactorProvider");
833
+ }
834
+ return useStore(ctx, selector);
835
+ }
836
+
837
+ // src/react/hooks.ts
838
+ import { useShallow } from "zustand/shallow";
839
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
840
+ function useReactor(selector) {
841
+ return useReactorStore(useShallow(selector));
842
+ }
843
+ function useReactorMessage(handler) {
844
+ const reactor = useReactor((state) => state.internal.reactor);
845
+ const handlerRef = useRef2(handler);
846
+ useEffect2(() => {
847
+ handlerRef.current = handler;
848
+ }, [handler]);
849
+ useEffect2(() => {
850
+ console.debug("[useReactorMessage] Setting up message subscription");
851
+ const stableHandler = (message) => {
852
+ console.debug("[useReactorMessage] Message received", { message });
853
+ handlerRef.current(message);
854
+ };
855
+ reactor.on("newMessage", stableHandler);
856
+ console.debug("[useReactorMessage] Message handler registered");
857
+ return () => {
858
+ console.debug("[useReactorMessage] Cleaning up message subscription");
859
+ reactor.off("newMessage", stableHandler);
860
+ };
861
+ }, [reactor]);
862
+ }
863
+
864
+ // src/react/ReactorView.tsx
865
+ import { useEffect as useEffect3, useRef as useRef3 } from "react";
866
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
867
+ function ReactorView({
868
+ width,
869
+ height,
870
+ className,
871
+ style
872
+ }) {
873
+ const { videoTrack, status } = useReactor((state) => ({
874
+ videoTrack: state.videoTrack,
875
+ status: state.status
876
+ }));
877
+ const videoRef = useRef3(null);
878
+ console.debug("[ReactorView] Render", {
879
+ hasVideoTrack: !!videoTrack,
880
+ status,
881
+ hasVideoElement: !!videoRef.current
882
+ });
883
+ useEffect3(() => {
884
+ console.debug("[ReactorView] Video track effect triggered", {
885
+ hasVideoElement: !!videoRef.current,
886
+ hasVideoTrack: !!videoTrack,
887
+ videoTrackKind: videoTrack == null ? void 0 : videoTrack.kind
888
+ });
889
+ if (videoRef.current && videoTrack) {
890
+ console.debug("[ReactorView] Attaching video track to element");
891
+ try {
892
+ videoTrack.attach(videoRef.current);
893
+ console.debug("[ReactorView] Video track attached successfully");
894
+ } catch (error) {
895
+ console.error("[ReactorView] Failed to attach video track:", error);
896
+ }
897
+ return () => {
898
+ console.debug("[ReactorView] Detaching video track from element");
899
+ if (videoRef.current) {
900
+ try {
901
+ videoTrack.detach(videoRef.current);
902
+ console.debug("[ReactorView] Video track detached successfully");
903
+ } catch (error) {
904
+ console.error("[ReactorView] Failed to detach video track:", error);
905
+ }
906
+ }
907
+ };
908
+ } else {
909
+ console.debug("[ReactorView] No video track or element to attach");
910
+ }
911
+ }, [videoTrack]);
912
+ const showPlaceholder = !videoTrack;
913
+ console.debug("[ReactorView] Placeholder state", { showPlaceholder, status });
914
+ return /* @__PURE__ */ jsxs(
915
+ "div",
916
+ {
917
+ style: __spreadValues(__spreadValues(__spreadValues({
918
+ position: "relative",
919
+ background: "#000"
920
+ }, width && { width }), height && { height }), style),
921
+ className,
922
+ children: [
923
+ /* @__PURE__ */ jsx2(
924
+ "video",
925
+ {
926
+ ref: videoRef,
927
+ style: {
928
+ width: "100%",
929
+ height: "100%",
930
+ objectFit: "contain",
931
+ display: showPlaceholder ? "none" : "block"
932
+ },
933
+ muted: true,
934
+ playsInline: true
935
+ }
936
+ ),
937
+ showPlaceholder && /* @__PURE__ */ jsx2(
938
+ "div",
939
+ {
940
+ style: {
941
+ position: "absolute",
942
+ top: 0,
943
+ left: 0,
944
+ width: "100%",
945
+ height: "100%",
946
+ color: "#fff",
947
+ display: "flex",
948
+ alignItems: "center",
949
+ justifyContent: "center",
950
+ fontSize: "16px",
951
+ fontFamily: "monospace",
952
+ textAlign: "center",
953
+ padding: "20px",
954
+ boxSizing: "border-box"
955
+ },
956
+ children: status
957
+ }
958
+ )
959
+ ]
960
+ }
961
+ );
962
+ }
963
+ export {
964
+ Reactor,
965
+ ReactorProvider,
966
+ ReactorView,
967
+ useReactor,
968
+ useReactorMessage,
969
+ useReactorStore
970
+ };
971
+ //# sourceMappingURL=index.mjs.map