Files
CT/src/Model/Global/fileTransferModel.ts
T

212 lines
6.3 KiB
TypeScript
Raw Normal View History

2026-04-18 11:55:01 +00:00
// cleanup: Phase A
2024-07-27 17:03:52 +02:00
2026-04-18 11:55:01 +00:00
import { Message } from "udn-frontend";
import StorageModel from "./storageModel";
import ConnectionModel from "./connectionModel";
2024-07-28 03:46:08 +02:00
import {
2026-04-01 08:29:39 +00:00
HandlerManager,
generateRandomToken,
parse,
stringify,
2024-07-28 03:46:08 +02:00
} from "../Utility/utility";
2024-07-27 18:03:12 +02:00
import { checkMatchesObjectStructure } from "../Utility/typeSafety";
2026-04-18 11:55:01 +00:00
import { decryptString, encryptString } from "../Utility/crypto";
2024-07-27 17:03:52 +02:00
export default class FileTransferModel {
2026-04-13 10:58:21 +00:00
readonly storageModel: StorageModel;
readonly connectionModel: ConnectionModel;
2026-04-01 08:29:39 +00:00
2026-04-06 15:50:13 +00:00
static READY_MESSAGE = "ready";
2026-04-01 08:29:39 +00:00
// data
2026-04-13 10:58:21 +00:00
transferData: TransferData | undefined;
2026-04-18 11:55:01 +00:00
direction: TransferDirections = TransferDirections.Send;
// handler managers
2026-04-13 10:58:21 +00:00
readonly fileHandlerManager = new HandlerManager<string>();
readonly readyToSendHandlerManager = new HandlerManager<boolean>();
2026-04-01 08:29:39 +00:00
// general
2026-04-18 11:55:01 +00:00
readonly generateTransferData = (): TransferData => {
2026-04-01 08:29:39 +00:00
const transferData: TransferData = {
channel: generateRandomToken(4),
key: generateRandomToken(6),
2026-04-01 08:29:39 +00:00
};
this.transferData = transferData;
return transferData;
2024-07-27 17:41:54 +02:00
};
2026-04-01 08:29:39 +00:00
2026-04-18 11:55:01 +00:00
readonly prepareToSend = (): void => {
2026-04-06 15:50:13 +00:00
this.direction = TransferDirections.Send;
this.connectionModel.addChannel(this.transferData.channel);
};
2026-04-18 11:55:01 +00:00
readonly prepareToReceive = (transferData: TransferData): void => {
2026-04-06 15:50:13 +00:00
this.direction = TransferDirections.Receive;
2026-04-01 08:29:39 +00:00
this.connectionModel.addChannel(transferData.channel);
this.transferData = transferData;
2026-04-06 15:50:13 +00:00
this.connectionModel.sendPlainMessage(
transferData.channel,
FileTransferModel.READY_MESSAGE,
);
2026-04-01 08:29:39 +00:00
};
// handlers
2026-04-18 11:55:01 +00:00
readonly handleMessage = (data: Message): void => {
if (this.transferData == undefined) return;
2026-04-06 15:50:13 +00:00
if (data.messageChannel != this.transferData.channel) return;
if (
data.messageBody == FileTransferModel.READY_MESSAGE &&
this.direction == TransferDirections.Send
) {
this.readyToSendHandlerManager.trigger(true);
}
2026-04-01 08:29:39 +00:00
if (this.transferData == undefined) return;
if (data.messageBody == undefined) return;
this.handleTransferredFile(data.messageBody);
2026-04-01 08:29:39 +00:00
};
2026-08-29 02:10:26 +02:00
readonly handleTransferredFile = async (
encryptedFileData: string,
): Promise<void> => {
2026-04-01 08:29:39 +00:00
if (this.transferData == undefined) return;
const decrypted: string = await decryptString(
encryptedFileData,
this.transferData.key,
);
2026-08-29 02:10:26 +02:00
this.handleDecryptedFile(decrypted);
};
readonly handleBackupFile = async (
encryptedBackup: string,
passphrase: string,
): Promise<void> => {
const decrypted: string = await decryptString(
encryptedBackup,
passphrase,
);
const parsed = parse(decrypted);
if (!Array.isArray(parsed)) {
console.error("Canceling backup - not an array", decrypted);
throw "no array";
}
for (const file of parsed) {
this.handleDecryptedFile(file);
}
};
2026-08-29 02:10:26 +02:00
readonly handleDecryptedFile = (data: string): void => {
const parsed: any = parse(data);
2026-04-01 08:29:39 +00:00
const isFileData: boolean = checkMatchesObjectStructure(
parsed,
FileDataReference,
);
2026-08-30 14:39:36 +02:00
if (isFileData == false) throw "not file data";
2026-04-01 08:29:39 +00:00
const fileData: FileData = parsed;
this.storageModel.write(fileData.path, fileData.body);
2024-07-28 02:27:09 +02:00
const pathString: string = StorageModel.pathComponentsToString(
2026-04-01 08:29:39 +00:00
...fileData.path,
2024-07-28 02:27:09 +02:00
);
2026-04-01 08:29:39 +00:00
this.fileHandlerManager.trigger(pathString);
2026-08-29 02:10:26 +02:00
};
2024-07-27 18:03:12 +02:00
2026-04-01 08:29:39 +00:00
// sending
2026-04-18 11:55:01 +00:00
readonly sendFiles = (
2026-04-01 08:29:39 +00:00
directoryPaths: IteratorObject<string[]>,
2026-08-29 02:10:26 +02:00
fileCallback: (path: string) => void,
2026-04-01 08:29:39 +00:00
): void => {
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
2026-08-29 02:10:26 +02:00
const stringifiedFileData =
this.prepareFileForSending(filePath);
this.sendFile(stringifiedFileData);
2026-04-01 08:29:39 +00:00
const pathString: string = StorageModel.pathComponentsToString(
...filePath,
);
2026-08-29 02:10:26 +02:00
fileCallback(pathString);
});
}
};
readonly generateBackup = async (
directoryPaths: IteratorObject<string[]>,
passphrase: string,
2026-08-30 00:15:30 +02:00
): Promise<Blob> => {
2026-08-29 02:10:26 +02:00
const files: string[] = [];
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
const stringifiedFileData =
this.prepareFileForSending(filePath);
files.push(stringifiedFileData);
2026-04-01 08:29:39 +00:00
});
}
const rawBackup = stringify(files);
2026-08-30 22:24:55 +02:00
console.log(rawBackup);
2026-08-30 00:15:30 +02:00
const encrypted = await encryptString(rawBackup, passphrase);
2026-08-30 22:24:55 +02:00
const blob = new Blob([encrypted], { type: "text/plain" });
return blob;
2026-04-01 08:29:39 +00:00
};
2024-07-27 18:03:12 +02:00
2026-08-29 02:10:26 +02:00
readonly prepareFileForSending = (filePath: string[]): string => {
2026-08-30 22:24:55 +02:00
const fileContent: string | undefined =
this.storageModel.read(filePath);
2026-08-30 14:39:36 +02:00
if (fileContent == null) return "";
2026-04-01 08:29:39 +00:00
const fileData: FileData = {
path: filePath,
body: fileContent,
};
2026-08-29 02:10:26 +02:00
return stringify(fileData);
};
readonly sendFile = async (stringifiedFileData: string): Promise<void> => {
2026-04-01 08:29:39 +00:00
const encryptedFileData: string = await encryptString(
stringifiedFileData,
this.transferData.key,
);
this.connectionModel.sendPlainMessage(
this.transferData.channel,
encryptedFileData,
);
2024-07-27 18:03:12 +02:00
};
2026-04-01 08:29:39 +00:00
// init
constructor(storageModel: StorageModel, connectionModel: ConnectionModel) {
this.storageModel = storageModel;
this.connectionModel = connectionModel;
this.connectionModel.messageHandlerManager.setHandler(
"file-transfer",
2026-04-01 08:29:39 +00:00
this.handleMessage,
);
}
2024-07-27 17:41:54 +02:00
}
export interface TransferData {
2026-04-13 10:58:21 +00:00
readonly channel: string;
readonly key: string;
2024-07-27 17:41:54 +02:00
}
2024-07-27 18:03:12 +02:00
export interface FileData {
2026-04-01 08:29:39 +00:00
path: string[];
body: string;
2024-07-27 18:03:12 +02:00
}
export const FileDataReference: FileData = {
2026-04-01 08:29:39 +00:00
path: [""],
body: "",
2024-07-27 18:03:12 +02:00
};
2026-04-06 15:50:13 +00:00
export enum TransferDirections {
Send,
Receive,
}