jsdomain-parser 1.0.6 → 1.0.7

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.
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Test Suite</title>
6
+ <title>Example</title>
7
7
 
8
8
  <style>
9
9
  #result {
package/index.js CHANGED
@@ -1,45 +1,4 @@
1
1
  const parseTld = require("./src/parseTld.js");
2
-
3
- function parse(url, options = {}) {
4
- try {
5
- if (!url.startsWith("http")) url = `http://${url}`;
6
- const urlObject = new URL(url);
7
-
8
- const tldData = parseTld(urlObject.hostname, options);
9
-
10
- let domain = urlObject.hostname;
11
- const hostnameParts = urlObject.hostname.split(".");
12
- for (let i = hostnameParts.length - 1; i >= 0; i--) {
13
- const extended = hostnameParts.slice(i);
14
- if (extended.join(".") === tldData.name) {
15
- domain = hostnameParts[i - 1] + "." + extended.join(".");
16
- break;
17
- }
18
- }
19
-
20
- const query = {};
21
- for (const [key, value] of urlObject.searchParams.entries()) {
22
- query[key] = value;
23
- }
24
-
25
- return {
26
- tld: tldData,
27
- url: {
28
- domain: domain,
29
- origin: urlObject.origin,
30
- protocol: urlObject.protocol,
31
- host: urlObject.host,
32
- hostname: urlObject.hostname,
33
- port: urlObject.port,
34
- pathname: urlObject.pathname,
35
- search: urlObject.search,
36
- hash: urlObject.hash,
37
- query,
38
- },
39
- };
40
- } catch (e) {
41
- throw new Error(`Invalid URL: ${e}`);
42
- }
43
- }
2
+ const parse = require("./src/parse.js");
44
3
 
45
4
  module.exports = { parse, parseTld };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "jsdomain-parser",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Parse URLs to extract TLDs, domain, protocols and more",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "build": "rollup -c"
7
+ "build": "rollup -c",
8
+ "test": "jest"
8
9
  },
