import { Binary, serialize } from 'bson'; import type { IncomingMessage } from '../../messages'; import { BinarySectionInfo } from '../../messages'; import { ClientAdapter } from '../adapter/ClientAdapter'; export class SendBinary { private readonly transactionId: string; private readonly chunkSize = 16384; constructor(private message: IncomingMessage, private adapter: ClientAdapter) { this.transactionId = message.transactionId; } public async send(): Promise { const all: Promise[] = []; const info = this.message.meta?.messageObjectInfo; if (!info) { return; } info.binaryData.forEach((item) => { if (item.data instanceof File || item.data instanceof ArrayBuffer) { all.push( this.sendFile(item), ); } }); await Promise.all(all); } private async sendFile(section: BinarySectionInfo) { const all: Promise[] = []; const file = section.data as ArrayBuffer; const size = this.getByteLength(file); const path = section.path.join('.'); const chunkCount = Math.ceil(size / this.chunkSize); for (let i = 0; i < chunkCount; i++) { const start = i * this.chunkSize; const stop = Math.min(this.chunkSize, size - start) + start; all.push( // eslint-disable-next-line no-await-in-loop this.sendChunk(path, await this.getChunk(file, start, stop)), ); } all.push( this.sendChunk(path, null), ); await Promise.all(all); } private getByteLength(file: any): number { return file.size || file.byteLength || 0; } private async getChunk(data: File | ArrayBuffer, start: number, stop: number): Promise { if (data instanceof File) { const chunk = data.slice(start, stop, 'binary'); return chunk.arrayBuffer(); } if (data instanceof ArrayBuffer) { return data.slice(start, stop); } throw new Error('Unknown binary data.'); } private async sendChunk(path: string, chunk: BinaryData | null) { const message = { transactionId: this.transactionId, path, chunk: chunk === null ? chunk : new Binary(chunk as Buffer), }; await this.adapter.sendBinary(serialize(message)); } }