/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import type {ServiceDownloadTaskPayload} from './serviceMessagePort'; import type ServiceMessagePort from './serviceMessagePort'; import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise'; import makeError from '../../helpers/makeError'; import pause from '../../helpers/schedulers/pause'; type DownloadType = Uint8Array; type DownloadItem = ServiceDownloadTaskPayload & { // transformStream: TransformStream, readableStream: ReadableStream, // writableStream: WritableStream, // writer: WritableStreamDefaultWriter, // controller: TransformStreamDefaultController, controller: ReadableStreamController, promise: CancellablePromise, // downloadPromise: Promise, used?: boolean }; const downloadMap: Map = new Map(); const DOWNLOAD_ERROR = makeError('UNKNOWN'); const DOWNLOAD_TEST = false; type A = Parameters['addMultipleEventsListeners']>[0]; const events: A = { download: (payload) => { const {id} = payload; if(downloadMap.has(id)) { return Promise.reject(DOWNLOAD_ERROR); } // const y = (20 * 1024 * 1024) / payload.limitPart; // const strategy = new ByteLengthQueuingStrategy({highWaterMark: y}); // let controller: TransformStreamDefaultController; const strategy = new CountQueuingStrategy({highWaterMark: 1}); // const transformStream = new TransformStream(/* { // start: (_controller) => controller = _controller, // }, */undefined, strategy, strategy); // const {readable, writable} = transformStream; // const writer = writable.getWriter(); const promise = deferredPromise(); promise.then(() => { setTimeout(() => { downloadMap.delete(id); }, 5e3); }, () => { downloadMap.delete(id); }); // writer.closed.then(promise.resolve, promise.reject); let controller: ReadableStreamController; const readable = new ReadableStream({ start: (_controller) => { controller = _controller; }, cancel: (reason) => { promise.reject(DOWNLOAD_ERROR); } }, strategy); // writer.closed.catch(noop).finally(() => { // log.error('closed writer'); // onEnd(); // }); // const downloadPromise = writer.closed.catch(() => {throw DOWNLOAD_ERROR;}); const item: DownloadItem = { ...payload, // transformStream, readableStream: readable, // writableStream: writable, // writer, // downloadPromise, promise, controller }; downloadMap.set(id, item); // return downloadPromise; return promise.catch(() => {throw DOWNLOAD_ERROR}); }, downloadChunk: ({id, chunk}) => { const item = downloadMap.get(id); if(!item) { return Promise.reject(); } // return item.controller.enqueue(chunk); // return item.writer.write(chunk); return item.controller.enqueue(chunk); }, downloadFinalize: (id) => { const item = downloadMap.get(id); if(!item) { return Promise.reject(); } item.promise.resolve(); // return item.controller.terminate(); // return item.writer.close(); return item.controller.close(); }, downloadCancel: (id) => { const item = downloadMap.get(id); if(!item) { return; } item.promise.reject(); // return item.controller.error(); // return item.writer.abort(); return item.controller.error(); } }; export default function handleDownload(serviceMessagePort: ServiceMessagePort) { serviceMessagePort.addMultipleEventsListeners(events); return { onDownloadFetch, onClosedWindows: cancelAllDownloads }; } function onDownloadFetch(event: FetchEvent, params: string) { event.respondWith(pause(100).then(() => { const item = downloadMap.get(params); if(!item || (item.used && !DOWNLOAD_TEST)) { return; } item.used = true; const stream = item.readableStream; const response = new Response(stream, {headers: item.headers}); return response; })); // event.respondWith(response); } function cancelAllDownloads() { if(downloadMap.size) { for(const [id, item] of downloadMap) { // item.writer.abort().catch(noop); item.controller.error(); } } }