filestack-js
Version:
Official JavaScript library for Filestack
198 lines (173 loc) • 4.92 kB
text/typescript
/*
* Copyright (c) 2019 by Filestack.
* Some rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { File as FsFile } from './file';
import { SanitizeOptions, getMimetype } from './../../utils';
import { FilestackError } from './../../../filestack_error';
export type RawFile = Blob | Buffer | File | string;
export type NamedInputFile = {
name?: string;
file: RawFile;
};
type InputFile = RawFile | NamedInputFile;
const base64Regexp = /data:([a-zA-Z]*\/[a-zA-Z]*);base64,([^\"]*)/i;
/**
* Check if file is blob
* @param input
*/
const isFileBlob = (input: InputFile): input is Blob => input.toString() === '[object Blob]';
/**
* Check if input is instance of browser file
*
* @browser
* @param input
*/
const isFileBrowser = (input: InputFile): input is File => input instanceof File;
/**
* Check if file is base64 string
*
* @param input
*/
const isFileBase = (input: InputFile): input is string => {
if (typeof input !== 'string') {
return false;
}
if (input.indexOf('base64') > -1) {
input = input.match(base64Regexp).pop();
}
try {
return btoa(atob(input)) === input;
} catch (err) {
/* istanbul ignore next */
return false;
}
};
/**
* Check if file is instance of named interface
*
* @param input
*/
const isFileNamed = (input: InputFile): input is NamedInputFile => input && input['file'] && input['name'];
/**
* Convert encoded base64 string or dataURI to blob
*
* @browser
* @param b64data String to decode
* @param sliceSize Byte quantity to split data into
* @private
* @returns {Blob}
*/
const b64toBlob = (b64Data: string, sliceSize = 512): Blob => {
let contentType = '';
if (b64Data.indexOf('base64') > -1) {
const matches = b64Data.match(base64Regexp);
b64Data = matches.pop();
contentType = matches[1];
}
const byteCharacters = atob(b64Data);
const byteArrays: any[] = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i += 1) {
byteNumbers[i] = slice.charCodeAt(i);
}
byteArrays.push(new Uint8Array(byteNumbers));
}
return new Blob(byteArrays, { type: contentType });
};
/**
* Read file as array buffer
*
* @browser
* @private
* @param blob
* @returns {Boolean}
*/
const readFile = async (file): Promise<any> => {
/* istanbul ignore next */
if (!File || !FileReader || !Blob) {
return Promise.reject(new FilestackError('The File APIs are not fully supported by your browser'));
}
return Promise.resolve({
slice: function(start: number, end: number) {
return readPart(start, end, file);
},
release: () => {
file = null;
},
});
};
/**
* Read file par instead of whole file to avoid browser crashing
*
* @param start - star byte
* @param end - end byte
* @param file - file to slice
*/
const readPart = (start: number, end: number, file): Promise<any> => {
return new Promise((resolve, reject) => {
const r = new FileReader();
const blob = file.slice(start, end);
r.onload = () => resolve(r.result);
r.onerror = reject;
r.readAsArrayBuffer(blob);
});
};
/**
* Accepts b64string or blob file
*
* @browser
* @param {*} fileOrString
* @returns {Promise<File>}
*/
export const getFile = (input: InputFile, sanitizeOptions?: SanitizeOptions): Promise<FsFile> => {
let filename;
let file: Blob;
if (isFileNamed(input)) {
filename = input.name;
input = input.file;
}
if (isFileBrowser(input)) {
file = input;
filename = input.name;
} else if (isFileBase(input)) {
file = b64toBlob(input);
} else if (isFileBlob(input)) {
file = input;
} else {
return Promise.reject(new FilestackError('Unsupported input file type'));
}
return readFile(file).then(
async res => {
let mime = file.type;
let minimumBytes = 4100;
if (!file.type || file.type.length === 0 || file.type === 'text/plain') {
mime = await getMimetype(await res.slice(0, minimumBytes), filename);
}
return new FsFile(
{
name: filename,
size: file.size,
type: mime,
slice: res.slice,
release: res.release,
},
sanitizeOptions
);
}
);
};