Skip to content

Commit

Permalink
Merge pull request #68 from Armored-Dragon/armored-chat
Browse files Browse the repository at this point in the history
Chat-ng
  • Loading branch information
ksuprynowicz authored Feb 24, 2024
2 parents 202b2a5 + c4a05f9 commit 63a894f
Show file tree
Hide file tree
Showing 22 changed files with 1,106 additions and 0 deletions.
27 changes: 27 additions & 0 deletions applications/armored-chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Armored Chat

Armored Chat is a light-weight alternative chat application that extends the existing chat features.

## Features

- (wip) Drop-in replacement for Fluffy chat
- (wip) E2EE Direct messages
- (wip) Group chats

- (?) Message signing

## Encryption

TODO:

- Algorithm
- Key exchange
- When and where
- How

## Group chats

TODO:

- How
- Limitations
234 changes: 234 additions & 0 deletions applications/armored-chat/armored_chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//
// armored_chat.js
//
// Created by Armored Dragon, 2024.
// Copyright 2024 Overte e.V.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

(function () {
"use strict";
// TODO: Encryption + PMs
// TODO: Find window init event method

var app_is_visible = false;
var settings = {
max_history: 250,
compact_chat: false,
external_window: false,
};
var app_data = { current_page: "domain" };
// Global vars
var ac_tablet;
var chat_overlay_window;
var app_button;
const channels = ["domain", "local", "system"];
var max_local_distance = 20; // Maximum range for the local chat

startup();

function startup() {
ac_tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");

app_button = ac_tablet.addButton({
icon: Script.resolvePath("./img/icon.png"),
text: "CHAT",
isActive: app_is_visible,
});

// When script ends, remove itself from tablet
Script.scriptEnding.connect(function () {
console.log("Shutting Down");
ac_tablet.removeButton(app_button);
chat_overlay_window.close();
});

// Overlay button toggle
app_button.clicked.connect(toggleMainChatWindow);

_openWindow();
}
function toggleMainChatWindow() {
app_is_visible = !app_is_visible;
console.log(`App is now ${app_is_visible ? "visible" : "hidden"}`);
app_button.editProperties({ isActive: app_is_visible });
chat_overlay_window.visible = app_is_visible;

// External window was closed; the window does not exist anymore
if (chat_overlay_window.title == "" && app_is_visible) {
_openWindow();
}
}
function _openWindow() {
chat_overlay_window = new Desktop.createWindow(Script.resourcesPath() + "qml/hifi/tablet/DynamicWebview.qml", {
title: "Overte Chat",
size: { x: 550, y: 400 },
additionalFlags: Desktop.ALWAYS_ON_TOP,
visible: app_is_visible, // FIXME Invalid?
presentationMode: Desktop.PresentationMode.VIRTUAL,
});
chat_overlay_window.visible = app_is_visible; // The "visible" field in the Desktop.createWindow does not seem to work. Force set it to false

chat_overlay_window.closed.connect(toggleMainChatWindow);
chat_overlay_window.sendToQml({ url: Script.resolvePath("./index.html") });
// FIXME: Loadsettings need to happen after the window is initialized?
// Script.setTimeout(_loadSettings, 1000);
chat_overlay_window.webEventReceived.connect(onWebEventReceived);
}

// Initialize default message subscriptions
Messages.subscribe("chat");
// Messages.subscribe("system");

Messages.messageReceived.connect(receivedMessage);

function receivedMessage(channel, message) {
console.log(`Received message:\n${message}`);
var message = JSON.parse(message);

channel = channel.toLowerCase();
if (channel !== "chat") return;

message.channel = message.channel.toLowerCase();

// For now, while we are working on superseding Floof, we will allow compatibility with it.
// If for_app exists, it came from us and we are just sending the message so Floof can read it.
// We don't need to listen to this message.
if (message.for_app) return;

// Check the channel is valid
if (!channels.includes(message.channel)) return;

// FIXME: Not doing distance check?
// If message is local, and if player is too far away from location, don't do anything
if (channel === "local" && Vec3.distance(MyAvatar.position, message.position) < max_local_distance) return;

// Floof chat compatibility.
if (message.type) delete message.type;

// Update web view of to new message
_emitEvent({ type: "show_message", ...message });

// Display on popup chat area
_overlayMessage({ sender: message.displayName, message: message });
}
function onWebEventReceived(event) {
console.log(`New web event:\n${event}`);
// FIXME: Lazy!
// Checks to see if the event is a JSON object
if (!event.includes("{")) return;

var parsed = JSON.parse(event);

// Not our app? Not our problem!
// if (parsed.app !== "ArmoredChat") return;

switch (parsed.type) {
case "page_update":
app_data.current_page = parsed.page;
break;

case "send_message":
_sendMessage(parsed.message);
break;

case "open_url":
Window.openUrl(parsed.message.toString());
break;

case "setting_update":
// Update local settings
settings[parsed.setting_name] = parsed.setting_value;
// Save local settings
_saveSettings();

switch (parsed.setting_name) {
case "external_window":
console.log(parsed.setting_value);
chat_overlay_window.presentationMode = parsed.setting_value ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL;
break;
}
break;

case "initialized":
_loadSettings();
break;
}
}
//
// Sending messages
// These functions just shout out their messages. We are listening to messages in an other function, and will record all heard messages there
function _sendMessage(message) {
Messages.sendMessage(
"chat",
JSON.stringify({
position: MyAvatar.position,
message: message,
displayName: MyAvatar.sessionDisplayName,
channel: app_data.current_page,
action: "send_chat_message",
})
);

// FloofyChat Compatibility
Messages.sendMessage(
"Chat",
JSON.stringify({
position: MyAvatar.position,
message: message,
displayName: MyAvatar.sessionDisplayName,
channel: app_data.current_page.charAt(0).toUpperCase() + app_data.current_page.slice(1),
type: "TransmitChatMessage",
for_app: "Floof",
})
);

// Show overlay of the message you sent
_overlayMessage({ sender: MyAvatar.sessionDisplayName, message: message });
}
function _overlayMessage(message) {
// Floofchat compatibility
// This makes it so that our own messages are not rendered.
// For now, Floofchat has priority over notifications as they use a strange system I don't want to touch yet.
if (!message.action) return;

Messages.sendLocalMessage(
"Floof-Notif",
JSON.stringify({
sender: message.sender,
text: message.message,
color: { red: 122, green: 122, blue: 122 },
})
);
}
function _loadSettings() {
console.log("Loading config");
settings = Settings.getValue("ArmoredChat-Config", settings);
console.log("\nSettings follow:");
console.log(JSON.stringify(settings, " ", 4));

// Compact chat
if (settings.compact_chat) {
_emitEvent({ type: "setting_update", setting_name: "compact_chat", setting_value: true });
}

// External Window
if (settings.external_window) {
chat_overlay_window.presentationMode = settings.external_window ? Desktop.PresentationMode.NATIVE : Desktop.PresentationMode.VIRTUAL;
_emitEvent({ type: "setting_update", setting_name: "external_window", setting_value: true });
}
}
function _saveSettings() {
console.log("Saving config");
Settings.setValue("ArmoredChat-Config", settings);
}
/**
* Emit a packet to the HTML front end. Easy communication!
* @param {Object} packet - The Object packet to emit to the HTML
* @param {("setting_update"|"show_message")} packet.type - The type of packet it is
*/
function _emitEvent(packet = { type: "" }) {
chat_overlay_window.emitScriptEvent(JSON.stringify(packet));
}
})();
22 changes: 22 additions & 0 deletions applications/armored-chat/compact-messages.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
body .page .content.message-list .message {
display: grid;
box-sizing: border-box;
grid-template-columns: 1fr 1fr;
grid-gap: inherit;
padding: 2px;
margin-bottom: 5px;
}
body .page .content.message-list .message .pfp {
display: none !important;
}
body .page .content.message-list .message .name {
color: #dbdbdb;
}
body .page .content.message-list .message .timestamp {
text-align: right;
color: #dbdbdb;
}
body .page .content.message-list .message .body {
grid-column-start: 1;
grid-column-end: 3;
}
29 changes: 29 additions & 0 deletions applications/armored-chat/compact-messages.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
body {
.page {
.content.message-list {
.message {
display: grid;
box-sizing: border-box;
grid-template-columns: 1fr 1fr;
grid-gap: inherit;
padding: 2px;
margin-bottom: 5px;

.pfp {
display: none !important;
}
.name {
color: #dbdbdb;
}
.timestamp {
text-align: right;
color: #dbdbdb;
}
.body {
grid-column-start: 1;
grid-column-end: 3;
}
}
}
}
}
22 changes: 22 additions & 0 deletions applications/armored-chat/encrpytion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(function () {
// TODO: Sign messages
// TODO: Verify signatures

let rsa = forge.pki.rsa;
let keypair;

function newKeyPair() {
// 2048 bits. Not the most super-duper secure length of 4096.
// This value must remain low to ensure lower-power machines can use.
// We will generate new keys automatically every so often and will also allow user to refresh keys.
keypair = rsa.generateKeyPair({ bits: 2048, workers: -1 });
}
function encrypt(message) {
if (!keypair) return null;
return keypair.publicKey.encrypt("Test message");
}
function decrypt(message) {
if (!keypair) return null;
return keypair.privateKey.decrypt(encrypted);
}
})();
Binary file added applications/armored-chat/img/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions applications/armored-chat/img/ui/send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added applications/armored-chat/img/ui/send_black.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added applications/armored-chat/img/ui/send_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added applications/armored-chat/img/ui/social_white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 63a894f

Please sign in to comment.