gologin 2.1.32 → 2.1.33
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 +8 -0
- package/package.json +1 -1
- package/src/browser/browser-checker.js +41 -48
- package/src/browser/browser-download-manager.js +222 -0
- package/src/gologin.js +12 -20
- package/src/utils/http.js +3 -1
- package/src/utils/sentry.js +73 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -7,9 +7,9 @@ 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 BrowserDownloadLockManager from './browser-download-manager.js';
|
|
13
13
|
|
|
14
14
|
const exec = util.promisify(execNonPromise);
|
|
15
15
|
const { access, mkdir, readdir, rmdir, unlink, copyFile, readlink, symlink, lstat, rename, writeFile, readFile } = _promises;
|
|
@@ -19,37 +19,31 @@ const ARCH = process.arch;
|
|
|
19
19
|
|
|
20
20
|
const VERSION_FILE = 'latest-version.txt';
|
|
21
21
|
|
|
22
|
-
const WIN_FOLDERSIZE_FILE = 'foldersize.txt';
|
|
23
|
-
const WIN_FOLDERSIZE_FILE_LINK = `https://orbita-browser-windows.gologin.com/${WIN_FOLDERSIZE_FILE}`;
|
|
24
|
-
|
|
25
22
|
const BROWSER_ARCHIVE_NAME = `orbita-browser-latest.${PLATFORM === 'win32' ? 'zip' : 'tar.gz'}`;
|
|
26
23
|
|
|
27
24
|
const MAC_HASH_FILE = 'hashfile.mtree';
|
|
28
25
|
const DEB_HASH_FILE = 'hashfile.txt';
|
|
29
|
-
const WIN_HASH_FILE = DEB_HASH_FILE;
|
|
30
26
|
const MAC_HASHFILE_LINK = `https://orbita-browser-mac.gologin.com/${MAC_HASH_FILE}`;
|
|
31
27
|
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
28
|
const MAC_ARM_HASHFILE_LINK = `https://orbita-browser-mac-arm.gologin.com/${MAC_HASH_FILE}`;
|
|
34
29
|
|
|
35
30
|
const FAIL_SUM_MATCH_MESSAGE = 'hash_sum_not_matched';
|
|
36
31
|
const EXTRACTED_FOLDER = 'extracted-browser';
|
|
37
32
|
|
|
38
33
|
export class BrowserChecker {
|
|
39
|
-
#homedir = homedir();
|
|
40
|
-
#browserPath = join(this.#homedir, '.gologin', 'browser');
|
|
41
|
-
#executableFilePath;
|
|
42
|
-
#skipOrbitaHashChecking = false;
|
|
43
|
-
|
|
44
34
|
constructor() {
|
|
45
|
-
|
|
35
|
+
this.homedir = homedir();
|
|
36
|
+
this.browserPath = join(this.homedir, '.gologin', 'browser');
|
|
37
|
+
this.executableFilePath = null;
|
|
38
|
+
this.skipOrbitaHashChecking = false;
|
|
39
|
+
this.downloadManager = BrowserDownloadLockManager.getInstance();
|
|
40
|
+
this.downloadManager.cleanupStaleLocks().catch(console.warn);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
async checkBrowser({ autoUpdateBrowser,
|
|
49
|
-
const isBrowserFolderExists = await access(join(this
|
|
50
|
-
|
|
43
|
+
async checkBrowser({ autoUpdateBrowser, majorVersion }) {
|
|
44
|
+
const isBrowserFolderExists = await access(join(this.browserPath, `orbita-browser-${majorVersion}`)).then(() => true).catch(() => false);
|
|
51
45
|
if (!isBrowserFolderExists || autoUpdateBrowser) {
|
|
52
|
-
await this.downloadBrowser(majorVersion);
|
|
46
|
+
await this.downloadManager.ensureBrowserDownload(majorVersion, () => this.downloadBrowser(majorVersion));
|
|
53
47
|
|
|
54
48
|
return this.getBrowserExecutablePath(majorVersion);
|
|
55
49
|
}
|
|
@@ -71,8 +65,6 @@ export class BrowserChecker {
|
|
|
71
65
|
// return this.getBrowserExecutablePath(majorVersion);
|
|
72
66
|
// }
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
68
|
// return new Promise(resolve => {
|
|
77
69
|
// const rl = createInterface(process.stdin, process.stdout);
|
|
78
70
|
// const timeout = setTimeout(() => {
|
|
@@ -94,9 +86,9 @@ export class BrowserChecker {
|
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
async downloadBrowser(majorVersion) {
|
|
97
|
-
await mkdir(this
|
|
89
|
+
await mkdir(this.browserPath, { recursive: true });
|
|
98
90
|
|
|
99
|
-
const browserPath = join(this
|
|
91
|
+
const browserPath = join(this.browserPath, BROWSER_ARCHIVE_NAME);
|
|
100
92
|
|
|
101
93
|
const browserDownloadUrl = this.getBrowserDownloadUrl(majorVersion);
|
|
102
94
|
|
|
@@ -110,13 +102,13 @@ export class BrowserChecker {
|
|
|
110
102
|
const os = getOS();
|
|
111
103
|
switch (os) {
|
|
112
104
|
case 'mac':
|
|
113
|
-
return join(this
|
|
105
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
|
|
114
106
|
case 'win':
|
|
115
|
-
return join(this
|
|
107
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'chrome.exe');
|
|
116
108
|
case 'macM1':
|
|
117
|
-
return join(this
|
|
109
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'Orbita-Browser.app', 'Contents', 'MacOS', 'Orbita');
|
|
118
110
|
default:
|
|
119
|
-
return join(this
|
|
111
|
+
return join(this.browserPath, `orbita-browser-${majorVersion}`, 'chrome');
|
|
120
112
|
}
|
|
121
113
|
}
|
|
122
114
|
|
|
@@ -135,8 +127,8 @@ export class BrowserChecker {
|
|
|
135
127
|
}
|
|
136
128
|
|
|
137
129
|
addLatestVersion(latestVersion) {
|
|
138
|
-
return mkdir(join(this
|
|
139
|
-
.then(() => writeFile(join(this
|
|
130
|
+
return mkdir(join(this.browserPath, 'orbita-browser', 'version'), { recursive: true })
|
|
131
|
+
.then(() => writeFile(join(this.browserPath, 'orbita-browser', 'version', 'latest-version.txt'), latestVersion));
|
|
140
132
|
}
|
|
141
133
|
|
|
142
134
|
async downloadBrowserArchive(link, pathStr) {
|
|
@@ -155,6 +147,7 @@ export class BrowserChecker {
|
|
|
155
147
|
const formattedLen = len / 1024 / 1024;
|
|
156
148
|
if (isNaN(formattedLen)) {
|
|
157
149
|
reject(new Error('Error downloading browser'));
|
|
150
|
+
|
|
158
151
|
return;
|
|
159
152
|
}
|
|
160
153
|
|
|
@@ -204,9 +197,9 @@ export class BrowserChecker {
|
|
|
204
197
|
|
|
205
198
|
async extractBrowser() {
|
|
206
199
|
console.log('Extracting Orbita');
|
|
207
|
-
await mkdir(join(this
|
|
200
|
+
await mkdir(join(this.browserPath, EXTRACTED_FOLDER), { recursive: true });
|
|
208
201
|
if (PLATFORM === 'win32') {
|
|
209
|
-
return decompress(join(this
|
|
202
|
+
return decompress(join(this.browserPath, BROWSER_ARCHIVE_NAME), join(this.browserPath, EXTRACTED_FOLDER),
|
|
210
203
|
{
|
|
211
204
|
plugins: [decompressUnzip()],
|
|
212
205
|
filter: file => !file.path.endsWith('/'),
|
|
@@ -215,20 +208,20 @@ export class BrowserChecker {
|
|
|
215
208
|
}
|
|
216
209
|
|
|
217
210
|
return exec(
|
|
218
|
-
`tar xzf ${join(this
|
|
211
|
+
`tar xzf ${join(this.browserPath, BROWSER_ARCHIVE_NAME)} --directory ${join(this.browserPath, EXTRACTED_FOLDER)}`,
|
|
219
212
|
);
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
async downloadHashFile(latestVersion) {
|
|
223
216
|
let hashLink = DEB_HASHFILE_LINK;
|
|
224
|
-
let resultPath = join(this
|
|
217
|
+
let resultPath = join(this.browserPath, DEB_HASH_FILE);
|
|
225
218
|
if (PLATFORM === 'darwin') {
|
|
226
219
|
hashLink = MAC_HASHFILE_LINK;
|
|
227
220
|
if (ARCH === 'arm64') {
|
|
228
221
|
hashLink = MAC_ARM_HASHFILE_LINK;
|
|
229
222
|
}
|
|
230
223
|
|
|
231
|
-
resultPath = join(this
|
|
224
|
+
resultPath = join(this.browserPath, MAC_HASH_FILE);
|
|
232
225
|
}
|
|
233
226
|
|
|
234
227
|
if (latestVersion) {
|
|
@@ -256,13 +249,13 @@ export class BrowserChecker {
|
|
|
256
249
|
}).on('error', (err) => writableStream.destroy(err)));
|
|
257
250
|
|
|
258
251
|
const hashFile = PLATFORM === 'darwin' ? MAC_HASH_FILE : DEB_HASH_FILE;
|
|
259
|
-
const hashFilePath = join(this
|
|
252
|
+
const hashFilePath = join(this.browserPath, hashFile);
|
|
260
253
|
|
|
261
254
|
return access(hashFilePath);
|
|
262
255
|
}
|
|
263
256
|
|
|
264
257
|
async checkBrowserSum(latestVersion) {
|
|
265
|
-
if (this
|
|
258
|
+
if (this.skipOrbitaHashChecking) {
|
|
266
259
|
return Promise.resolve();
|
|
267
260
|
}
|
|
268
261
|
|
|
@@ -274,7 +267,7 @@ export class BrowserChecker {
|
|
|
274
267
|
await this.downloadHashFile(latestVersion);
|
|
275
268
|
if (PLATFORM === 'darwin') {
|
|
276
269
|
const calculatedHash = await exec(
|
|
277
|
-
`mtree -p ${join(this
|
|
270
|
+
`mtree -p ${join(this.browserPath, EXTRACTED_FOLDER, 'Orbita-Browser.app')} < ${join(this.browserPath, MAC_HASH_FILE)} || echo ${FAIL_SUM_MATCH_MESSAGE}`,
|
|
278
271
|
);
|
|
279
272
|
|
|
280
273
|
const checkedRes = (calculatedHash || '').toString().trim();
|
|
@@ -285,16 +278,16 @@ export class BrowserChecker {
|
|
|
285
278
|
return;
|
|
286
279
|
}
|
|
287
280
|
|
|
288
|
-
const hashFileContent = await exec(`cat ${join(this
|
|
281
|
+
const hashFileContent = await exec(`cat ${join(this.browserPath, DEB_HASH_FILE)}`);
|
|
289
282
|
let serverRes = (hashFileContent.stdout || '').toString().trim();
|
|
290
283
|
serverRes = serverRes.split(' ')[0];
|
|
291
284
|
|
|
292
285
|
const calculateLocalBrowserHash = await exec(
|
|
293
|
-
`cd ${join(this
|
|
294
|
-
xargs -0 sha256sum > ${this
|
|
286
|
+
`cd ${join(this.browserPath, EXTRACTED_FOLDER)} && find orbita-browser -type f -print0 | sort -z | \
|
|
287
|
+
xargs -0 sha256sum > ${this.browserPath}/calculatedFolderSha.txt`,
|
|
295
288
|
);
|
|
296
289
|
|
|
297
|
-
const localHashContent = await exec(`cd ${this
|
|
290
|
+
const localHashContent = await exec(`cd ${this.browserPath} && sha256sum calculatedFolderSha.txt`);
|
|
298
291
|
let userRes = (localHashContent.stdout || '').toString().trim();
|
|
299
292
|
userRes = userRes.split(' ')[0];
|
|
300
293
|
if (userRes !== serverRes) {
|
|
@@ -304,28 +297,28 @@ export class BrowserChecker {
|
|
|
304
297
|
|
|
305
298
|
async replaceBrowser(majorVersion) {
|
|
306
299
|
console.log('Copy Orbita to target path');
|
|
307
|
-
const targetBrowserPath = join(this
|
|
300
|
+
const targetBrowserPath = join(this.browserPath, `orbita-browser-${majorVersion}`);
|
|
308
301
|
await this.deleteDir(targetBrowserPath);
|
|
309
302
|
|
|
310
303
|
if (PLATFORM === 'darwin') {
|
|
311
|
-
return rename(join(this
|
|
304
|
+
return rename(join(this.browserPath, EXTRACTED_FOLDER), targetBrowserPath);
|
|
312
305
|
}
|
|
313
306
|
|
|
314
307
|
await this.copyDir(
|
|
315
|
-
join(this
|
|
308
|
+
join(this.browserPath, EXTRACTED_FOLDER, 'orbita-browser'),
|
|
316
309
|
targetBrowserPath,
|
|
317
310
|
);
|
|
318
311
|
}
|
|
319
312
|
|
|
320
313
|
async deleteOldArchives() {
|
|
321
|
-
await this.deleteDir(join(this
|
|
314
|
+
await this.deleteDir(join(this.browserPath, EXTRACTED_FOLDER));
|
|
322
315
|
|
|
323
|
-
return readdir(this
|
|
316
|
+
return readdir(this.browserPath)
|
|
324
317
|
.then((files) => {
|
|
325
318
|
const promises = [];
|
|
326
319
|
files.forEach((filename) => {
|
|
327
320
|
if (filename.match(/(txt|dylib|mtree)/)) {
|
|
328
|
-
promises.push(unlink(join(this
|
|
321
|
+
promises.push(unlink(join(this.browserPath, filename)));
|
|
329
322
|
}
|
|
330
323
|
});
|
|
331
324
|
|
|
@@ -355,12 +348,12 @@ export class BrowserChecker {
|
|
|
355
348
|
}
|
|
356
349
|
|
|
357
350
|
async getCurrentVersion(majorVersion) {
|
|
358
|
-
let versionFilePath = join(this
|
|
351
|
+
let versionFilePath = join(this.browserPath, `orbita-browser-${majorVersion}`, 'version');
|
|
359
352
|
if (PLATFORM === 'darwin') {
|
|
360
|
-
versionFilePath = join(this
|
|
353
|
+
versionFilePath = join(this.browserPath, `orbita-browser-${majorVersion}`, 'version', VERSION_FILE);
|
|
361
354
|
}
|
|
362
355
|
|
|
363
|
-
return (await readFile(versionFilePath, 'utf8').catch(() => '0.0.0')).replace(/[\r\n\t\f\v\
|
|
356
|
+
return (await readFile(versionFilePath, 'utf8').catch(() => '0.0.0')).replace(/[\r\n\t\f\v\x00-\x1F\x7F]/g, '');
|
|
364
357
|
}
|
|
365
358
|
|
|
366
359
|
getLatestBrowserVersion() {
|
|
@@ -386,7 +379,7 @@ export class BrowserChecker {
|
|
|
386
379
|
}
|
|
387
380
|
|
|
388
381
|
get getOrbitaPath() {
|
|
389
|
-
return this
|
|
382
|
+
return this.executableFilePath;
|
|
390
383
|
}
|
|
391
384
|
|
|
392
385
|
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.33',
|
|
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
|
|
|
@@ -1371,18 +1361,20 @@ export class GoLogin {
|
|
|
1371
1361
|
}
|
|
1372
1362
|
} catch (error) {
|
|
1373
1363
|
if (!isSecondTry && (error.message.includes('table cookies has no column') || error.message.includes('NOT NULL constraint failed'))) {
|
|
1374
|
-
await
|
|
1364
|
+
await db.close();
|
|
1365
|
+
await _promises.rm(cookiesPaths.primary, { recursive: true, force: true }).catch(() => null);
|
|
1375
1366
|
await createDBFile({
|
|
1376
1367
|
cookiesFilePath: cookiesPaths.primary,
|
|
1377
1368
|
cookiesFileSecondPath: cookiesPaths.secondary,
|
|
1378
1369
|
createCookiesTableQuery: this.createCookiesTableQuery,
|
|
1379
|
-
});
|
|
1380
|
-
await this.writeCookiesToFile(cookies, true);
|
|
1370
|
+
}).catch(console.error);
|
|
1371
|
+
await this.writeCookiesToFile(cookies, true).catch(console.error);
|
|
1381
1372
|
|
|
1382
1373
|
return;
|
|
1383
1374
|
}
|
|
1384
1375
|
|
|
1385
1376
|
console.error(error.message);
|
|
1377
|
+
captureGroupedSentryError(error, { method: 'writeCookiesToFile', profileId: this.profile_id });
|
|
1386
1378
|
} finally {
|
|
1387
1379
|
db && await db.close();
|
|
1388
1380
|
await ensureDirectoryExists(cookiesPaths.primary);
|
|
@@ -1407,7 +1399,7 @@ export class GoLogin {
|
|
|
1407
1399
|
|
|
1408
1400
|
return { status: 'success', wsUrl: startResponse.wsUrl, resolution: startResponse.resolution };
|
|
1409
1401
|
} catch (error) {
|
|
1410
|
-
|
|
1402
|
+
captureGroupedSentryError(error, { method: 'start', profileId: this.profile_id });
|
|
1411
1403
|
throw error;
|
|
1412
1404
|
}
|
|
1413
1405
|
}
|
package/src/utils/http.js
CHANGED
|
@@ -46,7 +46,9 @@ export const makeRequest = async (url, options, internalOptions) => {
|
|
|
46
46
|
return await attemptRequest(url, options);
|
|
47
47
|
} catch (error) {
|
|
48
48
|
if (internalOptions?.fallbackUrl && !error.statusCode) {
|
|
49
|
-
|
|
49
|
+
const fallbackData = await attemptRequest(internalOptions.fallbackUrl, options);
|
|
50
|
+
|
|
51
|
+
return fallbackData;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
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.withScope((scope) => {
|
|
65
|
+
scope.setFingerprint(fingerprint);
|
|
66
|
+
scope.setTags(tags);
|
|
67
|
+
scope.setContext('errorDetails', {
|
|
68
|
+
originalMessage: errorMessage,
|
|
69
|
+
...context,
|
|
70
|
+
});
|
|
71
|
+
Sentry.captureException(error);
|
|
72
|
+
});
|
|
73
|
+
};
|