gologin 3.0.6 → 3.0.8
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 +12 -0
- package/index.d.ts +1 -0
- package/package.json +2 -2
- package/src/gologin.js +22 -2
- package/src/profile/profile-archiver.js +2 -2
- package/src/profile/profile-directories-to-remove.js +14 -4
- package/src/utils/launch-arguments.js +83 -0
- package/test/unit/launch-arguments.test.js +96 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Combined changelog for GoLogin node.js SDK
|
|
4
4
|
|
|
5
|
+
## [3.0.8] 2026-11-12
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* Added `isCloudBrowser` option for GoLogin cloud browser. When enabled, profile `launchArguments` that run other programs (`*-cmd-prefix`, `*-launcher`, `--browser-subprocess-path`), open remote debugging, override the proxy or DNS, or point at paths (`--user-data-dir`, `--load-extension`, ...) are dropped and logged with proxy values redacted. Off by default; local launches are unchanged
|
|
10
|
+
|
|
11
|
+
## [3.0.7] 2026-10-15
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
* Keep IndexedDB in the profile when `storage.indexedDb` is enabled
|
|
16
|
+
|
|
5
17
|
## [3.0.6] 2026-10-06
|
|
6
18
|
|
|
7
19
|
### Fixes
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gologin",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.8",
|
|
4
4
|
"description": "A high-level API to control Orbita browser over GoLogin API",
|
|
5
5
|
"types": "./index.d.ts",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"test": "npm run test:unit && npm run test:e2e",
|
|
62
|
-
"test:unit": "
|
|
62
|
+
"test:unit": "node --test 'test/unit/*.test.js'",
|
|
63
63
|
"test:e2e": "node test/e2e/run-tests.js",
|
|
64
64
|
"test:e2e:local": "HEADLESS=false npm run test:e2e",
|
|
65
65
|
"format": "npx prettier --single-quote src/* --write",
|
package/src/gologin.js
CHANGED
|
@@ -10,10 +10,12 @@ import {
|
|
|
10
10
|
downloadCookies, setExtPathsAndRemoveDeleted, setOriginalExtPaths, uploadCookies,
|
|
11
11
|
} from './browser/browser-user-data-manager.js';
|
|
12
12
|
import { getProfileChromeExtensions } from './extensions/get-extensions.js';
|
|
13
|
+
import { INDEXED_DB_DIRECTORY } from './profile/profile-directories-to-remove.js';
|
|
13
14
|
import { checkAutoLang, getIntlProfileConfig, securedOrbitaOpts } from './utils/browser.js';
|
|
14
15
|
import { API_URL, ensureDirectoryExists, FALLBACK_API_URL, getOsAdvanced } from './utils/common.js';
|
|
15
16
|
import { STORAGE_GATEWAY_BASE_URL } from './utils/constants.js';
|
|
16
17
|
import { checkSocksProxy, makeRequest } from './utils/http.js';
|
|
18
|
+
import { describeBlockedArgument, filterCloudLaunchArguments } from './utils/launch-arguments.js';
|
|
17
19
|
import {
|
|
18
20
|
// ensureSentryInitialized,
|
|
19
21
|
loadBrowserChecker,
|
|
@@ -66,7 +68,9 @@ export class GoLogin {
|
|
|
66
68
|
this.waitWebsocket = options.waitWebsocket ?? true;
|
|
67
69
|
this.isEmptyFonts = false;
|
|
68
70
|
this.isFirstSession = false;
|
|
71
|
+
this.keepIndexedDb = false;
|
|
69
72
|
this.isCloudHeadless = options.isCloudHeadless ?? true;
|
|
73
|
+
this.isCloudBrowser = options.isCloudBrowser ?? false;
|
|
70
74
|
this.storageGatewayUrl = `${STORAGE_GATEWAY_BASE_URL}/upload`;
|
|
71
75
|
|
|
72
76
|
this.tmpdir = tmpdir();
|
|
@@ -490,7 +494,19 @@ export class GoLogin {
|
|
|
490
494
|
throw new Error('Error fetching profile data');
|
|
491
495
|
}
|
|
492
496
|
|
|
497
|
+
this.keepIndexedDb = profile.storage?.indexedDb === true;
|
|
493
498
|
this.profileLaunchArguments = (profile.launchArguments || '').split(' ').filter(Boolean);
|
|
499
|
+
if (this.isCloudBrowser && this.profileLaunchArguments.length) {
|
|
500
|
+
const { allowed, blocked } = filterCloudLaunchArguments(this.profileLaunchArguments);
|
|
501
|
+
if (blocked.length) {
|
|
502
|
+
console.warn('[launch-arguments] cloud browser: dropped profile launch arguments', {
|
|
503
|
+
profileId: this.profile_id,
|
|
504
|
+
blocked: blocked.map(describeBlockedArgument),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this.profileLaunchArguments = allowed;
|
|
509
|
+
}
|
|
494
510
|
|
|
495
511
|
const { navigator = {}, fonts, os: profileOs } = profile;
|
|
496
512
|
this.fontsMasking = fonts?.enableMasking;
|
|
@@ -626,6 +642,7 @@ export class GoLogin {
|
|
|
626
642
|
} else {
|
|
627
643
|
this.browserLang = checkAutoLangResult;
|
|
628
644
|
}
|
|
645
|
+
|
|
629
646
|
const prefsToWrite = Object.assign(preferences, { gologin });
|
|
630
647
|
if (this.customChromeFrame === false) {
|
|
631
648
|
prefsToWrite.browser = {
|
|
@@ -1331,7 +1348,6 @@ export class GoLogin {
|
|
|
1331
1348
|
`${SEPARATOR}Default${SEPARATOR}Code Cache`,
|
|
1332
1349
|
`${SEPARATOR}Default${SEPARATOR}GPUCache`,
|
|
1333
1350
|
`${SEPARATOR}Default${SEPARATOR}Extensions`,
|
|
1334
|
-
`${SEPARATOR}Default${SEPARATOR}IndexedDB`,
|
|
1335
1351
|
`${SEPARATOR}Default${SEPARATOR}GPUCache`,
|
|
1336
1352
|
`${SEPARATOR}Default${SEPARATOR}DawnCache`,
|
|
1337
1353
|
`${SEPARATOR}Default${SEPARATOR}fonts_config`,
|
|
@@ -1350,6 +1366,10 @@ export class GoLogin {
|
|
|
1350
1366
|
`${SEPARATOR}BrowserMetrics-spare.pma`,
|
|
1351
1367
|
];
|
|
1352
1368
|
|
|
1369
|
+
if (!this.keepIndexedDb) {
|
|
1370
|
+
remove_dirs.push(`${SEPARATOR}Default${SEPARATOR}${INDEXED_DB_DIRECTORY}`);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1353
1373
|
const that = this;
|
|
1354
1374
|
|
|
1355
1375
|
await Promise.all(remove_dirs.map(d => {
|
|
@@ -1371,7 +1391,7 @@ export class GoLogin {
|
|
|
1371
1391
|
|
|
1372
1392
|
const profilePath = this.profilePath();
|
|
1373
1393
|
const { archiveProfile } = await loadProfileArchiver();
|
|
1374
|
-
const fileBuff = await archiveProfile(profilePath);
|
|
1394
|
+
const fileBuff = await archiveProfile(profilePath, { keepIndexedDb: this.keepIndexedDb });
|
|
1375
1395
|
|
|
1376
1396
|
debug('PROFILE ZIP CREATED', profilePath, zipPath);
|
|
1377
1397
|
|
|
@@ -13,7 +13,7 @@ const loadAdmZip = () => {
|
|
|
13
13
|
return admZipPromise;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
export const archiveProfile = async (profileFolder = '') => {
|
|
16
|
+
export const archiveProfile = async (profileFolder = '', { keepIndexedDb = false } = {}) => {
|
|
17
17
|
const AdmZip = await loadAdmZip();
|
|
18
18
|
const folderExists = await access(profileFolder).then(() => true, () => false);
|
|
19
19
|
if (!folderExists) {
|
|
@@ -28,7 +28,7 @@ export const archiveProfile = async (profileFolder = '') => {
|
|
|
28
28
|
archive.addFile('First Run', Buffer.from(''));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const dirsToRemove = getDirectoriesForArchiver();
|
|
31
|
+
const dirsToRemove = getDirectoriesForArchiver({ keepIndexedDb });
|
|
32
32
|
dirsToRemove.forEach(entry => archive.deleteFile(entry));
|
|
33
33
|
|
|
34
34
|
const archiveIsValid = checkProfileArchiveIsValid(archive);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export const INDEXED_DB_DIRECTORY = 'IndexedDB';
|
|
2
|
+
|
|
1
3
|
const DEFAULT_FOLDER_USELESS_FILE = [
|
|
2
4
|
{
|
|
3
5
|
name: 'Cache',
|
|
@@ -25,7 +27,7 @@ const DEFAULT_FOLDER_USELESS_FILE = [
|
|
|
25
27
|
isDirectory: true,
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
|
-
name:
|
|
30
|
+
name: INDEXED_DB_DIRECTORY,
|
|
29
31
|
subs: [],
|
|
30
32
|
isDirectory: true,
|
|
31
33
|
},
|
|
@@ -51,8 +53,16 @@ const DEFAULT_FOLDER_USELESS_FILE = [
|
|
|
51
53
|
},
|
|
52
54
|
];
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const getFoldersToRemove = ({ keepIndexedDb = false } = {}) => {
|
|
57
|
+
if (!keepIndexedDb) {
|
|
58
|
+
return DEFAULT_FOLDER_USELESS_FILE;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return DEFAULT_FOLDER_USELESS_FILE.filter((folder) => folder.name !== INDEXED_DB_DIRECTORY);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const getDirectoriesToDeleteForNode = (routerSlash = '/', { keepIndexedDb = false } = {}) =>
|
|
65
|
+
getFoldersToRemove({ keepIndexedDb }).reduce((res, el) => {
|
|
56
66
|
const basePath = routerSlash + 'Default' + routerSlash + el.name;
|
|
57
67
|
if (el.subs.length) {
|
|
58
68
|
el.subs.forEach(sub => res.push(basePath + routerSlash + sub));
|
|
@@ -63,7 +73,7 @@ export const getDirectoriesToDeleteForNode = (routerSlash = '/') =>
|
|
|
63
73
|
return res;
|
|
64
74
|
}, []);
|
|
65
75
|
|
|
66
|
-
export const getDirectoriesForArchiver = () =>
|
|
76
|
+
export const getDirectoriesForArchiver = ({ keepIndexedDb = false } = {}) => getFoldersToRemove({ keepIndexedDb }).reduce((res, el) => {
|
|
67
77
|
const { name, subs, isDirectory } = el;
|
|
68
78
|
const basePath = 'Default/' + name;
|
|
69
79
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Profile launch arguments (profile.launchArguments) are free text set by the user and
|
|
2
|
+
// appended to Orbita's command line. In GoLogin's cloud browser that command line runs
|
|
3
|
+
// on our servers, so some switches must not come from the user:
|
|
4
|
+
// - switches that make Chrome run another program in place of its own processes
|
|
5
|
+
// (with --no-sandbox that is arbitrary command execution inside the pod),
|
|
6
|
+
// - remote debugging / DevTools access, which the cloud sets up itself,
|
|
7
|
+
// - proxy and DNS overrides, which would bypass the profile's proxy,
|
|
8
|
+
// - filesystem paths (profile dir, cache, logs, extensions loaded from a path).
|
|
9
|
+
// Chrome uses the last value of a repeated switch, so a user switch would also override
|
|
10
|
+
// the SDK's own. Only applied when the SDK runs with isCloudBrowser: true.
|
|
11
|
+
|
|
12
|
+
const BLOCKED_SWITCHES = new Set([
|
|
13
|
+
'browser-subprocess-path',
|
|
14
|
+
'type',
|
|
15
|
+
'remote-debugging-port',
|
|
16
|
+
'remote-debugging-pipe',
|
|
17
|
+
'remote-debugging-address',
|
|
18
|
+
'remote-debugging-io-pipes',
|
|
19
|
+
'remote-debugging-targets',
|
|
20
|
+
'remote-allow-origins',
|
|
21
|
+
'proxy-server',
|
|
22
|
+
'proxy-pac-url',
|
|
23
|
+
'no-proxy-server',
|
|
24
|
+
'proxy-bypass-list',
|
|
25
|
+
'proxy-auto-detect',
|
|
26
|
+
'host-resolver-rules',
|
|
27
|
+
'host-rules',
|
|
28
|
+
'user-data-dir',
|
|
29
|
+
'disk-cache-dir',
|
|
30
|
+
'crash-dumps-dir',
|
|
31
|
+
'log-file',
|
|
32
|
+
'load-extension',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Any switch that wraps or replaces a child process launch:
|
|
36
|
+
// --renderer-cmd-prefix, --utility-cmd-prefix, --gpu-launcher, --ppapi-plugin-launcher, ...
|
|
37
|
+
const BLOCKED_SWITCH_PATTERNS = [/-cmd-prefix$/, /-launcher$/, /^nacl-gdb/];
|
|
38
|
+
|
|
39
|
+
// Chrome on Linux accepts both "--name" and "-name"; switch names are case-sensitive
|
|
40
|
+
// there, but lowercase them so no spelling slips through.
|
|
41
|
+
export const launchSwitchName = (arg) => {
|
|
42
|
+
const match = /^--?([^=]+)/.exec(arg);
|
|
43
|
+
|
|
44
|
+
return match ? match[1].trim().toLowerCase() : null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const isBlockedSwitch = (name) =>
|
|
48
|
+
BLOCKED_SWITCHES.has(name) || BLOCKED_SWITCH_PATTERNS.some((pattern) => pattern.test(name));
|
|
49
|
+
|
|
50
|
+
// Returns the arguments that may be passed to Chrome and the ones that were removed.
|
|
51
|
+
// A bare "--" is removed too: Chrome would treat every argument after it, including the
|
|
52
|
+
// SDK's own switches, as a URL.
|
|
53
|
+
export const filterCloudLaunchArguments = (args) => {
|
|
54
|
+
const allowed = [];
|
|
55
|
+
const blocked = [];
|
|
56
|
+
for (const arg of args) {
|
|
57
|
+
if (arg === '--') {
|
|
58
|
+
blocked.push(arg);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const name = launchSwitchName(arg);
|
|
63
|
+
if (name !== null && isBlockedSwitch(name)) {
|
|
64
|
+
blocked.push(arg);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
allowed.push(arg);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { allowed, blocked };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// For logs: keep what was tried (e.g. which program a *-cmd-prefix pointed at), but not
|
|
75
|
+
// proxy credentials.
|
|
76
|
+
export const describeBlockedArgument = (arg) => {
|
|
77
|
+
const name = launchSwitchName(arg);
|
|
78
|
+
if (name && /proxy|host-rules|host-resolver-rules/.test(name) && arg.includes('=')) {
|
|
79
|
+
return `${arg.slice(0, arg.indexOf('='))}=<redacted>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return arg;
|
|
83
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
describeBlockedArgument,
|
|
6
|
+
filterCloudLaunchArguments,
|
|
7
|
+
launchSwitchName,
|
|
8
|
+
} from '../../src/utils/launch-arguments.js';
|
|
9
|
+
|
|
10
|
+
// The arguments seen on profile 6aebd85725714ca1d36e3531 (2026-11-12).
|
|
11
|
+
const SEEN_IN_CLOUD = [
|
|
12
|
+
'--load-extension=/home/neko/.gologin/extensions/user-extensions/glpextkeepalive000000000001',
|
|
13
|
+
'--utility-cmd-prefix=/usr/bin/python3',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
test('drops the arguments seen in the cloud', () => {
|
|
17
|
+
const { allowed, blocked } = filterCloudLaunchArguments(SEEN_IN_CLOUD);
|
|
18
|
+
assert.deepEqual(allowed, []);
|
|
19
|
+
assert.deepEqual(blocked, SEEN_IN_CLOUD);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('drops every switch that runs another program', () => {
|
|
23
|
+
const args = [
|
|
24
|
+
'--renderer-cmd-prefix=/bin/sh -c id',
|
|
25
|
+
'--utility-cmd-prefix=/usr/bin/python3',
|
|
26
|
+
'--gpu-cmd-prefix=x',
|
|
27
|
+
'--zygote-cmd-prefix=x',
|
|
28
|
+
'--gpu-launcher=/bin/sh',
|
|
29
|
+
'--plugin-launcher=x',
|
|
30
|
+
'--ppapi-plugin-launcher=x',
|
|
31
|
+
'--browser-subprocess-path=/tmp/x',
|
|
32
|
+
'--nacl-gdb=/tmp/x',
|
|
33
|
+
'--nacl-gdb-script=/tmp/x',
|
|
34
|
+
'--type=renderer',
|
|
35
|
+
];
|
|
36
|
+
assert.deepEqual(filterCloudLaunchArguments(args).allowed, []);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('drops remote debugging, proxy, DNS and path overrides', () => {
|
|
40
|
+
const args = [
|
|
41
|
+
'--remote-debugging-port=9222',
|
|
42
|
+
'--remote-debugging-address=0.0.0.0',
|
|
43
|
+
'--remote-debugging-pipe',
|
|
44
|
+
'--remote-allow-origins=*',
|
|
45
|
+
'--proxy-server=http://user:pass@1.2.3.4:8080',
|
|
46
|
+
'--proxy-pac-url=http://x/pac',
|
|
47
|
+
'--no-proxy-server',
|
|
48
|
+
'--proxy-bypass-list=*',
|
|
49
|
+
'--host-resolver-rules=MAP * 1.2.3.4',
|
|
50
|
+
'--host-rules=MAP * 1.2.3.4',
|
|
51
|
+
'--user-data-dir=/tmp/other',
|
|
52
|
+
'--disk-cache-dir=/tmp/c',
|
|
53
|
+
'--crash-dumps-dir=/tmp/d',
|
|
54
|
+
'--log-file=/etc/passwd',
|
|
55
|
+
'--load-extension=/tmp/ext',
|
|
56
|
+
];
|
|
57
|
+
assert.deepEqual(filterCloudLaunchArguments(args).allowed, []);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('single dash and upper case spellings are caught too', () => {
|
|
61
|
+
const args = ['-utility-cmd-prefix=/usr/bin/python3', '--Renderer-Cmd-Prefix=/bin/sh', '-PROXY-SERVER=x'];
|
|
62
|
+
assert.deepEqual(filterCloudLaunchArguments(args).allowed, []);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('a bare -- is dropped: it would turn the SDK switches after it into URLs', () => {
|
|
66
|
+
assert.deepEqual(filterCloudLaunchArguments(['--', '--restore-last-session']), {
|
|
67
|
+
allowed: ['--restore-last-session'],
|
|
68
|
+
blocked: ['--'],
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('ordinary launch arguments pass through unchanged and in order', () => {
|
|
73
|
+
const args = [
|
|
74
|
+
'--lang=de-DE',
|
|
75
|
+
'--start-maximized',
|
|
76
|
+
'--window-size=1280,800',
|
|
77
|
+
'--disable-features=Translate',
|
|
78
|
+
'--enable-features=WebGPU',
|
|
79
|
+
'--force-device-scale-factor=1.25',
|
|
80
|
+
'--user-agent=Mozilla/5.0 test',
|
|
81
|
+
'--disable-web-security',
|
|
82
|
+
'https://example.com/start',
|
|
83
|
+
];
|
|
84
|
+
assert.deepEqual(filterCloudLaunchArguments(args), { allowed: args, blocked: [] });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('switch names are read without the value', () => {
|
|
88
|
+
assert.equal(launchSwitchName('--utility-cmd-prefix=/usr/bin/python3'), 'utility-cmd-prefix');
|
|
89
|
+
assert.equal(launchSwitchName('-No-Proxy-Server'), 'no-proxy-server');
|
|
90
|
+
assert.equal(launchSwitchName('https://example.com'), null);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('log description hides proxy credentials but keeps what was run', () => {
|
|
94
|
+
assert.equal(describeBlockedArgument('--proxy-server=http://user:pass@1.2.3.4:8080'), '--proxy-server=<redacted>');
|
|
95
|
+
assert.equal(describeBlockedArgument('--utility-cmd-prefix=/usr/bin/python3'), '--utility-cmd-prefix=/usr/bin/python3');
|
|
96
|
+
});
|