@wtasnorg/node-lib 0.0.6 → 0.0.8

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.
Files changed (57) hide show
  1. package/changelog.txt +14 -0
  2. package/dev_checklist.txt +56 -0
  3. package/docs/README.md +8 -34
  4. package/docs/docs.json +1538 -0
  5. package/docs/functions/createFindDirectories.md +2 -2
  6. package/docs/functions/hello.md +2 -2
  7. package/docs/functions/parseUserAgent.md +42 -0
  8. package/docs/functions/pojo.md +4 -4
  9. package/docs/interfaces/FileSystemDependencies.md +9 -9
  10. package/docs/interfaces/FindDirectoriesOptions.md +8 -8
  11. package/docs/interfaces/UserAgentInfo.md +61 -0
  12. package/eslint.config.js +52 -0
  13. package/gen-docs/001_commands.txt +44 -0
  14. package/gen-docs/001_coverage.txt +43 -0
  15. package/gen-docs/001_env.txt +33 -0
  16. package/gen-docs/001_lint.txt +40 -0
  17. package/gen-docs/001_state.txt +58 -0
  18. package/gen-docs/002_api.txt +34 -0
  19. package/gen-docs/002_deps.txt +46 -0
  20. package/gen-docs/002_errors.txt +34 -0
  21. package/gen-docs/002_naming.txt +36 -0
  22. package/gen-docs/002_notes.txt +20 -0
  23. package/gen-docs/002_purity.txt +36 -0
  24. package/gen-docs/002_scope.txt +28 -0
  25. package/gen-docs/002_srp.txt +34 -0
  26. package/gen-sec/001_commands.txt +65 -0
  27. package/gen-sec/001_env.txt +28 -0
  28. package/gen-sec/001_findings.txt +63 -0
  29. package/gen-sec/001_inventory.txt +41 -0
  30. package/gen-sec/001_owasp.txt +78 -0
  31. package/gen-sec/001_scope.txt +44 -0
  32. package/package.json +10 -3
  33. package/{README.md → readme.txt} +3 -1
  34. package/src/find.d.ts +4 -4
  35. package/src/find.js +13 -7
  36. package/src/find.test.js +2 -6
  37. package/src/find.test.ts +3 -11
  38. package/src/find.ts +13 -13
  39. package/src/index.d.ts +4 -2
  40. package/src/index.js +2 -1
  41. package/src/index.ts +6 -2
  42. package/src/pojo.d.ts +2 -2
  43. package/src/pojo.js +2 -2
  44. package/src/pojo.test.js +1 -3
  45. package/src/pojo.test.ts +2 -1
  46. package/src/pojo.ts +3 -3
  47. package/src/user-agent.d.ts +48 -0
  48. package/src/user-agent.js +189 -0
  49. package/src/user-agent.test.d.ts +2 -0
  50. package/src/user-agent.test.js +54 -0
  51. package/src/user-agent.test.ts +60 -0
  52. package/src/user-agent.ts +199 -0
  53. package/test_report +26 -0
  54. package/typedoc.json +6 -2
  55. package/DEV_CHECKLIST.md +0 -15
  56. package/docs/_media/LICENSE +0 -21
  57. package/docs/globals.md +0 -16
package/src/find.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  import { resolve, join } from "node:path";
2
2
 
3
3
  interface FileSystemDependencies {
4
- readdir: (path: string, opts: { withFileTypes: true }) => Promise<{ name: string; isDirectory(): boolean; }[]>;
5
- stat: (path: string) => Promise<{ isDirectory(): boolean }>;
4
+ readdir: (_path: string, _opts: { withFileTypes: true }) => Promise<{ name: string; isDirectory(): boolean; }[]>;
5
+ stat: (_path: string) => Promise<{ isDirectory(): boolean }>;
6
6
  }
7
7
 
8
8
  interface FindDirectoriesOptions {
9
9
  maxDepth?: number;
10
10
  followSymlinks?: boolean;
11
- allowlist?: string[] | ((absPath: string, name: string) => boolean);
12
- blocklist?: string[] | ((absPath: string, name: string) => boolean);
11
+ allowlist?: string[] | ((_absPath: string, _name: string) => boolean);
12
+ blocklist?: string[] | ((_absPath: string, _name: string) => boolean);
13
13
  }
