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

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 (45) hide show
  1. package/build/browsers/index.js +9 -3
  2. package/build/router.js +13 -6
  3. package/build/routes/chromium/http/content-post.body.json +15 -19
  4. package/build/routes/chromium/http/content-post.d.ts +1 -1
  5. package/build/routes/chromium/http/content-post.js +2 -4
  6. package/build/routes/chromium/http/content-post.query.json +1 -1
  7. package/build/routes/chromium/http/download-post.query.json +1 -1
  8. package/build/routes/chromium/http/function-post.query.json +1 -1
  9. package/build/routes/chromium/http/pdf-post.body.json +15 -19
  10. package/build/routes/chromium/http/pdf-post.d.ts +1 -1
  11. package/build/routes/chromium/http/pdf-post.js +10 -6
  12. package/build/routes/chromium/http/pdf-post.query.json +1 -1
  13. package/build/routes/chromium/http/performance.query.json +1 -1
  14. package/build/routes/chromium/http/scrape-post.body.json +15 -19
  15. package/build/routes/chromium/http/scrape-post.d.ts +3 -3
  16. package/build/routes/chromium/http/scrape-post.js +2 -4
  17. package/build/routes/chromium/http/scrape-post.query.json +1 -1
  18. package/build/routes/chromium/http/scrape-post.response.json +22 -38
  19. package/build/routes/chromium/http/screenshot-post.body.json +15 -19
  20. package/build/routes/chromium/http/screenshot-post.d.ts +1 -1
  21. package/build/routes/chromium/http/screenshot-post.js +2 -4
  22. package/build/routes/chromium/http/screenshot-post.query.json +1 -1
  23. package/build/routes/chromium/tests/content.spec.js +27 -1
  24. package/build/routes/chromium/tests/websocket.spec.js +53 -4
  25. package/build/routes/chromium/ws/browser.js +1 -1
  26. package/build/routes/chromium/ws/browser.query.json +1 -1
  27. package/build/routes/chromium/ws/cdp-chromium.query.json +1 -1
  28. package/build/routes/chromium/ws/page.query.json +1 -1
  29. package/build/routes/management/http/sessions-get.response.json +5 -1
  30. package/build/shim.js +1 -1
  31. package/build/types.d.ts +2 -1
  32. package/package.json +3 -3
  33. package/src/browsers/index.ts +11 -4
  34. package/src/router.ts +13 -7
  35. package/src/routes/chromium/http/content-post.ts +3 -4
  36. package/src/routes/chromium/http/pdf-post.ts +13 -6
  37. package/src/routes/chromium/http/scrape-post.ts +5 -6
  38. package/src/routes/chromium/http/screenshot-post.ts +3 -4
  39. package/src/routes/chromium/tests/content.spec.ts +28 -1
  40. package/src/routes/chromium/tests/websocket.spec.ts +70 -4
  41. package/src/routes/chromium/ws/browser.ts +1 -1
  42. package/src/shim.ts +1 -1
  43. package/src/types.ts +2 -1
  44. package/static/docs/swagger.json +72 -100
  45. package/static/function/client.js +192 -488
@@ -21,12 +21,14 @@ import {
21
21
  rejectRequestPattern,
22
22
  rejectResourceTypes,
23
23
  requestInterceptors,
24
+ sleep,
24
25
  waitForEvent as waitForEvt,
25
26
  waitForFunction as waitForFn,
26
27
  writeResponse,
27
28
  } from '@browserless.io/browserless';
28
29
  import { Page } from 'puppeteer-core';
29
30
  import { ServerResponse } from 'http';
31
+ import { Stream } from 'stream';
30
32
 
