UNPKG

15 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports["default"] = void 0;
9
10var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
12var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2"));
13
14var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
15
16var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
17
18var _tusJsClient = _interopRequireDefault(require("tus-js-client"));
19
20var _resolveUrl = _interopRequireDefault(require("@availity/resolve-url"));
21
22// https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript/8831937#8831937
23var hashCode = function hashCode(str) {
24 var hash = 0;
25 if (str.length === 0) return hash;
26
27 for (var i = 0; i < str.length; i++) {
28 var _char = str.charCodeAt(i);
29
30 hash = (hash << 5) - hash + _char; // eslint-disable-line no-bitwise
31 // eslint-disable-next-line operator-assignment
32
33 hash = hash & hash; // eslint-disable-line no-bitwise
34 }
35
36 return hash;
37};
38
39var defaultOptions = {
40 endpoint: '/ms/api/availity/internal/core/vault/upload/v1/resumable',
41 chunkSize: 3e6,
42 // 3MB
43 removeFingerprintOnSuccess: true,
44 pollingTime: 5000,
45 retryDelays: [0, 1000, 3000, 5000],
46 stripFileNamePathSegments: true,
47 fingerprint: function fingerprint(file) {
48 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
49 var callback = arguments.length > 2 ? arguments[2] : undefined;
50 var attributes = [file.name, file.type, file.size, file.lastModified];
51 var attributesKey = 'tus-';
52
53 for (var i = 0; i < attributes.length; i++) {
54 if (attributes[i]) {
55 attributesKey += "".concat(attributes[i], "-");
56 }
57 }
58
59 var keys = Object.keys(options.metadata || {}).map(function (key) {
60 return options.metadata[key];
61 });
62 var signature = [attributes.toString().replace(/,/g, ''), options.endpoint, keys].join('');
63 var print = Math.abs(hashCode(signature));
64
65 if (callback) {
66 return callback(null, "".concat(attributesKey).concat(print));
67 }
68
69 return "".concat(attributesKey).concat(print);
70 }
71};
72
73var Upload = /*#__PURE__*/function () {
74 function Upload(file, options) {
75 var _this = this;
76
77 (0, _classCallCheck2["default"])(this, Upload);
78
79 if (!file) {
80 throw new Error('[options.file] must be defined and of type File(s)');
81 }
82
83 if (!options || !options.bucketId) {
84 throw new Error('[options.bucketId] must be defined');
85 }
86
87 if (!options.customerId) {
88 throw new Error('[options.customerId] must be defined');
89 }
90
91 if (!options.clientId) {
92 throw new Error('[options.clientId] must be defined');
93 }
94
95 this.file = file;
96 this.options = (0, _objectSpread2["default"])({}, defaultOptions, {}, options);
97 this.options.endpoint = (0, _resolveUrl["default"])({
98 relative: this.options.endpoint
99 });
100 this.percentage = 0;
101 this.onError = [];
102 this.onSuccess = [];
103 this.onProgress = [];
104 this.bytesTotal = 0;
105 this.bytesSent = 0;
106 this.bytesScanned = 0;
107 this.errorMessage = null;
108 this.status = 'pending';
109 this.timeoutID = undefined;
110 this.error = null;
111 this.waitForPassword = true;
112 var fileName = this.trimFileName(file.name);
113 var metadata = {
114 'availity-filename': fileName,
115 'availity-content-type': file.type,
116 'availity-attachment-name': 'N/A'
117 };
118 Object.assign(metadata, this.options.metadata);
119 var upload = new _tusJsClient["default"].Upload(this.file, {
120 resume: true,
121 endpoint: "".concat(this.options.endpoint, "/").concat(this.options.bucketId, "/"),
122 chunkSize: this.options.chunkSize,
123 retryDelays: this.options.retryDelays,
124 removeFingerprintOnSuccess: this.options.removeFingerprintOnSuccess,
125 fingerprint: this.options.fingerprint,
126 metadata: metadata,
127 headers: {
128 'X-XSRF-TOKEN': this.getToken(),
129 'X-Availity-Customer-ID': this.options.customerId,
130 'X-Client-ID': this.options.clientId
131 },
132 onError: function onError(err) {
133 _this.setError('rejected', 'Network Error', err);
134
135 _this.error = err;
136 },
137 onProgress: function onProgress(bytesSent, bytesTotal) {
138 _this.bytesSent = bytesSent;
139 _this.bytesTotal = bytesTotal;
140 _this.percentage = _this.getPercentage();
141
142 _this.onProgress.forEach(function (cb) {
143 return cb();
144 });
145 },
146 onSuccess: function onSuccess() {
147 var xhr = _this.upload._xhr;
148 _this.bytesScanned = parseInt(xhr.getResponseHeader('AV-Scan-Bytes'), 10) || 0;
149 _this.percentage = _this.getPercentage();
150
151 var result = _this.getResult(xhr);
152
153 if (result.status === 'accepted') {
154 _this.percentage = 100;
155 _this.status = result.status;
156 _this.errorMessage = null;
157 var references = xhr.getResponseHeader('references');
158
159 if (references) {
160 _this.references = JSON.parse(references);
161 }
162
163 _this.onSuccess.forEach(function (cb) {
164 return cb();
165 });
166
167 return;
168 }
169
170 if (result.status === 'rejected') {
171 _this.setError(result.status, result.message);
172
173 return;
174 }
175
176 _this.scan();
177 }
178 });
179 this.upload = upload;
180 this.id = this.generateId();
181 }
182
183 (0, _createClass2["default"])(Upload, [{
184 key: "inStatusCategory",
185 value: function inStatusCategory(status, category) {
186 return status >= category && status < category + 100;
187 }
188 }, {
189 key: "scan",
190 value: function scan(data) {
191 var _this2 = this;
192
193 clearTimeout(this.timeoutID);
194 var xhr = new window.XMLHttpRequest();
195 xhr.open('HEAD', this.upload.url, true);
196 xhr.setRequestHeader('Tus-Resumable', '1.0.0');
197 xhr.setRequestHeader('X-Client-ID', this.options.clientId);
198 xhr.setRequestHeader('X-Availity-Customer-ID', this.options.customerId);
199 xhr.setRequestHeader('X-XSRF-TOKEN', this.getToken());
200
201 if (data) {
202 xhr.setRequestHeader(data.header, data.value);
203 } // eslint-disable-next-line unicorn/prefer-add-event-listener
204
205
206 xhr.onload = function () {
207 if (!_this2.inStatusCategory(xhr.status, 200)) {
208 _this2.setError('rejected', "Invalid status returned: ".concat(xhr.status), xhr);
209
210 return;
211 }
212
213 _this2.bytesScanned = parseInt(xhr.getResponseHeader('AV-Scan-Bytes'), 10);
214 _this2.percentage = _this2.getPercentage();
215
216 var result = _this2.getResult(xhr);
217
218 if (result.status === 'rejected') {
219 _this2.setError(result.status, result.message);
220
221 return;
222 }
223
224 if (result.status === 'encrypted') {
225 if (_this2.waitForPassword) {
226 _this2.setError(result.status, result.message);
227
228 clearTimeout(_this2.timeoutId);
229 return;
230 }
231 }
232
233 if (result.status === 'accepted') {
234 _this2.percentage = 100;
235 _this2.status = result.status;
236 _this2.errorMessage = null;
237 var references = xhr.getResponseHeader('references');
238
239 if (references) {
240 _this2.references = JSON.parse(references);
241 }
242
243 _this2.onSuccess.forEach(function (cb) {
244 return cb();
245 });
246
247 return;
248 }
249
250 if (result.status === 'decrypting') {
251 _this2.setError(result.status, result.message);
252 }
253
254 _this2.onProgress.forEach(function (cb) {
255 return cb();
256 });
257
258 _this2.timeoutId = setTimeout(function () {
259 _this2.scan();
260 }, _this2.options.pollingTime);
261 }; // eslint-disable-next-line unicorn/prefer-add-event-listener
262
263
264 xhr.onerror = function (err) {
265 _this2.setError('rejected', 'Network Error', err);
266
267 _this2.error = err;
268 };
269
270 xhr.send(null);
271 }
272 }, {
273 key: "getPercentage",
274 value: function getPercentage() {
275 var processedBytes = this.bytesSent + this.bytesScanned;
276 var combinedTotalBytes = this.bytesTotal * 2;
277 return processedBytes / combinedTotalBytes * 100;
278 }
279 }, {
280 key: "getToken",
281 value: function getToken() {
282 return document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*=\s*([^;]*).*$)|^.*$/, '$1');
283 }
284 }, {
285 key: "start",
286 value: function start() {
287 if (!this.isValidFile()) {
288 return;
289 }
290
291 this.upload.start();
292 }
293 }, {
294 key: "generateId",
295 value: function generateId() {
296 var fingerprint = this.options.fingerprint;
297 return fingerprint(this.file, this.options).replace(/[^a-zA-Z0-9-]/g, '');
298 }
299 }, {
300 key: "fingerprint",
301 value: function fingerprint(file) {
302 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
303 var callback = arguments.length > 2 ? arguments[2] : undefined;
304 var attributes = [file.name, file.type, file.size, file.lastModified];
305 var attributesKey = 'tus-';
306
307 for (var i = 0; i < attributes.length; i++) {
308 if (attributes[i]) {
309 attributesKey += "".concat(attributes[i], "-");
310 }
311 }
312
313 var keys = Object.keys(options.metadata || {}).map(function (key) {
314 return options.metadata[key];
315 });
316 var signature = [attributes.toString().replace(/,/g, ''), options.endpoint, keys].join('');
317 var print = Math.abs(hashCode(signature));
318
319 if (callback) {
320 return callback(null, "".concat(attributesKey).concat(print));
321 }
322
323 return "".concat(attributesKey).concat(print);
324 }
325 }, {
326 key: "sendPassword",
327 value: function sendPassword(pw) {
328 this.waitForPassword = false;
329 this.scan({
330 header: 'Encryption-Password',
331 value: pw
332 });
333 }
334 }, {
335 key: "isValidSize",
336 value: function isValidSize() {
337 if (this.options.maxSize) {
338 if (this.file.size > this.options.maxSize) {
339 this.setError('rejected', 'Document is too large');
340 return false;
341 }
342 }
343
344 return true;
345 }
346 }, {
347 key: "isAllowedFileTypes",
348 value: function isAllowedFileTypes() {
349 if (this.options.fileTypes) {
350 if (!this.file.name) {
351 return false;
352 }
353
354 var fileName = this.file.name;
355 var fileExt = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
356
357 for (var i = 0; i < this.options.fileTypes.length; i++) {
358 this.options.fileTypes[i] = this.options.fileTypes[i].toLowerCase();
359 }
360
361 if (this.options.fileTypes.indexOf(fileExt) < 0) {
362 this.setError('rejected', "Document type ".concat(fileExt, " is not allowed"));
363 return false;
364 }
365 }
366
367 return true;
368 }
369 }, {
370 key: "isAllowedFileNameCharacters",
371 value: function isAllowedFileNameCharacters() {
372 if (this.options.allowedFileNameCharacters) {
373 var fileName = this.file.name.substring(0, this.file.name.lastIndexOf('.'));
374 var regExp = new RegExp("([^".concat(this.options.allowedFileNameCharacters, "])"), 'g');
375
376 if (fileName && fileName.match(regExp) !== null) {
377 this.setError('rejected', 'File name contains characters not allowed');
378 return false;
379 }
380 }
381
382 return true;
383 }
384 }, {
385 key: "isValidFile",
386 value: function isValidFile() {
387 return this.isAllowedFileNameCharacters() && this.isAllowedFileTypes() && this.isValidSize();
388 }
389 }, {
390 key: "trimFileName",
391 value: function trimFileName(fileName) {
392 if (this.options.stripFileNamePathSegments) {
393 fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
394 fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
395 }
396
397 return fileName;
398 }
399 }, {
400 key: "getResult",
401 value: function getResult(xhr) {
402 var scanResult = xhr.getResponseHeader('AV-Scan-Result');
403 var uploadResult = xhr.getResponseHeader('Upload-Result');
404 var decryptResult = xhr.getResponseHeader('Decryption-Result');
405 var msg = xhr.getResponseHeader('Upload-Message');
406
407 if (scanResult === 'rejected') {
408 return {
409 status: scanResult,
410 message: msg || 'Failed AV scan'
411 };
412 }
413
414 if (uploadResult === 'rejected') {
415 this.waitForPassword = true;
416
417 if (decryptResult === 'rejected') {
418 return {
419 status: uploadResult,
420 message: msg || 'Maximum password attempts reached'
421 };
422 }
423
424 return {
425 status: uploadResult,
426 message: msg || 'File upload rejected'
427 };
428 }
429
430 if (uploadResult === 'encrypted') {
431 // needs pw, isDecrypting, isScanning
432 if (!this.waitForPassword && (decryptResult === null || decryptResult === 'pending')) {
433 return {
434 status: 'decrypting',
435 message: msg || 'Decrypting file'
436 };
437 }
438
439 if (decryptResult === 'rejected') {
440 this.waitForPassword = true;
441 return {
442 status: uploadResult,
443 message: msg || 'Incorrect password'
444 };
445 }
446
447 return {
448 status: uploadResult,
449 message: msg || 'Encrypted files require a password'
450 };
451 }
452
453 if (scanResult === 'accepted' && uploadResult === 'accepted') {
454 return {
455 status: 'accepted',
456 message: msg || ''
457 };
458 }
459
460 return {
461 status: 'pending',
462 message: msg || ''
463 };
464 }
465 }, {
466 key: "setError",
467 value: function setError(status, message, err) {
468 var _this3 = this;
469
470 this.status = status;
471
472 try {
473 this.parseErrorMessage(message, err);
474 } catch (error) {
475 /* the error callback should always be called */
476 }
477
478 this.onError.forEach(function (cb) {
479 return cb(err || new Error(_this3.errorMessage));
480 });
481 }
482 }, {
483 key: "parseErrorMessage",
484 value: function parseErrorMessage(message, err) {
485 if (err) {
486 var msg = err.originalRequest.getResponseHeader('Upload-Message');
487
488 if (!msg) {
489 var temp = err.message.match(/response\Wtext:\W(.*)\)/);
490
491 if (temp && temp.length === 2) {
492 var _temp = (0, _slicedToArray2["default"])(temp, 2);
493
494 msg = _temp[1];
495 }
496 }
497
498 if (!msg) {
499 msg = message;
500 }
501
502 this.errorMessage = msg;
503 } else {
504 this.errorMessage = message;
505 }
506 }
507 }, {
508 key: "abort",
509 value: function abort() {
510 if (this.upload) {
511 this.upload.abort();
512 }
513 }
514 }]);
515 return Upload;
516}();
517
518var _default = Upload;
519exports["default"] = _default;
\No newline at end of file