diff --git a/android/app/src/main/assets/public/blah/en-cat.json b/android/app/src/main/assets/public/blah/en-cat.json
index a41d1b4f..75fcf968 100644
--- a/android/app/src/main/assets/public/blah/en-cat.json
+++ b/android/app/src/main/assets/public/blah/en-cat.json
@@ -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",
diff --git a/android/app/src/main/assets/public/blah/en-us.json b/android/app/src/main/assets/public/blah/en-us.json
index 31c45501..104354bb 100644
--- a/android/app/src/main/assets/public/blah/en-us.json
+++ b/android/app/src/main/assets/public/blah/en-us.json
@@ -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",
diff --git a/android/app/src/main/assets/public/main.js b/android/app/src/main/assets/public/main.js
index 084d54cb..868fe841 100644
--- a/android/app/src/main/assets/public/main.js
+++ b/android/app/src/main/assets/public/main.js
@@ -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 = `
desc.messages.encryption.active
`;
+ msgContainer.innerHTML = `desc.messages.loading
`;
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 = `error:messages.decrypt.failed`;
}
@@ -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);
+ };
}
\ No newline at end of file
diff --git a/android/app/src/main/assets/public/screens.js b/android/app/src/main/assets/public/screens.js
index 2dc7189f..bdd58c06 100644
--- a/android/app/src/main/assets/public/screens.js
+++ b/android/app/src/main/assets/public/screens.js
@@ -132,10 +132,9 @@ var invitesEntry = `
var chatScreen = `
-
-
+