gologin 2.1.32 → 2.1.34

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/.eslintrc.json CHANGED
@@ -4,6 +4,7 @@
4
4
  "es2021": true,
5
5
  "node": true
6
6
  },
7
+ "parser": "@babel/eslint-parser",
7
8
  "extends": "eslint:recommended",
8
9
  "overrides": [
9
10
  {
@@ -20,8 +21,19 @@
20
21
  }
21
22
  ],
22
23
  "parserOptions": {
23
- "ecmaVersion": 2020,
24
- "sourceType": "module"
24
+ "ecmaVersion": "latest",
25
+ "sourceType": "module",
26
+ "requireConfigFile": false,
27
+ "babelOptions": {
28
+ "plugins": [
29
+ [
30
+ "@babel/plugin-syntax-import-assertions",
31
+ {
32
+ "deprecatedAssertSyntax": true
33
+ }
34
+ ]
35
+ ]
36
+ }
25
37
  },
26
38
  "plugins": [
27
39
  "simple-import-sort"
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  Combined changelog for GoLogin node.js SDK
4
4
 
5
+ ## [2.1.34] 2025-09-11
6
+
7
+
8
+ ### Fixes
9
+
10
+ * Errors grouping for internal uses
11
+
12
+ ## [2.1.33] 2025-09-11
13
+
14
+
15
+ ### Fixes
16
+
17
+ * Orbita download doesnt interupts while opening two profiles in parallel
18
+ * Error gathering changes
19
+
5
20
  ## [2.1.32] 2025-09-11
6
21
 
7
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "2.1.32",
3
+ "version": "2.1.34",
4
4
  "description": "A high-level API to control Orbita browser over GoLogin API",
5
5
  "types": "./index.d.ts",
6
6
  "main": "./src/gologin.js",
@@ -9,7 +9,7 @@
9
9
  "url": "git+https://github.com/gologinapp/gologin.git"
10
10
  },
11
11
  "engines": {
12
- "node": ">=16.0.0"
12
+ "node": ">=20.0.0"
13
13
  },
14
14
  "type": "module",
15
15
  "author": "The GoLogin Authors",
@@ -39,6 +39,9 @@
39
39
  "example": "examples"
40
40
  },
41
41
  "devDependencies": {
42
+ "@babel/core": "^7.28.5",
43
+ "@babel/eslint-parser": "^7.28.5",
44
+ "@babel/plugin-syntax-import-assertions": "^7.27.1",
42
45
  "eslint": "^8.21.0",
43
46
  "eslint-plugin-simple-import-sort": "^8.0.0"
44
47
  },
@@ -7,9 +7,10 @@ import { homedir } from 'os';
7
7
  import { join } from 'path';
8
8
  import ProgressBar from 'progress';
9
9
  import util from 'util';
10
- import { createInterface } from 'readline';
11
10
 
12
11
  import { API_URL, getOS } from '../utils/common.js';
12
+ import { makeRequest } from '../utils/http.js';
13
+ import BrowserDownloadLockManager from './browser-download-manager.js';
13
14
 
14
15
  const exec = util.promisify(execNonPromise);
15
16
  const { access, mkdir, readdir, rmdir, unlink, copyFile, readlink, symlink, lstat, rename, writeFile, readFile } = _promises;
@@ -19,37 +20,31 @@ const ARCH = process.arch;
19
20
 
20
21
  const VERSION_FILE = 'latest-version.txt';
21
22
 
22
- const WIN_FOLDERSIZE_FILE = 'foldersize.txt';
23
- const WIN_FOLDERSIZE_FILE_LINK = `https://orbita-browser-windows.gologin.com/${WIN_FOLDERSIZE_FILE}`;
24
-
25
23
  const BROWSER_ARCHIVE_NAME = `orbita-browser-latest.${PLATFORM === 'win32' ? 'zip' : 'tar.gz'}`;
26
24
 
27
25
  const MAC_HASH_FILE = 'hashfile.mtree';
28
26
  const DEB_HASH_FILE = 'hashfile.txt';
29
- const WIN_HASH_FILE = DEB_HASH_FILE;
30
27
  const MAC_HASHFILE_LINK = `https://orbita-browser-mac.gologin.com/${MAC_HASH_FILE}`;
31
28
  const DEB_HASHFILE_LINK = `https://orbita-browser-linux.gologin.com/${DEB_HASH_FILE}`;
32
- const WIN_HASHFILE_LINK = `https://orbita-browser-windows.gologin.com/${WIN_HASH_FILE}`;
33
29
  const MAC_ARM_HASHFILE_LINK = `https://orbita-browser-mac-arm.gologin.com/${MAC_HASH_FILE}`;
34
30
 
35
31
  const FAIL_SUM_MATCH_MESSAGE = 'hash_sum_not_matched';
36
32
  const EXTRACTED_FOLDER = 'extracted-browser';
37
33
 
