403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/cvetdv.ru/bitrix/js/im/v2/provider/service/uploading/src/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/cvetdv.ru/bitrix/js/im/v2/provider/service/uploading/src/uploading.js
import { Type } from 'main.core';
import { BaseEvent, EventEmitter } from 'main.core.events';
import { getFilesFromDataTransfer, isFilePasted } from 'ui.uploader.core';
import { runAction } from 'im.v2.lib.rest';

import { Core } from 'im.v2.application.core';
import { FileStatus, FileType, RestMethod } from 'im.v2.const';
import { Utils } from 'im.v2.lib.utils';
import { Logger } from 'im.v2.lib.logger';
import { Notifier } from 'im.v2.lib.notifier';
import { SendingService } from 'im.v2.provider.service.sending';

import { UploaderWrapper } from './classes/uploader-wrapper';

import type { ImModelChat, ImModelUser, ImModelMessage, ImModelFile } from 'im.v2.model';
import type { UploaderFile, UploaderError } from 'ui.uploader.core';
import type { Store } from 'ui.vue3.vuex';
import type { RestClient } from 'rest.client';
import type {
	MessageWithFile,
	FileFromDisk,
	FileCommitParams,
	UploadFilesParams,
	UploadFromClipboardParams,
	UploadFromInputParams,
	UploadFromDragAndDrop,
} from './types/uploading';

type CreateUploaderParams = {
	dialogId: number,
	autoUpload: boolean,
	maxParallelUploads: number,
	maxParallelLoads: number,
};

const EVENT_NAMESPACE = 'BX.Messenger.v2.Service.UploadingService';

