UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

259 lines (223 loc) • 8.88 kB
import { Assert } from '../misc/Assert'; import { Utils } from '../utils/Utils'; import * as _ from 'underscore'; import { Logger } from '../MiscModules'; export class HashUtils { private static DELIMITER = { objectStart: '{', objectEnd: '}', arrayStart: '[', arrayEnd: ']', arrayStartRegExp: /^\[/, arrayEndRegExp: /\]$/ }; public static getHash(w: Window = window): string { Assert.exists(w); // window.location.hash returns the DECODED hash on Firefox (it's a well known bug), // so any & in values will be already unescaped. This breaks our value splitting. // The following trick works on all browsers. const ret = '#' + (w.location.href.split('#')[1] || ''); return HashUtils.getAjaxcrawlableHash(ret); } public static getValue(key: string, toParse: string): any { Assert.isNonEmptyString(key); Assert.exists(toParse); toParse = HashUtils.getAjaxcrawlableHash(toParse); let paramValue = HashUtils.getRawValue(key, toParse); if (paramValue != undefined) { paramValue = HashUtils.getValueDependingOnType(key, paramValue); } return paramValue; } public static encodeValues(values: {}): string { const hash: String[] = []; _.each(<_.Dictionary<any>>values, (valueToEncode, key, obj?) => { let encodedValue = ''; if (Utils.isNonEmptyArray(valueToEncode)) { encodedValue = HashUtils.encodeArray(valueToEncode); } else if (_.isObject(valueToEncode) && Utils.isNonEmptyArray(_.keys(valueToEncode))) { encodedValue = HashUtils.encodeObject(valueToEncode); } else if (!Utils.isNullOrUndefined(valueToEncode)) { encodedValue = Utils.safeEncodeURIComponent(valueToEncode.toString()); } if (encodedValue != '') { hash.push(key + '=' + encodedValue); } }); return hash.join('&'); } private static getAjaxcrawlableHash(hash: string) { if (hash[1] != undefined && hash[1] == '!') { return hash.substring(0, 1) + hash.substring(2); } else { return hash; } } private static getRawValue(key: string, toParse: string): string { Assert.exists(key); Assert.exists(toParse); Assert.check(toParse.indexOf('#') == 0 || toParse == ''); const toParseArray = toParse.substr(1).split('&'); let paramPos = 0; let loop = true; let paramValue: string = undefined; while (loop) { const paramValuePair = toParseArray[paramPos].split('='); if (paramValuePair[0] == key) { loop = false; paramValue = paramValuePair[1]; } else { paramPos++; if (paramPos >= toParseArray.length) { paramPos = undefined; loop = false; } } } return paramValue; } private static getValueDependingOnType(key: string, paramValue: string): any { const type = HashUtils.getValueType(key, paramValue); let returnValue; if (type == 'object') { returnValue = HashUtils.decodeObject(paramValue); } else if (type == 'array') { returnValue = HashUtils.decodeArray(paramValue); } else { try { returnValue = decodeURIComponent(paramValue); } catch (e) { new Logger(HashUtils).warn('Error while decoding a value from the URL as a standard value', e, key, paramValue); } } return returnValue; } private static getValueType(key: string, paramValue: string): string { if (key == 'q') { return 'other'; } else if (HashUtils.isObject(paramValue)) { return 'object'; } else if (HashUtils.startsOrEndsWithSquareBracket(paramValue)) { return 'array'; } else { return 'other'; } } private static startsWithLeftSquareBracket(value: string) { return HashUtils.DELIMITER.arrayStartRegExp.test(value); } private static startsWithEncodedLeftSquareBracket(value: string) { return value.indexOf(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.arrayStart)) == 0; } private static endsWithRightSquareBracket(value: string) { return HashUtils.DELIMITER.arrayEndRegExp.test(value); } private static endsWithEncodedRightSquareBracket(value: string) { const encodedBracket = Utils.safeEncodeURIComponent(HashUtils.DELIMITER.arrayEnd); const encodedBracketIndex = value.indexOf(encodedBracket); const hasEncodedBracket = encodedBracketIndex !== -1; return hasEncodedBracket && encodedBracketIndex == value.length - encodedBracket.length; } private static isObjectStartNotEncoded(value: string) { return value.substr(0, 1) == HashUtils.DELIMITER.objectStart; } private static isObjectStartEncoded(value: string) { return value.indexOf(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.objectStart)) == 0; } private static isObjectEndNotEncoded(value: string) { return value.substr(value.length - 1) == HashUtils.DELIMITER.objectEnd; } private static isObjectEndEncoded(value: string) { return ( value.indexOf(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.objectEnd)) == value.length - Utils.safeEncodeURIComponent(HashUtils.DELIMITER.objectEnd).length ); } private static isObject(value: string) { const isObjectStart = HashUtils.isObjectStartNotEncoded(value) || HashUtils.isObjectStartEncoded(value); const isObjectEnd = HashUtils.isObjectEndNotEncoded(value) || HashUtils.isObjectEndEncoded(value); return isObjectStart && isObjectEnd; } private static startsOrEndsWithSquareBracket(value: string) { const isArrayStart = HashUtils.startsWithLeftSquareBracket(value) || HashUtils.startsWithEncodedLeftSquareBracket(value); const isArrayEnd = HashUtils.endsWithRightSquareBracket(value) || HashUtils.endsWithEncodedRightSquareBracket(value); return isArrayStart || isArrayEnd; } public static encodeArray(array: string[]): string { const arrayReturn = _.map(array, value => { return Utils.safeEncodeURIComponent(value); }); return HashUtils.DELIMITER.arrayStart + arrayReturn.join(',') + HashUtils.DELIMITER.arrayEnd; } public static encodeObject(obj: Object): string { const retArray = _.map(<_.Dictionary<any>>obj, (val, key?, obj?) => { return `"${Utils.safeEncodeURIComponent(key)}":${this.encodeValue(val)}`; }); return HashUtils.DELIMITER.objectStart + retArray.join(' , ') + HashUtils.DELIMITER.objectEnd; } private static encodeValue(val: any) { let encodedValue = ''; if (_.isArray(val)) { encodedValue = HashUtils.encodeArray(val); } else if (_.isObject(val)) { encodedValue = JSON.stringify(val); } else if (_.isNumber(val) || _.isBoolean(val)) { encodedValue = Utils.safeEncodeURIComponent(val.toString()); } else { encodedValue = '"' + Utils.safeEncodeURIComponent(val) + '"'; } return encodedValue; } private static decodeObject(obj: string): Object { if (HashUtils.isObjectStartEncoded(obj) && HashUtils.isObjectEndEncoded(obj)) { obj = obj.replace(/encodeURIComponent(HashUtils.Delimiter.objectStart)/, HashUtils.DELIMITER.objectStart); obj = obj.replace(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.objectEnd), HashUtils.DELIMITER.objectEnd); } try { const containsArray = /(\[.*\])/.exec(obj); if (containsArray) { obj = obj.replace( /(\[.*\])/, `[${this.decodeArray(containsArray[1]) .map(val => `"${val}"`) .join(',')}]` ); } const decoded = decodeURIComponent(obj); return JSON.parse(decoded); } catch (e) { new Logger(HashUtils).warn('Error while decoding a value from the URL as an object', e, obj); return {}; } } private static decodeArray(value: string): any[] { const valueWithoutSquareBrackets = HashUtils.removeSquareBrackets(value); const array = valueWithoutSquareBrackets.split(','); return _.chain(array) .map(val => { try { return decodeURIComponent(val); } catch (e) { new Logger(HashUtils).warn('Error while decoding a value from the URL as an array', e, val, value); return null; } }) .compact() .value(); } private static removeSquareBrackets(value: string) { if (HashUtils.startsWithEncodedLeftSquareBracket(value)) { value = value.replace(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.arrayStart), ''); } if (HashUtils.endsWithEncodedRightSquareBracket(value)) { value = value.replace(Utils.safeEncodeURIComponent(HashUtils.DELIMITER.arrayEnd), ''); } if (HashUtils.startsWithLeftSquareBracket(value)) { value = value.replace(HashUtils.DELIMITER.arrayStart, ''); } if (HashUtils.endsWithRightSquareBracket(value)) { value = value.replace(HashUtils.DELIMITER.arrayEnd, ''); } return value; } }