-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from rambler-digital-solutions/url
feat(url): add url tools
- Loading branch information
Showing
8 changed files
with
307 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# URL tools | ||
|
||
Enhanced tools for URL conversion | ||
|
||
## Install | ||
|
||
``` | ||
npm install -D @rambler-tech/url | ||
``` | ||
|
||
or | ||
|
||
``` | ||
yarn add -D @rambler-tech/url | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { | ||
getUrlParams, | ||
getUrlHashParams, | ||
addUrlParams, | ||
removeUrlHash, | ||
removeUrlNullableParams, | ||
formatUrl, | ||
encodeURIComponentRFC3986 | ||
} from '.' | ||
|
||
test('getUrlParams: valid url', () => { | ||
const href = 'https://foo.bar?action=login&provider=provider' | ||
|
||
expect(getUrlParams(href)).toEqual({ | ||
action: 'login', | ||
provider: 'provider' | ||
}) | ||
}) | ||
|
||
test('getUrlParams: valid url with empty search params', () => { | ||
const href = 'https://foo.bar' | ||
|
||
expect(getUrlParams(href)).toEqual({}) | ||
}) | ||
|
||
test('getUrlHashParams: valid url', () => { | ||
const href = 'https://foo.bar#action=login&provider=provider' | ||
|
||
expect(getUrlHashParams(href)).toEqual({ | ||
action: 'login', | ||
provider: 'provider' | ||
}) | ||
}) | ||
|
||
test('getUrlHashParams: valid url with empty hash', () => { | ||
const href = 'https://foo.baz' | ||
|
||
expect(getUrlHashParams(href)).toEqual({}) | ||
}) | ||
|
||
test('addUrlParams with search params', () => { | ||
expect( | ||
addUrlParams('https://foo.bar/', { | ||
logged: false, | ||
action: 'login', | ||
provider: 'provider' | ||
}) | ||
).toEqual('https://foo.bar/?logged=false&action=login&provider=provider') | ||
}) | ||
|
||
test('addUrlParams with cleaned search params', () => { | ||
expect( | ||
addUrlParams('https://foo.bar/', { | ||
logged: false, | ||
action: 'login', | ||
provider: 'provider', | ||
test: undefined, | ||
foobar: null | ||
}) | ||
).toEqual('https://foo.bar/?logged=false&action=login&provider=provider') | ||
}) | ||
|
||
test('addUrlParams with hash', () => { | ||
expect( | ||
addUrlParams( | ||
'https://foo.bar/?test=test#bar=baz', | ||
{ | ||
logged: false, | ||
action: 'login', | ||
provider: 'provider' | ||
}, | ||
{foo: 'bar'} | ||
) | ||
).toEqual( | ||
'https://foo.bar/?test=test&logged=false&action=login&provider=provider#bar=baz&foo=bar' | ||
) | ||
}) | ||
|
||
test('removeUrlHash', () => { | ||
const href = 'https://foo.bar/baz/?foo=bar' | ||
|
||
expect(removeUrlHash(href)).toEqual(href) | ||
expect(removeUrlHash('https://foo.bar/baz/?foo=bar#foo=bar')).toEqual(href) | ||
}) | ||
|
||
test('should clean query', () => { | ||
expect( | ||
removeUrlNullableParams({test: undefined, foo: 'bar', bar: undefined}) | ||
).toEqual({ | ||
foo: 'bar' | ||
}) | ||
}) | ||
|
||
test('should format url', () => { | ||
expect( | ||
formatUrl({ | ||
pathname: '/login', | ||
query: { | ||
email: 'testbar.ru', | ||
rname: undefined, | ||
src: null, | ||
type: 'test', | ||
back: 'back.ru' | ||
} | ||
}) | ||
).toBe('/login?email=testbar.ru&type=test&back=back.ru') | ||
}) | ||
|
||
test('should format full url', () => { | ||
expect( | ||
formatUrl({ | ||
protocol: 'https:', | ||
host: 'somehost.com', | ||
pathname: '/login', | ||
query: { | ||
email: 'testbar.ru', | ||
rname: undefined, | ||
src: null, | ||
type: 'test', | ||
back: 'back.ru' | ||
} | ||
}) | ||
).toBe('https://somehost.com/login?email=testbar.ru&type=test&back=back.ru') | ||
}) | ||
|
||
test('should encode with RFC 3986 standard', () => { | ||
expect(encodeURIComponentRFC3986('my file(2).txt')).toBe( | ||
'my%20file%282%29.txt' | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* eslint-disable import/no-unused-modules */ | ||
|
||
export type ParsedQuery<T = string> = Record<string, T | T[] | null | undefined> | ||
|
||
export type Query = Record<string, any> | ||
|
||
interface UrlObject { | ||
protocol?: string | null | ||
host?: string | null | ||
pathname?: string | null | ||
query?: string | Query | null | ||
} | ||
|
||
/** Format URL to string */ | ||
export function formatUrl(urlObject: UrlObject): string { | ||
const {protocol, host, pathname, query} = urlObject | ||
|
||
let cleanedQueryString | ||
|
||
if (query && typeof query === 'object') { | ||
// NOTE: remove undefined or null values from query | ||
const cleanedQuery = removeUrlNullableParams(query) | ||
|
||
cleanedQueryString = new URLSearchParams(cleanedQuery).toString() | ||
} else { | ||
cleanedQueryString = query | ||
} | ||
|
||
return ( | ||
(protocol ? `${protocol}//` : '') + | ||
(host ?? '') + | ||
pathname + | ||
(cleanedQueryString ? `?${cleanedQueryString}` : '') | ||
) | ||
} | ||
|
||
/** Get URL query params */ | ||
export function getUrlParams(url: string): ParsedQuery { | ||
const searchParams: ParsedQuery = {} | ||
|
||
const {searchParams: iterableSearchParams} = new URL(url) | ||
|
||
iterableSearchParams.forEach((value, key) => { | ||
searchParams[key] = value | ||
}) | ||
|
||
return searchParams | ||
} | ||
|
||
/** Get URL hash params */ | ||
export function getUrlHashParams(url: string): ParsedQuery { | ||
const hashParams: ParsedQuery = {} | ||
|
||
const {hash} = new URL(url) | ||
const iterableHashParams = new URLSearchParams(hash.substring(1)) | ||
|
||
iterableHashParams.forEach((value, key) => { | ||
hashParams[key] = value | ||
}) | ||
|
||
return hashParams | ||
} | ||
|
||
/** Add URL query params */ | ||
export function addUrlParams( | ||
url: string, | ||
searchParams: Query, | ||
hashParams?: Query | ||
): string { | ||
if ( | ||
!Object.keys(searchParams).length && | ||
(!hashParams || !Object.keys(hashParams).length) | ||
) | ||
return url | ||
|
||
const {protocol, host, pathname} = new URL(url) | ||
|
||
const initialSearchParams = getUrlParams(url) | ||
const mergedSearchParams = removeUrlNullableParams({ | ||
...initialSearchParams, | ||
...searchParams | ||
}) | ||
|
||
const search = new URLSearchParams(mergedSearchParams).toString() | ||
const urlSearch = search ? `?${search}` : '' | ||
|
||
const initialHashParams = getUrlHashParams(url) | ||
const mergedHashParams = removeUrlNullableParams({ | ||
...initialHashParams, | ||
...hashParams | ||
}) | ||
|
||
const hash = new URLSearchParams(mergedHashParams).toString() | ||
const urlHash = hash ? `#${hash}` : '' | ||
|
||
return `${protocol}//${host}${pathname}${urlSearch}${urlHash}` | ||
} | ||
|
||
/** Remove URL nullable query params (`null` or `undefined`) */ | ||
export function removeUrlNullableParams<T extends Query>(query: T): T { | ||
const resultQuery: Query = {} | ||
|
||
Object.keys(query).forEach((key) => { | ||
if (query[key] != null) { | ||
resultQuery[key] = query[key] | ||
} | ||
}) | ||
|
||
return resultQuery as T | ||
} | ||
|
||
/** Remove URL hash */ | ||
export function removeUrlHash(url: string): string { | ||
const {protocol, host, pathname, search} = new URL(url) | ||
|
||
return `${protocol}//${host}${pathname}${search}` | ||
} | ||
|
||
/** | ||
* Encode URI for RFC3986 | ||
* | ||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent | ||
*/ | ||
export function encodeURIComponentRFC3986(value: string): string { | ||
return encodeURIComponent(value).replace( | ||
/[!'()*]/g, | ||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers | ||
(c) => `%${c.charCodeAt(0).toString(16)}` | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "@rambler-tech/url", | ||
"version": "0.0.0", | ||
"main": "dist", | ||
"module": "dist", | ||
"types": "dist/index.d.ts", | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"module": "commonjs", | ||
"baseUrl": ".", | ||
"outDir": "dist" | ||
}, | ||
"include": [".", "../../types"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"extends": "@rambler-tech/typedoc-config", | ||
"readme": "../../README.md", | ||
"entryPoints": ["./index.ts"] | ||
} |