@browserless.io/browserless 2.1.0 → 2.2.0-beta-2

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/bin/browserless.js +49 -13
  3. package/bin/scaffold/tsconfig.json +1 -1
  4. package/build/browserless.js +9 -2
  5. package/build/browsers/cdp-chromium.d.ts +1 -1
  6. package/build/browsers/cdp-chromium.js +3 -3
  7. package/build/browsers/index.d.ts +2 -10
  8. package/build/browsers/index.js +28 -24
  9. package/build/browsers/playwright-chromium.d.ts +1 -1
  10. package/build/browsers/playwright-firefox.d.ts +1 -1
  11. package/build/browsers/playwright-webkit.d.ts +1 -1
  12. package/build/constants.d.ts +1 -0
  13. package/build/constants.js +1 -0
  14. package/build/data/classes.json +1 -1
  15. package/build/data/selectors.json +1 -1
  16. package/build/http.d.ts +3 -0
  17. package/build/http.js +3 -0
  18. package/build/routes/chromium/http/content-post.body.json +8 -8
  19. package/build/routes/chromium/http/json-list.d.ts +15 -0
  20. package/build/routes/chromium/http/json-list.js +23 -0
  21. package/build/routes/chromium/http/json-list.response.json +52 -0
  22. package/build/routes/chromium/http/json-new.d.ts +15 -0
  23. package/build/routes/chromium/http/json-new.js +23 -0
  24. package/build/routes/chromium/http/json-new.response.json +44 -0
  25. package/build/routes/chromium/http/json-protocol-get.d.ts +15 -0
  26. package/build/routes/chromium/http/json-protocol-get.js +20 -0
  27. package/build/routes/chromium/http/json-protocol-get.response.json +6 -0
  28. package/build/routes/chromium/http/json-version-get.d.ts +1 -1
  29. package/build/routes/chromium/http/json-version-get.js +1 -1
  30. package/build/routes/chromium/http/pdf-post.body.json +12 -8
  31. package/build/routes/chromium/http/scrape-post.body.json +8 -8
  32. package/build/routes/chromium/http/screenshot-post.body.json +8 -8
  33. package/build/routes/chromium/utils/cdp.d.ts +2 -0
  34. package/build/routes/chromium/utils/cdp.js +14 -0
  35. package/build/types.d.ts +31 -0
  36. package/build/utils.d.ts +10 -0
  37. package/build/utils.js +30 -2
  38. package/package.json +11 -13
  39. package/src/browserless.ts +12 -1
  40. package/src/browsers/cdp-chromium.ts +5 -7
  41. package/src/browsers/index.ts +31 -34
  42. package/src/browsers/playwright-chromium.ts +1 -1
  43. package/src/browsers/playwright-firefox.ts +1 -1
  44. package/src/browsers/playwright-webkit.ts +1 -1
  45. package/src/constants.ts +1 -0
  46. package/src/http.ts +3 -0
  47. package/src/routes/chromium/http/json-list.ts +50 -0
  48. package/src/routes/chromium/http/json-new.ts +50 -0
  49. package/src/routes/chromium/http/json-protocol-get.ts +38 -0
  50. package/src/routes/chromium/http/json-version-get.ts +1 -1
  51. package/src/routes/chromium/utils/cdp.ts +19 -0
  52. package/src/types.ts +38 -0
  53. package/src/utils.ts +52 -4
  54. package/static/docs/swagger.json +315 -10
  55. package/static/function/client.js +2328 -1975
  56. package/browser.json +0 -7
  57. package/scripts/install-cdp-json.js +0 -37
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
- # [Latest](https://github.com/browserless/chrome/compare/v2.0.0...master)
2
- - Dependency updates.
1
+ # [Latest](https://github.com/browserless/chrome/compare/v2.1.0...main)
2
+ - Dependency updates.
3
+ - Add `clean` command to `@browserless.io/browserless` CLI.
4
+ - Adds support for `/json/list`, `/json/new` and `json/protocol` APIs (Chrome only).
5
+ - Removes legacy `browser.json` files since browserless now generates those and caches when requested.
6
+ - Only verify that internally managed browsers are installed when starting.
7
+
8
+ # [v2.1.0](https://github.com/browserless/browserless/compare/v2.0.0-beta-1...v2.1.0)
9
+ - Dependency updates.
10
+ - Name is now `@browserless.io/browserless` to reflect our npm package.
11
+ - NEW: SDK is now live here: https://www.npmjs.com/package/@browserless.io/browserless.
12
+ - Drops gulp and other gulp utilities in favor of our own. Move to modules in `scripts` dir.
13
+ - Many private class properties now use `protected` so they can be referenced in SDK extensions.
14
+ - Adds a `/json/version` route for older libraries that use it.
15
+ - Merge startup/test scripts into `scripts` dir.
16
+ - Moves installed browsers to `/usr/local/bin/playwright-browsers`.
17
+ - Merge root files into package.json where possible.
18
+ - README updates and fixes.
19
+ - Numerous link and copyright fixes.
3
20
 