9
10
  "repository": {
10
11
  "type": "git",
@@ -24,6 +25,7 @@
24
25
  "license": "MIT",
25
26
  "devDependencies": {
26
27
  "@rollup/plugin-json": "^6.1.0",
28
+ "jest": "^29.7.0",
27
29
  "rollup": "^2.79.2",
28
30
  "rollup-plugin-commonjs": "^10.1.0",
29
31
  "rollup-plugin-node-resolve": "^5.2.0",
package/src/parse.js ADDED
@@ -0,0 +1,65 @@
1
+ const parseTld = require("./parseTld.js");
2
+ const parseUrl = require("./parseUrl.js");
3
+
4
+ function parse(url, options = {}) {
5
+ try {
6
+ const urlObject = parseUrl(url);
7
+
8
+ if (
9
+ urlObject.hostname.split(".").filter((i) => i.trim()).length <= 1 &&
10
+ urlObject.hostname !== "localhost"
11
+ ) {
12
+ throw new Error(
13
+ `Invalid domain name: "${urlObject.hostname}" is not a valid domain.`
14
+ );
15
+ }
16
+
17
+ const tldData = parseTld(url, options);
18
+
19
+ let domain = urlObject.hostname;
20
+ const hostnameParts = urlObject.hostname.split(".");
21
+ for (let i = hostnameParts.length - 1; i >= 0; i--) {
22
+ const extended = hostnameParts.slice(i);
23
+
24
+ if (extended.join(".") === tldData.name) {
25
+ if (hostnameParts[i - 1]) {
26
+ domain = hostnameParts[i - 1] + "." + extended.join(".");
27
+ }
28
+
29
+ break;
30
+ }
31
+ }
32
+
33
+ const query = {};
34
+ for (const [key, value] of urlObject.searchParams.entries()) {
35
+ query[key] = value;
36
+ }
37
+
38
+ let urlData = {
39
+ domain: domain,
40
+ origin: urlObject.origin,
41
+ protocol: urlObject.protocol,
42
+ host: urlObject.host,
43
+ hostname: urlObject.hostname,
44
+ port: urlObject.port,
45
+ pathname: urlObject.pathname,
46
+ search: urlObject.search,
47
+ hash: urlObject.hash,
48
+ query,
49
+ };
50
+
51
+ // handle for protocols that aren't supported by URL constructor
52
+ if (urlObject.origin === "null") {
53
+ urlData.origin = urlObject.protocol + "//" + urlObject.hostname;
54
+ }
55
+
56
+ return {
57
+ tld: tldData,
58
+ url: urlData,
59
+ };
60
+ } catch (e) {
61
+ throw new Error(`Invalid URL: ${e.message}`);
62
+ }
63
+ }
64
+
65
+ module.exports = parse;
package/src/parseTld.js CHANGED
@@ -1,6 +1,7 @@
1
+ const parseUrl = require("./parseUrl");
1
2
  const tlds = require("./tlds.json");
2
3
 
3
- const parseTld = (hostname, options = {}) => {
4
+ const parseTld = (url, options = {}) => {
4
5
  const {
5
6
  allowUnknown = false,
6
7
  allowPrivate = true,
@@ -11,6 +12,12 @@ const parseTld = (hostname, options = {}) => {
11
12
  throw new Error("customTlds must be an array");
12
13
  }
13
14
 
15
+ const { hostname } = parseUrl(url);
16
+
17
+ // handle localhost as a special case
18
+ if (hostname == "localhost" && allowPrivate)
19
+ return { name: "localhost", length: 1, parts: ["localhost"] };
20
+
14
21
  const parts = hostname.split(".");
15
22
 
16
23
  const customTlds = extendedTlds.map((tld) => [tld, tld.split(".").length]);
@@ -40,7 +47,10 @@ const parseTld = (hostname, options = {}) => {
40
47
  }
41
48
  }
42
49
 
43
- if (detected.length == 0) {
50
+ // handle case where hostname is an IP address
51
+ const isIP = /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname);
52
+
53
+ if (detected.length == 0 && !isIP) {
44
54
  throw new Error(
45
55
  "Could not detect TLD. You can set allowUnknown to true for allowing unknown TLDs."
46
56
  );
@@ -0,0 +1,15 @@
1
+ const parseUrl = (url) => {
2
+ if (!url) throw new Error("Invalid domain name");
3
+
4
+ const haveProtocol = /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(url);
5
+ if (!haveProtocol) url = `http://${url}`;
6
+ const urlObject = new URL(url);
7
+
8
+ // check for hostname validity
9
+ if (urlObject.hostname.split(".").findIndex((it) => it.trim() == "") >= 0)
10
+ throw new Error("Invalid domain name");
11
+
12
+ return urlObject;
13
+ };
14
+
15
+ module.exports = parseUrl;
package/src/tlds.json CHANGED
@@ -9776,6 +9776,7 @@
9776
9776
  "meinforum.net": 2,
9777
9777
  "affinitylottery.org.uk": 3,
9778
9778
  "raffleentry.org.uk": 3,
9779
+ "localhost": 1,
9779
9780
  "weeklylottery.org.uk": 3
9780
9781
  }
9781
9782
  }
@@ -0,0 +1,152 @@
1
+ const parse = require("../src/parse");
2
+
3
+ describe("JS Domain Parser Tests", () => {
4
+ describe("1. General Domain Validation", () => {
5
+ test("Valid domains with TLDs", () => {
6
+ expect(parse("example.com")).toBeTruthy();
7
+ expect(parse("subdomain.example.com")).toBeTruthy();
8
+ expect(parse("example.co.uk")).toBeTruthy();
9
+ expect(
10
+ parse("example.custom", { extendedTlds: ["custom"] })
11
+ ).toBeTruthy();
12
+ });
13
+
14
+ test("Domains without TLDs", () => {
15
+ expect(() => parse("example")).toThrow();
16
+ expect(parse("localhost").url.hostname).toBe("localhost");
17
+ expect(() => parse("localhost", { allowPrivate: false })).toThrow();
18
+ });
19
+
20
+ test("Invalid domains", () => {
21
+ expect(() => parse("invalid_domain")).toThrow();
22
+ expect(() => parse("com.")).toThrow();
23
+ expect(() => parse(".example.com")).toThrow();
24
+ expect(() => parse("example..com")).toThrow();
25
+ });
26
+ });
27
+
28
+ describe("2. Protocol Handling", () => {
29
+ test("Supported protocols", () => {
30
+ expect(parse("http://example.com").url.protocol).toBe("http:");
31
+ expect(parse("https://example.com").url.protocol).toBe("https:");
32
+ expect(parse("ftp://example.com").url.protocol).toBe("ftp:");
33
+ expect(parse("ssh://example.com").url.protocol).toBe("ssh:");
34
+ expect(parse("telnet://example.com").url.protocol).toBe("telnet:");
35
+ });
36
+
37
+ test("No protocol (assume http)", () => {
38
+ expect(parse("example.com").url.protocol).toBe("http:");
39
+ expect(parse("subdomain.example.com").url.protocol).toBe("http:");
40
+ });
41
+
42
+ test("Invalid protocol cases", () => {
43
+ expect(() => parse("mailto:")).toThrow();
44
+ expect(() => parse("ftp://")).toThrow();
45
+ });
46
+
47
+ test("Non-standard or custom protocols", () => {
48
+ expect(
49
+ parse("custom://example.com", { allowUnknown: true }).url.protocol
50
+ ).toBe("custom:");
51
+ expect(parse("telnet://192.168.1.1").url.protocol).toBe("telnet:");
52
+ });
53
+ });
54
+
55
+ describe("3. Query Strings", () => {
56
+ test("With query strings", () => {
57
+ const result = parse("https://example.com?param=value");
58
+ expect(result.url.query.param).toBe("value");
59
+ });
60
+
61
+ test("Encoded query strings", () => {
62
+ const result = parse("https://example.com?param=%20value");
63
+ expect(result.url.query.param).toBe(" value");
64
+ });
65
+
66
+ test("Empty query strings", () => {
67
+ const result = parse("https://example.com?empty=&param=value");
68
+ expect(result.url.query.empty).toBe("");
69
+ expect(result.url.query.param).toBe("value");
70
+ });
71
+ });
72
+
73
+ describe("4. Path Handling", () => {
74
+ test("With paths", () => {
75
+ expect(parse("https://example.com/path").url.pathname).toBe("/path");
76
+ expect(parse("https://example.com/path/to/resource").url.pathname).toBe(
77
+ "/path/to/resource"
78
+ );
79
+ });
80
+
81
+ test("Edge cases for paths", () => {
82
+ expect(parse("https://example.com//double-slash").url.pathname).toBe(
83
+ "//double-slash"
84
+ );
85
+ expect(parse("https://example.com/path with spaces").url.pathname).toBe(
86
+ "/path%20with%20spaces"
87
+ );
88
+ });
89
+ });
90
+
91
+ describe("5. Port Handling", () => {
92
+ test("Standard ports", () => {
93
+ expect(parse("http://example.com:80").url.port).toBe("");
94
+ expect(parse("https://example.com:443").url.port).toBe("");
95
+ });
96
+
97
+ test("Non-standard ports", () => {
98
+ expect(parse("http://example.com:8080").url.port).toBe("8080");
99
+ });
100
+ });
101
+
102
+ describe("6. TLD Handling", () => {
103
+ test("Known TLDs", () => {
104
+ expect(parse("example.com").tld.name).toBe("com");
105
+ expect(parse("example.org").tld.name).toBe("org");
106
+ });
107
+
108
+ test("Private TLDs", () => {
109
+ expect(parse("example.github.io", { allowPrivate: true }).tld.name).toBe(
110
+ "github.io"
111
+ );
112
+ expect(parse("example.github.io", { allowPrivate: false }).tld.name).toBe(
113
+ "io"
114
+ );
115
+ });
116
+
117
+ test("Unknown TLDs", () => {
118
+ expect(parse("example.unknown", { allowUnknown: true }).tld.name).toBe(
119
+ "unknown"
120
+ );
121
+ expect(() => parse("example.unknown", { allowUnknown: false })).toThrow();
122
+ });
123
+ });
124
+
125
+ describe("8. Invalid Inputs", () => {
126
+ test("Empty strings", () => {
127
+ expect(() => parse("")).toThrow();
128
+ });
129
+
130
+ test("Improperly formatted URLs", () => {
131
+ expect(() => parse("http://")).toThrow();
132
+ expect(() => parse("https://.")).toThrow();
133
+ });
134
+ });
135
+
136
+ describe("9. Nested Subdomains", () => {
137
+ test("Deeply nested subdomains", () => {
138
+ const result = parse("a.b.c.example.com");
139
+ expect(result.url.hostname).toBe("a.b.c.example.com");
140
+ expect(result.url.domain).toBe("example.com");
141
+ });
142
+ });
143
+
144
+ describe("10. Mixed Case", () => {
145
+ test("Case-insensitive domains", () => {
146
+ expect(parse("HTTP://EXAMPLE.COM").url.hostname).toBe("example.com");
147
+ expect(parse("https://Sub.Example.CoM").url.hostname).toBe(
148
+ "sub.example.com"
149
+ );
150
+ });
151
+ });
152
+ });