1 import { TransferCancel } from '../../components/TransferManager/transfer';
2 import { sendErrorReport } from '../../utils/errorHandling';
10 UploadFileProgressCallbacks,
12 import { getMediaInfo } from './media';
13 import { mimeTypeFromFile } from './mimeTypeParser/mimeTypeParser';
14 import { UploadWorkerController } from './workerController';
16 type LogCallback = (message: string) => void;
18 class TransferRetry extends Error {
19 constructor(options: { message: string }) {
20 super(options.message);
21 this.name = 'TransferRetry';
25 export function initUploadFileWorker(
35 notifyVerificationError,
38 ): UploadFileControls {
39 const abortController = new AbortController();
40 let workerApi: UploadWorkerController;
42 // Start detecting mime type right away to have this information once the
43 // upload starts, so we can generate thumbnail as fast as possible without
44 // need to wait for creation of revision on API.
45 const mimeTypePromise = mimeTypeFromFile(file);
47 const start = async ({ onInit, onProgress, onNetworkError, onFinalize }: UploadFileProgressCallbacks = {}) => {
48 // Worker has a slight overhead about 40 ms. Let's start generating
49 // thumbnail a bit sooner.
50 const mediaInfoPromise = getMediaInfo(mimeTypePromise, file, isForPhotos);
52 return new Promise<void>((resolve, reject) => {
53 const worker = new Worker(
54 /* webpackChunkName: "drive-worker" */
55 /* webpackPrefetch: true */
56 /* webpackPreload: true */
57 new URL('./worker/worker.ts', import.meta.url)
60 workerApi = new UploadWorkerController(worker, log, {
61 keysGenerated: (keys: FileKeys) => {
63 .then(async (mimeType) => {
64 return createFileRevision(abortController.signal, mimeType, keys).then(
65 async (fileRevision) => {
66 onInit?.(mimeType, fileRevision.fileName);
70 getVerificationData(abortController.signal),
71 ]).then(async ([mediaInfo, verificationData]) => {
72 await workerApi.postStart(
78 width: mediaInfo?.width,
79 height: mediaInfo?.height,
80 duration: mediaInfo?.duration,
82 thumbnails: mediaInfo?.thumbnails,
84 fileRevision.address.privateKey,
85 fileRevision.address.email,
86 fileRevision.privateKey,
87 fileRevision.sessionKey,
88 fileRevision.parentHashKey,
97 createBlocks: (fileBlocks: FileRequestBlock[], thumbnailBlocks?: ThumbnailRequestBlock[]) => {
98 createBlockLinks(abortController.signal, fileBlocks, thumbnailBlocks)
99 .then(({ fileLinks, thumbnailLinks }) => workerApi.postCreatedBlocks(fileLinks, thumbnailLinks))
102 onProgress: (increment: number) => {
103 onProgress?.(increment);
105 finalize: (signature: string, signatureAddress: string, xattr: string, photo?: PhotoUpload) => {
107 finalize(signature, signatureAddress, xattr, photo).then(resolve).catch(reject);
109 onNetworkError: (error: Error) => {
110 onNetworkError?.(error);
112 onError: (error: Error) => {
115 notifySentry: (error: Error) => {
116 sendErrorReport(error);
118 notifyVerificationError: (retryHelped: boolean) => {
119 notifyVerificationError(retryHelped);
122 reject(new TransferCancel({ message: `Transfer canceled for ${file.name}` }));
124 onHeartbeatTimeout: () => {
125 reject(new TransferRetry({ message: `Heartbeat timeout` }));
129 initialize(abortController.signal)
130 .then(async ({ addressPrivateKey, parentPrivateKey }) => {
131 await workerApi.postGenerateKeys(addressPrivateKey, parentPrivateKey);
137 const pause = async () => {
138 workerApi?.postPause();
141 const resume = async () => {
142 workerApi?.postResume();
145 const cancel = async () => {
146 abortController.abort();
151 start: (progressCallbacks?: UploadFileProgressCallbacks) =>
152 start(progressCallbacks)
154 abortController.abort();
159 workerApi?.postClose();
160 // We give some time to the worker to `close()` itself, to safely erase the stored private keys.
161 // We still forcefully terminate it after a few seconds, in case the worker is unexpectedly stuck
162 // in a bad state, hence couldn't close itself.
164 workerApi?.terminate();