14
14
 
15
15
  /**
@@ -38,7 +38,7 @@ function createFindDirectories(deps: FileSystemDependencies) {
38
38
  function isAllowed(absPath: string, name: string): boolean {
39
39
  if (allowlist) {
40
40
  if (Array.isArray(allowlist)) {
41
- if (!allowlist.includes(name)) return false;
41
+ if (!allowlist.includes(name)) { return false; }
42
42
  } else if (!allowlist(absPath, name)) {
43
43
  return false;
44
44
  }
@@ -49,7 +49,7 @@ function createFindDirectories(deps: FileSystemDependencies) {
49
49
  function isBlocked(absPath: string, name: string): boolean {
50
50
  if (blocklist) {
51
51
  if (Array.isArray(blocklist)) {
52
- if (blocklist.includes(name)) return true;
52
+ if (blocklist.includes(name)) { return true; }
53
53
  } else if (blocklist(absPath, name)) {
54
54
  return true;
55
55
  }
@@ -58,19 +58,19 @@ function createFindDirectories(deps: FileSystemDependencies) {
58
58
  }
59
59
 
60
60
  async function walk(currentPath: string, depth: number): Promise<void> {
61
- if (depth > maxDepth) return;
61
+ if (depth > maxDepth) { return; }
62
62
 
63
63
  const entries = await readdir(currentPath, { withFileTypes: true });
64
64
 
65
65
  for (const entry of entries) {
66
66
  const childPath = resolve(join(currentPath, entry.name));
67
67
 
68
- let isDirectory = entry.isDirectory();
68
+ const isDirectory = entry.isDirectory();
69
69
 
70
- if (!isDirectory) continue;
70
+ if (!isDirectory) { continue; }
71
71
 
72
- if (isBlocked(childPath, entry.name)) continue;
73
- if (!isAllowed(childPath, entry.name)) continue;
72
+ if (isBlocked(childPath, entry.name)) { continue; }
73
+ if (!isAllowed(childPath, entry.name)) { continue; }
74
74
 
75
75
  results.push(childPath);
76
76
 
@@ -91,9 +91,9 @@ function createFindDirectories(deps: FileSystemDependencies) {
91
91
 
92
92
  export {
93
93
  createFindDirectories,
94
- }
94
+ };
95
95
 
96
96
  export type {
97
97
  FileSystemDependencies,
98
98
  FindDirectoriesOptions,
99
- }
99
+ };
package/src/index.d.ts CHANGED
@@ -2,6 +2,8 @@ import { hello } from "./hello.js";
2
2
  import { pojo } from "./pojo.js";
3
3
  import type { FindDirectoriesOptions, FileSystemDependencies } from "./find.js";
4
4
  import { createFindDirectories } from "./find.js";
5
- export { hello, pojo, createFindDirectories };
6
- export type { FindDirectoriesOptions, FileSystemDependencies, };
5
+ import type { UserAgentInfo } from "./user-agent.js";
6
+ import { parseUserAgent } from "./user-agent.js";
7
+ export { hello, pojo, createFindDirectories, parseUserAgent };
8
+ export type { FindDirectoriesOptions, FileSystemDependencies, UserAgentInfo, };
7
9
  //# sourceMappingURL=index.d.ts.map
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { hello } from "./hello.js";
2
2
  import { pojo } from "./pojo.js";
3
3
  import { createFindDirectories } from "./find.js";
4
- export { hello, pojo, createFindDirectories };
4
+ import { parseUserAgent } from "./user-agent.js";
5
+ export { hello, pojo, createFindDirectories, parseUserAgent };
5
6
  //# sourceMappingURL=index.js.map
package/src/index.ts CHANGED
@@ -2,14 +2,18 @@ import { hello } from "./hello.js";
2
2
  import { pojo } from "./pojo.js";
3
3
  import type { FindDirectoriesOptions, FileSystemDependencies } from "./find.js";
4
4
  import { createFindDirectories } from "./find.js";
5
+ import type { UserAgentInfo } from "./user-agent.js";
6
+ import { parseUserAgent } from "./user-agent.js";
5
7
 
6
8
  export {
7
9
  hello,
8
10
  pojo,
9
- createFindDirectories
11
+ createFindDirectories,
12
+ parseUserAgent
10
13
  };
11
14
 
12
15
  export type {
13
16
  FindDirectoriesOptions,
14
17
  FileSystemDependencies,
15
- }
18
+ UserAgentInfo,
19
+ };
package/src/pojo.d.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * @template T
7
7
  * @param {T} instance - A class instance to convert.
8
- * @returns {Object<string, any>} A plain JavaScript object containing only data fields.
8
+ * @returns {Object<string, unknown>} A plain JavaScript object containing only data fields.
9
9
  */
