filestack-js
Version:
Official JavaScript library for Filestack
268 lines (223 loc) • 6.44 kB
text/typescript
/*
* Copyright (c) 2018 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 { Session } from '../client';
import { Hosts } from './../../config';
import { ExtensionsMap } from './extensions';
import { fromBuffer } from 'file-type';
import isutf8 from 'isutf8';
/**
* Resolve cdn url based on handle type
*
* @private
* @param session session object
* @param handle file handle (hash, src://alias, url)
*/
export const resolveCdnUrl = (session: Session, handle: string): string => {
const cdnURL = session.urls.cdnUrl;
if (handle && (handle.indexOf('src:') === 0 || handle.indexOf('http') === 0)) {
if (!session.apikey) {
throw new Error('Api key is required when storage alias is provided');
}
// apikey is required for alias or external sources call
return `${cdnURL}/${session.apikey}`;
}
return cdnURL;
};
/**
* Resolve all urls with provided cnames
*
* @private
* @param urls
* @param cname
*/
export const resolveHost = (urls: Hosts, cname: string): Hosts => {
if (!cname) {
return urls;
}
const hosts = /filestackapi.com|filestackcontent.com/i;
Object.keys(urls).forEach(key => {
urls[key] = urls[key].replace(hosts, cname);
});
return urls;
};
/**
* Removes empty options from object
*
* @private
* @param obj
*/
export const removeEmpty = (obj: any) => {
const newObj = { ...obj };
Object.keys(newObj).forEach(k => !newObj[k] && typeof newObj[k] !== 'boolean' && delete newObj[k]);
return newObj;
};
/**
* Returns unique time
*/
let last;
export const uniqueTime = () => {
const time = Date.now();
last = time === last ? time + 1 : time;
return last;
};
/**
* Generates random string with provided length
*
* @param len
*/
export const uniqueId = (len: number = 10): string => {
return new Array(len).join().replace(/(.|$)/g, () => ((Math.random() * 36) | 0).toString(36)[Math.random() < 0.5 ? 'toString' : 'toUpperCase']());
};
/**
* Check if input is a svg
*
* @param {Uint8Array | Buffer} file
* @returns {string} - mimetype
*/
export const getMimetype = async (file: Uint8Array | Buffer, name?: string): Promise<string> => {
let type;
try {
type = await fromBuffer(file);
} catch (e) {
console.warn('An exception occurred while processing the buffer:', e.message);
}
const excludedMimetypes = ['text/plain', 'application/octet-stream', 'application/x-ms', 'application/x-msi', 'application/zip', 'audio/x-m4a'];
if (type && excludedMimetypes.indexOf(type.mime) === -1) {
return type.mime;
}
if (name && name.indexOf('.') > -1) {
const mime = extensionToMime(name);
if (mime) {
return mime;
}
}
try {
if (isutf8(file)) {
return 'text/plain';
}
} catch (e) {
/* istanbul ignore next */
console.warn('Additional mimetype checks (text/plain) are currently not supported for browsers');
}
// this is only fallback, omit it in coverage
/* istanbul ignore next */
// if we cant find types by extensions and we have magic bytes fallback to it
if (type) {
return type.mime;
}
return 'application/octet-stream';
};
/**
* Change extension to according mimetype using ext=>mimetype map
*
* @param ext - string
* @return string|boolean
*/
export const extensionToMime = (ext: string) => {
if (!ext || ext.length === 0) {
return;
}
if (ext.split('/').length === 2) {
return ext;
}
if (ext.indexOf('.') > -1) {
ext = ext.split('.').pop();
}
ext = ext.toLocaleLowerCase();
const keys = Object.keys(ExtensionsMap);
const mapLen = keys.length;
for (let i = 0; i < mapLen; i++) {
if (ExtensionsMap[keys[i]].indexOf(ext) > -1) {
return keys[i];
}
}
return;
};
/**
* Sanitizer Options
*/
export type SanitizeOptions =
| boolean
| {
exclude?: string[];
replacement?: string;
};
/**
* Sanitize file name
*
* @param name
* @param {bool} options - enable,disable sanitizer, default enabled
* @param {string} options.replacement - replacement for sanitized chars defaults to "-"
* @param {string[]} options.exclude - array with excluded chars default - `['\', '{', '}','|', '%', '`', '"', "'", '~', '[', ']', '#', '|', '^', '<', '>']`
*/
export const sanitizeName = (name: string, options: SanitizeOptions = true): string => {
if (typeof options === 'boolean' && !options) {
return name;
}
let ext;
const replacement = typeof options !== 'boolean' && options.replacement ? options.replacement : '-';
const exclude = typeof options !== 'boolean' && options.exclude ? options.exclude : ['\\', '{', '}', '|', '%', '`', '"', "'", '~', '[', ']', '#', '|', '^', '<', '>'];
if (!name || name.length === 0) {
return 'undefined';
}
const fileParts = name.split('.');
if (fileParts.length > 1) {
ext = fileParts.pop();
}
return `${fileParts
.join('.')
.split('')
.map(char => (exclude.indexOf(char) > -1 ? replacement : char))
.join('')}${ext ? '.' + ext : ''}`;
};
/**
* Filter object to given fields
*
* @param toFilter
* @param requiredFields
*/
export const filterObject = (toFilter, requiredFields: string[]) => {
if (!requiredFields || requiredFields.length === 0) {
return toFilter;
}
if (Object.keys(toFilter).length === 0) {
return toFilter;
}
return Object.keys(toFilter)
.filter(f => requiredFields.indexOf(f) > -1)
.reduce((obj, key) => ({ ...obj, [key]: toFilter[key] }), {});
};
/**
* Deep cleanup object from functions
*
* @param obj
*/
export const cleanUpCallbacks = (obj: any) => {
if (!obj || Object.keys(obj).length === 0) {
return obj;
}
Object.keys(obj).forEach(k => {
if (typeof obj[k] === 'function') {
obj[k] = undefined;
}
if (obj[k] === Object(obj[k])) {
obj[k] = cleanUpCallbacks(obj[k]);
}
});
return obj;
};
export * from './index.node';