Files
2026-07-02 09:12:57 +07:00

187 lines
6.1 KiB
JavaScript

window.BackOneAddons = window.BackOneAddons || {};
window.BackOneAddons['secure-chat'] = function secureChatAddon() {
const messagesByPeer = {};
let selectedPeer = null;
let root = null;
let unsubChat = null;
function escapeHtml(s) {
return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function clearSessionMessages() {
Object.keys(messagesByPeer).forEach(k => delete messagesByPeer[k]);
}
function getThread(peerId) {
if (!messagesByPeer[peerId]) messagesByPeer[peerId] = [];
return messagesByPeer[peerId];
}
function formatTime(ts) {
if (!ts) return '';
const d = new Date(ts);
return d.toLocaleString(undefined, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' });
}
function networkLabel() {
const n = BackOneNetworkSession.getActiveNetwork();
return n ? (n.name || n.id) : BackOneNetworkSession.getNetworkId();
}
function renderHeader() {
const header = root?.querySelector('#secure-chat-header');
if (!header) return;
const net = networkLabel();
if (!selectedPeer) {
header.textContent = net ? `Select a contact on ${net}` : 'Join a network in the header bar';
return;
}
header.innerHTML = `<strong>${escapeHtml(selectedPeer.displayName || selectedPeer.peerId)}</strong> <small>${escapeHtml(selectedPeer.peerId)}${net ? ' · ' + escapeHtml(net) : ''}</small>`;
}
function selfName() {
const addr = (BackOneNetworkSession.getCoreStatus().address || '').toLowerCase();
return addr ? addr.slice(0, 10) + '…' : 'me';
}
return {
mount(container) {
root = container;
clearSessionMessages();
container.innerHTML = `
<div class="comm-layout comm-split-layout">
<aside class="comm-sidebar">
<div class="comm-sidebar-head">
<h2>Secure Chat</h2>
<p class="comm-sidebar-sub">Contacts · session only</p>
</div>
<div class="comm-sidebar-body">
<div id="secure-chat-peer-picker"></div>
</div>
</aside>
<div class="comm-main">
<div class="chat-thread-panel">
<div class="chat-thread-header" id="secure-chat-header">Select a contact</div>
<div id="secure-chat-thread" class="chat-messages"></div>
<div class="chat-compose">
<textarea id="secure-chat-input" rows="2" placeholder="Type a secure message" disabled></textarea>
<div class="chat-compose-row">
<button type="button" id="secure-chat-send" class="btn-primary" disabled>Send</button>
</div>
</div>
</div>
</div>
</div>`;
const thread = container.querySelector('#secure-chat-thread');
const input = container.querySelector('#secure-chat-input');
const sendBtn = container.querySelector('#secure-chat-send');
const render = () => {
if (!selectedPeer) {
thread.innerHTML = '<p class="chat-empty">Choose a contact from the list</p>';
return;
}
const messages = getThread(selectedPeer.peerId);
if (!messages.length) {
thread.innerHTML = '<p class="chat-empty">No messages yet — start the conversation</p>';
return;
}
thread.innerHTML = messages.map(m => `
<div class="chat-message">
<div class="chat-message-meta">
<span class="chat-message-sender">${escapeHtml(m.senderName || m.sender || 'me')}</span>
<span class="chat-message-time">${escapeHtml(formatTime(m.at))}</span>
</div>
${m.body ? `<div class="chat-message-body">${escapeHtml(m.body)}</div>` : ''}
</div>`).join('');
thread.scrollTop = thread.scrollHeight;
};
function updateInputs() {
const networkId = BackOneNetworkSession.getNetworkId();
const ok = !!networkId && !!selectedPeer;
input.disabled = !ok;
sendBtn.disabled = !ok;
renderHeader();
}
if (unsubChat) unsubChat();
unsubChat = BackOneWebHubClient.subscribe('chat_message', (msg) => {
const networkId = BackOneNetworkSession.getNetworkId();
if (!networkId || (msg.networkId && msg.networkId.toLowerCase() !== networkId)) return;
const senderId = (msg.senderId || '').toLowerCase();
const peerId = selectedPeer?.peerId?.toLowerCase();
if (!peerId || senderId !== peerId) {
if (senderId) {
getThread(senderId).push({
sender: senderId,
senderName: msg.senderName || senderId,
body: msg.body || '',
at: msg.at || Date.now()
});
}
return;
}
getThread(peerId).push({
sender: senderId,
senderName: msg.senderName || senderId,
body: msg.body || '',
at: msg.at || Date.now()
});
render();
});
BackOnePeerPicker(container.querySelector('#secure-chat-peer-picker'), {
layout: 'sidebar',
showActionButton: false,
onSelect(peer) {
selectedPeer = peer;
updateInputs();
render();
},
onNetworkChange() {
selectedPeer = null;
clearSessionMessages();
updateInputs();
render();
}
});
BackOneNetworkSession.subscribe(() => {
updateInputs();
render();
});
sendBtn.onclick = () => {
const networkId = BackOneNetworkSession.getNetworkId();
if (!selectedPeer || !networkId) return;
const body = input.value.trim();
if (!body) return;
const now = Date.now();
const name = selfName();
BackOneWebHubClient.send({
type: 'chat_message',
networkId,
targetId: selectedPeer.peerId,
senderName: name,
body
});
getThread(selectedPeer.peerId).push({
sender: 'me',
senderName: name,
body,
at: now
});
input.value = '';
render();
};
updateInputs();
render();
}
};
};