@browserless.io/browserless 2.0.0-beta-5 → 2.0.0-beta-7

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 (246) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +11 -11
  3. package/bin/browserless.js +169 -11
  4. package/bin/scaffold/README.md +415 -0
  5. package/bin/scaffold/package.json +21 -0
  6. package/bin/scaffold/src/hello-world.http.ts +27 -0
  7. package/bin/scaffold/tsconfig.json +4 -0
  8. package/build/browserless.js +18 -15
  9. package/build/browsers/index.d.ts +2 -18
  10. package/build/browsers/index.js +43 -14
  11. package/build/file-system.d.ts +5 -0
  12. package/build/file-system.js +20 -5
  13. package/build/file-system.spec.d.ts +1 -0
  14. package/build/file-system.spec.js +44 -0
  15. package/build/http.d.ts +3 -3
  16. package/build/http.js +3 -3
  17. package/build/router.js +2 -4
  18. package/build/routes/chromium/http/content-post.body.json +8 -8
  19. package/build/routes/chromium/http/content-post.d.ts +15 -3
  20. package/build/routes/chromium/http/content-post.js +14 -15
  21. package/build/routes/chromium/http/download-post.d.ts +16 -3
  22. package/build/routes/chromium/http/download-post.js +17 -22
  23. package/build/routes/chromium/http/function-post.d.ts +16 -3
  24. package/build/routes/chromium/http/function-post.js +17 -22
  25. package/build/routes/chromium/http/pdf-post.body.json +8 -8
  26. package/build/routes/chromium/http/pdf-post.d.ts +15 -3
  27. package/build/routes/chromium/http/pdf-post.js +19 -15
  28. package/build/routes/chromium/http/performance.d.ts +15 -3
  29. package/build/routes/chromium/http/performance.js +15 -23
  30. package/build/routes/chromium/http/scrape-post.body.json +8 -8
  31. package/build/routes/chromium/http/scrape-post.d.ts +15 -3
  32. package/build/routes/chromium/http/scrape-post.js +15 -16
  33. package/build/routes/chromium/http/screenshot-post.body.json +8 -8
  34. package/build/routes/chromium/http/screenshot-post.d.ts +15 -3
  35. package/build/routes/chromium/http/screenshot-post.js +18 -15
  36. package/build/routes/chromium/tests/websocket.spec.js +20 -1
  37. package/build/routes/chromium/utils/function/handler.js +2 -2
  38. package/build/routes/chromium/ws/browser.d.ts +13 -3
  39. package/build/routes/chromium/ws/browser.js +10 -11
  40. package/build/routes/chromium/ws/cdp-chromium.d.ts +13 -3
  41. package/build/routes/chromium/ws/cdp-chromium.js +10 -11
  42. package/build/routes/chromium/ws/page.d.ts +13 -3
  43. package/build/routes/chromium/ws/page.js +10 -11
  44. package/build/routes/chromium/ws/playwright-chromium.d.ts +13 -3
  45. package/build/routes/chromium/ws/playwright-chromium.js +11 -12
  46. package/build/routes/firefox/ws/playwright-firefox.d.ts +13 -3
  47. package/build/routes/firefox/ws/playwright-firefox.js +11 -12
  48. package/build/routes/management/http/config-get.d.ts +15 -3
  49. package/build/routes/management/http/config-get.js +15 -20
  50. package/build/routes/management/http/metrics-get.d.ts +15 -3
  51. package/build/routes/management/http/metrics-get.js +16 -21
  52. package/build/routes/management/http/metrics-total-get.d.ts +15 -3
  53. package/build/routes/management/http/metrics-total-get.js +16 -21
  54. package/build/routes/management/http/sessions-get.d.ts +15 -3
  55. package/build/routes/management/http/sessions-get.js +16 -20
  56. package/build/routes/management/http/static-get.d.ts +15 -3
  57. package/build/routes/management/http/static-get.js +15 -20
  58. package/build/routes/webkit/ws/playwright-webkit.d.ts +13 -3
  59. package/build/routes/webkit/ws/playwright-webkit.js +11 -12
  60. package/build/server.js +0 -1
  61. package/build/types.d.ts +48 -38
  62. package/build/types.js +135 -0
  63. package/extensions/ublock/1p-filters.html +0 -1
  64. package/extensions/ublock/3p-filters.html +0 -2
  65. package/extensions/ublock/_locales/bg/messages.json +6 -6
  66. package/extensions/ublock/_locales/br_FR/messages.json +14 -14
  67. package/extensions/ublock/_locales/bs/messages.json +8 -8
  68. package/extensions/ublock/_locales/ca/messages.json +1 -1
  69. package/extensions/ublock/_locales/da/messages.json +5 -5
  70. package/extensions/ublock/_locales/fa/messages.json +1 -1
  71. package/extensions/ublock/_locales/fi/messages.json +6 -6
  72. package/extensions/ublock/_locales/hr/messages.json +4 -4
  73. package/extensions/ublock/_locales/nb/messages.json +1 -1
  74. package/extensions/ublock/_locales/no/messages.json +1 -1
  75. package/extensions/ublock/_locales/ro/messages.json +2 -2
  76. package/extensions/ublock/_locales/ru/messages.json +1 -1
  77. package/extensions/ublock/_locales/sk/messages.json +1 -1
  78. package/extensions/ublock/_locales/sv/messages.json +2 -2
  79. package/extensions/ublock/_locales/te/messages.json +17 -17
  80. package/extensions/ublock/_locales/vi/messages.json +12 -12
  81. package/extensions/ublock/_locales/zh_TW/messages.json +13 -13
  82. package/extensions/ublock/assets/assets.json +3 -3
  83. package/extensions/ublock/assets/resources/scriptlets.js +218 -97
  84. package/extensions/ublock/assets/thirdparties/easylist/easylist.txt +3010 -2056
  85. package/extensions/ublock/assets/thirdparties/easylist/easyprivacy.txt +624 -433
  86. package/extensions/ublock/assets/thirdparties/pgl.yoyo.org/as/serverlist +93 -24
  87. package/extensions/ublock/assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat +7 -15
  88. package/extensions/ublock/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt +795 -777
  89. package/extensions/ublock/assets/ublock/badware.min.txt +138 -72
  90. package/extensions/ublock/assets/ublock/filters.min.txt +1929 -2735
  91. package/extensions/ublock/assets/ublock/privacy.min.txt +57 -26
  92. package/extensions/ublock/assets/ublock/quick-fixes.min.txt +125 -74
  93. package/extensions/ublock/assets/ublock/unbreak.min.txt +46 -53
  94. package/extensions/ublock/css/codemirror.css +8 -7
  95. package/extensions/ublock/css/dom-inspector.css +40 -0
  96. package/extensions/ublock/css/logger-ui-inspector.css +7 -1
  97. package/extensions/ublock/css/logger-ui.css +12 -5
  98. package/extensions/ublock/css/popup-fenix.css +1 -1
  99. package/extensions/ublock/devtools.html +1 -0
  100. package/extensions/ublock/js/1p-filters.js +4 -3
  101. package/extensions/ublock/js/3p-filters.js +25 -31
  102. package/extensions/ublock/js/about.js +1 -1
  103. package/extensions/ublock/js/advanced-settings.js +1 -1
  104. package/extensions/ublock/js/asset-viewer.js +1 -1
  105. package/extensions/ublock/js/assets.js +74 -44
  106. package/extensions/ublock/js/background.js +9 -3
  107. package/extensions/ublock/js/base64-custom.js +1 -1
  108. package/extensions/ublock/js/benchmarks.js +1 -1
  109. package/extensions/ublock/js/biditrie.js +1 -1
  110. package/extensions/ublock/js/broadcast.js +75 -0
  111. package/extensions/ublock/js/cachestorage.js +68 -45
  112. package/extensions/ublock/js/click2load.js +1 -1
  113. package/extensions/ublock/js/cloud-ui.js +1 -1
  114. package/extensions/ublock/js/code-viewer.js +1 -1
  115. package/extensions/ublock/js/codemirror/search-thread.js +1 -1
  116. package/extensions/ublock/js/codemirror/search.js +1 -1
  117. package/extensions/ublock/js/codemirror/ubo-dynamic-filtering.js +1 -1
  118. package/extensions/ublock/js/codemirror/ubo-static-filtering.js +98 -24
  119. package/extensions/ublock/js/commands.js +1 -1
  120. package/extensions/ublock/js/console.js +1 -1
  121. package/extensions/ublock/js/contentscript-extra.js +1 -1
  122. package/extensions/ublock/js/contentscript.js +1 -3
  123. package/extensions/ublock/js/contextmenu.js +1 -1
  124. package/extensions/ublock/js/cosmetic-filtering.js +4 -4
  125. package/extensions/ublock/js/dashboard-common.js +1 -1
  126. package/extensions/ublock/js/dashboard.js +1 -1
  127. package/extensions/ublock/js/devtools.js +23 -15
  128. package/extensions/ublock/js/diff-updater.js +3 -3
  129. package/extensions/ublock/js/document-blocked.js +1 -1
  130. package/extensions/ublock/js/dom-inspector.js +68 -0
  131. package/extensions/ublock/js/dom.js +1 -1
  132. package/extensions/ublock/js/dyna-rules.js +1 -1
  133. package/extensions/ublock/js/dynamic-net-filtering.js +1 -1
  134. package/extensions/ublock/js/epicker-ui.js +35 -59
  135. package/extensions/ublock/js/fa-icons.js +1 -1
  136. package/extensions/ublock/js/filtering-context.js +1 -1
  137. package/extensions/ublock/js/filtering-engines.js +1 -1
  138. package/extensions/ublock/js/hnswitches.js +1 -1
  139. package/extensions/ublock/js/hntrie.js +1 -1
  140. package/extensions/ublock/js/html-filtering.js +1 -1
  141. package/extensions/ublock/js/httpheader-filtering.js +1 -1
  142. package/extensions/ublock/js/i18n.js +1 -1
  143. package/extensions/ublock/js/is-webrtc-supported.js +1 -1
  144. package/extensions/ublock/js/logger-ui-inspector.js +203 -145
  145. package/extensions/ublock/js/logger-ui.js +21 -5
  146. package/extensions/ublock/js/logger.js +6 -2
  147. package/extensions/ublock/js/lz4.js +2 -2
  148. package/extensions/ublock/js/messaging.js +266 -166
  149. package/extensions/ublock/js/mrucache.js +58 -0
  150. package/extensions/ublock/js/pagestore.js +1 -1
  151. package/extensions/ublock/js/popup-fenix.js +2 -1
  152. package/extensions/ublock/js/redirect-engine.js +1 -1
  153. package/extensions/ublock/js/redirect-resources.js +1 -12
  154. package/extensions/ublock/js/reverselookup-worker.js +1 -1
  155. package/extensions/ublock/js/reverselookup.js +1 -1
  156. package/extensions/ublock/js/scriptlet-filtering-core.js +300 -0
  157. package/extensions/ublock/js/scriptlet-filtering.js +122 -350
  158. package/extensions/ublock/js/scriptlets/cosmetic-logger.js +36 -47
  159. package/extensions/ublock/js/scriptlets/cosmetic-off.js +1 -1
  160. package/extensions/ublock/js/scriptlets/cosmetic-on.js +1 -1
  161. package/extensions/ublock/js/scriptlets/cosmetic-report.js +1 -1
  162. package/extensions/ublock/js/scriptlets/dom-inspector.js +341 -323
  163. package/extensions/ublock/js/scriptlets/dom-survey-elements.js +1 -1
  164. package/extensions/ublock/js/scriptlets/dom-survey-scripts.js +1 -1
  165. package/extensions/ublock/js/scriptlets/epicker.js +80 -89
  166. package/extensions/ublock/js/scriptlets/load-3p-css.js +1 -1
  167. package/extensions/ublock/js/scriptlets/load-large-media-all.js +1 -1
  168. package/extensions/ublock/js/scriptlets/load-large-media-interactive.js +1 -1
  169. package/extensions/ublock/js/scriptlets/noscript-spoof.js +1 -1
  170. package/extensions/ublock/js/scriptlets/should-inject-contentscript.js +1 -1
  171. package/extensions/ublock/js/scriptlets/subscriber.js +1 -1
  172. package/extensions/ublock/js/scriptlets/updater.js +20 -3
  173. package/extensions/ublock/js/settings.js +1 -1
  174. package/extensions/ublock/js/start.js +19 -20
  175. package/extensions/ublock/js/static-dnr-filtering.js +1 -1
  176. package/extensions/ublock/js/static-ext-filtering-db.js +1 -1
  177. package/extensions/ublock/js/static-ext-filtering.js +1 -1
  178. package/extensions/ublock/js/static-filtering-io.js +1 -1
  179. package/extensions/ublock/js/static-filtering-parser.js +5 -3
  180. package/extensions/ublock/js/static-net-filtering.js +57 -37
  181. package/extensions/ublock/js/storage.js +49 -29
  182. package/extensions/ublock/js/support.js +4 -4
  183. package/extensions/ublock/js/tab.js +1 -1
  184. package/extensions/ublock/js/tasks.js +1 -1
  185. package/extensions/ublock/js/text-encode.js +1 -1
  186. package/extensions/ublock/js/text-utils.js +1 -1
  187. package/extensions/ublock/js/theme.js +1 -1
  188. package/extensions/ublock/js/traffic.js +2 -1
  189. package/extensions/ublock/js/ublock.js +15 -11
  190. package/extensions/ublock/js/uri-utils.js +1 -1
  191. package/extensions/ublock/js/url-net-filtering.js +1 -1
  192. package/extensions/ublock/js/utils.js +1 -73
  193. package/extensions/ublock/js/vapi-background-ext.js +1 -1
  194. package/extensions/ublock/js/vapi-background.js +92 -83
  195. package/extensions/ublock/js/vapi-client.js +4 -33
  196. package/extensions/ublock/js/vapi-common.js +16 -30
  197. package/extensions/ublock/js/vapi.js +1 -1
  198. package/extensions/ublock/js/wasm/biditrie.wat +1 -1
  199. package/extensions/ublock/js/wasm/hntrie.wat +1 -1
  200. package/extensions/ublock/js/webext.js +1 -1
  201. package/extensions/ublock/js/whitelist.js +1 -1
  202. package/extensions/ublock/logger-ui.html +2 -2
  203. package/extensions/ublock/manifest.json +1 -1
  204. package/extensions/ublock/support.html +0 -1
  205. package/extensions/ublock/web_accessible_resources/dom-inspector.html +25 -0
  206. package/extensions/ublock/web_accessible_resources/epicker-ui.html +0 -1
  207. package/extensions/ublock/web_accessible_resources/googletagservices_gpt.js +1 -0
  208. package/package.json +7 -19
  209. package/scripts/build-open-api.js +7 -4
  210. package/src/browserless.ts +42 -18
  211. package/src/browsers/index.ts +48 -20
  212. package/src/file-system.spec.ts +58 -0
  213. package/src/file-system.ts +36 -8
  214. package/src/http.ts +3 -3
  215. package/src/router.ts +2 -6
  216. package/src/routes/chromium/http/content-post.ts +13 -16
  217. package/src/routes/chromium/http/download-post.ts +16 -27
  218. package/src/routes/chromium/http/function-post.ts +16 -25
  219. package/src/routes/chromium/http/pdf-post.ts +19 -15
  220. package/src/routes/chromium/http/performance.ts +14 -26
  221. package/src/routes/chromium/http/scrape-post.ts +14 -16
  222. package/src/routes/chromium/http/screenshot-post.ts +18 -15
  223. package/src/routes/chromium/tests/websocket.spec.ts +28 -1
  224. package/src/routes/chromium/utils/function/handler.ts +2 -1
  225. package/src/routes/chromium/ws/browser.ts +10 -12
  226. package/src/routes/chromium/ws/cdp-chromium.ts +10 -12
  227. package/src/routes/chromium/ws/page.ts +10 -12
  228. package/src/routes/chromium/ws/playwright-chromium.ts +10 -12
  229. package/src/routes/firefox/ws/playwright-firefox.ts +10 -12
  230. package/src/routes/management/http/config-get.ts +14 -23
  231. package/src/routes/management/http/metrics-get.ts +15 -24
  232. package/src/routes/management/http/metrics-total-get.ts +15 -26
  233. package/src/routes/management/http/sessions-get.ts +15 -23
  234. package/src/routes/management/http/static-get.ts +14 -22
  235. package/src/routes/webkit/ws/playwright-webkit.ts +10 -12
  236. package/src/server.ts +0 -1
  237. package/src/types.ts +59 -45
  238. package/static/docs/browserless-logo-inline.svg +1 -0
  239. package/static/docs/index.html +27 -0
  240. package/static/docs/swagger.json +33 -33
  241. package/static/function/client.js +626 -78
  242. package/extensions/ublock/js/vapi-client-extra.js +0 -312
  243. package/extensions/ublock/web_accessible_resources/addthis_widget.js +0 -39
  244. package/extensions/ublock/web_accessible_resources/ligatus_angular-tag.js +0 -29
  245. package/extensions/ublock/web_accessible_resources/monkeybroker.js +0 -43
  246. package/extensions/ublock/web_accessible_resources/mxpnl_mixpanel.js +0 -51
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserless.io/browserless",
3
- "version": "2.0.0-beta-5",
3
+ "version": "2.0.0-beta-7",
4
4
  "license": "SSPL",
