@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.mjs CHANGED
@@ -167,6 +167,8 @@ var VideoSDKCore = class {
167
167
  this.ws = null;
168
168
  this.peers = {};
169
169
  this.initiators = /* @__PURE__ */ new Set();
170
+ this.lastPong = Date.now();
171
+ this.intentionalDisconnect = false;
170
172
  this.roomId = null;
171
173
  this.localStream = null;
172
174
  this.screenStream = null;
@@ -238,18 +240,18 @@ var VideoSDKCore = class {
238
240
  this.emitError("WS_ERROR", "WebSocket encountered an error", err, true);
239
241
  };
240
242
  this.ws.onclose = (e) => {
241
- this.emitError(
242
- "WS_CLOSED",
243
- `Connection closed (${e.code}) ${e.reason || ""}`,
244
- e,
245
- true
246
- );
247
243
  this.joinRejecter?.({
248
244
  code: "WS_CLOSED",
249
245
  message: "Connection closed before join completed",
250
246
  raw: e
251
247
  });
252
248
  this.joinRejecter = void 0;
249
+ if (this.intentionalDisconnect) {
250
+ return;
251
+ }
252
+ if (e.code === 1e3 || e.code === 1001) {
253
+ return;
254
+ }
253
255
  this.scheduleReconnect();
254
256
  };
255
257
  this.ws.onmessage = async (e) => {
@@ -373,46 +375,36 @@ var VideoSDKCore = class {
373
375
  var _a, _b, _c, _d;
374
376
  if (msg.sender === this.myId) return;
375
377
  switch (msg.type) {
376
- case "EXISTING_USERS":
377
- if (msg.presenterId) {
378
- this.state.setPresenterId(msg.presenterId);
379
- this.events.onScreenShareStarted?.(msg.presenterId, null);
380
- }
381
- for (const p of msg.participants || []) {
382
- if (!p?.id || p.id === this.myId) continue;
383
- this.state.addParticipant(p);
384
- this.events.onUserJoined?.(p);
385
- }
378
+ case "PONG":
379
+ this.lastPong = Date.now();
386
380
  break;
387
- case "JOINED": {
388
- this.startHeartbeat();
389
- this.joinResolver?.();
390
- this.joinResolver = void 0;
391
- this.joinRejecter = void 0;
392
- break;
393
- }
394
- case "USER_JOINED": {
395
- const p = msg.participant;
396
- if (!p?.id || p.id === this.myId) return;
397
- this.state.addParticipant(p);
398
- this.events.onUserJoined?.(p);
399
- break;
400
- }
401
381
  case "OFFER":
402
382
  await this.handleOffer(msg.payload, msg.sender);
403
383
  break;
404
384
  case "ANSWER": {
405
385
  const pc = this.peers[msg.sender];
406
386
  if (!pc) return;
407
- if (pc.signalingState === "stable") {
408
- console.warn("Late answer received, restarting negotiation");
387
+ if (pc.signalingState !== "have-local-offer") {
388
+ console.warn(
389
+ `[Signaling] Unexpected ANSWER in state "${pc.signalingState}", ignoring`
390
+ );
409
391
  return;
410
392
  }
411
- await pc.setRemoteDescription({
412
- type: "answer",
413
- sdp: msg.payload
414
- });
415
- await this.flushIce(msg.sender, pc);
393
+ try {
394
+ await pc.setRemoteDescription({
395
+ type: "answer",
396
+ sdp: msg.payload
397
+ });
398
+ await this.flushIce(msg.sender, pc);
399
+ } catch (err) {
400
+ console.error("[Signaling] Failed to apply answer:", err);
401
+ this.emitError(
402
+ "ANSWER_FAILED",
403
+ `Failed to apply answer from ${msg.sender}`,
404
+ err,
405
+ true
406
+ );
407
+ }
416
408
  break;
417
409
  }
418
410
  case "ICE": {
@@ -435,6 +427,35 @@ var VideoSDKCore = class {
435
427
  }
436
428
  break;
437
429
  }
430
+ case "EXISTING_USERS":
431
+ if (msg.presenterId) {
432
+ this.state.setPresenterId(msg.presenterId);
433
+ this.events.onScreenShareStarted?.(msg.presenterId, null);
434
+ }
435
+ for (const p of msg.participants || []) {
436
+ if (!p?.id || p.id === this.myId) continue;
437
+ this.state.addParticipant(p);
438
+ this.events.onUserJoined?.(p);
439
+ await this.createOffer(p.id);
440
+ }
441
+ break;
442
+ case "JOINED": {
443
+ this.intentionalDisconnect = false;
444
+ this.reconnectAttempts = 0;
445
+ this.startHeartbeat();
446
+ this.joinResolver?.();
447
+ this.joinResolver = void 0;
448
+ this.joinRejecter = void 0;
449
+ break;
450
+ }
451
+ case "USER_JOINED": {
452
+ const p = msg.participant;
453
+ if (!p?.id || p.id === this.myId) return;
454
+ this.state.addParticipant(p);
455
+ this.events.onUserJoined?.(p);
456
+ await this.createOffer(p.id);
457
+ break;
458
+ }
438
459
  case "USER_LEFT":
439
460
  const peerId = msg.participant.id;
440
461
  this.closePeer(peerId);
@@ -518,17 +539,44 @@ var VideoSDKCore = class {
518
539
  const pc = new RTCPeerConnection({
519
540
  iceServers: [
520
541
  {
521
- urls: [
522
- "stun:stun.l.google.com:19302",
523
- "stun:stun1.l.google.com:19302"
524
- ]
542
+ urls: "stun:stun.relay.metered.ca:80"
543
+ },
544
+ {
545
+ urls: "turn:global.relay.metered.ca:80",
546
+ username: "25aed888d2d360e9fae0e812",
547
+ credential: "WPYstojO9Wf3+HsQ"
548
+ },
549
+ {
550
+ urls: "turn:global.relay.metered.ca:80?transport=tcp",
551
+ username: "25aed888d2d360e9fae0e812",
552
+ credential: "WPYstojO9Wf3+HsQ"
553
+ },
554
+ {
555
+ urls: "turn:global.relay.metered.ca:443",
556
+ username: "25aed888d2d360e9fae0e812",
557
+ credential: "WPYstojO9Wf3+HsQ"
558
+ },
559
+ {
560
+ urls: "turns:global.relay.metered.ca:443?transport=tcp",
561
+ username: "25aed888d2d360e9fae0e812",
562
+ credential: "WPYstojO9Wf3+HsQ"
525
563
  }
526
564
  ]
527
565
  });
528
566
  pc.ontrack = (event) => {
529
- const incomingStream = event.streams[0];
567
+ console.log(`Track received: ${event.track.kind}`, {
568
+ trackId: event.track.id,
569
+ streamCount: event.streams.length,
570
+ streamTrackCount: event.streams[0]?.getTracks().length
571
+ });
572
+ const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
530
573
  const participant = this.state.getParticipant(id);
531
574
  const isScreenStream = incomingStream.id === participant?.media?.remoteScreenStreamId;
575
+ if (event.track.kind === "video") {
576
+ event.track.onunmute = () => {
577
+ console.log("Video track unmuted for", id);
578
+ };
579
+ }
532
580
  if (isScreenStream) {
533
581
  const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
534
582
  this.state.updateParticipantMedia(id, {
@@ -558,6 +606,9 @@ var VideoSDKCore = class {
558
606
  target: id
559
607
  });
560
608
  };
609
+ pc.oniceconnectionstatechange = () => {
610
+ console.log(`ICE Connection State: ${pc.iceConnectionState}`);
611
+ };
561
612
  pc.onconnectionstatechange = () => {
562
613
  if (pc.connectionState === "failed") {
563
614
  try {
@@ -580,18 +631,45 @@ var VideoSDKCore = class {
580
631
  }
581
632
  // ---------------- OFFER ----------------
582
633
  async createOffer(id, isRenegotiation = false) {
634
+ if (!isRenegotiation && this.initiators.has(id)) {
635
+ console.debug(
636
+ `[Offer] Already initiating with ${id}, skipping duplicate`
637
+ );
638
+ }
639
+ if (isRenegotiation && this.peers[id]) {
640
+ const pc2 = this.peers[id];
641
+ if (pc2.signalingState !== "stable") {
642
+ console.warn(
643
+ `[Offer] Cannot renegotiate: peer in state "${pc2.signalingState}"`
644
+ );
645
+ }
646
+ }
647
+ if (!isRenegotiation) {
648
+ this.initiators.add(id);
649
+ }
583
650
  if (!this.peers[id]) {
584
651
  this.peers[id] = this.createPeer(id);
585
652
  }
586
653
  const pc = this.peers[id];
587
- const offer = await pc.createOffer();
588
- await pc.setLocalDescription(offer);
589
- this.send({
590
- type: "OFFER",
591
- payload: offer.sdp,
592
- sender: this.myId,
593
- target: id
594
- });
654
+ try {
655
+ const offer = await pc.createOffer();
656
+ await pc.setLocalDescription(offer);
657
+ this.send({
658
+ type: "OFFER",
659
+ payload: offer.sdp,
660
+ sender: this.myId,
661
+ target: id
662
+ });
663
+ console.debug(`[Offer] Sent to ${id}`);
664
+ } catch (err) {
665
+ console.error(`[Offer] Failed for ${id}:`, err);
666
+ this.emitError(
667
+ "OFFER_FAILED",
668
+ `Failed to create offer for ${id}`,
669
+ err,
670
+ true
671
+ );
672
+ }
595
673
  }
596
674
  // ---------------- ANSWER ----------------
597
675
  async handleOffer(sdp, id) {
@@ -599,28 +677,45 @@ var VideoSDKCore = class {
599
677
  this.peers[id] = this.createPeer(id);
600
678
  }
601
679
  const pc = this.peers[id];
602
- await pc.setRemoteDescription({
603
- type: "offer",
604
- sdp
605
- });
606
- const pending = this.pendingIceCandidates[id] || [];
607
- for (const candidate of pending) {
608
- try {
609
- await pc.addIceCandidate(candidate);
610
- } catch (err) {
611
- console.warn(err);
680
+ try {
681
+ if (pc.signalingState !== "stable" && pc.signalingState !== "have-local-offer") {
682
+ console.warn(
683
+ `[Signaling] Cannot accept OFFER in state "${pc.signalingState}"`
684
+ );
685
+ return;
686
+ }
687
+ await pc.setRemoteDescription({
688
+ type: "offer",
689
+ sdp
690
+ });
691
+ const pending = this.pendingIceCandidates[id] || [];
692
+ for (const candidate of pending) {
693
+ try {
694
+ await pc.addIceCandidate(candidate);
695
+ } catch (err) {
696
+ console.warn("[ICE] Failed to add candidate:", err);
697
+ }
612
698
  }
699
+ delete this.pendingIceCandidates[id];
700
+ const answer = await pc.createAnswer();
701
+ await pc.setLocalDescription(answer);
702
+ await this.flushIce(id, pc);
703
+ this.send({
704
+ type: "ANSWER",
705
+ payload: answer.sdp,
706
+ sender: this.myId,
707
+ target: id
708
+ });
709
+ console.debug(`[Answer] Sent to ${id}`);
710
+ } catch (err) {
711
+ console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);
712
+ this.emitError(
713
+ "OFFER_HANDLING_FAILED",
714
+ `Failed to handle offer from ${id}`,
715
+ err,
716
+ true
717
+ );
613
718
  }
614
- delete this.pendingIceCandidates[id];
615
- const answer = await pc.createAnswer();
616
- await pc.setLocalDescription(answer);
617
- await this.flushIce(id, pc);
618
- this.send({
619
- type: "ANSWER",
620
- payload: answer.sdp,
621
- sender: this.myId,
622
- target: id
623
- });
624
719
  }
625
720
  // ---------------- CLEANUP ----------------
626
721
  closePeer(id) {
@@ -751,6 +846,7 @@ var VideoSDKCore = class {
751
846
  });
752
847
  }
753
848
  disconnect() {
849
+ this.intentionalDisconnect = true;
754
850
  this.stopScreenShare();
755
851
  Object.values(this.peers).forEach((pc) => pc.close());
756
852
  this.peers = {};
@@ -964,21 +1060,14 @@ var useParticipants = () => {
964
1060
  };
965
1061
 
966
1062
  // src/react/useRemoteMedia.ts
967
- import { useCallback as useCallback2, useEffect as useEffect5, useState as useState4 } from "react";
1063
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
968
1064
  var useRemoteMedia = (participantId) => {
969
1065
  const { sdk } = useMeetingContext();
1066
+ const videoRef = useRef3(null);
1067
+ const audioRef = useRef3(null);
970
1068
  const [participant, setParticipant] = useState4(
971
1069
  () => sdk.state.getParticipant(participantId) || null
972
1070
  );
973
- const buildMediaStream = (participant2) => {
974
- if (!participant2?.media) return null;
975
- const stream = new MediaStream();
976
- const videoTrack = participant2.media.stream?.getVideoTracks?.()?.[0];
977
- const audioTrack = participant2.media.stream?.getAudioTracks?.()?.[0];
978
- if (videoTrack) stream.addTrack(videoTrack);
979
- if (audioTrack) stream.addTrack(audioTrack);
980
- return stream;
981
- };
982
1071
  useEffect5(() => {
983
1072
  const unsub = sdk.state.subscribe(`participant:${participantId}`, () => {
984
1073
  const p = sdk.state.getParticipant(participantId);
@@ -986,34 +1075,22 @@ var useRemoteMedia = (participantId) => {
986
1075
  });
987
1076
  return unsub;
988
1077
  }, [participantId, sdk]);
989
- const videoRef = useCallback2(
990
- (node) => {
991
- if (!node) return;
992
- const stream = participant?.media?.stream;
993
- if (!stream) return;
994
- node.pause();
995
- node.srcObject = stream;
996
- node.muted = true;
997
- node.playsInline = true;
998
- node.autoplay = true;
999
- node.play().catch(() => {
1078
+ useEffect5(() => {
1079
+ const stream = participant?.media?.stream;
1080
+ if (!stream) return;
1081
+ if (videoRef.current) {
1082
+ videoRef.current.srcObject = stream;
1083
+ videoRef.current.muted = true;
1084
+ videoRef.current.playsInline = true;
1085
+ videoRef.current.play().catch(() => {
1000
1086
  });
1001
- },
1002
- [participant?.media?.stream]
1003
- );
1004
- const audioRef = useCallback2(
1005
- (node) => {
1006
- if (!node) return;
1007
- const stream = participant?.media?.stream;
1008
- if (!stream) return;
1009
- node.pause();
1010
- node.srcObject = stream;
1011
- node.muted = false;
1012
- node.play().catch(() => {
1087
+ }
1088
+ if (audioRef.current) {
1089
+ audioRef.current.srcObject = stream;
1090
+ audioRef.current.play().catch(() => {
1013
1091
  });
1014
- },
1015
- [participant?.media?.stream]
1016
- );
1092
+ }
1093
+ }, [participant?.media?.stream]);
1017
1094
  return {
1018
1095
  videoRef,
1019
1096
  audioRef,