UNPKG

10.2 kBJavaScriptView Raw
1import io from 'socket.io-client';
2import DBManager from './dbManager';
3
4function worker(self) {
5 const window = self;
6 const indexedDB = self.indexedDB || self.webkitIndexedDB || self.mozIndexedDB || self.OIndexedDB || self.msIndexedDB;
7 const IDBTransaction = self.IDBTransaction || self.webkitIDBTransaction || self.msIDBTransaction;
8 const IDBKeyRange = self.IDBKeyRange || self.webkitIDBKeyRange || self.msIDBKeyRange;
9 const FileReaderSync = self.FileReaderSync;
10
11 class SubWorker {
12 constructor({ onChange, ...params}, file) {
13 const defaultParams = {
14 chunkSize: 1 * 1024 * 1024,
15 maxConnectionAttempts: 10,
16 fileThrottle: 1000,
17 url: 'ws://localhost:5000/upload',
18 events: {
19 GET_LAST_CHUNK: 'get-last-chunk',
20 SEND_NEXT_CHUNK: 'send-next-chunk',
21 SEND_NEXT_CHUNK_SUCCESS: 'send-next-chunk-successful',
22 SEND_FILE_SUCCESS: 'send-file-successful',
23 CANCEL_UPLOAD: 'cancel-upload',
24 SEND_CHUNK_AGAIN: 'send-chunk-again',
25 ERROR: 'error'
26 }
27 };
28
29 this.onMessage = onChange;
30 this.socket = null;
31 this.file = file;
32 this.params = {...defaultParams, ...params};
33 this.events = this.params.events;
34 this.fileSize = this.file.data.size;
35 this.chunkSize = Math.min(this.params.chunkSize, this.fileSize);
36 this.maxChunk = (~~(this.fileSize / this.chunkSize)) - 1;
37 this.offset = 0;
38 this.start = 0;
39 this.end = this.chunkSize;
40 this.progress = 0;
41 this.maxConnectionAttempts = this.params.maxConnectionAttempts;
42 this.errorCount = 1;
43 this.throttle = this.params.fileThrottle;
44 this.previousUpdateTime = Date.now();
45 this.isSuspended = false;
46 this.updateFileState(false);
47 this.openSocket();
48 }
49
50 postMessage = (message) => {
51 this[message.event](message.payload);
52 }
53
54 pause = () => {
55 this.isSuspended = true;
56 }
57
58 resume = () => {
59 this.isSuspended = false;
60 this.process();
61 }
62
63 stop = () => {
64 this.onMessage({
65 data: {
66 payload: this.createFileObject(false),
67 event: 'cancelFileSender'
68 }
69 });
70 this.socket.emit(this.events.CANCEL_UPLOAD, this.file.id);
71 }
72
73 updateFileState = () => {
74 const now = Date.now();
75
76 if ((now - this.previousUpdateTime) >= this.throttle) {
77 this.previousUpdateTime = now;
78
79 this.onMessage({
80 data: {
81 payload: this.createFileObject(false),
82 event: 'onProgress'
83 }
84 });
85 }
86 }
87
88 closeFileSender = () => {
89 this.onMessage({
90 data: {
91 payload: this.createFileObject(true),
92 event: 'closeFileSender'
93 }
94 });
95 }
96
97 handleErrorMessage = (error) => {
98 this.onMessage({
99 data: {
100 payload: error,
101 event: 'onError'
102 }
103 });
104 }
105
106 createFileObject = (status) => {
107 return {
108 fileId: this.file.id,
109 name: this.file.data.name,
110 size: this.file.data.size,
111 passedBytes: this.end,
112 progress: this.progress,
113 currentChunk: this.offset,
114 type: this.file.data.type,
115 isFinal: status
116 };
117 }
118
119 process = () => {
120 const reader = new FileReaderSync();
121
122 this.start = this.offset * this.chunkSize;
123 this.end = Math.min(this.fileSize, (this.offset + 1) * this.chunkSize);
124
125 if (this.fileSize - this.end < this.chunkSize) {
126 this.end = this.fileSize;
127 }
128
129 const blob = this.slice(this.file.data, this.start, this.end);
130 const dataUrl = reader.readAsArrayBuffer(blob);
131 const final = this.offset === this.maxChunk;
132
133 const post = {
134 chunk: dataUrl,
135 fileId: this.file.id,
136 chunkNum: this.offset,
137 chunkSize: dataUrl.byteLength,
138 type: this.file.data.type,
139 name: this.file.data.name,
140 isFinal: final
141 };
142
143 this.socket.emit(this.events.SEND_NEXT_CHUNK, post);
144 this.previousSendTime = Date.now();
145 }
146
147 slice = (file, start, end) => {
148 const slice = file.mozSlice ||
149 file.webkitSlice ||
150 file.slice;
151
152 return slice.bind(file)(start, end);
153 }
154
155 openSocket = () => {
156 this.socket = io(this.params.url, { transports: ['websocket'] });
157
158 this.socket.on('connect', () => {
159 console.log('Socket ID: ' + this.socket.id + ' CONNECTED');
160
161 this.socket.emit(this.events.GET_LAST_CHUNK, JSON.stringify({id: this.file.id}));
162 });
163
164 this.socket.on(this.events.GET_LAST_CHUNK, (data) => {
165 this.offset = data;
166 this.updateFileState();
167 this.process();
168 });
169
170 this.socket.on(this.events.SEND_NEXT_CHUNK_SUCCESS, (event) => {
171 this.offset += 1;
172 this.progress = (this.offset / this.maxChunk) * 100;
173 this.updateFileState();
174
175 if (!this.isSuspended) {
176 this.process();
177 }
178 });
179
180 this.socket.on(this.events.SEND_FILE_SUCCESS, (event) => {
181 this.progress = this.offset > 0 ? (this.offset / this.maxChunk) * 100 : 100;
182 this.closeFileSender();
183 });
184
185 this.socket.on(this.events.SEND_CHUNK_AGAIN, () => {
186 this.process();
187 });
188
189 this.socket.on('disconnect', (reason) => {
190 if (reason === 'io server disconnect') {
191 console.log('Connection closed by server');
192 }
193
194 if (reason === 'transport close') {
195 if (this.errorCount <= this.maxConnectionAttempts) {
196 this.handleErrorMessage({
197 identifier: this.file.id,
198 error: {
199 message: 'disconnect',
200 reason: reason
201 }
202 });
203
204 console.log(this.errorCount + ' attempts - ' + 'Server Crashed');
205 this.errorCount += 1;
206 this.socket.open();
207 } else {
208 this.socket.disconnect(true);
209 console.log('Maximum reconnection attempts');
210 }
211 }
212
213 console.log('Connection closed, reason: ' + reason);
214 });
215
216 this.socket.on('connect_error', (reason) => {
217 console.log(reason);
218 this.handleErrorMessage({
219 identifier: this.file.id,
220 error: {
221 message: 'connect_error',
222 reason: ''
223 }
224 });
225
226 if (this.errorCount <= this.maxConnectionAttempts) {
227 console.log('Server is not responding or unavailable');
228 this.errorCount += 1;
229 } else {
230 this.socket.disconnect(true);
231 console.log('Maximum reconnection attempts');
232 }
233 });
234
235 this.socket.on('connect_failed', () => {
236 this.handleErrorMessage({
237 identifier: this.file.id,
238 error: {
239 message: 'connect_failed',
240 reason: ''
241 }
242 });
243
244 console.log('Connection Failed');
245 });
246
247 this.socket.on(this.events.ERROR, (error) => {
248 this.handleErrorMessage({
249 identifier: this.file.id,
250 error: error
251 });
252 });
253 }
254 }
255
256 class WorkersManager {
257 constructor() {
258 this.subWorkers = {};
259 this.DB = new DBManager();
260 this.params = null;
261 this.throttle = 1000;
262 this.previousTime = Date.now();
263 this.filesState = {};
264 this.bindEvents();
265 }
266
267 bindEvents = () => {
268 window.onmessage = this.onMessage;
269 }
270
271 postMessage = (data) => {
272 window.postMessage(data);
273 }
274
275 onMessage = (message) => {
276 this[message.data.event](message.data.payload);
277 }
278
279 initialize = () => {
280 this.DB.getStorage((rows) => {
281 const ids = rows.map((row) => row.id);
282
283 rows.forEach((row) => {
284 const id = row.id;
285
286 this.subWorkers;
287 this.subWorkers[id] = new SubWorker({onChange: this.onMessage, ...this.params}, row);
288 });
289
290 this.refreshUploadedFiles(ids);
291 });
292 }
293
294 refreshUploadedFiles = (data) => {
295 this.postMessage({payload: data, event: 'refreshUploadedFiles'});
296 }
297
298 setFiles = (payload) => {
299 const url = payload.url;
300
301 payload.files.forEach((file) => {
302 let params = {
303 onChange: this.onMessage,
304 ...this.params
305 };
306
307 if (url) {
308 params = {...params, url: url};
309 }
310
311 this.DB.setFile(file);
312 this.subWorkers[file.id] = new SubWorker(params, file);
313 });
314 }
315
316 pauseUpload = (fileId) => {
317 this.subWorkers[fileId].pause();
318 }
319
320 resumeUpload = (fileId) => {
321 this.subWorkers[fileId].resume();
322 }
323
324 stopUpload = (fileId) => {
325 this.subWorkers[fileId].stop();
326 }
327
328 deleteFile = (file) => {
329 this.DB.delFile(file);
330 }
331
332 setParams = (params) => {
333 this.params = params;
334 this.throttle = this.params.mainThrottle || this.throttle;
335 this.initialize();
336 }
337
338 onProgress = (data, force) => {
339 this.filesState[data.fileId] = data;
340 const filesArray = this.arrayFrom(this.filesState);
341 const now = Date.now();
342
343 if ((now - this.previousTime) >= this.throttle) {
344 this.previousTime = now;
345 this.postMessage({payload: filesArray, event: 'onProgress'});
346 }
347
348 if (force) {
349 this.postMessage({payload: filesArray, event: 'onProgress'});
350 }
351 }
352
353 onError = (error) => {
354 this.postMessage({payload: error, event: 'onError'});
355 }
356
357 closeFileSender = (data) => {
358 this.postMessage({payload: data, event: 'complete'});
359 this.onProgress(data, true);
360 this.deleteFile(data.fileId);
361
362 delete this.subWorkers[data.fileId];
363 }
364
365 cancelFileSender = (data) => {
366 delete this.filesState[data.fileId];
367 const filesArray = this.arrayFrom(this.filesState);
368
369 this.postMessage({payload: filesArray, event: 'onProgress'});
370 this.deleteFile(data.fileId);
371 delete this.subWorkers[data.fileId];
372 }
373
374 arrayFrom = (obj) => {
375 const array = [];
376
377 for (let key in obj) {
378 array.push(obj[key]);
379 }
380
381 return array;
382 }
383 }
384
385 const Manager = new WorkersManager();
386}
387
388export default worker;