Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/plugins/default-browser-emulator/lib/helpers/selectUserAgentOption.ts
1030 views
1
import { UAParser } from 'ua-parser-js';
2
import { pickRandom } from '@secret-agent/commons/utils';
3
import IUserAgentOption, { IVersion } from '@secret-agent/interfaces/IUserAgentOption';
4
import { IDataUserAgentOption, IDataUserAgentOptions } from '../../interfaces/IBrowserData';
5
import { latestChromeBrowserVersion } from '../../index';
6
7
const compareVersions = require('compare-versions');
8
9
export default function selectUserAgentOption(
10
userAgentSelector: string,
11
dataUserAgentOptions: IDataUserAgentOptions,
12
): IUserAgentOption {
13
userAgentSelector = userAgentSelector?.trim();
14
if (userAgentSelector === 'chrome-latest') userAgentSelector = '';
15
16
if (!userAgentSelector) {
17
const filteredOptions = dataUserAgentOptions.filter(x => {
18
return (
19
x.browserName === 'chrome' &&
20
x.browserVersion.major === latestChromeBrowserVersion.major &&
21
x.browserVersion.minor === latestChromeBrowserVersion.minor
22
);
23
});
24
return pickRandomUserAgentOption(filteredOptions);
25
}
26
27
if (userAgentSelector.startsWith('~')) {
28
return findUserAgentOption(userAgentSelector, dataUserAgentOptions);
29
}
30
31
return createUserAgentOption(userAgentSelector);
32
}
33
34
function pickRandomUserAgentOption(dataUserAgentOptions: IDataUserAgentOptions) {
35
const dataUserAgentOption = pickRandom<IDataUserAgentOption>(dataUserAgentOptions);
36
return convertToUserAgentOption(dataUserAgentOption);
37
}
38
39
function findUserAgentOption(
40
userAgentSelector: string,
41
dataUserAgentOptions: IDataUserAgentOptions,
42
) {
43
const selectors = extractUserAgentSelectors(userAgentSelector);
44
45
const filteredOptions = dataUserAgentOptions.filter(userAgentOption => {
46
return isSelectorMatch(userAgentOption, selectors);
47
});
48
49
if (!filteredOptions.length) {
50
throw new Error(`No installed UserAgent Emulators match your criteria (${userAgentSelector})`);
51
}
52
53
const dataUserAgentOption = pickRandom<IDataUserAgentOption>(filteredOptions);
54
return convertToUserAgentOption(dataUserAgentOption);
55
}
56
57
function convertToUserAgentOption(dataUserAgentOption: IDataUserAgentOption) {
58
return {
59
...dataUserAgentOption,
60
strings: undefined,
61
string: pickRandom(dataUserAgentOption.strings),
62
} as IUserAgentOption;
63
}
64
65
function isSelectorMatch(userAgentOption: IDataUserAgentOption, selectors: ISelector[]) {
66
if (!selectors.length) return true;
67
68
const browserVersion = convertToSemVer(userAgentOption.browserVersion);
69
const operatingSystemVersion = convertToSemVer(userAgentOption.operatingSystemVersion);
70
71
for (const { name, matches } of selectors) {
72
let version: string;
73
if (name === userAgentOption.browserName) {
74
version = browserVersion;
75
} else if (name === userAgentOption.operatingSystemName) {
76
version = operatingSystemVersion;
77
} else {
78
return false;
79
}
80
81
for (const match of matches) {
82
if (match.version === '*.*.*') continue;
83
const isValid = compareVersions.compare(version, match.version, match.operator);
84
85
// must match every selector
86
if (!isValid) return false;
87
}
88
}
89
return true;
90
}
91
92
interface ISelectorMatch {
93
operator: string;
94
version: string;
95
}
96
97
interface ISelector {
98
name: string;
99
matches: ISelectorMatch[];
100
}
101
102
function convertToSemVer(version: IVersion) {
103
return [version.major, version.minor, version.patch].filter(x => x).join('.');
104
}
105
106
function extractUserAgentSelectors(userAgentSelector: string): ISelector[] {
107
const selectorByName: { [name: string]: ISelector } = {};
108
const parts = userAgentSelector
109
.substr(1)
110
.toLowerCase()
111
.split('&')
112
.map(x => x.trim());
113
for (const part of parts) {
114
const matches = part.match(/^([a-z\s-]+)([\s><=]+)?([0-9.x*]+)?/);
115
if (!matches?.length) continue;
116
const [rawName, rawOperator, rawVersion] = matches.slice(1);
117
const name = cleanupName(rawName);
118
const operator = cleanupOperator(rawOperator);
119
let version = cleanupVersion(rawVersion);
120
selectorByName[name] = selectorByName[name] || { name, matches: [] };
121
122
if (operator === '=') {
123
const versionParts = version.split('.');
124
const missingParts = 3 - versionParts.length;
125
for (let i = 0; i < missingParts; i += 1) {
126
versionParts.push('*');
127
}
128
version = versionParts.join('.');
129
}
130
if (version) selectorByName[name].matches.push({ operator, version });
131
}
132
133
return Object.values(selectorByName);
134
}
135
136
function cleanupName(name: string) {
137
name = name.trim();
138
if (name.startsWith('chrome')) return 'chrome';
139
if (name.startsWith('firefox')) return 'firefox';
140
if (name.startsWith('safari')) return 'safari';
141
if (name.startsWith('mac')) return 'mac-os';
142
if (name.startsWith('win')) return 'windows';
143
if (name.startsWith('linux')) return 'linux';
144
return name.split(' ')[0];
145
}
146
147
function cleanupOperator(operator: string) {
148
if (!operator) return '=';
149
return operator.replace(/[^<>=]+/g, '');
150
}
151
152
function cleanupVersion(version: string) {
153
if (!version) return '*';
154
return version.trim().replace(/[^0-9x*]+/g, '.');
155
}
156
157
function createUserAgentOption(userAgentString: string): IUserAgentOption {
158
const uaParser = new UAParser(userAgentString);
159
const uaBrowser = uaParser.getBrowser();
160
const uaOs = uaParser.getOS();
161
162
const [browserVersionMajor, browserVersionMinor, browserVersionPatch] = uaBrowser.version
163
.split('.')
164
.map(x => Number(x));
165
const browserName = (uaBrowser.name || '').toLowerCase().replace(' ', '-');
166
167
let [osVersionMajor, osVersionMinor] = uaOs.version.split('.').map(x => Number(x));
168
const operatingSystemName = (uaOs.name || '').toLowerCase().replace(' ', '-');
169
if (osVersionMajor === 10 && osVersionMinor === 16) {
170
osVersionMajor = 11;
171
osVersionMinor = undefined;
172
}
173
174
return {
175
browserName,
176
browserVersion: {
177
major: browserVersionMajor ? String(browserVersionMajor) : '1',
178
minor: browserVersionMinor ? String(browserVersionMinor) : '0',
179
patch: browserVersionPatch ? String(browserVersionPatch) : undefined,
180
},
181
operatingSystemName,
182
operatingSystemPlatform: '',
183
operatingSystemVersion: {
184
major: osVersionMajor ? String(osVersionMajor) : undefined,
185
minor: osVersionMinor ? String(osVersionMinor) : undefined,
186
},
187
string: userAgentString,
188
};
189
}
190
191