trousse 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm version](https://img.shields.io/npm/v/trousse.svg)](https://www.npmjs.com/package/trousse)
5
5
  [![license](https://img.shields.io/npm/l/trousse.svg)](https://github.com/macieklamberski/trousse/blob/main/LICENSE)
6
6
 
7
- Personal toolbox of shared TypeScript utilities used across my projects: type guards, coercions, matching, array, object and locale interpolation helpers.
7
+ Personal toolbox of shared TypeScript utilities used across my projects: type guards, coercions, matching, URL, array, object and locale interpolation helpers.
8
8
 
9
9
  ## Installation
10
10
 
@@ -0,0 +1,12 @@
1
+ //#region src/files.d.ts
2
+ export declare const audioExtensions: string[];
3
+ export declare const videoExtensions: string[];
4
+ export declare const imageExtensions: string[];
5
+ export declare const documentExtensions: string[];
6
+ export declare const archiveExtensions: string[];
7
+ export declare const installerExtensions: string[];
8
+ export declare const flashExtensions: string[];
9
+ export declare const codeExtensions: string[];
10
+ export declare const fontExtensions: string[];
11
+ export declare const subtitleExtensions: string[];
12
+ //#endregion
package/dist/files.js ADDED
@@ -0,0 +1,101 @@
1
+ //#region src/files.ts
2
+ const audioExtensions = [
3
+ "aac",
4
+ "aif",
5
+ "aiff",
6
+ "amr",
7
+ "flac",
8
+ "m4a",
9
+ "m4b",
10
+ "mid",
11
+ "midi",
12
+ "mka",
13
+ "mp3",
14
+ "oga",
15
+ "ogg",
16
+ "opus",
17
+ "wav",
18
+ "weba",
19
+ "wma"
20
+ ];
21
+ const videoExtensions = [
22
+ "3gp",
23
+ "avi",
24
+ "flv",
25
+ "m4v",
26
+ "mkv",
27
+ "mov",
28
+ "mp4",
29
+ "mpeg",
30
+ "mpg",
31
+ "ogv",
32
+ "webm",
33
+ "wmv"
34
+ ];
35
+ const imageExtensions = [
36
+ "avif",
37
+ "bmp",
38
+ "gif",
39
+ "heic",
40
+ "heif",
41
+ "ico",
42
+ "jpeg",
43
+ "jpg",
44
+ "jxl",
45
+ "png",
46
+ "svg",
47
+ "tif",
48
+ "tiff",
49
+ "webp"
50
+ ];
51
+ const documentExtensions = [
52
+ "doc",
53
+ "docx",
54
+ "epub",
55
+ "mobi",
56
+ "odp",
57
+ "ods",
58
+ "odt",
59
+ "pdf",
60
+ "ppt",
61
+ "pptx",
62
+ "rtf",
63
+ "xls",
64
+ "xlsx"
65
+ ];
66
+ const archiveExtensions = [
67
+ "7z",
68
+ "bz2",
69
+ "gz",
70
+ "rar",
71
+ "tar",
72
+ "tgz",
73
+ "xz",
74
+ "zip"
75
+ ];
76
+ const installerExtensions = [
77
+ "apk",
78
+ "deb",
79
+ "dmg",
80
+ "exe",
81
+ "iso",
82
+ "msi",
83
+ "pkg",
84
+ "rpm"
85
+ ];
86
+ const flashExtensions = ["swf"];
87
+ const codeExtensions = [
88
+ "css",
89
+ "js",
90
+ "mjs"
91
+ ];
92
+ const fontExtensions = [
93
+ "eot",
94
+ "otf",
95
+ "ttf",
96
+ "woff",
97
+ "woff2"
98
+ ];
99
+ const subtitleExtensions = ["srt", "vtt"];
100
+ //#endregion
101
+ export { archiveExtensions, audioExtensions, codeExtensions, documentExtensions, flashExtensions, fontExtensions, imageExtensions, installerExtensions, subtitleExtensions, videoExtensions };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { omitEmpty } from "./arrays.js";
2
- import { AnyOf, DeepOmit, IsPlainObject, MaybePromise, Nullish, PartialNullish, Pattern } from "./types.js";
2
+ import { AnyOf, DeepOmit, IsPlainObject, MaybePromise, NormalizeOptions, Nullish, PartialNullish, Pattern } from "./types.js";
3
3
  import { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString } from "./coercions.js";
4
+ import { archiveExtensions, audioExtensions, codeExtensions, documentExtensions, flashExtensions, fontExtensions, imageExtensions, installerExtensions, subtitleExtensions, videoExtensions } from "./files.js";
4
5
  import { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate } from "./is.js";
5
6
  import { isJsonLike, isParseableJson } from "./json.js";
6
7
  import { escapeHtml, t, tx } from "./locales.js";
7
8
  import { anyWordMatchesAnyOf, endsWithAnyOf, escapeRegex, getAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf } from "./matching.js";
8
9
  import { omit, pick, toMap, trimObject } from "./objects.js";
9
10
  import { sleep } from "./timers.js";
10
- import { decodeSegment, getPathSegments, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isSubdomainOf, parseUrl } from "./urls.js";
11
- export { type AnyOf, type DeepOmit, type IsPlainObject, type MaybePromise, type Nullish, type PartialNullish, type Pattern, anyWordMatchesAnyOf, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, decodeSegment, endsWithAnyOf, escapeHtml, escapeRegex, getAnyOf, getPathSegments, getSubdomain, includesAnyOf, isAnyOf, isBoolean, isFunction, isHostOf, isHostOrSubdomainOf, isHttpUrl, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, omit, omitEmpty, parseUrl, pick, sleep, startsWithAnyOf, t, toMap, trimObject, tx };
11
+ import { addMissingProtocol, decodeSegment, fixMalformedProtocol, getPathSegments, getRegistrableDomain, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isSubdomainOf, normalizeUrl, parseUrl, resolveFeedProtocol, resolveUrl, stripWww, upgradeProtocol } from "./urls.js";
12
+ export { type AnyOf, type DeepOmit, type IsPlainObject, type MaybePromise, type NormalizeOptions, type Nullish, type PartialNullish, type Pattern, addMissingProtocol, anyWordMatchesAnyOf, archiveExtensions, audioExtensions, codeExtensions, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, decodeSegment, documentExtensions, endsWithAnyOf, escapeHtml, escapeRegex, fixMalformedProtocol, flashExtensions, fontExtensions, getAnyOf, getPathSegments, getRegistrableDomain, getSubdomain, imageExtensions, includesAnyOf, installerExtensions, isAnyOf, isBoolean, isFunction, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, normalizeUrl, omit, omitEmpty, parseUrl, pick, resolveFeedProtocol, resolveUrl, sleep, startsWithAnyOf, stripWww, subtitleExtensions, t, toMap, trimObject, tx, upgradeProtocol, videoExtensions };
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { isBoolean, isFunction, isNonEmptyString, isNullish, isNumber, isObject, isPlainObject, isPresent, isString, isValidDate } from "./is.js";
2
2
  import { omitEmpty } from "./arrays.js";
3
3
  import { coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString } from "./coercions.js";
4
+ import { archiveExtensions, audioExtensions, codeExtensions, documentExtensions, flashExtensions, fontExtensions, imageExtensions, installerExtensions, subtitleExtensions, videoExtensions } from "./files.js";
4
5
  import { isJsonLike, isParseableJson } from "./json.js";
5
6
  import { escapeHtml, t, tx } from "./locales.js";
6
7
  import { anyWordMatchesAnyOf, endsWithAnyOf, escapeRegex, getAnyOf, includesAnyOf, isAnyOf, startsWithAnyOf } from "./matching.js";
7
8
  import { omit, pick, toMap, trimObject } from "./objects.js";
8
9
  import { sleep } from "./timers.js";
9
- import { decodeSegment, getPathSegments, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isSubdomainOf, parseUrl } from "./urls.js";
10
- export { anyWordMatchesAnyOf, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, decodeSegment, endsWithAnyOf, escapeHtml, escapeRegex, getAnyOf, getPathSegments, getSubdomain, includesAnyOf, isAnyOf, isBoolean, isFunction, isHostOf, isHostOrSubdomainOf, isHttpUrl, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, omit, omitEmpty, parseUrl, pick, sleep, startsWithAnyOf, t, toMap, trimObject, tx };
10
+ import { addMissingProtocol, decodeSegment, fixMalformedProtocol, getPathSegments, getRegistrableDomain, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isSubdomainOf, normalizeUrl, parseUrl, resolveFeedProtocol, resolveUrl, stripWww, upgradeProtocol } from "./urls.js";
11
+ export { addMissingProtocol, anyWordMatchesAnyOf, archiveExtensions, audioExtensions, codeExtensions, coerceArray, coerceBoolean, coerceDate, coerceNumber, coerceSingular, coerceString, decodeSegment, documentExtensions, endsWithAnyOf, escapeHtml, escapeRegex, fixMalformedProtocol, flashExtensions, fontExtensions, getAnyOf, getPathSegments, getRegistrableDomain, getSubdomain, imageExtensions, includesAnyOf, installerExtensions, isAnyOf, isBoolean, isFunction, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isJsonLike, isNonEmptyString, isNullish, isNumber, isObject, isParseableJson, isPlainObject, isPresent, isString, isSubdomainOf, isValidDate, normalizeUrl, omit, omitEmpty, parseUrl, pick, resolveFeedProtocol, resolveUrl, sleep, startsWithAnyOf, stripWww, subtitleExtensions, t, toMap, trimObject, tx, upgradeProtocol, videoExtensions };
package/dist/types.d.ts CHANGED
@@ -6,4 +6,20 @@ export type Nullish<T> = T | null | undefined;
6
6
  export type PartialNullish<T> = { [Key in keyof T]?: Nullish<T[Key]>; };
7
7
  export type MaybePromise<T> = T | Promise<T>;
8
8
  export type Pattern = string | RegExp;
9
+ export type NormalizeOptions = {
10
+ stripProtocol?: boolean;
11
+ stripAuthentication?: boolean;
12
+ stripWww?: boolean;
13
+ stripTrailingSlash?: boolean;
14
+ stripRootSlash?: boolean;
15
+ collapseSlashes?: boolean;
16
+ stripHash?: boolean;
17
+ sortQueryParams?: boolean;
18
+ stripQueryParams?: Array<string>;
19
+ stripQuery?: boolean;
20
+ stripEmptyQuery?: boolean;
21
+ lowercaseQuery?: boolean;
22
+ normalizeEncoding?: boolean;
23
+ normalizeUnicode?: boolean;
24
+ };
9
25
  //#endregion
package/dist/urls.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { NormalizeOptions } from "./types.js";
1
2
  //#region src/urls.d.ts
2
3
  export declare const parseUrl: (value: string | URL, base?: string | URL) => URL | undefined;
3
4
  export declare const getPathSegments: (value: string | URL) => Array<string>;
@@ -7,4 +8,13 @@ export declare const isHttpUrl: (url: string | URL) => boolean;
7
8
  export declare const isHostOrSubdomainOf: (url: string | URL, domains: string | ReadonlyArray<string>) => boolean;
8
9
  export declare const getSubdomain: (url: string | URL, domains: string | ReadonlyArray<string>) => string | undefined;
9
10
  export declare const decodeSegment: (segment: string | undefined) => string | undefined;
11
+ export declare const fixMalformedProtocol: (url: string) => string;
12
+ export declare const resolveFeedProtocol: (url: string, protocol?: 'http' | 'https') => string;
13
+ export declare const addMissingProtocol: (url: string, protocol?: 'http' | 'https') => string;
14
+ export declare const upgradeProtocol: (url: string, protocol?: 'http' | 'https') => string;
15
+ export declare const resolveUrl: (url: string, base?: string) => string | undefined;
16
+ export declare const normalizeUrl: (url: string, options: NormalizeOptions) => string;
17
+ export declare const stripWww: (hostname: string) => string;
18
+ export declare const isIpAddress: (hostname: string) => boolean;
19
+ export declare const getRegistrableDomain: (url: string | URL) => string | undefined;
10
20
  //#endregion
package/dist/urls.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { endsWithAnyOf, isAnyOf } from "./matching.js";
2
+ import { decodeHTMLStrict } from "entities";
2
3
  //#region src/urls.ts
3
4
  const httpProtocols = ["http:", "https:"];
4
5
  const parseUrl = (value, base) => {
@@ -43,5 +44,218 @@ const decodeSegment = (segment) => {
43
44
  return decodeURIComponent(segment);
44
45
  } catch {}
45
46
  };
47
+ const strippedParamsCache = /* @__PURE__ */ new WeakMap();
48
+ const getStrippedParamsSet = (params) => {
49
+ let cached = strippedParamsCache.get(params);
50
+ if (!cached) {
51
+ cached = new Set(params.map((param) => param.toLowerCase()));
52
+ strippedParamsCache.set(params, cached);
53
+ }
54
+ return cached;
55
+ };
56
+ const ipv4Regex = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
57
+ const ipv6Regex = /^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i;
58
+ const wwwPrefixRegex = /^www\./;
59
+ const ipv6BracketsRegex = /^\[|\]$/g;
60
+ const safePathCharsRegex = /[a-zA-Z0-9._~!$&'()*+,;=:@-]/;
61
+ const httpsLetterRegex = /s/i;
62
+ const protocolPrefixRegex = /^https?:\/\//;
63
+ const percentEscapeOrLettersRegex = /%[0-9A-Fa-f]{2}|[A-Z]+/g;
64
+ const plusRegex = /\+/g;
65
+ const httpProtocolRegex = /^http:\/\//i;
66
+ const httpsProtocolRegex = /^https:\/\//i;
67
+ const leadingWhitespaceChars = [
68
+ " ",
69
+ " ",
70
+ "\n"
71
+ ];
72
+ const validUrlRegex = /^https?:\/\/(?:www\.|[a-vx-z0-9])/i;
73
+ const schemeSeparator = String.raw`\.*[:\s=\\/][:\s=.\\/]*`;
74
+ const doubledProtocolRegex = new RegExp(String.raw`^\/?[htps]{2,7}${schemeSeparator}([htps]{2,7})${schemeSeparator}[.,:/]*(www[./]+)?`, "i");
75
+ const singleMalformedRegex = new RegExp(String.raw`^\/?(?:h[htps():]{1,10}|t{1,2}ps?)${schemeSeparator}[.,:/]*(www[./]+)?`, "i");
76
+ const fixMalformedProtocol = (url) => {
77
+ if (validUrlRegex.test(url) && !doubledProtocolRegex.test(url)) return url;
78
+ const doubledMatch = doubledProtocolRegex.exec(url);
79
+ if (doubledMatch) {
80
+ const inner = doubledMatch[1];
81
+ const www = doubledMatch[2];
82
+ const rest = url.slice(doubledMatch[0].length);
83
+ return (httpsLetterRegex.test(inner) ? "https://" : "http://") + (www ? "www." : "") + rest;
84
+ }
85
+ const singleMatch = singleMalformedRegex.exec(url);
86
+ if (singleMatch) {
87
+ const fullMatch = singleMatch[0];
88
+ const www = singleMatch[1];
89
+ const rest = url.slice(fullMatch.length);
90
+ return (httpsLetterRegex.test(fullMatch) ? "https://" : "http://") + (www ? "www." : "") + rest;
91
+ }
92
+ return url;
93
+ };
94
+ const feedProtocols = [
95
+ "feed:",
96
+ "rss:",
97
+ "podcast:",
98
+ "podcasts:",
99
+ "pcast:",
100
+ "itpc:",
101
+ "itms:",
102
+ "itms-pcast:",
103
+ "itms-pcasts:",
104
+ "itms-podcast:",
105
+ "itms-podcasts:"
106
+ ];
107
+ const resolveFeedProtocol = (url, protocol = "https") => {
108
+ const firstCharCode = url.charCodeAt(0) | 32;
109
+ if (firstCharCode !== 102 && firstCharCode !== 114 && firstCharCode !== 112 && firstCharCode !== 105) return url;
110
+ const urlLower = url.toLowerCase();
111
+ for (const scheme of feedProtocols) {
112
+ if (!urlLower.startsWith(scheme)) continue;
113
+ if (urlLower.startsWith(`${scheme}http://`) || urlLower.startsWith(`${scheme}https://`)) return url.slice(scheme.length);
114
+ if (urlLower.startsWith(`${scheme}//`)) return `${protocol}:${url.slice(scheme.length)}`;
115
+ }
116
+ return url;
117
+ };
118
+ const addMissingProtocol = (url, protocol = "https") => {
119
+ const colonIndex = url.indexOf(":");
120
+ if (colonIndex > 0) {
121
+ const beforeColon = url.slice(0, colonIndex);
122
+ if (!beforeColon.includes(".") && !beforeColon.includes("/") && beforeColon !== "localhost") return url;
123
+ }
124
+ if (url.startsWith("//") && !url.startsWith("///")) {
125
+ const parsed = parseUrl(`${protocol}:${url}`);
126
+ if (!parsed) return url;
127
+ const hostname = parsed.hostname;
128
+ if (hostname.includes(".") || hostname === "localhost" || isIpAddress(hostname)) return parsed.href;
129
+ return url;
130
+ }
131
+ if (url.startsWith("/") || url.startsWith(".")) return url;
132
+ const slashIndex = url.indexOf("/");
133
+ const dotIndex = url.indexOf(".");
134
+ if (dotIndex === -1 || slashIndex !== -1 && dotIndex > slashIndex) {
135
+ if (!url.startsWith("localhost")) return url;
136
+ }
137
+ const firstChar = url.charAt(0);
138
+ if (leadingWhitespaceChars.includes(firstChar)) return url;
139
+ return `${protocol}://${url}`;
140
+ };
141
+ const upgradeProtocol = (url, protocol = "https") => {
142
+ if (protocol === "https") return url.replace(httpProtocolRegex, "https://");
143
+ return url.replace(httpsProtocolRegex, "http://");
144
+ };
145
+ const resolveUrl = (url, base) => {
146
+ if (url.startsWith("#") && !base) return;
147
+ let resolvedUrl;
148
+ resolvedUrl = url.includes("&") ? decodeHTMLStrict(url) : url;
149
+ resolvedUrl = resolveFeedProtocol(resolvedUrl);
150
+ resolvedUrl = fixMalformedProtocol(resolvedUrl);
151
+ if (base) {
152
+ const resolved = parseUrl(resolvedUrl, base);
153
+ if (!resolved) return;
154
+ if (isHttpUrl(resolved)) return resolved.href;
155
+ resolvedUrl = resolved.href;
156
+ }
157
+ resolvedUrl = addMissingProtocol(resolvedUrl);
158
+ const parsed = parseUrl(resolvedUrl);
159
+ if (!parsed || !isHttpUrl(parsed)) return;
160
+ return parsed.href;
161
+ };
162
+ const decodeAndNormalizeEncoding = (value) => {
163
+ if (!value.includes("%")) return value;
164
+ return value.replace(/%([0-9A-Fa-f]{2})/g, (_match, hex) => {
165
+ const charCode = Number.parseInt(hex, 16);
166
+ const char = String.fromCharCode(charCode);
167
+ if (safePathCharsRegex.test(char)) return char;
168
+ return `%${hex.toUpperCase()}`;
169
+ });
170
+ };
171
+ const decodeQueryKey = (pair) => {
172
+ const key = pair.split("=")[0].replace(plusRegex, " ");
173
+ try {
174
+ return decodeURIComponent(key);
175
+ } catch {
176
+ return key;
177
+ }
178
+ };
179
+ const compareQueryPairs = (a, b) => {
180
+ const keyA = decodeQueryKey(a);
181
+ const keyB = decodeQueryKey(b);
182
+ if (keyA < keyB) return -1;
183
+ if (keyA > keyB) return 1;
184
+ return 0;
185
+ };
186
+ const lowercaseQueryPair = (pair) => {
187
+ return pair.replace(percentEscapeOrLettersRegex, (match) => {
188
+ return match.startsWith("%") ? match : match.toLowerCase();
189
+ });
190
+ };
191
+ const normalizeUrl = (url, options) => {
192
+ try {
193
+ const parsed = new URL(url);
194
+ if (options.normalizeUnicode) {
195
+ parsed.hostname = parsed.hostname.normalize("NFC");
196
+ parsed.pathname = parsed.pathname.normalize("NFC");
197
+ }
198
+ if (options.stripAuthentication) {
199
+ parsed.username = "";
200
+ parsed.password = "";
201
+ }
202
+ if (options.stripWww) parsed.hostname = stripWww(parsed.hostname);
203
+ if (options.stripHash) parsed.hash = "";
204
+ let pathname = parsed.pathname;
205
+ if (options.normalizeEncoding) pathname = decodeAndNormalizeEncoding(pathname);
206
+ if (options.collapseSlashes) pathname = pathname.replace(/\/+/g, "/");
207
+ if (options.stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
208
+ if (options.stripRootSlash && pathname === "/") pathname = "";
209
+ parsed.pathname = pathname;
210
+ if (options.stripQuery) parsed.search = "";
211
+ if (parsed.search && (options.stripQueryParams || options.lowercaseQuery)) {
212
+ let pairs = parsed.search.slice(1).split("&");
213
+ if (options.stripQueryParams) {
214
+ const strippedSet = getStrippedParamsSet(options.stripQueryParams);
215
+ pairs = pairs.filter((pair) => !strippedSet.has(decodeQueryKey(pair).toLowerCase()));
216
+ }
217
+ if (options.lowercaseQuery) pairs = pairs.map(lowercaseQueryPair);
218
+ parsed.search = pairs.join("&");
219
+ }
220
+ if (options.sortQueryParams && parsed.search.includes("&")) parsed.search = parsed.search.slice(1).split("&").filter(Boolean).sort(compareQueryPairs).join("&");
221
+ if (options.stripEmptyQuery && parsed.href.endsWith("?")) parsed.search = "";
222
+ let result = parsed.href;
223
+ if (options.stripRootSlash && result === `${parsed.origin}/`) result = parsed.origin;
224
+ if (options.stripProtocol) result = result.replace(protocolPrefixRegex, "");
225
+ return result;
226
+ } catch {
227
+ return url;
228
+ }
229
+ };
230
+ const stripWww = (hostname) => {
231
+ return hostname.replace(wwwPrefixRegex, "");
232
+ };
233
+ const isIpAddress = (hostname) => {
234
+ return ipv4Regex.test(hostname) || ipv6Regex.test(hostname.replace(ipv6BracketsRegex, ""));
235
+ };
236
+ const genericSecondLevels = [
237
+ "ac",
238
+ "co",
239
+ "com",
240
+ "edu",
241
+ "go",
242
+ "gob",
243
+ "gov",
244
+ "id",
245
+ "ne",
246
+ "net",
247
+ "or",
248
+ "org"
249
+ ];
250
+ const getRegistrableDomain = (url) => {
251
+ const hostname = parseUrl(url)?.hostname;
252
+ if (!hostname) return;
253
+ if (isIpAddress(hostname)) return hostname;
254
+ const parts = hostname.split(".");
255
+ if (parts.length <= 2) return hostname;
256
+ const [secondLevel, topLevel] = parts.slice(-2);
257
+ const labels = topLevel.length === 2 && genericSecondLevels.includes(secondLevel) ? 3 : 2;
258
+ return parts.slice(-labels).join(".");
259
+ };
46
260
  //#endregion
47
- export { decodeSegment, getPathSegments, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isSubdomainOf, parseUrl };
261
+ export { addMissingProtocol, decodeSegment, fixMalformedProtocol, getPathSegments, getRegistrableDomain, getSubdomain, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isSubdomainOf, normalizeUrl, parseUrl, resolveFeedProtocol, resolveUrl, stripWww, upgradeProtocol };
package/package.json CHANGED
@@ -43,5 +43,8 @@
43
43
  "kvalita": "^1.24.0",
44
44
  "tsdown": "^0.23.0"
45
45
  },
46
- "version": "2.4.0"
46
+ "dependencies": {
47
+ "entities": "^8.1.0"
48
+ },
49
+ "version": "2.6.0"
47
50
  }