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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "A high-level API to control Orbita browser over GoLogin API",
5
5
  "types": "./index.d.ts",
6
6
  "main": "./src/index.js",
@@ -29,11 +29,13 @@ export const deleteExtensionArchive = (dest) => {
29
29
  throw new Error('Missing parameter');
30
30
  }
31
31
 
32
- return access(dest)
33
- .then(
34
- () => unlink(dest),
35
- () => Promise.resolve(),
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 || 13 * 1000;
80
- this.proxyCheckAttempts = options.proxyCheckAttempts || 3;
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
- startupDepsPromise.then(() => removePath(profilePath)
472
- .then(() => debug('-', profilePath, 'dropped'))),
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
- this.getTimeZone(proxy).catch((e) => {
531
- console.error('Proxy Error. Check it and try again.');
532
- throw new Error(`Proxy Error. ${e.message}`);
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 _promises.rm(join(profilePath, 'Default', 'Sync Data'), { recursive: true }).catch(() => null);
538
- const pref_file_name = join(profilePath, 'Default', 'Preferences');
539
- debug('reading', pref_file_name);
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 prefFileExists = await access(pref_file_name).then(() => true).catch(() => false);
542
- if (!prefFileExists) {
543
- debug('Preferences file not exists waiting', pref_file_name, '. Using empty profile');
544
- await mkdir(join(profilePath, 'Default'), { recursive: true });
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
- this.setupProfileExtensions({
556
- preferences, allExtensions, userChromeExtensions, profilePath,
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 writeFile(join(profilePath, 'orbita.config'), JSON.stringify(orbitaConfig, null, '\t'), { encoding: 'utf-8' }).catch(console.log);
610
- await writeFile(join(profilePath, 'Default', 'Preferences'), JSON.stringify(prefsToWrite));
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 this.getRandomPort();
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 this.getBrowserChecker();
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 this.getTimeZone(this.proxy).catch((e) => {
1011
- console.error('Proxy Error. Check it and try again.');
1012
- throw e;
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 makeRequest(
1094
- `http://127.0.0.1:${remote_debugging_port}/json/version`,
1095
- { json: true, maxAttempts: 60, retryDelay: 100, method: 'GET' },
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
- await this.createStartup(true);
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
- req.on('error', reject);
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
- req.destroy(new Error(`Request timeout after ${timeoutMs}ms`));
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
  }