diff --git a/package.json b/package.json index 845bdce..6100878 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "url-normalize", - "version": "1.1.1", + "version": "1.2.0", "author": "Vlad Pronsky ", "repository": "vladkens/url-normalize", "description": "Normalize URLs to a standardized form by removing unnecessary characters, decoding encoded characters, removing default index pages, removing trailing slashes, and more.", @@ -26,6 +26,7 @@ "main": "dist/main.cjs", "exports": "dist/main.mjs" }, + "types": "./dist/main.d.ts", "exports": { "types": "./dist/main.d.ts", "import": "./dist/main.mjs", diff --git a/readme.md b/readme.md index 2a9ef43..3574c02 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ - + license @@ -66,9 +66,25 @@ urlNormalize("https://example.com", { defaultProtocol: "http" }) // -> "https://example.com" ``` +#### keepProtocol `(default: true)` + +```typescript +urlNormalize("https://example.com") +// -> "https://example.com" + +urlNormalize("https://example.com", { keepProtocol: false }) +// -> "example.com" + +urlNormalize("https://example.com/foo?bar=baz", { keepProtocol: false }) +// -> "example.com/foo?bar=baz" +``` + #### keepWWW `(default: false)` ```typescript +urlNormalize("www.example.com") +// -> "https://example.com" + urlNormalize("www.example.com", { keepWWW: true }) // -> "https://www.example.com" ``` @@ -163,6 +179,16 @@ urlNormalize("tg://example.com", { allowCustomProtocol: true }) // -> "tg://example.com" ``` +#### forceProtocol + +```typescript +urlNormalize("https://example.com", { forceProtocol: "sftp" }) +// -> "sftp://example.com" + +urlNormalize("tg://example.com", { forceProtocol: "we" }) +// -> "we://example.com" +``` + ### Advanced #### urlNormalizeOrFail diff --git a/src/main.ts b/src/main.ts index 54cc2df..c67fe32 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,10 +1,12 @@ export type Options = { allowCustomProtocol?: boolean defaultProtocol?: string + forceProtocol?: string keepAuth?: boolean keepDirectoryIndex?: boolean keepHash?: boolean keepPort?: boolean // default port always removed + keepProtocol?: boolean keepQueryParams?: boolean keepTextFragment?: boolean keepWWW?: boolean @@ -18,6 +20,7 @@ const DefaultOptions: Options = { keepDirectoryIndex: true, keepHash: true, keepPort: true, + keepProtocol: true, keepQueryParams: true, keepTextFragment: false, keepWWW: false, @@ -46,7 +49,8 @@ export const urlNormalizeOrFail = (url: string, options?: Options): string => { if ( !options.allowCustomProtocol && - !["http:", "https:", `${options.defaultProtocol}:`].includes(obj.protocol) + !["http:", "https:", `${options.defaultProtocol}:`].includes(obj.protocol) && + !options.forceProtocol // URL in nodejs not allow to change protocol, so do it on the end ) { throw new Error("Invalid protocol") } @@ -105,12 +109,24 @@ export const urlNormalizeOrFail = (url: string, options?: Options): string => { let res = obj.toString() if (res.endsWith("/") && !res.endsWith("/#/")) res = res.slice(0, -1) + let protocol = obj.protocol + if (options.forceProtocol) { + const regex = new RegExp(`^${obj.protocol}//`, "i") + res = res.replace(regex, `${options.forceProtocol}://`) + protocol = `${options.forceProtocol}:` + } + + if (!options.keepProtocol) { + const regex = new RegExp(`^${protocol}//`, "i") + res = res.replace(regex, "") + } + return res } -export const urlNormalize = (url: string, options?: Options): string | null => { +export const urlNormalize = (url?: string | null, options?: Options): string | null => { try { - return urlNormalizeOrFail(url, options) + return urlNormalizeOrFail(url ?? "", options) } catch { return null } @@ -122,9 +138,9 @@ export const extractDomainOrFail = (url: string): string => { return obj.hostname } -export const extractDomain = (url: string): string | null => { +export const extractDomain = (url?: string | null): string | null => { try { - return extractDomainOrFail(url) + return extractDomainOrFail(url ?? "") } catch { return null } diff --git a/tests/main.test.ts b/tests/main.test.ts index b000cae..b0fe2a8 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -143,6 +143,23 @@ test("should allow custom options", () => { t(`abc://www.example.com/foo/bar`, null, { allowCustomProtocol: false }) t(`abc://www.example.com/foo/bar`, null, { allowCustomProtocol: false }) + // keep protocol + t("https://example.com", "https://example.com", { keepProtocol: true }) + t("https://example.com", "example.com", { keepProtocol: false }) + t("https://example.com/abc", "example.com/abc", { keepProtocol: false }) + t("https://example.com:80/abc", "example.com:80/abc", { keepProtocol: false }) + t("https://example.com/foo?bar=baz", "example.com/foo?bar=baz", { keepProtocol: false }) + t("abc://example.com:80/abc", "example.com:80/abc", { keepProtocol: false, allowCustomProtocol: true }) + t("abc://example.com:80/abc", null, { keepProtocol: false, allowCustomProtocol: false }) + + // force protocol + t("https://example.com", "http://example.com", { forceProtocol: "http" }) + t("https://example.com", "https://example.com", { forceProtocol: "https" }) + t("https://example.com", "abc://example.com", { forceProtocol: "abc" }) + t("https://example.com", "example.com", { forceProtocol: "abc", keepProtocol: false }) + t("https://example.com", "sftp://example.com", { forceProtocol: "sftp" }) + t("tg://example.com", "we://example.com", { forceProtocol: "we" }) + // keep www t("www.example.com", "https://www.example.com", { keepWWW: true }) t("www.example.com", "https://example.com", { keepWWW: false })