5
5
  "description": "The browserless platform",
6
6
  "author": "browserless.io",
@@ -29,7 +29,7 @@
29
29
  "install:dev": "npm run install:browsers && npm run install:cdp-json",
30
30
  "lint": "eslint . --ext .ts --fix",
31
31
  "prepack": "npm run build:dev",
32
- "prettier": "prettier '{src,functions,scripts,bin,external}/**/*.{js,ts,json}' --log-level error --write",
32
+ "prettier": "prettier '{src,functions,scripts,bin,external,bin}/**/*.{js,ts,json}' --log-level error --write",
33
33
  "test": "cross-env DEBUG=quiet mocha"
34
34
  },
35
35
  "files": [
@@ -71,15 +71,15 @@
71
71
  "@types/http-proxy": "^1.17.14",
72
72
  "@types/micromatch": "^4.0.6",
73
73
  "@types/mocha": "^10.0.6",
74
- "@types/node": "^20.10.5",
74
+ "@types/node": "^20.10.8",
75
75
  "@types/sinon": "^17.0.2",
76
- "@typescript-eslint/eslint-plugin": "^6.15.0",
77
- "@typescript-eslint/parser": "^6.15.0",
76
+ "@typescript-eslint/eslint-plugin": "^6.18.1",
77
+ "@typescript-eslint/parser": "^6.18.1",
78
78
  "assert": "^2.0.0",
79
- "chai": "^4.3.6",
79
+ "chai": "^5.0.0",
80
80
  "cross-env": "^7.0.3",
81
81
  "env-cmd": "^10.1.0",
82
- "esbuild": "^0.19.10",
82
+ "esbuild": "^0.19.11",
83
83
  "esbuild-plugin-polyfill-node": "^0.3.0",
84
84
  "eslint": "^8.56.0",
85
85
  "eslint-plugin-import": "^2.29.1",
@@ -158,18 +158,6 @@
158
158
  "timeout": 30000,
159
159
  "slow": 5000
160
160
  },