export class UploadingService extends EventEmitter
{
	#store: Store;
	#restClient: RestClient;
	#isRequestingDiskFolderId: boolean = false;
	#diskFolderIdRequestPromise: { [string]: Promise } = {};
	#uploaderWrapper: UploaderWrapper;
	#sendingService: SendingService;
	#uploaderFilesRegistry: {
		[uploaderId: string]: {
			autoUpload: boolean,
			wasSent: boolean,
			text: string,
			dialogId: string,
			tempMessageId: string,
			previewCreatedStatus: { [string]: boolean },
			previewSentStatus: { [string]: boolean },
		}
	} = {};

	static event = {
		uploadStart: 'uploadStart',
		uploadComplete: 'uploadComplete',
		uploadError: 'uploadError',
		uploadCancel: 'uploadCancel',
	};

	#queue: Array<string> = [];
	#isUploading: boolean = false;

	static instance = null;

	static getInstance(): UploadingService
	{
		if (!this.instance)
		{
			this.instance = new this();
		}

		return this.instance;
	}

	constructor()
	{
		super();
		this.setEventNamespace(EVENT_NAMESPACE);

		this.#store = Core.getStore();
		this.#restClient = Core.getRestClient();
		this.#sendingService = SendingService.getInstance();
		this.#initUploader();
	}

	async uploadFromClipboard(params: UploadFromClipboardParams): Promise<{files: UploaderFile[], uploaderId: string}>
	{
		const { clipboardEvent, dialogId, autoUpload, imagesOnly } = params;

		const { clipboardData } = clipboardEvent;
		if (!clipboardData || !isFilePasted(clipboardData))
		{
			return '';
		}

		clipboardEvent.preventDefault();

		let files: File[] = await getFilesFromDataTransfer(clipboardData);
		if (imagesOnly)
		{
			files = files.filter((file) => Utils.file.isImage(file.name));
			if (imagesOnly.length === 0)
			{
				return '';
			}
		}

		const { uploaderFiles, uploaderId } = await this.addFiles({
			files,
			dialogId,
			autoUpload,
		});

		if (uploaderFiles.length === 0)
		{
			return '';
		}

		return uploaderId;
	}

	async uploadFromInput(params: UploadFromInputParams): Promise<string>
	{
		const { event, sendAsFile, autoUpload, dialogId } = params;
		const rawFiles = Object.values(event.target.files);
		if (rawFiles.length === 0)
		{
			return '';
		}

		const { uploaderId } = await this.addFiles({
			files: rawFiles,
			dialogId,
			autoUpload,
			sendAsFile,
		});

		return uploaderId;
	}

	async uploadFromDragAndDrop(params: UploadFromDragAndDrop): Promise<string>
	{
		const { event, dialogId, autoUpload, sendAsFile } = params;
		event.preventDefault();

		const rawFiles = await getFilesFromDataTransfer(event.dataTransfer);
		if (rawFiles.length === 0)
		{
			return '';
		}

		const { uploaderId } = await this.addFiles({
			files: rawFiles,
			dialogId,
			autoUpload,
			sendAsFile,
		});

		return uploaderId;
	}

	#createUploader(params: CreateUploaderParams): Promise<string>
	{
		const {
			dialogId,
			autoUpload,
			maxParallelUploads,
			maxParallelLoads,
		} = params;

		const uploaderId = Utils.text.getUuidV4();
		const chatId = this.#getChatId(dialogId);

		return this.checkDiskFolderId(dialogId).then((diskFolderId: number) => {
			this.#uploaderWrapper.createUploader({
				diskFolderId,
				uploaderId,
				autoUpload,
				chatId,
				dialogId,
				maxParallelUploads,
				maxParallelLoads,
			});

			return uploaderId;
		});
	}

	addFiles(params: UploadFilesParams): Promise<{uploaderFiles: UploaderFile[], uploaderId: string}>
	{
		const {
			files,
			dialogId,
			autoUpload,
			sendAsFile = false,
			maxParallelUploads,
			maxParallelLoads,
		} = params;

		const uploaderParams: CreateUploaderParams = {
			dialogId,
			autoUpload,
			maxParallelUploads,
			maxParallelLoads,
		};

		return this.#createUploader(uploaderParams).then((uploaderId: string) => {
			const filesForUploader = [];
			files.forEach((file) => {
				const preparedFile = this.#prepareFileForUploader(file, dialogId, uploaderId, sendAsFile);
				filesForUploader.push(preparedFile);
			});

			const addedFiles = this.#uploaderWrapper.addFiles(filesForUploader);
			this.#registerFiles(uploaderId, addedFiles, dialogId, autoUpload);

			return {
				uploaderFiles: addedFiles,
				uploaderId,
			};
		});
	}

	getFiles(uploaderId): UploaderFile[]
	{
		return this.#uploaderWrapper.getFiles(uploaderId);
	}

	#addToQueue(uploaderId: string)
	{
		this.#queue.push(uploaderId);
		this.#processQueue();
	}

	#processQueue()
	{
		if (this.#isUploading || this.#queue.length === 0)
		{
			return;
		}

		this.#isUploading = true;

		const uploaderId: string = this.#queue.shift();
		this.#uploaderFilesRegistry[uploaderId].autoUpload = true;
		this.#uploaderWrapper.start(uploaderId);
	}

	start(uploaderId: string)
	{
		this.#addToQueue(uploaderId);
	}

	stop(uploaderId: string)
	{
		this.#uploaderWrapper.stop(uploaderId);
	}

	uploadFileFromDisk(files, dialogId)
	{
		Object.values(files).forEach((file) => {
			const messageWithFile = this.#prepareFileFromDisk(file, dialogId);

			this.#addFileFromDiskToModel(messageWithFile).then(() => {
				const message = {
					tempMessageId: messageWithFile.tempMessageId,
					fileIds: [messageWithFile.tempFileId],
					dialogId: messageWithFile.dialogId,
				};

				return this.#sendingService.sendMessageWithFiles(message);
			}).then(() => {
				this.commitFile({
					chatId: messageWithFile.chatId,
					temporaryFileId: messageWithFile.tempFileId,
					tempMessageId: messageWithFile.tempMessageId,
					realFileId: messageWithFile.file.id.slice(1),
					fromDisk: true,
				});
			}).catch((error) => {
				console.error('SendingService: sendFilesFromDisk error:', error);
			});
		});
	}

	#addFileFromDiskToModel(messageWithFile: MessageWithFile): Promise
	{
		return this.#store.dispatch('files/add', {
			id: messageWithFile.tempFileId,
			chatId: messageWithFile.chatId,
			authorId: Core.getUserId(),
			name: messageWithFile.file.name,
			type: Utils.file.getFileTypeByExtension(messageWithFile.file.ext),
			extension: messageWithFile.file.ext,
			size: messageWithFile.file.sizeInt,
			status: FileStatus.wait,
			progress: 0,
			authorName: this.#getCurrentUser().name,
		});
	}

	#initUploader()
	{
		this.#uploaderWrapper = new UploaderWrapper();
		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileAddStart, (event: BaseEvent) => {
			const { file } = event.getData();
			this.#addFileToStore(file);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileAdd, (event: BaseEvent) => {
			const { file, uploaderId } = event.getData();
			this.#updateFilePreviewInStore(file);
			this.#setPreviewCreatedStatus(uploaderId, file.getId());
			this.#tryToSendMessage(uploaderId);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileUploadStart, (event: BaseEvent) => {
			const { file } = event.getData();
			this.#updateFileSizeInStore(file);
			this.emit(UploadingService.event.uploadStart);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileUploadProgress, (event: BaseEvent) => {
			const { file } = event.getData();
			this.#updateFileProgress(file.getId(), file.getProgress(), FileStatus.upload);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileUploadComplete, async (event: BaseEvent) => {
			const { file, uploaderId }: {file: UploaderFile, uploaderId: string} = event.getData();
			const serverFileId: number = file.getServerFileId().toString().slice(1);
			const temporaryFileId: string = file.getId();
			if (this.#isMediaFile(temporaryFileId))
			{
				this.#setFileMapping({ serverFileId, temporaryFileId });
			}

			this.#updateFileProgress(temporaryFileId, file.getProgress(), FileStatus.wait);

			await this.#uploadPreview(file);
			this.#setPreviewSentStatus(uploaderId, temporaryFileId);

			void this.#tryCommit(uploaderId);
			this.emit(UploadingService.event.uploadComplete);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileUploadError, (event: BaseEvent) => {
			const { file, error, uploaderId } = event.getData();

			this.#setMessageError(file.getCustomData('tempMessageId'));

			this.getFiles(uploaderId).forEach((uploaderFile: UploaderFile) => {
				this.#updateFileProgress(uploaderFile.getId(), 0, FileStatus.error);
			});

			if (this.#isMaxFileSizeExceeded(error))
			{
				Notifier.file.handleUploadError(error);
			}

			Logger.error('UploadingService: upload error', error);
			this.emit(UploadingService.event.uploadError);

			this.#isUploading = false;
			this.#processQueue();
			this.stop(uploaderId);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onFileUploadCancel, (event: BaseEvent) => {
			const { tempMessageId, tempFileId } = event.getData();
			this.#cancelUpload(tempMessageId, tempFileId);
			this.emit(UploadingService.event.uploadCancel);
		});

		this.#uploaderWrapper.subscribe(UploaderWrapper.events.onUploadComplete, () => {
			this.#isUploading = false;
			this.#processQueue();
		});
	}

	#isMediaFile(fileId: string | number): boolean
	{
		const mediaFileTypes: Array<string> = [FileType.image, FileType.video];
		const file: ?ImModelFile = this.#store.getters['files/get'](fileId);

		return Boolean(file) && mediaFileTypes.includes(file.type);
	}

	#setFileMapping(options: {serverFileId: number, temporaryFileId: string})
	{
		void this.#store.dispatch('files/setTemporaryFileMapping', options);
	}

	checkDiskFolderId(dialogId: string): Promise<number>
	{
		if (this.#getDiskFolderId(dialogId) > 0)
		{
			return Promise.resolve(this.#getDiskFolderId(dialogId));
		}

		if (this.#isRequestingDiskFolderId)
		{
			return this.#diskFolderIdRequestPromise[dialogId];
		}

		this.#diskFolderIdRequestPromise[dialogId] = this.#requestDiskFolderId(dialogId);

		return this.#diskFolderIdRequestPromise[dialogId];
	}

	#requestDiskFolderId(dialogId: string): Promise
	{
		return new Promise((resolve, reject) => {
			this.#isRequestingDiskFolderId = true;

			const chatId = this.#getChatId(dialogId);
			this.#restClient.callMethod(RestMethod.imDiskFolderGet, { chat_id: chatId }).then((response) => {
				const { ID: diskFolderId } = response.data();
				this.#isRequestingDiskFolderId = false;
				void this.#store.dispatch('chats/update', {
					dialogId,
					fields: {
						diskFolderId,
					},
				});
				resolve(diskFolderId);
			}).catch((error) => {
				this.#isRequestingDiskFolderId = false;
				reject(error);
			});
		});
	}

	commitFile(params: FileCommitParams)
	{
		const { temporaryFileId, tempMessageId, chatId, realFileId, fromDisk, messageText = '', sendAsFile = false } = params;

		const fileIdParams = {};
		if (fromDisk)
		{
			fileIdParams.disk_id = realFileId;
		}
		else
		{
			fileIdParams.upload_id = realFileId.toString().slice(1);
		}

		this.#restClient.callMethod(RestMethod.imDiskFileCommit, {
			chat_id: chatId,
			message: messageText,
			template_id: tempMessageId,
			file_template_id: temporaryFileId,
			as_file: sendAsFile ? 'Y' : 'N',
			...fileIdParams,
		}).catch((error) => {
			this.#setMessageError(tempMessageId);
			this.#updateFileProgress(temporaryFileId, 0, FileStatus.error);
			console.error('commitFile error', error);
		});
	}

	commitMessage(uploaderId: string): Promise
	{
		const dialogId = this.#uploaderFilesRegistry[uploaderId].dialogId;
		const chatId = this.#getChatId(dialogId);

		const fileIds = this.#uploaderWrapper.getFiles(uploaderId).map((file) => {
			return file.getServerFileId().toString().slice(1);
		});

		const sendAsFile = this.#uploaderWrapper.getFiles(uploaderId).every((file) => {
			return file.getCustomData('sendAsFile');
		});

		return this.#restClient.callMethod(RestMethod.imDiskFileCommit, {
			chat_id: chatId,
			message: this.#uploaderFilesRegistry[uploaderId].text,
			template_id: this.#uploaderFilesRegistry[uploaderId].tempMessageId,
			// file_template_id: temporaryFileId,
			as_file: sendAsFile ? 'Y' : 'N',
			upload_id: fileIds,
		});
	}

	async #tryCommit(uploaderId: string)
	{
		if (!this.#readyToCommit(uploaderId))
		{
			return;
		}

		await this.commitMessage(uploaderId);
		this.#destroyUploader(uploaderId);
	}

	async #uploadPreview(file: UploaderFile): Promise
	{
		const needPreview = this.#getFileType(file.getBinary()) === FileType.video || file.isAnimated();
		if (!needPreview)
		{
			return Promise.resolve();
		}

		const id = file.getServerFileId().toString().slice(1);
		const previewFile = file.getClientPreview();
		if (!previewFile)
		{
			file.setCustomData('sendAsFile', true);

			return Promise.resolve();
		}

		const formData = new FormData();
		formData.append('id', id);
		formData.append('previewFile', previewFile, `preview_${file.getName()}.jpg`);

		return runAction(RestMethod.imDiskFilePreviewUpload, { data: formData })
			.catch(([error]) => {
				console.error('imDiskFilePreviewUpload request error', error);
			});
	}

	#prepareFileForUploader(file: File, dialogId: string, uploaderId, sendAsFile: boolean): MessageWithFile
	{
		const tempMessageId = Utils.text.getUuidV4();
		const tempFileId = Utils.text.getUuidV4();
		const chatId = this.#getChatId(dialogId);

		return {
			tempMessageId,
			tempFileId,
			file,
			dialogId,
			chatId,
			uploaderId,
			sendAsFile,
		};
	}

	#updateFileProgress(id: string, progress: number, status: string)
	{
		void this.#store.dispatch('files/update', {
			id,
			fields: {
				progress: (progress === 100 ? 99 : progress),
				status,
			},
		});
	}

	#cancelUpload(tempMessageId: string, tempFileId)
	{
		const message: ImModelMessage = this.#store.getters['messages/getById'](tempMessageId);
		if (message)
		{
			void this.#store.dispatch('messages/delete', { id: tempMessageId });
			void this.#store.dispatch('files/delete', { id: tempFileId });

			const chat: ImModelChat = this.#store.getters['chats/getByChatId'](message.chatId);
			const lastMessageId: string | number | null = this.#store.getters['messages/findLastChatMessageId'](message.chatId);

			if (Type.isString(lastMessageId) || Type.isNumber(lastMessageId))
			{
				void this.#store.dispatch('recent/update', {
					id: chat.dialogId,
					fields: { messageId: lastMessageId },
				});
			}
			else
			{
				void this.#store.dispatch('recent/delete', {
					id: chat.dialogId,
				});
			}
		}
	}

	#addFileToStore(file: UploaderFile)
	{
		const taskId = file.getId();
		const fileBinary = file.getBinary();
		const previewData = this.#preparePreview(file);
		const asFile = file.getCustomData('sendAsFile');

		void this.#store.dispatch('files/add', {
			id: taskId,
			chatId: file.getCustomData('chatId'),
			authorId: Core.getUserId(),
			name: fileBinary.name,
			size: file.getSize(),
			type: asFile ? FileType.file : this.#getFileType(fileBinary),
			extension: this.#getFileExtension(fileBinary),
			status: file.isFailed() ? FileStatus.error : FileStatus.progress,
			progress: 0,
			authorName: this.#getCurrentUser().name,
			urlDownload: URL.createObjectURL(file.getBinary()),
			...previewData,
		});
	}

	#updateFilePreviewInStore(file: UploaderFile)
	{
		const previewData = this.#preparePreview(file);

		void this.#store.dispatch('files/update', {
			id: file.getId(),
			fields: {
				...previewData,
			},
		});
	}

	#updateFileSizeInStore(file: UploaderFile)
	{
		void this.#store.dispatch('files/update', {
			id: file.getId(),
			fields: {
				size: file.getSize(),
			},
		});
	}

	#preparePreview(file: UploaderFile): { image: { width: number, height: number }, urlPreview: Blob }
	{
		if (file.getCustomData('sendAsFile'))
		{
			return {};
		}

		const preview = {
			blob: file.getPreviewUrl(),
			width: file.getPreviewWidth(),
			height: file.getPreviewHeight(),
		};

		const previewData = {};
		if (preview.blob)
		{
			previewData.image = {
				width: preview.width,
				height: preview.height,
			};

			previewData.urlPreview = preview.blob;
		}

		if (file.getClientPreview())
		{
			previewData.urlPreview = URL.createObjectURL(file.getClientPreview());
		}

		return previewData;
	}

	#getDiskFolderId(dialogId: string): number
	{
		return this.#getDialog(dialogId).diskFolderId;
	}

	#getFileType(file: File): string
	{
		let fileType = FileType.file;
		if (file.type.startsWith('image'))
		{
			fileType = FileType.image;
		}
		else if (file.type.startsWith('video'))
		{
			fileType = FileType.video;
		}

		return fileType;
	}

	#getFileExtension(file: File): string
	{
		return file.name.split('.').splice(-1)[0];
	}

	#getDialog(dialogId: string): ImModelChat
	{
		return this.#store.getters['chats/get'](dialogId);
	}

	#getCurrentUser(): ImModelUser
	{
		const userId = Core.getUserId();

		return this.#store.getters['users/get'](userId);
	}

	#getChatId(dialogId: string): ?number
	{
		return this.#getDialog(dialogId)?.chatId;
	}

	#registerFiles(uploaderId: string, files: UploaderFile[], dialogId: string, autoUpload: boolean)
	{
		if (!this.#uploaderFilesRegistry[uploaderId])
		{
			this.#uploaderFilesRegistry[uploaderId] = {
				previewCreatedStatus: {},
				previewSentStatus: {},
				dialogId,
				text: '',
				autoUpload,
			};
		}

		files.forEach((file) => {
			const fileId = file.getId();
			if (!this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus)
			{
				this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus = {};
			}

			this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus[fileId] = false;
			this.#uploaderFilesRegistry[uploaderId].previewSentStatus[fileId] = false;
		});
	}

	#unregisterFiles(uploaderId: string, files: UploaderFile[])
	{
		const entry = this.#uploaderFilesRegistry[uploaderId];
		if (entry)
		{
			files.forEach((file: UploaderFile) => {
				const fileId = file.getId();
				if (this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus)
				{
					delete this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus[fileId];
					delete this.#uploaderFilesRegistry[uploaderId].previewSentStatus[fileId];
				}
			});
		}
	}

	#setPreviewCreatedStatus(uploaderId: string, fileId: string)
	{
		this.#uploaderFilesRegistry[uploaderId].previewCreatedStatus[fileId] = true;
	}

	#setPreviewSentStatus(uploaderId: string, fileId: string)
	{
		this.#uploaderFilesRegistry[uploaderId].previewSentStatus[fileId] = true;
	}

	#setMessagesText(uploaderId: string, text: string)
	{
		this.#uploaderFilesRegistry[uploaderId].text = text;
	}

	#setAutoUpload(uploaderId: string, autoUploadFlag: boolean)
	{
		this.#uploaderFilesRegistry[uploaderId].autoUpload = autoUploadFlag;
	}

	// we don't use it now, because we always send several files in ONE message
	// noinspection JSUnusedGlobalSymbols
	sendSeparateMessagesWithFiles(params: { uploaderId: string, text: string})
	{
		const { uploaderId, text } = params;

		this.#setMessagesText(uploaderId, text);
		this.#setAutoUpload(uploaderId, true);
		this.#tryToSendMessages(uploaderId);
	}

	sendMessageWithFiles(params: { uploaderId: string, text: string})
	{
		const { uploaderId, text } = params;

		this.#setMessagesText(uploaderId, text);
		this.#setAutoUpload(uploaderId, true);
		this.#tryToSendMessage(uploaderId);
	}

	#createMessagesFromFiles(uploaderId): {comment: {text: string, dialogId: string}, files: []}
	{
		const messagesToSend = {
			comment: {},
			files: [],
		};

		const files = this.getFiles(uploaderId);
		const text = this.#uploaderFilesRegistry[uploaderId].text;
		const dialogId = this.#uploaderFilesRegistry[uploaderId].dialogId;
		const hasText = text.length > 0;

		// if we have more than one file and text, we need to send text message first
		if (files.length > 1 && hasText)
		{
			messagesToSend.comment = { dialogId, text };
		}

		files.forEach((file) => {
			if (file.getError())
			{
				return;
			}

			const messageId = Utils.text.getUuidV4();

			file.setCustomData('messageId', messageId);
			if (files.length === 1 && hasText)
			{
				file.setCustomData('messageText', text);
			}

			messagesToSend.files.push({
				fileIds: [file.getId()],
				tempMessageId: file.getCustomData('tempMessageId'),
				dialogId,
				text: file.getCustomData('messageText') ?? '',
			});
		});

		return messagesToSend;
	}

	#createMessageFromFiles(uploaderId): {text: string, dialogId: string, tempMessageId: string, fileIds: []}
	{
		const fileIds = [];
		const files = this.getFiles(uploaderId);
		files.forEach((file) => {
			if (!file.getError())
			{
				fileIds.push(file.getId());
			}
		});

		const text = this.#uploaderFilesRegistry[uploaderId].text;
		const dialogId = this.#uploaderFilesRegistry[uploaderId].dialogId;

		const tempMessageId = files[0].getCustomData('tempMessageId');
		this.#uploaderFilesRegistry[uploaderId].tempMessageId = tempMessageId;

		return {
			fileIds,
			tempMessageId,
			dialogId,
			text,
		};
	}

	#readyToAddMessages(uploaderId): boolean
	{
		if (
			!this.#uploaderFilesRegistry[uploaderId]
			|| !this.#uploaderFilesRegistry[uploaderId].autoUpload
			|| this.#uploaderFilesRegistry[uploaderId].wasSent
		)
		{
			return false;
		}

		const { previewCreatedStatus } = this.#uploaderFilesRegistry[uploaderId];

		return Object.values(previewCreatedStatus).every((flag) => flag === true);
	}

	#readyToCommit(uploaderId: string): boolean
	{
		if (!this.#uploaderFilesRegistry[uploaderId])
		{
			return false;
		}

		const { previewSentStatus } = this.#uploaderFilesRegistry[uploaderId];

		return Object.values(previewSentStatus).every((flag) => flag === true);
	}

	#tryToSendMessage(uploaderId: string)
	{
		if (!this.#readyToAddMessages(uploaderId))
		{
			return;
		}

		this.#uploaderFilesRegistry[uploaderId].wasSent = true;

		const message = this.#createMessageFromFiles(uploaderId);
		void this.#sendingService.sendMessageWithFiles(message);
		this.start(uploaderId);
	}

	// we don't use it now, because we always send several files in ONE message
	// noinspection JSUnusedGlobalSymbols
	#tryToSendMessages(uploaderId: string)
	{
		if (!this.#readyToAddMessages(uploaderId))
		{
			return;
		}

		this.#uploaderFilesRegistry[uploaderId].wasSent = true;
		const { comment, files } = this.#createMessagesFromFiles(uploaderId);
		if (comment.text)
		{
			this.#sendingService.sendMessage(comment);
		}

		files.forEach((message) => {
			void this.#sendingService.sendMessageWithFiles(message);
		});
		this.start(uploaderId);
	}

	#prepareFileFromDisk(file: FileFromDisk, dialogId: string): MessageWithFile
	{
		const tempMessageId = Utils.text.getUuidV4();
		const realFileId = file.id.slice(1); // 'n123' => '123'
		const tempFileId = `${tempMessageId}|${realFileId}`;

		return {
			tempMessageId,
			tempFileId,
			dialogId,
			file,
			chatId: this.#getDialog(dialogId).chatId,
		};
	}

	#isMaxFileSizeExceeded(error: UploaderError): boolean
	{
		return error.getCode() === 'MAX_FILE_SIZE_EXCEEDED';
	}

	#setMessageError(tempMessageId: string)
	{
		void this.#store.dispatch('messages/update', {
			id: tempMessageId,
			fields: {
				error: true,
			},
		});
	}

	getUploaderIdByFileId(fileId: string | number): ?string
	{
		const uploaderIds: Array<string> = Object.keys(this.#uploaderFilesRegistry);

		return uploaderIds.find((uploaderId: string) => {
			return this.getFiles(uploaderId).some((file: UploaderFile) => {
				return file.getId() === fileId;
			});
		});
	}

	removeFileFromUploader(options: { uploaderId: string, filesIds: Array<string>, restartUploading: boolean })
	{
		const { uploaderId, filesIds, restartUploading = false } = options;

		const files: Array<UploaderFile> = this.#uploaderWrapper.getFiles(uploaderId).filter((file: UploaderFile) => {
			return filesIds.includes(file.getId());
		});

		files.forEach((file: UploaderFile) => {
			file.remove();
			file.abort();
		});

		this.#unregisterFiles(uploaderId, files);

		if (restartUploading)
		{
			const [firstFile] = this.getFiles(uploaderId);
			if (firstFile)
			{
				firstFile.upload();
			}
		}
	}

	destroy()
	{
		this.#uploaderWrapper.destroy();
	}

	#destroyUploader(uploaderId: string)
	{
		this.#uploaderWrapper.destroyUploader(uploaderId);
		delete this.#uploaderFilesRegistry[uploaderId];
	}

	#getBinaryFiles(uploaderId: string): Array<File>
	{
		return this.getFiles(uploaderId).map((file: UploaderFile) => {
			return file.getBinary();
		});
	}

	async retry(uploaderId: string)
	{
		const { dialogId, text, tempMessageId } = this.#uploaderFilesRegistry[uploaderId];
		const sendAsFile: boolean = this.getFiles(uploaderId).some((file) => {
			return file.getCustomData('sendAsFile');
		});

		const binaryFiles: Array<File> = this.#getBinaryFiles(uploaderId);

		const { uploaderId: newUploaderId } = await this.addFiles({
			dialogId,
			files: binaryFiles,
			sendAsFile,
		});

		void this.#store.dispatch('messages/delete', {
			id: tempMessageId,
		});

		this.sendMessageWithFiles({
			uploaderId: newUploaderId,
			text,
		});

		this.#destroyUploader(uploaderId);
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit