@browserless.io/browserless 2.24.0-beta-5 → 2.24.1

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 (79) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/bin/browserless.js +1 -1
  3. package/build/browsers/browsers.playwright.d.ts +1 -0
  4. package/build/browsers/browsers.playwright.js +3 -0
  5. package/build/browsers/index.d.ts +2 -1
  6. package/build/browsers/index.js +28 -7
  7. package/build/http.d.ts +5 -0
  8. package/build/http.js +1 -0
  9. package/build/routes/chrome/http/content.post.body.json +8 -8
  10. package/build/routes/chrome/http/content.post.query.json +4 -0
  11. package/build/routes/chrome/http/download.post.query.json +4 -0
  12. package/build/routes/chrome/http/function.post.query.json +4 -0
  13. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  14. package/build/routes/chrome/http/pdf.post.query.json +4 -0
  15. package/build/routes/chrome/http/performance.post.query.json +4 -0
  16. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  17. package/build/routes/chrome/http/scrape.post.query.json +4 -0
  18. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  19. package/build/routes/chrome/http/screenshot.post.query.json +4 -0
  20. package/build/routes/chrome/tests/kill-sessions.spec.d.ts +1 -0
  21. package/build/routes/chrome/tests/kill-sessions.spec.js +80 -0
  22. package/build/routes/chrome/ws/browser.query.json +4 -0
  23. package/build/routes/chrome/ws/cdp.query.json +4 -0
  24. package/build/routes/chrome/ws/page.query.json +4 -0
  25. package/build/routes/chrome/ws/playwright.query.json +4 -0
  26. package/build/routes/chromium/http/content.post.body.json +8 -8
  27. package/build/routes/chromium/http/content.post.query.json +4 -0
  28. package/build/routes/chromium/http/download.post.query.json +4 -0
  29. package/build/routes/chromium/http/function.post.query.json +4 -0
  30. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  31. package/build/routes/chromium/http/pdf.post.query.json +4 -0
  32. package/build/routes/chromium/http/performance.post.query.json +4 -0
  33. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  34. package/build/routes/chromium/http/scrape.post.query.json +4 -0
  35. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  36. package/build/routes/chromium/http/screenshot.post.query.json +4 -0
  37. package/build/routes/chromium/tests/kill-sessions.spec.d.ts +1 -0
  38. package/build/routes/chromium/tests/kill-sessions.spec.js +80 -0
  39. package/build/routes/chromium/tests/websocket.spec.js +23 -0
  40. package/build/routes/chromium/ws/browser.query.json +4 -0
  41. package/build/routes/chromium/ws/cdp.query.json +4 -0
  42. package/build/routes/chromium/ws/page.query.json +4 -0
  43. package/build/routes/chromium/ws/playwright.query.json +4 -0
  44. package/build/routes/firefox/tests/kill-sessions.spec.d.ts +1 -0
  45. package/build/routes/firefox/tests/kill-sessions.spec.js +72 -0
  46. package/build/routes/firefox/ws/playwright.query.json +4 -0
  47. package/build/routes/management/http/kill.get.d.ts +21 -0
  48. package/build/routes/management/http/kill.get.js +19 -0
  49. package/build/routes/management/http/kill.get.query.json +193 -0
  50. package/build/routes/management/http/meta.get.js +3 -2
  51. package/build/routes/management/http/sessions.get.query.json +1 -0
  52. package/build/routes/management/tests/management.spec.js +12 -0
  53. package/build/routes/webkit/tests/kill-sessions.spec.d.ts +1 -0
  54. package/build/routes/webkit/tests/kill-sessions.spec.js +72 -0
  55. package/build/routes/webkit/ws/playwright.query.json +4 -0
  56. package/build/shared/utils/performance/main.js +2 -1
  57. package/build/types.d.ts +2 -0
  58. package/build/types.js +1 -0
  59. package/build/utils.d.ts +1 -1
  60. package/build/utils.js +1 -10
  61. package/package.json +13 -13
  62. package/src/browsers/browsers.playwright.ts +3 -0
  63. package/src/browsers/index.ts +33 -12
  64. package/src/http.ts +6 -0
  65. package/src/routes/chrome/tests/kill-sessions.spec.ts +99 -0
  66. package/src/routes/chromium/tests/kill-sessions.spec.ts +99 -0
  67. package/src/routes/chromium/tests/websocket.spec.ts +29 -0
  68. package/src/routes/firefox/tests/kill-sessions.spec.ts +99 -0
  69. package/src/routes/management/http/kill.get.ts +40 -0
  70. package/src/routes/management/http/meta.get.ts +12 -10
  71. package/src/routes/management/tests/management.spec.ts +19 -0
  72. package/src/routes/webkit/tests/kill-sessions.spec.ts +99 -0
  73. package/src/shared/utils/performance/main.ts +2 -8
  74. package/src/types.ts +1 -0
  75. package/src/utils.ts +2 -11
  76. package/static/docs/swagger.json +297 -10
  77. package/static/docs/swagger.min.json +296 -9
  78. package/static/function/client.js +157 -42
  79. package/static/function/index.html +157 -42
