/** * JCHAT Web Client - JMAP-based Chat Application */ class JChatClient { constructor() { this.serverUrl = JChatConfig.API_BASE_URL; this.session = null; this.conversations = new Map(); this.messages = new Map(); this.currentConversationId = null; this.userId = 'user1'; // Demo user this.init(); } async init() { try { await this.loadSession(); await this.loadConversations(); this.updateConnectionStatus('Connected', 'success'); // Set up polling for new messages (in production, use EventSource/WebSockets) this.startPolling(); } catch (error) { console.error('Failed to initialize client:', error); this.updateConnectionStatus('Connection failed', 'error'); } } async loadSession() { const response = await fetch(`${this.serverUrl}/jmap/session`); if (!response.ok) { throw new Error('Failed to load session'); } this.session = await response.json(); console.log('Session loaded:', this.session); } async makeJMAPRequest(methodCalls) { const request = { using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:chat'], methodCalls: methodCalls }; const response = await fetch(`${this.serverUrl}/jmap/api`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request) }); if (!response.ok) { throw new Error(`JMAP request failed: ${response.status}`); } return await response.json(); } async loadConversations() { try { // For demo, create a sample conversation if none exist const queryResponse = await this.makeJMAPRequest([ ['Conversation/query', { accountId: 'default' }, 'q1'] ]); const queryResult = queryResponse.methodResponses[0][1]; if (queryResult.ids.length === 0) { // Create a demo conversation await this.createDemoConversation(); return this.loadConversations(); } // Load conversation details const getResponse = await this.makeJMAPRequest([ ['Conversation/get', { accountId: 'default', ids: queryResult.ids }, 'g1'] ]); const conversations = getResponse.methodResponses[0][1].list; this.conversations.clear(); conversations.forEach(conv => { this.conversations.set(conv.id, conv); }); this.renderConversations(); } catch (error) { console.error('Failed to load conversations:', error); this.showStatus('Failed to load conversations', 'error'); } } async createDemoConversation() { try { const response = await this.makeJMAPRequest([ ['Conversation/set', { accountId: 'default', create: { 'demo1': { title: 'Demo Conversation', participantIds: [this.userId, 'user2'] } } }, 'c1'] ]); console.log('Demo conversation created:', response); } catch (error) { console.error('Failed to create demo conversation:', error); } } async loadMessages(conversationId) { try { // Query messages for the conversation const queryResponse = await this.makeJMAPRequest([ ['Message/query', { accountId: 'default', filter: { inConversation: conversationId }, sort: [{ property: 'sentAt', isAscending: true }] }, 'mq1'] ]); const messageIds = queryResponse.methodResponses[0][1].ids; if (messageIds.length === 0) { this.messages.set(conversationId, []); this.renderMessages(); return; } // Get message details const getResponse = await this.makeJMAPRequest([ ['Message/get', { accountId: 'default', ids: messageIds }, 'mg1'] ]); const messages = getResponse.methodResponses[0][1].list; this.messages.set(conversationId, messages); this.renderMessages(); } catch (error) { console.error('Failed to load messages:', error); this.showStatus('Failed to load messages', 'error'); } } renderConversations() { const container = document.getElementById('conversations'); container.innerHTML = ''; this.conversations.forEach(conv => { const div = document.createElement('div'); div.className = 'conversation'; div.onclick = () => this.selectConversation(conv.id); if (conv.id === this.currentConversationId) { div.classList.add('active'); } div.innerHTML = `
Start the conversation by sending a message