Dhurgh commited on
Commit
4461dec
·
1 Parent(s): 02c81f5

Replace phone call transport with hosted Jitsi room inside call window (v2.9.0)

Browse files
Files changed (3) hide show
  1. client.js +149 -22
  2. index.html +16 -2
  3. package.json +1 -1
client.js CHANGED
@@ -34,6 +34,9 @@ let pendingIceRecoveryTimer = null;
34
  let pendingRelayFallbackTimer = null;
35
  let pendingRemoteIceCandidates = [];
36
  let callEventLog = [];
 
 
 
37
  let hasUserInteractedWithPage = false;
38
  let activeRoomSubscription = null;
39
  let roomMessages = JSON.parse(localStorage.getItem('roomMessages') || '{}');
@@ -383,6 +386,122 @@ function setCallStatus(statusText) {
383
  }
384
  }
385
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  let callControlBindingsDone = false;
387
  function bindCallControlButtons() {
388
  if (callControlBindingsDone) return;
@@ -1034,12 +1153,12 @@ function connectSocket() {
1034
  const byName = payload?.byUsername || 'User';
1035
  stopIncomingCallAlert();
1036
  showToast(`${byName} accepted the call`, 'success');
1037
- const peerId = activeCallState?.peerId || payload?.byUserId;
1038
- if (peerId) {
1039
  closeCustomDialog();
1040
- startCallSession(byName, peerId, true).catch((error) => {
1041
- console.error('Failed to start outgoing call session:', error);
1042
- showToast('Unable to start camera/microphone for this call', 'error');
1043
  cleanupCallSession(false);
1044
  });
1045
  }
@@ -6132,20 +6251,6 @@ function openVideoCallModal(targetName, targetId, callType) {
6132
  setCallStatus('Dialing...');
6133
  appendCallLog(`Outgoing ${callType.toLowerCase()} to ${targetName}`);
6134
 
6135
- ensureRtcConfigLoaded()
6136
- .then(() => ensureLocalCallStream())
6137
- .then(() => {
6138
- const localVideo = document.getElementById('localCallVideo');
6139
- if (localVideo && localCallStream) {
6140
- localVideo.srcObject = localCallStream;
6141
- ensureLocalPreviewPlayback(localVideo);
6142
- appendCallLog('Local preview started before answer');
6143
- }
6144
- })
6145
- .catch((error) => {
6146
- appendCallLog(`Local preview unavailable: ${error?.message || error}`);
6147
- });
6148
-
6149
  if (socket && socket.connected && targetId) {
6150
  socket.emit('start video call', {
6151
  targetId: targetId,
@@ -6184,9 +6289,9 @@ function handleIncomingVideoCall(payload) {
6184
  toId: activeCallState.peerId
6185
  });
6186
  closeCustomDialog();
6187
- startCallSession(callerName, activeCallState.peerId, false).catch((error) => {
6188
- console.error('Failed to initialize incoming call session:', error);
6189
- showToast('Unable to initialize call media devices', 'error');
6190
  cleanupCallSession(false);
6191
  });
6192
 
@@ -6613,6 +6718,9 @@ async function startCallSession(peerName, peerId, isCaller) {
6613
  }
6614
 
6615
  function endVideoCall() {
 
 
 
6616
 
6617
  window.userIntentionallEndedCall = true;
6618
 
@@ -6628,6 +6736,10 @@ function endVideoCall() {
6628
  }
6629
 
6630
  function toggleLocalMute() {
 
 
 
 
6631
  if (!localCallStream) {
6632
  showToast('Microphone unavailable on this device/browser', 'warning');
6633
  return;
@@ -6655,6 +6767,10 @@ function toggleLocalMute() {
6655
  }
6656
 
6657
  function toggleCallCamera() {
 
 
 
 
6658
  if (!localCallStream) {
6659
  showToast('Camera unavailable on this device/browser', 'warning');
6660
  return;
@@ -6734,6 +6850,17 @@ function cleanupCallSession(keepDialogOpen = false) {
6734
  stopIncomingCallAlert();
6735
  stopCallTimer();
6736
 
 
 
 
 
 
 
 
 
 
 
 
6737
  if (peerConnection) {
6738
  peerConnection.onicecandidate = null;
6739
  peerConnection.ontrack = null;
 
34
  let pendingRelayFallbackTimer = null;
35
  let pendingRemoteIceCandidates = [];
36
  let callEventLog = [];
37
+ let currentCallProvider = 'webrtc';
38
+ let currentJitsiApi = null;
39
+ let currentCallRoomName = null;
40
  let hasUserInteractedWithPage = false;
41
  let activeRoomSubscription = null;
42
  let roomMessages = JSON.parse(localStorage.getItem('roomMessages') || '{}');
 
386
  }
387
  }
388
 
389
+ function ensureJitsiApiLoaded() {
390
+ if (typeof window.JitsiMeetExternalAPI === 'function') {
391
+ return Promise.resolve();
392
+ }
393
+
394
+ return new Promise((resolve, reject) => {
395
+ const existing = document.querySelector('script[data-jitsi-external-api="true"]');
396
+ if (existing) {
397
+ existing.addEventListener('load', () => resolve(), { once: true });
398
+ existing.addEventListener('error', () => reject(new Error('Failed to load hosted call API')), { once: true });
399
+ return;
400
+ }
401
+
402
+ const script = document.createElement('script');
403
+ script.src = 'https://meet.jit.si/external_api.js';
404
+ script.async = true;
405
+ script.dataset.jitsiExternalApi = 'true';
406
+ script.onload = () => resolve();
407
+ script.onerror = () => reject(new Error('Failed to load hosted call API'));
408
+ document.head.appendChild(script);
409
+ });
410
+ }
411
+
412
+ function setHostedCallVisible(isVisible) {
413
+ const host = document.getElementById('hostedCallContainer');
414
+ const remoteVideo = document.getElementById('remoteCallVideo');
415
+ const localVideo = document.getElementById('localCallVideo');
416
+ if (host) host.style.display = isVisible ? 'block' : 'none';
417
+ if (remoteVideo) remoteVideo.style.display = isVisible ? 'none' : 'block';
418
+ if (localVideo) localVideo.style.display = isVisible ? 'none' : 'block';
419
+ }
420
+
421
+ async function startHostedCallRoom(peerName, roomName) {
422
+ if (!roomName) {
423
+ throw new Error('Missing hosted room name');
424
+ }
425
+
426
+ openCallDialog(peerName);
427
+ setCallStatus('Joining hosted call...');
428
+ appendCallLog(`Joining hosted room ${roomName}`);
429
+
430
+ await ensureJitsiApiLoaded();
431
+
432
+ const host = document.getElementById('hostedCallContainer');
433
+ if (!host) {
434
+ throw new Error('Hosted call container not found');
435
+ }
436
+
437
+ if (currentJitsiApi) {
438
+ currentJitsiApi.dispose();
439
+ currentJitsiApi = null;
440
+ }
441
+
442
+ host.innerHTML = '';
443
+ setHostedCallVisible(true);
444
+ currentCallProvider = 'hosted';
445
+ currentCallRoomName = roomName;
446
+
447
+ currentJitsiApi = new window.JitsiMeetExternalAPI('meet.jit.si', {
448
+ roomName,
449
+ parentNode: host,
450
+ width: '100%',
451
+ height: '100%',
452
+ userInfo: {
453
+ displayName: currentUser?.username || 'User'
454
+ },
455
+ configOverwrite: {
456
+ prejoinPageEnabled: false,
457
+ startWithAudioMuted: false,
458
+ startWithVideoMuted: false,
459
+ disableDeepLinking: true
460
+ },
461
+ interfaceConfigOverwrite: {
462
+ MOBILE_APP_PROMO: false,
463
+ SHOW_JITSI_WATERMARK: false,
464
+ SHOW_WATERMARK_FOR_GUESTS: false
465
+ }
466
+ });
467
+
468
+ currentJitsiApi.addListener('videoConferenceJoined', () => {
469
+ setCallStatus('Connected');
470
+ appendCallLog('Hosted room joined');
471
+ if (!callTimerInterval) {
472
+ startCallTimer();
473
+ }
474
+ });
475
+ currentJitsiApi.addListener('participantJoined', (payload) => {
476
+ appendCallLog(`Participant joined: ${payload?.displayName || payload?.id || 'remote user'}`);
477
+ });
478
+ currentJitsiApi.addListener('participantLeft', (payload) => {
479
+ appendCallLog(`Participant left: ${payload?.id || 'remote user'}`);
480
+ });
481
+ currentJitsiApi.addListener('audioMuteStatusChanged', ({ muted }) => {
482
+ const btn = document.getElementById('muteBtn');
483
+ if (btn) {
484
+ btn.textContent = muted ? '🔇' : '🎤';
485
+ btn.classList.toggle('muted', !!muted);
486
+ }
487
+ });
488
+ currentJitsiApi.addListener('videoMuteStatusChanged', ({ muted }) => {
489
+ const btn = document.getElementById('cameraBtn');
490
+ if (btn) {
491
+ btn.textContent = muted ? '🚫' : '📷';
492
+ btn.classList.toggle('cam-off', !!muted);
493
+ }
494
+ });
495
+ currentJitsiApi.addListener('readyToClose', () => {
496
+ appendCallLog('Hosted room requested close');
497
+ cleanupCallSession(false);
498
+ });
499
+ currentJitsiApi.addListener('videoConferenceLeft', () => {
500
+ appendCallLog('Hosted room left');
501
+ cleanupCallSession(false);
502
+ });
503
+ }
504
+
505
  let callControlBindingsDone = false;
506
  function bindCallControlButtons() {
507
  if (callControlBindingsDone) return;
 
1153
  const byName = payload?.byUsername || 'User';
1154
  stopIncomingCallAlert();
1155
  showToast(`${byName} accepted the call`, 'success');
1156
+ const roomName = activeCallState?.callId || payload?.callId;
1157
+ if (roomName) {
1158
  closeCustomDialog();
1159
+ startHostedCallRoom(byName, `firetech-${roomName}`).catch((error) => {
1160
+ console.error('Failed to start hosted call session:', error);
1161
+ showToast('Unable to join hosted call', 'error');
1162
  cleanupCallSession(false);
1163
  });
1164
  }
 
6251
  setCallStatus('Dialing...');
6252
  appendCallLog(`Outgoing ${callType.toLowerCase()} to ${targetName}`);
6253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6254
  if (socket && socket.connected && targetId) {
6255
  socket.emit('start video call', {
6256
  targetId: targetId,
 
6289
  toId: activeCallState.peerId
6290
  });
6291
  closeCustomDialog();
6292
+ startHostedCallRoom(callerName, `firetech-${activeCallState.callId}`).catch((error) => {
6293
+ console.error('Failed to initialize hosted call session:', error);
6294
+ showToast('Unable to join hosted call', 'error');
6295
  cleanupCallSession(false);
6296
  });
6297
 
 
6718
  }
6719
 
6720
  function endVideoCall() {
6721
+ if (currentCallProvider === 'hosted' && currentJitsiApi) {
6722
+ currentJitsiApi.executeCommand('hangup');
6723
+ }
6724
 
6725
  window.userIntentionallEndedCall = true;
6726
 
 
6736
  }
6737
 
6738
  function toggleLocalMute() {
6739
+ if (currentCallProvider === 'hosted' && currentJitsiApi) {
6740
+ currentJitsiApi.executeCommand('toggleAudio');
6741
+ return;
6742
+ }
6743
  if (!localCallStream) {
6744
  showToast('Microphone unavailable on this device/browser', 'warning');
6745
  return;
 
6767
  }
6768
 
6769
  function toggleCallCamera() {
6770
+ if (currentCallProvider === 'hosted' && currentJitsiApi) {
6771
+ currentJitsiApi.executeCommand('toggleVideo');
6772
+ return;
6773
+ }
6774
  if (!localCallStream) {
6775
  showToast('Camera unavailable on this device/browser', 'warning');
6776
  return;
 
6850
  stopIncomingCallAlert();
6851
  stopCallTimer();
6852
 
6853
+ if (currentJitsiApi) {
6854
+ try {
6855
+ currentJitsiApi.dispose();
6856
+ } catch (_) {
6857
+ }
6858
+ currentJitsiApi = null;
6859
+ }
6860
+ currentCallProvider = 'webrtc';
6861
+ currentCallRoomName = null;
6862
+ setHostedCallVisible(false);
6863
+
6864
  if (peerConnection) {
6865
  peerConnection.onicecandidate = null;
6866
  peerConnection.ontrack = null;
index.html CHANGED
@@ -969,6 +969,18 @@
969
  #remoteCallVideo, #localCallVideo, #localCallVideoOld {
970
  display: block;
971
  }
 
 
 
 
 
 
 
 
 
 
 
 
972
  #remoteCallVideo {
973
  pointer-events: none;
974
  }
@@ -3111,7 +3123,7 @@
3111
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
3112
  </div>
3113
  <div class="chat-header-actions">
3114
- <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.8.4</span>
3115
  <button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
3116
  <i class="fas fa-archive"></i>
3117
  </button>
@@ -3693,6 +3705,7 @@
3693
  <!-- ========== FULL-SCREEN CALL OVERLAY ========== -->
3694
  <div id="callOverlay">
3695
  <div class="call-overlay-stage">
 
3696
  <video id="remoteCallVideo" autoplay playsinline style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0;"></video>
3697
  <video id="localCallVideo" autoplay muted playsinline style="position:absolute; bottom:110px; right:14px; width:110px; height:150px; border-radius:12px; border:2px solid rgba(255,255,255,0.6); object-fit:cover; z-index:2;"></video>
3698
 
@@ -3730,8 +3743,9 @@
3730
  </div>
3731
 
3732
  <script src="/socket.io/socket.io.js"></script>
 
3733
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
3734
- <script src="client.js?v=2.8.4"></script>
3735
  </body>
3736
  </html>
3737
 
 
969
  #remoteCallVideo, #localCallVideo, #localCallVideoOld {
970
  display: block;
971
  }
972
+ #hostedCallContainer {
973
+ position: absolute;
974
+ inset: 0;
975
+ z-index: 1;
976
+ display: none;
977
+ background: #0f1116;
978
+ }
979
+ #hostedCallContainer iframe {
980
+ width: 100%;
981
+ height: 100%;
982
+ border: 0;
983
+ }
984
  #remoteCallVideo {
985
  pointer-events: none;
986
  }
 
3123
  <div class="chat-header-title" id="chatTitle">💬 Select a friend to chat</div>
3124
  </div>
3125
  <div class="chat-header-actions">
3126
+ <span class="build-badge mobile-essential" id="buildVersionBadge" title="Frontend build version">v2.9.0</span>
3127
  <button class="header-btn mobile-essential" onclick="toggleArchiveChat()" title="Archive chat">
3128
  <i class="fas fa-archive"></i>
3129
  </button>
 
3705
  <!-- ========== FULL-SCREEN CALL OVERLAY ========== -->
3706
  <div id="callOverlay">
3707
  <div class="call-overlay-stage">
3708
+ <div id="hostedCallContainer"></div>
3709
  <video id="remoteCallVideo" autoplay playsinline style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0;"></video>
3710
  <video id="localCallVideo" autoplay muted playsinline style="position:absolute; bottom:110px; right:14px; width:110px; height:150px; border-radius:12px; border:2px solid rgba(255,255,255,0.6); object-fit:cover; z-index:2;"></video>
3711
 
 
3743
  </div>
3744
 
3745
  <script src="/socket.io/socket.io.js"></script>
3746
+ <script src="https://meet.jit.si/external_api.js"></script>
3747
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
3748
+ <script src="client.js?v=2.9.0"></script>
3749
  </body>
3750
  </html>
3751
 
package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "discord-like-app",
3
- "version": "2.8.4",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",
 
1
  {
2
  "name": "discord-like-app",
3
+ "version": "2.9.0",
4
  "main": "server.js",
5
  "scripts": {
6
  "dev": "nodemon server.js",