@@ -0,0 +1,193 @@
1
+ {
2
+ "type": "object",
3
+ "properties": {
4
+ "token": {
5
+ "description": "The authorization token",
6
+ "type": "string"
7
+ },
8
+ "browserId": {
9
+ "type": "string"
10
+ },
11
+ "trackingId": {
12
+ "description": "Custom session identifier",
13
+ "type": "string"
14
+ },
15
+ "blockAds": {
16
+ "description": "Whether or nor to load ad-blocking extensions for the session.\nThis currently uses uBlock Origin and may cause certain sites\nto not load properly.",
17
+ "type": "boolean"
18
+ },
19
+ "launch": {
20
+ "description": "Launch options, which can be either an object\nof puppeteer.launch options or playwright.launchServer\noptions, depending on the API. Must be either JSON\nobject, or a base64-encoded JSON object.",
21
+ "anyOf": [
22
+ {
23
+ "$ref": "#/definitions/CDPLaunchOptions"
24
+ },
25
+ {
26
+ "$ref": "#/definitions/BrowserServerOptions"
27
+ },
28
+ {
29
+ "type": "string"
30
+ }
31
+ ]
32
+ },
33
+ "timeout": {
34
+ "description": "Override the system-level timeout for this request.\nAccepts a value in milliseconds.",
35
+ "type": "number"
36
+ }
37
+ },
38
+ "additionalProperties": false,
39
+ "required": [
40
+ "token"
41
+ ],
42
+ "definitions": {
43
+ "CDPLaunchOptions": {
44
+ "type": "object",
45
+ "properties": {
46
+ "args": {
47
+ "type": "array",
48
+ "items": {
49
+ "type": "string"
50
+ }
51
+ },
52
+ "defaultViewport": {
53
+ "type": "object",
54
+ "properties": {
55
+ "deviceScaleFactor": {
56
+ "type": "number"
57
+ },
58
+ "hasTouch": {
59
+ "type": "boolean"
60
+ },
61
+ "height": {
62
+ "type": "number"
63
+ },
64
+ "isLandscape": {
65
+ "type": "boolean"
66
+ },
67
+ "isMobile": {
68
+ "type": "boolean"
69
+ },
70
+ "width": {
71
+ "type": "number"
72
+ }
73
+ },
74
+ "additionalProperties": false,
75
+ "required": [
76
+ "height",
77
+ "width"
78
+ ]
79
+ },
80
+ "devtools": {
81
+ "type": "boolean"
82
+ },
83
+ "dumpio": {
84
+ "type": "boolean"
85
+ },
86
+ "headless": {
87
+ "enum": [
88
+ false,
89
+ "shell",
90
+ true
91
+ ]
92
+ },
93
+ "ignoreDefaultArgs": {
94
+ "anyOf": [
95
+ {
96
+ "type": "array",
97
+ "items": {
98
+ "type": "string"
99
+ }
100
+ },
101
+ {
102
+ "type": "boolean"
103
+ }
104
+ ]
105
+ },
106
+ "ignoreHTTPSErrors": {
107
+ "type": "boolean"
108
+ },
109
+ "slowMo": {
110
+ "type": "number"
111
+ },
112
+ "stealth": {
113
+ "type": "boolean"
114
+ },
115
+ "timeout": {
116
+ "type": "number"
117
+ },
118
+ "userDataDir": {
119
+ "type": "string"
120
+ },
121
+ "waitForInitialPage": {
122
+ "type": "boolean"
123
+ }
124
+ },
125
+ "additionalProperties": false
126
+ },
127
+ "BrowserServerOptions": {
128
+ "type": "object",
129
+ "properties": {
130
+ "args": {
131
+ "type": "array",
132
+ "items": {
133
+ "type": "string"
134
+ }
135
+ },
136
+ "chromiumSandbox": {
137
+ "type": "boolean"
138
+ },
139
+ "devtools": {
140
+ "type": "boolean"
141
+ },
142
+ "downloadsPath": {
143
+ "type": "string"
144
+ },
145
+ "headless": {
146
+ "type": "boolean"
147
+ },
148
+ "ignoreDefaultArgs": {
149
+ "anyOf": [
150
+ {
151
+ "type": "array",
152
+ "items": {
153
+ "type": "string"
154
+ }
155
+ },
156
+ {
157
+ "type": "boolean"
158
+ }
159
+ ]
160
+ },
161
+ "proxy": {
162
+ "type": "object",
163
+ "properties": {
164
+ "bypass": {
165
+ "type": "string"
166
+ },
167
+ "password": {
168
+ "type": "string"
169
+ },
170
+ "server": {
171
+ "type": "string"
172
+ },
173
+ "username": {
174
+ "type": "string"
175
+ }
176
+ },
177
+ "additionalProperties": false,
178
+ "required": [
179
+ "server"
180
+ ]
181
+ },
182
+ "timeout": {
183
+ "type": "number"
184
+ },
185
+ "tracesDir": {
186
+ "type": "string"
187
+ }
188
+ },
189
+ "additionalProperties": false
190
+ }
191
+ },
192
+ "$schema": "http://json-schema.org/draft-07/schema#"
193
+ }
@@ -1,9 +1,10 @@
1
1
  import { APITags, BrowserlessRoutes, ChromiumCDP, FirefoxPlaywright, HTTPManagementRoutes, HTTPRoute, Methods, WebKitPlaywright, availableBrowsers, contentTypes, jsonResponse, } from '@browserless.io/browserless';
