working messages!!@!@
All checks were successful
Android Build / publish (push) Successful in 31s
Linux Build / publish (push) Successful in 51s

This commit is contained in:
olcxja 2026-06-02 12:53:41 +02:00
commit 92edb123f3
8 changed files with 189 additions and 18 deletions

View file

@ -121,7 +121,7 @@
"placeholder.username": "cat name",
"placeholder.captcha.code": "captcha code",
"placeholder.message.input": "meow...",
"desc.messages.encryption.active": "meows are end-to-end encrypted :3",
"desc.messages.loading": "loading meows...",
"desc.no.dms": "no direct meowchats :c",
"action.dm.opening": "opening meowchat...",
"dm.open.failed": "failed to open meowchat :c",

View file

@ -120,7 +120,7 @@
"placeholder.username": "username",
"placeholder.captcha.code": "captcha code",
"placeholder.message.input": "Message...",
"desc.messages.encryption.active": "Messages are end-to-end encrypted",
"desc.messages.loading": "Fetching messages...",
"desc.no.dms": "No direct messages",
"action.dm.opening": "Opening DM...",
"dm.open.failed": "Failed to open DM",

View file

@ -1645,6 +1645,7 @@ async function gotoHome() {
switchRoomContent("title.splash", splashScreen, false);
switchRoomsBar("title.home", homeRoomBar);
setActiveRoombarItem(null);
setupWebSocket();
await refreshDms(210);
}
@ -1852,7 +1853,7 @@ async function openDm(dmId, username, targetId) {
let msgContainer = document.getElementById("chat-messages");
if (msgContainer) {
msgContainer.innerHTML = `<div style="text-align: center; opacity: 0.5; padding: 1rem;"><blah>desc.messages.encryption.active</blah></div>`;
msgContainer.innerHTML = `<div style="text-align: center; opacity: 0.5; padding: 1rem;"><blah>desc.messages.loading</blah></div>`;
let blahTags = msgContainer.getElementsByTagName("blah");
for (let i = 0; i < blahTags.length; i++) {
blahTags[i].innerHTML = processBlah(blahTags[i].innerHTML);
@ -1861,6 +1862,15 @@ async function openDm(dmId, username, targetId) {
await loadDmMessages(dmId);
if (dmMessagePollInterval) clearInterval(dmMessagePollInterval);
dmMessagePollInterval = setInterval(() => {
if (currentDmId === dmId) {
loadDmMessages(dmId);
} else {
clearInterval(dmMessagePollInterval);
}
}, 15000);
setActiveRoombarItem(`dm-btn-${dmId}`);
clearAction("dmopen");
} catch (e) {
@ -1906,7 +1916,8 @@ async function renderMessages(messages) {
if (msg.key && msg.key !== "") {
try {
content = await decryptAesGcmFromBase64(content, currentDmKey);
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
content = await decryptAesGcmFromBase64(content, dmKeyBytes);
} catch(e) {
content = `<span style="color:var(--error-color)"><blah>error:messages.decrypt.failed</blah></span>`;
}
@ -1957,4 +1968,79 @@ async function renderMessages(messages) {
container.innerHTML = html;
container.scrollTop = container.scrollHeight;
}
async function sendMessage() {
if (!currentDmId || !currentDmKey) return;
let input = document.getElementById("chat-input");
let content = input.value.trim();
if (!content) return;
try {
let dmKeyBytes = await sha256Bytes(base64ToUint8(currentDmKey));
let encryptedContent = await encryptAesGcmToBase64(content, dmKeyBytes);
let msgPayload = {
string1: currentDmId,
string2: encryptedContent,
string3: ""
};
input.value = "";
let res = await fetchEncrypted("dm/message/send", JSON.stringify(msgPayload));
if (res && res.startsWith("error:")) {
showBlahNotification(res);
} else {
await loadDmMessages(currentDmId);
}
} catch (e) {
console.error(e);
showBlahNotification("error:message.send.failed");
}
}
function handleChatInputKey(event) {
if (event.key === "Enter" && !event.shiftKey) {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (!isMobile) {
event.preventDefault();
sendMessage();
}
}
}
let dmMessagePollInterval = null;
let appWebSocket = null;
function setupWebSocket() {
if (appWebSocket && appWebSocket.readyState === WebSocket.OPEN) return;
let wsUrl = url.replace(/^http/, "ws") + "/ws";
appWebSocket = new WebSocket(wsUrl);
appWebSocket.onopen = async () => {
try {
let nonce = await getNonce(id, passwordHash);
let secretEnc = await encryptWithNonce(passwordHash, passwordHash, nonce);
appWebSocket.send(JSON.stringify({ string1: id, string2: secretEnc }));
} catch (e) {
console.error("WS auth failed", e);
}
};
appWebSocket.onmessage = (event) => {
let data = event.data;
if (data.startsWith("dm_message:")) {
let msgDmId = data.substring("dm_message:".length);
if (currentDmId === msgDmId) {
loadDmMessages(msgDmId);
}
}
};
appWebSocket.onclose = () => {
setTimeout(setupWebSocket, 5000);
};
}

View file

@ -132,10 +132,9 @@ var invitesEntry = `
var chatScreen = `
<div style="display: flex; flex-direction: column; height: 100%; width: 100%;">
<div id="chat-messages" style="flex-grow: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.5rem;">
<!-- messages go here -->
</div>
<div style="padding: 1rem; border-top: var(--border-width) solid var(--light-border-color); display: flex; gap: 0.5rem;">
<input type="text" id="chat-input" class="forminput" style="flex-grow: 1; margin: 0;" placeholder="{blah(placeholder.message.input)}">
<textarea id="chat-input" class="forminput" style="flex-grow: 1; margin: 0; resize: none; min-height: 2.5rem; max-height: 8rem; padding: 0.5rem;" placeholder="{blah(placeholder.message.input)}" onkeydown="handleChatInputKey(event)"></textarea>
<button class="submit-button" onclick="sendMessage()" style="margin: 0; padding: 0; aspect-ratio: 1;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.9 -0.5 15.6 16" fill="none" width="1.5rem">
<path stroke="var(--main-bg-color)" stroke-linecap="round" stroke-linejoin="round" d="m3.75 7.5 -1.875 5.625 11.25 -5.625L1.875 1.875l1.875 5.625zm0 0h3.75" stroke-width="1.1"></path>

View file

@ -71,7 +71,7 @@ loading {
user-select: none !important;
-webkit-user-drag: none !important;
}
button, input {
button, input, textarea {
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.05);
margin: var(--button-margin);
@ -92,7 +92,7 @@ button, input {
touch-action: manipulation;
-webkit-touch-callout: none;
}
input {
input, textarea {
padding-left: calc(var(--button-margin) * 2);
-webkit-touch-callout: default;
user-select: text !important;
@ -677,7 +677,7 @@ space{
border-radius: 0.8rem;
transform: translateY(calc( ( var(--icon-button-height) + (var(--button-margin) * 2) ) / 2 - 0.6rem));
}
button:hover, input:hover {
button:hover, input:hover, textarea:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:hover {
@ -694,14 +694,14 @@ space{
filter: brightness(0.93);
}
}
button:active, button.is-pressed, input:active, input.is-pressed {
button:active, button.is-pressed, input:active, input.is-pressed, textarea:active, textarea.is-pressed {
background-color: rgba(255, 255, 255, 0.1);
transform: var(--press-scale);
}
.invite-actions button:active {
filter: brightness(0.85);
}
button.active, input.active {
button.active, input.active, textarea.active {
background-color: rgba(255, 255, 255, 0.1);
}
.submit-button:active, .submit-button.is-pressed {