[call-me] - feat: use RINGING_TIMEOUT env var for call timeout, dynamic progress bar, and looping ring sound

This commit is contained in:
Miroslav Pejic
2026-05-07 21:25:10 +02:00
parent dda520f24c
commit 361c322bc8
6 changed files with 74 additions and 74 deletions
+5 -1
View File
@@ -76,4 +76,8 @@ PUSH_VAPID_EMAIL='mailto:admin@example.com'
SENTRY_ENABLED=false # true or false
SENTRY_LOG_LEVELS=error # Log levels to capture in Sentry (e.g., error,warn)
SENTRY_DSN=
SENTRY_TRACES_SAMPLE_RATE=0.5 # Adjust the sample rate for performance monitoring (0.0 to 1.0)
SENTRY_TRACES_SAMPLE_RATE=0.5 # Adjust the sample rate for performance monitoring (0.0 to 1.0)
# Time in seconds before a call is considered unanswered (default 30 seconds)
RINGING_TIMEOUT=30
+2
View File
@@ -92,6 +92,7 @@ const config = {
pushVapidPrivateKey: process.env.PUSH_VAPID_PRIVATE_KEY || '',
pushVapidEmail: process.env.PUSH_VAPID_EMAIL || 'mailto:admin@example.com',
randomImageUrl: process.env.RANDOM_IMAGE_URL || '',
ringTimeout: parseInt(process.env.RINGING_TIMEOUT, 10) || 30,
apiBasePath: '/api/v1',
swaggerDocument: yaml.load(fs.readFileSync(path.join(__dirname, '/api/swagger.yaml'), 'utf8')),
};
@@ -529,6 +530,7 @@ function handleConnection(socket) {
message: 'Hello Client!',
iceServers: config.iceServers,
pushEnabled: config.pushEnabled,
ringTimeout: config.ringTimeout,
});
}
+21 -66
View File
@@ -1,16 +1,16 @@
{
"name": "call-me",
"version": "1.3.44",
"version": "1.3.45",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "call-me",
"version": "1.3.44",
"version": "1.3.45",
"license": "AGPLv3",
"dependencies": {
"@ngrok/ngrok": "1.7.0",
"@sentry/node": "^10.51.0",
"@sentry/node": "^10.52.0",
"axios": "^1.16.0",
"colors": "^1.4.0",
"cors": "^2.8.6",
@@ -522,23 +522,6 @@
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/instrumentation-ioredis": {
"version": "0.62.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.62.0.tgz",
"integrity": "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/redis-common": "^0.38.2",
"@opentelemetry/semantic-conventions": "^1.33.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/instrumentation-kafkajs": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.23.0.tgz",
@@ -690,23 +673,6 @@
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/instrumentation-redis": {
"version": "0.62.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.62.0.tgz",
"integrity": "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/redis-common": "^0.38.2",
"@opentelemetry/semantic-conventions": "^1.27.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/instrumentation-tedious": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.33.0.tgz",
@@ -724,15 +690,6 @@
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/redis-common": {
"version": "0.38.3",
"resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.3.tgz",
"integrity": "sha512-VCghU1JYs/4gP6Gqf/xro9MEsZ7LrMv2uONVsaESKL38ZOB9BqnI98FfS23wjMnHlpuE+TTaWSoAVNpTwYXzjw==",
"license": "Apache-2.0",
"engines": {
"node": "^18.19.0 || >=20.6.0"
}
},
"node_modules/@opentelemetry/resources": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz",
@@ -844,18 +801,18 @@
}
},
"node_modules/@sentry/core": {
"version": "10.51.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.51.0.tgz",
"integrity": "sha512-Y45V/YXvVLEXmOdkbD1oG1gkRWFi9guCEGg3PlIlIpRjAbZUrvLGgjRJIc1E7XpSzmOnWbs5BbUxMv4PDaPj2w==",
"version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.52.0.tgz",
"integrity": "sha512-VA/kAqLhkMnRWY2RXdBLyTemR9D4m7MVRy/gyapoq9yvllVPx9WXbvKgnMP2LQp7mFgT/oLFvw58aQKaYTGn3A==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/node": {
"version": "10.51.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.51.0.tgz",
"integrity": "sha512-2yZLRZwS1dKG8/4eOTpGSo/gO/EgmT9aPj6lAzUkRa7bZCTTdW4BraaHU0leX5T94909Qfhbr3W5AVTfDOCKiQ==",
"version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.52.0.tgz",
"integrity": "sha512-9+p3KJUk3rHO1HOEZuSknP2RgKCJZONDm4HWgkVDtVBtocb66KLtVlMjc59d2/bWP7tM3wc877tpG30quFfU9g==",
"license": "MIT",
"dependencies": {
"@fastify/otel": "0.18.0",
@@ -870,7 +827,6 @@
"@opentelemetry/instrumentation-graphql": "0.62.0",
"@opentelemetry/instrumentation-hapi": "0.60.0",
"@opentelemetry/instrumentation-http": "0.214.0",
"@opentelemetry/instrumentation-ioredis": "0.62.0",
"@opentelemetry/instrumentation-kafkajs": "0.23.0",
"@opentelemetry/instrumentation-knex": "0.58.0",
"@opentelemetry/instrumentation-koa": "0.62.0",
@@ -880,14 +836,13 @@
"@opentelemetry/instrumentation-mysql": "0.60.0",
"@opentelemetry/instrumentation-mysql2": "0.60.0",
"@opentelemetry/instrumentation-pg": "0.66.0",
"@opentelemetry/instrumentation-redis": "0.62.0",
"@opentelemetry/instrumentation-tedious": "0.33.0",
"@opentelemetry/sdk-trace-base": "^2.6.1",
"@opentelemetry/semantic-conventions": "^1.40.0",
"@prisma/instrumentation": "7.6.0",
"@sentry/core": "10.51.0",
"@sentry/node-core": "10.51.0",
"@sentry/opentelemetry": "10.51.0",
"@sentry/core": "10.52.0",
"@sentry/node-core": "10.52.0",
"@sentry/opentelemetry": "10.52.0",
"import-in-the-middle": "^3.0.0"
},
"engines": {
@@ -895,13 +850,13 @@
}
},
"node_modules/@sentry/node-core": {
"version": "10.51.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.51.0.tgz",
"integrity": "sha512-VP9DMEzBEuauABrfDHYz/pRYa74M09uRJLz0ls3yel3sKhYHMyCB29ZxbKcciUhD4d33dwgi8DbaPZV2H/wnfQ==",
"version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.52.0.tgz",
"integrity": "sha512-IG7MBtLRPQ2LuU+kbD14AFZroZgAeUmJQTP1FI/F8n56O31+p+9R703LuBTpvZr6sm+eRYDMWcGYYkfLHRVjwg==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.51.0",
"@sentry/opentelemetry": "10.51.0",
"@sentry/core": "10.52.0",
"@sentry/opentelemetry": "10.52.0",
"import-in-the-middle": "^3.0.0"
},
"engines": {
@@ -937,12 +892,12 @@
}
},
"node_modules/@sentry/opentelemetry": {
"version": "10.51.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.51.0.tgz",
"integrity": "sha512-Qc7AlCE4uhB+SvHLqah4RgR1WdY7wmmr/hx9g/prDP9R1ocshmUEMrZK9qjuwaklW7/fmkFCXI8ETxo5L1bHIA==",
"version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.52.0.tgz",
"integrity": "sha512-Sc7StsvC0bwhMcgDfTRWUIexO5cNzzKUurvUwtpgQUnxO7AzexU3lkY3yHYDsCbWYAEQMXAgQYQtbcqoh+Ie7g==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.51.0"
"@sentry/core": "10.52.0"
},
"engines": {
"node": ">=18"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "call-me",
"version": "1.3.44",
"version": "1.3.45",
"description": "Your Go-To for Instant Video Calls",
"author": "Miroslav Pejic - miroslav.pejic.85@gmail.com",
"license": "AGPLv3",
@@ -21,7 +21,7 @@
},
"dependencies": {
"@ngrok/ngrok": "1.7.0",
"@sentry/node": "^10.51.0",
"@sentry/node": "^10.52.0",
"axios": "^1.16.0",
"colors": "^1.4.0",
"cors": "^2.8.6",
+43 -4
View File
@@ -90,6 +90,8 @@ let callingTimerId = null; // Timer for calling overlay
let callingElapsed = 0; // Seconds elapsed while calling
let incomingCallData = null; // Pending incoming call data
let incomingCallTimerId = null; // Auto-decline timer for incoming call
let ringTimeout = 30; // Seconds before unanswered call is auto-cancelled/declined (from server)
let ringingAudio = null; // Looping ring sound for incoming call
let thisConnection;
let camera = 'user';
let stream;
@@ -777,6 +779,9 @@ function showCallingOverlay(targetUser) {
callingTimerId = setInterval(() => {
callingElapsed++;
if (callingTimer) callingTimer.textContent = callingElapsed + 's';
if (callingElapsed >= ringTimeout) {
handleCancelCall();
}
}, 1000);
}
@@ -1375,6 +1380,9 @@ function handlePing(data) {
if (data.pushEnabled !== undefined) {
pushEnabled = data.pushEnabled;
}
if (data.ringTimeout !== undefined) {
ringTimeout = data.ringTimeout;
}
sendMsg({
type: 'pong',
message: {
@@ -1717,7 +1725,6 @@ function offerAccept(data) {
incomingCallData = data;
showIncomingCallOverlay(data.from);
sound('ring');
}
// Show incoming call overlay
@@ -1725,20 +1732,47 @@ function showIncomingCallOverlay(callerName) {
if (!incomingCallOverlay) return;
if (incomingCallUsername) incomingCallUsername.textContent = callerName;
// Reset timer bar animation
// Reset timer bar animation with dynamic duration
if (incomingCallTimer) {
incomingCallTimer.style.setProperty('--ring-duration', ringTimeout + 's');
incomingCallTimer.style.animation = 'none';
incomingCallTimer.offsetHeight; // Force reflow
incomingCallTimer.style.animation = '';
}
// Start looping ring sound with a 1s gap between plays
if (ringingAudio) {
ringingAudio.pause();
ringingAudio = null;
}
let ringingDelayTimer = null;
function playRing() {
if (!ringingAudio) return;
ringingAudio.currentTime = 0;
ringingAudio.play().catch(() => {});
}
ringingAudio = new Audio('./assets/ring.wav');
ringingAudio.volume = 0.5;
ringingAudio.addEventListener('ended', () => {
ringingDelayTimer = setTimeout(playRing, 3000);
});
// Store the delay timer on the audio object so hideIncomingCallOverlay can clear it
ringingAudio._delayTimer = null;
Object.defineProperty(ringingAudio, '_delayTimer', {
get: () => ringingDelayTimer,
set: (v) => {
ringingDelayTimer = v;
},
});
playRing();
incomingCallOverlay.style.display = 'flex';
// Auto-decline after 10 seconds
// Auto-decline after ringTimeout seconds
if (incomingCallTimerId) clearTimeout(incomingCallTimerId);
incomingCallTimerId = setTimeout(() => {
handleDeclineIncomingCall();
}, 10000);
}, ringTimeout * 1000);
}
// Hide incoming call overlay
@@ -1749,6 +1783,11 @@ function hideIncomingCallOverlay() {
clearTimeout(incomingCallTimerId);
incomingCallTimerId = null;
}
if (ringingAudio) {
clearTimeout(ringingAudio._delayTimer);
ringingAudio.pause();
ringingAudio = null;
}
incomingCallData = null;
}
+1 -1
View File
@@ -3010,7 +3010,7 @@ z-index:
height: 100%;
width: 100%;
background: linear-gradient(90deg, var(--success-color), var(--primary-color));
animation: incomingTimerBar 10s linear forwards;
animation: incomingTimerBar var(--ring-duration, 30s) linear forwards;
transform-origin: left;
}