@afosecure/meetingsdk 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React from 'react';
2
+ import * as react from 'react';
3
+ import react__default from 'react';
3
4
 
4
5
  type Events = {
5
6
  onMicToggled?: (peerId: string, enabled: boolean) => void;
@@ -107,6 +108,8 @@ declare class VideoSDKCore {
107
108
  private ws;
108
109
  private peers;
109
110
  private initiators;
111
+ private lastPong;
112
+ private intentionalDisconnect;
110
113
  private myId;
111
114
  private roomId;
112
115
  private localStream;
@@ -170,7 +173,7 @@ type MeetingContextValue = {
170
173
  };
171
174
  declare const MeetingProvider: ({ config, children, }: {
172
175
  config: MeetingConfig;
173
- children: React.ReactNode;
176
+ children: react__default.ReactNode;
174
177
  }) => react_jsx_runtime.JSX.Element;
175
178
  declare const useMeetingContext: () => MeetingContextValue;
176
179
 
@@ -200,8 +203,8 @@ declare const useMeeting: (handlers?: {
200
203
  declare const useParticipants: () => Participant[];
201
204
 
202
205
  declare const useRemoteMedia: (participantId: string) => {
203
- videoRef: (node: HTMLVideoElement | null) => void;
204
- audioRef: (node: HTMLAudioElement | null) => void;
206
+ videoRef: react.RefObject<HTMLVideoElement | null>;
207
+ audioRef: react.RefObject<HTMLAudioElement | null>;
205
208
  isCamActive: boolean;
206
209
  isMicEnabled: boolean;
207
210
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React from 'react';
2
+ import * as react from 'react';
3
+ import react__default from 'react';
3
4
 
4
5
  type Events = {
5
6
  onMicToggled?: (peerId: string, enabled: boolean) => void;
@@ -107,6 +108,8 @@ declare class VideoSDKCore {
107
108
  private ws;
108
109
  private peers;
109
110
  private initiators;
111
+ private lastPong;
112
+ private intentionalDisconnect;
110
113
  private myId;
111
114
  private roomId;
112
115
  private localStream;
@@ -170,7 +173,7 @@ type MeetingContextValue = {
170
173
  };
171
174
  declare const MeetingProvider: ({ config, children, }: {
172
175
  config: MeetingConfig;
173
- children: React.ReactNode;
176
+ children: react__default.ReactNode;
174
177
  }) => react_jsx_runtime.JSX.Element;
175
178
  declare const useMeetingContext: () => MeetingContextValue;
176
179
 
@@ -200,8 +203,8 @@ declare const useMeeting: (handlers?: {
200
203
  declare const useParticipants: () => Participant[];
201
204
 
202
205
  declare const useRemoteMedia: (participantId: string) => {
203
- videoRef: (node: HTMLVideoElement | null) => void;
204
- audioRef: (node: HTMLAudioElement | null) => void;
206
+ videoRef: react.RefObject<HTMLVideoElement | null>;
207
+ audioRef: react.RefObject<HTMLAudioElement | null>;
205
208
  isCamActive: boolean;
206
209
  isMicEnabled: boolean;
207
210
  };
package/dist/index.js CHANGED
@@ -200,6 +200,8 @@ var VideoSDKCore = class {
200
200
  this.ws = null;
201
201
  this.peers = {};
202
202
  this.initiators = /* @__PURE__ */ new Set();
203
+ this.lastPong = Date.now();
204
+ this.intentionalDisconnect = false;
203
205
  this.roomId = null;
204
206
  this.localStream = null;
205
207
  this.screenStream = null;
@@ -271,18 +273,18 @@ var VideoSDKCore = class {
271
273
  this.emitError("WS_ERROR", "WebSocket encountered an error", err, true);
272
274
  };
273
275
  this.ws.onclose = (e) => {
274
- this.emitError(
275
- "WS_CLOSED",
276
- `Connection closed (${e.code}) ${e.reason || ""}`,
277
- e,
278
- true
279
- );
280
276
  this.joinRejecter?.({
281
277
  code: "WS_CLOSED",
282
278
  message: "Connection closed before join completed",
283
279
  raw: e
284
280
  });
285
281
  this.joinRejecter = void 0;
282
+ if (this.intentionalDisconnect) {
283
+ return;
284
+ }
285
+ if (e.code === 1e3 || e.code === 1001) {
286
+ return;
287
+ }
286
288
  this.scheduleReconnect();
287
289
  };
288
290
  this.ws.onmessage = async (e) => {
@@ -406,46 +408,36 @@ var VideoSDKCore = class {
406
408
  var _a, _b, _c, _d;
407
409
  if (msg.sender === this.myId) return;
408
410
  switch (msg.type) {
409
- case "EXISTING_USERS":
410
- if (msg.presenterId) {
411
- this.state.setPresenterId(msg.presenterId);
412
- this.events.onScreenShareStarted?.(msg.presenterId, null);
413
- }
414
- for (const p of msg.participants || []) {
415
- if (!p?.id || p.id === this.myId) continue;
416
- this.state.addParticipant(p);
417
- this.events.onUserJoined?.(p);
418
- }
411
+ case "PONG":
412
+ this.lastPong = Date.now();
419
413
  break;
420
- case "JOINED": {
421
- this.startHeartbeat();
422
- this.joinResolver?.();
423
- this.joinResolver = void 0;
424
- this.joinRejecter = void 0;
425
- break;
426
- }
427
- case "USER_JOINED": {
428
- const p = msg.participant;
429
- if (!p?.id || p.id === this.myId) return;
430
- this.state.addParticipant(p);
431
- this.events.onUserJoined?.(p);
432
- break;
433
- }
434
414
  case "OFFER":
435
415
  await this.handleOffer(msg.payload, msg.sender);
436
416
  break;
437
417
  case "ANSWER": {
438
418
  const pc = this.peers[msg.sender];
439
419
  if (!pc) return;
440
- if (pc.signalingState === "stable") {
441
- console.warn("Late answer received, restarting negotiation");
420
+ if (pc.signalingState !== "have-local-offer") {
421
+ console.warn(
422
+ `[Signaling] Unexpected ANSWER in state "${pc.signalingState}", ignoring`
423
+ );
442
424
  return;
443
425
  }
444
- await pc.setRemoteDescription({
445
- type: "answer",
446
- sdp: msg.payload
447
- });
448
- await this.flushIce(msg.sender, pc);
426
+ try {
427
+ await pc.setRemoteDescription({
428
+ type: "answer",
429
+ sdp: msg.payload
430
+ });
431
+ await this.flushIce(msg.sender, pc);
432
+ } catch (err) {
433
+ console.error("[Signaling] Failed to apply answer:", err);
434
+ this.emitError(
435
+ "ANSWER_FAILED",
436
+ `Failed to apply answer from ${msg.sender}`,
437
+ err,
438
+ true
439
+ );
440
+ }
449
441
  break;
450
442
  }
451
443
  case "ICE": {
@@ -468,6 +460,35 @@ var VideoSDKCore = class {
468
460
  }
469
461
  break;
470
462
  }
463
+ case "EXISTING_USERS":
464
+ if (msg.presenterId) {
465
+ this.state.setPresenterId(msg.presenterId);
466
+ this.events.onScreenShareStarted?.(msg.presenterId, null);
467
+ }
468
+ for (const p of msg.participants || []) {
469
+ if (!p?.id || p.id === this.myId) continue;
470
+ this.state.addParticipant(p);
471
+ this.events.onUserJoined?.(p);
472
+ await this.createOffer(p.id);
473
+ }
474
+ break;
475
+ case "JOINED": {
476
+ this.intentionalDisconnect = false;
477
+ this.reconnectAttempts = 0;
478
+ this.startHeartbeat();
479
+ this.joinResolver?.();
480
+ this.joinResolver = void 0;
481
+ this.joinRejecter = void 0;
482
+ break;
483
+ }
484
+ case "USER_JOINED": {
485
+ const p = msg.participant;
486
+ if (!p?.id || p.id === this.myId) return;
487
+ this.state.addParticipant(p);
488
+ this.events.onUserJoined?.(p);
489
+ await this.createOffer(p.id);
490
+ break;
491
+ }
471
492
  case "USER_LEFT":
472
493
  const peerId = msg.participant.id;
473
494
  this.closePeer(peerId);
@@ -551,17 +572,44 @@ var VideoSDKCore = class {
551
572
  const pc = new RTCPeerConnection({
552
573
  iceServers: [
553
574
  {
554
- urls: [
555
- "stun:stun.l.google.com:19302",
556
- "stun:stun1.l.google.com:19302"
557
- ]
575
+ urls: "stun:stun.relay.metered.ca:80"
576
+ },
577
+ {
578
+ urls: "turn:global.relay.metered.ca:80",
579
+ username: "25aed888d2d360e9fae0e812",
580
+ credential: "WPYstojO9Wf3+HsQ"
581
+ },
582
+ {
583
+ urls: "turn:global.relay.metered.ca:80?transport=tcp",
584
+ username: "25aed888d2d360e9fae0e812",
585
+ credential: "WPYstojO9Wf3+HsQ"
586
+ },
587
+ {
588
+ urls: "turn:global.relay.metered.ca:443",
589
+ username: "25aed888d2d360e9fae0e812",
590
+ credential: "WPYstojO9Wf3+HsQ"
591
+ },
592
+ {
593
+ urls: "turns:global.relay.metered.ca:443?transport=tcp",
594
+ username: "25aed888d2d360e9fae0e812",
595
+ credential: "WPYstojO9Wf3+HsQ"
558
596
  }
559
597
  ]
560
598
  });
561
599
  pc.ontrack = (event) => {
562
- const incomingStream = event.streams[0];
600
+ console.log(`Track received: ${event.track.kind}`, {
601
+ trackId: event.track.id,
602
+ streamCount: event.streams.length,
603
+ streamTrackCount: event.streams[0]?.getTracks().length
604
+ });
605
+ const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
563
606
  const participant = this.state.getParticipant(id);
564
607
  const isScreenStream = incomingStream.id === participant?.media?.remoteScreenStreamId;
608
+ if (event.track.kind === "video") {
609
+ event.track.onunmute = () => {
610
+ console.log("Video track unmuted for", id);
611
+ };
612
+ }
565
613
  if (isScreenStream) {
566
614
  const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
567
615
  this.state.updateParticipantMedia(id, {
@@ -591,6 +639,9 @@ var VideoSDKCore = class {
591
639
  target: id
592
640
  });
593
641
  };
642
+ pc.oniceconnectionstatechange = () => {
643
+ console.log(`ICE Connection State: ${pc.iceConnectionState}`);
644
+ };
594
645
  pc.onconnectionstatechange = () => {
595
646
  if (pc.connectionState === "failed") {
596
647
  try {
@@ -613,18 +664,45 @@ var VideoSDKCore = class {
613
664
  }
614
665
  // ---------------- OFFER ----------------
615
666
  async createOffer(id, isRenegotiation = false) {
667
+ if (!isRenegotiation && this.initiators.has(id)) {
668
+ console.debug(
669
+ `[Offer] Already initiating with ${id}, skipping duplicate`
670
+ );
671
+ }
672
+ if (isRenegotiation && this.peers[id]) {
673
+ const pc2 = this.peers[id];
674
+ if (pc2.signalingState !== "stable") {
675
+ console.warn(
676
+ `[Offer] Cannot renegotiate: peer in state "${pc2.signalingState}"`
677
+ );
678
+ }
679
+ }
680
+ if (!isRenegotiation) {
681
+ this.initiators.add(id);
682
+ }
616
683
  if (!this.peers[id]) {
617
684
  this.peers[id] = this.createPeer(id);
618
685
  }
619
686
  const pc = this.peers[id];
620
- const offer = await pc.createOffer();
621
- await pc.setLocalDescription(offer);
622
- this.send({
623
- type: "OFFER",
624
- payload: offer.sdp,
625
- sender: this.myId,
626
- target: id
627
- });
687
+ try {
688
+ const offer = await pc.createOffer();
689
+ await pc.setLocalDescription(offer);
690
+ this.send({
691
+ type: "OFFER",
692
+ payload: offer.sdp,
693
+ sender: this.myId,
694
+ target: id
695
+ });
696
+ console.debug(`[Offer] Sent to ${id}`);
697
+ } catch (err) {
698
+ console.error(`[Offer] Failed for ${id}:`, err);
699
+ this.emitError(
700
+ "OFFER_FAILED",
701
+ `Failed to create offer for ${id}`,
702
+ err,
703
+ true
704
+ );
705
+ }
628
706
  }
629
707
  // ---------------- ANSWER ----------------
630
708
  async handleOffer(sdp, id) {
@@ -632,28 +710,45 @@ var VideoSDKCore = class {
632
710
  this.peers[id] = this.createPeer(id);
633
711
  }
634
712
  const pc = this.peers[id];
635
- await pc.setRemoteDescription({
636
- type: "offer",
637
- sdp
638
- });
639
- const pending = this.pendingIceCandidates[id] || [];
640
- for (const candidate of pending) {
641
- try {
642
- await pc.addIceCandidate(candidate);
643
- } catch (err) {
644
- console.warn(err);
713
+ try {
714
+ if (pc.signalingState !== "stable" && pc.signalingState !== "have-local-offer") {
715
+ console.warn(
716
+ `[Signaling] Cannot accept OFFER in state "${pc.signalingState}"`
717
+ );
718
+ return;
719
+ }
720
+ await pc.setRemoteDescription({
721
+ type: "offer",
722
+ sdp
723
+ });
724
+ const pending = this.pendingIceCandidates[id] || [];
725
+ for (const candidate of pending) {
726
+ try {
727
+ await pc.addIceCandidate(candidate);
728
+ } catch (err) {
729
+ console.warn("[ICE] Failed to add candidate:", err);
730
+ }
645
731
  }
732
+ delete this.pendingIceCandidates[id];
733
+ const answer = await pc.createAnswer();
734
+ await pc.setLocalDescription(answer);
735
+ await this.flushIce(id, pc);
736
+ this.send({
737
+ type: "ANSWER",
738
+ payload: answer.sdp,
739
+ sender: this.myId,
740
+ target: id
741
+ });
742
+ console.debug(`[Answer] Sent to ${id}`);
743
+ } catch (err) {
744
+ console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);
745
+ this.emitError(
746
+ "OFFER_HANDLING_FAILED",
747
+ `Failed to handle offer from ${id}`,
748
+ err,
749
+ true
750
+ );
646
751
  }
647
- delete this.pendingIceCandidates[id];
648
- const answer = await pc.createAnswer();
649
- await pc.setLocalDescription(answer);
650
- await this.flushIce(id, pc);
651
- this.send({
652
- type: "ANSWER",
653
- payload: answer.sdp,
654
- sender: this.myId,
655
- target: id
656
- });
657
752
  }
658
753
  // ---------------- CLEANUP ----------------
659
754
  closePeer(id) {
@@ -784,6 +879,7 @@ var VideoSDKCore = class {
784
879
  });
785
880
  }
786
881
  disconnect() {
882
+ this.intentionalDisconnect = true;
787
883
  this.stopScreenShare();
788
884
  Object.values(this.peers).forEach((pc) => pc.close());
789
885
  this.peers = {};
@@ -1000,18 +1096,11 @@ var useParticipants = () => {
1000
1096
  var import_react6 = require("react");
1001
1097
  var useRemoteMedia = (participantId) => {
1002
1098
  const { sdk } = useMeetingContext();
1099
+ const videoRef = (0, import_react6.useRef)(null);
1100
+ const audioRef = (0, import_react6.useRef)(null);
1003
1101
  const [participant, setParticipant] = (0, import_react6.useState)(
1004
1102
  () => sdk.state.getParticipant(participantId) || null
1005
1103
  );
1006
- const buildMediaStream = (participant2) => {
1007
- if (!participant2?.media) return null;
1008
- const stream = new MediaStream();
1009
- const videoTrack = participant2.media.stream?.getVideoTracks?.()?.[0];
1010
- const audioTrack = participant2.media.stream?.getAudioTracks?.()?.[0];
1011
- if (videoTrack) stream.addTrack(videoTrack);
1012
- if (audioTrack) stream.addTrack(audioTrack);
1013
- return stream;
1014
- };
1015
1104
  (0, import_react6.useEffect)(() => {
1016
1105
  const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {
1017
1106
  const p = sdk.state.getParticipant(participantId);
@@ -1019,34 +1108,22 @@ var useRemoteMedia = (participantId) => {
1019
1108
  });
1020
1109
  return unsub;
1021
1110
  }, [participantId, sdk]);
1022
- const videoRef = (0, import_react6.useCallback)(
1023
- (node) => {
1024
- if (!node) return;
1025
- const stream = participant?.media?.stream;
1026
- if (!stream) return;
1027
- node.pause();
1028
- node.srcObject = stream;
1029
- node.muted = true;
1030
- node.playsInline = true;
1031
- node.autoplay = true;
1032
- node.play().catch(() => {
1111
+ (0, import_react6.useEffect)(() => {
1112
+ const stream = participant?.media?.stream;
1113
+ if (!stream) return;
1114
+ if (videoRef.current) {
1115
+ videoRef.current.srcObject = stream;
1116
+ videoRef.current.muted = true;
1117
+ videoRef.current.playsInline = true;
1118
+ videoRef.current.play().catch(() => {
1033
1119
  });
1034
- },
1035
- [participant?.media?.stream]
1036
- );
1037
- const audioRef = (0, import_react6.useCallback)(
1038
- (node) => {
1039
- if (!node) return;
1040
- const stream = participant?.media?.stream;
1041
- if (!stream) return;
1042
- node.pause();
1043
- node.srcObject = stream;
1044
- node.muted = false;
1045
- node.play().catch(() => {
1120
+ }
1121
+ if (audioRef.current) {
1122
+ audioRef.current.srcObject = stream;
1123
+ audioRef.current.play().catch(() => {
1046
1124
  });
1047
- },
1048
- [participant?.media?.stream]
1049
- );
1125
+ }
1126
+ }, [participant?.media?.stream]);
1050
1127
  return {
1051
1128
  videoRef,
1052
1129
  audioRef,