gologin 3.0.2 → 3.0.4
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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/src/extensions/extensions-extractor.js +7 -5
- package/src/extensions/extensions-manager.js +3 -1
- package/src/gologin.js +107 -46
- package/src/utils/http.js +33 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
Combined changelog for GoLogin node.js SDK
|
|
4
4
|
|
|
5
|
+
## [3.0.4] 2026-09-04
|
|
6
|
+
|
|
7
|
+
### Fixes
|
|
8
|
+
|
|
9
|
+
* Fixed proxy request timeout not aborting hung TCP connects during timezone checks
|
|
10
|
+
* Reduced default proxy check timeout to 10s and attempts to 2
|
|
11
|
+
|
|
12
|
+
## [3.0.3] 2026-08-03
|
|
13
|
+
|
|
14
|
+
### Fixes
|
|
15
|
+
|
|
16
|
+
* Fixed Chrome extensions download race when the same extension IDs are listed in both `chromeExtensions` and `userChromeExtensions`
|
|
17
|
+
* Made extension archive deletion ignore missing files (`ENOENT`) during parallel extract cleanup
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
* Added profile start step timings via `debug` (`DEBUG=gologin`)
|
|
22
|
+
|
|
5
23
|
## [3.0.2] 2026-07-29
|
|
6
24
|
|
|
7
25
|
### Features
|
package/package.json
CHANGED
|
@@ -29,11 +29,13 @@ export const deleteExtensionArchive = (dest) => {
|
|
|
29
29
|
throw new Error('Missing parameter');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
return unlink(dest).catch((error) => {
|
|
33
|
+
if (error?.code === 'ENOENT') {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
const withRetry = optionsOrUndefined => {
|
|
@@ -107,11 +107,13 @@ export class ExtensionsManager extends UserExtensionsManager {
|
|
|
107
107
|
|
|
108
108
|
return originalId;
|
|
109
109
|
});
|
|
110
|
+
const queuedOriginalIds = new Set();
|
|
110
111
|
|
|
111
112
|
return profileExtensions.reduce((res, val) => {
|
|
112
113
|
const [originalId] = val.split('@');
|
|
113
|
-
const extensionExists = existedOriginalIds.includes(originalId);
|
|
114
|
+
const extensionExists = existedOriginalIds.includes(originalId) || queuedOriginalIds.has(originalId);
|
|
114
115
|
if (!extensionExists) {
|
|
116
|
+
queuedOriginalIds.add(originalId);
|
|
115
117
|
res.push(val);
|
|
116
118
|
}
|
|
117
119
|
|
package/src/gologin.js
CHANGED
|
@@ -40,6 +40,15 @@ const PROXY_NONE = 'none';
|
|
|
40
40
|
const debug = debugDefault('gologin');
|
|
41
41
|
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
42
42
|
|
|
43
|
+
const withTiming = async ({ name, promise }) => {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
try {
|
|
46
|
+
return await promise;
|
|
47
|
+
} finally {
|
|
48
|
+
debug(`[timing] ${name}: ${Date.now() - start}ms`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
43
52
|
export class GoLogin {
|
|
44
53
|
constructor(options = {}) {
|
|
45
54
|
this.browserLang = 'en-US';
|
|
@@ -76,8 +85,8 @@ export class GoLogin {
|
|
|
76
85
|
this.processKillTimeout = 1 * 1000;
|
|
77
86
|
this.browserMajorVersion = options.browserMajorVersion || 0;
|
|
78
87
|
this.newProxyOrbitaMajorVersion = 135;
|
|
79
|
-
this.proxyCheckTimeout = options.proxyCheckTimeout ||
|
|
80
|
-
this.proxyCheckAttempts = options.proxyCheckAttempts ||
|
|
88
|
+
this.proxyCheckTimeout = options.proxyCheckTimeout || 10 * 1000;
|
|
89
|
+
this.proxyCheckAttempts = options.proxyCheckAttempts || 2;
|
|
81
90
|
this.customChromeFrame = options.customChromeFrame;
|
|
82
91
|
|
|
83
92
|
if (options.tmpdir) {
|
|
@@ -463,13 +472,17 @@ export class GoLogin {
|
|
|
463
472
|
}
|
|
464
473
|
|
|
465
474
|
async createStartup(local = false) {
|
|
475
|
+
const startupStart = Date.now();
|
|
466
476
|
const profilePath = join(this.tmpdir, `gologin_profile_${this.profile_id}`);
|
|
467
477
|
const startupDepsPromise = preloadStartupDeps();
|
|
468
478
|
|
|
469
479
|
const [profile] = await Promise.all([
|
|
470
|
-
this.getProfile(),
|
|
471
|
-
|
|
472
|
-
|
|
480
|
+
withTiming({ name: 'getProfile', promise: this.getProfile() }),
|
|
481
|
+
withTiming({
|
|
482
|
+
name: 'preloadDepsAndClearProfile',
|
|
483
|
+
promise: startupDepsPromise.then(() => removePath(profilePath)
|
|
484
|
+
.then(() => debug('-', profilePath, 'dropped'))),
|
|
485
|
+
}),
|
|
473
486
|
]);
|
|
474
487
|
|
|
475
488
|
if (!profile) {
|
|
@@ -525,37 +538,55 @@ export class GoLogin {
|
|
|
525
538
|
this.proxy = proxy;
|
|
526
539
|
|
|
527
540
|
await Promise.all([
|
|
528
|
-
this.resolveProfileBrowserVersion(profile),
|
|
529
|
-
this.initProfileStorage({ profile, local }),
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
541
|
+
withTiming({ name: 'resolveProfileBrowserVersion', promise: this.resolveProfileBrowserVersion(profile) }),
|
|
542
|
+
withTiming({ name: 'initProfileStorage', promise: this.initProfileStorage({ profile, local }) }),
|
|
543
|
+
withTiming({
|
|
544
|
+
name: 'getTimeZone',
|
|
545
|
+
promise: this.getTimeZone(proxy).catch((e) => {
|
|
546
|
+
console.error('Proxy Error. Check it and try again.');
|
|
547
|
+
throw new Error(`Proxy Error. ${e.message}`);
|
|
548
|
+
}),
|
|
533
549
|
}),
|
|
534
|
-
this.initCookiesFile(profile),
|
|
550
|
+
withTiming({ name: 'initCookiesFile', promise: this.initCookiesFile(profile) }),
|
|
535
551
|
]);
|
|
536
552
|
|
|
537
|
-
await
|
|
538
|
-
|
|
539
|
-
|
|
553
|
+
await withTiming({
|
|
554
|
+
name: 'preparePreferencesFile',
|
|
555
|
+
promise: (async () => {
|
|
556
|
+
await _promises.rm(join(profilePath, 'Default', 'Sync Data'), { recursive: true }).catch(() => null);
|
|
557
|
+
const pref_file_name = join(profilePath, 'Default', 'Preferences');
|
|
558
|
+
debug('reading', pref_file_name);
|
|
559
|
+
|
|
560
|
+
const prefFileExists = await access(pref_file_name).then(() => true).catch(() => false);
|
|
561
|
+
if (!prefFileExists) {
|
|
562
|
+
debug('Preferences file not exists waiting', pref_file_name, '. Using empty profile');
|
|
563
|
+
await mkdir(join(profilePath, 'Default'), { recursive: true });
|
|
564
|
+
await writeFile(pref_file_name, '{}');
|
|
565
|
+
}
|
|
566
|
+
})(),
|
|
567
|
+
});
|
|
540
568
|
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
await writeFile(pref_file_name, '{}');
|
|
546
|
-
}
|
|
569
|
+
const preferences_raw = await withTiming({
|
|
570
|
+
name: 'readPreferences',
|
|
571
|
+
promise: readFile(join(profilePath, 'Default', 'Preferences')),
|
|
572
|
+
});
|
|
547
573
|
|
|
548
|
-
const preferences_raw = await readFile(pref_file_name);
|
|
549
574
|
const preferences = JSON.parse(preferences_raw.toString());
|
|
550
575
|
const chromeExtensions = get(profile, 'chromeExtensions') || [];
|
|
551
576
|
const userChromeExtensions = get(profile, 'userChromeExtensions') || [];
|
|
552
|
-
const allExtensions = [...chromeExtensions, ...userChromeExtensions];
|
|
577
|
+
const allExtensions = [...new Set([...chromeExtensions, ...userChromeExtensions])];
|
|
553
578
|
|
|
554
579
|
const [, orbitaParamsToken] = await Promise.all([
|
|
555
|
-
|
|
556
|
-
|
|
580
|
+
withTiming({
|
|
581
|
+
name: 'setupProfileExtensions',
|
|
582
|
+
promise: this.setupProfileExtensions({
|
|
583
|
+
preferences,
|
|
584
|
+
allExtensions,
|
|
585
|
+
userChromeExtensions: [...new Set(userChromeExtensions)],
|
|
586
|
+
profilePath,
|
|
587
|
+
}),
|
|
557
588
|
}),
|
|
558
|
-
this.fetchOrbitaParamsToken(profile),
|
|
589
|
+
withTiming({ name: 'fetchOrbitaParamsToken', promise: this.fetchOrbitaParamsToken(profile) }),
|
|
559
590
|
]);
|
|
560
591
|
|
|
561
592
|
if (this.fontsMasking) {
|
|
@@ -606,14 +637,21 @@ export class GoLogin {
|
|
|
606
637
|
},
|
|
607
638
|
};
|
|
608
639
|
|
|
609
|
-
await
|
|
610
|
-
|
|
640
|
+
await withTiming({
|
|
641
|
+
name: 'writeProfileFiles',
|
|
642
|
+
promise: (async () => {
|
|
643
|
+
await writeFile(join(profilePath, 'orbita.config'), JSON.stringify(orbitaConfig, null, '\t'), { encoding: 'utf-8' }).catch(console.log);
|
|
644
|
+
await writeFile(join(profilePath, 'Default', 'Preferences'), JSON.stringify(prefsToWrite));
|
|
645
|
+
|
|
646
|
+
const bookmarksParsedData = await getCurrentProfileBookmarks(this.bookmarksFilePath);
|
|
647
|
+
const bookmarksFromDb = profile.bookmarks?.bookmark_bar;
|
|
648
|
+
bookmarksParsedData.roots = bookmarksFromDb ? profile.bookmarks : bookmarksParsedData.roots;
|
|
649
|
+
await writeFile(this.bookmarksFilePath, JSON.stringify(bookmarksParsedData));
|
|
650
|
+
})(),
|
|
651
|
+
});
|
|
611
652
|
|
|
612
|
-
const bookmarksParsedData = await getCurrentProfileBookmarks(this.bookmarksFilePath);
|
|
613
|
-
const bookmarksFromDb = profile.bookmarks?.bookmark_bar;
|
|
614
|
-
bookmarksParsedData.roots = bookmarksFromDb ? profile.bookmarks : bookmarksParsedData.roots;
|
|
615
|
-
await writeFile(this.bookmarksFilePath, JSON.stringify(bookmarksParsedData));
|
|
616
653
|
debug('Profile ready. Path: ', profilePath, 'PROXY', JSON.stringify(get(preferences, 'gologin.proxy')));
|
|
654
|
+
debug(`[timing] createStartup total: ${Date.now() - startupStart}ms`);
|
|
617
655
|
|
|
618
656
|
return profilePath;
|
|
619
657
|
}
|
|
@@ -723,8 +761,9 @@ export class GoLogin {
|
|
|
723
761
|
|
|
724
762
|
const profileExtensionPathRes = extensionsResult.find(el => 'profileExtensionsCheckRes' in el) || {};
|
|
725
763
|
const profileUserExtensionPathRes = extensionsResult.find(el => 'profileUserExtensionsCheckRes' in el);
|
|
726
|
-
profileExtensionsCheckRes =
|
|
727
|
-
(profileExtensionPathRes?.profileExtensionsCheckRes || []).concat(profileUserExtensionPathRes?.profileUserExtensionsCheckRes || [])
|
|
764
|
+
profileExtensionsCheckRes = [...new Set(
|
|
765
|
+
(profileExtensionPathRes?.profileExtensionsCheckRes || []).concat(profileUserExtensionPathRes?.profileUserExtensionsCheckRes || []),
|
|
766
|
+
)];
|
|
728
767
|
}
|
|
729
768
|
|
|
730
769
|
let extSettings;
|
|
@@ -978,9 +1017,13 @@ export class GoLogin {
|
|
|
978
1017
|
}
|
|
979
1018
|
|
|
980
1019
|
async spawnBrowser() {
|
|
1020
|
+
const spawnStart = Date.now();
|
|
981
1021
|
let { remote_debugging_port, customArgs } = this;
|
|
982
1022
|
if (!remote_debugging_port) {
|
|
983
|
-
remote_debugging_port = await
|
|
1023
|
+
remote_debugging_port = await withTiming({
|
|
1024
|
+
name: 'getRandomPort',
|
|
1025
|
+
promise: this.getRandomPort(),
|
|
1026
|
+
});
|
|
984
1027
|
}
|
|
985
1028
|
|
|
986
1029
|
const profile_path = this.profilePath();
|
|
@@ -996,7 +1039,11 @@ export class GoLogin {
|
|
|
996
1039
|
|
|
997
1040
|
let ORBITA_BROWSER = this.executablePath;
|
|
998
1041
|
if (!ORBITA_BROWSER) {
|
|
999
|
-
const browserChecker = await
|
|
1042
|
+
const browserChecker = await withTiming({
|
|
1043
|
+
name: 'getBrowserChecker',
|
|
1044
|
+
promise: this.getBrowserChecker(),
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1000
1047
|
ORBITA_BROWSER = browserChecker.getOrbitaPath;
|
|
1001
1048
|
}
|
|
1002
1049
|
|
|
@@ -1007,9 +1054,12 @@ export class GoLogin {
|
|
|
1007
1054
|
env[key] = process.env[key];
|
|
1008
1055
|
});
|
|
1009
1056
|
|
|
1010
|
-
const tz = await
|
|
1011
|
-
|
|
1012
|
-
|
|
1057
|
+
const tz = await withTiming({
|
|
1058
|
+
name: 'getTimeZone',
|
|
1059
|
+
promise: this.getTimeZone(this.proxy).catch((e) => {
|
|
1060
|
+
console.error('Proxy Error. Check it and try again.');
|
|
1061
|
+
throw e;
|
|
1062
|
+
}),
|
|
1013
1063
|
});
|
|
1014
1064
|
|
|
1015
1065
|
env.TZ = tz;
|
|
@@ -1090,17 +1140,23 @@ export class GoLogin {
|
|
|
1090
1140
|
|
|
1091
1141
|
if (this.waitWebsocket) {
|
|
1092
1142
|
debug('GETTING WS URL FROM BROWSER');
|
|
1093
|
-
const data = await
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1143
|
+
const data = await withTiming({
|
|
1144
|
+
name: 'waitWebsocket',
|
|
1145
|
+
promise: makeRequest(
|
|
1146
|
+
`http://127.0.0.1:${remote_debugging_port}/json/version`,
|
|
1147
|
+
{ json: true, maxAttempts: 120, retryDelay: 100, method: 'GET' },
|
|
1148
|
+
),
|
|
1149
|
+
});
|
|
1097
1150
|
|
|
1098
1151
|
debug('WS IS', get(data, 'webSocketDebuggerUrl', ''));
|
|
1099
1152
|
this.is_active = true;
|
|
1153
|
+
debug(`[timing] spawnBrowser total: ${Date.now() - spawnStart}ms`);
|
|
1100
1154
|
|
|
1101
1155
|
return { wsUrl: get(data, 'webSocketDebuggerUrl', ''), resolution: this.resolution };
|
|
1102
1156
|
}
|
|
1103
1157
|
|
|
1158
|
+
debug(`[timing] spawnBrowser total: ${Date.now() - spawnStart}ms`);
|
|
1159
|
+
|
|
1104
1160
|
return '';
|
|
1105
1161
|
}
|
|
1106
1162
|
|
|
@@ -1508,10 +1564,12 @@ export class GoLogin {
|
|
|
1508
1564
|
|
|
1509
1565
|
async start() {
|
|
1510
1566
|
// ensureSentryInitialized({ release: process.env.npm_package_version || '2.1.34' });
|
|
1567
|
+
const startTotal = Date.now();
|
|
1511
1568
|
|
|
1512
|
-
await this.createStartup();
|
|
1513
|
-
const startResponse = await this.spawnBrowser();
|
|
1569
|
+
await withTiming({ name: 'createStartup', promise: this.createStartup() });
|
|
1570
|
+
const startResponse = await withTiming({ name: 'spawnBrowser', promise: this.spawnBrowser() });
|
|
1514
1571
|
this.setActive(true);
|
|
1572
|
+
debug(`[timing] start total: ${Date.now() - startTotal}ms`);
|
|
1515
1573
|
|
|
1516
1574
|
return {
|
|
1517
1575
|
status: 'success',
|
|
@@ -1521,11 +1579,14 @@ export class GoLogin {
|
|
|
1521
1579
|
}
|
|
1522
1580
|
|
|
1523
1581
|
async startLocal() {
|
|
1524
|
-
|
|
1582
|
+
const startTotal = Date.now();
|
|
1583
|
+
|
|
1584
|
+
await withTiming({ name: 'createStartup', promise: this.createStartup(true) });
|
|
1525
1585
|
|
|
1526
1586
|
// await this.createBrowserExtension();
|
|
1527
|
-
const startResponse = await this.spawnBrowser();
|
|
1587
|
+
const startResponse = await withTiming({ name: 'spawnBrowser', promise: this.spawnBrowser() });
|
|
1528
1588
|
this.setActive(true);
|
|
1589
|
+
debug(`[timing] startLocal total: ${Date.now() - startTotal}ms`);
|
|
1529
1590
|
|
|
1530
1591
|
return { status: 'success', wsUrl: startResponse.wsUrl };
|
|
1531
1592
|
}
|
package/src/utils/http.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { get as _get, request as httpsRequest } from 'https';
|
|
2
1
|
import { request as httpRequest } from 'http';
|
|
2
|
+
import { get as _get, request as httpsRequest } from 'https';
|
|
3
3
|
|
|
4
4
|
import packageJson from '../../package.json' with { type: 'json' };
|
|
5
5
|
import { loadHttpsProxyAgent } from './lazy-deps.js';
|
|
@@ -115,9 +115,11 @@ const normalizeProxyUrl = (proxyUrl) => {
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
const executeProxyRequest = async (url, options, internalOptions) => {
|
|
118
|
-
const HttpsProxyAgent = await loadHttpsProxyAgent();
|
|
119
|
-
const agent = new HttpsProxyAgent(normalizeProxyUrl(options.proxy));
|
|
120
118
|
const timeoutMs = options.timeout || DEFAULT_TIMEOUT_MS;
|
|
119
|
+
const HttpsProxyAgent = await loadHttpsProxyAgent();
|
|
120
|
+
const agent = new HttpsProxyAgent(normalizeProxyUrl(options.proxy), {
|
|
121
|
+
timeout: timeoutMs,
|
|
122
|
+
});
|
|
121
123
|
const headers = buildRequestHeaders({ options, internalOptions });
|
|
122
124
|
const body = buildRequestBody(options);
|
|
123
125
|
const method = options.method || 'GET';
|
|
@@ -139,6 +141,19 @@ const executeProxyRequest = async (url, options, internalOptions) => {
|
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
return new Promise((resolve, reject) => {
|
|
144
|
+
let settled = false;
|
|
145
|
+
let timeoutId;
|
|
146
|
+
|
|
147
|
+
const settle = (handler) => (value) => {
|
|
148
|
+
if (settled) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
settled = true;
|
|
153
|
+
clearTimeout(timeoutId);
|
|
154
|
+
handler(value);
|
|
155
|
+
};
|
|
156
|
+
|
|
142
157
|
const req = requestFn(requestOptions, (response) => {
|
|
143
158
|
let responseText = '';
|
|
144
159
|
|
|
@@ -151,18 +166,28 @@ const executeProxyRequest = async (url, options, internalOptions) => {
|
|
|
151
166
|
const error = new Error(responseText);
|
|
152
167
|
error.statusCode = response.statusCode;
|
|
153
168
|
|
|
154
|
-
reject(error);
|
|
169
|
+
settle(reject)(error);
|
|
155
170
|
|
|
156
171
|
return;
|
|
157
172
|
}
|
|
158
173
|
|
|
159
|
-
resolve(parseProxyResponseBody(responseText, options));
|
|
174
|
+
settle(resolve)(parseProxyResponseBody(responseText, options));
|
|
160
175
|
});
|
|
176
|
+
|
|
177
|
+
response.on('error', settle(reject));
|
|
161
178
|
});
|
|
162
179
|
|
|
163
|
-
|
|
180
|
+
timeoutId = setTimeout(() => {
|
|
181
|
+
const timeoutError = new Error(`Request timeout after ${timeoutMs}ms`);
|
|
182
|
+
settle(reject)(timeoutError);
|
|
183
|
+
req.destroy(timeoutError);
|
|
184
|
+
}, timeoutMs);
|
|
185
|
+
|
|
186
|
+
req.on('error', settle(reject));
|
|
164
187
|
req.on('timeout', () => {
|
|
165
|
-
|
|
188
|
+
const timeoutError = new Error(`Request timeout after ${timeoutMs}ms`);
|
|
189
|
+
settle(reject)(timeoutError);
|
|
190
|
+
req.destroy(timeoutError);
|
|
166
191
|
});
|
|
167
192
|
|
|
168
193
|
if (body) {
|
|
@@ -242,6 +267,7 @@ export const makeRequest = async (url, options = {}, internalOptions) => {
|
|
|
242
267
|
try {
|
|
243
268
|
return await executeFetch(url, options, internalOptions);
|
|
244
269
|
} catch (error) {
|
|
270
|
+
console.log('makeRequest error', error);
|
|
245
271
|
if (internalOptions?.fallbackUrl && !error.statusCode) {
|
|
246
272
|
return executeFetch(internalOptions.fallbackUrl, options, internalOptions);
|
|
247
273
|
}
|