4
21
  # [v2.0.0](https://github.com/browserless/chrome/compare/master...feat/browserless-2.0)
5
22
  browserless 2.0.0 represents the best body of work after running browserless for over 5 years. It contains mostly the same functionality and more, and is rebuilt to be more modular and offer a NodeJS SDK. It's also much lighter and faster than prior versions and includes a lot of semantic changes.
@@ -14,7 +14,9 @@ import fs from 'fs/promises';
14
14
  import path from 'path';
15
15
  import { spawn } from 'child_process';
16
16
 
17
- debug.enable('browserless*');
17
+ if (typeof process.env.DEBUG === 'undefined') {
18
+ debug.enable('browserless*');
19
+ }
18
20
 
19
21
  const log = debug('browserless.io:sdk:log');
20
22
  const promptLog = debug('browserless.io:prompt');
@@ -22,9 +24,17 @@ const promptLog = debug('browserless.io:prompt');
22
24
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
25
  const cmd = process.argv[2];
24
26
  const subCMD = process.argv[3];
25
- const cwd = process.cwd();
26
- const allowedCMDs = ['build', 'dev', 'docker', 'start', 'create', 'help'];
27
- const srcDir = path.join(cwd, 'build');
27
+ const allowedCMDs = [
28
+ 'build',
29
+ 'dev',
30
+ 'docker',
31
+ 'start',
32
+ 'create',
33
+ 'help',
34
+ 'clean',
35
+ ];
36
+ const projectDir = process.cwd();
37
+ const compiledDir = path.join(projectDir, 'build');
28
38
  const packageJSON = readFile(path.join(__dirname, '..', 'package.json')).then(
29
39
  (r) => JSON.parse(r.toString()),
30
40
  );
@@ -59,7 +69,7 @@ const importClassOverride = async (files, className) => {
59
69
  return;
60
70
  }
61
71
 
62
- const classModuleFullFilePath = path.join(srcDir, classModuleFile);
72
+ const classModuleFullFilePath = path.join(compiledDir, classModuleFile);
63
73
 
