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 = {
|
2026-04-01 13:40:07 +00:00
|
|
|
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 => {
|
2026-04-06 19:19:57 +00:00
|
|
|
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;
|
|
|
|
|
|
2026-08-29 00:48:55 +02:00
|
|
|
this.handleTransferredFile(data.messageBody);
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
2026-08-29 00:48:55 +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 00:48:55 +02:00
|
|
|
this.handleDecryptedFile(decrypted);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
readonly handleDecryptedFile = (data: string): void => {
|
|
|
|
|
const parsed: any = parse(data);
|
2026-04-01 08:29:39 +00:00
|
|
|
const isFileData: boolean = checkMatchesObjectStructure(
|
|
|
|
|
parsed,
|
|
|
|
|
FileDataReference,
|
|
|
|
|
);
|
|
|
|
|
if (isFileData == false) return;
|
|
|
|
|
|
|
|
|
|
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 00:48:55 +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[]>,
|
|
|
|
|
callback: (path: string) => void,
|
|
|
|
|
): void => {
|
|
|
|
|
for (const directoryPath of directoryPaths) {
|
|
|
|
|
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
|
|
|
|
|
this.sendFile(filePath);
|
|
|
|
|
const pathString: string = StorageModel.pathComponentsToString(
|
|
|
|
|
...filePath,
|
|
|
|
|
);
|
|
|
|
|
callback(pathString);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-27 18:03:12 +02:00
|
|
|
|
2026-08-29 00:48:55 +02:00
|
|
|
readonly prepareFileForSending = (filePath: string[]): void => {
|
2026-04-01 08:29:39 +00:00
|
|
|
if (this.transferData == undefined) return;
|
|
|
|
|
const fileContent: string | null = this.storageModel.read(filePath);
|
|
|
|
|
if (fileContent == null) return;
|
|
|
|
|
|
|
|
|
|
const fileData: FileData = {
|
|
|
|
|
path: filePath,
|
|
|
|
|
body: fileContent,
|
|
|
|
|
};
|
|
|
|
|
const stringifiedFileData: string = stringify(fileData);
|
2026-08-29 00:48:55 +02:00
|
|
|
this.sendFile(stringifiedFileData);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-04-14 09:20:44 +00:00
|
|
|
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,
|
|
|
|
|
}
|