import { config } from "./config.js"; import { waha } from "./waha.js"; import { ui, elements } from "./ui.js"; import { websocket } from "./websocket.js"; import { compensateMessageOrdering, formatTime } from "./utils.js"; import { fetchChats, getAppUser, getChatMessages, getChats, updateOnlineStatus } from "./storage.js"; import { upsertMessages } from "./db.js"; let activeChatState = null; const messageTone = new Audio("./message.ogg"); document.addEventListener('DOMContentLoaded', async () => { await updateOnlineStatus(); setupEventListeners(); try { elements.loggedUserName.textContent = await getAppUser().pushName; loadChats(); checkWahaStatus(); initWebSocket(); } finally { elements.chatsLoader.classList.add('hidden'); } }); function loadChats() { try { elements.chatsLoader.classList.remove('hidden'); fetchChats().then(() => { ui.renderChatList(getChats(), activeChatState, selectChat); }); } catch (error) { console.error('Failed to load chats:', error); elements.chatList.innerHTML = `
  • Connection to WAHA failed.

    Ensure WAHA server is running and CORS is enabled, or click Settings to configure.

    ${error.message}

  • `; } finally { elements.chatsLoader.classList.add('hidden'); } } function setupEventListeners() { document.addEventListener('keydown', (e) => { if (e.code == "Escape") { e.preventDefault(); ui.toggleChatState(); } }); elements.refreshChatsBtn.addEventListener('click', () => { elements.refreshChatsBtn.classList.add('spinning'); loadChats() setTimeout(() => { elements.refreshChatsBtn.classList.remove('spinning'); }, 600); }); elements.chatSearch.addEventListener('input', (e) => { const query = e.target.value.toLowerCase(); const filtered = getChats().filter(chat => chat.name.toLowerCase().includes(query) ); ui.renderChatList(filtered, activeChatState, selectChat); }); elements.messageForm.addEventListener('submit', (e) => { e.preventDefault(); sendMessage(); }); elements.chatBottomBar.style.height = `${elements.chatInputPanel.offsetHeight}px`; const observer = new ResizeObserver(() => { elements.chatBottomBar.style.height = `${elements.chatInputPanel.offsetHeight}px`; }); observer.observe(elements.chatInputPanel); elements.chatBottomBarBtn.addEventListener('click', ui.toggleChatBottomBar); elements.chatBottomBar.addEventListener('click', (e) => { if (e.target == e.currentTarget) ui.toggleChatBottomBar(); }); elements.attachmentBtn.addEventListener('click', () => { elements.attachmentInput.click(); }) elements.attachmentInput.addEventListener('change', function () { const firstFile = this.files[0]; sendFileMessage(firstFile); }) elements.backToSidebarBtn.addEventListener('click', () => { elements.sidebar.classList.remove('hidden'); elements.activeChatContainer.classList.add('hidden'); }); elements.settingsIconBtn.addEventListener('click', openSettings); elements.cancelSettingsBtn.addEventListener('click', () => ui.toggleModal(false)); elements.saveSettingsBtn.addEventListener('click', saveSettings); } function initWebSocket() { websocket.connect((data) => { // console.log('[WS] Received event:', data.event, data); const ev = data.event; if (ev === 'message' || ev === 'message.any' || ev === 'message.ack') { handleIncomingMessage(data.payload); upsertMessages([data.payload]); } }); } function normalizeChatId(raw) { if (!raw) return null; if (typeof raw === 'object') { return raw._serialized || raw.user || JSON.stringify(raw); } return raw; } function handleIncomingMessage(msg) { if (!msg) return; // console.log('[WS] handleIncomingMessage payload:', msg); ui.updateChatInChatList(msg); const rawChatId = msg.chatId || msg.from || (msg.chat && msg.chat.id); const msgChatId = normalizeChatId(rawChatId); if (!msgChatId) { console.warn('[WS] Could not resolve chatId from payload:', msg); return; } // console.log('[WS] Resolved msgChatId:', msgChatId, '| activeChatState:', activeChatState?.id); if (!msg.fromMe) { messageTone.play(); } if (activeChatState && activeChatState.id === msgChatId) { const msgId = normalizeChatId(msg.id) || msg.id; const exists = document.getElementById(msgId); if (!exists) { ui.appendSingleMessage({ ...msg, chatId: msgChatId }, activeChatState.name, getAppUser().id); ui.scrollToBottom(); } } const chatIndex = getChats().findIndex(c => c.id === msgChatId); if (chatIndex !== -1) { const chat = getChats()[chatIndex]; chat.lastMessage = msg.body || msg.text || 'Media message'; chat.timestamp = msg.timestamp ? (msg.timestamp * 1000) : Date.now(); if (!msg.fromMe && (!activeChatState || activeChatState.id !== msgChatId)) { chat.unreadCount = (chat.unreadCount || 0) + 1; } } } async function selectChat(chat) { activeChatState = chat; chat.unreadCount = 0; // ui.renderChatList(chatsState, activeChatState, selectChat); ui.toggleChatState(true); elements.activeChatName.textContent = chat.name.toUpperCase(); elements.activeChatAvatar.textContent = chat.name ? chat.name.substring(0, 1).toUpperCase() : '?'; elements.messagesContainer.innerHTML = `
    Loading messages...
    `; if (window.innerWidth <= 768) { elements.sidebar.classList.add('hidden'); } try { const rawMessages = await getChatMessages(chat.id); const processedMessages = compensateMessageOrdering(rawMessages); ui.renderMessages(processedMessages, chat.name, getAppUser().id, chat.id); } catch (error) { console.error('Failed to load messages:', error); elements.messagesContainer.innerHTML = '
    Error loading messages
    '; } } async function sendMessage() { const text = elements.messageInput.value.trim(); if (!text || !activeChatState) return; elements.messageInput.value = ''; const tempMsg = { id: 'temp-' + Date.now(), body: text, fromMe: true, sender: 'me', timestamp: new Date().toISOString(), status: 'sending' }; ui.appendSingleMessage(tempMsg, activeChatState.name, getAppUser().id); ui.scrollToBottom(); try { try { await waha.startTyping(activeChatState.id); const delay = Math.min(4000, Math.max(1000, text.length * 50)); await new Promise(resolve => setTimeout(resolve, delay)); } catch (e) { console.warn('Presence start failed:', e); } try { await waha.stopTyping(activeChatState.id); } catch (e) { console.warn('Presence stop failed:', e); } try { if (!activeChatState.id.endsWith('@lid')) { await waha.readChat(activeChatState.id); } } catch (e) { console.warn('readChat failed (non-fatal):', e.message); } const responseData = await waha.sendTextMessage(activeChatState.id, text); const tempBubble = document.getElementById(tempMsg.id); if (tempBubble) { if (responseData && responseData.id) { tempBubble.id = responseData.id; } const meta = tempBubble.querySelector('.message-meta'); meta.innerHTML = `${formatTime(new Date())}`; } activeChatState.lastMessage = text; activeChatState.timestamp = new Date(); } catch (error) { console.error('Failed to send message:', error); const tempBubble = document.getElementById(tempMsg.id); if (tempBubble) { const meta = tempBubble.querySelector('.message-meta'); meta.innerHTML = `Failed to send`; } } } async function sendFileMessage(file) { try { const tempId = 'temp-' + Date.now(); const tempMsg = { _data: { mimetype: file.type }, id: tempId, body: "", fromMe: true, sender: 'me', timestamp: new Date().toISOString(), status: 'sending', hasMedia: true, media: { url: URL.createObjectURL(file), filename: file.name } }; ui.appendSingleMessage(tempMsg, activeChatState.name, getAppUser().id, activeChatState.id, true); ui.scrollToBottom(); const result = await waha.sendFileMessage(activeChatState.id, file); ui.removeChatMessage(tempId); ui.appendSingleMessage(result.id, activeChatState.name, getAppUser().id, activeChatState.id); } catch (error) { console.error(error.message); } } function openSettings() { elements.inputWahaUrl.value = config.wahaUrl; elements.inputSession.value = config.session; elements.inputApiKey.value = config.apiKey; ui.toggleModal(true); } function saveSettings() { config.save( elements.inputWahaUrl.value, elements.inputSession.value, elements.inputApiKey.value ); ui.toggleModal(false); loadChats(); checkWahaStatus(); initWebSocket(); } async function checkWahaStatus() { try { const data = await waha.getVersion(); ui.updateConnectionStatus(true, `WAHA Connected: v${data.version || 'OK'}`); } catch (e) { ui.updateConnectionStatus(false, 'WAHA Server Offline'); } }