// @flow import * as electron from 'electron'; import { Readable, Writable } from 'stream'; import fuzzysort from 'fuzzysort'; import uuid from 'uuid'; import zlib from 'zlib'; import { join as pathJoin } from 'path'; import { DEBOUNCE_MILLIS } from './constants'; const ESCAPE_REGEX_MATCH = /[-[\]/{}()*+?.\\^$|]/g; type Header = { name: string, value: string }; type Parameter = { name: string, value: string }; export function filterParameters( parameters: Array, name: string ): Array { if (!Array.isArray(parameters) || !name) { return []; } return parameters.filter(h => (!h || !h.name ? false : h.name === name)); } export function filterHeaders( headers: Array, name: string ): Array { if (!Array.isArray(headers) || !name) { return []; } return headers.filter(h => { if (!h || !h.name) { return false; } else { return h.name.toLowerCase() === name.toLowerCase(); } }); } export function hasContentTypeHeader(headers: Array): boolean { return filterHeaders(headers, 'content-type').length > 0; } export function hasContentLengthHeader(headers: Array): boolean { return filterHeaders(headers, 'content-length').length > 0; } export function hasAuthHeader(headers: Array): boolean { return filterHeaders(headers, 'authorization').length > 0; } export function hasAcceptHeader(headers: Array): boolean { return filterHeaders(headers, 'accept').length > 0; } export function hasUserAgentHeader(headers: Array): boolean { return filterHeaders(headers, 'user-agent').length > 0; } export function hasAcceptEncodingHeader(headers: Array): boolean { return filterHeaders(headers, 'accept-encoding').length > 0; } export function getSetCookieHeaders(headers: Array): Array { return filterHeaders(headers, 'set-cookie'); } export function getLocationHeader(headers: Array): T | null { const matches = filterHeaders(headers, 'location'); return matches.length ? matches[0] : null; } export function getContentTypeHeader(headers: Array): T | null { const matches = filterHeaders(headers, 'content-type'); return matches.length ? matches[0] : null; } export function getHostHeader(headers: Array): T | null { const matches = filterHeaders(headers, 'host'); return matches.length ? matches[0] : null; } export function getContentDispositionHeader( headers: Array ): T | null { const matches = filterHeaders(headers, 'content-disposition'); return matches.length ? matches[0] : null; } export function getContentLengthHeader(headers: Array): T | null { const matches = filterHeaders(headers, 'content-length'); return matches.length ? matches[0] : null; } /** * Generate an ID of the format "_" * @param prefix * @returns {string} */ export function generateId(prefix: string): string { const id = uuid.v4().replace(/-/g, ''); if (prefix) { return `${prefix}_${id}`; } else { return id; } } export function delay(milliseconds: number = DEBOUNCE_MILLIS): Promise { return new Promise(resolve => setTimeout(resolve, milliseconds)); } export function removeVowels(str: string): string { return str.replace(/[aeiouyAEIOUY]/g, ''); } export function keyedDebounce( callback: Function, millis: number = DEBOUNCE_MILLIS ): Function { let timeout; let results = {}; return function(key, ...args) { results[key] = args; clearTimeout(timeout); timeout = setTimeout(() => { if (!Object.keys(results).length) { return; } callback(results); results = {}; }, millis); }; } export function debounce( callback: Function, millis: number = DEBOUNCE_MILLIS ): Function { // For regular debounce, just use a keyed debounce with a fixed key return keyedDebounce(results => { callback.apply(null, results['__key__']); }, millis).bind(null, '__key__'); } export function describeByteSize(bytes: number, long: boolean = false): string { bytes = Math.round(bytes * 10) / 10; let size; // NOTE: We multiply these by 2 so we don't end up with // values like 0 GB let unit = long ? 'bytes' : 'B'; if (bytes < 1024 * 2) { size = bytes; unit = long ? 'bytes' : 'B'; } else if (bytes < 1024 * 1024 * 2) { size = bytes / 1024; unit = long ? 'kilobytes' : 'KB'; } else if (bytes < 1024 * 1024 * 1024 * 2) { size = bytes / 1024 / 1024; unit = long ? 'megabytes' : 'MB'; } else { size = bytes / 1024 / 1024 / 1024; unit = long ? 'gigabytes' : 'GB'; } const rounded = Math.round(size * 10) / 10; return `${rounded} ${unit}`; } export function nullFn(): void { // Do nothing } export function preventDefault(e: Event): void { e.preventDefault(); } export function clickLink(href: string): void { electron.shell.openExternal(href); } export function fnOrString(v: string | Function, ...args: Array) { if (typeof v === 'string') { return v; } else { return v(...args); } } export function compressObject(obj: any): string { const compressed = zlib.gzipSync(JSON.stringify(obj)); return compressed.toString('base64'); } export function decompressObject(input: string): any { const jsonBuffer = zlib.gunzipSync(Buffer.from(input, 'base64')); return JSON.parse(jsonBuffer.toString('utf8')); } export function resolveHomePath(p: string): string { if (p.indexOf('~/') === 0) { return pathJoin(process.env.HOME || '/', p.slice(1)); } else { return p; } } export function jsonParseOr(str: string, fallback: any): any { try { return JSON.parse(str); } catch (err) { return fallback; } } /** * Escape a dynamic string for use inside of a regular expression * @param str - string to escape * @returns {string} escaped string */ export function escapeRegex(str: string): string { return str.replace(ESCAPE_REGEX_MATCH, '\\$&'); } export function fuzzyMatch( searchString: string, text: string ): { searchTermsMatched: number, indexes: number[] } { const searchTerms = searchString.trim().split(' '); const emptyResults = { searchTermsMatched: 0, searchTermsCount: searchTerms.length, indexes: [] }; if ( !searchString || !searchString.trim() || !searchTerms || searchTerms.length === 0 ) { return emptyResults; } const results = searchTerms.reduce((prevResult, nextTerm) => { const nextResult = fuzzysort.single(nextTerm, text); if (!nextResult || nextResult.score < -8000) { return prevResult; } if (!prevResult) { return nextResult; } const sort = array => array.sort((a, b) => a - b); const uniq = array => Array.from(new Set(array)); return { ...prevResult, ...nextResult, searchTermsMatched: prevResult.searchTermsMatched + 1, indexes: sort(uniq([...prevResult.indexes, ...nextResult.indexes])) }; }, emptyResults); if (results.indexes.length === 0) { return emptyResults; } return results; } export function fuzzyMatchAll( searchString: string, allText: Array ): boolean { if (!searchString || !searchString.trim()) { return true; } return searchString .split(' ') .map(searchTerm => searchTerm.trim()) .filter(searchTerm => !!searchTerm) .every(searchTerm => allText.some(text => fuzzyMatch(searchTerm, text).searchTermsMatched > 0) ); } export function getViewportSize(): string | null { const { BrowserWindow } = electron.remote || electron; const w = BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0]; if (w) { const { width, height } = w.getContentBounds(); return `${width}x${height}`; } else { // No windows open return null; } } export function getScreenResolution(): string { const { screen } = electron.remote || electron; const { width, height } = screen.getPrimaryDisplay().workAreaSize; return `${width}x${height}`; } export function getUserLanguage(): string { const { app } = electron.remote || electron; return app.getLocale(); } export async function waitForStreamToFinish( s: Readable | Writable ): Promise { return new Promise(resolve => { if ((s: any)._readableState && (s: any)._readableState.finished) { return resolve(); } if ((s: any)._writableState && (s: any)._writableState.finished) { return resolve(); } s.on('close', () => { resolve(); }); s.on('error', () => { resolve(); }); }); }