import { CONSTANTS } from '../constants';
/**
* Service for downloading files in browser
* @export
* @class
*/
export class DownloadService {
/**
* Constructor of DownloadService
* @constructor
*/
constructor() {
this.mimeType = '';
this.toString = a => String(a);
this.fileName = '';
this.reader = new FileReader();
}
/**
* Download file
* @param data {string|Object} - Downloaded data
* @param strFileName {string} - Name of downloaded file
* @param strMimeType {string} - Mime type of downloaded file
* @return {Promise<void>} - Promise resolved after downloading file
* @async
*/
download(data, strFileName, strMimeType) {
this.fileName = strFileName || CONSTANTS.DEFAULT_FILE_NAME;
this.mimeType = strMimeType;
this.payload = data;
let MyBlob = (window.Blob || window['MozBlob'] || window['WebKitBlob'] || this.toString);
MyBlob = MyBlob.call ? MyBlob.bind(window) : Blob ;
if (this._isPayloadBase64()) {
if (this._isBigBase64(MyBlob)) {
this._convertBase64ToBlob(MyBlob);
} else {
return this._saveBase64(MyBlob);
}
}
return this._saveBlob(MyBlob);
}
/**
* Converting big base64 string to blob
* @param MyBlob - Copy of Blob class for current browser
* @private
*/
_convertBase64ToBlob(MyBlob) {
this.payload = this._dataUrlToBlob(this.payload, MyBlob);
this.mimeType = this.payload.type || CONSTANTS.DEFAULT_TYPE;
}
/**
* Download data in base64 format
* @param MyBlob - Copy of Blob class for current browser
* @private
*/
_saveBase64(MyBlob) {
return new Promise((resolve) => {
if (navigator.msSaveBlob) {
navigator.msSaveBlob(this._dataUrlToBlob(this.payload, MyBlob), this.fileName);
} else {
this._save(this.payload);
}
resolve();
});
}
/**
* Download data in Blob format
* @param MyBlob - Copy of Blob class for current browser
* @private
*/
_saveBlob(MyBlob) {
const blob = this.payload instanceof MyBlob ?
this.payload :
new MyBlob([ this.payload ], { type: this.mimeType });
if (navigator.msSaveBlob) {
return navigator.msSaveBlob(blob, this.fileName);
}
if (window.URL) {
return this._save(window.URL.createObjectURL(blob), true);
} else {
if (typeof blob === 'string' || blob.constructor === this.toString ) {
return this._saveBigBase64(blob);
}
return this._convertByFileReader(blob)
.then(base64 => this._save(base64));
}
}
/**
* Download file in Blob (String) format
* @param blob - Instance of Blob for current browser
* @private
*/
_saveBigBase64(blob) {
try {
return this._save('data:' + this.mimeType + ';base64,' + window.btoa(blob));
} catch(y) {
return this._save('data:' + this.mimeType + ',' + encodeURIComponent(blob));
}
}
/**
* Download converted data
* @param url - URL of converted downloaded data
* @param winMode - Indicator showing data has been converted by window.URL
* @private
*/
_save(url, winMode) {
const anchor = document.createElement('a');
if ('download' in anchor) {
return this._saveByAnchor(anchor, url, winMode);
}
if (this._isSafari()) {
return this._saveByLocationForSafari(url);
}
this._saveByIframe(url, winMode);
}
/**
* Finish downloading by anchor
* @param anchor - Created link for downloading
* @param url URL - of converted downloaded data
* @param winMode - Indicator showing data has been converted by window.URL
* @private
*/
_saveByAnchor(anchor, url, winMode) {
return new Promise((resolve) => {
anchor.href = url;
anchor.setAttribute('download', this.fileName);
anchor.style.display = 'none';
document.body.appendChild(anchor);
setTimeout(() => {
anchor.click();
document.body.removeChild(anchor);
if (winMode) setTimeout(() => window.URL.revokeObjectURL(anchor.href), 250);
resolve();
}, 66);
});
}
/**
* Finish downloading by window location (for Safari)
* @param url - URL of converted downloaded data
* @private
*/
_saveByLocationForSafari(url) {
const urlWithoutBase64 = url.replace(CONSTANTS.BASE_64_REGEX, CONSTANTS.DEFAULT_TYPE);
if (!window.open(urlWithoutBase64)) {
if (window.confirm(CONSTANTS.CONFIRM_TEXT)) {
window.location.href = urlWithoutBase64;
}
}
return true;
}
/**
* Finish downloading by iframe
* @param url - URL of converted downloaded data
* @param winMode - Indicator showing data has been converted by window.URL
* @private
*/
_saveByIframe(url, winMode) {
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.src = winMode ? url : 'data:' + url.replace(CONSTANTS.BASE_64_REGEX, CONSTANTS.DEFAULT_TYPE);
setTimeout(() => document.body.removeChild(iframe), 333);
}
/**
* Convert blob to base64 using FileReader
* @param blob - Instance of Blob for current browser
* @private
*/
_convertByFileReader(blob) {
return new Promise((resolve) => {
this.reader.onload = () => this.reader.result && resolve(this.reader.result);
this.reader.readAsDataURL(blob);
});
}
/**
* Convert URL string to blob
* @param strUrl - URL of downloaded data
* @param MyBlob - Copy of Blob class for current browser
* @private
*/
_dataUrlToBlob(strUrl, MyBlob) {
const parts = strUrl.split(/[:;,]/);
const type = parts[1];
const decoder = parts[2] === 'base64' ? atob : decodeURIComponent;
const binData = decoder(parts.pop() || '');
const mx = binData.length;
const uiArr = new Uint8Array(mx);
let index = 0;
for (index; index < mx; ++index) uiArr[index] = binData.charCodeAt(index);
return new MyBlob([uiArr], {type});
}
/**
* Check that downloaded data has base64 format
* @return {boolean}
* @private
*/
_isPayloadBase64() {
return CONSTANTS.FULL_BASE_64_REGEX.test(this.payload);
}
/**
* Check that downloaded base64 is to big
* @return {boolean}
* @private
*/
_isBigBase64(MyBlob) {
return this.payload.length > CONSTANTS.MAX_PAYLOAD_LENGTH && MyBlob !== this.toString;
}
/**
* Check that current browser is Safari
* @return {boolean}
* @private
*/
_isSafari() {
return CONSTANTS.SAFARI_REGEX.test(navigator.userAgent);
}
}