158 lines
4.0 KiB
TypeScript
158 lines
4.0 KiB
TypeScript
/* eslint-disable class-methods-use-this */
|
|
/* eslint-disable no-param-reassign */
|
|
/* eslint-disable max-len */
|
|
/* eslint-disable no-plusplus */
|
|
export class URLSearchParams {
|
|
private dict: { [key: string]: string[] } = {}
|
|
|
|
constructor(search: string | string[] | any | URLSearchParams = '') {
|
|
if (search instanceof URLSearchParams) {
|
|
this.dict = this.parseToDict(search.toString())
|
|
} else {
|
|
this.dict = this.parseToDict(search)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends a specified key/value pair as a new search parameter
|
|
*/
|
|
public append(name: string, value: string): void {
|
|
this.appendTo(this.dict, name, value)
|
|
}
|
|
|
|
/**
|
|
* Deletes this give search parameter, and its associated value, from the list of all search parameters
|
|
*/
|
|
public delete(name: string): void {
|
|
delete this.dict[name]
|
|
}
|
|
|
|
/**
|
|
* Returns the first value associated to the given search parameter
|
|
*/
|
|
get(name: string): string | null {
|
|
return name in this.dict ? this.dict[name][0] : null
|
|
}
|
|
|
|
/**
|
|
* Returns all the values association with a given parameter
|
|
*/
|
|
getAll(name: string): string[] {
|
|
return name in this.dict ? this.dict[name].slice(0) : []
|
|
}
|
|
|
|
/**
|
|
* Test if the search parameter exists
|
|
*/
|
|
has(name: string): boolean {
|
|
return name in this.dict
|
|
}
|
|
|
|
/**
|
|
* Sets the value associated to a given search parameter to
|
|
* the given value. If there were several values, delete the others.
|
|
*/
|
|
set(name: string, value: string): void {
|
|
this.dict[name] = [value]
|
|
}
|
|
|
|
/**
|
|
* Returns a string containing a query string suitable for use in a URL
|
|
*/
|
|
toString(): string {
|
|
return Object.entries(this.dict)
|
|
.map(([key, value]) => `${key}=${this.encode(value)}`)
|
|
.join('&')
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
parseToDict(search: string | string[] | any): any {
|
|
const dict = {}
|
|
|
|
if (typeof search === 'object') {
|
|
// if 'search' is an array, treat it as a sequence
|
|
if (Array.isArray(search)) {
|
|
for (let i = 0; i < search.length; i++) {
|
|
const item = search[i]
|
|
if (Array.isArray(item) && item.length === 2) {
|
|
this.appendTo(dict, item[0], item[1])
|
|
} else {
|
|
throw new TypeError(
|
|
"Failed to construct 'URLSearchParams': Sequence initalizer must only contain pair elements"
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
Object.entries(search).forEach(([key, value]) =>
|
|
this.appendTo(dict, key, value)
|
|
)
|
|
}
|
|
} else {
|
|
// remove 1st ?
|
|
if (search.indexOf('?') === 0) {
|
|
search = search.slice(1)
|
|
}
|
|
|
|
const pairs = search.split('&')
|
|
for (let j = 0; j < pairs.length; j++) {
|
|
const value = pairs[j]
|
|
const index = value.indexOf('=')
|
|
if (index > -1) {
|
|
this.appendTo(
|
|
dict,
|
|
this.decode(value.slice(0, index)),
|
|
this.decode(value.slice(index + 1))
|
|
)
|
|
} else if (value) {
|
|
this.appendTo(dict, this.decode(value), '')
|
|
}
|
|
}
|
|
}
|
|
|
|
return dict
|
|
}
|
|
|
|
appendTo(dict: any, name: string, value: string | (() => void) | any): void {
|
|
// eslint-disable-next-line no-nested-ternary
|
|
const val =
|
|
typeof value === 'string'
|
|
? value
|
|
: value !== null &&
|
|
value !== undefined &&
|
|
typeof value.toString === 'function'
|
|
? value.toString()
|
|
: JSON.stringify(value)
|
|
|
|
if (name in dict) {
|
|
dict[name].push(value)
|
|
} else {
|
|
dict[name] = [val]
|
|
}
|
|
}
|
|
|
|
decode(str: string): string {
|
|
return str
|
|
.replace(/[ +]/g, '%20')
|
|
.replace(/(%[a-f0-9]{2})+/gi, (match) => decodeURIComponent(match))
|
|
}
|
|
|
|
encode(str: string[]): string {
|
|
const replace: { [key: string]: string } = {
|
|
'!': '%21',
|
|
"'": '%27',
|
|
'(': '%28',
|
|
')': '%29',
|
|
'~': '%7E',
|
|
'%20': '+',
|
|
'%00': '\x00',
|
|
}
|
|
return encodeURIComponent(str.join(',')).replace(
|
|
// eslint-disable-next-line no-useless-escape
|
|
/[!'\(\)~]|%20|%00/g,
|
|
(match) => replace[match] || ''
|
|
)
|
|
}
|
|
}
|