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 +14 -2
- package/CHANGELOG.md +15 -0
- package/package.json +5 -2
- package/src/browser/browser-checker.js +45 -65
- package/src/browser/browser-download-manager.js +222 -0
- package/src/gologin.js +12 -31
- package/src/utils/common.js +6 -4
- package/src/utils/http.js +8 -2
- package/src/utils/sentry.js +73 -0
- package/.sentry-native/3af76fbc-ac64-4947-d1bf-0ab01540301f.run.lock +0 -0
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":
|
|
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.
|
|
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": ">=
|
|
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,
|
|
49
|
-
const isBrowserFolderExists = await access(join(this
|
|
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
|
|
90
|
+
await mkdir(this.browserPath, { recursive: true });
|
|
98
91
|
|
|
99
|
-
const browserPath = join(this
|
|
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
|
|
106
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
|
|
114
107
|
case 'win':
|
|
115
|
-
return join(this
|
|
108
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'chrome.exe');
|
|
116
109
|
case 'macM1':
|
|
117
|
-
return join(this
|
|
110
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
|
|
118
111
|
default:
|
|
119
|
-
return join(this
|
|
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
|
|
139
|
-
.then(() => writeFile(join(this
|
|
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
|
|
203
|
+
await mkdir(join(this.browserPath, EXTRACTED_FOLDER), { recursive: true });
|
|
208
204
|
if (PLATFORM === 'win32') {
|
|
209
|
-
return decompress(join(this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
294
|
-
xargs -0 sha256sum > ${this
|
|
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
|
|
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
|
|
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
|
|
307
|
+
return rename(join(this.browserPath, EXTRACTED_FOLDER), targetBrowserPath);
|
|
312
308
|
}
|
|
313
309
|
|
|
314
310
|
await this.copyDir(
|
|
315
|
-
join(this
|
|
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
|
|
317
|
+
await this.deleteDir(join(this.browserPath, EXTRACTED_FOLDER));
|
|
322
318
|
|
|
323
|
-
return readdir(this
|
|
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
|
|
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
|
|
354
|
+
let versionFilePath = join(this.browserPath, `orbita-browser-${majorVersion}`, 'version');
|
|
359
355
|
if (PLATFORM === 'darwin') {
|
|
360
|
-
versionFilePath = join(this
|
|
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\
|
|
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
|
|
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
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
package/src/utils/common.js
CHANGED
|
@@ -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 (
|
|
69
|
+
if (PLATFORM === 'win32') {
|
|
68
70
|
return 'win';
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
if (
|
|
72
|
-
return
|
|
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':
|
|
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
|
-
|
|
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
|
+
};
|
|
File without changes
|