[call-me] - improve UI & logs

This commit is contained in:
Miroslav Pejic
2026-02-08 23:10:17 +01:00
parent 48f3d75347
commit d011fea861
8 changed files with 1394 additions and 509 deletions
+1 -4
View File
@@ -9,6 +9,7 @@ PORT=8000
# Logging
LOGS_DEBUG=true # true or false
LOGS_JSON=false # Enable JSON formatted logs (true/false)
LOGS_JSON_PRETTY=false # Enable pretty JSON formatted logs (true/false)
@@ -50,10 +51,6 @@ NGROK_AUTH_TOKEN=YourNgrokAuthToken
TZ=UTC
# Logs
DEBUG=true # true or false
# API
API_KEY_SECRET=call_me_api_key_secret # change me
+2 -1
View File
@@ -4,6 +4,7 @@ const util = require('util');
const colors = require('colors');
colors.enable(); // colors.disable();
const LOGS_DEBUG = process.env.LOGS_DEBUG !== undefined ? process.env.LOGS_DEBUG === 'true' : true;
const LOGS_JSON = process.env.LOGS_JSON ? process.env.LOGS_JSON === 'true' : false;
const LOGS_JSON_PRETTY = process.env.LOGS_JSON_PRETTY ? process.env.LOGS_JSON_PRETTY === 'true' : false;
@@ -15,7 +16,7 @@ const options = {
module.exports = class Logs {
constructor(appName = 'call-me') {
this.appName = colors.yellow(appName);
this.debugOn = process.env.DEBUG !== undefined ? process.env.DEBUG === 'true' : true;
this.debugOn = LOGS_DEBUG;
this.timeStart = Date.now();
this.timeEnd = null;
this.timeElapsedMs = null;
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "call-me",
"version": "1.2.89",
"version": "1.2.90",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "call-me",
"version": "1.2.89",
"version": "1.2.90",
"license": "AGPLv3",
"dependencies": {
"@ngrok/ngrok": "1.7.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "call-me",
"version": "1.2.89",
"version": "1.2.90",
"description": "Your Go-To for Instant Video Calls",
"author": "Miroslav Pejic - miroslav.pejic.85@gmail.com",
"license": "AGPLv3",
+51 -17
View File
@@ -182,6 +182,8 @@ async function checkHostPassword(maxRetries = 3, attempts = 0) {
if (config.isPasswordRequired) {
// Show prompt for the password
const { value: password } = await Swal.fire({
heightAuto: false,
scrollbarPadding: false,
title: 'Host Protected',
text: 'Please enter the host password:',
input: 'password',
@@ -216,6 +218,8 @@ async function checkHostPassword(maxRetries = 3, attempts = 0) {
if (validationResult.success) {
await Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
icon: 'success',
title: 'Access Granted',
@@ -228,6 +232,8 @@ async function checkHostPassword(maxRetries = 3, attempts = 0) {
attempts++;
if (attempts < maxRetries) {
await Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
icon: 'error',
title: 'Invalid Password',
@@ -237,6 +243,8 @@ async function checkHostPassword(maxRetries = 3, attempts = 0) {
await checkHostPassword(maxRetries, attempts);
} else {
await Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
icon: 'warning',
title: 'Too Many Attempts',
@@ -251,6 +259,8 @@ async function checkHostPassword(maxRetries = 3, attempts = 0) {
} catch (error) {
console.error('Error:', error);
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
icon: 'error',
title: 'Error',
@@ -460,12 +470,12 @@ async function handleEnumerateDevices() {
const videoInputs = devices.filter((device) => device.kind === 'videoinput');
if (videoInputs.length > 1 && userInfo.device.isMobile) {
swapCameraBtn.addEventListener('click', swapCamera);
elemDisplay(swapCameraBtn, true, 'inline');
elemDisplay(swapCameraBtn, true, 'inline-flex');
}
// Check if screen sharing is supported
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
elemDisplay(screenShareBtn, true, 'inline');
elemDisplay(screenShareBtn, true, 'inline-flex');
} else {
elemDisplay(screenShareBtn, false);
console.log('Screen sharing not supported in this browser');
@@ -1217,6 +1227,8 @@ function handleMediaStreamError(error) {
}
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
icon: 'warning',
html: errorMessage,
@@ -1343,6 +1355,8 @@ function offerAccept(data) {
}
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'top',
imageUrl: 'assets/ring.png',
imageWidth: 284,
@@ -1617,17 +1631,15 @@ function handleLeave(disconnect = true) {
// Display toast messages
function toast(message, icon = 'info', position = 'top', timer = 3000) {
const Toast = Swal.mixin({
toast: true,
position: position,
icon: icon,
showConfirmButton: false,
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position,
icon,
html: message,
timer,
timerProgressBar: true,
timer: timer,
});
Toast.fire({
icon: icon,
title: message,
showConfirmButton: false,
showClass: { popup: 'animate__animated animate__fadeInDown' },
hideClass: { popup: 'animate__animated animate__fadeOutUp' },
});
@@ -1638,6 +1650,8 @@ function handleError(message, error = false, position = 'top', timer = 6000) {
if (error) console.error(error);
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position,
icon: 'warning',
html: message,
@@ -1653,6 +1667,8 @@ function handleError(message, error = false, position = 'top', timer = 6000) {
// Display Message to user
function popupMsg(message, position = 'top', timer = 4000) {
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position,
html: message,
timerProgressBar: true,
@@ -1767,8 +1783,21 @@ function setupDataChannel(channel) {
console.log('Data channel closed');
};
dataChannel.onerror = (error) => {
console.error('Data channel error:', error);
dataChannel.onerror = (event) => {
// Some browsers emit an error event during/after close (e.g. "User-Initiated Abort, reason=Close called").
// That's expected during hang-up/teardown and shouldn't be shown to the user.
const rtcError = event?.error;
const message = (rtcError && (rtcError.message || rtcError.reason)) || '';
const isClosingOrClosed = dataChannel?.readyState && dataChannel.readyState !== 'open';
const isBenignCloseError =
rtcError?.name === 'OperationError' && /close called|user-initiated abort|abort/i.test(String(message));
if (isClosingOrClosed || isBenignCloseError) {
console.debug('Ignoring data channel close-related error:', event);
return;
}
console.error('Data channel error:', event);
toast('Data channel error occurred', 'warning', 'top', 3000);
};
@@ -1985,7 +2014,8 @@ function renderUserList() {
if (isInActiveCall) {
// Show hang-up button only if in active call (user has answered)
actionBtnEl.className = 'btn btn-custom btn-danger btn-s hangup-user-btn fas fa-phone-slash';
actionBtnEl.className = 'btn btn-custom btn-danger btn-s hangup-user-btn';
actionBtnEl.innerHTML = '<i class="fas fa-phone-slash"></i>';
actionBtnEl.title = `Hang up call with ${user}`;
actionBtnEl.addEventListener('click', (e) => {
e.stopPropagation();
@@ -1998,7 +2028,8 @@ function renderUserList() {
});
} else {
// Show call button if not in active call
actionBtnEl.className = 'btn btn-custom btn-warning btn-s call-user-btn fas fa-phone';
actionBtnEl.className = 'btn btn-custom btn-warning btn-s call-user-btn';
actionBtnEl.innerHTML = '<i class="fas fa-phone"></i>';
actionBtnEl.title = `Call ${user}`;
actionBtnEl.addEventListener('click', (e) => {
e.stopPropagation();
@@ -2013,7 +2044,8 @@ function renderUserList() {
// Send file button
const sendFileBtn = document.createElement('button');
sendFileBtn.className = 'btn btn-custom btn-secondary btn-s fas fa-paperclip';
sendFileBtn.className = 'btn btn-custom btn-secondary btn-s';
sendFileBtn.innerHTML = '<i class="fas fa-paperclip"></i>';
sendFileBtn.style.marginRight = '10px';
sendFileBtn.style.cursor = 'pointer';
sendFileBtn.setAttribute('data-toggle', 'tooltip');
@@ -2538,6 +2570,8 @@ function handleClearChatClick() {
}
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
position: 'center',
icon: 'question',
title: 'Clear Chat Messages',
+2
View File
@@ -249,6 +249,8 @@ function updateCustomTranslations() {
function showTranslatedAlert(titleKey, textKey, icon = 'info') {
if (typeof Swal !== 'undefined') {
Swal.fire({
heightAuto: false,
scrollbarPadding: false,
title: t(titleKey),
text: t(textKey),
icon: icon,
+104 -110
View File
@@ -61,29 +61,25 @@
</div>
<!-- Sign-in Page -->
<div id="signInPage" class="container text-center center">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header"><h1 id="appName" data-i18n="appName">Call-me</h1></div>
<div class="card-body">
<!-- Sign-in Form -->
<div class="mb-3">
<!-- Input field for entering the username -->
<input
id="usernameIn"
type="text"
placeholder="Enter username"
data-i18n-placeholder="signIn.username"
required
/>
</div>
<!-- Sign-in button -->
<button id="signInBtn" class="btn btn-primary" data-i18n="signIn.button">
Sign In
</button>
<div id="signInPage" class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header"><h1 id="appName" data-i18n="appName">Call-me</h1></div>
<div class="card-body">
<!-- Sign-in Form -->
<div class="mb-3">
<!-- Input field for entering the username -->
<input
id="usernameIn"
type="text"
placeholder="Enter username"
data-i18n-placeholder="signIn.username"
required
/>
</div>
<!-- Sign-in button -->
<button id="signInBtn" class="btn btn-primary" data-i18n="signIn.button">Sign In</button>
</div>
</div>
</div>
@@ -91,7 +87,7 @@
</div>
<!-- Room Page -->
<div id="roomPage" class="container text-center center">
<div id="roomPage" class="container">
<!-- Session time -->
<span id="sessionTime">0s</span>
@@ -111,89 +107,87 @@
<span id="remoteUsername" class="hide"></span>
</div>
<div class="row text-center">
<div class="col-md-12">
<!-- Button share Room -->
<button
id="shareRoomBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Share room"
>
<i class="fas fa-share-nodes"></i>
</button>
<!-- Button to hide/show the local video -->
<button
id="hideBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle hide me"
>
<i class="fas fa-eye-slash"></i>
</button>
<!-- Button to toggle audio stream -->
<button
id="audioBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle audio"
>
<i class="fas fa-microphone"></i>
</button>
<!-- Button to swap camera -->
<button
id="swapCameraBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Swap camera"
>
<i class="fas fa-camera-rotate"></i>
</button>
<!-- Button to toggle video stream -->
<button
id="videoBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle video"
>
<i class="fas fa-video"></i>
</button>
<!-- Button to share screen -->
<button
id="screenShareBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle screen"
>
<i class="fas fa-desktop"></i>
</button>
<!-- Toggle user sidebar button -->
<button
id="sidebarBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle users"
>
<i class="fas fa-users"></i>
</button>
<!-- Button to leave the call -->
<button
id="leaveBtn"
class="btn btn-custom btn-danger btn-m"
data-toggle="tooltip"
data-placement="top"
title="Leave"
>
<i class="fas fa-door-open"></i>
</button>
</div>
<div class="row justify-content-center">
<!-- Button share Room -->
<button
id="shareRoomBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Share room"
>
<i class="fas fa-share-nodes"></i>
</button>
<!-- Button to hide/show the local video -->
<button
id="hideBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle hide me"
>
<i class="fas fa-eye-slash"></i>
</button>
<!-- Button to toggle audio stream -->
<button
id="audioBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle audio"
>
<i class="fas fa-microphone"></i>
</button>
<!-- Button to toggle video stream -->
<button
id="videoBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle video"
>
<i class="fas fa-video"></i>
</button>
<!-- Button to swap camera -->
<button
id="swapCameraBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Swap camera"
>
<i class="fas fa-camera-rotate"></i>
</button>
<!-- Button to share screen -->
<button
id="screenShareBtn"
class="btn btn-custom btn-success btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle screen"
>
<i class="fas fa-desktop"></i>
</button>
<!-- Toggle user sidebar button -->
<button
id="sidebarBtn"
class="btn btn-custom btn-primary btn-m"
data-toggle="tooltip"
data-placement="top"
title="Toggle users"
>
<i class="fas fa-users"></i>
</button>
<!-- Button to leave the call -->
<button
id="leaveBtn"
class="btn btn-custom btn-danger btn-m"
data-toggle="tooltip"
data-placement="top"
title="Leave"
>
<i class="fas fa-phone-slash red"></i>
</button>
</div>
</div>
@@ -233,7 +227,7 @@
</div>
<!-- Chat Tab Content -->
<div id="chatContent" class="tab-content mt-5">
<div id="chatContent" class="tab-content">
<div id="chatMessages" class="chat-messages"></div>
<form id="chatForm" class="chat-form" autocomplete="off">
<button
@@ -335,13 +329,13 @@
<!-- Include SweetAlert JS file -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.4.8/dist/sweetalert2.all.min.js"></script>
<!-- Include Bootstrap JavaScript file -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<!-- Include GitHub buttons -->
<script src="https://buttons.github.io/buttons.js"></script>
<!-- Include Emoji Mart for emoji picker -->
<script defer src="https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"></script>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+1231 -374
View File
File diff suppressed because it is too large Load Diff