diff --git a/dist/index.css b/dist/index.css index 64fb34d4..9689aecc 100644 --- a/dist/index.css +++ b/dist/index.css @@ -438,6 +438,9 @@ body[showing-chat] #chat-page-wrapper { .message-bubble .sender-name { font-weight: bold; } +.message-bubble .body { + word-break: break-all; +} .message-bubble[sentbyuser] { justify-content: flex-end; } diff --git a/dist/index.js b/dist/index.js index 28732fb0..bf853567 100644 --- a/dist/index.js +++ b/dist/index.js @@ -205,9 +205,7 @@ try { const [listState, toElement] = value; listState.handleAddition((newItem) => { - console.log(newItem, toElement); const child = toElement(newItem); - console.log(child); listState.handleRemoval( newItem, () => child.remove() @@ -372,6 +370,79 @@ chatObjects: (id) => [DATA_VERSION, "chat", id, "objects"] }; + // src/Model/Utility/crypto.ts + var IV_SIZE = 12; + var ENCRYPTION_ALG = "AES-GCM"; + async function encryptString(plaintext, passphrase) { + if (!window.crypto.subtle) return plaintext; + const iv = generateIV(); + const key = await importKey(passphrase, "encrypt"); + const encryptedArray = await encrypt(iv, key, plaintext); + const encryptionData = { + iv: uInt8ToArray(iv), + encryptedArray: uInt8ToArray(encryptedArray) + }; + return btoa(JSON.stringify(encryptionData)); + } + async function decryptString(cyphertext, passphrase) { + try { + const encrypionData = JSON.parse(atob(cyphertext)); + const iv = arrayToUint8(encrypionData.iv); + const encryptedArray = arrayToUint8(encrypionData.encryptedArray); + const key = await importKey(passphrase, "decrypt"); + return await decrypt(iv, key, encryptedArray); + } catch { + return cyphertext; + } + } + function encode(string) { + return new TextEncoder().encode(string); + } + function decode(array) { + return new TextDecoder("utf-8").decode(array); + } + async function encrypt(iv, key, message) { + const arrayBuffer = await window.crypto.subtle.encrypt( + { name: ENCRYPTION_ALG, iv }, + key, + encode(message) + ); + return new Uint8Array(arrayBuffer); + } + async function decrypt(iv, key, cyphertext) { + const arrayBuffer = await crypto.subtle.decrypt( + { name: ENCRYPTION_ALG, iv }, + key, + cyphertext + ); + return arrayBufferToString(arrayBuffer); + } + async function hash(encoded) { + return await crypto.subtle.digest("SHA-256", encoded); + } + function generateIV() { + return crypto.getRandomValues(new Uint8Array(IV_SIZE)); + } + async function importKey(passphrase, purpose) { + return await crypto.subtle.importKey( + "raw", + await hash(encode(passphrase)), + { name: ENCRYPTION_ALG }, + false, + [purpose] + ); + } + function arrayBufferToString(arrayBuffer) { + const uInt8Array = new Uint8Array(arrayBuffer); + return decode(uInt8Array); + } + function uInt8ToArray(uInt8Array) { + return Array.from(uInt8Array); + } + function arrayToUint8(array) { + return new Uint8Array(array); + } + // src/ViewModel/colors.ts var Color = /* @__PURE__ */ ((Color2) => { Color2["Standard"] = "standard"; @@ -509,7 +580,7 @@ return objects; } // messaging - sendMessage = (body) => { + sendMessage = async (body) => { const senderName = this.settingsModel.username; if (senderName == "") return false; const allChannels = [this.info.primaryChannel]; @@ -517,6 +588,9 @@ allChannels.push(secondaryChannel); } const combinedChannel = allChannels.join("/"); + if (this.info.encryptionKey != "") { + body = await encryptString(body, this.info.encryptionKey); + } const chatMessage = _ChatModel.createChatMessage( combinedChannel, senderName, @@ -525,11 +599,19 @@ this.connectionModel.sendMessageOrStore(chatMessage); return true; }; - handleMessage = (channel, body) => { + handleMessage = async (body) => { const chatMessage = _ChatModel.parseMessage(body); if (chatMessage == null) return; + await this.decryptMessage(chatMessage); this.addMessage(chatMessage); }; + decryptMessage = async (chatMessage) => { + const decryptedBody = await decryptString( + chatMessage.body, + this.info.encryptionKey + ); + chatMessage.body = decryptedBody; + }; setMessageHandler = (handler) => { this.chatMessageHandler = handler; }; @@ -595,7 +677,7 @@ const allChannels = channel.split("/"); for (const channel2 of allChannels) { if (channel2 != chatModel.info.primaryChannel) continue; - chatModel.handleMessage(channel2, body); + chatModel.handleMessage(body); break; } } @@ -916,17 +998,16 @@ class: "message-bubble", "toggle:sentbyuser": chatMessageViewModel.sentByUser }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( "span", { class: "body", "subscribe:innerText": chatMessageViewModel.body } - ), /* @__PURE__ */ createElement("span", { class: "timestamp" }, chatMessageViewModel.dateSent)) + ), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, chatMessageViewModel.dateSent)) ); } var ChatMessageViewModelToView = (chatMessageViewModel) => { - console.log(chatMessageViewModel); return ChatMessage(chatMessageViewModel); }; @@ -1030,7 +1111,6 @@ } ); function scrollDown() { - console.log("down"); messageContainer.scrollTop = messageContainer.scrollHeight; } function scrollDownIfApplicable() { diff --git a/src/Model/chatListModel.ts b/src/Model/chatListModel.ts index 8484a689..8fb8e27e 100644 --- a/src/Model/chatListModel.ts +++ b/src/Model/chatListModel.ts @@ -27,7 +27,7 @@ export default class ChatListModel { const allChannels = channel.split("/"); for (const channel of allChannels) { if (channel != chatModel.info.primaryChannel) continue; - chatModel.handleMessage(channel, body); + chatModel.handleMessage(body); break; } } diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index e4250b87..f810ee3e 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -2,6 +2,7 @@ import StorageModel, { storageKeys } from "./storageModel"; import { createTimestamp, parse } from "./Utility/utility"; +import { decryptString, encryptString } from "./Utility/crypto"; import ChatListModel from "./chatListModel"; import { Color } from "../ViewModel/colors"; @@ -166,7 +167,7 @@ export class ChatModel { } // messaging - sendMessage = (body: string): boolean => { + sendMessage = async (body: string): Promise => { const senderName = this.settingsModel.username; if (senderName == "") return false; @@ -175,7 +176,10 @@ export class ChatModel { allChannels.push(secondaryChannel); } - const combinedChannel = allChannels.join("/"); + const combinedChannel: string = allChannels.join("/"); + if (this.info.encryptionKey != "") { + body = await encryptString(body, this.info.encryptionKey); + } const chatMessage: ChatMessage = ChatModel.createChatMessage( combinedChannel, @@ -187,13 +191,22 @@ export class ChatModel { return true; }; - handleMessage = (channel: string, body: string): void => { + handleMessage = async (body: string): Promise => { const chatMessage: ChatMessage | null = ChatModel.parseMessage(body); if (chatMessage == null) return; + await this.decryptMessage(chatMessage); this.addMessage(chatMessage); }; + decryptMessage = async (chatMessage: ChatMessage): Promise => { + const decryptedBody: string = await decryptString( + chatMessage.body, + this.info.encryptionKey + ); + chatMessage.body = decryptedBody; + }; + setMessageHandler = (handler: (chatMessage: ChatMessage) => void): void => { this.chatMessageHandler = handler; }; diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index 096dd65d..02afc374 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -100,6 +100,10 @@ font-weight: bold; } +.message-bubble .body { + word-break: break-all; +} + .message-bubble[sentbyuser] { justify-content: flex-end; } diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index 8bf4d69c..2b70442e 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -18,7 +18,6 @@ export function MessagePage(chatViewModel: ChatViewModel) { ); function scrollDown() { - console.log("down"); messageContainer.scrollTop = messageContainer.scrollHeight; } function scrollDownIfApplicable() { diff --git a/src/View/Components/chatMessage.tsx b/src/View/Components/chatMessage.tsx index 76e653ae..1a8578a1 100644 --- a/src/View/Components/chatMessage.tsx +++ b/src/View/Components/chatMessage.tsx @@ -9,12 +9,12 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) { toggle:sentbyuser={chatMessageViewModel.sentByUser} >
- {chatMessageViewModel.sender} + {chatMessageViewModel.sender} - {chatMessageViewModel.dateSent} + {chatMessageViewModel.dateSent}
); @@ -23,6 +23,5 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) { export const ChatMessageViewModelToView: React.StateItemConverter< ChatMessageViewModel > = (chatMessageViewModel: ChatMessageViewModel) => { - console.log(chatMessageViewModel); return ChatMessage(chatMessageViewModel); };