/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type { SerializableArray, SerializableObject } from '../types'; import stringify from './stringify'; const KEY_DENY_LIST = [ '__proto__', 'constructor', 'prototype', ]; const sanitizeKey = (key: string): string => ( KEY_DENY_LIST.includes(key.toLowerCase()) ? key.toUpperCase() : key ); /** * Appends the parameters to the given url. * The base url can contain existing query parameters. * @param {String} url The base url. * @param {Object} parameters The parameters to add. * @return {String} A new valid url with the parameters appended. */ export function appendQueryParameters(url: string, parameters: SerializableObject): string { let hash = ''; let newUrl = url; const indexOf = url.indexOf('#'); if (indexOf !== -1) { hash = url.substring(indexOf); newUrl = url.substring(0, indexOf); } return newUrl + (newUrl.indexOf('?') === -1 ? '?' : '&') + stringify(parameters) + hash; } function isStringOfNumbers(str: string): boolean { return str.split('').every((char: string) => char.charCodeAt(0) >= 48 && char.charCodeAt(0) <= 57); } /** * @description Parse the query parameters from the search (window.location.search) part of * a URL. This will allow parsing a full JSON tree, however the limitation being all ultimate * values will be strings as types can not be accurately inferred from parasing a query-string. * * @param {string} search The search string, typically from window.location.search * @return {Object} A map of the query parameters */ export function parseQueryString( search?: string, ): T { let queryString = search; const params: SerializableObject = {}; if (!queryString) { return params as T; } if (queryString.indexOf('?') === 0) { queryString = queryString.substring(1); } const pairs = queryString.split('&'); const pairsLength = pairs.length; for (let iPair = 0; iPair < pairsLength; iPair += 1) { const multiLevelKeyRegex = /\[?([^\][]+)\]?/g; const keyVal = pairs[iPair].split('='); const key = decodeURIComponent(keyVal[0]); const value = decodeURIComponent(keyVal[1]); if (!multiLevelKeyRegex.test(key)) { params[sanitizeKey(key)] = value; } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any let parentRef: any = params; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const keys = key.match(multiLevelKeyRegex)!.map((keyRes) => keyRes.replace(/[[\]]+/g, '')); keys.forEach((subKey, i) => { const sanitizedSubKey = sanitizeKey(subKey); const isLastKey = i === (keys.length - 1); const isCurrentKeyValueArray = isLastKey ? false : isStringOfNumbers(keys[i + 1]); if (!isLastKey) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access parentRef[sanitizedSubKey] = parentRef[sanitizedSubKey] || ((isCurrentKeyValueArray) ? new Array() : ({} as SerializableObject)); } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access parentRef[sanitizedSubKey] = value; } if (!isLastKey) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access parentRef = parentRef[sanitizedSubKey]; } }); } } return params as T; }