vg-x07df 1.2.0 → 1.3.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
@@ -2,7 +2,10 @@ import React, { createContext, useMemo, useEffect, useContext, useState, useRef
2
2
  import { io } from 'socket.io-client';
3
3
  import { create } from 'zustand';
4
4
  import { immer } from 'zustand/middleware/immer';
5
+ import mitt from 'mitt';
5
6
  import { z } from 'zod';
7
+ import { Room } from 'livekit-client';
8
+ import { useParticipants, useParticipantInfo } from '@livekit/components-react';
6
9
 
7
10
  // src/provider/RtcProvider.tsx
8
11
 
@@ -128,10 +131,10 @@ function setGlobalLoggerOptions(options) {
128
131
 
129
132
  // src/state/types.ts
130
133
  var defaultState = {
131
- session: { status: "IDLE", initiatedByMe: false },
132
- room: {
133
- participants: {}
134
- },
134
+ initiated: false,
135
+ session: null,
136
+ incomingInvite: null,
137
+ outgoingInvites: {},
135
138
  errors: []
136
139
  };
137
140
 
@@ -154,14 +157,14 @@ var useRtcStore = create()(
154
157
  var rtcStore = useRtcStore;
155
158
 
156
159
  // src/state/errors.ts
157
- function pushError(code, message, context, logger3) {
160
+ function pushError(code, message, context, logger4) {
158
161
  const error = {
159
162
  code,
160
163
  message,
161
164
  timestamp: Date.now(),
162
165
  context
163
166
  };
164
- logger3?.("error", message, { code, context });
167
+ logger4?.("error", message, { code, context });
165
168
  rtcStore.getState().addError(error);
166
169
  }
167
170
  function clearErrors(predicate) {
@@ -173,73 +176,73 @@ function clearErrors(predicate) {
173
176
  }
174
177
  });
175
178
  }
176
- function pushSocketValidationError(eventType, issues, payload, logger3) {
179
+ function pushSocketValidationError(eventType, issues, payload, logger4) {
177
180
  pushError(
178
181
  "SOCKET_PAYLOAD",
179
182
  `Invalid ${eventType} event payload`,
180
183
  { eventType, issues, payload },
181
- logger3
184
+ logger4
182
185
  );
183
186
  }
184
- function pushIdentityGuardError(reason, expected, received, logger3) {
187
+ function pushIdentityGuardError(reason, expected, received, logger4) {
185
188
  pushError(
186
189
  "JOIN_FLOW",
187
190
  // Use new error code
188
191
  `Identity guard failed: ${reason}`,
189
192
  { expected, received },
190
- logger3
193
+ logger4
191
194
  );
192
195
  }
193
- function pushLiveKitConnectError(message, error, logger3) {
196
+ function pushLiveKitConnectError(message, error, logger4) {
194
197
  pushError(
195
198
  "LIVEKIT_CONNECT",
196
199
  `LiveKit connection failed: ${message}`,
197
200
  { originalError: error },
198
- logger3
201
+ logger4
199
202
  );
200
203
  }
201
- function pushStaleEventError(eventType, reason, context, logger3) {
204
+ function pushStaleEventError(eventType, reason, context, logger4) {
202
205
  pushError(
203
206
  "JOIN_FLOW",
204
207
  // Use new error code
205
208
  `Ignored stale ${eventType} event: ${reason}`,
206
209
  context,
207
- logger3
210
+ logger4
208
211
  );
209
212
  }
210
- function pushApiError(operation, error, logger3) {
213
+ function pushApiError(operation, error, logger4) {
211
214
  const errorMessage = error instanceof Error ? error.message : String(error);
212
215
  pushError(
213
216
  "API_ERROR",
214
217
  `API ${operation} failed: ${errorMessage}`,
215
218
  { operation, originalError: error },
216
- logger3
219
+ logger4
217
220
  );
218
221
  }
219
- function pushNetworkError(operation, error, logger3) {
222
+ function pushNetworkError(operation, error, logger4) {
220
223
  const errorMessage = error instanceof Error ? error.message : String(error);
221
224
  pushError(
222
225
  "NETWORK",
223
226
  `Network error during ${operation}: ${errorMessage}`,
224
227
  { operation, originalError: error },
225
- logger3
228
+ logger4
226
229
  );
227
230
  }
228
- function pushMediaPermissionError(device, error, logger3) {
231
+ function pushMediaPermissionError(device, error, logger4) {
229
232
  pushError(
230
233
  "MEDIA_PERMISSION",
231
234
  `${device} permission denied`,
232
235
  { device, originalError: error },
233
- logger3
236
+ logger4
234
237
  );
235
238
  }
236
- function pushDeviceError(operation, device, error, logger3) {
239
+ function pushDeviceError(operation, device, error, logger4) {
237
240
  const errorMessage = error instanceof Error ? error.message : String(error);
238
241
  pushError(
239
242
  "DEVICE_SWITCH",
240
243
  `Failed to ${operation} ${device}: ${errorMessage}`,
241
244
  { operation, device, originalError: error },
242
- logger3
245
+ logger4
243
246
  );
244
247
  }
245
248
 
@@ -300,248 +303,37 @@ var BaseSocketHandler = class {
300
303
  return this.options.livekit;
301
304
  }
302
305
  };
303
-
304
- // src/utils/participant-adapter.ts
305
- function findCaller(participants) {
306
- return participants.find((p) => p.role === "CALLER" || p.role === "HOST") || null;
307
- }
308
- function extractCallerInfo(participants) {
309
- const caller = findCaller(participants);
310
- if (!caller) return null;
311
- const result = {
312
- id: caller.id,
313
- name: [caller.firstName, caller.lastName].filter(Boolean).join(" ") || `Guest ${caller.id}`
314
- };
315
- if (caller.profilePhoto) {
316
- result.avatarUrl = caller.profilePhoto;
317
- }
318
- return result;
319
- }
320
-
321
- // src/core/events/event-bus.ts
322
- var logger = createLogger("core:event-bus");
323
306
  var EventBus = class {
324
- constructor(debugMode = false) {
325
- this.listeners = /* @__PURE__ */ new Map();
326
- this.isDebugMode = false;
327
- this.eventHistory = [];
328
- this.maxHistorySize = 100;
329
- this.isDebugMode = debugMode;
330
- }
331
- /**
332
- * Subscribe to events of a specific type
333
- */
334
- on(eventType, handler, filter) {
335
- const actualHandler = filter ? (event) => {
336
- if (filter(event)) {
337
- handler(event);
338
- }
339
- } : handler;
340
- if (!this.listeners.has(eventType)) {
341
- this.listeners.set(eventType, /* @__PURE__ */ new Set());
342
- }
343
- this.listeners.get(eventType)?.add(actualHandler);
344
- if (this.isDebugMode) {
345
- logger.debug(`Event listener added for: ${eventType}`, {
346
- eventType,
347
- totalListeners: this.listeners.get(eventType)?.size
348
- });
349
- }
350
- return {
351
- unsubscribe: () => {
352
- const handlers = this.listeners.get(eventType);
353
- if (handlers) {
354
- handlers.delete(actualHandler);
355
- if (handlers.size === 0) {
356
- this.listeners.delete(eventType);
357
- }
358
- }
359
- if (this.isDebugMode) {
360
- logger.debug(`Event listener removed for: ${eventType}`, {
361
- eventType,
362
- remainingListeners: handlers?.size || 0
363
- });
364
- }
365
- }
366
- };
307
+ constructor() {
308
+ this.emitter = mitt();
367
309
  }
368
- /**
369
- * Subscribe to events matching a pattern (e.g., "call:*")
370
- */
371
- onPattern(pattern, handler, filter) {
372
- const regex = new RegExp(pattern.replace(/\*/g, ".*"));
373
- const patternHandler = (event) => {
374
- if (regex.test(event.type)) {
375
- if (filter && !filter(event)) return;
376
- handler(event);
377
- }
310
+ on(eventType, handler) {
311
+ const wrappedHandler = (event) => {
312
+ handler(event);
378
313
  };
379
- const patternKey = `__pattern__${pattern}`;
380
- if (!this.listeners.has(patternKey)) {
381
- this.listeners.set(patternKey, /* @__PURE__ */ new Set());
382
- }
383
- this.listeners.get(patternKey)?.add(patternHandler);
384
- if (this.isDebugMode) {
385
- logger.debug(`Pattern listener added: ${pattern}`, { pattern });
386
- }
314
+ this.emitter.on(eventType.toString(), wrappedHandler);
387
315
  return {
388
316
  unsubscribe: () => {
389
- const handlers = this.listeners.get(patternKey);
390
- if (handlers) {
391
- handlers.delete(patternHandler);
392
- if (handlers.size === 0) {
393
- this.listeners.delete(patternKey);
394
- }
395
- }
396
- if (this.isDebugMode) {
397
- logger.debug(`Pattern listener removed: ${pattern}`, { pattern });
398
- }
317
+ this.emitter.off(eventType.toString(), wrappedHandler);
399
318
  }
400
319
  };
401
320
  }
402
- /**
403
- * Subscribe to a single event occurrence
404
- */
405
- once(eventType, handler, filter) {
406
- const subscription = this.on(
407
- eventType,
408
- (event) => {
409
- handler(event);
410
- subscription.unsubscribe();
411
- },
412
- filter
413
- );
414
- return subscription;
415
- }
416
- /**
417
- * Emit an event
418
- */
419
- emit(eventType, payload, source = "user") {
321
+ emit(eventType, payload) {
420
322
  const event = {
421
323
  type: eventType.toString(),
422
324
  payload,
423
- timestamp: Date.now(),
424
- source
325
+ timestamp: Date.now()
425
326
  };
426
- this.addToHistory(event);
427
- if (this.isDebugMode) {
428
- logger.debug(`Event emitted: ${eventType}`, {
429
- eventType,
430
- payload,
431
- source,
432
- timestamp: event.timestamp
433
- });
434
- }
435
- const exactListeners = this.listeners.get(eventType.toString());
436
- if (exactListeners) {
437
- for (const handler of exactListeners) {
438
- try {
439
- handler(event);
440
- } catch (error) {
441
- logger.error(`Error in event handler for ${eventType}`, {
442
- error,
443
- eventType,
444
- payload
445
- });
446
- }
447
- }
448
- }
449
- this.listeners.forEach((handlers, key) => {
450
- if (key.startsWith("__pattern__")) {
451
- for (const handler of handlers) {
452
- try {
453
- handler(event);
454
- } catch (error) {
455
- logger.error(`Error in pattern handler for ${eventType}`, {
456
- error,
457
- eventType,
458
- pattern: key,
459
- payload
460
- });
461
- }
462
- }
463
- }
464
- });
327
+ this.emitter.emit(eventType.toString(), event);
465
328
  }
466
- /**
467
- * Remove all listeners for a specific event type
468
- */
469
329
  off(eventType) {
470
- this.listeners.delete(eventType.toString());
471
- if (this.isDebugMode) {
472
- logger.debug(`All listeners removed for: ${eventType}`, { eventType });
473
- }
330
+ this.emitter.off(eventType.toString());
474
331
  }
475
- /**
476
- * Remove all listeners
477
- */
478
332
  removeAllListeners() {
479
- const totalListeners = Array.from(this.listeners.values()).reduce(
480
- (sum, handlers) => sum + handlers.size,
481
- 0
482
- );
483
- this.listeners.clear();
484
- if (this.isDebugMode) {
485
- logger.debug("All listeners removed", { removedCount: totalListeners });
486
- }
487
- }
488
- /**
489
- * Get the count of listeners for an event type
490
- */
491
- listenerCount(eventType) {
492
- return this.listeners.get(eventType.toString())?.size || 0;
493
- }
494
- /**
495
- * Get all event types that have listeners
496
- */
497
- getEventTypes() {
498
- return Array.from(this.listeners.keys());
499
- }
500
- /**
501
- * Enable or disable debug mode
502
- */
503
- setDebugMode(enabled) {
504
- this.isDebugMode = enabled;
505
- logger.debug(`Debug mode ${enabled ? "enabled" : "disabled"}`);
506
- }
507
- /**
508
- * Get event history for debugging
509
- */
510
- getEventHistory() {
511
- return [...this.eventHistory];
512
- }
513
- /**
514
- * Clear event history
515
- */
516
- clearHistory() {
517
- this.eventHistory = [];
518
- if (this.isDebugMode) {
519
- logger.debug("Event history cleared");
520
- }
521
- }
522
- /**
523
- * Get events matching a filter
524
- */
525
- getEventsWhere(filter) {
526
- return this.eventHistory.filter(filter);
527
- }
528
- addToHistory(event) {
529
- this.eventHistory.push(event);
530
- if (this.eventHistory.length > this.maxHistorySize) {
531
- this.eventHistory.shift();
532
- }
533
- }
534
- /**
535
- * Set maximum history size
536
- */
537
- setMaxHistorySize(size) {
538
- this.maxHistorySize = size;
539
- if (this.eventHistory.length > size) {
540
- this.eventHistory = this.eventHistory.slice(-size);
541
- }
333
+ this.emitter.all.clear();
542
334
  }
543
335
  };
544
- var eventBus = new EventBus(false);
336
+ var eventBus = new EventBus();
545
337
 
546
338
  // src/core/events/types.ts
547
339
  var SdkEventType = /* @__PURE__ */ ((SdkEventType2) => {
@@ -566,163 +358,482 @@ var SdkEventType = /* @__PURE__ */ ((SdkEventType2) => {
566
358
  SdkEventType2["ERROR_OCCURRED"] = "error:occurred";
567
359
  return SdkEventType2;
568
360
  })(SdkEventType || {});
569
- var socketParticipantSchema = z.object({
570
- id: z.string(),
571
- firstName: z.string().nullable(),
572
- lastName: z.string().nullable(),
573
- username: z.string().nullable(),
574
- profilePhoto: z.string().nullable(),
575
- role: z.enum(["CALLER", "CALLEE", "HOST", "MEMBER"]).optional()
576
- });
577
- var callIncomingSchema = z.object({
361
+ var callCancelledSchema = z.object({
578
362
  callId: z.string(),
579
- type: z.enum(["AUDIO", "VIDEO"]),
580
- participants: z.array(socketParticipantSchema).min(1),
581
- // Required, at least 1 participant
582
- timestamp: z.number()
583
- });
584
- var callParticipantAcceptedSchema = z.object({
585
- callId: z.string(),
586
- participantId: z.string(),
587
- participant: socketParticipantSchema,
588
- acceptedAt: z.string().optional()
589
- });
590
- var callJoinInfoSchema = z.object({
363
+ status: z.literal("cancelled"),
364
+ cancelledAt: z.string()
365
+ }).strict();
366
+ var callCreatedSchema = z.object({
591
367
  callId: z.string(),
592
- token: z.string(),
593
- url: z.string().optional(),
368
+ status: z.literal("pending"),
594
369
  roomName: z.string(),
595
- expiresAt: z.number().optional()
596
- });
370
+ createdAt: z.string(),
371
+ host: z.object({
372
+ firstName: z.string().nullable(),
373
+ lastName: z.string().nullable(),
374
+ username: z.string().nullable(),
375
+ email: z.string().nullable(),
376
+ profilePhoto: z.string().nullable(),
377
+ userId: z.string()
378
+ }).strict(),
379
+ participants: z.array(z.object({
380
+ firstName: z.string().nullable(),
381
+ lastName: z.string().nullable(),
382
+ username: z.string().nullable(),
383
+ email: z.string().nullable(),
384
+ profilePhoto: z.string().nullable(),
385
+ userId: z.string()
386
+ }).strict())
387
+ }).strict();
597
388
  var callEndedSchema = z.object({
598
389
  callId: z.string(),
599
- reason: z.string().optional(),
600
- endedAt: z.string().optional()
601
- }).passthrough();
602
- var callTimeoutSchema = z.object({
390
+ endedAt: z.string(),
391
+ endedByUserId: z.string().optional(),
392
+ reason: z.string().optional()
393
+ }).strict();
394
+ var callInviteSchema = z.object({
603
395
  callId: z.string(),
604
- reason: z.string().optional(),
605
- timestamp: z.number().optional()
606
- });
607
- var callParticipantDeclinedSchema = z.object({
396
+ status: z.enum(["pending", "active", "ended"]),
397
+ caller: z.object({
398
+ firstName: z.string().nullable(),
399
+ lastName: z.string().nullable(),
400
+ username: z.string().nullable(),
401
+ email: z.string().nullable(),
402
+ profilePhoto: z.string().nullable(),
403
+ userId: z.string()
404
+ }).strict(),
405
+ mode: z.enum(["VIDEO", "AUDIO"]),
406
+ expiresAt: z.string(),
407
+ expiresInMs: z.number()
408
+ }).strict();
409
+ var callInviteAcceptedSchema = z.object({
410
+ callId: z.string(),
411
+ status: z.enum(["pending", "active", "ended"]),
412
+ participant: z.object({
413
+ firstName: z.string().nullable(),
414
+ lastName: z.string().nullable(),
415
+ username: z.string().nullable(),
416
+ email: z.string().nullable(),
417
+ profilePhoto: z.string().nullable(),
418
+ userId: z.string()
419
+ }).strict(),
420
+ acceptedAt: z.string()
421
+ }).strict();
422
+ var callInviteCancelledSchema = z.object({
608
423
  callId: z.string(),
609
- participantId: z.string(),
610
- participant: socketParticipantSchema,
611
- declinedAt: z.string().optional()
612
- });
613
- var callCanceledSchema = z.object({
424
+ status: z.enum(["pending", "active", "ended"]),
425
+ cancelledByUserId: z.string(),
426
+ cancelledAt: z.string(),
427
+ reason: z.string()
428
+ }).strict();
429
+ var callInviteDeclinedSchema = z.object({
614
430
  callId: z.string(),
431
+ status: z.enum(["pending", "active", "ended"]),
432
+ participant: z.object({
433
+ firstName: z.string().nullable(),
434
+ lastName: z.string().nullable(),
435
+ username: z.string().nullable(),
436
+ email: z.string().nullable(),
437
+ profilePhoto: z.string().nullable(),
438
+ userId: z.string()
439
+ }).strict(),
615
440
  reason: z.string().optional(),
616
- timestamp: z.number().optional(),
617
- by: socketParticipantSchema.optional()
618
- });
441
+ declinedAt: z.string()
442
+ }).strict();
443
+ var callInviteMissedSchema = z.object({
444
+ callId: z.string(),
445
+ status: z.enum(["pending", "active", "ended"]),
446
+ participant: z.object({
447
+ firstName: z.string().nullable(),
448
+ lastName: z.string().nullable(),
449
+ username: z.string().nullable(),
450
+ email: z.string().nullable(),
451
+ profilePhoto: z.string().nullable(),
452
+ userId: z.string()
453
+ }).strict(),
454
+ missedAt: z.string()
455
+ }).strict();
456
+ var callInviteSentSchema = z.object({
457
+ callId: z.string(),
458
+ invitee: z.object({
459
+ firstName: z.string().nullable(),
460
+ lastName: z.string().nullable(),
461
+ username: z.string().nullable(),
462
+ email: z.string().nullable(),
463
+ profilePhoto: z.string().nullable(),
464
+ userId: z.string()
465
+ }).strict(),
466
+ status: z.literal("sent")
467
+ }).strict();
468
+ var callJoinInfoSchema = z.object({
469
+ callId: z.string(),
470
+ token: z.string(),
471
+ roomName: z.string(),
472
+ lkUrl: z.string(),
473
+ role: z.enum(["HOST", "PARTICIPANT", "GUEST"]),
474
+ participantId: z.string().optional()
475
+ }).strict();
476
+ var callMissedSchema = z.object({
477
+ callId: z.string(),
478
+ endedAt: z.string()
479
+ }).strict();
480
+ var callParticipantAddedSchema = z.object({
481
+ callId: z.string(),
482
+ status: z.enum(["pending", "active", "ended"]),
483
+ participant: z.object({
484
+ participantId: z.string(),
485
+ userId: z.string(),
486
+ firstName: z.string().optional(),
487
+ lastName: z.string().optional(),
488
+ username: z.string().optional(),
489
+ email: z.string().optional(),
490
+ profilePhoto: z.string().optional(),
491
+ role: z.enum(["HOST", "PARTICIPANT", "GUEST"]),
492
+ joinState: z.enum(["not_joined", "joined"])
493
+ }).strict()
494
+ }).strict();
619
495
 
620
- // src/core/socketio/handlers/call-incoming.handler.ts
621
- var CallIncomingHandler = class extends BaseSocketHandler {
496
+ // src/core/socketio/handlers/invite.handler.ts
497
+ var InviteHandler = class extends BaseSocketHandler {
622
498
  constructor() {
623
499
  super(...arguments);
624
- this.eventName = "call.incoming";
625
- this.schema = callIncomingSchema;
500
+ this.eventName = "call:invite";
501
+ this.schema = callInviteSchema;
626
502
  }
627
503
  handle(data) {
628
- const callerInfo = extractCallerInfo(data.participants);
629
- if (!callerInfo) {
630
- this.logger.error("No active caller found in participants", data);
631
- return;
632
- }
504
+ this.logger.info("Incoming call invitation", {
505
+ callId: data.callId,
506
+ caller: data.caller.userId,
507
+ mode: data.mode
508
+ });
633
509
  this.updateStore((state) => {
510
+ state.incomingInvite = {
511
+ callId: data.callId,
512
+ caller: {
513
+ userId: data.caller.userId,
514
+ firstName: data.caller.firstName,
515
+ lastName: data.caller.lastName,
516
+ username: data.caller.username,
517
+ email: data.caller.email,
518
+ profilePhoto: data.caller.profilePhoto
519
+ },
520
+ mode: data.mode,
521
+ expiresAt: data.expiresAt,
522
+ expiresInMs: data.expiresInMs
523
+ };
634
524
  state.session = {
635
525
  id: data.callId,
636
- status: "RINGING",
637
- mode: data.type,
638
- initiatedByMe: false
526
+ status: "pending",
527
+ mode: data.mode,
528
+ role: "PARTICIPANT"
639
529
  };
640
- state.room.participants = {};
641
530
  });
642
- eventBus.emit(
643
- "call:incoming" /* CALL_INCOMING */,
644
- {
645
- callId: data.callId,
646
- caller: callerInfo,
647
- type: data.type,
648
- timestamp: data.timestamp,
649
- participants: data.participants
531
+ eventBus.emit("call:incoming" /* CALL_INCOMING */, {
532
+ callId: data.callId,
533
+ caller: {
534
+ id: data.caller.userId,
535
+ firstName: data.caller.firstName,
536
+ lastName: data.caller.lastName,
537
+ avatarUrl: data.caller.profilePhoto
650
538
  },
651
- "socket"
652
- );
653
- this.logger.debug("Incoming call event emitted", {
539
+ type: data.mode,
540
+ timestamp: Date.now()
541
+ });
542
+ this.logger.debug("Incoming invite state updated");
543
+ }
544
+ };
545
+
546
+ // src/core/socketio/handlers/invite-sent.handler.ts
547
+ var InviteSentHandler = class extends BaseSocketHandler {
548
+ constructor() {
549
+ super(...arguments);
550
+ this.eventName = "call:inviteSent";
551
+ this.schema = callInviteSentSchema;
552
+ }
553
+ handle(data) {
554
+ const currentState = rtcStore.getState();
555
+ this.logger.info("Invitation sent to participant", {
556
+ callId: data.callId,
557
+ invitee: data.invitee.userId
558
+ });
559
+ if (currentState.session?.id !== data.callId) {
560
+ pushStaleEventError("call:inviteSent", "callId mismatch", {
561
+ eventCallId: data.callId,
562
+ sessionCallId: currentState.session?.id
563
+ });
564
+ this.logger.warn("Ignoring invite sent for different call", { callId: data.callId });
565
+ return;
566
+ }
567
+ this.updateStore((state) => {
568
+ const userId = data.invitee.userId;
569
+ state.outgoingInvites[userId] = {
570
+ userId,
571
+ status: "sent",
572
+ participant: {
573
+ userId: data.invitee.userId,
574
+ firstName: data.invitee.firstName,
575
+ lastName: data.invitee.lastName,
576
+ username: data.invitee.username,
577
+ email: data.invitee.email,
578
+ profilePhoto: data.invitee.profilePhoto
579
+ }
580
+ };
581
+ });
582
+ eventBus.emit("participant:invited" /* PARTICIPANT_INVITED */, {
583
+ callId: data.callId,
584
+ participant: {
585
+ id: data.invitee.userId,
586
+ firstName: data.invitee.firstName,
587
+ lastName: data.invitee.lastName,
588
+ avatarUrl: data.invitee.profilePhoto
589
+ },
590
+ timestamp: Date.now()
591
+ });
592
+ this.logger.debug("Outgoing invite tracked", {
593
+ userId: data.invitee.userId
594
+ });
595
+ }
596
+ };
597
+
598
+ // src/core/socketio/handlers/invite-accepted.handler.ts
599
+ var InviteAcceptedHandler = class extends BaseSocketHandler {
600
+ constructor() {
601
+ super(...arguments);
602
+ this.eventName = "call:inviteAccepted";
603
+ this.schema = callInviteAcceptedSchema;
604
+ }
605
+ handle(data) {
606
+ const currentState = rtcStore.getState();
607
+ this.logger.info("Participant accepted invitation", {
654
608
  callId: data.callId,
655
- caller: callerInfo.name,
656
- type: data.type
609
+ participant: data.participant.userId
610
+ });
611
+ if (currentState.session?.id !== data.callId) {
612
+ pushStaleEventError("call:inviteAccepted", "callId mismatch", {
613
+ eventCallId: data.callId,
614
+ sessionCallId: currentState.session?.id
615
+ });
616
+ this.logger.warn("Ignoring accept event for different call", { callId: data.callId });
617
+ return;
618
+ }
619
+ this.updateStore((state) => {
620
+ const userId = data.participant.userId;
621
+ if (state.outgoingInvites[userId]) {
622
+ state.outgoingInvites[userId].status = "accepted";
623
+ } else {
624
+ state.outgoingInvites[userId] = {
625
+ userId,
626
+ status: "accepted",
627
+ participant: {
628
+ userId: data.participant.userId,
629
+ firstName: data.participant.firstName,
630
+ lastName: data.participant.lastName,
631
+ username: data.participant.username,
632
+ email: data.participant.email,
633
+ profilePhoto: data.participant.profilePhoto
634
+ }
635
+ };
636
+ }
637
+ });
638
+ this.logger.debug("Outgoing invite marked as accepted", {
639
+ userId: data.participant.userId
657
640
  });
658
641
  }
659
642
  };
660
643
 
661
- // src/core/socketio/handlers/call-accepted.handler.ts
662
- var CallParticipantAcceptedHandler = class extends BaseSocketHandler {
644
+ // src/core/socketio/handlers/invite-declined.handler.ts
645
+ var InviteDeclinedHandler = class extends BaseSocketHandler {
663
646
  constructor() {
664
647
  super(...arguments);
665
- this.eventName = "call.participant-accepted";
666
- this.schema = callParticipantAcceptedSchema;
648
+ this.eventName = "call:inviteDeclined";
649
+ this.schema = callInviteDeclinedSchema;
667
650
  }
668
- async handle(data) {
651
+ handle(data) {
669
652
  const currentState = rtcStore.getState();
670
- if (currentState.session.id !== data.callId) {
671
- pushStaleEventError("call.participant-accepted", "callId mismatch", {
653
+ this.logger.info("Participant declined invitation", {
654
+ callId: data.callId,
655
+ participant: data.participant.userId,
656
+ reason: data.reason
657
+ });
658
+ if (currentState.session?.id !== data.callId) {
659
+ pushStaleEventError("call:inviteDeclined", "callId mismatch", {
672
660
  eventCallId: data.callId,
673
- sessionCallId: currentState.session.id
661
+ sessionCallId: currentState.session?.id
674
662
  });
663
+ this.logger.warn("Ignoring decline event for different call", { callId: data.callId });
675
664
  return;
676
665
  }
677
666
  this.updateStore((state) => {
678
- state.session.status = "ACCEPTED";
667
+ const userId = data.participant.userId;
668
+ if (state.outgoingInvites[userId]) {
669
+ state.outgoingInvites[userId].status = "declined";
670
+ } else {
671
+ state.outgoingInvites[userId] = {
672
+ userId,
673
+ status: "declined",
674
+ participant: {
675
+ userId: data.participant.userId,
676
+ firstName: data.participant.firstName,
677
+ lastName: data.participant.lastName,
678
+ username: data.participant.username,
679
+ email: data.participant.email,
680
+ profilePhoto: data.participant.profilePhoto
681
+ }
682
+ };
683
+ }
684
+ });
685
+ eventBus.emit("call:declined" /* CALL_DECLINED */, {
686
+ callId: data.callId,
687
+ participantId: data.participant.userId,
688
+ reason: data.reason,
689
+ timestamp: Date.now()
690
+ });
691
+ this.logger.debug("Outgoing invite marked as declined", {
692
+ userId: data.participant.userId
679
693
  });
680
694
  }
681
695
  };
682
696
 
683
- // src/core/socketio/handlers/call-declined.handler.ts
684
- var CallParticipantDeclinedHandler = class extends BaseSocketHandler {
697
+ // src/core/socketio/handlers/invite-missed.handler.ts
698
+ var InviteMissedHandler = class extends BaseSocketHandler {
685
699
  constructor() {
686
700
  super(...arguments);
687
- this.eventName = "call.participant-declined";
688
- this.schema = callParticipantDeclinedSchema;
701
+ this.eventName = "call:inviteMissed";
702
+ this.schema = callInviteMissedSchema;
689
703
  }
690
704
  handle(data) {
705
+ const currentState = rtcStore.getState();
706
+ this.logger.info("Participant missed invitation", {
707
+ callId: data.callId,
708
+ participant: data.participant.userId
709
+ });
710
+ if (currentState.session?.id !== data.callId) {
711
+ pushStaleEventError("call:inviteMissed", "callId mismatch", {
712
+ eventCallId: data.callId,
713
+ sessionCallId: currentState.session?.id
714
+ });
715
+ this.logger.warn("Ignoring missed invite for different call", { callId: data.callId });
716
+ return;
717
+ }
691
718
  this.updateStore((state) => {
692
- if (state.session.id === data.callId) {
693
- state.session.status = "IDLE";
719
+ const userId = data.participant.userId;
720
+ if (state.outgoingInvites[userId]) {
721
+ state.outgoingInvites[userId].status = "missed";
722
+ } else {
723
+ state.outgoingInvites[userId] = {
724
+ userId,
725
+ status: "missed",
726
+ participant: {
727
+ userId: data.participant.userId,
728
+ firstName: data.participant.firstName,
729
+ lastName: data.participant.lastName,
730
+ username: data.participant.username,
731
+ email: data.participant.email,
732
+ profilePhoto: data.participant.profilePhoto
733
+ }
734
+ };
694
735
  }
695
736
  });
696
- eventBus.emit(
697
- "call:declined" /* CALL_DECLINED */,
698
- {
699
- callId: data.callId,
700
- participantId: data.participantId,
701
- reason: "declined",
702
- timestamp: Date.now()
703
- },
704
- "socket"
705
- );
737
+ this.logger.debug("Outgoing invite marked as missed", {
738
+ userId: data.participant.userId
739
+ });
740
+ }
741
+ };
742
+
743
+ // src/core/socketio/handlers/invite-cancelled.handler.ts
744
+ var InviteCancelledHandler = class extends BaseSocketHandler {
745
+ constructor() {
746
+ super(...arguments);
747
+ this.eventName = "call:inviteCancelled";
748
+ this.schema = callInviteCancelledSchema;
749
+ }
750
+ handle(data) {
751
+ const currentState = rtcStore.getState();
752
+ this.logger.info("Invitation cancelled by host", {
753
+ callId: data.callId,
754
+ cancelledBy: data.cancelledByUserId,
755
+ reason: data.reason
756
+ });
757
+ if (currentState.incomingInvite?.callId !== data.callId) {
758
+ this.logger.debug("Ignoring cancelled invite for different call", {
759
+ callId: data.callId
760
+ });
761
+ return;
762
+ }
763
+ this.updateStore((state) => {
764
+ state.incomingInvite = null;
765
+ state.session = null;
766
+ });
767
+ eventBus.emit("call:canceled" /* CALL_CANCELED */, {
768
+ callId: data.callId,
769
+ reason: data.reason,
770
+ timestamp: Date.now()
771
+ });
772
+ this.logger.debug("Incoming invite cleared due to cancellation");
773
+ }
774
+ };
775
+
776
+ // src/core/socketio/handlers/call-created.handler.ts
777
+ var SessionCreatedHandler = class extends BaseSocketHandler {
778
+ constructor() {
779
+ super(...arguments);
780
+ this.eventName = "call:created";
781
+ this.schema = callCreatedSchema;
782
+ }
783
+ handle(data) {
784
+ this.logger.info("Call session created", {
785
+ callId: data.callId,
786
+ roomName: data.roomName,
787
+ host: data.host.userId,
788
+ participantCount: data.participants.length
789
+ });
790
+ this.updateStore((state) => {
791
+ state.initiated = true;
792
+ state.session = {
793
+ id: data.callId,
794
+ status: "pending",
795
+ mode: "VIDEO",
796
+ role: "HOST"
797
+ };
798
+ state.outgoingInvites = {};
799
+ });
800
+ eventBus.emit("call:initiated" /* CALL_INITIATED */, {
801
+ callId: data.callId,
802
+ participants: data.participants.map((p) => p.userId),
803
+ type: "VIDEO",
804
+ // Default, TODO: get from API request context
805
+ timestamp: Date.now()
806
+ });
807
+ this.logger.debug("Session created for outbound call");
706
808
  }
707
809
  };
708
810
 
709
811
  // src/core/socketio/handlers/call-ended.handler.ts
710
- var CallEndedHandler = class extends BaseSocketHandler {
812
+ var SessionEndedHandler = class extends BaseSocketHandler {
711
813
  constructor() {
712
814
  super(...arguments);
713
- this.eventName = "call.ended";
815
+ this.eventName = "call:ended";
714
816
  this.schema = callEndedSchema;
715
817
  }
716
818
  handle(data) {
819
+ const currentState = rtcStore.getState();
820
+ this.logger.info("Call session ended", {
821
+ callId: data.callId,
822
+ endedBy: data.endedByUserId,
823
+ reason: data.reason
824
+ });
825
+ if (currentState.session?.id !== data.callId) {
826
+ pushStaleEventError("call:ended", "callId mismatch", {
827
+ eventCallId: data.callId,
828
+ sessionCallId: currentState.session?.id
829
+ });
830
+ this.logger.warn("Ignoring end event for different call", { callId: data.callId });
831
+ return;
832
+ }
717
833
  this.updateStore((state) => {
718
- if (state.session.id !== data.callId) {
719
- pushStaleEventError("call.ended", "callId mismatch", {
720
- eventCallId: data.callId,
721
- sessionCallId: state.session.id
722
- });
723
- return;
724
- }
725
- state.session.status = "ENDED";
834
+ state.initiated = false;
835
+ state.session = null;
836
+ state.outgoingInvites = {};
726
837
  state.room.participants = {};
727
838
  });
728
839
  if (this.livekit) {
@@ -730,83 +841,165 @@ var CallEndedHandler = class extends BaseSocketHandler {
730
841
  this.logger.error("Error disconnecting from LiveKit", { error });
731
842
  });
732
843
  }
844
+ eventBus.emit("call:ended" /* CALL_ENDED */, {
845
+ callId: data.callId,
846
+ endedBy: data.endedByUserId,
847
+ reason: "user",
848
+ timestamp: Date.now()
849
+ });
850
+ this.logger.debug("Session cleared and LiveKit disconnected");
733
851
  }
734
852
  };
735
853
 
736
- // src/core/socketio/handlers/call-join-info.handler.ts
737
- var CallJoinInfoHandler = class extends BaseSocketHandler {
854
+ // src/core/socketio/handlers/call-cancelled.handler.ts
855
+ var SessionCancelledHandler = class extends BaseSocketHandler {
738
856
  constructor() {
739
857
  super(...arguments);
740
- this.eventName = "call.join-info";
741
- this.schema = callJoinInfoSchema;
858
+ this.eventName = "call:cancelled";
859
+ this.schema = callCancelledSchema;
742
860
  }
743
- async handle(data) {
861
+ handle(data) {
862
+ const currentState = rtcStore.getState();
863
+ this.logger.info("Call session cancelled", {
864
+ callId: data.callId,
865
+ status: data.status
866
+ });
867
+ if (currentState.session?.id !== data.callId) {
868
+ pushStaleEventError("call:cancelled", "callId mismatch", {
869
+ eventCallId: data.callId,
870
+ sessionCallId: currentState.session?.id
871
+ });
872
+ this.logger.warn("Ignoring cancel event for different call", { callId: data.callId });
873
+ return;
874
+ }
744
875
  this.updateStore((state) => {
745
- state.session.livekitInfo = {
746
- token: data.token,
747
- url: data.url,
748
- roomName: data.roomName,
749
- callId: data.callId
750
- };
751
- state.session.status = "READY_TO_JOIN";
876
+ state.initiated = false;
877
+ state.session = null;
878
+ state.outgoingInvites = {};
879
+ state.room.participants = {};
752
880
  });
753
- eventBus.emit(
754
- "join-info:received" /* JOIN_INFO_RECEIVED */,
755
- {
756
- callId: data.callId,
757
- timestamp: Date.now(),
758
- url: data.url,
759
- roomName: data.roomName,
760
- token: data.token
761
- },
762
- "socket"
763
- );
881
+ eventBus.emit("call:canceled" /* CALL_CANCELED */, {
882
+ callId: data.callId,
883
+ reason: "cancelled by host",
884
+ timestamp: Date.now()
885
+ });
886
+ this.logger.debug("Session cleared due to cancellation");
764
887
  }
765
888
  };
766
889
 
767
- // src/core/socketio/handlers/call-timeout.handler.ts
768
- var CallTimeoutHandler = class extends BaseSocketHandler {
890
+ // src/core/socketio/handlers/call-missed.handler.ts
891
+ var SessionMissedHandler = class extends BaseSocketHandler {
769
892
  constructor() {
770
893
  super(...arguments);
771
- this.eventName = "call.timeout";
772
- this.schema = callTimeoutSchema;
894
+ this.eventName = "call:missed";
895
+ this.schema = callMissedSchema;
773
896
  }
774
897
  handle(data) {
775
- const reason = data.reason || "timeout";
776
- this.logger.info(`Call timeout: ${reason}`, { callId: data.callId });
898
+ const currentState = rtcStore.getState();
899
+ this.logger.info("Call missed by all participants", {
900
+ callId: data.callId
901
+ });
902
+ if (currentState.session?.id !== data.callId) {
903
+ pushStaleEventError("call:missed", "callId mismatch", {
904
+ eventCallId: data.callId,
905
+ sessionCallId: currentState.session?.id
906
+ });
907
+ this.logger.warn("Ignoring missed call for different session", { callId: data.callId });
908
+ return;
909
+ }
777
910
  this.updateStore((state) => {
778
- if (state.session.id === data.callId) {
779
- state.session.status = "ENDED";
780
- state.room.participants = {};
781
- }
911
+ state.initiated = false;
912
+ state.session = null;
913
+ state.outgoingInvites = {};
914
+ state.room.participants = {};
915
+ });
916
+ eventBus.emit("call:timeout" /* CALL_TIMEOUT */, {
917
+ callId: data.callId,
918
+ reason: "All participants missed/declined",
919
+ timestamp: Date.now()
782
920
  });
921
+ this.logger.debug("Session cleared - call was missed by all");
783
922
  }
784
923
  };
785
924
 
786
- // src/core/socketio/handlers/call-canceled.handler.ts
787
- var CallCanceledHandler = class extends BaseSocketHandler {
925
+ // src/core/socketio/handlers/join-info.handler.ts
926
+ var JoinInfoHandler = class extends BaseSocketHandler {
788
927
  constructor() {
789
928
  super(...arguments);
790
- this.eventName = "call.canceled";
791
- this.schema = callCanceledSchema;
929
+ this.eventName = "call:joinInfo";
930
+ this.schema = callJoinInfoSchema;
792
931
  }
793
932
  handle(data) {
794
- const reason = data.reason || "canceled";
795
- this.logger.info(`Call canceled: ${reason}`, {
933
+ const currentState = rtcStore.getState();
934
+ this.logger.info("Received LiveKit join info", {
796
935
  callId: data.callId,
797
- by: data.by?.id
936
+ roomName: data.roomName,
937
+ role: data.role
798
938
  });
939
+ if (currentState.session?.id !== data.callId) {
940
+ pushStaleEventError("call:joinInfo", "callId mismatch", {
941
+ eventCallId: data.callId,
942
+ sessionCallId: currentState.session?.id
943
+ });
944
+ this.logger.warn("Ignoring join info for unknown call", { callId: data.callId });
945
+ return;
946
+ }
799
947
  this.updateStore((state) => {
800
- if (state.session.id === data.callId) {
801
- state.session.status = "ENDED";
802
- state.room.participants = {};
948
+ if (state.session?.id === data.callId) {
949
+ state.session.livekitInfo = {
950
+ token: data.token,
951
+ roomName: data.roomName,
952
+ url: data.lkUrl
953
+ };
954
+ state.session.role = data.role;
803
955
  }
804
956
  });
957
+ eventBus.emit("join-info:received" /* JOIN_INFO_RECEIVED */, {
958
+ callId: data.callId,
959
+ participantId: data.participantId || "",
960
+ timestamp: Date.now(),
961
+ url: data.lkUrl,
962
+ token: data.token
963
+ });
964
+ this.logger.debug("Session updated with LiveKit credentials");
965
+ }
966
+ };
967
+
968
+ // src/core/socketio/handlers/participant-added.handler.ts
969
+ var ParticipantAddedHandler = class extends BaseSocketHandler {
970
+ constructor() {
971
+ super(...arguments);
972
+ this.eventName = "call:participantAdded";
973
+ this.schema = callParticipantAddedSchema;
974
+ }
975
+ handle(data) {
976
+ const currentState = rtcStore.getState();
977
+ this.logger.info("Participant added to call", {
978
+ callId: data.callId,
979
+ participantId: data.participant.participantId,
980
+ userId: data.participant.userId,
981
+ role: data.participant.role
982
+ });
983
+ if (currentState.session?.id !== data.callId) {
984
+ pushStaleEventError("call:participantAdded", "callId mismatch", {
985
+ eventCallId: data.callId,
986
+ sessionCallId: currentState.session?.id
987
+ });
988
+ this.logger.warn("Ignoring participant added for different call", { callId: data.callId });
989
+ return;
990
+ }
991
+ eventBus.emit("participant:updated" /* PARTICIPANT_UPDATED */, {
992
+ participantId: data.participant.participantId,
993
+ timestamp: Date.now()
994
+ });
995
+ this.logger.debug("Participant added event processed", {
996
+ participantId: data.participant.participantId
997
+ });
805
998
  }
806
999
  };
807
1000
 
808
1001
  // src/core/socketio/handlers/handler.registry.ts
809
- var logger2 = createLogger("socketio:registry");
1002
+ var logger = createLogger("socketio:registry");
810
1003
  var SocketHandlerRegistry = class {
811
1004
  constructor(options = {}) {
812
1005
  this.options = options;
@@ -815,34 +1008,52 @@ var SocketHandlerRegistry = class {
815
1008
  }
816
1009
  initializeHandlers() {
817
1010
  const handlers = [
818
- new CallIncomingHandler(this.options),
819
- new CallParticipantAcceptedHandler(this.options),
820
- new CallParticipantDeclinedHandler(this.options),
821
- new CallEndedHandler(this.options),
822
- new CallJoinInfoHandler(this.options),
823
- new CallTimeoutHandler(this.options),
824
- new CallCanceledHandler(this.options)
1011
+ // Phase 1: Core flow handlers
1012
+ new InviteHandler(this.options),
1013
+ new JoinInfoHandler(this.options),
1014
+ new SessionEndedHandler(this.options),
1015
+ new SessionCreatedHandler(this.options),
1016
+ // Phase 2: Invite management handlers
1017
+ new InviteAcceptedHandler(this.options),
1018
+ new InviteDeclinedHandler(this.options),
1019
+ new InviteCancelledHandler(this.options),
1020
+ new InviteSentHandler(this.options),
1021
+ // Phase 3: Edge case handlers
1022
+ new InviteMissedHandler(this.options),
1023
+ new SessionCancelledHandler(this.options),
1024
+ new SessionMissedHandler(this.options),
1025
+ new ParticipantAddedHandler(this.options)
825
1026
  ];
826
1027
  for (const handler of handlers) {
827
1028
  this.handlers.set(handler.eventName, handler);
828
1029
  }
1030
+ logger.info(`Registered ${handlers.length} socket event handlers`);
829
1031
  }
830
1032
  registerEventListeners(socket) {
831
1033
  for (const [eventName, handler] of this.handlers) {
832
1034
  socket.on(eventName, (rawData) => {
833
1035
  handler.handleRaw(rawData).catch((error) => {
834
- logger2.error(`Handler error for ${eventName}:`, error);
1036
+ logger.error(`Handler error for ${eventName}:`, error);
835
1037
  });
836
1038
  });
837
1039
  }
1040
+ logger.debug("Event listeners registered on socket");
838
1041
  }
839
1042
  removeEventListeners(socket) {
840
1043
  for (const eventName of this.handlers.keys()) {
841
1044
  socket.off(eventName);
842
1045
  }
1046
+ logger.debug("Event listeners removed from socket");
843
1047
  }
844
1048
  destroy() {
845
1049
  this.handlers.clear();
1050
+ logger.debug("Handler registry destroyed");
1051
+ }
1052
+ /**
1053
+ * Get all registered event names
1054
+ */
1055
+ getRegisteredEvents() {
1056
+ return Array.from(this.handlers.keys());
846
1057
  }
847
1058
  };
848
1059
 
@@ -1396,17 +1607,17 @@ var request = (config, options) => {
1396
1607
  // src/generated/api/services.ts
1397
1608
  var CallsService = class {
1398
1609
  /**
1399
- * @returns any Call started successfully
1610
+ * @returns any Invites sent successfully
1400
1611
  * @throws ApiError
1401
1612
  */
1402
- static postSignalCalls(data) {
1613
+ static postSignalCallsInvite(data) {
1403
1614
  const {
1404
1615
  appId,
1405
1616
  requestBody
1406
1617
  } = data;
1407
1618
  return request(OpenAPI, {
1408
1619
  method: "POST",
1409
- url: "/signal/calls",
1620
+ url: "/signal/calls/invite",
1410
1621
  query: {
1411
1622
  appId
1412
1623
  },
@@ -1414,7 +1625,9 @@ var CallsService = class {
1414
1625
  mediaType: "application/json",
1415
1626
  errors: {
1416
1627
  400: `Invalid request`,
1417
- 401: `Authentication required`
1628
+ 401: `Authentication required`,
1629
+ 403: `User does not have permission to invite`,
1630
+ 404: `Call not found`
1418
1631
  }
1419
1632
  });
1420
1633
  }
@@ -1452,7 +1665,8 @@ var CallsService = class {
1452
1665
  static postSignalCallsByCallIdDecline(data) {
1453
1666
  const {
1454
1667
  callId,
1455
- appId
1668
+ appId,
1669
+ requestBody
1456
1670
  } = data;
1457
1671
  return request(OpenAPI, {
1458
1672
  method: "POST",
@@ -1463,6 +1677,8 @@ var CallsService = class {
1463
1677
  query: {
1464
1678
  appId
1465
1679
  },
1680
+ body: requestBody,
1681
+ mediaType: "application/json",
1466
1682
  errors: {
1467
1683
  400: `Invalid request`,
1468
1684
  401: `Authentication required`,
@@ -1473,17 +1689,17 @@ var CallsService = class {
1473
1689
  });
1474
1690
  }
1475
1691
  /**
1476
- * @returns any Left call successfully
1692
+ * @returns any Call cancelled successfully
1477
1693
  * @throws ApiError
1478
1694
  */
1479
- static postSignalCallsByCallIdLeave(data) {
1695
+ static postSignalCallsByCallIdCancel(data) {
1480
1696
  const {
1481
1697
  callId,
1482
1698
  appId
1483
1699
  } = data;
1484
1700
  return request(OpenAPI, {
1485
1701
  method: "POST",
1486
- url: "/signal/calls/{callId}/leave",
1702
+ url: "/signal/calls/{callId}/cancel",
1487
1703
  path: {
1488
1704
  callId
1489
1705
  },
@@ -1493,151 +1709,180 @@ var CallsService = class {
1493
1709
  errors: {
1494
1710
  400: `Invalid request`,
1495
1711
  401: `Authentication required`,
1496
- 403: `User is not a participant`,
1712
+ 403: `User does not have permission to cancel`,
1497
1713
  404: `Call not found`,
1498
- 409: `Call cannot be left in current state`
1714
+ 409: `Call cannot be cancelled in current state`
1499
1715
  }
1500
1716
  });
1501
1717
  }
1502
1718
  /**
1503
- * @returns any Call retrieved successfully
1719
+ * @returns any Transfer initiated successfully
1504
1720
  * @throws ApiError
1505
1721
  */
1506
- static getSignalCallsByCallId(data) {
1722
+ static postSignalCallsByCallIdTransfer(data) {
1507
1723
  const {
1508
1724
  callId,
1509
- appId
1725
+ appId,
1726
+ requestBody
1510
1727
  } = data;
1511
1728
  return request(OpenAPI, {
1512
- method: "GET",
1513
- url: "/signal/calls/{callId}",
1729
+ method: "POST",
1730
+ url: "/signal/calls/{callId}/transfer",
1514
1731
  path: {
1515
1732
  callId
1516
1733
  },
1517
1734
  query: {
1518
1735
  appId
1519
1736
  },
1737
+ body: requestBody,
1738
+ mediaType: "application/json",
1520
1739
  errors: {
1521
1740
  400: `Invalid request`,
1522
1741
  401: `Authentication required`,
1523
- 403: `User does not have access to this call`,
1524
- 404: `Call not found`
1742
+ 403: `User does not have permission to transfer`,
1743
+ 404: `Call not found`,
1744
+ 409: `Call cannot be transferred in current state`
1525
1745
  }
1526
1746
  });
1527
1747
  }
1528
1748
  /**
1529
- * @returns any Participant info retrieved successfully
1749
+ * @returns any Participant kicked successfully
1530
1750
  * @throws ApiError
1531
1751
  */
1532
- static getSignalCallsParticipantsByIdentity(data) {
1752
+ static postSignalCallsByCallIdKick(data) {
1533
1753
  const {
1534
- identity,
1535
- appId
1754
+ callId,
1755
+ appId,
1756
+ requestBody
1536
1757
  } = data;
1537
1758
  return request(OpenAPI, {
1538
- method: "GET",
1539
- url: "/signal/calls/participants/{identity}",
1759
+ method: "POST",
1760
+ url: "/signal/calls/{callId}/kick",
1540
1761
  path: {
1541
- identity
1762
+ callId
1542
1763
  },
1543
1764
  query: {
1544
1765
  appId
1545
1766
  },
1767
+ body: requestBody,
1768
+ mediaType: "application/json",
1546
1769
  errors: {
1547
1770
  400: `Invalid request`,
1548
1771
  401: `Authentication required`,
1549
- 404: `Participant not found`
1772
+ 403: `User does not have permission to kick`,
1773
+ 404: `Call not found`,
1774
+ 409: `Participant cannot be kicked in current state`
1550
1775
  }
1551
1776
  });
1552
1777
  }
1553
- };
1554
-
1555
- // src/core/signal/signal.client.ts
1556
- var SignalClient = class {
1557
- constructor(config) {
1558
- this.logger = createLogger("signal");
1559
- this.config = config;
1560
- }
1561
- async initiate(params) {
1562
- try {
1563
- return await CallsService.postSignalCalls({
1564
- appId: this.config.appId,
1565
- requestBody: {
1566
- mode: params.mode || "AUDIO",
1567
- participants: params.invitees.map((userId) => ({ userId }))
1568
- }
1569
- });
1570
- } catch (error) {
1571
- this.handleApiError("initiate", error);
1572
- throw error;
1573
- }
1574
- }
1575
- async accept(callId) {
1576
- try {
1577
- const response = await CallsService.postSignalCallsByCallIdAccept({
1578
- callId,
1579
- appId: this.config.appId
1580
- });
1581
- return {
1582
- ...response,
1583
- callId,
1584
- state: "ACTIVE",
1585
- message: "Call accepted"
1586
- };
1587
- } catch (error) {
1588
- this.handleApiError("accept", error);
1589
- throw error;
1590
- }
1591
- }
1592
- async decline(callId) {
1593
- try {
1594
- const response = await CallsService.postSignalCallsByCallIdDecline({
1595
- callId,
1596
- appId: this.config.appId
1597
- });
1598
- return {
1599
- ...response,
1600
- callId,
1601
- state: "ENDED",
1602
- message: "Call declined"
1603
- };
1604
- } catch (error) {
1605
- this.handleApiError("decline", error);
1606
- throw error;
1607
- }
1778
+ /**
1779
+ * @returns any Participant muted successfully
1780
+ * @throws ApiError
1781
+ */
1782
+ static postSignalCallsByCallIdMute(data) {
1783
+ const {
1784
+ callId,
1785
+ appId,
1786
+ requestBody
1787
+ } = data;
1788
+ return request(OpenAPI, {
1789
+ method: "POST",
1790
+ url: "/signal/calls/{callId}/mute",
1791
+ path: {
1792
+ callId
1793
+ },
1794
+ query: {
1795
+ appId
1796
+ },
1797
+ body: requestBody,
1798
+ mediaType: "application/json",
1799
+ errors: {
1800
+ 400: `Invalid request`,
1801
+ 401: `Authentication required`,
1802
+ 403: `User does not have permission to mute`,
1803
+ 404: `Call not found`,
1804
+ 409: `Participant cannot be muted in current state`
1805
+ }
1806
+ });
1608
1807
  }
1609
- async leave(callId) {
1610
- try {
1611
- const response = await CallsService.postSignalCallsByCallIdLeave({
1612
- callId,
1613
- appId: this.config.appId
1614
- });
1615
- return {
1616
- ...response,
1617
- callId,
1618
- state: "ENDED",
1619
- message: "Left call"
1620
- };
1621
- } catch (error) {
1622
- this.handleApiError("leave", error);
1623
- throw error;
1624
- }
1808
+ /**
1809
+ * @returns any Call ended successfully
1810
+ * @throws ApiError
1811
+ */
1812
+ static postSignalCallsByCallIdEnd(data) {
1813
+ const {
1814
+ callId,
1815
+ appId
1816
+ } = data;
1817
+ return request(OpenAPI, {
1818
+ method: "POST",
1819
+ url: "/signal/calls/{callId}/end",
1820
+ path: {
1821
+ callId
1822
+ },
1823
+ query: {
1824
+ appId
1825
+ },
1826
+ errors: {
1827
+ 400: `Invalid request`,
1828
+ 401: `Authentication required`,
1829
+ 403: `Only host can end call`,
1830
+ 404: `Call not found`
1831
+ }
1832
+ });
1625
1833
  }
1626
- handleApiError(operation, error) {
1627
- const errorMessage = error?.body?.message || error?.message || "Unknown error";
1628
- const errorCode = error?.status || error?.code || "UNKNOWN";
1629
- this.logger.error(`Signal API error during ${operation}`, {
1630
- operation,
1631
- errorCode,
1632
- errorMessage,
1633
- error
1834
+ /**
1835
+ * @returns any Left call successfully
1836
+ * @throws ApiError
1837
+ */
1838
+ static postSignalCallsByCallIdLeave(data) {
1839
+ const {
1840
+ callId,
1841
+ appId
1842
+ } = data;
1843
+ return request(OpenAPI, {
1844
+ method: "POST",
1845
+ url: "/signal/calls/{callId}/leave",
1846
+ path: {
1847
+ callId
1848
+ },
1849
+ query: {
1850
+ appId
1851
+ },
1852
+ errors: {
1853
+ 400: `Invalid request`,
1854
+ 401: `Authentication required`,
1855
+ 404: `Call not found`
1856
+ }
1634
1857
  });
1635
1858
  }
1636
1859
  };
1637
1860
 
1638
1861
  // src/core/signal/api.config.ts
1862
+ var logger2 = createLogger("api-config");
1639
1863
  var OpenApiConfigService = class _OpenApiConfigService {
1640
1864
  constructor() {
1865
+ this.configured = false;
1866
+ OpenAPI.interceptors.request.use((request2) => {
1867
+ const headers = request2.headers;
1868
+ let authHeader = null;
1869
+ if (headers instanceof Headers) {
1870
+ authHeader = headers.get("Authorization") || null;
1871
+ } else if (headers && typeof headers === "object") {
1872
+ authHeader = headers["Authorization"] || null;
1873
+ }
1874
+ if (authHeader) {
1875
+ logger2.debug("API Request with Authorization header", {
1876
+ hasAuth: true,
1877
+ authPrefix: authHeader.substring(0, 20) + "..."
1878
+ });
1879
+ } else {
1880
+ logger2.warn("API Request WITHOUT Authorization header", {
1881
+ hasAuth: false
1882
+ });
1883
+ }
1884
+ return request2;
1885
+ });
1641
1886
  }
1642
1887
  static getInstance() {
1643
1888
  if (!_OpenApiConfigService.instance) {
@@ -1651,7 +1896,15 @@ var OpenApiConfigService = class _OpenApiConfigService {
1651
1896
  const tokenFn = config.token;
1652
1897
  OpenAPI.TOKEN = async (_options) => {
1653
1898
  const result = tokenFn();
1654
- return typeof result === "string" ? result : await result;
1899
+ const token = typeof result === "string" ? result : await result;
1900
+ if (!token) {
1901
+ logger2.warn("Token provider returned empty token");
1902
+ } else {
1903
+ logger2.debug("Token resolved successfully", {
1904
+ tokenPrefix: token.substring(0, 10) + "..."
1905
+ });
1906
+ }
1907
+ return token;
1655
1908
  };
1656
1909
  } else {
1657
1910
  OpenAPI.TOKEN = config.token;
@@ -1661,6 +1914,14 @@ var OpenApiConfigService = class _OpenApiConfigService {
1661
1914
  if (config.headers) {
1662
1915
  OpenAPI.HEADERS = config.headers;
1663
1916
  }
1917
+ this.configured = true;
1918
+ logger2.info("API configuration completed", {
1919
+ baseUrl: config.baseUrl,
1920
+ hasToken: !!config.token
1921
+ });
1922
+ }
1923
+ isConfigured() {
1924
+ return this.configured;
1664
1925
  }
1665
1926
  setToken(token) {
1666
1927
  OpenAPI.TOKEN = token;
@@ -1668,92 +1929,119 @@ var OpenApiConfigService = class _OpenApiConfigService {
1668
1929
  };
1669
1930
  var apiConfig = OpenApiConfigService.getInstance();
1670
1931
 
1671
- // src/services/call-actions.ts
1672
- function createCallActions(signal, auth) {
1673
- const logger3 = createLogger("call-actions");
1932
+ // src/services/calls.service.ts
1933
+ var logger3 = createLogger("calls-service");
1934
+ function createCallsService(config) {
1935
+ const { appId } = config;
1936
+ const ensureApiConfigured = () => {
1937
+ if (!apiConfig.isConfigured()) {
1938
+ logger3.error("API not configured before making call service request");
1939
+ throw new Error(
1940
+ "API configuration missing. Ensure the SDK is properly initialized."
1941
+ );
1942
+ }
1943
+ };
1674
1944
  async function initiate(params) {
1675
- const response = await signal.initiate(params);
1676
- rtcStore.getState().patch((state) => {
1677
- state.session = {
1678
- id: response.id,
1679
- status: "CALLING",
1680
- // Caller initiated, waiting for acceptance
1681
- mode: response.mode,
1682
- // Identity context: I initiated this call, so I'm the caller
1683
- myRole: "CALLER",
1684
- initiatedByMe: true
1685
- };
1686
- state.room.participants = {};
1687
- logger3.debug("Call initiated - waiting for participants to join", {
1688
- callId: response.id,
1689
- invitedCount: response.participants?.length || 0
1690
- });
1945
+ ensureApiConfigured();
1946
+ const requestBody = {
1947
+ mode: params.mode || "AUDIO",
1948
+ participants: params.invitees.map((userId) => ({ userId }))
1949
+ };
1950
+ if (params.callId) {
1951
+ requestBody.callId = params.callId;
1952
+ }
1953
+ return CallsService.postSignalCallsInvite({
1954
+ appId,
1955
+ requestBody
1691
1956
  });
1692
- return response;
1693
1957
  }
1694
1958
  async function accept(callId) {
1695
- const response = await signal.accept(callId);
1696
- rtcStore.getState().patch((state) => {
1697
- state.session = {
1698
- ...state.session,
1699
- id: callId,
1700
- status: "ACCEPTED",
1701
- // Call accepted but not yet joined media
1702
- myRole: "CALLEE",
1703
- initiatedByMe: false
1704
- };
1705
- state.room.participants = {};
1959
+ ensureApiConfigured();
1960
+ return CallsService.postSignalCallsByCallIdAccept({
1961
+ callId,
1962
+ appId
1706
1963
  });
1707
- return response;
1708
1964
  }
1709
1965
  async function decline(callId, reason) {
1710
- logger3.debug("Starting decline action", { callId, reason });
1711
- try {
1712
- const response = await signal.decline(callId);
1713
- logger3.info("Decline API success", { callId, response });
1714
- rtcStore.getState().patch((state) => {
1715
- if (state.session.id === callId) {
1716
- state.session.status = response.state;
1717
- }
1718
- });
1719
- eventBus.emit(
1720
- "call:declined" /* CALL_DECLINED */,
1721
- {
1722
- callId,
1723
- reason,
1724
- timestamp: Date.now()
1725
- },
1726
- "user"
1727
- );
1728
- return response;
1729
- } catch (error) {
1730
- logger3.error("Decline API failed", { callId, error });
1731
- rtcStore.getState().patch((state) => {
1732
- state.session.status = "IDLE";
1733
- logger3.warn("Force-cleared session due to API failure");
1734
- });
1735
- eventBus.emit(
1736
- "call:declined" /* CALL_DECLINED */,
1737
- {
1738
- callId,
1739
- reason: "api_error",
1740
- timestamp: Date.now()
1741
- },
1742
- "user"
1743
- );
1744
- throw error;
1966
+ ensureApiConfigured();
1967
+ const payload = {
1968
+ callId,
1969
+ appId
1970
+ };
1971
+ if (reason) {
1972
+ payload.requestBody = { reason };
1745
1973
  }
1974
+ return CallsService.postSignalCallsByCallIdDecline(payload);
1975
+ }
1976
+ async function cancel(callId) {
1977
+ ensureApiConfigured();
1978
+ return CallsService.postSignalCallsByCallIdCancel({
1979
+ callId,
1980
+ appId
1981
+ });
1746
1982
  }
1747
1983
  async function leave(callId) {
1748
- rtcStore.getState().patch((state) => {
1749
- state.session.status = "IDLE";
1984
+ ensureApiConfigured();
1985
+ return CallsService.postSignalCallsByCallIdLeave({
1986
+ callId,
1987
+ appId
1988
+ });
1989
+ }
1990
+ async function end(callId) {
1991
+ ensureApiConfigured();
1992
+ return CallsService.postSignalCallsByCallIdEnd({
1993
+ callId,
1994
+ appId
1995
+ });
1996
+ }
1997
+ async function transfer(callId, targetParticipantId, reason) {
1998
+ ensureApiConfigured();
1999
+ const requestBody = {
2000
+ targetParticipantId
2001
+ };
2002
+ if (reason) {
2003
+ requestBody.reason = reason;
2004
+ }
2005
+ return CallsService.postSignalCallsByCallIdTransfer({
2006
+ callId,
2007
+ appId,
2008
+ requestBody
2009
+ });
2010
+ }
2011
+ async function kick(callId, participantId, reason) {
2012
+ ensureApiConfigured();
2013
+ const requestBody = {
2014
+ participantId
2015
+ };
2016
+ if (reason) {
2017
+ requestBody.reason = reason;
2018
+ }
2019
+ return CallsService.postSignalCallsByCallIdKick({
2020
+ callId,
2021
+ appId,
2022
+ requestBody
2023
+ });
2024
+ }
2025
+ async function mute(callId, participantId) {
2026
+ ensureApiConfigured();
2027
+ return CallsService.postSignalCallsByCallIdMute({
2028
+ callId,
2029
+ appId,
2030
+ requestBody: {
2031
+ participantId
2032
+ }
1750
2033
  });
1751
2034
  }
1752
2035
  return {
1753
2036
  initiate,
1754
2037
  accept,
1755
2038
  decline,
1756
- leave
2039
+ cancel,
2040
+ leave,
2041
+ end,
2042
+ transfer,
2043
+ kick,
2044
+ mute
1757
2045
  };
1758
2046
  }
1759
2047
 
@@ -1772,12 +2060,14 @@ function buildSdk(opts) {
1772
2060
  setGlobalLoggerOptions(loggerOptions);
1773
2061
  const auth = new AuthManager(opts.authProvider);
1774
2062
  const socket = SocketManager.getInstance();
1775
- const signal = new SignalClient({
2063
+ const callsService = createCallsService({ appId: opts.appId });
2064
+ apiConfig.configure({
1776
2065
  baseUrl: opts.signalHost,
1777
- appId: opts.appId,
1778
- authManager: auth
2066
+ token: async () => {
2067
+ const token = auth.getCurrentToken();
2068
+ return token || "";
2069
+ }
1779
2070
  });
1780
- const callActions = createCallActions(signal);
1781
2071
  const cleanup = () => {
1782
2072
  socket.destroy();
1783
2073
  rtcStore.getState().reset();
@@ -1786,10 +2076,9 @@ function buildSdk(opts) {
1786
2076
  store: rtcStore,
1787
2077
  auth,
1788
2078
  socket,
1789
- signal,
1790
- ...callActions,
2079
+ calls: callsService,
1791
2080
  cleanup,
1792
- // API configuration
2081
+ // API configuration - can be called again to override if needed
1793
2082
  configureApi: (config) => {
1794
2083
  apiConfig.configure(config);
1795
2084
  }
@@ -1804,24 +2093,6 @@ function RtcProvider({
1804
2093
  }) {
1805
2094
  const sdk = useMemo(() => buildSdk(options), [options]);
1806
2095
  useEffect(() => {
1807
- try {
1808
- sdk.configureApi({
1809
- baseUrl: options.signalHost,
1810
- token: async () => {
1811
- const token = sdk.auth.getCurrentToken();
1812
- return token || "";
1813
- }
1814
- });
1815
- options.log?.("info", "API configured successfully");
1816
- } catch (error) {
1817
- options.log?.("error", "Failed to configure API", error);
1818
- rtcStore.getState().addError({
1819
- code: "API_CONFIG_ERROR",
1820
- message: "Failed to configure API",
1821
- timestamp: Date.now(),
1822
- context: error
1823
- });
1824
- }
1825
2096
  sdk.socket.initialize(
1826
2097
  options.signalHost,
1827
2098
  sdk.auth,
@@ -1856,36 +2127,104 @@ var useSdk = () => {
1856
2127
  // src/hooks/useCallState.ts
1857
2128
  function useCallState() {
1858
2129
  const session = useRtcStore((state) => state.session);
2130
+ if (!session) {
2131
+ return {
2132
+ id: null,
2133
+ status: null,
2134
+ mode: null,
2135
+ roomName: null
2136
+ };
2137
+ }
1859
2138
  return {
1860
2139
  id: session.id,
1861
2140
  status: session.status,
1862
2141
  mode: session.mode,
1863
- roomName: session.livekitInfo?.roomName
2142
+ roomName: session.livekitInfo?.roomName || null
1864
2143
  };
1865
2144
  }
1866
2145
 
2146
+ // src/hooks/useSessionId.ts
2147
+ function useSessionId() {
2148
+ return useRtcStore((state) => state.session?.id ?? null);
2149
+ }
2150
+
2151
+ // src/hooks/useIncomingInvite.ts
2152
+ function useIncomingInvite() {
2153
+ return useRtcStore((state) => state.incomingInvite);
2154
+ }
2155
+
1867
2156
  // src/hooks/useCallActions.ts
1868
2157
  function useCallActions() {
1869
2158
  const sdk = useSdk();
2159
+ const sessionId = useSessionId();
2160
+ const incomingInvite = useIncomingInvite();
1870
2161
  return {
1871
2162
  initiate: (participants, type) => {
1872
- return sdk.initiate({ invitees: participants, mode: type });
2163
+ return sdk.calls.initiate({ invitees: participants, mode: type });
2164
+ },
2165
+ invite: (participants) => {
2166
+ if (!sessionId) {
2167
+ throw new Error("No active session to invite participants to");
2168
+ }
2169
+ const session = sdk.store.getState().session;
2170
+ const mode = session?.mode || "VIDEO";
2171
+ return sdk.calls.initiate({
2172
+ invitees: participants,
2173
+ mode,
2174
+ callId: sessionId
2175
+ });
2176
+ },
2177
+ accept: () => {
2178
+ if (!incomingInvite) {
2179
+ throw new Error("No incoming invite to accept");
2180
+ }
2181
+ return sdk.calls.accept(incomingInvite.callId);
2182
+ },
2183
+ decline: (reason) => {
2184
+ if (!incomingInvite) {
2185
+ throw new Error("No incoming invite to decline");
2186
+ }
2187
+ return sdk.calls.decline(incomingInvite.callId, reason);
2188
+ },
2189
+ cancel: () => {
2190
+ if (!sessionId) {
2191
+ throw new Error("No active session to cancel");
2192
+ }
2193
+ return sdk.calls.cancel(sessionId);
2194
+ },
2195
+ leave: () => {
2196
+ if (!sessionId) {
2197
+ throw new Error("No active session to leave");
2198
+ }
2199
+ return sdk.calls.leave(sessionId);
1873
2200
  },
1874
- accept: (callId) => {
1875
- return sdk.accept(callId);
2201
+ end: () => {
2202
+ if (!sessionId) {
2203
+ throw new Error("No active session to end");
2204
+ }
2205
+ return sdk.calls.end(sessionId);
1876
2206
  },
1877
- decline: (callId) => {
1878
- return sdk.decline(callId);
2207
+ transfer: (targetParticipantId, reason) => {
2208
+ if (!sessionId) {
2209
+ throw new Error("No active session to transfer");
2210
+ }
2211
+ return sdk.calls.transfer(sessionId, targetParticipantId, reason);
1879
2212
  },
1880
- end: (callId) => {
1881
- return sdk.leave(callId);
2213
+ kick: (participantId, reason) => {
2214
+ if (!sessionId) {
2215
+ throw new Error("No active session to kick participant from");
2216
+ }
2217
+ return sdk.calls.kick(sessionId, participantId, reason);
1882
2218
  },
1883
- cancel: (callId) => {
1884
- return sdk.leave(callId);
2219
+ mute: (participantId) => {
2220
+ if (!sessionId) {
2221
+ throw new Error("No active session to mute participant in");
2222
+ }
2223
+ return sdk.calls.mute(sessionId, participantId);
1885
2224
  }
1886
2225
  };
1887
2226
  }
1888
- function useEvent(eventType, callback, filter) {
2227
+ function useEvent(eventType, callback) {
1889
2228
  const [lastEvent, setLastEvent] = useState(
1890
2229
  void 0
1891
2230
  );
@@ -1898,14 +2237,96 @@ function useEvent(eventType, callback, filter) {
1898
2237
  callbackRef.current(event);
1899
2238
  }
1900
2239
  };
1901
- const subscription = eventType.includes("*") ? eventBus.onPattern(eventType, handler, filter) : eventBus.on(eventType, handler, filter);
2240
+ const subscription = eventBus.on(eventType, handler);
1902
2241
  return () => {
1903
2242
  subscription.unsubscribe();
1904
2243
  };
1905
- }, [eventType, filter]);
2244
+ }, [eventType]);
1906
2245
  return lastEvent;
1907
2246
  }
1908
2247
 
1909
- export { RtcProvider, SdkEventType, apiConfig, clearErrors, eventBus, pushApiError, pushDeviceError, pushError, pushIdentityGuardError, pushLiveKitConnectError, pushMediaPermissionError, pushNetworkError, pushSocketValidationError, pushStaleEventError, useCallActions, useCallState, useEvent, useSdk };
2248
+ // src/hooks/useOutgoingInvites.ts
2249
+ function useOutgoingInvites() {
2250
+ return useRtcStore((state) => state.outgoingInvites);
2251
+ }
2252
+
2253
+ // src/hooks/useInviteAccepted.ts
2254
+ function useInviteAccepted() {
2255
+ return useRtcStore((state) => {
2256
+ return Object.values(state.outgoingInvites).some(
2257
+ (invite) => invite.status === "accepted"
2258
+ );
2259
+ });
2260
+ }
2261
+
2262
+ // src/hooks/useCallInitiated.ts
2263
+ function useCallInitiated() {
2264
+ return useRtcStore((state) => state.initiated);
2265
+ }
2266
+
2267
+ // src/hooks/useSessionDuration.ts
2268
+ function useSessionDuration() {
2269
+ return 0;
2270
+ }
2271
+ var defaultRoomOptions = {
2272
+ adaptiveStream: true,
2273
+ dynacast: true
2274
+ };
2275
+ function useRoom(options) {
2276
+ const [room] = useState(() => new Room(options || defaultRoomOptions));
2277
+ const livekitInfo = useRtcStore((state) => state.session?.livekitInfo);
2278
+ useEffect(() => {
2279
+ if (!livekitInfo) {
2280
+ return;
2281
+ }
2282
+ room.connect(livekitInfo.url, livekitInfo.token);
2283
+ return () => {
2284
+ room.disconnect();
2285
+ };
2286
+ }, [livekitInfo, room]);
2287
+ if (!livekitInfo) {
2288
+ return null;
2289
+ }
2290
+ return room;
2291
+ }
2292
+ function useParticipantMetadata(participantOrIdentity) {
2293
+ const room = useRoom();
2294
+ const participants = useParticipants(room ? { room } : {});
2295
+ let resolvedParticipant;
2296
+ if (typeof participantOrIdentity === "string") {
2297
+ resolvedParticipant = participants.find(
2298
+ (p) => p.identity === participantOrIdentity
2299
+ );
2300
+ } else if (participantOrIdentity) {
2301
+ resolvedParticipant = participantOrIdentity;
2302
+ }
2303
+ const livekitInfo = useParticipantInfo(
2304
+ resolvedParticipant ? { participant: resolvedParticipant } : {}
2305
+ );
2306
+ if (!livekitInfo.metadata) {
2307
+ return null;
2308
+ }
2309
+ try {
2310
+ const metadata = JSON.parse(livekitInfo.metadata);
2311
+ if (!metadata.userId || !metadata.role) {
2312
+ console.warn("Invalid participant metadata: missing required fields", metadata);
2313
+ return null;
2314
+ }
2315
+ return {
2316
+ userId: metadata.userId,
2317
+ role: metadata.role,
2318
+ firstName: metadata.firstName || "",
2319
+ lastName: metadata.lastName || "",
2320
+ username: metadata.username || "",
2321
+ email: metadata.email || "",
2322
+ profilePhoto: metadata.profilePhoto || ""
2323
+ };
2324
+ } catch (error) {
2325
+ console.error("Failed to parse participant metadata:", error);
2326
+ return null;
2327
+ }
2328
+ }
2329
+
2330
+ export { RtcProvider, SdkEventType, apiConfig, callCancelledSchema, callCreatedSchema, callEndedSchema, callInviteAcceptedSchema, callInviteCancelledSchema, callInviteDeclinedSchema, callInviteMissedSchema, callInviteSchema, callInviteSentSchema, callJoinInfoSchema, callMissedSchema, callParticipantAddedSchema, clearErrors, eventBus, pushApiError, pushDeviceError, pushError, pushIdentityGuardError, pushLiveKitConnectError, pushMediaPermissionError, pushNetworkError, pushSocketValidationError, pushStaleEventError, useCallActions, useCallInitiated, useCallState, useEvent, useIncomingInvite, useInviteAccepted, useOutgoingInvites, useParticipantMetadata, useRoom, useSdk, useSessionDuration, useSessionId };
1910
2331
  //# sourceMappingURL=index.mjs.map
1911
2332
  //# sourceMappingURL=index.mjs.map