Files
CT/src/model.ts
T

257 lines
6.6 KiB
TypeScript
Raw Normal View History

2024-07-07 19:20:51 +02:00
import * as React from "bloatless-react";
2024-07-08 00:39:22 +02:00
import {
arrayToUint8,
decode,
decrypt,
encrypt,
generateIV,
importKey,
splitMergedString,
uInt8ToArray,
} from "./cryptUtility";
2024-07-07 19:20:51 +02:00
import UDNFrontend from "udn-frontend";
2024-07-08 00:39:22 +02:00
import { translation } from "./translations";
2024-07-07 19:20:51 +02:00
const UDN = new UDNFrontend();
// TYPE
export class Channel implements React.Identifiable {
id = React.UUID();
constructor(public channelName: string) {}
}
export interface Message extends React.Identifiable {
channel: string;
sender: string;
body: string;
isoDate: string;
}
export class Message implements React.Identifiable {
id = React.UUID();
constructor(
public channel: string,
public sender: string,
public body: string,
public isoDate: string
) {}
}
// STATE
// connection
2024-07-07 22:11:18 +02:00
export const currentAddress = new React.State("");
export const serverAddress = React.restoreState("socket-address", "");
2024-07-07 19:20:51 +02:00
export const isConnected = new React.State(false);
2024-07-08 00:48:58 +02:00
export const connectionMessage = React.createProxyState([serverAddress], () =>
translation.connectedTo(serverAddress.value)
);
2024-07-07 19:20:51 +02:00
2024-07-07 22:11:18 +02:00
export const cannotConnect = React.createProxyState(
2024-07-08 00:39:22 +02:00
[serverAddress, currentAddress, isConnected],
2024-07-07 22:11:18 +02:00
() =>
serverAddress.value == "" ||
(currentAddress.value == serverAddress.value && isConnected.value == true)
);
2024-07-08 00:39:22 +02:00
// encryption
export const encryptionKey = React.restoreState("encryption-key", "");
export const isEncryptionUnavailable = window.crypto.subtle == undefined;
2024-07-07 19:20:51 +02:00
// sending
2024-07-07 22:11:18 +02:00
export const currentPrimaryChannel = new React.State("");
2024-07-07 19:20:51 +02:00
export const primaryChannel = React.restoreState("primary-channel", "");
2024-07-08 02:44:23 +02:00
2024-07-07 22:11:18 +02:00
export const newSecondaryChannelName = new React.State("");
export const secondaryChannels =
React.restoreListState<Channel>("secondary-channels");
2024-07-08 02:44:23 +02:00
2024-07-07 19:20:51 +02:00
export const senderName = React.restoreState("sender-name", "");
2024-07-08 02:44:23 +02:00
2024-07-07 22:11:18 +02:00
export const messageBody = React.restoreState("message", "");
export const cannotSetChannel = React.createProxyState(
[primaryChannel, currentPrimaryChannel, isConnected],
2024-07-08 02:44:23 +02:00
() => primaryChannel.value == "" || isConnected.value == false
);
export const cannotLeaveChannel = React.createProxyState(
[currentPrimaryChannel, isConnected],
() => currentPrimaryChannel.value == "" || isConnected.value == false
2024-07-07 22:11:18 +02:00
);
export const cannotAddSecondaryChannel = React.createProxyState(
[newSecondaryChannelName],
() => newSecondaryChannelName.value == ""
);
2024-07-07 19:20:51 +02:00
export const cannotSendMessage = React.createProxyState(
2024-07-07 22:11:18 +02:00
[currentPrimaryChannel, messageBody, senderName],
() =>
currentPrimaryChannel.value == "" ||
messageBody.value == "" ||
senderName.value == ""
2024-07-07 19:20:51 +02:00
);
// receiving
export const messages = React.restoreListState<Message>("messages");
// METHODS
export function connect() {
2024-07-07 22:11:18 +02:00
if (cannotConnect.value == true) return;
currentAddress.value = serverAddress.value;
2024-07-08 00:39:22 +02:00
isConnected.value = false;
2024-07-07 22:11:18 +02:00
UDN.connect(serverAddress.value);
2024-07-07 19:20:51 +02:00
}
2024-07-08 00:39:22 +02:00
export async function sendMessage(): Promise<void> {
2024-07-07 19:20:51 +02:00
if (cannotSendMessage.value == true) return;
// get channels
2024-07-07 22:11:18 +02:00
const secondaryChannelNames: string[] = [
...secondaryChannels.value.values(),
2024-07-07 19:20:51 +02:00
].map((channel) => channel.channelName);
const allChannelNames: string[] = [
primaryChannel.value,
2024-07-07 22:11:18 +02:00
...secondaryChannelNames,
2024-07-07 19:20:51 +02:00
];
2024-07-08 00:39:22 +02:00
// encrypt
const encrypted = await encryptMessage();
if (encrypted == undefined) return;
2024-07-07 19:20:51 +02:00
// create object
const joinedChannelName = allChannelNames.join("/");
const messageObject = new Message(
joinedChannelName,
senderName.value,
2024-07-08 00:39:22 +02:00
encrypted,
2024-07-07 19:20:51 +02:00
new Date().toISOString()
);
const messageString = JSON.stringify(messageObject);
2024-07-07 22:11:18 +02:00
// send
2024-07-07 19:20:51 +02:00
UDN.sendMessage(joinedChannelName, messageString);
2024-07-07 22:11:18 +02:00
// clear
messageBody.value = "";
2024-07-07 19:20:51 +02:00
}
2024-07-08 12:34:37 +02:00
export function clearMessageHistory(): void {
2024-07-08 00:39:22 +02:00
messages.clear();
}
2024-07-08 12:34:37 +02:00
export function deleteMessage(message: Message): void {
messages.remove(message);
}
2024-07-08 12:50:54 +02:00
export async function decryptReceivedMessage(message: Message): Promise<void> {
message.body = await decryptMessage(message.body);
messages.callSubscriptions();
}
2024-07-07 19:20:51 +02:00
export function setChannel(): void {
2024-07-07 22:11:18 +02:00
if (cannotSetChannel.value == true) return;
if (currentPrimaryChannel != undefined) {
UDN.unsubscribe(currentPrimaryChannel.value);
2024-07-07 19:20:51 +02:00
}
UDN.subscribe(primaryChannel.value);
}
2024-07-08 02:44:23 +02:00
export function leaveChannel(): void {
if (cannotLeaveChannel.value == true) return;
UDN.unsubscribe(currentPrimaryChannel.value);
}
2024-07-07 22:11:18 +02:00
export function addSecondaryChannel(): void {
if (cannotAddSecondaryChannel.value == true) return;
const channelObject = new Channel(newSecondaryChannelName.value);
secondaryChannels.add(channelObject);
newSecondaryChannelName.value = "";
2024-07-07 19:20:51 +02:00
}
2024-07-07 22:11:18 +02:00
export function removeSecondaryChannel(channel: Channel): void {
secondaryChannels.remove(channel);
2024-07-07 19:20:51 +02:00
}
// LISTENERS
UDN.onconnect = () => {
isConnected.value = true;
2024-07-08 00:39:22 +02:00
setChannel();
2024-07-07 19:20:51 +02:00
};
UDN.onmessage = (data) => {
2024-07-07 22:11:18 +02:00
if (data.subscribed != undefined && data.messageChannel != undefined) {
2024-07-07 19:20:51 +02:00
return handleSubscriptionConfirmation(data.messageChannel, data.subscribed);
} else if (data.messageBody) {
handleMessage(data.messageBody);
}
};
UDN.ondisconnect = () => {
isConnected.value = false;
};
// UTILITY
function handleSubscriptionConfirmation(
channel: string,
isSubscribed: boolean
): void {
if (isSubscribed == true) {
2024-07-07 22:11:18 +02:00
currentPrimaryChannel.value = channel;
2024-07-08 02:44:23 +02:00
} else {
currentPrimaryChannel.value = "";
2024-07-07 19:20:51 +02:00
}
}
2024-07-08 00:39:22 +02:00
async function handleMessage(body: string): Promise<void> {
2024-07-07 19:20:51 +02:00
try {
const object: Message = JSON.parse(body);
if (!object.channel || !object.sender || !object.body || !object.isoDate)
return;
const messageObject = new Message(
object.channel,
object.sender,
2024-07-08 00:39:22 +02:00
await decryptMessage(object.body),
2024-07-07 19:20:51 +02:00
object.isoDate
);
messages.add(messageObject);
} catch {}
}
2024-07-08 00:39:22 +02:00
async function encryptMessage(): Promise<string> {
if (encryptionKey.value == "") return messageBody.value;
if (!window.crypto.subtle) return messageBody.value;
const iv = generateIV();
const key = await importKey(encryptionKey.value, "encrypt");
const encryptedArray = await encrypt(iv, key, messageBody.value);
const encryptionData = {
iv: uInt8ToArray(iv),
encryptedArray: uInt8ToArray(encryptedArray),
};
return btoa(JSON.stringify(encryptionData));
}
async function decryptMessage(encryptionData: string): Promise<string> {
try {
const encrypionData = JSON.parse(atob(encryptionData));
const iv = arrayToUint8(encrypionData.iv);
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
const key = await importKey(encryptionKey.value, "decrypt");
return await decrypt(iv, key, encryptedArray);
} catch (error) {
console.error(error);
return encryptionData;
}
}