2
2
  import { createRequire } from 'module';
3
+ import path from 'path';
3
4
  const semverReg = /(\*|\^|>|=|<|~)/gi;
4
5
  const require = createRequire(import.meta.url);
5
- const blessPackageJSON = require('../../../../package.json');
6
- const { browsers } = require('../../../../node_modules/playwright-core/browsers.json');
6
+ const blessPackageJSON = require(path.join(process.cwd(), 'package.json'));
7
+ const { browsers } = require(path.join(process.cwd(), 'node_modules', 'playwright-core', 'browsers.json'));
7
8
  const chromium = browsers.find((b) => b.name === 'chromium').browserVersion;
8
9
  const firefox = browsers.find((b) => b.name === 'firefox').browserVersion;
9
10
  const webkit = browsers.find((b) => b.name === 'webkit').browserVersion;
@@ -6,6 +6,7 @@
6
6
  "type": "string"
7
7
  },
8
8
  "trackingId": {
9
+ "description": "Custom session identifier",
9
10
  "type": "string"
10
11
  },
11
12
  "blockAds": {
@@ -68,4 +68,16 @@ describe('Management APIs', function () {
68
68
  expect(res.status).to.equal(204);
69
69
  });
70
70
  });
71
+ it('allows requests to /kill', async () => {
72
+ await start();
73
+ await fetch('http://localhost:3000/kill/all?token=6R0W53R135510').then(async (res) => {
74
+ expect(res.status).to.equal(204);
75
+ });
76
+ });
77
+ it('Throws an error trying to kill invalid session', async () => {
78
+ await start();
79
+ await fetch(`http://localhost:3000/kill/invalid-session?token=6R0W53R135510`).then(async (res) => {
80
+ expect(res.status).to.equal(404);
81
+ });
82
+ });
71
83
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { Browserless, Config, Metrics } from '@browserless.io/browserless';
2
+ import { expect } from 'chai';
3
+ import { webkit } from 'playwright-core';
4
+ describe('/kill API webkit', function () {
5
+ let browserless;
6
+ const start = ({ config = new Config(), metrics = new Metrics(), } = {}) => {
7
+ config.setToken('browserless');
8
+ browserless = new Browserless({ config, metrics });
9
+ return browserless.start();
10
+ };
11
+ afterEach(async () => {
12
+ await browserless.stop();
13
+ });
14
+ it('Kill all sessions', async () => {
15
+ await start();
16
+ const browser1 = await webkit.connect(`ws://localhost:3000/webkit/playwright?token=browserless&trackingId=session-1`);
17
+ const browser2 = await webkit.connect(`ws://localhost:3000/webkit/playwright?token=browserless&trackingId=session-2`);
18
+ await fetch('http://localhost:3000/kill/all?token=browserless').then(async (res) => {
19
+ expect(res.status).to.equal(204);
20
+ });
21
+ let errorThrown1;
22
+ try {
23
+ await browser1.newPage();
24
+ }
25
+ catch (e) {
26
+ errorThrown1 = e;
27
+ }
28
+ let errorThrown2;
29
+ try {
30
+ await browser2.newPage();
31
+ }
32
+ catch (e) {
33
+ errorThrown2 = e;
34
+ }
35
+ expect(errorThrown1.message).contains('closed');
36
+ expect(errorThrown2.message).contains('closed');
37
+ });
38
+ it('Kill session by browserId', async () => {
39
+ await start();
40
+ const browser = await webkit.connect(`ws://localhost:3000/webkit/playwright?token=browserless`);
41
+ await fetch('http://localhost:3000/sessions?token=browserless').then(async (res) => {
42
+ const sessions = await res.json();
43
+ const browserId = sessions[0].browserId;
44
+ await fetch(`http://localhost:3000/kill/${browserId}?token=browserless`).then(async (res) => {
45
+ expect(res.status).to.equal(204);
46
+ });
47
+ });
48
+ let errorThrown;
49
+ try {
50
+ await browser.newPage();
51
+ }
52
+ catch (e) {
53
+ errorThrown = e;
54
+ }
55
+ expect(errorThrown.message).contains('closed');
56
+ });
57
+ it('Kill session by trackingId', async () => {
58
+ await start();
59
+ const browser = await webkit.connect(`ws://localhost:3000/webkit/playwright?token=browserless&trackingId=session-1`);
60
+ await fetch('http://localhost:3000/kill/session-1?token=browserless').then(async (res) => {
61
+ expect(res.status).to.equal(204);
62
+ });
63
+ let errorThrown;
64
+ try {
65
+ await browser.newPage();
66
+ }
67
+ catch (e) {
68
+ errorThrown = e;
69
+ }
70
+ expect(errorThrown.message).contains('closed');
71
+ });
72
+ });
@@ -23,6 +23,10 @@
23
23
  "token": {
24
24
  "description": "The authorization token",
25
25
  "type": "string"
26
+ },
27
+ "trackingId": {
28
+ "description": "Custom session identifier",
29
+ "type": "string"
26
30
  }