161
- "nodemonConfig": {
162
- "ignoreRoot": [
163
- ".git",
164
- ".no-git"
165
- ],
166
- "watch": [
167
- "src"
168
- ],
169
- "exec": "npx tsc && node ./build/index.js",
170
- "ext": "ts json",
171
- "signal": "SIGTERM"
172
- },
173
161
  "prettier": {
174
162
  "semi": true,
175
163
  "trailingComma": "all",
@@ -56,17 +56,19 @@ const buildOpenAPI = async (
56
56
  const changelog = marked.parse(
57
57
  (await fs.readFile('CHANGELOG.md').catch(() => '')).toString(),
58
58
  );
59
+
59
60
  const [httpRoutes, wsRoutes] = await getRouteFiles(new Config());
60
61
  const swaggerJSON = {
61
62
  customSiteTitle: 'Browserless Documentation',
62
63
  definitions: {},
63
64
  info: {
64
- description: readme + changelog,
65
+ // Concatenation necessary for Changelog to show up in sidebar
66
+ description: readme + `\n# Changelog\n` + changelog,
65
67
  title: 'Browserless',
66
68
  version: JSON.parse(packageJSON.toString()).version,
67
69
  'x-logo': {
68
70
  altText: 'browserless logo',
69
- url: './docs/browserless-logo.png',
71
+ url: './docs/browserless-logo-inline.svg',
70
72
  },
71
73
  },
72
74
  openapi: '3.0.0',
@@ -86,10 +88,11 @@ const buildOpenAPI = async (
86
88
  .sort()
87
89
  .map(async (routeModule) => {
88
90
  const routeImport = `${isWin ? 'file:///' : ''}${routeModule}`;
89
- const { default: route } = await import(routeImport);
90
- if (!route) {
91
+ const { default: Route } = await import(routeImport);
92
+ if (!Route) {
91
93
  throw new Error(`Invalid route file to import docs ${routeModule}`);
92
94
  }
95
+ const route = new Route();
93
96
  const { name } = parse(routeModule);
94
97
  const body = routeModule.replace('.js', '.body.json');
95
98
  const query = routeModule.replace('.js', '.query.json');
@@ -27,6 +27,10 @@ import { userInfo } from 'os';
27
27
 
28
28
  const routeSchemas = ['body', 'query'];
29
29
 
30
+ type Implements<T> = {
31
+ new (...args: unknown[]): T;
32
+ };
33
+
30
34
  export class Browserless {
31
35
  protected debug: debug.Debugger = createLogger('index');
32
36
  protected browserManager: BrowserManager;
@@ -181,16 +185,25 @@ export class Browserless {
181
185
  this.config.getIsWin() ? 'file:///' : ''
182
186
  }${httpRoute}`;
183
187
  const logger = createLogger(`http:${name}`);
184
- const { default: route }: { default: HTTPRoute | BrowserHTTPRoute } =
188
+ const {
189
+ default: Route,
190
+ }: { default: Implements<HTTPRoute> | Implements<BrowserHTTPRoute> } =
185
191
  await import(routeImport + `?cb=${Date.now()}`);
186
-
192
+ const route = new Route(
193
+ this.browserManager,
194
+ this.config,
195
+ this.fileSystem,
196
+ logger,
197
+ this.metrics,
198
+ this.monitoring,
199
+ );
187
200
  route.bodySchema = safeParse(bodySchema);
188
201
  route.querySchema = safeParse(querySchema);
189
- route.getConfig = () => this.config;
190
- route.getMetrics = () => this.metrics;
191
- route.getMonitoring = () => this.monitoring;
192
- route.getFileSystem = () => this.fileSystem;
193
- route.getDebug = () => logger;
202
+ route.config = () => this.config;
203
+ route.metrics = () => this.metrics;
204
+ route.monitoring = () => this.monitoring;
205
+ route.fileSystem = () => this.fileSystem;
206
+ route.debug = () => logger;
194
207
 
195
208
  httpRoutes.push(route);
196
209
  }
@@ -215,17 +228,27 @@ export class Browserless {
215
228
  }${wsRoute}`;
216
229
  const logger = createLogger(`ws:${name}`);
217
230
  const {
218
- default: route,
219
- }: { default: WebSocketRoute | BrowserWebsocketRoute } = await import(
220
- wsImport + `?cb=${Date.now()}`
221
- );
231
+ default: Route,
232
+ }: {
233
+ default:
234
+ | Implements<WebSocketRoute>
235
+ | Implements<BrowserWebsocketRoute>;
236
+ } = await import(wsImport + `?cb=${Date.now()}`);
222
237
 
238
+ const route = new Route(
239
+ this.browserManager,
240
+ this.config,
241
+ this.fileSystem,
242
+ logger,
243
+ this.metrics,
244
+ this.monitoring,
245
+ );
223
246
  route.querySchema = safeParse(querySchema);
224
- route.getConfig = () => this.config;
225
- route.getMetrics = () => this.metrics;
226
- route.getMonitoring = () => this.monitoring;
227
- route.getFileSystem = () => this.fileSystem;
228
- route.getDebug = () => logger;
247
+ route.config = () => this.config;
248
+ route.metrics = () => this.metrics;
249
+ route.monitoring = () => this.monitoring;
250
+ route.fileSystem = () => this.fileSystem;
251
+ route.debug = () => logger;
229
252
 
230
253
  wsRoutes.push(route);
231
254
  }
@@ -234,11 +257,12 @@ export class Browserless {
234
257
  // Validate that browsers are installed and route paths are unique
235
258
  [...httpRoutes, ...wsRoutes].forEach((route) => {
236
259
  if (
260
+ 'browser' in route &&
237
261
  route.browser &&
238
- !installedBrowsers.some((b) => b.name === route.browser.name)
262
+ !installedBrowsers.some((b) => b.name === route.browser?.name)
239
263
  ) {
240
264
  throw new Error(
241
- `Couldn't load route "${route.path}" due to missing browser of "${route.browser.name}"`,
265
+ `Couldn't load route "${route.path}" due to missing browser of "${route.browser?.name}"`,
242
266
  );
243
267
  }
244
268
  });
@@ -82,27 +82,52 @@ export class BrowserManager {
82
82
  return dataDirPath;
83
83
  };
84
84
 
85
- protected generateSessionJson = (
85
+ private generateSessionJson = async (
86
86
  browser: BrowserInstance,
87
87
  session: BrowserlessSession,
88
88
  ) => {
89
89
  const serverAddress = this.config.getExternalAddress();
90
90
 
91
- return {
92
- ...session,
93
- browser: browser.constructor.name,
94
- browserId: browser.wsEndpoint()?.split('/').pop(),
95
- initialConnectURL: new URL(session.initialConnectURL, serverAddress).href,
96
- killURL: session.id
97
- ? makeExternalURL(
98
- serverAddress,
99
- HTTPManagementRoutes.sessions,
100
- session.id,
101
- )
102
- : null,
103
- running: browser.isRunning(),
104
- timeAliveMs: Date.now() - session.startedOn,
105
- };
91
+ const sessions = [
92
+ {
93
+ ...session,
94
+ browser: browser.constructor.name,
95
+ browserId: browser.wsEndpoint()?.split('/').pop(),
96
+ initialConnectURL: new URL(session.initialConnectURL, serverAddress)
97
+ .href,
98
+ killURL: session.id
99
+ ? makeExternalURL(
100
+ serverAddress,
101
+ HTTPManagementRoutes.sessions,
102
+ session.id,
103
+ )
104
+ : null,
105
+ running: browser.isRunning(),
106
+ timeAliveMs: Date.now() - session.startedOn,
107
+ type: 'browser',
108
+ },
109
+ ];
110
+
111
+ const wsEndpoint = browser.wsEndpoint();
112
+ if (browser.constructor.name === 'CDPChromium' && wsEndpoint) {
113
+ const port = new URL(wsEndpoint).port;
114
+ const response = await fetch(`http://127.0.0.1:${port}/json/list`, {
115
+ headers: {
116
+ Host: '127.0.0.1',
117
+ },
118
+ });
119
+ if (response.ok) {
120
+ const body = await response.json();
121
+ for (const page of body) {
122
+ sessions.push({
123
+ ...sessions[0],
124
+ ...page,
125
+ browserWSEndpoint: wsEndpoint,
126
+ });
127
+ }
128
+ }
129
+ }
130
+ return sessions;
106
131
  };
107
132
 
108
133
  public close = async (
@@ -129,9 +154,12 @@ export class BrowserManager {
129
154
  public getAllSessions = async (): Promise<BrowserlessSessionJSON[]> => {
130
155
  const sessions = Array.from(this.browsers);
131
156
 
132
- return sessions.map(([browser, session]) =>
133
- this.generateSessionJson(browser, session),
134
- );
157
+ const formattedSessions: BrowserlessSessionJSON[] = [];
158
+ for (const [browser, session] of sessions) {
159
+ const formattedSession = await this.generateSessionJson(browser, session);
160
+ formattedSessions.push(...formattedSession);
161
+ }
162
+ return formattedSessions;
135
163
  };
136
164
 
137
165
  public complete = async (browser: BrowserInstance): Promise<void> => {
@@ -208,7 +236,7 @@ export class BrowserManager {
208
236
  const manualUserDataDir =
209
237
  launchOptions.args
210
238
  ?.find((arg) => arg.includes('--user-data-dir='))
211
- ?.split('=')[2] || (launchOptions as CDPLaunchOptions).userDataDir;
239
+ ?.split('=')[1] || (launchOptions as CDPLaunchOptions).userDataDir;
212
240
 
213
241
  // Always specify a user-data-dir since plugins can "inject" their own
214
242
  // unless it's playwright which takes care of its own data-dirs
@@ -0,0 +1,58 @@
1
+ import { Config, FileSystem, sleep } from '@browserless.io/browserless';
2
+ import { readFile, unlink } from 'fs/promises';
3
+ import { expect } from 'chai';
4
+
5
+ const filePath = '/tmp/_browserless_test_fs_';
6
+
7
+ describe.only('File-System', () => {
8
+ afterEach(async () => unlink(filePath));
9
+
10
+ it('saves and encodes files', async () => {
11
+ const mySecretContents = 'pony-foo';
12
+ const config = new Config();
13
+ config.setToken('browserless.io');
14
+ const f = new FileSystem(config);
15
+
16
+ await f.append(filePath, mySecretContents);
17
+
18
+ expect(await f.read(filePath)).to.eql([mySecretContents]);
19
+ const rawText = (await readFile(filePath)).toString();
20
+
21
+ expect(rawText.toString()).to.not.include(mySecretContents);
22
+ });
23
+
24
+ it('appends newlines to files', async () => {
25
+ const mySecretContents = 'pony-foo';
26
+ const moreSecretContents = 'pony-pony-foo-foo';
27
+ const config = new Config();
28
+ config.setToken('browserless.io');
29
+ const f = new FileSystem(config);
30
+
31
+ await f.append(filePath, mySecretContents);
32
+
33
+ expect(await f.read(filePath)).to.eql([mySecretContents]);
34
+
35
+ await f.append(filePath, moreSecretContents);
36
+
37
+ expect(await f.read(filePath)).to.eql([
38
+ mySecretContents,
39
+ moreSecretContents,
40
+ ]);
41
+ });
42
+
43
+ it('re-encodes files on token changes', async () => {
44
+ const config = new Config();
45
+ config.setToken('browserless.io');
46
+ const f = new FileSystem(config);
47
+ const mySecretContents = 'pony-foo';
48
+
49
+ await f.append(filePath, mySecretContents);
50
+ const oldText = (await readFile(filePath)).toString();
51
+ config.setToken('super-browserless-64');
52
+ await sleep(200);
53
+ const newText = (await readFile(filePath)).toString();
54
+
55
+ expect(oldText).to.not.equal(newText);
56
+ expect(await f.read(filePath)).to.eql([mySecretContents]);
57
+ });
58
+ });
@@ -1,10 +1,39 @@
1
- import { Config, decrypt, encrypt } from '@browserless.io/browserless';
1
+ import {
2
+ Config,
3
+ createLogger,
4
+ decrypt,
5
+ encrypt,
6
+ } from '@browserless.io/browserless';
2
7
  import { readFile, writeFile } from 'fs/promises';
3
8
 
4
9
  export class FileSystem {
5
10
  protected fsMap: Map<string, string[]> = new Map();
11
+ protected currentAESKey: Buffer;
12
+ protected log = createLogger('file-system');
6
13
 
7
- constructor(protected config: Config) {}
14
+ constructor(protected config: Config) {
15
+ this.currentAESKey = config.getAESKey();
16
+ this.config.on('token', this.handleTokenChange);
17
+ }
18
+
19
+ private handleTokenChange = async () => {
20
+ this.log(`Token has changed, updating file-system contents`);
21
+ const start = Date.now();
22
+ const newAESKey = this.config.getAESKey();
23
+ await Promise.all(
24
+ Array.from(this.fsMap).map(async ([filePath, contents]) => {
25
+ const newlyEncoded = encrypt(
26
+ contents.join('\n'),
27
+ Buffer.from(newAESKey),
28
+ );
29
+ return writeFile(filePath, newlyEncoded);
30
+ }),
31
+ ).catch((e) => {
32
+ this.log(`Error in setting new token: "${e}"`);
33
+ });
34
+ this.log(`Successfully updated file encodings in ${Date.now() - start}ms`);
35
+ this.currentAESKey = this.config.getAESKey();
36
+ };
8
37
 
9
38
  /**
10
39
  * Appends contents to a file-path for persistance. File contents are
@@ -20,9 +49,10 @@ export class FileSystem {
20
49
 
21
50
  contents.push(newContent);
22
51
  this.fsMap.set(path, contents);
23
-
24
- const key = this.config.getAESKey();
25
- const encoded = await encrypt(contents.join('\n'), Buffer.from(key));
52
+ const encoded = await encrypt(
53
+ contents.join('\n'),
54
+ Buffer.from(this.currentAESKey),
55
+ );
26
56
 
27
57
  return writeFile(path, encoded.toString());
28
58
  };
@@ -40,11 +70,9 @@ export class FileSystem {
40
70
  if (hasKey) {
41
71
  return this.fsMap.get(path) as string[];
42
72
  }
43
-
44
- const key = this.config.getAESKey();
45
73
  const contents = (await readFile(path).catch(() => '')).toString();
46
74
  const splitContents = contents.length
47
- ? (await decrypt(contents, Buffer.from(key))).split('\n')
75
+ ? (await decrypt(contents, Buffer.from(this.currentAESKey))).split('\n')
48
76
  : [];
49
77
 
50
78
  this.fsMap.set(path, splitContents);
package/src/http.ts CHANGED
@@ -107,9 +107,9 @@ export enum HTTPManagementRoutes {
107
107
  }
108
108
 
109
109
  export enum APITags {
110
- 'browserAPI' = 'Browser APIs',
111
- 'browserWS' = 'Browser WebSockets',
112
- 'management' = 'Management APIs',
110
+ 'browserAPI' = 'Browser REST APIs',
111
+ 'browserWS' = 'Browser WebSocket APIs',
112
+ 'management' = 'Management REST APIs',
113
113
  }
114
114
 
115
115
  export interface Request extends http.IncomingMessage {
package/src/router.ts CHANGED
@@ -67,7 +67,7 @@ export class Router {
67
67
  return Promise.resolve();
68
68
  }
69
69
 
70
- if (route.browser) {
70
+ if ('browser' in route && route.browser) {
71
71
  const browser = await this.browserManager.getBrowserForRequest(
72
72
  req,
73
73
  route,
@@ -111,7 +111,7 @@ export class Router {
111
111
  return Promise.resolve();
112
112
  }
113
113
 
114
- if (route.browser) {
114
+ if ('browser' in route && route.browser) {
115
115
  const browser = await this.browserManager.getBrowserForRequest(
116
116
  req,
117
117
  route,
@@ -146,8 +146,6 @@ export class Router {
146
146
  `Registering HTTP ${route.method.toUpperCase()} ${route.path}`,
147
147
  );
148
148
 
149
- route.getBrowserManager = () => this.browserManager;
150
-
151
149
  const bound = route.handler.bind(route);
152
150
  const wrapped = this.wrapHTTPHandler(route, bound);
153
151
 
@@ -170,8 +168,6 @@ export class Router {
170
168
  ): WebSocketRoute | BrowserWebsocketRoute {
171
169
  this.verbose(`Registering WebSocket "${route.path}"`);
172
170
 
173
- route.getBrowserManager = () => this.browserManager;
174
-
175
171
  const bound = route.handler.bind(route);
176
172
  const wrapped = this.wrapWebSocketHandler(route, bound);
177
173
 
@@ -61,15 +61,17 @@ export type QuerySchema = SystemQueryParameters & {
61
61
  launch?: CDPLaunchOptions | string;
62
62
  };
63
63
 
64
- const route: BrowserHTTPRoute = {
65
- accepts: [contentTypes.json],
66
- auth: true,
67
- browser: CDPChromium,
68
- concurrency: true,
69
- contentTypes: [contentTypes.html],
70
- description: `A JSON-based API. Given a "url" or "html" field, runs and returns HTML content after the page has loaded and JavaScript has parsed.`,
71
-
72
- handler: async (
64
+ export default class ContentPostRoute extends BrowserHTTPRoute {
65
+ accepts = [contentTypes.json];
66
+ auth = true;
67
+ browser = CDPChromium;
68
+ concurrency = true;
69
+ contentTypes = [contentTypes.html];
70
+ description = `A JSON-based API. Given a "url" or "html" field, runs and returns HTML content after the page has loaded and JavaScript has parsed.`;
71
+ method = Methods.post;
72
+ path = HTTPRoutes.content;
73
+ tags = [APITags.browserAPI];
74
+ handler = async (
73
75
  req: Request,
74
76
  res: ServerResponse,
75
77
  browser: BrowserInstance,
@@ -229,10 +231,5 @@ const route: BrowserHTTPRoute = {
229
231
  page.close().catch(noop);
230
232
 
231
233
  return writeResponse(res, 200, markup, contentTypes.html);
232
- },
233
- method: Methods.post,
234
- path: HTTPRoutes.content,
235
- tags: [APITags.browserAPI],
236
- };
237
-
238
- export default route;
234
+ };
235
+ }
@@ -8,7 +8,6 @@ import {
8
8
  Methods,
9
9
  NotFound,
10
10
  Request,
11
- ServerError,
12
11
  SystemQueryParameters,
13
12
  contentTypes,
14
13
  dedent,
@@ -41,35 +40,30 @@ export interface QuerySchema extends SystemQueryParameters {
41
40
  */
42
41
  export type ResponseSchema = unknown;
43
42
 
44
- const route: BrowserHTTPRoute = {
45
- accepts: [contentTypes.json, contentTypes.javascript],
46
- auth: true,
47
- browser: CDPChromium,
48
- concurrency: true,
49
- contentTypes: [contentTypes.any],
50
- description: dedent(`
43
+ export default class DownloadPost extends BrowserHTTPRoute {
44
+ accepts = [contentTypes.json, contentTypes.javascript];
45
+ auth = true;
46
+ browser = CDPChromium;
47
+ concurrency = true;
48
+ contentTypes = [contentTypes.any];
49
+ description = dedent(`
51
50
  A JSON or JavaScript content-type API for returning files Chrome has downloaded during
52
51
  the execution of puppeteer code, which is ran inside context of the browser.
53
52
  Browserless sets up a blank page, a fresh download directory, injects your puppeteer code, and then executes it.
54
53
  You can load external libraries via the "import" syntax, and import ESM-style modules
55
54
  that are written for execution inside of the browser. Once your script is finished, any
56
- downloaded files from Chromium are returned back with the appropriate content-type header.`),
57
- handler: async (
55
+ downloaded files from Chromium are returned back with the appropriate content-type header.`);
56
+ method = Methods.post;
57
+ path = HTTPRoutes.download;
58
+ tags = [APITags.browserAPI];
59
+ handler = async (
58
60
  req: Request,
59
61
  res: ServerResponse,
60
62
  browser: BrowserInstance,
61
63
  ): Promise<void> =>
62
64
  new Promise(async (resolve, reject) => {
63
- const { getConfig: getConfig, getDebug: getDebug } = route;
64
-
65
- if (!getConfig || !getDebug) {
66
- return reject(
67
- new ServerError(`Couldn't load configuration for request`),
68
- );
69
- }
70
-
71
- const debug = getDebug();
72
- const config = getConfig();
65
+ const debug = this.debug();
66
+ const config = this.config();
73
67
  const downloadPath = path.join(
74
68
  await config.getDownloadsDir(),
75
69
  `.browserless.download.${id()}`,
@@ -153,10 +147,5 @@ const route: BrowserHTTPRoute = {
153
147
  return resolve();
154
148
  })
155
149
  .pipe(res);
156
- }),
157
- method: Methods.post,
158
- path: HTTPRoutes.download,
159
- tags: [APITags.browserAPI],
160
- };
161
-
162
- export default route;
150
+ });
151
+ }
@@ -8,7 +8,6 @@ import {
8
8
  HTTPRoutes,
9
9
  Methods,
10
10
  Request,
11
- ServerError,
12
11
  SystemQueryParameters,
13
12
  contentTypes,
14
13
  dedent,
@@ -38,31 +37,28 @@ export interface QuerySchema extends SystemQueryParameters {
38
37
  */
39
38
  export type ResponseSchema = unknown;
40
39
 
41
- const route: BrowserHTTPRoute = {
42
- accepts: [contentTypes.json, contentTypes.javascript],
43
- auth: true,
44
- browser: CDPChromium,
45
- concurrency: true,
46
- contentTypes: [contentTypes.any],
47
- description: dedent(`
40
+ export default class FunctionPost extends BrowserHTTPRoute {
41
+ accepts = [contentTypes.json, contentTypes.javascript];
42
+ auth = true;
43
+ browser = CDPChromium;
44
+ concurrency = true;
45
+ contentTypes = [contentTypes.any];
46
+ description = dedent(`
48
47
  A JSON or JavaScript content-type API for running puppeteer code in the browser's context.
49
48
  Browserless sets up a blank page, injects your puppeteer code, and runs it.
50
49
  You can optionally load external libraries via the "import" module that are meant for browser usage.
51
50
  Values returned from the function are checked and an appropriate content-type and response is sent back
52
- to your HTTP call.`),
53
- handler: async (
51
+ to your HTTP call.`);
52
+ method = Methods.post;
53
+ path = HTTPRoutes.function;
54
+ tags = [APITags.browserAPI];
55
+ handler = async (
54
56
  req: Request,
55
57
  res: ServerResponse,
56
58
  browser: BrowserInstance,
57
59
  ): Promise<void> => {
58
- const { getConfig: getConfig, getDebug: getDebug } = route;
59
-
60
- if (!getConfig || !getDebug) {
61
- throw new ServerError(`Couldn't load configuration for request`);
62
- }
63
-
64
- const debug = getDebug();
65
- const config = getConfig();
60
+ const debug = this.debug();
61
+ const config = this.config();
66
62
  const handler = functionHandler(config, debug);
67
63
  const { contentType, payload, page } = await handler(req, browser);
68
64
 
@@ -89,10 +85,5 @@ const route: BrowserHTTPRoute = {
89
85
  }
90
86
 
91
87
  return;
92
- },
93
- method: Methods.post,
94
- path: HTTPRoutes.function,
95
- tags: [APITags.browserAPI],
96
- };
97
-
98
- export default route;
88
+ };
89
+ }