user-agents 2.0.0-alpha.98 → 2.1.1
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 +70 -15
- package/dist/index.cjs +3 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/user-agents.json +136416 -69115
- package/package.json +21 -18
- package/src/update-data.ts +11 -3
- package/src/user-agent.ts +66 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "user-agents",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A JavaScript library for generating random user agents. ",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"import": "./dist/index.js",
|
|
20
20
|
"require": "./dist/index.cjs"
|
|
21
21
|
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
22
25
|
"license": "BSD-2-Clause",
|
|
23
26
|
"private": false,
|
|
24
27
|
"type": "module",
|
|
@@ -26,8 +29,8 @@
|
|
|
26
29
|
"build": "tsup",
|
|
27
30
|
"postbuild": "yarn gunzip-data && cp src/user-agents.json dist/",
|
|
28
31
|
"gunzip-data": "node --loader ts-node/esm src/gunzip-data.ts src/user-agents.json.gz",
|
|
29
|
-
"lint": "eslint src/ && prettier --check src/",
|
|
30
|
-
"test": "
|
|
32
|
+
"lint": "eslint src/ test/ && prettier --check src/ test/",
|
|
33
|
+
"test": "NODE_OPTIONS='--import tsx' mocha --exit --extensions '.ts, .js'",
|
|
31
34
|
"update-data": "node --loader ts-node/esm src/update-data.ts src/user-agents.json.gz"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
@@ -35,39 +38,39 @@
|
|
|
35
38
|
"@babel/core": "^7.23.2",
|
|
36
39
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
37
40
|
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
38
|
-
"@babel/plugin-syntax-import-
|
|
41
|
+
"@babel/plugin-syntax-import-attributes": "^7.22.5",
|
|
39
42
|
"@babel/plugin-transform-classes": "^7.22.15",
|
|
40
43
|
"@babel/preset-env": "^7.23.2",
|
|
41
44
|
"@babel/preset-typescript": "^7.23.2",
|
|
42
45
|
"@babel/register": "^7.22.15",
|
|
43
|
-
"@types/
|
|
46
|
+
"@types/node": "^25.5.0",
|
|
44
47
|
"@types/ua-parser-js": "^0.7.38",
|
|
45
|
-
"@typescript-eslint/eslint-plugin": "^6.9.0",
|
|
46
|
-
"@typescript-eslint/parser": "^6.9.0",
|
|
47
48
|
"babel-loader": "^9.1.3",
|
|
48
49
|
"babel-plugin-add-module-exports": "^1.0.4",
|
|
49
50
|
"babel-preset-power-assert": "^3.0.0",
|
|
50
51
|
"dynamoose": "^3.2.1",
|
|
51
|
-
"esbuild": "^0.
|
|
52
|
-
"eslint": "^
|
|
53
|
-
"eslint-config-
|
|
54
|
-
"eslint-config-prettier": "^9.0.0",
|
|
55
|
-
"eslint-plugin-import": "^2.29.0",
|
|
52
|
+
"esbuild": "^0.25.0",
|
|
53
|
+
"eslint": "^9",
|
|
54
|
+
"eslint-config-prettier": "^10.1.8",
|
|
56
55
|
"fast-json-stable-stringify": "^2.1.0",
|
|
57
56
|
"imports-loader": "^4.0.1",
|
|
58
57
|
"isbot": "^3.7.0",
|
|
59
|
-
"mocha": "^
|
|
58
|
+
"mocha": "^11.7.5",
|
|
60
59
|
"power-assert": "^1.6.1",
|
|
61
|
-
"prettier": "^3.0
|
|
60
|
+
"prettier": "^3.1.0",
|
|
62
61
|
"random": "^4.1.0",
|
|
63
62
|
"source-map-support": "^0.5.21",
|
|
64
63
|
"ts-node": "^10.9.1",
|
|
65
|
-
"tsup": "^
|
|
66
|
-
"
|
|
64
|
+
"tsup": "^8.5.0",
|
|
65
|
+
"tsx": "^4.21.0",
|
|
66
|
+
"typescript": "^5.8.0",
|
|
67
|
+
"typescript-eslint": "^8.57.0",
|
|
67
68
|
"typescript-language-server": "^4.0.0",
|
|
68
69
|
"ua-parser-js": "^1.0.36"
|
|
69
70
|
},
|
|
70
|
-
"dependencies": {
|
|
71
|
-
|
|
71
|
+
"dependencies": {},
|
|
72
|
+
"resolutions": {
|
|
73
|
+
"diff": "^8.0.3",
|
|
74
|
+
"serialize-javascript": "^7.0.3"
|
|
72
75
|
}
|
|
73
76
|
}
|
package/src/update-data.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import { argv } from 'process';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
@@ -67,6 +66,16 @@ const getUserAgentTable = async (limit = 1e4) => {
|
|
|
67
66
|
// Filter out bots like Googlebot and YandexBot.
|
|
68
67
|
if (isbot(profile.userAgent)) return;
|
|
69
68
|
|
|
69
|
+
// Strip extra quotes that some browsers include around the user agent string.
|
|
70
|
+
profile.userAgent = (profile.userAgent as string).replace(/^"|"$/g, '');
|
|
71
|
+
|
|
72
|
+
// Filter out spam/fake user agents with non-ASCII characters.
|
|
73
|
+
// Real browser UA strings are always pure ASCII per the HTTP spec.
|
|
74
|
+
if (/[^\x20-\x7E]/.test(profile.userAgent as string)) return;
|
|
75
|
+
|
|
76
|
+
// Filter out the literal `{userAgent}` placeholder string that some clients send.
|
|
77
|
+
if (profile.userAgent === '{userAgent}') return;
|
|
78
|
+
|
|
70
79
|
// Track the counts for this exact profile.
|
|
71
80
|
const stringifiedProfile = stableStringify(profile);
|
|
72
81
|
if (!countsByProfile[stringifiedProfile]) {
|
|
@@ -91,7 +100,7 @@ const getUserAgentTable = async (limit = 1e4) => {
|
|
|
91
100
|
// Accumulate the profiles and add/remove a few properties to match the historical format.
|
|
92
101
|
const profiles: UserAgentData[] = [];
|
|
93
102
|
Object.entries(countsByProfile).forEach(([stringifiedProfile, weight]) => {
|
|
94
|
-
if (
|
|
103
|
+
if (Object.hasOwn(countsByProfile, stringifiedProfile)) {
|
|
95
104
|
const profile = JSON.parse(stringifiedProfile);
|
|
96
105
|
profile.weight = weight;
|
|
97
106
|
delete profile.sessionId;
|
|
@@ -136,7 +145,6 @@ if (fileURLToPath(import.meta.url) === argv[1]) {
|
|
|
136
145
|
fs.writeFileSync(filename, content);
|
|
137
146
|
})
|
|
138
147
|
.catch((error) => {
|
|
139
|
-
// eslint-disable-next-line no-console
|
|
140
148
|
console.error(error);
|
|
141
149
|
process.exit(1);
|
|
142
150
|
});
|
package/src/user-agent.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import untypedUserAgents from './user-agents.json' assert { type: 'json' };
|
|
1
|
+
import untypedUserAgents from './user-agents.json' with { type: 'json' };
|
|
4
2
|
|
|
5
3
|
const userAgents: UserAgentData[] = untypedUserAgents as UserAgentData[];
|
|
6
4
|
|
|
@@ -108,7 +106,7 @@ const constructFilter = <T extends UserAgentData | NestedValueOf<UserAgentData>>
|
|
|
108
106
|
try {
|
|
109
107
|
const value = accessor(parentObject);
|
|
110
108
|
return childFilters.every((childFilter) => childFilter(value as T));
|
|
111
|
-
} catch
|
|
109
|
+
} catch {
|
|
112
110
|
// This happens when a user-agent lacks a nested property.
|
|
113
111
|
return false;
|
|
114
112
|
}
|
|
@@ -146,9 +144,34 @@ const setCumulativeWeightIndexPairs = (
|
|
|
146
144
|
});
|
|
147
145
|
};
|
|
148
146
|
|
|
149
|
-
|
|
147
|
+
// WeakSet tracking all UserAgent instances for `instanceof` support through proxies.
|
|
148
|
+
const userAgentInstances = new WeakSet<object>();
|
|
149
|
+
|
|
150
|
+
export class UserAgent {
|
|
151
|
+
static [Symbol.hasInstance](instance: unknown): boolean {
|
|
152
|
+
return instance instanceof Object && userAgentInstances.has(instance as object);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static random = (filters: Filter) => {
|
|
156
|
+
try {
|
|
157
|
+
return new UserAgent(filters);
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Static version that creates a temporary instance with the given filters and returns the top entries.
|
|
164
|
+
static top = (count?: number, filters?: Filter): UserAgentData[] => {
|
|
165
|
+
try {
|
|
166
|
+
return new UserAgent(filters).top(count);
|
|
167
|
+
} catch {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
readonly data!: UserAgentData;
|
|
173
|
+
|
|
150
174
|
constructor(filters?: Filter) {
|
|
151
|
-
super();
|
|
152
175
|
setCumulativeWeightIndexPairs(this, constructCumulativeWeightIndexPairsFromFilters(filters));
|
|
153
176
|
if (this.cumulativeWeightIndexPairs.length === 0) {
|
|
154
177
|
throw new Error('No user agents matched your filters.');
|
|
@@ -156,39 +179,41 @@ export class UserAgent extends Function {
|
|
|
156
179
|
|
|
157
180
|
this.randomize();
|
|
158
181
|
|
|
159
|
-
//
|
|
160
|
-
|
|
182
|
+
// Use a plain function as the proxy target so the `apply` trap works without
|
|
183
|
+
// extending `Function`, which requires `eval` and violates CSP in browser extensions.
|
|
184
|
+
// All property access is forwarded to the real UserAgent instance via the traps.
|
|
185
|
+
const target = () => {};
|
|
186
|
+
const proxy = new Proxy(target as unknown as this, {
|
|
161
187
|
apply: () => this.random(),
|
|
162
|
-
get: (
|
|
163
|
-
|
|
164
|
-
|
|
188
|
+
get: (_target, property) => {
|
|
189
|
+
if (
|
|
190
|
+
this.data &&
|
|
165
191
|
typeof property === 'string' &&
|
|
166
|
-
Object.prototype.hasOwnProperty.call(
|
|
167
|
-
Object.prototype.propertyIsEnumerable.call(
|
|
168
|
-
|
|
169
|
-
const value =
|
|
192
|
+
Object.prototype.hasOwnProperty.call(this.data, property) &&
|
|
193
|
+
Object.prototype.propertyIsEnumerable.call(this.data, property)
|
|
194
|
+
) {
|
|
195
|
+
const value = this.data[property as keyof UserAgentData];
|
|
170
196
|
if (value !== undefined) {
|
|
171
197
|
return value;
|
|
172
198
|
}
|
|
173
199
|
}
|
|
174
200
|
|
|
175
|
-
return Reflect.get(
|
|
201
|
+
return Reflect.get(this, property);
|
|
176
202
|
},
|
|
203
|
+
set: (_target, property, value) => Reflect.set(this, property, value),
|
|
204
|
+
defineProperty: (_target, property, descriptor) =>
|
|
205
|
+
Reflect.defineProperty(this, property, descriptor),
|
|
206
|
+
getOwnPropertyDescriptor: (_target, property) =>
|
|
207
|
+
Reflect.getOwnPropertyDescriptor(this, property),
|
|
208
|
+
has: (_target, property) => Reflect.has(this, property),
|
|
209
|
+
deleteProperty: (_target, property) => Reflect.deleteProperty(this, property),
|
|
210
|
+
ownKeys: () => Reflect.ownKeys(this),
|
|
211
|
+
getPrototypeOf: () => UserAgent.prototype,
|
|
177
212
|
});
|
|
213
|
+
userAgentInstances.add(proxy);
|
|
214
|
+
return proxy;
|
|
178
215
|
}
|
|
179
216
|
|
|
180
|
-
static random = (filters: Filter) => {
|
|
181
|
-
try {
|
|
182
|
-
return new UserAgent(filters);
|
|
183
|
-
} catch (error) {
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
// Standard Object Methods
|
|
190
|
-
//
|
|
191
|
-
|
|
192
217
|
[Symbol.toPrimitive] = (): string => this.data.userAgent;
|
|
193
218
|
|
|
194
219
|
toString = (): string => this.data.userAgent;
|
|
@@ -200,6 +225,18 @@ export class UserAgent extends Function {
|
|
|
200
225
|
return userAgent;
|
|
201
226
|
};
|
|
202
227
|
|
|
228
|
+
top = (count?: number): UserAgentData[] => {
|
|
229
|
+
// Recover individual weights from the cumulative distribution and sort by descending weight.
|
|
230
|
+
const pairs = this.cumulativeWeightIndexPairs;
|
|
231
|
+
const entries = pairs.map(([cumWeight, index], i) => ({
|
|
232
|
+
weight: i > 0 ? cumWeight - pairs[i - 1][0] : cumWeight,
|
|
233
|
+
index,
|
|
234
|
+
}));
|
|
235
|
+
entries.sort((a, b) => b.weight - a.weight);
|
|
236
|
+
const n = count != null ? Math.min(count, entries.length) : entries.length;
|
|
237
|
+
return entries.slice(0, n).map(({ index }) => structuredClone(userAgents[index]));
|
|
238
|
+
};
|
|
239
|
+
|
|
203
240
|
randomize = (): void => {
|
|
204
241
|
// Find a random raw random user agent.
|
|
205
242
|
const randomNumber = Math.random();
|
|
@@ -210,8 +247,7 @@ export class UserAgent extends Function {
|
|
|
210
247
|
if (index == null) {
|
|
211
248
|
throw new Error('Error finding a random user agent.');
|
|
212
249
|
}
|
|
213
|
-
const rawUserAgent = userAgents[index];
|
|
214
250
|
|
|
215
|
-
(this as { data: UserAgentData }).data =
|
|
251
|
+
(this as { data: UserAgentData }).data = structuredClone(userAgents[index]);
|
|
216
252
|
};
|
|
217
253
|
}
|