27
31
  },
28
32
  "additionalProperties": false,
@@ -1,11 +1,12 @@
1
1
  import { fork } from 'child_process';
2
2
  import path from 'path';
3
+ const __dirname = import.meta.dirname;
3
4
  const DEFAULT_AUDIT_CONFIG = {
4
5
  extends: 'lighthouse:default',
5
6
  };
6
7
  export default async ({ browser, context, logger, timeout, }) => {
7
8
  return new Promise((resolve, reject) => {
8
- const childPath = path.join('./', 'build', 'shared', 'utils', 'performance', 'child.js');
9
+ const childPath = path.join(__dirname, 'child.js');
9
10
  logger.trace(`Starting up child at ${childPath}`);
10
11
  const child = fork(childPath);
11
12
  const port = new URL(browser.wsEndpoint() || '').port;
package/build/types.d.ts CHANGED
@@ -520,6 +520,7 @@ export declare const BrowserlessWebKitRoutes: {
520
520
  export declare const BrowserlessManagementRoutes: {
521
521
  ActiveGetRoute: string;
522
522
  ConfigGetRoute: string;
523
+ KillGetRoute: string;
523
524
  MetaGetRoute: string;
524
525
  MetricsGetRoute: string;
525
526
  MetricsTotalGetRoute: string;
@@ -530,6 +531,7 @@ export declare const BrowserlessManagementRoutes: {
530
531
  export declare const BrowserlessRoutes: {
531
532
  ActiveGetRoute: string;
532
533
  ConfigGetRoute: string;
534
+ KillGetRoute: string;
533
535
  MetaGetRoute: string;
534
536
  MetricsGetRoute: string;
535
537
  MetricsTotalGetRoute: string;
package/build/types.js CHANGED
@@ -190,6 +190,7 @@ export const BrowserlessWebKitRoutes = {
190
190
  export const BrowserlessManagementRoutes = {
191
191
  ActiveGetRoute: 'ActiveGetRoute',
192
192
  ConfigGetRoute: 'ConfigGetRoute',
193
+ KillGetRoute: 'KillGetRoute',
193
194
  MetaGetRoute: 'MetaGetRoute',
194
195
  MetricsGetRoute: 'MetricsGetRoute',
195
196
  MetricsTotalGetRoute: 'MetricsTotalGetRoute',
package/build/utils.d.ts CHANGED
@@ -126,7 +126,7 @@ export declare class Timeout extends Error {
126
126
  export declare const bestAttemptCatch: (bestAttempt: boolean) => (err: Error) => void;
127
127
  export declare const parseBooleanParam: (params: URLSearchParams, name: string, defaultValue: boolean) => boolean;
128
128
  export declare const parseNumberParam: (params: URLSearchParams, name: string, defaultValue: number) => number;
129
- export declare const parseStringParam: (params: URLSearchParams, name: string, defaultValue: string) => any;
129
+ export declare const parseStringParam: (params: URLSearchParams, name: string, defaultValue: string) => string;
130
130
  export declare const encrypt: (text: string, secret: Buffer) => string;
131
131
  export declare const decrypt: (encryptedText: string, secret: Buffer) => string;
132
132
  interface RequestInitTimeout extends RequestInit {
package/build/utils.js CHANGED
@@ -538,16 +538,7 @@ export const parseStringParam = (params, name, defaultValue) => {
538
538
  if (value === null) {
539
539
  return defaultValue;
540
540
  }
541
- // ?param format (no specified value)
542
- if (value === '') {
543
- return true;
544
- }
545
- try {
546
- return JSON.parse(value);
547
- }
548
- catch {
549
- return value;
550
- }
541
+ return value;
551
542
  };
552
543
  export const encrypt = (text, secret) => {
553
544
  const iv = crypto.randomBytes(16);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserless.io/browserless",
3
- "version": "2.24.0-beta-5",
3
+ "version": "2.24.1",
4
4
  "license": "SSPL",
5
5
  "description": "The browserless platform",
6
6
  "author": "browserless.io",
@@ -48,25 +48,25 @@
48
48
  "tsconfig.json"
49
49
  ],
50
50
  "dependencies": {
51
- "debug": "^4.3.7",
51
+ "debug": "^4.4.0",
52
52
  "del": "^8.0.0",
53
53
  "enjoi": "^9.0.1",
54
54
  "file-type": "^19.6.0",
55
55
  "get-port": "^7.1.0",
56
56
  "gradient-string": "^3.0.0",
57
57
  "http-proxy": "^1.18.1",
58
- "lighthouse": "^12.2.2",
58
+ "lighthouse": "^12.3.0",
59
59
  "micromatch": "^4.0.8",
60
60
  "playwright-1.45": "npm:playwright-core@1.45.3",
61
61
  "playwright-1.46": "npm:playwright-core@1.46.1",
62
62
  "playwright-1.47": "npm:playwright-core@1.47.2",
63
63
  "playwright-1.48": "npm:playwright-core@1.48.2",
64
- "playwright-core": "^1.49.0",
65
- "puppeteer-core": "^23.10.0",
64
+ "playwright-core": "^1.49.1",
65
+ "puppeteer-core": "^23.11.1",
66
66
  "puppeteer-extra": "^3.3.6",
67
67
  "puppeteer-extra-plugin-stealth": "^2.11.2",
68
68
  "queue": "^7.0.0",
69
- "systeminformation": "^5.23.5",
69
+ "systeminformation": "^5.23.16",
70
70
  "tar-fs": "^3.0.6"
71
71
  },
72
72
  "optionalDependencies": {
@@ -76,23 +76,23 @@
76
76
  "@types/http-proxy": "^1.17.15",
77
77
  "@types/micromatch": "^4.0.9",
78
78
  "@types/mocha": "^10.0.10",
79
- "@types/node": "^22.10.1",
79
+ "@types/node": "^22.10.2",
80
80
  "@types/sinon": "^17.0.3",
81
- "@typescript-eslint/eslint-plugin": "^8.17.0",
82
- "@typescript-eslint/parser": "^8.17.0",
81
+ "@typescript-eslint/eslint-plugin": "^8.18.1",
82
+ "@typescript-eslint/parser": "^8.18.1",
83
83
  "assert": "^2.0.0",
84
84
  "chai": "^5.1.2",
85
85
  "cross-env": "^7.0.3",
86
86
  "env-cmd": "^10.1.0",
87
- "esbuild": "^0.24.0",
87
+ "esbuild": "^0.24.2",
88
88
  "esbuild-plugin-polyfill-node": "^0.3.0",
89
- "eslint": "^9.16.0",
89
+ "eslint": "^9.17.0",
90
90
  "extract-zip": "^2.0.1",
91
91
  "gunzip-maybe": "^1.4.2",
92
- "marked": "^15.0.3",
92
+ "marked": "^15.0.4",
93
93
  "mocha": "^11.0.1",
94
94
  "move-file": "^3.1.0",
95
- "prettier": "^3.4.1",
95
+ "prettier": "^3.4.2",
96
96
  "sinon": "^19.0.2",
97
97
  "ts-node": "^10.9.2",
98
98
  "typescript": "^5.7.2",
@@ -24,6 +24,7 @@ class BasePlaywright extends EventEmitter {
24
24
  protected userDataDir: string | null;
25
25
  protected running = false;
26
26
  protected logger: Logger;
27
+ protected socket: Duplex | null = null;
27
28
  protected proxy = httpProxy.createProxyServer();
28
29
  protected browser: playwright.BrowserServer | null = null;
29
30
  protected browserWSEndpoint: string | null = null;
@@ -78,6 +79,7 @@ class BasePlaywright extends EventEmitter {
78
79
  this.logger.info(
79
80
  `Closing ${this.constructor.name} process and all listeners`,
80
81
  );
82
+ this.socket?.destroy();
81
83
  this.emit('close');
82
84
  this.cleanListeners();
83
85
  this.browser.close();
@@ -167,6 +169,7 @@ class BasePlaywright extends EventEmitter {
167
169
  socket: Duplex,
168
170
  head: Buffer,
169
171
  ): Promise<void> {
172
+ this.socket = socket;
170
173
  return new Promise((resolve, reject) => {
171
174
  if (!this.browserWSEndpoint) {
172
175
  throw new ServerError(
@@ -16,7 +16,6 @@ import {
16
16
  Config,
17
17
  FileSystem,
18
18
  FirefoxPlaywright,
19
- HTTPManagementRoutes,
20
19
  Hooks,
21
20
  Logger,
22
21
  NotFound,
@@ -35,6 +34,7 @@ import {
35
34
  } from '@browserless.io/browserless';
36
35
  import { Page } from 'puppeteer-core';
37
36
  import { deleteAsync } from 'del';
37
+ import micromatch from 'micromatch';
38
38
  import path from 'path';
39
39
 
40
40
  export class BrowserManager {
@@ -236,11 +236,7 @@ export class BrowserManager {
236
236
  initialConnectURL: new URL(session.initialConnectURL, serverAddress)
237
237
  .href,
238
238
  killURL: session.id
239
- ? makeExternalURL(
240
- serverAddress,
241
- HTTPManagementRoutes.sessions,
242
- session.id,
243
- )
239
+ ? makeExternalURL(serverAddress, '/kill/', session.id)
244
240
  : null,
245
241
  running: browser.isRunning(),
246
242
  timeAliveMs: Date.now() - session.startedOn,
@@ -277,12 +273,13 @@ export class BrowserManager {
277
273
  public async close(
278
274
  browser: BrowserInstance,
279
275
  session: BrowserlessSession,
276
+ force = false,
280
277
  ): Promise<void> {
281
278
  const now = Date.now();
282
279
  const keepUntil = browser.keepUntil();
283
280
  const connected = session.numbConnected;
284
281
  const hasKeepUntil = keepUntil > now;
285
- const keepOpen = connected > 0 || hasKeepUntil;
282
+ const keepOpen = (connected > 0 || hasKeepUntil) && !force;
286
283
  const cleanupACtions: Array<() => Promise<void>> = [];
287
284
  const priorTimer = this.timers.get(session.id);
288
285
 
@@ -292,10 +289,10 @@ export class BrowserManager {
292
289
  }
293
290
 
294
291
  this.log.info(
295
- `${session.numbConnected} Client(s) are currently connected, Keep-until: ${keepUntil}`,
292
+ `${session.numbConnected} Client(s) are currently connected, Keep-until: ${keepUntil}, force: ${force}`,
296
293
  );
297
294
 
298
- if (hasKeepUntil) {
295
+ if (!force && hasKeepUntil) {
299
296
  const timeout = keepUntil - now;
300
297
  this.log.trace(
301
298
  `Setting timer ${timeout.toLocaleString()} for "${session.id}"`,
@@ -328,6 +325,28 @@ export class BrowserManager {
328
325
  }
329
326
  }
330
327
 
328
+ public async killSessions(target: string): Promise<void> {
329
+ this.log.info(`killSessions invoked target: "${target}"`);
330
+ const sessions = Array.from(this.browsers);
331
+ let closed = 0;
332
+ for (const [browser, session] of sessions) {
333
+ if (
334
+ session.trackingId === target ||
335
+ session.id === target ||
336
+ target === 'all'
337
+ ) {
338
+ this.log.info(
339
+ `Closing browser via killSessions BrowserId: "${session.id}", trackingId: "${session.trackingId}"`,
340
+ );
341
+ this.close(browser, session, true);
342
+ closed++;
343
+ }
344
+ }
345
+ if (closed === 0 && target !== 'all') {
346
+ throw new NotFound(`Couldn't locate session for id: "${target}"`);
347
+ }
348
+ }
349
+
331
350
  public async getAllSessions(
332
351
  trackingId?: string,
333
352
  ): Promise<BrowserlessSessionJSON[]> {
@@ -398,12 +417,14 @@ export class BrowserManager {
398
417
  );
399
418
  }
400
419
 
401
- if (
402
- ['/', '.', '\\'].some((routeLike) => trackingId.includes(routeLike))
403
- ) {
420
+ if (!micromatch.isMatch(trackingId, '+([0-9a-zA-Z-_])')) {
404
421
  throw new BadRequest(`trackingId contains invalid characters`);
405
422
  }
406
423
 
424
+ if (trackingId === 'all') {
425
+ throw new BadRequest(`trackingId cannot be the reserved word "all"`);
426
+ }
427
+
407
428
  this.log.info(`Assigning session trackingId "${trackingId}"`);
408
429
  }
409
430
 
package/src/http.ts CHANGED
@@ -126,6 +126,7 @@ export enum HTTPRoutes {
126
126
  export enum HTTPManagementRoutes {
127
127
  active = '/active?(/)',
128
128
  config = '/config?(/)',
129
+ kill = '/kill/+([0-9a-zA-Z-_])?(/)',
129
130
  meta = '/meta?(/)',
130
131
  metrics = '/metrics?(/)',
131
132
  metricsTotal = '/metrics/total?(/)',
@@ -174,4 +175,9 @@ export interface SystemQueryParameters {
174
175
  * The authorization token
175
176
  */
176
177
  token?: string;
178
+
179
+ /**
180
+ * Custom session identifier
181
+ */
182
+ trackingId?: string;
177
183
  }