64
74
  if (!classModuleFile) {
65
75
  return;
@@ -68,6 +78,12 @@ const importClassOverride = async (files, className) => {
68
78
  return (await import(classModuleFullFilePath)).default;
69
79
  };
70
80
 
81
+ const clean = async () =>
82
+ fs.rm(path.join(compiledDir), {
83
+ force: true,
84
+ recursive: true,
85
+ });
86
+
71
87
  const installDependencies = async (workingDirectory) =>
72
88
  new Promise((resolve, reject) => {
73
89
  spawn('npm', ['i'], {
@@ -88,7 +104,7 @@ const buildDockerImage = async (cmd) => {
88
104
  new Promise((resolve, reject) => {
89
105
  const [docker, ...args] = cmd.split(' ');
90
106
  spawn(docker, args, {
91
- cwd,
107
+ cwd: projectDir,
92
108
  stdio: 'inherit',
93
109
  }).once('close', (code) => {
94
110
  if (code === 0) {
@@ -105,7 +121,7 @@ const buildDockerImage = async (cmd) => {
105
121
  const buildTypeScript = async () =>
106
122
  new Promise((resolve, reject) => {
107
123
  spawn('npx', ['tsc', '--outDir', 'build'], {
108
- cwd,
124
+ cwd: projectDir,
109
125
  stdio: 'inherit',
110
126
  }).once('close', (code) => {
111
127
  if (code === 0) {
@@ -118,16 +134,16 @@ const buildTypeScript = async () =>
118
134
  });
119
135
 
120
136
  const getSourceFiles = async () => {
121
- const files = await fs.readdir(srcDir, { recursive: true });
137
+ const files = await fs.readdir(compiledDir, { recursive: true });
122
138
  const [httpRoutes, webSocketRoutes] = files.reduce(
123
139
  ([httpRoutes, webSocketRoutes], file) => {
124
140
  const parsed = path.parse(file);
125
141
  if (parsed.name.endsWith('http')) {
126
- httpRoutes.push(path.join(srcDir, file));
142
+ httpRoutes.push(path.join(compiledDir, file));
127
143
  }
128
144
 
129
145
  if (parsed.name.endsWith('ws')) {
130
- webSocketRoutes.push(path.join(srcDir, file));
146
+ webSocketRoutes.push(path.join(compiledDir, file));
131
147
  }
132
148
 
133
149
  return [httpRoutes, webSocketRoutes];
@@ -177,6 +193,9 @@ const getArgSwitches = () => {
177
193
  * and validation. Doesn't start the HTTP server.
178
194
  */
179
195
  const build = async () => {
196
+ log(`Cleaning build directory`);
197
+ await clean();
198
+
180
199
  log(`Compiling TypeScript`);
181
200
  await buildTypeScript();
182
201
 
@@ -309,7 +328,7 @@ const start = async (dev = false) => {
309
328
  };
310
329
 
311
330
  const buildDocker = async () => {
312
- const finalDockerPath = path.join(cwd, 'build', 'Dockerfile');
331
+ const finalDockerPath = path.join(projectDir, 'build', 'Dockerfile');
313
332
  const argSwitches = getArgSwitches();
314
333
 
315
334
  await build();
@@ -320,7 +339,10 @@ const buildDocker = async () => {
320
339
 
321
340
  log(`Generating Dockerfile at "${finalDockerPath}"`);
322
341
 
323
- await fs.writeFile(path.join(cwd, 'build', 'Dockerfile'), dockerContents);
342
+ await fs.writeFile(
343
+ path.join(projectDir, 'build', 'Dockerfile'),
344
+ dockerContents,
345
+ );
324
346
 
325
347
  const from =
326
348
  argSwitches.from ||
@@ -391,7 +413,7 @@ const create = async () => {
391
413
  throw new Error(`Name must not include special characters.`);
392
414
  }
393
415
 
394
- const installPath = path.join(cwd, directory);
416
+ const installPath = path.join(projectDir, directory);
395
417
  log(`Creating folder "${installPath}"...`);
396
418
  await fs.mkdir(installPath);
397
419
 
@@ -440,6 +462,15 @@ const help = () => {
440
462
  `);
441
463
  break;
442
464
 
465
+ case 'clean':
466
+ console.log(dedent`
467
+ Usage: npx @browserless.io/browserless clean
468
+
469
+ Description: Cleans the TypeScript generated JavaScript found
470
+ in the "build" directory and any other temporary assets.
471
+ `);
472
+ break;
473
+
443
474
  case 'dev':
444
475
  console.log(dedent`
445
476
  Usage: npx @browserless.io/browserless dev
@@ -492,6 +523,7 @@ const help = () => {
492
523
  Usage: npx @browserless.io/browserless [command] [arguments]
493
524
 
494
525
  Options:
526
+ clean Removes build artifacts and other temporary directories.
495
527
  create Creates a new scaffold project, installs dependencies, and exits.
496
528
  dev Compiles TypeScript, generates build assets and starts the server.
497
529
  build Compiles TypeScript, generates build assets and exits.
@@ -501,6 +533,10 @@ const help = () => {
501
533
  };
502
534
 
503
535
  switch (cmd) {
536
+ case 'clean':
537
+ clean();
538
+ break;
539
+
504
540
  case 'start':
505
541
  start(false);
506
542
  break;
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "extends": "./node_modules/@browserless.io/browserless/tsconfig.json",
3
- "include": ["./src/**/*"],
3
+ "include": ["./src/**/*"]
4
4
  }
@@ -1,5 +1,5 @@
1
1
  import * as path from 'path';
2
- import { BrowserManager, Config, FileSystem, HTTPServer, Limiter, Metrics, Monitoring, Router, Token, WebHooks, availableBrowsers, createLogger, getRouteFiles, makeExternalURL, printLogo, safeParse, } from '@browserless.io/browserless';
2
+ import { BrowserManager, CDPChromium, Config, FileSystem, HTTPServer, Limiter, Metrics, Monitoring, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, Router, Token, WebHooks, availableBrowsers, createLogger, getRouteFiles, makeExternalURL, printLogo, safeParse, } from '@browserless.io/browserless';
3
3
  import { readFile } from 'fs/promises';
4
4
  import { userInfo } from 'os';
5
5
  const routeSchemas = ['body', 'query'];
@@ -88,6 +88,12 @@ export class Browserless {
88
88
  async start() {
89
89
  const httpRoutes = [];
90
90
  const wsRoutes = [];
91
+ const internalBrowsers = [
92
+ CDPChromium,
93
+ PlaywrightFirefox,
94
+ PlaywrightChromium,
95
+ PlaywrightWebkit,
96
+ ];
91
97
  const [[httpRouteFiles, wsRouteFiles], installedBrowsers] = await Promise.all([getRouteFiles(this.config), availableBrowsers]);
92
98
  const docsLink = makeExternalURL(this.config.getExternalAddress(), '/docs');
93
99
  this.debug(printLogo(docsLink));
@@ -137,10 +143,11 @@ export class Browserless {
137
143
  wsRoutes.push(route);
138
144
  }
139
145
  }
140
- // Validate that browsers are installed and route paths are unique
146
+ // Validate that we have the browsers they are asking for
141
147
  [...httpRoutes, ...wsRoutes].forEach((route) => {
142
148
  if ('browser' in route &&
143
149
  route.browser &&
150
+ internalBrowsers.includes(route.browser) &&
144
151
  !installedBrowsers.some((b) => b.name === route.browser?.name)) {
145
152
  throw new Error(`Couldn't load route "${route.path}" due to missing browser of "${route.browser?.name}"`);
146
153
  }
@@ -38,7 +38,7 @@ export declare class CDPChromium extends EventEmitter {
38
38
  process: () => import("child_process").ChildProcess | null;
39
39
  launch: (options?: CDPLaunchOptions) => Promise<Browser>;
40
40
  wsEndpoint: () => string | null;
41
- publicWSEndpoint: (token: string) => string | null;
41
+ publicWSEndpoint: (token: string | null) => string | null;
42
42
  proxyPageWebSocket: (req: Request, socket: Duplex, head: Buffer) => Promise<void>;
43
43
  proxyWebSocket: (req: Request, socket: Duplex, head: Buffer) => Promise<void>;
44
44
  }
@@ -243,14 +243,14 @@ export class CDPChromium extends EventEmitter {
243
243
  throw new ServerError(`No browserWSEndpoint found, did you launch first?`);
244
244
  }
245
245
  socket.once('close', resolve);
246
- this.debug(`Proxying ${req.parsed.href} to browser ${this.browserWSEndpoint}`);
247
246
  const [page] = await this.browser.pages();
248
247
  const pageLocation = `/devtools/page/${this.getPageId(page)}`;
249
248
  this.debug(`Proxying ${req.parsed.href} to page "${pageLocation}"`);
250
- req.url = pageLocation;
249
+ const target = new URL(pageLocation, this.browserWSEndpoint).href;
250
+ req.url = '';
251
251
  this.proxy.ws(req, socket, head, {
252
252
  changeOrigin: true,
253
- target: this.browserWSEndpoint,
253
+ target,
254
254
  }, (error) => {
255
255
  this.debug(`Error proxying session: ${error}`);
256
256
  this.close();
@@ -6,18 +6,10 @@ export declare class BrowserManager {
6
6
  protected launching: Map<string, Promise<unknown>>;
7
7
  protected timers: Map<string, number>;
8
8
  protected debug: import("debug").Debugger;
9
+ protected playwrightBrowserNames: string[];
9
10
  constructor(config: Config);
10
11
  protected removeUserDataDir: (userDataDir: string | null) => Promise<void>;
11
- /**
12
- * Generates a directory for the user-data-dir contents to be saved in. Uses
13
- * the provided sessionId, or creates one when omitted,
14
- * and appends it to the name of the directory. If the
15
- * directory already exists then no action is taken, verified by run `stat`
16
- *
17
- * @param sessionId The ID of the session
18
- * @returns Promise<string> of the fully-qualified path of the directory
19
- */
20
- protected generateDataDir: (sessionId?: string) => Promise<string>;
12
+ getProtocolJSON: () => Promise<object>;
21
13
  getVersionJSON: () => Promise<{
22
14
  Browser: string;
23
15
  'Debugger-Version': string;
@@ -1,13 +1,17 @@
1
- import { BadRequest, CDPChromium, HTTPManagementRoutes, NotFound, ServerError, browserHook, convertIfBase64, createLogger, exists, id, makeExternalURL, noop, pageHook, parseBooleanParam, } from '@browserless.io/browserless';
2
- import path, { join } from 'path';
1
+ import { BadRequest, CDPChromium, HTTPManagementRoutes, NotFound, PlaywrightChromium, PlaywrightFirefox, PlaywrightWebkit, browserHook, convertIfBase64, createLogger, exists, generateDataDir, makeExternalURL, noop, pageHook, parseBooleanParam, } from '@browserless.io/browserless';
3
2
  import { deleteAsync } from 'del';
4
- import { mkdir } from 'fs/promises';
3
+ import path from 'path';
5
4
  export class BrowserManager {
6
5
  config;
7
6
  browsers = new Map();
8
7
  launching = new Map();
9
8
  timers = new Map();
10
9
  debug = createLogger('browser-manager');
10
+ playwrightBrowserNames = [
11
+ PlaywrightChromium.name,
12
+ PlaywrightFirefox.name,
13
+ PlaywrightWebkit.name,
14
+ ];
11
15
  constructor(config) {
12
16
  this.config = config;
13
17
  }
@@ -19,29 +23,27 @@ export class BrowserManager {
19
23
  });
20
24
  }
21
25
  };
22
- /**
23
- * Generates a directory for the user-data-dir contents to be saved in. Uses
24
- * the provided sessionId, or creates one when omitted,
25
- * and appends it to the name of the directory. If the
26
- * directory already exists then no action is taken, verified by run `stat`
27
- *
28
- * @param sessionId The ID of the session
29
- * @returns Promise<string> of the fully-qualified path of the directory
30
- */
31
- generateDataDir = async (sessionId = id()) => {
32
- const baseDirectory = await this.config.getDataDir();
33
- const dataDirPath = join(baseDirectory, `browserless-data-dir-${sessionId}`);
34
- if (await exists(dataDirPath)) {
35
- this.debug(`Data directory already exists, not creating "${dataDirPath}"`);
36
- return dataDirPath;
37
- }
38
- this.debug(`Generating user-data-dir at ${dataDirPath}`);
39
- await mkdir(dataDirPath, { recursive: true }).catch((err) => {
40
- throw new ServerError(`Error creating data-directory "${dataDirPath}": ${err}`);
26
+ getProtocolJSON = async () => {
27
+ this.debug(`Launching Chrome to generate /json/protocol results`);
28
+ const browser = new CDPChromium({
29
+ blockAds: false,
30
+ config: this.config,
31
+ record: false,
32
+ userDataDir: null,
41
33
  });
42
- return dataDirPath;
34
+ await browser.launch();
35
+ const wsEndpoint = browser.wsEndpoint();
36
+ if (!wsEndpoint) {
37
+ throw new Error('There was an error launching the browser');
38
+ }
39
+ const { port } = new URL(wsEndpoint);
40
+ const res = await fetch(`http://127.0.0.1:${port}/json/protocol`);
41
+ const protocolJSON = await res.json();
42
+ browser.close();
43
+ return protocolJSON;
43
44
  };
44
45
  getVersionJSON = async () => {
46
+ this.debug(`Launching Chrome to generate /json/version results`);
45
47
  const browser = new CDPChromium({
46
48
  blockAds: false,
47
49
  config: this.config,
@@ -174,7 +176,9 @@ export class BrowserManager {
174
176
  // Always specify a user-data-dir since plugins can "inject" their own
175
177
  // unless it's playwright which takes care of its own data-dirs
176
178
  const userDataDir = manualUserDataDir ||
177
- (Browser.name === CDPChromium.name ? await this.generateDataDir() : null);
179
+ (!this.playwrightBrowserNames.includes(Browser.name)
180
+ ? await generateDataDir(undefined, this.config)
181
+ : null);
178
182
  const proxyServerArg = launchOptions.args?.find((arg) => arg.includes('--proxy-server='));
179
183
  if (launchOptions.args &&
180
184
  proxyServerArg &&
@@ -31,7 +31,7 @@ export declare class PlaywrightChromium extends EventEmitter {
31
31
  newPage: () => Promise<Page>;
32
32
  launch: (options?: BrowserServerOptions) => Promise<playwright.BrowserServer>;
33
33
  wsEndpoint: () => string | null;
34
- publicWSEndpoint: (token: string) => string | null;
34
+ publicWSEndpoint: (token: string | null) => string | null;
35
35
  proxyPageWebSocket: () => Promise<void>;
36
36
  proxyWebSocket: (req: Request, socket: Duplex, head: Buffer) => Promise<void>;
37
37
  }
@@ -31,7 +31,7 @@ export declare class PlaywrightFirefox extends EventEmitter {
31
31
  newPage: () => Promise<Page>;
32
32
  launch: (options?: BrowserServerOptions) => Promise<playwright.BrowserServer>;
33
33
  wsEndpoint: () => string | null;
34
- publicWSEndpoint: (token: string) => string | null;
34
+ publicWSEndpoint: (token: string | null) => string | null;
35
35
  proxyPageWebSocket: () => Promise<void>;
36
36
  proxyWebSocket: (req: Request, socket: Duplex, head: Buffer) => Promise<void>;
37
37
  }
@@ -31,7 +31,7 @@ export declare class PlaywrightWebkit extends EventEmitter {
31
31
  newPage: () => Promise<Page>;
32
32
  launch: (options?: BrowserServerOptions) => Promise<playwright.BrowserServer>;
33
33
  wsEndpoint: () => string | null;
34
- publicWSEndpoint: (token: string) => string | null;
34
+ publicWSEndpoint: (token: string | null) => string | null;
35
35
  proxyPageWebSocket: () => Promise<void>;
36
36
  proxyWebSocket: (req: Request, socket: Duplex, head: Buffer) => Promise<void>;
37
37
  }
@@ -2,3 +2,4 @@ export declare const encryptionAlgo = "aes-192-cbc";
2
2
  export declare const encryptionSep = ".";
3
3
  export declare const liveURLSep = ":";
4
4
  export declare const keyLength = 24;
5
+ export declare const BLESS_PAGE_IDENTIFIER = "BLESS";
@@ -2,3 +2,4 @@ export const encryptionAlgo = 'aes-192-cbc';
2
2
  export const encryptionSep = '.';
3
3
  export const liveURLSep = ':';
4
4
  export const keyLength = 24;
5
+ export const BLESS_PAGE_IDENTIFIER = 'BLESS';
@@ -1 +1 @@
1
- ["b-modal-banner--disabled","blocked","bodyBlocked","c24-cc-visible","cbar","cmp-open","consent-modal-open","consent-modal-overflow","CookiePolicy--show","cookies","cu_k_cookie_consent_modal_open","gcdc-locked","gdpr-infobar-visible","gdprbanner_consent_gdpr_consent","gdprCookieBanner-acceptedAll","hasCookieBanner","ibeugdpr-disabled","modal--is-open","modal-open","no_scroll","no-scroll","shopee-no-scroll","show-cookie-policy-info","sp-message-open","ta-cc-modal-open","touchevents-false","wt-cli-eu-country","wt-cli-geoip-on"]
1
+ ["b-modal-banner--disabled","blocked","bodyBlocked","c24-cc-visible","cbar","ccm-blocked","cmp-open","consent-modal-open","consent-modal-overflow","CookiePolicy--show","cookies","cu_k_cookie_consent_modal_open","gcdc-locked","gdpr-infobar-visible","gdprbanner_consent_gdpr_consent","gdprCookieBanner-acceptedAll","hasCookieBanner","ibeugdpr-disabled","idxrcookies-block-user-nav","modal--is-open","modal-open","no_scroll","no-scroll","noScroll","popin-gdpr-no-scroll","shopee-no-scroll","show-cookie-policy-info","sp-message-open","start-cookies","ta-cc-modal-open","touchevents-false","wt-cli-eu-country","wt-cli-geoip-on"]