save throttle improvements, remove dead code, cleanup

This commit is contained in:
Radon 2025-08-29 14:05:37 -05:00
parent 9350e27644
commit b40a639499
4 changed files with 102 additions and 81 deletions

6
.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="PROJECT" libraries="{embed, platform, widgets}" />
</component>
</project>

3
.idea/radchat.iml generated
View File

@ -5,5 +5,8 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="widgets" level="application" />
<orderEntry type="library" name="platform" level="application" />
<orderEntry type="library" name="embed" level="application" />
</component>
</module>

View File

@ -27,17 +27,13 @@ const CONFIG = {
}
},
APP: {
SAVE_RATE_THROTTLE: 500,
SAVE_RATE_THROTTLE: 1000,
SYSTEM_MSG_DEFAULT_TIMEOUT: 5000,
}
};
const $ = (sel) => document.querySelector(sel);
const $$ = (sel) => document.querySelectorAll(sel);
const enable = (el) => { if (el) el.disabled = true; };
const disable = (el) => { if (el) el.disabled = false; };
const show = (el, value) => { if (el) el.style.display = on ? '' : 'none'; };
const setText = (el, text) => { if (el) el.textContent = text; };
const EMOJIS = [
@ -118,7 +114,6 @@ class AppState {
this.mutedUsers = new Set();
this.userVolumes = new Map();
this.currentModalUserId = null;
this.isChatVisible = false;
this.messageTimeouts = new Map();
this.intentionalDisconnect = false;
this.peerHealthCheckInterval = null;
@ -133,7 +128,16 @@ class AppState {
const timeUntilNextSaveAllowed = CONFIG.APP.SAVE_RATE_THROTTLE - timeSinceLastSave;
if (timeUntilNextSaveAllowed > 0) {
console.log('Save throttled, please wait', timeUntilNextSaveAllowed, 'ms before saving');
if (state.saveTimeoutHandler) {
clearTimeout(state.saveTimeoutHandler);
}
state.saveTimeoutHandler = setTimeout(() => {
this.save();
clearTimeout(state.saveTimeoutHandler);
state.saveTimeoutHandler = null;
},
timeUntilNextSaveAllowed
);
return;
}
@ -225,9 +229,9 @@ class AppState {
return state.isDeafened ? 0 : state.headphoneVolume / CONFIG.MEDIA.MAX_VOLUME;
}
apparentInputVolume() {
return state.isMuted ? 0 : state.micVolume / CONFIG.MEDIA.MAX_VOLUME;
}
// apparentInputVolume() {
// return state.isMuted ? 0 : state.micVolume / CONFIG.MEDIA.MAX_VOLUME;
// }
}
const state = new AppState();
// ==================== UTILITY FUNCTIONS ====================
@ -257,67 +261,64 @@ class Utils {
}
static processMediaEmbeds(text) {
// Images (including GIFs)
text = text.replace(/(https?:\/\/[^\s]+\.(?:jpg|jpeg|png|gif|webp|svg|bmp|tiff|ico|avif|jfif)(?:\?[^\s]*)?)/gi,
text = text.replace(/(https?:\/\/\S+\.(?:jpg|jpeg|png|gif|webp|svg|bmp|tiff|ico|avif|jfif)(?:\?\S*)?)/gi,
'<img src="$1" alt="Image" class="embedded-image" loading="lazy">');
// Videos
text = text.replace(/(https?:\/\/[^\s]+\.(?:mp4|webm|ogg|avi|mov|wmv|flv|mkv|m4v|3gp)(?:\?[^\s]*)?)/gi,
text = text.replace(/(https?:\/\/\S+\.(?:mp4|webm|ogg|avi|mov|wmv|flv|mkv|m4v|3gp)(?:\?\S*)?)/gi,
'<video class="embedded-video" controls preload="metadata"><source src="$1">Your browser does not support the video tag.</video>');
// Audio
text = text.replace(/(https?:\/\/[^\s]+\.(?:mp3|wav|ogg|m4a|aac|flac|wma|opus)(?:\?[^\s]*)?)/gi,
text = text.replace(/(https?:\/\/\S+\.(?:mp3|wav|ogg|m4a|aac|flac|wma|opus)(?:\?\S*)?)/gi,
'<audio class="embedded-audio" controls preload="metadata"><source src="$1">Your browser does not support the audio tag.</audio>');
// YouTube (multiple formats)
text = text.replace(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})(?:[^\s]*)?/gi,
'<iframe class="embedded-video youtube" src="https://www.youtube.com/embed/$1" frameborder="0" allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/|youtube\.com\/v\/|youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})(?:\S*)?/gi,
'<iframe class="embedded-video youtube" src="https://www.youtube.com/embed/$1" style="border: 0;" allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>');
// Vimeo
text = text.replace(/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(?:video\/)?(\d+)(?:[^\s]*)?/gi,
'<iframe class="embedded-video vimeo" src="https://player.vimeo.com/video/$1" frameborder="0" allowfullscreen allow="autoplay; fullscreen; picture-in-picture"></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(?:video\/)?(\d+)(?:\S*)?/gi,
'<iframe class="embedded-video vimeo" src="https://player.vimeo.com/video/$1" style="border: 0;" allowfullscreen allow="autoplay; fullscreen; picture-in-picture"></iframe>');
// Twitch (clips and videos)
text = text.replace(/(?:https?:\/\/)?(?:www\.)?twitch\.tv\/videos\/(\d+)(?:[^\s]*)?/gi,
'<iframe class="embedded-video twitch" src="https://player.twitch.tv/?video=$1&parent=localhost" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:clips\.twitch\.tv\/|www\.twitch\.tv\/\w+\/clip\/)([a-zA-Z0-9_-]+)(?:[^\s]*)?/gi,
'<iframe class="embedded-video twitch" src="https://clips.twitch.tv/embed?clip=$1&parent=localhost" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:www\.)?twitch\.tv\/videos\/(\d+)(?:\S*)?/gi,
'<iframe class="embedded-video twitch" src="https://player.twitch.tv/?video=$1&parent=localhost" style="border: 0;" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:clips\.twitch\.tv\/|www\.twitch\.tv\/\w+\/clip\/)([a-zA-Z0-9_-]+)(?:\S*)?/gi,
'<iframe class="embedded-video twitch" src="https://clips.twitch.tv/embed?clip=$1&parent=localhost" style="border: 0;" allowfullscreen></iframe>');
// TikTok
text = text.replace(/(?:https?:\/\/)?(?:www\.)?tiktok\.com\/@[\w.-]+\/video\/(\d+)(?:[^\s]*)?/gi,
'<iframe class="embedded-video tiktok" src="https://www.tiktok.com/embed/$1" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:www\.)?tiktok\.com\/@[\w.-]+\/video\/(\d+)(?:\S*)?/gi,
'<iframe class="embedded-video tiktok" src="https://www.tiktok.com/embed/$1" style="border: 0;" allowfullscreen></iframe>');
// Twitter/X
text = text.replace(/(?:https?:\/\/)?(?:www\.)?(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)(?:[^\s]*)?/gi,
text = text.replace(/(?:https?:\/\/)?(?:www\.)?(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)(?:\S*)?/gi,
'<blockquote class="twitter-tweet"><a href="https://twitter.com/i/status/$1">Loading tweet...</a></blockquote><script async src="https://platform.twitter.com/widgets.js"></script>');
// Instagram
text = text.replace(/(?:https?:\/\/)?(?:www\.)?instagram\.com\/p\/([a-zA-Z0-9_-]+)(?:[^\s]*)?/gi,
text = text.replace(/(?:https?:\/\/)?(?:www\.)?instagram\.com\/p\/([a-zA-Z0-9_-]+)(?:\S*)?/gi,
'<blockquote class="instagram-media" data-instgrm-permalink="https://www.instagram.com/p/$1/"><a href="https://www.instagram.com/p/$1/">Loading Instagram post...</a></blockquote><script async src="//www.instagram.com/embed.js"></script>');
// Spotify
text = text.replace(/(?:https?:\/\/)?(?:open\.)?spotify\.com\/(track|album|playlist|artist)\/([a-zA-Z0-9]+)(?:[^\s]*)?/gi,
'<iframe class="embedded-audio spotify" src="https://open.spotify.com/embed/$1/$2" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:open\.)?spotify\.com\/(track|album|playlist|artist)\/([a-zA-Z0-9]+)(?:\S*)?/gi,
'<iframe class="embedded-audio spotify" src="https://open.spotify.com/embed/$1/$2" style="border: 0;" allowtransparency="true" allow="encrypted-media"></iframe>');
// SoundCloud
text = text.replace(/(?:https?:\/\/)?(?:www\.)?soundcloud\.com\/[\w-]+\/[\w-]+(?:[^\s]*)?/gi, (match) => {
return `<iframe class="embedded-audio soundcloud" src="https://w.soundcloud.com/player/?url=${encodeURIComponent(match)}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true" frameborder="0" allow="autoplay"></iframe>`;
text = text.replace(/(?:https?:\/\/)?(?:www\.)?soundcloud\.com\/[\w-]+\/[\w-]+(?:\S*)?/gi, (match) => {
return `<iframe class="embedded-audio soundcloud" src="https://w.soundcloud.com/player/?url=${encodeURIComponent(match)}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true" style="border: 0;" allow="autoplay"></iframe>`;
});
// Reddit
text = text.replace(/(?:https?:\/\/)?(?:www\.)?reddit\.com\/r\/[\w]+\/comments\/([a-zA-Z0-9]+)(?:[^\s]*)?/gi,
text = text.replace(/(?:https?:\/\/)?(?:www\.)?reddit\.com\/r\/\w+\/comments\/([a-zA-Z0-9]+)(?:\S*)?/gi,
'<blockquote class="reddit-embed-bq"><a href="https://www.reddit.com/r/posts/comments/$1/">Loading Reddit post...</a></blockquote><script async src="https://embed.redditmedia.com/widgets/platform.js"></script>');
// Discord Message Links (limited embed support)
text = text.replace(/(?:https?:\/\/)?(?:www\.)?discord\.com\/channels\/(\d+)\/(\d+)\/(\d+)(?:[^\s]*)?/gi,
text = text.replace(/(?:https?:\/\/)?(?:www\.)?discord\.com\/channels\/(\d+)\/(\d+)\/(\d+)(?:\S*)?/gi,
'<div class="discord-embed"><a href="https://discord.com/channels/$1/$2/$3" target="_blank">Discord Message Link</a></div>');
// GitHub Gists
text = text.replace(/(?:https?:\/\/)?gist\.github\.com\/[\w-]+\/([a-zA-Z0-9]+)(?:[^\s]*)?/gi,
'<script src="https://gist.github.com/$1.js"></script>');
// CodePen
text = text.replace(/(?:https?:\/\/)?codepen\.io\/([\w-]+)\/pen\/([a-zA-Z0-9]+)(?:[^\s]*)?/gi,
'<iframe class="embedded-code codepen" src="https://codepen.io/$1/embed/$2?default-tab=result" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?codepen\.io\/([\w-]+)\/pen\/([a-zA-Z0-9]+)(?:\S*)?/gi,
'<iframe class="embedded-code codepen" src="https://codepen.io/$1/embed/$2?default-tab=result" style="border: 0;" allowfullscreen></iframe>');
// JSFiddle
text = text.replace(/(?:https?:\/\/)?jsfiddle\.net\/([\w-]+\/)?([a-zA-Z0-9]+)(?:\/\d+)?(?:[^\s]*)?/gi,
'<iframe class="embedded-code jsfiddle" src="https://jsfiddle.net/$1$2/embedded/result/" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?jsfiddle\.net\/([\w-]+\/)?([a-zA-Z0-9]+)(?:\/\d+)?(?:\S*)?/gi,
'<iframe class="embedded-code jsfiddle" src="https://jsfiddle.net/$1$2/embedded/result/" style="border: 0;" allowfullscreen></iframe>');
// PDF files
text = text.replace(/(https?:\/\/[^\s]+\.pdf(?:\?[^\s]*)?)/gi,
'<iframe class="embedded-document pdf" src="$1" frameborder="0">Your browser does not support PDFs. <a href="$1">Download PDF</a></iframe>');
text = text.replace(/(https?:\/\/\S+\.pdf(?:\?\S*)?)/gi,
'<iframe class="embedded-document pdf" src="$1" style="border: 0;">Your browser does not support PDFs. <a href="$1">Download PDF</a></iframe>');
// Google Drive files (public)
text = text.replace(/(?:https?:\/\/)?drive\.google\.com\/file\/d\/([a-zA-Z0-9_-]+)(?:[^\s]*)?/gi,
'<iframe class="embedded-document google-drive" src="https://drive.google.com/file/d/$1/preview" frameborder="0" allowfullscreen></iframe>');
text = text.replace(/(?:https?:\/\/)?drive\.google\.com\/file\/d\/([a-zA-Z0-9_-]+)(?:\S*)?/gi,
'<iframe class="embedded-document google-drive" src="https://drive.google.com/file/d/$1/preview" style="border: 0;" allowfullscreen></iframe>');
// Dropbox files
text = text.replace(/(?:https?:\/\/)?(?:www\.)?dropbox\.com\/s\/([a-zA-Z0-9_-]+)\/[^?\s]*(?:\?[^\s]*)?/gi,
'<iframe class="embedded-document dropbox" src="https://www.dropbox.com/s/$1?raw=1" frameborder="0"></iframe>');
text = text.replace(/(?:https?:\/\/)?(?:www\.)?dropbox\.com\/s\/([a-zA-Z0-9_-]+)\/[^?\s]*(?:\?\S*)?/gi,
'<iframe class="embedded-document dropbox" src="https://www.dropbox.com/s/$1?raw=1" style="border: 0;"></iframe>');
// Generic document types (will be downloaded)
text = text.replace(/(https?:\/\/[^\s]+\.(?:doc|docx|xls|xlsx|ppt|pptx|txt|rtf|odt|ods|odp)(?:\?[^\s]*)?)/gi,
text = text.replace(/(https?:\/\/\S+\.(?:doc|docx|xls|xlsx|ppt|pptx|txt|rtf|odt|ods|odp)(?:\?\S*)?)/gi,
'<div class="embedded-document generic"><a href="$1" download>📄 Download Document</a></div>');
// Convert remaining plain URLs to clickable links (do this last to avoid conflicts)
// First, temporarily replace all existing HTML to protect it
@ -328,7 +329,7 @@ class Utils {
return placeholder;
});
// Convert remaining plain URLs to clickable links (only http/https)
text = text.replace(/(^|[\s\n])(https?:\/\/[^\s]+)/gi, (_, leadingChar, url) => {
text = text.replace(/(^|[\s\n])(https?:\/\/\S+)/gi, (_, leadingChar, url) => {
const cleanUrl = url.trim();
const maxUrlLength = 50;
// Shorten display URL if too long
@ -343,7 +344,14 @@ class Utils {
}
static formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
return date.toLocaleTimeString([], {
month: 'numeric',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
static async checkUsernameAvailability(username) {
try {
@ -385,9 +393,10 @@ class UIManager {
}
static scrollChatToBottom() {
const atBottom = UIManager.isChatScrolledToBottom($('.chat-messages'));
const chatMessages = $('.chat-messages');
const atBottom = UIManager.isChatScrolledToBottom(chatMessages);
if (!atBottom) {
$('.chat-messages').scrollTop = $('.chat-messages').scrollHeight;
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
@ -511,8 +520,8 @@ class UIManager {
// if (chatInputContainer) {
chatInputContainer.style.position = 'relative';
chatInputContainer.appendChild(emojiPicker);
chatInputContainer.style.position = 'relative';
chatInputContainer.appendChild(emojiPicker);
// }
}
@ -585,19 +594,24 @@ class AudioManager {
if (state.localStream) {
state.localStream.getTracks().forEach(track => track.stop());
}
// Clean up existing audio processing
state.cleanupAudioProcessing();
const stream = await navigator.mediaDevices.getUserMedia({
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Microphone request timeout')), CONFIG.OUTPUT.MIC_REQUEST_TIMEOUT);
});
const mediaPromise = navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: CONFIG.OUTPUT.ECHO_CANCELLATION,
noiseSuppression: CONFIG.OUTPUT.NOISE_SUPPRESSION,
latency: CONFIG.OUTPUT.LATENCY,
sampleRate: CONFIG.OUTPUT.DEVICE_ID,
sampleRate: CONFIG.OUTPUT.SAMPLE_RATE, // Fixed: was using DEVICE_ID
deviceId: CONFIG.OUTPUT.DEVICE_ID,
}
});
state.localStream = stream;
state.localStream = await Promise.race([mediaPromise, timeoutPromise]);
state.micPermissionGranted = true;
// Set up audio processing with gain control
@ -1245,7 +1259,7 @@ class PeerConnectionManager {
PeerConnectionManager.stopPeerHealthCheck();
return;
}
const userCards = document.querySelectorAll('.user-card:not(.current-user-card)');
const userCards = $$('.user-card:not(.current-user-card)');
const expectedPeerIds = Array.from(userCards).map(card => card.dataset.userId);
if (expectedPeerIds.length > 0) {
PeerConnectionManager.retryMissingPeerConnections(expectedPeerIds);
@ -1467,7 +1481,7 @@ class UserManager {
}
} else {
console.log('Error: User card not found for userId:', userId);
const allCards = document.querySelectorAll('.user-card');
const allCards = $$('.user-card');
console.log('Info: All user cards:', Array.from(allCards).map(card => ({
id: card.dataset.userId,
element: card
@ -1577,7 +1591,7 @@ class VoiceControls {
}
static toggleDeafen() {
state.isDeafened = !state.isDeafened;
const audioElements = document.querySelectorAll('#audio-container audio');
const audioElements = $$('#audio-container audio');
audioElements.forEach(audio => {
audio.muted = state.isDeafened;
});
@ -1613,7 +1627,7 @@ class VoiceControls {
const percentage = document.getElementById('headphone-volume-percentage');
state.headphoneVolume = parseInt(slider.value);
percentage.textContent = `${state.headphoneVolume}%`;
const audioElements = document.querySelectorAll('#audio-container audio');
const audioElements = $$('#audio-container audio');
audioElements.forEach(audio => {
const userId = audio.id.replace('audio-', '');
const userVolume = state.userVolumes.get(userId) || 100;
@ -1697,12 +1711,12 @@ class ModalManager {
modal.style.display = 'flex';
console.log('Opened self audio settings modal');
}
static closeSelfModal() {
const modal = document.getElementById('user-control-modal');
modal.style.display = 'none';
state.currentModalUserId = null;
console.log('Closed self audio settings modal');
}
// static closeSelfModal() {
// const modal = document.getElementById('user-control-modal');
// modal.style.display = 'none';
// state.currentModalUserId = null;
// console.log('Closed self audio settings modal');
// }
static openUserModal(userId, username) {
state.currentModalUserId = userId;
const modal = document.getElementById('user-control-modal');
@ -1762,7 +1776,7 @@ class ModalManager {
audioElement.volume = volume / 100;
}
// now update the audio so the changes actually take effect
const audioElements = document.querySelectorAll('#audio-container audio');
const audioElements = $$('#audio-container audio');
audioElements.forEach(audio => {
const userId = audio.id.replace('audio-', '');
const userVolume = state.userVolumes.get(userId) || 100;
@ -1906,7 +1920,7 @@ class EventHandlers {
});
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') {
const audioElements = document.querySelectorAll('#audio-container audio');
const audioElements = $$('#audio-container audio');
audioElements.forEach(audio => {
if (audio.paused) {
audio.play().catch(console.error);
@ -2001,7 +2015,7 @@ class DebugUtils {
Object.entries(state.peerConnections).forEach(([userId, pc]) => {
console.log(`🔍 Peer ${userId}: `, pc.connectionState);
});
const audioElements = document.querySelectorAll('#audio-container audio');
const audioElements = $$('#audio-container audio');
console.log('🔍 Remote audio elements:', audioElements.length);
audioElements.forEach((audio, i) => {
console.log(`🔍 Audio ${i}: `, {
@ -2028,7 +2042,7 @@ class DebugUtils {
}
// ==================== PERIODIC TASKS ====================
const checkAudioContext = setInterval(() => {
setInterval(() => {
if (state.isInVoiceChat) {
console.log('🔄 Performing periodic voice chat maintenance tasks...');
if (state.audioContext.state === 'suspended') {
@ -2039,15 +2053,15 @@ const checkAudioContext = setInterval(() => {
}, 5000);
$('.chat-messages').addEventListener('scroll', function() {
const atBottom = UIManager.isChatScrolledToBottom();
if (!atBottom) {
$('#scroll-btn').style.display = 'block';
}
if (atBottom) {
$('#scroll-btn').style.display = 'none';
}
});
$('.chat-messages').addEventListener('scroll', function() {
const atBottom = UIManager.isChatScrolledToBottom();
if (!atBottom) {
$('#scroll-btn').style.display = 'block';
}
if (atBottom) {
$('#scroll-btn').style.display = 'none';
}
});
// ==================== INITIALIZATION ====================
@ -2100,4 +2114,4 @@ window.toggleEmojiPicker = UIManager.toggleEmojiPicker;
// Debug functions
window.debugVoiceSetup = DebugUtils.debugVoiceSetup;
window.fixVoice = DebugUtils.fixVoice;
window.scrollChatToBottom = UIManager.scrollChatToBottom;
window.scrollChatToBottom = UIManager.scrollChatToBottom;

View File

@ -305,7 +305,6 @@ main {
background-color: currentColor;
opacity: 0.3;
animation: timeoutBar var(--timeout-duration, 0ms) linear forwards;
}
@keyframes timeoutBar {
@ -678,7 +677,6 @@ main {
margin: 0 auto;
}
/* Beautiful Message Styling */
.chat-message {
padding: 0.75rem 1rem;
border-radius: 12px;