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 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
@@ -8,6 +8,7 @@ export type GoLoginOptions = {
8
8
  tmpdir?: string;
9
9
  waitWebsocket?: boolean;
10
10
  isCloudHeadless?: boolean;
11
+ isCloudBrowser?: boolean;
11
12
  skipOrbitaHashChecking?: boolean;
12
13
  uploadCookiesToServer?: boolean;
13
14
  writeCookiesFromServer?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gologin",
3
- "version": "3.0.6",
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": "echo \"No unit tests specified\" && exit 0",
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: 'IndexedDB',
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
- export const getDirectoriesToDeleteForNode = (routerSlash = '/') =>
55
- DEFAULT_FOLDER_USELESS_FILE.reduce((res, el) => {
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 = () => DEFAULT_FOLDER_USELESS_FILE.reduce((res, el) => {
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
+ });