31
33
  export interface BodySchema {
32
34
  addScriptTag?: Array<Parameters<Page['addScriptTag']>[0]>;
@@ -50,7 +52,7 @@ export interface BodySchema {
50
52
  waitForEvent?: WaitForEventOptions;
51
53
  waitForFunction?: WaitForFunctionOptions;
52
54
  waitForSelector?: WaitForSelectorOptions;
53
- waitForTimeout?: Parameters<Page['waitForTimeout']>[0];
55
+ waitForTimeout?: number;
54
56
  }
55
57
 
56
58
  export interface QuerySchema extends SystemQueryParameters {
@@ -111,9 +113,10 @@ export default class PDFPost extends BrowserHTTPRoute {
111
113
  setJavaScriptEnabled,
112
114
  userAgent,
113
115
  viewport,
116
+ waitForEvent,
114
117
  waitForFunction,
115
118
  waitForSelector,
116
- waitForEvent,
119
+ waitForTimeout,
117
120
  bestAttempt = false,
118
121
  } = req.body as BodySchema;
119
122
 
@@ -196,6 +199,10 @@ export default class PDFPost extends BrowserHTTPRoute {
196
199
  bestAttemptCatch(bestAttempt),
197
200
  );
198
201
 
202
+ if (waitForTimeout) {
203
+ await sleep(waitForTimeout).catch(bestAttemptCatch(bestAttempt));
204
+ }
205
+
199
206
  if (waitForFunction) {
200
207
  await waitForFn(page, waitForFunction).catch(
201
208
  bestAttemptCatch(bestAttempt),
@@ -227,11 +234,11 @@ export default class PDFPost extends BrowserHTTPRoute {
227
234
  }
228
235
  }
229
236
 
230
- const pdfStream = await page.createPDFStream(options);
237
+ const pdfBuffer = await page.pdf(options);
238
+ const readStream = new Stream.PassThrough();
239
+ readStream.end(pdfBuffer);
231
240
 
232
- await new Promise((resolve, reject) => {
233
- return pdfStream.pipe(res).once('finish', resolve).once('error', reject);
234
- });
241
+ await new Promise((r) => readStream.pipe(res).once('close', r));
235
242
 
236
243
  page.close().catch(noop);
237
244
  };
@@ -28,10 +28,11 @@ import {
28
28
  rejectRequestPattern,
29
29
  rejectResourceTypes,
30
30
  requestInterceptors,
31
+ sleep,
31
32
  waitForEvent as waitForEvt,
32
33
  waitForFunction as waitForFn,
33
34
  } from '@browserless.io/browserless';
34
- import { Page, Protocol } from 'puppeteer-core';
35
+ import { Cookie, Page } from 'puppeteer-core';
35
36
  import { ServerResponse } from 'http';
36
37
 
37
38
  export interface BodySchema {
@@ -56,7 +57,7 @@ export interface BodySchema {
56
57
  waitForEvent?: WaitForEventOptions;
57
58
  waitForFunction?: WaitForFunctionOptions;
58
59
  waitForSelector?: WaitForSelectorOptions;
59
- waitForTimeout?: Parameters<Page['waitForTimeout']>[0];
60
+ waitForTimeout?: number;
60
61
  }
61
62
 
62
63
  export type QuerySchema = SystemQueryParameters & {
@@ -135,7 +136,7 @@ export interface ResponseSchema {
135
136
  /**
136
137
  * List of cookies for the site or null
137
138
  */
138
- cookies: Protocol.Network.Cookie[] | null;
139
+ cookies: Cookie[] | null;
139
140
 
140
141
  /**
141
142
  * The HTML string of the website or null
@@ -361,9 +362,7 @@ export default class ScrapePost extends BrowserHTTPRoute {
361
362
  }
362
363
 
363
364
  if (waitForTimeout) {
364
- await page
365
- .waitForTimeout(waitForTimeout)
366
- .catch(bestAttemptCatch(bestAttempt));
365
+ await sleep(waitForTimeout).catch(bestAttemptCatch(bestAttempt));
367
366
  }
368
367
 
369
368
  if (waitForFunction) {
@@ -22,6 +22,7 @@ import {
22
22
  rejectResourceTypes,
23
23
  requestInterceptors,
24
24
  scrollThroughPage,
25
+ sleep,
25
26
  waitForEvent as waitForEvt,
26
27
  waitForFunction as waitForFn,
27
28
  } from '@browserless.io/browserless';
@@ -62,7 +63,7 @@ export interface BodySchema {
62
63
  waitForEvent?: WaitForEventOptions;
63
64
  waitForFunction?: WaitForFunctionOptions;
64
65
  waitForSelector?: WaitForSelectorOptions;
65
- waitForTimeout?: Parameters<Page['waitForTimeout']>[0];
66
+ waitForTimeout?: number;
66
67
  }
67
68
 
68
69
  export default class ScreenshotPost extends BrowserHTTPRoute {
@@ -205,9 +206,7 @@ export default class ScreenshotPost extends BrowserHTTPRoute {
205
206
  );
206
207
 
207
208
  if (waitForTimeout) {
208
- await page
209
- .waitForTimeout(waitForTimeout)
210
- .catch(bestAttemptCatch(bestAttempt));
209
+ await sleep(waitForTimeout).catch(bestAttemptCatch(bestAttempt));
211
210
  }
212
211
 
213
212
  if (waitForFunction) {
@@ -1,4 +1,4 @@
1
- import { Browserless, Config, Metrics } from '@browserless.io/browserless';
1
+ import { Browserless, Config, Metrics, sleep } from '@browserless.io/browserless';
2
2
  import { expect } from 'chai';
3
3
 
4
4
  describe('/content API', function () {
@@ -43,6 +43,33 @@ describe('/content API', function () {
43
43
  });
44
44
  });
45
45
 
46
+ it('cancels request when they are closed early', async () => {
47
+ const config = new Config();
48
+ const metrics = new Metrics();
49
+ await start({ config, metrics });
50
+ const body = {
51
+ url: 'https://cnn.com',
52
+ };
53
+ const controller = new AbortController();
54
+ const signal = controller.signal;
55
+ const promise = fetch('http://localhost:3000/content', {
56
+ body: JSON.stringify(body),
57
+ headers: {
58
+ 'content-type': 'application/json',
59
+ },
60
+ method: 'POST',
61
+ signal,
62
+ }).catch(async (error) => {
63
+ await sleep(100);
64
+ expect(error).to.have.property('name', 'AbortError');
65
+ expect(metrics.get().error).to.equal(1);
66
+ expect(metrics.get().successful).to.equal(0);
67
+ });
68
+ await sleep(1000);
69
+ controller.abort();
70
+ return promise;
71
+ });
72
+
46
73
  it('404s GET requests', async () => {
47
74
  const config = new Config();
48
75
  config.setToken('browserless');
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  Browserless,
3
+ BrowserlessSessionJSON,
3
4
  Config,
4
5
  Metrics,
5
6
  exists,
7
+ fetchJson,
6
8
  sleep,
7
9
  } from '@browserless.io/browserless';
8
10
  import { chromium } from 'playwright-core';
@@ -85,6 +87,70 @@ describe('WebSocket API', function () {
85
87
  await Promise.all([browser.disconnect(), browserTwo.disconnect()]);
86
88
  });
87
89
 
90
+ it('does not close browsers when multiple clients are connected', async () => {
91
+ const config = new Config();
92
+ config.setToken('browserless');
93
+ const metrics = new Metrics();
94
+ await start({ config, metrics });
95
+
96
+ // Single session
97
+ const browser = await puppeteer.connect({
98
+ browserWSEndpoint: `ws://localhost:3000?token=browserless`,
99
+ });
100
+ const [session] = (await fetchJson(
101
+ 'http://localhost:3000/sessions?token=browserless',
102
+ )) as BrowserlessSessionJSON[];
103
+ expect(session.numbConnected).to.equal(1);
104
+
105
+ // Two sessions
106
+ const browserTwo = await puppeteer.connect({
107
+ browserWSEndpoint: `ws://localhost:3000/devtools/browser/${session.browserId}?token=browserless`,
108
+ });
109
+ const [twoSessions] = (await fetchJson(
110
+ 'http://localhost:3000/sessions?token=browserless',
111
+ )) as BrowserlessSessionJSON[];
112
+ expect(twoSessions.numbConnected).to.equal(2);
113
+
114
+ // Back to a single session
115
+ await browser.disconnect();
116
+ await sleep(50);
117
+ const [oneSession] = (await fetchJson(
118
+ 'http://localhost:3000/sessions?token=browserless',
119
+ )) as BrowserlessSessionJSON[];
120
+ expect(oneSession.numbConnected).to.equal(1);
121
+
122
+ // No sessions connected
123
+ await browserTwo.disconnect();
124
+ await sleep(50);
125
+ const sessionsFinal = (await fetchJson(
126
+ 'http://localhost:3000/sessions?token=browserless',
127
+ )) as BrowserlessSessionJSON[];
128
+ expect(sessionsFinal).to.have.length(0);
129
+ });
130
+
131
+ it('disconnects all clients when the timeout is reached', async () => {
132
+ const config = new Config();
133
+ config.setToken('browserless');
134
+ config.setTimeout(1000);
135
+ config.setConcurrent(2);
136
+ const metrics = new Metrics();
137
+ await start({ config, metrics });
138
+ const browser = await puppeteer.connect({
139
+ browserWSEndpoint: `ws://localhost:3000?token=browserless`,
140
+ });
141
+ const [session] = (await fetchJson(
142
+ 'http://localhost:3000/sessions?token=browserless',
143
+ )) as BrowserlessSessionJSON[];
144
+ const browserTwo = await puppeteer.connect({
145
+ browserWSEndpoint: `ws://localhost:3000/devtools/browser/${session.browserId}?token=browserless`,
146
+ });
147
+ await sleep(3000);
148
+ expect(metrics.get().successful).to.equal(0);
149
+ expect(metrics.get().timedout).to.equal(2);
150
+ expect(browser.connected).to.be.false;
151
+ expect(browserTwo.connected).to.be.false;
152
+ });
153
+
88
154
  it('rejects websocket requests', async () => {
89
155
  const config = new Config();
90
156
  config.setToken('browserless');
@@ -261,13 +327,13 @@ describe('WebSocket API', function () {
261
327
  await start({ config, metrics });
262
328
 
263
329
  const browser = await puppeteer.connect({
264
- browserWSEndpoint: `ws://localhost:3000?timeout=500&token=browserless`,
330
+ browserWSEndpoint: `ws://localhost:3000?timeout=1000&token=browserless`,
265
331
  });
266
-
267
- await sleep(750);
268
- browser.disconnect();
332
+ expect(browser.connected).to.be.true;
333
+ await sleep(1200);
269
334
  expect(metrics.get().timedout).to.equal(1);
270
335
  expect(metrics.get().successful).to.equal(0);
336
+ expect(browser.connected).to.be.false;
271
337
  });
272
338
 
273
339
  it('allows the file-chooser', async () =>
@@ -16,7 +16,7 @@ export interface QuerySchema extends SystemQueryParameters {
16
16
  export default class CDPExistingBrowser extends BrowserWebsocketRoute {
17
17
  auth = true;
18
18
  browser = CDPChromium;
19
- concurrency = false;
19
+ concurrency = true;
20
20
  description = `Connect to an already-running Chromium with a library like puppeteer, or others, that work over chrome-devtools-protocol.`;
21
21
  path = WebsocketRoutes.browser;
22
22
  tags = [APITags.browserWS];
package/src/shim.ts CHANGED
@@ -36,7 +36,7 @@ export const shimLegacyRequests = (url: URL): URL => {
36
36
  typeof headless !== 'undefined' &&
37
37
  launchParams.headless === undefined
38
38
  ) {
39
- launchParams.headless = headless === 'new' ? 'new' : headless !== 'false';
39
+ launchParams.headless = headless === 'shell' ? 'shell' : headless !== 'false';
40
40
  }
41
41
 
42
42
  if (typeof slowMo !== 'undefined' && launchParams.slowMo === undefined) {
package/src/types.ts CHANGED
@@ -323,7 +323,7 @@ export interface CDPLaunchOptions extends BrowserlessLaunch {
323
323
  };
324
324
  devtools?: boolean;
325
325
  dumpio?: boolean;
326
- headless?: boolean | 'new';
326
+ headless?: boolean | 'shell';
327
327
  ignoreDefaultArgs?: boolean | string[];
328
328
  ignoreHTTPSErrors?: boolean;
329
329
  slowMo?: number;
@@ -365,6 +365,7 @@ export interface BrowserlessSession {
365
365
 
366
366
  export interface BrowserlessSessionJSON {
367
367
  browser: string;
368
+ browserId: string;
368
369
  id: string | null;
369
370
  initialConnectURL: string;
370
371
  killURL: string | null;