38
34
  export class BrowserChecker {
39
- #homedir = homedir();
40
- #browserPath = join(this.#homedir, '.gologin', 'browser');
41
- #executableFilePath;
42
- #skipOrbitaHashChecking = false;
43
-
44
35
  constructor() {
45
-
36
+ this.homedir = homedir();
37
+ this.browserPath = join(this.homedir, '.gologin', 'browser');
38
+ this.executableFilePath = null;
39
+ this.skipOrbitaHashChecking = false;
40
+ this.downloadManager = BrowserDownloadLockManager.getInstance();
41
+ this.downloadManager.cleanupStaleLocks().catch(console.warn);
46
42
  }
47
43
 
48
- async checkBrowser({ autoUpdateBrowser, checkBrowserUpdate, majorVersion }) {
49
- const isBrowserFolderExists = await access(join(this.#browserPath, `orbita-browser-${majorVersion}`)).then(() => true).catch(() => false);
50
-
44
+ async checkBrowser({ autoUpdateBrowser, majorVersion }) {
45
+ const isBrowserFolderExists = await access(join(this.browserPath, `orbita-browser-${majorVersion}`)).then(() => true).catch(() => false);
51
46
  if (!isBrowserFolderExists || autoUpdateBrowser) {
52
- await this.downloadBrowser(majorVersion);
47
+ await this.downloadManager.ensureBrowserDownload(majorVersion, () => this.downloadBrowser(majorVersion));
53
48
 
54
49
  return this.getBrowserExecutablePath(majorVersion);
55
50
  }
@@ -71,8 +66,6 @@ export class BrowserChecker {
71
66
  // return this.getBrowserExecutablePath(majorVersion);
72
67
  // }
73
68
 
74
-
75
-
76
69
  // return new Promise(resolve => {
77
70
  // const rl = createInterface(process.stdin, process.stdout);
78
71
  // const timeout = setTimeout(() => {
@@ -94,9 +87,9 @@ export class BrowserChecker {
94
87
  }
95
88
 
96
89
  async downloadBrowser(majorVersion) {
97
- await mkdir(this.#browserPath, { recursive: true });
90
+ await mkdir(this.browserPath, { recursive: true });
98
91
 
99
- const browserPath = join(this.#browserPath, BROWSER_ARCHIVE_NAME);
92
+ const browserPath = join(this.browserPath, BROWSER_ARCHIVE_NAME);
100
93
 
101
94
  const browserDownloadUrl = this.getBrowserDownloadUrl(majorVersion);
102
95
 
@@ -110,13 +103,13 @@ export class BrowserChecker {
110
103
  const os = getOS();
111
104
  switch (os) {
112
105
  case 'mac':
113
- return join(this.#browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
106
+ return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
114
107
  case 'win':
115
- return join(this.#browserPath, `orbita-browser-${majorVersion}`, 'chrome.exe');
108
+ return join(this.browserPath, `orbita-browser-${majorVersion}`, 'chrome.exe');
116
109
  case 'macM1':
117
- return join(this.#browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
110
+ return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
118
111
  default:
119
- return join(this.#browserPath, `orbita-browser-${majorVersion}`, 'chrome');
112
+ return join(this.browserPath, `orbita-browser-${majorVersion}`, 'chrome');
120
113
  }
121
114
  }
122
115
 
@@ -129,14 +122,16 @@ export class BrowserChecker {
129
122
  return `https://orbita-browser-windows.gologin.com/orbita-browser-latest-${majorVersion}.zip`;
130
123
  case 'macM1':
131
124
  return `https://orbita-browser-mac-arm.gologin.com/orbita-browser-latest-${majorVersion}.tar.gz`;
125
+ case 'linArm':
126
+ return `https://orbita-browser-linux-arm.gologin.com/orbita-browser-latest-${majorVersion}.tar.gz`;
132
127
  default:
133
128
  return `https://orbita-browser-linux.gologin.com/orbita-browser-latest-${majorVersion}.tar.gz`;
134
129
  }
135
130
  }
136
131
 
137
132
  addLatestVersion(latestVersion) {
138
- return mkdir(join(this.#browserPath, 'orbita-browser', 'version'), { recursive: true })
139
- .then(() => writeFile(join(this.#browserPath, 'orbita-browser', 'version', 'latest-version.txt'), latestVersion));
133
+ return mkdir(join(this.browserPath, 'orbita-browser', 'version'), { recursive: true })
134
+ .then(() => writeFile(join(this.browserPath, 'orbita-browser', 'version', 'latest-version.txt'), latestVersion));
140
135
  }
141
136
 
142
137
  async downloadBrowserArchive(link, pathStr) {
@@ -155,6 +150,7 @@ export class BrowserChecker {
155
150
  const formattedLen = len / 1024 / 1024;
156
151
  if (isNaN(formattedLen)) {
157
152
  reject(new Error('Error downloading browser'));
153
+
158
154
  return;
159
155
  }
160
156
 
@@ -204,9 +200,9 @@ export class BrowserChecker {
204
200
 
205
201
  async extractBrowser() {
206
202
  console.log('Extracting Orbita');
207
- await mkdir(join(this.#browserPath, EXTRACTED_FOLDER), { recursive: true });
203
+ await mkdir(join(this.browserPath, EXTRACTED_FOLDER), { recursive: true });
208
204
  if (PLATFORM === 'win32') {
209
- return decompress(join(this.#browserPath, BROWSER_ARCHIVE_NAME), join(this.#browserPath, EXTRACTED_FOLDER),
205
+ return decompress(join(this.browserPath, BROWSER_ARCHIVE_NAME), join(this.browserPath, EXTRACTED_FOLDER),
210
206
  {
211
207
  plugins: [decompressUnzip()],
212
208
  filter: file => !file.path.endsWith('/'),
@@ -215,20 +211,20 @@ export class BrowserChecker {
215
211
  }
216
212
 
217
213
  return exec(
218
- `tar xzf ${join(this.#browserPath, BROWSER_ARCHIVE_NAME)} --directory ${join(this.#browserPath, EXTRACTED_FOLDER)}`,
214
+ `tar xzf ${join(this.browserPath, BROWSER_ARCHIVE_NAME)} --directory ${join(this.browserPath, EXTRACTED_FOLDER)}`,
219
215
  );
220
216
  }
221
217
 
222
218
  async downloadHashFile(latestVersion) {
223
219
  let hashLink = DEB_HASHFILE_LINK;
224
- let resultPath = join(this.#browserPath, DEB_HASH_FILE);
220
+ let resultPath = join(this.browserPath, DEB_HASH_FILE);
225
221
  if (PLATFORM === 'darwin') {
226
222
  hashLink = MAC_HASHFILE_LINK;
227
223
  if (ARCH === 'arm64') {
228
224
  hashLink = MAC_ARM_HASHFILE_LINK;
229
225
  }
230
226
 
231
- resultPath = join(this.#browserPath, MAC_HASH_FILE);
227
+ resultPath = join(this.browserPath, MAC_HASH_FILE);
232
228
  }
233
229
 
234
230
  if (latestVersion) {
@@ -256,13 +252,13 @@ export class BrowserChecker {
256
252
  }).on('error', (err) => writableStream.destroy(err)));
257
253
 
258
254
  const hashFile = PLATFORM === 'darwin' ? MAC_HASH_FILE : DEB_HASH_FILE;
259
- const hashFilePath = join(this.#browserPath, hashFile);
255
+ const hashFilePath = join(this.browserPath, hashFile);
260
256
 
261
257
  return access(hashFilePath);
262
258
  }
263
259
 
264
260
  async checkBrowserSum(latestVersion) {
265
- if (this.#skipOrbitaHashChecking) {
261
+ if (this.skipOrbitaHashChecking) {
266
262
  return Promise.resolve();
267
263
  }
268
264
 
@@ -274,7 +270,7 @@ export class BrowserChecker {
274
270
  await this.downloadHashFile(latestVersion);
275
271
  if (PLATFORM === 'darwin') {
276
272
  const calculatedHash = await exec(
277
- `mtree -p ${join(this.#browserPath, EXTRACTED_FOLDER, 'Orbita-Browser.app')} < ${join(this.#browserPath, MAC_HASH_FILE)} || echo ${FAIL_SUM_MATCH_MESSAGE}`,
273
+ `mtree -p ${join(this.browserPath, EXTRACTED_FOLDER, 'Orbita-Browser.app')} < ${join(this.browserPath, MAC_HASH_FILE)} || echo ${FAIL_SUM_MATCH_MESSAGE}`,
278
274
  );
279
275
 
280
276
  const checkedRes = (calculatedHash || '').toString().trim();
@@ -285,16 +281,16 @@ export class BrowserChecker {
285
281
  return;
286
282
  }
287
283
 
288
- const hashFileContent = await exec(`cat ${join(this.#browserPath, DEB_HASH_FILE)}`);
284
+ const hashFileContent = await exec(`cat ${join(this.browserPath, DEB_HASH_FILE)}`);
289
285
  let serverRes = (hashFileContent.stdout || '').toString().trim();
290
286
  serverRes = serverRes.split(' ')[0];
291
287
 
292
288
  const calculateLocalBrowserHash = await exec(
293
- `cd ${join(this.#browserPath, EXTRACTED_FOLDER)} && find orbita-browser -type f -print0 | sort -z | \
294
- xargs -0 sha256sum > ${this.#browserPath}/calculatedFolderSha.txt`,
289
+ `cd ${join(this.browserPath, EXTRACTED_FOLDER)} && find orbita-browser -type f -print0 | sort -z | \
290
+ xargs -0 sha256sum > ${this.browserPath}/calculatedFolderSha.txt`,
295
291
  );
296
292
 
297
- const localHashContent = await exec(`cd ${this.#browserPath} && sha256sum calculatedFolderSha.txt`);
293
+ const localHashContent = await exec(`cd ${this.browserPath} && sha256sum calculatedFolderSha.txt`);
298
294
  let userRes = (localHashContent.stdout || '').toString().trim();
299
295
  userRes = userRes.split(' ')[0];
300
296
  if (userRes !== serverRes) {
@@ -304,28 +300,28 @@ export class BrowserChecker {
304
300
 
305
301
  async replaceBrowser(majorVersion) {
306
302
  console.log('Copy Orbita to target path');
307
- const targetBrowserPath = join(this.#browserPath, `orbita-browser-${majorVersion}`);
303
+ const targetBrowserPath = join(this.browserPath, `orbita-browser-${majorVersion}`);
308
304
  await this.deleteDir(targetBrowserPath);
309
305
 
310
306
  if (PLATFORM === 'darwin') {
311
- return rename(join(this.#browserPath, EXTRACTED_FOLDER), targetBrowserPath);
307
+ return rename(join(this.browserPath, EXTRACTED_FOLDER), targetBrowserPath);
312
308
  }
313
309
 
314
310
  await this.copyDir(
315
- join(this.#browserPath, EXTRACTED_FOLDER, 'orbita-browser'),
311
+ join(this.browserPath, EXTRACTED_FOLDER, 'orbita-browser'),
316
312
  targetBrowserPath,
317
313
  );
318
314
  }
319
315
 
320
316
  async deleteOldArchives() {
321
- await this.deleteDir(join(this.#browserPath, EXTRACTED_FOLDER));
317
+ await this.deleteDir(join(this.browserPath, EXTRACTED_FOLDER));
322
318
 
323
- return readdir(this.#browserPath)
319
+ return readdir(this.browserPath)
324
320
  .then((files) => {
325
321
  const promises = [];
326
322
  files.forEach((filename) => {
327
323
  if (filename.match(/(txt|dylib|mtree)/)) {
328
- promises.push(unlink(join(this.#browserPath, filename)));
324
+ promises.push(unlink(join(this.browserPath, filename)));
329
325
  }
330
326
  });
331
327
 
@@ -355,38 +351,22 @@ export class BrowserChecker {
355
351
  }
356
352
 
357
353
  async getCurrentVersion(majorVersion) {
358
- let versionFilePath = join(this.#browserPath, `orbita-browser-${majorVersion}`, 'version');
354
+ let versionFilePath = join(this.browserPath, `orbita-browser-${majorVersion}`, 'version');
359
355
  if (PLATFORM === 'darwin') {
360
- versionFilePath = join(this.#browserPath, `orbita-browser-${majorVersion}`, 'version', VERSION_FILE);
356
+ versionFilePath = join(this.browserPath, `orbita-browser-${majorVersion}`, 'version', VERSION_FILE);
361
357
  }
362
358
 
363
- return (await readFile(versionFilePath, 'utf8').catch(() => '0.0.0')).replace(/[\r\n\t\f\v\u0000-\u001F\u007F]/g, '');
359
+ return (await readFile(versionFilePath, 'utf8').catch(() => '0.0.0')).replace(/[\r\n\t\f\v\x00-\x1F\x7F]/g, '');
364
360
  }
365
361
 
366
362
  getLatestBrowserVersion() {
367
363
  const userOs = getOS();
368
364
 
369
- return new Promise(resolve => get(`${API_URL}/gologin-global-settings/latest-browser-info?os=${userOs}`,
370
- {
371
- timeout: 15 * 1000,
372
- headers: {
373
- 'Content-Type': 'application/json',
374
- 'User-Agent': 'gologin-api',
375
- },
376
- }, (res) => {
377
- res.setEncoding('utf8');
378
-
379
- let resultResponse = '';
380
- res.on('data', (data) => resultResponse += data);
381
-
382
- res.on('end', () => {
383
- resolve(JSON.parse(resultResponse.trim()));
384
- });
385
- }).on('error', (err) => resolve('')));
365
+ return makeRequest(`${API_URL}/gologin-global-settings/latest-browser-info?os=${userOs}`);
386
366
  }
387
367
 
388
368
  get getOrbitaPath() {
389
- return this.#executableFilePath;
369
+ return this.executableFilePath;
390
370
  }
391
371
 
392
372
  async deleteDir(path = '') {
@@ -0,0 +1,222 @@
1
+ import { spawn } from 'child_process';
2
+ import { promises as _promises } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+
6
+ const { access, writeFile, unlink, mkdir } = _promises;
7
+
8
+ const HOMEDIR = homedir();
9
+ const BROWSER_PATH = join(HOMEDIR, '.gologin', 'browser');
10
+ const LOCK_FILE_PREFIX = 'download-lock-';
11
+
12
+ export class BrowserDownloadLockManager {
13
+ constructor() {
14
+ if (BrowserDownloadLockManager.instance) {
15
+ return BrowserDownloadLockManager.instance;
16
+ }
17
+
18
+ BrowserDownloadLockManager.instance = this;
19
+ BrowserDownloadLockManager.downloadPromises = new Map();
20
+ }
21
+
22
+ static getInstance() {
23
+ if (!BrowserDownloadLockManager.instance) {
24
+ BrowserDownloadLockManager.instance = new BrowserDownloadLockManager();
25
+ }
26
+
27
+ return BrowserDownloadLockManager.instance;
28
+ }
29
+
30
+ async ensureBrowserDownload(majorVersion, downloadFunction) {
31
+ const lockKey = `${majorVersion}`;
32
+
33
+ if (BrowserDownloadLockManager.downloadPromises.has(lockKey)) {
34
+ return BrowserDownloadLockManager.downloadPromises.get(lockKey);
35
+ }
36
+
37
+ const lockFilePath = join(BROWSER_PATH, `${LOCK_FILE_PREFIX}${majorVersion}.json`);
38
+
39
+ try {
40
+ await mkdir(BROWSER_PATH, { recursive: true });
41
+
42
+ const isBrowserExists = await this.checkBrowserExists(majorVersion);
43
+ if (isBrowserExists) {
44
+ return majorVersion;
45
+ }
46
+
47
+ const lockPromise = this.acquireLockAndDownload(lockFilePath, majorVersion, downloadFunction);
48
+ BrowserDownloadLockManager.downloadPromises.set(lockKey, lockPromise);
49
+
50
+ try {
51
+ const result = await lockPromise;
52
+
53
+ return result;
54
+ } finally {
55
+ BrowserDownloadLockManager.downloadPromises.delete(lockKey);
56
+ }
57
+ } catch (error) {
58
+ BrowserDownloadLockManager.downloadPromises.delete(lockKey);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ async acquireLockAndDownload(lockFilePath, majorVersion, downloadFunction) {
64
+ const maxWaitTime = 300000;
65
+ const checkInterval = 1000;
66
+ const startTime = Date.now();
67
+
68
+ while (Date.now() - startTime < maxWaitTime) {
69
+ try {
70
+ await this.createLockFile(lockFilePath, majorVersion);
71
+
72
+ const isBrowserExists = await this.checkBrowserExists(majorVersion);
73
+ if (isBrowserExists) {
74
+ await this.releaseLock(lockFilePath);
75
+
76
+ return majorVersion;
77
+ }
78
+
79
+ await downloadFunction();
80
+ await this.releaseLock(lockFilePath);
81
+
82
+ return majorVersion;
83
+ } catch (error) {
84
+ if (error.code === 'EEXIST') {
85
+ const lockValid = await this.checkLockValidity(lockFilePath);
86
+ if (!lockValid) {
87
+ console.log(`Lock file ${lockFilePath} is stale, removing it`);
88
+ await this.releaseLock(lockFilePath);
89
+ continue;
90
+ }
91
+
92
+ await this.waitForLockRelease(lockFilePath, checkInterval);
93
+ continue;
94
+ }
95
+
96
+ await this.releaseLock(lockFilePath);
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ throw new Error(`Timeout waiting for browser download lock for version ${majorVersion}`);
102
+ }
103
+
104
+ async createLockFile(lockFilePath, majorVersion) {
105
+ const lockData = {
106
+ majorVersion,
107
+ timestamp: Date.now(),
108
+ pid: process.pid,
109
+ };
110
+
111
+ await writeFile(lockFilePath, JSON.stringify(lockData), { flag: 'wx' });
112
+ }
113
+
114
+ async releaseLock(lockFilePath) {
115
+ try {
116
+ await unlink(lockFilePath);
117
+ } catch (error) {
118
+ if (error.code !== 'ENOENT') {
119
+ console.warn('Failed to release download lock:', error.message);
120
+ }
121
+ }
122
+ }
123
+
124
+ async waitForLockRelease(lockFilePath, checkInterval) {
125
+ return new Promise((resolve) => {
126
+ const checkLock = async () => {
127
+ try {
128
+ await access(lockFilePath);
129
+ setTimeout(checkLock, checkInterval);
130
+ } catch (error) {
131
+ if (error.code === 'ENOENT') {
132
+ resolve();
133
+ } else {
134
+ setTimeout(checkLock, checkInterval);
135
+ }
136
+ }
137
+ };
138
+
139
+ checkLock();
140
+ });
141
+ }
142
+
143
+ async checkBrowserExists(majorVersion) {
144
+ try {
145
+ await access(join(BROWSER_PATH, `orbita-browser-${majorVersion}`));
146
+
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ async isProcessRunning(pid) {
154
+ return new Promise((resolve) => {
155
+ const { platform } = process;
156
+ let command;
157
+ let args;
158
+
159
+ if (platform === 'win32') {
160
+ command = 'tasklist';
161
+ args = ['/FI', `PID eq ${pid}`];
162
+ } else {
163
+ command = 'ps';
164
+ args = ['-p', pid.toString()];
165
+ }
166
+
167
+ const child = spawn(command, args, { stdio: 'pipe' });
168
+
169
+ child.on('error', () => resolve(false));
170
+ child.on('close', (code) => {
171
+ resolve(code === 0);
172
+ });
173
+ });
174
+ }
175
+
176
+ async checkLockValidity(lockFilePath) {
177
+ try {
178
+ const lockContent = await _promises.readFile(lockFilePath, 'utf8');
179
+ const lockData = JSON.parse(lockContent);
180
+
181
+ const lockAge = Date.now() - lockData.timestamp;
182
+ const maxLockAge = 300000;
183
+
184
+ if (lockAge > maxLockAge) {
185
+ return false;
186
+ }
187
+
188
+ const isProcessAlive = await this.isProcessRunning(lockData.pid);
189
+
190
+ return isProcessAlive;
191
+ } catch (error) {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ async cleanupStaleLocks() {
197
+ try {
198
+ const files = await _promises.readdir(BROWSER_PATH);
199
+ const lockFiles = files.filter(file => file.startsWith(LOCK_FILE_PREFIX));
200
+
201
+ for (const lockFile of lockFiles) {
202
+ try {
203
+ const lockFilePath = join(BROWSER_PATH, lockFile);
204
+ const isValid = await this.checkLockValidity(lockFilePath);
205
+
206
+ if (!isValid) {
207
+ await unlink(lockFilePath);
208
+ console.log(`Cleaned up stale lock file: ${lockFile}`);
209
+ }
210
+ } catch (error) {
211
+ console.warn(`Failed to process lock file ${lockFile}:`, error.message);
212
+ }
213
+ }
214
+ } catch (error) {
215
+ if (error.code !== 'ENOENT') {
216
+ console.warn('Failed to cleanup stale locks:', error.message);
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ export default BrowserDownloadLockManager;
package/src/gologin.js CHANGED
@@ -5,10 +5,9 @@ import decompress from 'decompress';
5
5
  import decompressUnzip from 'decompress-unzip';
6
6
  import { existsSync, mkdirSync, promises as _promises } from 'fs';
7
7
  import { tmpdir } from 'os';
8
- import { dirname, join, resolve as _resolve, sep } from 'path';
8
+ import { join, resolve as _resolve, sep } from 'path';
9
9
  import rimraf from 'rimraf';
10
10
  import { SocksProxyAgent } from 'socks-proxy-agent';
11
- import { fileURLToPath } from 'url';
12
11
 
13
12
  import { fontsCollection } from '../fonts.js';
14
13
  import { getCurrentProfileBookmarks } from './bookmarks/utils.js';
@@ -33,8 +32,10 @@ import { STORAGE_GATEWAY_BASE_URL } from './utils/constants.js';
33
32
  import { get, isPortReachable } from './utils/utils.js';
34
33
  export { exitAll, GologinApi } from './gologin-api.js';
35
34
  import { checkSocksProxy, makeRequest } from './utils/http.js';
35
+ import { captureGroupedSentryError } from './utils/sentry.js';
36
36
  import { zeroProfileBookmarks } from './utils/zero-profile-bookmarks.js';
37
37
  import { zeroProfilePreferences } from './utils/zero-profile-preferences.js';
38
+
38
39
  const { access, unlink, writeFile, readFile, mkdir, copyFile } = _promises;
39
40
 
40
41
  const SEPARATOR = sep;
@@ -89,7 +90,7 @@ export class GoLogin {
89
90
  dsn: 'https://a13d5939a60ae4f6583e228597f1f2a0@sentry-new.amzn.pro/24',
90
91
  tracesSampleRate: 1.0,
91
92
  defaultIntegrations: false,
92
- release: process.env.npm_package_version || '2.1.24',
93
+ release: process.env.npm_package_version || '2.1.34',
93
94
  });
94
95
  }
95
96
 
@@ -213,16 +214,6 @@ export class GoLogin {
213
214
  console.log('Profile has been uploaded to S3 successfully');
214
215
  }
215
216
 
216
- async emptyProfileFolder() {
217
- debug('get emptyProfileFolder');
218
- const currentDir = dirname(fileURLToPath(import.meta.url));
219
- const zeroProfilePath = join(currentDir, '..', 'zero_profile.zip');
220
- const profile = await readFile(_resolve(zeroProfilePath));
221
- debug('emptyProfileFolder LENGTH ::', profile.length);
222
-
223
- return profile;
224
- }
225
-
226
217
  getGologinPreferences(profileData) {
227
218
  const os = profileData.os || '';
228
219
  const osSpec = profileData.osSpec || '';
@@ -417,9 +408,7 @@ export class GoLogin {
417
408
  debug('extraction done');
418
409
  } catch (e) {
419
410
  console.trace(e);
420
- profile_folder = await this.emptyProfileFolder();
421
- await writeFile(this.profile_zip_path, profile_folder);
422
- await this.extractProfile(profilePath, this.profile_zip_path);
411
+ await this.createZeroProfile(this.createCookiesTableQuery);
423
412
  }
424
413
 
425
414
  const singletonLockPath = join(profilePath, 'SingletonLock');
@@ -1094,6 +1083,7 @@ export class GoLogin {
1094
1083
  debug('browser killed');
1095
1084
  } catch (error) {
1096
1085
  console.error(error);
1086
+ captureGroupedSentryError(error, { method: 'killBrowser', profileId: this.profile_id });
1097
1087
  }
1098
1088
  }
1099
1089
 
@@ -1187,13 +1177,6 @@ export class GoLogin {
1187
1177
  const fingerprint = await this.getRandomFingerprint(options);
1188
1178
  debug('fingerprint=', fingerprint);
1189
1179
 
1190
- if (fingerprint.statusCode === 500) {
1191
- throw new Error('no valid random fingerprint check os param');
1192
- }
1193
-
1194
- if (fingerprint.statusCode === 401) {
1195
- throw new Error('invalid token');
1196
- }
1197
1180
 
1198
1181
  const { navigator, fonts, webGLMetadata, webRTC } = fingerprint;
1199
1182
  let deviceMemory = navigator.deviceMemory || 2;
@@ -1371,18 +1354,20 @@ export class GoLogin {
1371
1354
  }
1372
1355
  } catch (error) {
1373
1356
  if (!isSecondTry && (error.message.includes('table cookies has no column') || error.message.includes('NOT NULL constraint failed'))) {
1374
- await _promises.rm(cookiesPaths.primary, { recursive: true, force: true });
1357
+ await db.close();
1358
+ await _promises.rm(cookiesPaths.primary, { recursive: true, force: true }).catch(() => null);
1375
1359
  await createDBFile({
1376
1360
  cookiesFilePath: cookiesPaths.primary,
1377
1361
  cookiesFileSecondPath: cookiesPaths.secondary,
1378
1362
  createCookiesTableQuery: this.createCookiesTableQuery,
1379
- });
1380
- await this.writeCookiesToFile(cookies, true);
1363
+ }).catch(console.error);
1364
+ await this.writeCookiesToFile(cookies, true).catch(console.error);
1381
1365
 
1382
1366
  return;
1383
1367
  }
1384
1368
 
1385
1369
  console.error(error.message);
1370
+ captureGroupedSentryError(error, { method: 'writeCookiesToFile', profileId: this.profile_id });
1386
1371
  } finally {
1387
1372
  db && await db.close();
1388
1373
  await ensureDirectoryExists(cookiesPaths.primary);
@@ -1407,7 +1392,7 @@ export class GoLogin {
1407
1392
 
1408
1393
  return { status: 'success', wsUrl: startResponse.wsUrl, resolution: startResponse.resolution };
1409
1394
  } catch (error) {
1410
- Sentry.captureException(error);
1395
+ captureGroupedSentryError(error, { method: 'start', profileId: this.profile_id });
1411
1396
  throw error;
1412
1397
  }
1413
1398
  }
@@ -1514,10 +1499,6 @@ export class GoLogin {
1514
1499
  method: 'GET',
1515
1500
  }, { token: this.access_token, fallbackUrl: `${FALLBACK_API_URL}/browser/v2` });
1516
1501
 
1517
- if (profilesResponse.statusCode !== 200) {
1518
- throw new Error('Gologin /browser response error');
1519
- }
1520
-
1521
1502
  return JSON.parse(profilesResponse);
1522
1503
  }
1523
1504
 
@@ -14,6 +14,8 @@ const CHROME_EXT_DIR_NAME = 'chrome-extensions';
14
14
  const EXTENSIONS_PATH = join(HOMEDIR, '.gologin', 'extensions');
15
15
  const CHROME_EXTENSIONS_PATH = join(EXTENSIONS_PATH, CHROME_EXT_DIR_NAME);
16
16
  const USER_EXTENSIONS_PATH = join(HOMEDIR, '.gologin', 'extensions', 'user-extensions');
17
+ const PLATFORM = process.platform;
18
+ const ARCH = process.arch;
17
19
 
18
20
  const composeExtractionPromises = (filteredArchives, destPath = CHROME_EXTENSIONS_PATH) => (
19
21
  filteredArchives.map((extArchivePath) => {
@@ -64,15 +66,15 @@ const getOsAdvanced = async () => {
64
66
  };
65
67
 
66
68
  const getOS = () => {
67
- if (process.platform === 'win32') {
69
+ if (PLATFORM === 'win32') {
68
70
  return 'win';
69
71
  }
70
72
 
71
- if (process.platform === 'darwin') {
72
- return process.arch === 'arm64' ? 'macM1' : 'mac';
73
+ if (PLATFORM === 'darwin') {
74
+ return ARCH === 'arm64' ? 'macM1' : 'mac';
73
75
  }
74
76
 
75
- return 'lin';
77
+ return ARCH === 'arm64' ? 'linArm' : 'lin';
76
78
  };
77
79
 
78
80
  const _composeExtractionPromises = composeExtractionPromises;
package/src/utils/http.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { get as _get } from 'https';
2
2
  import requests from 'requestretry';
3
3
 
4
+ import packageJson from '../../package.json' assert { type: 'json' };
5
+
6
+ const { version } = packageJson;
7
+
4
8
  const TIMEZONE_URL = 'https://geo.myip.link';
5
9
 
6
10
  const createTimeoutPromise = (timeoutMs) => new Promise((_, reject) => {
@@ -32,7 +36,7 @@ const attemptRequest = async (requestUrl, options) => {
32
36
  export const makeRequest = async (url, options, internalOptions) => {
33
37
  options.headers = {
34
38
  ...options.headers,
35
- 'User-Agent': 'gologin-nodejs-sdk',
39
+ 'User-Agent': `gologin-nodejs-sdk/${version}`,
36
40
  };
37
41
 
38
42
  if (internalOptions?.token) {
@@ -46,7 +50,9 @@ export const makeRequest = async (url, options, internalOptions) => {
46
50
  return await attemptRequest(url, options);
47
51
  } catch (error) {
48
52
  if (internalOptions?.fallbackUrl && !error.statusCode) {
49
- return attemptRequest(internalOptions.fallbackUrl, options);
53
+ const fallbackData = await attemptRequest(internalOptions.fallbackUrl, options);
54
+
55
+ return fallbackData;
50
56
  }
51
57
 
52
58
  throw error;
@@ -0,0 +1,73 @@
1
+ import * as Sentry from '@sentry/node';
2
+
3
+ export const captureGroupedSentryError = (error, context = {}) => {
4
+ if (process.env.DISABLE_TELEMETRY === 'true') {
5
+ return;
6
+ }
7
+
8
+ const errorMessage = error?.message || 'Unknown error';
9
+ let fingerprint = ['default'];
10
+ let tags = { errorType: 'unknown' };
11
+
12
+ switch (true) {
13
+ case errorMessage.includes('Profile deleted or not found'):
14
+ fingerprint = ['profile-not-found'];
15
+ tags = { errorType: 'profile', category: 'configuration' };
16
+ break;
17
+
18
+ case errorMessage.includes('Request timeout after 13000ms'):
19
+ case errorMessage.includes('Proxy Error'):
20
+ fingerprint = ['proxy-error'];
21
+ tags = { errorType: 'proxy', category: 'configuration' };
22
+ break;
23
+
24
+ case errorMessage.includes('ENOSPC'):
25
+ case errorMessage.includes('database or disk is full'):
26
+ fingerprint = ['out-of-space'];
27
+ tags = { errorType: 'out-of-space', category: 'filesystem' };
28
+ break;
29
+
30
+ case errorMessage.includes('ECONNREFUSED 127.0.0.1:'):
31
+ fingerprint = ['browser-not-found'];
32
+ tags = { errorType: 'browser', category: 'configuration' };
33
+ break;
34
+
35
+ case errorMessage.includes('end of central directory record signature not found'):
36
+ case errorMessage.includes('invalid code lengths set'):
37
+ case errorMessage.includes('Command failed: tar xzf'):
38
+ fingerprint = ['archive-error'];
39
+ tags = { errorType: 'archive', category: 'binaries' };
40
+ break;
41
+
42
+ case errorMessage.includes('spawn UNKNOWN'):
43
+ fingerprint = ['spawn-error'];
44
+ tags = { errorType: 'spawn', category: 'runtime' };
45
+ break;
46
+
47
+ case errorMessage.includes('unable to verify the first certificate'):
48
+ case errorMessage.includes('write EPROTO'):
49
+ fingerprint = ['ssl-error'];
50
+ tags = { errorType: 'ssl', category: 'network' };
51
+ break;
52
+
53
+ case errorMessage.includes('You have reached your free API requests limit'):
54
+ fingerprint = ['api-limit-reached'];
55
+ tags = { errorType: 'api', category: 'rate-limit' };
56
+ break;
57
+
58
+ default:
59
+ fingerprint = ['uncategorized', errorMessage.substring(0, 50)];
60
+ tags = { errorType: 'uncategorized', category: 'unknown' };
61
+ break;
62
+ }
63
+
64
+ Sentry.captureException(error, scope => {
65
+ scope.setFingerprint(fingerprint);
66
+ scope.setTransactionName(fingerprint);
67
+ scope.setTags(tags);
68
+ scope.setContext('errorDetails', {
69
+ originalMessage: errorMessage,
70
+ ...context,
71
+ });
72
+ });
73
+ };