10
- declare function pojo<T extends object>(instance: T): Record<string, any>;
10
+ declare function pojo<T extends object>(instance: T): Record<string, unknown>;
11
11
  export { pojo };
12
12
  //# sourceMappingURL=pojo.d.ts.map
package/src/pojo.js CHANGED
@@ -5,10 +5,10 @@
5
5
  *
6
6
  * @template T
7
7
  * @param {T} instance - A class instance to convert.
8
- * @returns {Object<string, any>} A plain JavaScript object containing only data fields.
8
+ * @returns {Object<string, unknown>} A plain JavaScript object containing only data fields.
9
9
  */
10
10
  function pojo(instance) {
11
- return Object.fromEntries(Object.entries(instance).filter(([_, value]) => typeof value !== "function"));
11
+ return Object.fromEntries(Object.entries(instance).filter((entry) => typeof entry[1] !== "function"));
12
12
  }
13
13
  export { pojo };
14
14
  //# sourceMappingURL=pojo.js.map
package/src/pojo.test.js CHANGED
@@ -28,9 +28,7 @@ test("pojo() should exclude methods", () => {
28
28
  test("pojo() should exclude prototype properties", () => {
29
29
  class Product {
30
30
  id;
31
- constructor(id) {
32
- this.id = id;
33
- }
31
+ constructor(id) { this.id = id; }
34
32
  get info() {
35
33
  return `Product:${this.id}`;
36
34
  }
package/src/pojo.test.ts CHANGED
@@ -35,7 +35,8 @@ test("pojo() should exclude methods", () => {
35
35
 
36
36
  test("pojo() should exclude prototype properties", () => {
37
37
  class Product {
38
- constructor(public id: number) { }
38
+ id: number;
39
+ constructor(id: number) { this.id = id; }
39
40
 
40
41
  get info() {
41
42
  return `Product:${this.id}`;
package/src/pojo.ts CHANGED
@@ -5,11 +5,11 @@
5
5
  *
6
6
  * @template T
7
7
  * @param {T} instance - A class instance to convert.
8
- * @returns {Object<string, any>} A plain JavaScript object containing only data fields.
8
+ * @returns {Object<string, unknown>} A plain JavaScript object containing only data fields.
9
9
  */
10
- function pojo<T extends object>(instance: T): Record<string, any> {
10
+ function pojo<T extends object>(instance: T): Record<string, unknown> {
11
11
  return Object.fromEntries(
12
- Object.entries(instance).filter(([_, value]) => typeof value !== "function")
12
+ Object.entries(instance).filter((entry) => typeof entry[1] !== "function")
13
13
  );
14
14
  }
15
15
 
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Information extracted from a user-agent string.
3
+ */
4
+ export interface UserAgentInfo {
5
+ /**
6
+ * Browser name (e.g., Chrome, Firefox, Safari, Edge, Opera, Other).
7
+ */
8
+ browser: string;
9
+ /**
10
+ * Browser version (e.g., 120.0.0.0).
11
+ */
12
+ version: string;
13
+ /**
14
+ * Operating system (e.g., Windows, macOS, Linux, iOS, Android, Other).
15
+ */
16
+ os: string;
17
+ /**
18
+ * Device type (e.g., Mobile, Tablet, Desktop, Other).
19
+ */
20
+ device: string;
21
+ /**
22
+ * Rendering engine (e.g., Blink, WebKit, Gecko, Presto, Other).
23
+ */
24
+ engine: string;
25
+ }
26
+ /**
27
+ * Parses a user-agent string into a UserAgentInfo object.
28
+ *
29
+ * @param ua - The user-agent string to parse.
30
+ * @returns An object containing the extracted information.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * // Success Example (Chrome on Windows)
35
+ * const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
36
+ * const info = parseUserAgent(ua);
37
+ * // { browser: "Chrome", version: "120.0.0.0", os: "Windows", device: "Desktop", engine: "Blink" }
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // Error/Fallback Example (Empty string)
43
+ * const info = parseUserAgent("");
44
+ * // { browser: "Other", version: "0", os: "Other", device: "Desktop", engine: "Other" }
45
+ * ```
46
+ */
47
+ export declare function parseUserAgent(ua: string): UserAgentInfo;
48
+ //# sourceMappingURL=user-agent.d.ts.map
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Parses a user-agent string into a UserAgentInfo object.
3
+ *
4
+ * @param ua - The user-agent string to parse.
5
+ * @returns An object containing the extracted information.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // Success Example (Chrome on Windows)
10
+ * const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
11
+ * const info = parseUserAgent(ua);
12
+ * // { browser: "Chrome", version: "120.0.0.0", os: "Windows", device: "Desktop", engine: "Blink" }
13
+ * ```
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Error/Fallback Example (Empty string)
18
+ * const info = parseUserAgent("");
19
+ * // { browser: "Other", version: "0", os: "Other", device: "Desktop", engine: "Other" }
20
+ * ```
21
+ */
22
+ export function parseUserAgent(ua) {
23
+ if (!ua) {
24
+ return {
25
+ browser: "Other",
26
+ version: "0",
27
+ os: "Other",
28
+ device: "Desktop",
29
+ engine: "Other",
30
+ };
31
+ }
32
+ const browserInfo = detectBrowser(ua);
33
+ return {
34
+ browser: browserInfo.name,
35
+ version: browserInfo.version,
36
+ os: detectOS(ua),
37
+ device: detectDeviceType(ua),
38
+ engine: detectEngine(ua),
39
+ };
40
+ }
41
+ /**
42
+ * Detects the browser name and version from a user-agent string.
43
+ *
44
+ * @param ua - The user-agent string.
45
+ * @returns An object with name and version.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // Success Example
50
+ * detectBrowser("Mozilla/5.0 ... Firefox/121.0");
51
+ * // { name: "Firefox", version: "121.0" }
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // Error/Fallback Example
57
+ * detectBrowser("UnknownBot/1.0");
58
+ * // { name: "Other", version: "0" }
59
+ * ```
60
+ */
61
+ function detectBrowser(ua) {
62
+ let name = "Other";
63
+ let version = "0";
64
+ if (ua.includes("Edg/")) {
65
+ name = "Edge";
66
+ version = ua.split("Edg/")[1]?.split(" ")[0] || "0";
67
+ }
68
+ else if (ua.includes("Chrome/") && !ua.includes("Chromium/")) {
69
+ name = "Chrome";
70
+ version = ua.split("Chrome/")[1]?.split(" ")[0] || "0";
71
+ }
72
+ else if (ua.includes("Safari/") && !ua.includes("Chrome/")) {
73
+ name = "Safari";
74
+ version = ua.split("Version/")[1]?.split(" ")[0] || ua.split("Safari/")[1]?.split(" ")[0] || "0";
75
+ }
76
+ else if (ua.includes("Firefox/")) {
77
+ name = "Firefox";
78
+ version = ua.split("Firefox/")[1]?.split(" ")[0] || "0";
79
+ }
80
+ else if (ua.includes("OPR/") || ua.includes("Opera/")) {
81
+ name = "Opera";
82
+ version = (ua.split("OPR/")[1] || ua.split("Opera/")[1])?.split(" ")[0] || "0";
83
+ }
84
+ return { name, version };
85
+ }
86
+ /**
87
+ * Detects the operating system from a user-agent string.
88
+ *
89
+ * @param ua - The user-agent string.
90
+ * @returns The operating system name.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * // Success Example
95
+ * detectOS("Mozilla/5.0 (iPhone; ...)");
96
+ * // "iOS"
97
+ * ```
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * // Error/Fallback Example
102
+ * detectOS("Custom OS");
103
+ * // "Other"
104
+ * ```
105
+ */
106
+ function detectOS(ua) {
107
+ if (ua.includes("iPhone") || ua.includes("iPad") || ua.includes("iPod")) {
108
+ return "iOS";
109
+ }
110
+ if (ua.includes("Windows")) {
111
+ return "Windows";
112
+ }
113
+ if (ua.includes("Mac OS X")) {
114
+ return "macOS";
115
+ }
116
+ if (ua.includes("Android")) {
117
+ return "Android";
118
+ }
119
+ if (ua.includes("Linux")) {
120
+ return "Linux";
121
+ }
122
+ return "Other";
123
+ }
124
+ /**
125
+ * Detects the device type (Desktop, Mobile, Tablet) from a user-agent string.
126
+ *
127
+ * @param ua - The user-agent string.
128
+ * @returns The device type.
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * // Success Example
133
+ * detectDeviceType("Mozilla/5.0 (iPad; ...)");
134
+ * // "Tablet"
135
+ * ```
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * // Error/Fallback Example
140
+ * detectDeviceType("Generic Browser");
141
+ * // "Desktop"
142
+ * ```
143
+ */
144
+ function detectDeviceType(ua) {
145
+ if (ua.includes("iPad") || (ua.includes("Android") && !ua.includes("Mobile"))) {
146
+ return "Tablet";
147
+ }
148
+ if (ua.includes("Mobi")) {
149
+ return "Mobile";
150
+ }
151
+ return "Desktop";
152
+ }
153
+ /**
154
+ * Detects the rendering engine from a user-agent string.
155
+ *
156
+ * @param ua - The user-agent string.
157
+ * @returns The engine name.
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * // Success Example
162
+ * detectEngine("Mozilla/5.0 ... AppleWebKit/537.36 ...");
163
+ * // "Blink"
164
+ * ```
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * // Error/Fallback Example
169
+ * detectEngine("Unknown Engine");
170
+ * // "Other"
171
+ * ```
172
+ */
173
+ function detectEngine(ua) {
174
+ // Blink check must come first: Chrome/Chromium browsers use AppleWebKit but are Blink-based
175
+ if (ua.includes("Chrome/") && ua.includes("AppleWebKit/")) {
176
+ return "Blink";
177
+ }
178
+ if (ua.includes("AppleWebKit")) {
179
+ return "WebKit";
180
+ }
181
+ if (ua.includes("Gecko/")) {
182
+ return "Gecko";
183
+ }
184
+ if (ua.includes("Presto/")) {
185
+ return "Presto";
186
+ }
187
+ return "Other";
188
+ }
189
+ //# sourceMappingURL=user-agent.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=user-agent.test.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { parseUserAgent } from "./user-agent.js";
4
+ test("parseUserAgent - Chrome on Windows", () => {
5
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
6
+ const info = parseUserAgent(ua);
7
+ assert.strictEqual(info.browser, "Chrome");
8
+ assert.strictEqual(info.os, "Windows");
9
+ assert.strictEqual(info.device, "Desktop");
10
+ assert.strictEqual(info.version, "120.0.0.0");
11
+ });
12
+ test("parseUserAgent - Firefox on macOS", () => {
13
+ const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:121.0) Gecko/20100101 Firefox/121.0";
14
+ const info = parseUserAgent(ua);
15
+ assert.strictEqual(info.browser, "Firefox");
16
+ assert.strictEqual(info.os, "macOS");
17
+ assert.strictEqual(info.version, "121.0");
18
+ assert.strictEqual(info.engine, "Gecko");
19
+ });
20
+ test("parseUserAgent - Safari on iPhone", () => {
21
+ const ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1";
22
+ const info = parseUserAgent(ua);
23
+ assert.strictEqual(info.browser, "Safari");
24
+ assert.strictEqual(info.os, "iOS");
25
+ assert.strictEqual(info.device, "Mobile");
26
+ assert.strictEqual(info.version, "17.2");
27
+ });
28
+ test("parseUserAgent - Edge on Windows", () => {
29
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0";
30
+ const info = parseUserAgent(ua);
31
+ assert.strictEqual(info.browser, "Edge");
32
+ assert.strictEqual(info.os, "Windows");
33
+ assert.strictEqual(info.version, "120.0.0.0");
34
+ });
35
+ test("parseUserAgent - Chrome on Android (Mobile)", () => {
36
+ const ua = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36";
37
+ const info = parseUserAgent(ua);
38
+ assert.strictEqual(info.browser, "Chrome");
39
+ assert.strictEqual(info.os, "Android");
40
+ assert.strictEqual(info.device, "Mobile");
41
+ });
42
+ test("parseUserAgent - Android Tablet", () => {
43
+ const ua = "Mozilla/5.0 (Linux; Android 10; SM-T510) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
44
+ const info = parseUserAgent(ua);
45
+ assert.strictEqual(info.browser, "Chrome");
46
+ assert.strictEqual(info.os, "Android");
47
+ assert.strictEqual(info.device, "Tablet");
48
+ });
49
+ test("parseUserAgent - Empty/Null UA", () => {
50
+ const info = parseUserAgent("");
51
+ assert.strictEqual(info.browser, "Other");
52
+ assert.strictEqual(info.device, "Desktop");
53
+ });
54
+ //# sourceMappingURL=user-agent.test.js.map
@@ -0,0 +1,60 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+ import { parseUserAgent } from "./user-agent.js";
4
+
5
+ test("parseUserAgent - Chrome on Windows", () => {
6
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
7
+ const info = parseUserAgent(ua);
8
+ assert.strictEqual(info.browser, "Chrome");
9
+ assert.strictEqual(info.os, "Windows");
10
+ assert.strictEqual(info.device, "Desktop");
11
+ assert.strictEqual(info.version, "120.0.0.0");
12
+ });
13
+
14
+ test("parseUserAgent - Firefox on macOS", () => {
15
+ const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:121.0) Gecko/20100101 Firefox/121.0";
16
+ const info = parseUserAgent(ua);
17
+ assert.strictEqual(info.browser, "Firefox");
18
+ assert.strictEqual(info.os, "macOS");
19
+ assert.strictEqual(info.version, "121.0");
20
+ assert.strictEqual(info.engine, "Gecko");
21
+ });
22
+
23
+ test("parseUserAgent - Safari on iPhone", () => {
24
+ const ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1";
25
+ const info = parseUserAgent(ua);
26
+ assert.strictEqual(info.browser, "Safari");
27
+ assert.strictEqual(info.os, "iOS");
28
+ assert.strictEqual(info.device, "Mobile");
29
+ assert.strictEqual(info.version, "17.2");
30
+ });
31
+
32
+ test("parseUserAgent - Edge on Windows", () => {
33
+ const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0";
34
+ const info = parseUserAgent(ua);
35
+ assert.strictEqual(info.browser, "Edge");
36
+ assert.strictEqual(info.os, "Windows");
37
+ assert.strictEqual(info.version, "120.0.0.0");
38
+ });
39
+
40
+ test("parseUserAgent - Chrome on Android (Mobile)", () => {
41
+ const ua = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36";
42
+ const info = parseUserAgent(ua);
43
+ assert.strictEqual(info.browser, "Chrome");
44
+ assert.strictEqual(info.os, "Android");
45
+ assert.strictEqual(info.device, "Mobile");
46
+ });
47
+
48
+ test("parseUserAgent - Android Tablet", () => {
49
+ const ua = "Mozilla/5.0 (Linux; Android 10; SM-T510) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
50
+ const info = parseUserAgent(ua);
51
+ assert.strictEqual(info.browser, "Chrome");
52
+ assert.strictEqual(info.os, "Android");
53
+ assert.strictEqual(info.device, "Tablet");
54
+ });
55
+
56
+ test("parseUserAgent - Empty/Null UA", () => {
57
+ const info = parseUserAgent("");
58
+ assert.strictEqual(info.browser, "Other");
59
+ assert.strictEqual(info.device, "Desktop");
60
+ });