@browserless.io/browserless 2.4.0 → 2.5.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 +7 -0
  2. package/bin/browserless.js +17 -127
  3. package/bin/scaffold/README.md +95 -14
  4. package/build/browserless.d.ts +5 -3
  5. package/build/browserless.js +10 -6
  6. package/build/browsers/index.d.ts +5 -3
  7. package/build/browsers/index.js +6 -4
  8. package/build/data/selectors.json +1 -1
  9. package/build/exports.d.ts +1 -0
  10. package/build/exports.js +1 -0
  11. package/build/file-system.d.ts +2 -3
  12. package/build/file-system.js +10 -21
  13. package/build/file-system.spec.js +35 -18
  14. package/build/hooks.d.ts +18 -4
  15. package/build/hooks.js +33 -4
  16. package/build/limiter.d.ts +3 -2
  17. package/build/limiter.js +5 -3
  18. package/build/limiter.spec.js +45 -26
  19. package/build/routes/chrome/http/content.post.body.json +8 -8
  20. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  21. package/build/routes/chrome/http/scrape.post.body.json +8 -8
  22. package/build/routes/chrome/http/screenshot.post.body.json +8 -8
  23. package/build/routes/chromium/http/content.post.body.json +8 -8
  24. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  25. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  26. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  27. package/build/routes/management/http/metrics-total.get.js +1 -1
  28. package/build/routes/management/http/metrics.get.js +2 -2
  29. package/build/sdk-utils.d.ts +13 -0
  30. package/build/sdk-utils.js +94 -0
  31. package/build/server.d.ts +3 -2
  32. package/build/server.js +6 -4
  33. package/build/types.d.ts +2 -2
  34. package/build/utils.js +6 -10
  35. package/external/after.js +1 -1
  36. package/external/before.js +1 -1
  37. package/external/browser.js +1 -1
  38. package/external/page.js +1 -1
  39. package/package.json +7 -7
  40. package/src/browserless.ts +21 -3
  41. package/src/browsers/index.ts +9 -6
  42. package/src/exports.ts +1 -0
  43. package/src/file-system.spec.ts +43 -18
  44. package/src/file-system.ts +16 -30
  45. package/src/hooks.ts +46 -4
  46. package/src/limiter.spec.ts +82 -112
  47. package/src/limiter.ts +3 -3
  48. package/src/routes/management/http/metrics-total.get.ts +3 -3
  49. package/src/routes/management/http/metrics.get.ts +2 -2
  50. package/src/sdk-utils.ts +136 -0
  51. package/src/server.ts +4 -3
  52. package/src/shared/content.http.ts +0 -1
  53. package/src/types.ts +2 -2
  54. package/src/utils.ts +10 -11
  55. package/static/docs/swagger.json +11 -11
  56. package/static/docs/swagger.min.json +10 -10
  57. package/static/function/client.js +76 -63
@@ -11,6 +11,7 @@ import {
11
11
  FirefoxPlaywright,
12
12
  HTTPRoute,
13
13
  HTTPServer,
14
+ Hooks,
14
15
  IBrowserlessStats,
15
16
  Limiter,
16
17
  Metrics,
@@ -48,6 +49,7 @@ export class Browserless extends EventEmitter {
48
49
  protected browserManager: BrowserManager;
49
50
  protected config: Config;
50
51
  protected fileSystem: FileSystem;
52
+ protected hooks: Hooks;
51
53
  protected limiter: Limiter;
52
54
  protected metrics: Metrics;
53
55
  protected monitoring: Monitoring;
@@ -67,6 +69,7 @@ export class Browserless extends EventEmitter {
67
69
  browserManager,
68
70
  config,
69
71
  fileSystem,
72
+ hooks,
70
73
  limiter,
71
74
  metrics,
72
75
  monitoring,
@@ -77,6 +80,7 @@ export class Browserless extends EventEmitter {
77
80
  browserManager?: Browserless['browserManager'];
78
81
  config?: Browserless['config'];
79
82
  fileSystem?: Browserless['fileSystem'];
83
+ hooks?: Browserless['hooks'];
80
84
  limiter?: Browserless['limiter'];
81
85
  metrics?: Browserless['metrics'];
82
86
  monitoring?: Browserless['monitoring'];
@@ -88,13 +92,21 @@ export class Browserless extends EventEmitter {
88
92
  this.config = config || new Config();
89
93
  this.metrics = metrics || new Metrics();
90
94
  this.token = token || new Token(this.config);
95
+ this.hooks = hooks || new Hooks();
91
96
  this.webhooks = webhooks || new WebHooks(this.config);
92
- this.browserManager = browserManager || new BrowserManager(this.config);
97
+ this.browserManager =
98
+ browserManager || new BrowserManager(this.config, this.hooks);
93
99
  this.monitoring = monitoring || new Monitoring(this.config);
94
100
  this.fileSystem = fileSystem || new FileSystem(this.config);
95
101
  this.limiter =
96
102
  limiter ||
97
- new Limiter(this.config, this.metrics, this.monitoring, this.webhooks);
103
+ new Limiter(
104
+ this.config,
105
+ this.metrics,
106
+ this.monitoring,
107
+ this.webhooks,
108
+ this.hooks,
109
+ );
98
110
  this.router =
99
111
  router || new Router(this.config, this.browserManager, this.limiter);
100
112
  }
@@ -129,7 +141,11 @@ export class Browserless extends EventEmitter {
129
141
 
130
142
  if (metricsPath) {
131
143
  this.debug(`Saving metrics to "${metricsPath}"`);
132
- this.fileSystem.append(metricsPath, JSON.stringify(aggregatedStats));
144
+ this.fileSystem.append(
145
+ metricsPath,
146
+ JSON.stringify(aggregatedStats),
147
+ false,
148
+ );
133
149
  }
134
150
  };
135
151
 
@@ -190,6 +206,7 @@ export class Browserless extends EventEmitter {
190
206
  this.router.shutdown(),
191
207
  this.token.shutdown(),
192
208
  this.webhooks.shutdown(),
209
+ this.hooks.shutdown(),
193
210
  ]);
194
211
  }
195
212
 
@@ -350,6 +367,7 @@ export class Browserless extends EventEmitter {
350
367
  this.metrics,
351
368
  this.token,
352
369
  this.router,
370
+ this.hooks,
353
371
  );
354
372
 
355
373
  await this.server.start();
@@ -16,21 +16,21 @@ import {
16
16
  Config,
17
17
  FirefoxPlaywright,
18
18
  HTTPManagementRoutes,
19
+ Hooks,
19
20
  NotFound,
20
21
  Request,
21
22
  ServerError,
22
23
  WebkitPlaywright,
23
24
  availableBrowsers,
24
- browserHook,
25
25
  convertIfBase64,
26
26
  createLogger,
27
27
  exists,
28
28
  generateDataDir,
29
29
  makeExternalURL,
30
30
  noop,
31
- pageHook,
32
31
  parseBooleanParam,
33
32
  } from '@browserless.io/browserless';
33
+ import { Page } from 'puppeteer-core';
34
34
  import { deleteAsync } from 'del';
35
35
  import path from 'path';
36
36
 
@@ -47,7 +47,10 @@ export class BrowserManager {
47
47
  WebkitPlaywright.name,
48
48
  ];
49
49
 
50
- constructor(protected config: Config) {}
50
+ constructor(
51
+ protected config: Config,
52
+ protected hooks: Hooks,
53
+ ) {}
51
54
 
52
55
  private browserIsChrome = (b: BrowserInstance) =>
53
56
  this.chromeBrowsers.some((chromeBrowser) => b instanceof chromeBrowser);
@@ -63,8 +66,8 @@ export class BrowserManager {
63
66
  }
64
67
  };
65
68
 
66
- protected onNewPage = async (req: Request, page: unknown) => {
67
- await pageHook({ meta: req.parsed, page });
69
+ protected onNewPage = async (req: Request, page: Page) => {
70
+ await this.hooks.page({ meta: req.parsed, page });
68
71
  };
69
72
 
70
73
  /**
@@ -463,7 +466,7 @@ export class BrowserManager {
463
466
  this.browsers.set(browser, connectionMeta);
464
467
 
465
468
  await browser.launch(launchOptions as object);
466
- await browserHook({ browser, meta: req.parsed });
469
+ await this.hooks.browser({ browser, meta: req.parsed });
467
470
 
468
471
  browser.on('newPage', async (page) => {
469
472
  await this.onNewPage(req, page);
package/src/exports.ts CHANGED
@@ -10,6 +10,7 @@ export * from './metrics.js';
10
10
  export * from './mime-types.js';
11
11
  export * from './monitoring.js';
12
12
  export * from './router.js';
13
+ export * from './sdk-utils.js';
13
14
  export * from './server.js';
14
15
  export * from './shim.js';
15
16
  export * from './token.js';
@@ -1,11 +1,11 @@
1
- import { Config, FileSystem, sleep } from '@browserless.io/browserless';
1
+ import { Config, FileSystem, noop } from '@browserless.io/browserless';
2
2
  import { readFile, unlink } from 'fs/promises';
3
3
  import { expect } from 'chai';
4
4
 
5
5
  const filePath = '/tmp/_browserless_test_fs_';
6
6
 
7
7
  describe('File-System', () => {
8
- afterEach(async () => unlink(filePath));
8
+ afterEach(async () => unlink(filePath).catch(noop));
9
9
 
10
10
  it('saves and encodes files', async () => {
11
11
  const mySecretContents = 'pony-foo';
@@ -13,46 +13,71 @@ describe('File-System', () => {
13
13
  config.setToken('browserless.io');
14
14
  const f = new FileSystem(config);
15
15
 
16
- await f.append(filePath, mySecretContents);
16
+ await f.append(filePath, mySecretContents, true);
17
17
 
18
- expect(await f.read(filePath)).to.eql([mySecretContents]);
18
+ expect(await f.read(filePath, true)).to.eql([mySecretContents]);
19
19
  const rawText = (await readFile(filePath)).toString();
20
20
 
21
21
  expect(rawText.toString()).to.not.include(mySecretContents);
22
22
  });
23
23
 
24
- it('appends newlines to files', async () => {
24
+ it('saves files without encoding', async () => {
25
+ const mySecretContents = 'pony-foo';
26
+ const config = new Config();
27
+ config.setToken('browserless.io');
28
+ const f = new FileSystem(config);
29
+
30
+ await f.append(filePath, mySecretContents, false);
31
+
32
+ expect(await f.read(filePath, false)).to.eql([mySecretContents]);
33
+ const rawText = (await readFile(filePath)).toString();
34
+
35
+ expect(rawText.toString()).to.include(mySecretContents);
36
+ });
37
+
38
+ it('appends newlines to files and encodes them', async () => {
25
39
  const mySecretContents = 'pony-foo';
26
40
  const moreSecretContents = 'pony-pony-foo-foo';
27
41
  const config = new Config();
28
42
  config.setToken('browserless.io');
29
43
  const f = new FileSystem(config);
30
44
 
31
- await f.append(filePath, mySecretContents);
45
+ await f.append(filePath, mySecretContents, true);
32
46
 
33
- expect(await f.read(filePath)).to.eql([mySecretContents]);
47
+ expect(await f.read(filePath, true)).to.eql([mySecretContents]);
34
48
 
35
- await f.append(filePath, moreSecretContents);
49
+ await f.append(filePath, moreSecretContents, true);
36
50
 
37
- expect(await f.read(filePath)).to.eql([
51
+ expect(await f.read(filePath, true)).to.eql([
38
52
  mySecretContents,
39
53
  moreSecretContents,
40
54
  ]);
55
+ const rawText = (await readFile(filePath)).toString();
56
+
57
+ expect(rawText).to.not.include(mySecretContents);
58
+ expect(rawText).to.not.include(moreSecretContents);
41
59
  });
42
60
 
43
- it('re-encodes files on token changes', async () => {
61
+ it('appends newlines to files and does not encode them', async () => {
62
+ const mySecretContents = 'pony-foo';
63
+ const moreSecretContents = 'pony-pony-foo-foo';
44
64
  const config = new Config();
45
65
  config.setToken('browserless.io');
46
66
  const f = new FileSystem(config);
47
- const mySecretContents = 'pony-foo';
48
67
 
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();
68
+ await f.append(filePath, mySecretContents, false);
69
+
70
+ expect(await f.read(filePath, false)).to.eql([mySecretContents]);
71
+
72
+ await f.append(filePath, moreSecretContents, false);
73
+
74
+ expect(await f.read(filePath, false)).to.eql([
75
+ mySecretContents,
76
+ moreSecretContents,
77
+ ]);
78
+ const rawText = (await readFile(filePath)).toString();
54
79
 
55
- expect(oldText).to.not.equal(newText);
56
- expect(await f.read(filePath)).to.eql([mySecretContents]);
80
+ expect(rawText).to.include(mySecretContents);
81
+ expect(rawText).to.include(moreSecretContents);
57
82
  });
58
83
  });
@@ -15,28 +15,8 @@ export class FileSystem extends EventEmitter {
15
15
  constructor(protected config: Config) {
16
16
  super();
17
17
  this.currentAESKey = config.getAESKey();
18
- this.config.on('token', this.handleTokenChange);
19
18
  }
20
19
 
21
- private handleTokenChange = async () => {
22
- this.log(`Token has changed, updating file-system contents`);
23
- const start = Date.now();
24
- const newAESKey = this.config.getAESKey();
25
- await Promise.all(
26
- Array.from(this.fsMap).map(async ([filePath, contents]) => {
27
- const newlyEncoded = encrypt(
28
- contents.join('\n'),
29
- Buffer.from(newAESKey),
30
- );
31
- return writeFile(filePath, newlyEncoded);
32
- }),
33
- ).catch((e) => {
34
- this.log(`Error in setting new token: "${e}"`);
35
- });
36
- this.log(`Successfully updated file encodings in ${Date.now() - start}ms`);
37
- this.currentAESKey = this.config.getAESKey();
38
- };
39
-
40
20
  /**
41
21
  * Appends contents to a file-path for persistance. File contents are
42
22
  * encrypted before being saved to disk. Reads happen via the in-memory
@@ -46,15 +26,19 @@ export class FileSystem extends EventEmitter {
46
26
  * @param newContent A string of new content to add to the file
47
27
  * @returns void
48
28
  */
49
- append = async (path: string, newContent: string): Promise<void> => {
50
- const contents = await this.read(path);
29
+ append = async (
30
+ path: string,
31
+ newContent: string,
32
+ shouldEncode: boolean,
33
+ ): Promise<void> => {
34
+ const contents = await this.read(path, shouldEncode);
51
35
 
52
36
  contents.push(newContent);
53
37
  this.fsMap.set(path, contents);
54
- const encoded = await encrypt(
55
- contents.join('\n'),
56
- Buffer.from(this.currentAESKey),
57
- );
38
+
39
+ const encoded = shouldEncode
40
+ ? await encrypt(contents.join('\n'), this.currentAESKey)
41
+ : contents.join('\n');
58
42
 
59
43
  return writeFile(path, encoded.toString());
60
44
  };
@@ -66,16 +50,18 @@ export class FileSystem extends EventEmitter {
66
50
  * @param path The filepath of the contents to read
67
51
  * @returns Promise of the contents separated by newlines
68
52
  */
69
- read = async (path: string): Promise<string[]> => {
53
+ read = async (path: string, encoded: boolean): Promise<string[]> => {
70
54
  const hasKey = this.fsMap.has(path);
71
55
 
72
56
  if (hasKey) {
73
57
  return this.fsMap.get(path) as string[];
74
58
  }
75
59
  const contents = (await readFile(path).catch(() => '')).toString();
76
- const splitContents = contents.length
77
- ? (await decrypt(contents, Buffer.from(this.currentAESKey))).split('\n')
78
- : [];
60
+ const decoded =
61
+ encoded && contents.length
62
+ ? await decrypt(contents, this.currentAESKey)
63
+ : contents;
64
+ const splitContents = decoded.length ? decoded.split('\n') : [];
79
65
 
80
66
  this.fsMap.set(path, splitContents);
81
67
 
package/src/hooks.ts CHANGED
@@ -1,8 +1,50 @@
1
+ import {
2
+ AfterResponse,
3
+ BeforeRequest,
4
+ BrowserHook,
5
+ PageHook,
6
+ } from '@browserless.io/browserless';
7
+ import { EventEmitter } from 'events';
8
+
9
+ // KEPT for backwards compatibility reasons since some downstream
10
+ // docker images will override these files to inject their own hook
11
+ // behaviors
1
12
  // @ts-ignore
2
- export { default as beforeRequest } from '../external/before.js';
13
+ import { default as afterRequest } from '../external/after.js';
3
14
  // @ts-ignore
4
- export { default as afterRequest } from '../external/after.js';
15
+ import { default as beforeRequest } from '../external/before.js';
5
16
  // @ts-ignore
6
- export { default as pageHook } from '../external/page.js';
17
+ import { default as browserHook } from '../external/browser.js';
7
18
  // @ts-ignore
8
- export { default as browserHook } from '../external/browser.js';
19
+ import { default as pageHook } from '../external/page.js';
20
+
21
+ export class Hooks extends EventEmitter {
22
+ before(args: BeforeRequest): Promise<boolean> {
23
+ return beforeRequest(args);
24
+ }
25
+
26
+ after(args: AfterResponse): Promise<unknown> {
27
+ return afterRequest(args);
28
+ }
29
+
30
+ page(args: PageHook): Promise<unknown> {
31
+ return pageHook(args);
32
+ }
33
+
34
+ browser(args: BrowserHook): Promise<unknown> {
35
+ return browserHook(args);
36
+ }
37
+
38
+ /**
39
+ * Implement any browserless-core-specific shutdown logic here.
40
+ * Calls the empty-SDK stop method for downstream implementations.
41
+ */
42
+ public shutdown = async () => {
43
+ await this.stop();
44
+ };
45
+
46
+ /**
47
+ * Left blank for downstream SDK modules to optionally implement.
48
+ */
49
+ public stop = () => {};
50
+ }
@@ -1,23 +1,19 @@
1
1
  import {
2
2
  Config,
3
+ Hooks,
3
4
  Limiter,
4
5
  Metrics,
5
6
  Monitoring,
6
7
  WebHooks,
7
8
  sleep,
8
9
  } from '@browserless.io/browserless';
10
+ import Sinon, { spy } from 'sinon';
9
11
  import { expect } from 'chai';
10
- import { spy } from 'sinon';
11
12
 
12
13
  const asyncNoop = () => Promise.resolve(undefined);
13
14
  const noop = () => undefined;
14
- const webHooks = {
15
- callErrorAlertURL: spy(),
16
- callFailedHealthURL: spy(),
17
- callQueueAlertURL: spy(),
18
- callRejectAlertURL: spy(),
19
- callTimeoutAlertURL: spy(),
20
- };
15
+ const webHooks = Sinon.createStubInstance(WebHooks);
16
+ const hooks = Sinon.createStubInstance(Hooks);
21
17
 
22
18
  describe(`Limiter`, () => {
23
19
  afterEach(() => {
@@ -26,9 +22,13 @@ describe(`Limiter`, () => {
26
22
  webHooks.callRejectAlertURL.resetHistory();
27
23
  webHooks.callTimeoutAlertURL.resetHistory();
28
24
  webHooks.callErrorAlertURL.resetHistory();
25
+ hooks.before.resetHistory();
26
+ hooks.after.resetHistory();
27
+ hooks.browser.resetHistory();
28
+ hooks.page.resetHistory();
29
29
  });
30
30
 
31
- it('limits and queues function calls and calls queue alert urls', async () => {
31
+ it('limits and queues function calls, calls hooks, and calls queue alert urls', async () => {
32
32
  return new Promise((resolve, reject) => {
33
33
  const config = new Config();
34
34
  config.setQueueAlertURL('https://example.com');
@@ -40,12 +40,7 @@ describe(`Limiter`, () => {
40
40
  config.setQueued(1);
41
41
  config.setTimeout(-1);
42
42
 
43
- const limiter = new Limiter(
44
- config,
45
- metrics,
46
- monitoring,
47
- webHooks as unknown as WebHooks,
48
- );
43
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
49
44
  const handler = spy();
50
45
  const job = limiter.limit(handler, asyncNoop, asyncNoop, noop);
51
46
 
@@ -56,6 +51,7 @@ describe(`Limiter`, () => {
56
51
 
57
52
  limiter.addEventListener('end', () => {
58
53
  try {
54
+ expect(hooks.after.called).to.be.true;
59
55
  expect(handler.calledTwice).to.be.true;
60
56
  expect(webHooks.callQueueAlertURL.calledOnce).to.be.true;
61
57
  expect(metrics.get().queued).to.equal(1);
@@ -69,27 +65,36 @@ describe(`Limiter`, () => {
69
65
  });
70
66
  }).timeout(5000);
71
67
 
72
- it('passes through arguments', () => {
73
- const args = ['one', 'two', 'three'];
74
- const config = new Config();
75
- const metrics = new Metrics();
76
- const monitoring = new Monitoring(config);
77
- config.setConcurrent(1);
78
- config.setQueued(0);
79
- config.setTimeout(-1);
68
+ it('passes through arguments', () =>
69
+ new Promise((resolve, reject) => {
70
+ const args = ['one', 'two', 'three'];
71
+ const config = new Config();
72
+ const metrics = new Metrics();
73
+ const monitoring = new Monitoring(config);
74
+ config.setConcurrent(1);
75
+ config.setQueued(0);
76
+ config.setTimeout(-1);
80
77
 
81
- const limiter = new Limiter(
82
- config,
83
- metrics,
84
- monitoring,
85
- webHooks as unknown as WebHooks,
86
- );
87
- const handler = spy();
88
- const job = limiter.limit(handler, asyncNoop, asyncNoop, noop);
89
- // @ts-ignore will fix later
90
- job(...args);
91
- expect(handler.args[0]).to.eql(args);
92
- });
78
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
79
+ const handler = spy();
80
+ const job = limiter.limit(handler, asyncNoop, asyncNoop, noop);
81
+ // @ts-ignore will fix later
82
+ job(...args);
83
+ expect(handler.args[0]).to.eql(args);
84
+
85
+ limiter.addEventListener('end', () => {
86
+ try {
87
+ expect(hooks.after.args[0][0]).to.have.property('start');
88
+ expect(hooks.after.args[0][0]).to.have.property(
89
+ 'status',
90
+ 'successful',
91
+ );
92
+ } catch (e) {
93
+ return reject(e);
94
+ }
95
+ resolve(undefined);
96
+ });
97
+ }));
93
98
 
94
99
  it('waits to run jobs until the first are done', async () => {
95
100
  const config = new Config();
@@ -99,12 +104,7 @@ describe(`Limiter`, () => {
99
104
  config.setQueued(1);
100
105
  config.setTimeout(-1);
101
106
 
102
- const limiter = new Limiter(
103
- config,
104
- metrics,
105
- monitoring,
106
- webHooks as unknown as WebHooks,
107
- );
107
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
108
108
  const handlerOne = () => new Promise((r) => setTimeout(r, 50));
109
109
  const handlerTwo = spy();
110
110
 
@@ -129,12 +129,7 @@ describe(`Limiter`, () => {
129
129
  config.setQueued(1);
130
130
  config.setTimeout(-1);
131
131
 
132
- const limiter = new Limiter(
133
- config,
134
- metrics,
135
- monitoring,
136
- webHooks as unknown as WebHooks,
137
- );
132
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
138
133
  const spy = () => new Promise((_r, rej) => rej(error));
139
134
 
140
135
  const job = limiter.limit(spy, asyncNoop, asyncNoop, noop);
@@ -150,6 +145,7 @@ describe(`Limiter`, () => {
150
145
  error,
151
146
  );
152
147
  expect(webHooks.callErrorAlertURL.calledOnce).to.be.true;
148
+ expect(hooks.after.args[0][0]).to.have.property('status', 'error');
153
149
  });
154
150
  });
155
151
  });
@@ -163,12 +159,7 @@ describe(`Limiter`, () => {
163
159
  config.setQueued(0);
164
160
  config.setTimeout(-1);
165
161
 
166
- const limiter = new Limiter(
167
- config,
168
- metrics,
169
- monitoring,
170
- webHooks as unknown as WebHooks,
171
- );
162
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
172
163
 
173
164
  const handler = spy();
174
165
  const onError = spy();
@@ -184,37 +175,41 @@ describe(`Limiter`, () => {
184
175
  expect(onError.args[0]).to.eql(args);
185
176
  });
186
177
 
187
- it('calls a timeout handler with arguments if a job takes too long', (r) => {
188
- const args = ['one', 'two', 'three'];
189
- const config = new Config();
190
- const metrics = new Metrics();
191
- const monitoring = new Monitoring(config);
192
- config.setConcurrent(1);
193
- config.setQueued(0);
194
- config.setTimeout(10);
195
-
196
- let timer: NodeJS.Timer;
197
- const limiter = new Limiter(
198
- config,
199
- metrics,
200
- monitoring,
201
- webHooks as unknown as WebHooks,
202
- );
203
- const handler = () =>
204
- new Promise((d) => (timer = global.setTimeout(d, 1000)));
205
-
206
- const onTimeout = (...calledArgs: unknown[]) => {
207
- clearTimeout(timer as unknown as number);
208
- expect(calledArgs).to.eql(args);
209
- expect(webHooks.callTimeoutAlertURL.calledOnce).to.be.true;
210
- r(null);
211
- };
178
+ it('calls a timeout handler with arguments if a job takes too long', () =>
179
+ new Promise((resolve, reject) => {
180
+ const args = ['one', 'two', 'three'];
181
+ const config = new Config();
182
+ const metrics = new Metrics();
183
+ const monitoring = new Monitoring(config);
184
+ config.setConcurrent(1);
185
+ config.setQueued(0);
186
+ config.setTimeout(10);
212
187
 
213
- const job = limiter.limit(handler, noop, onTimeout, noop);
188
+ let timer: NodeJS.Timer;
189
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
190
+ const handler = () =>
191
+ new Promise((d) => (timer = global.setTimeout(d, 1000)));
214
192
 
215
- // @ts-ignore
216
- job(...args);
217
- });
193
+ const onTimeout = (...calledArgs: unknown[]) => {
194
+ clearTimeout(timer as unknown as number);
195
+ expect(calledArgs).to.eql(args);
196
+ expect(webHooks.callTimeoutAlertURL.calledOnce).to.be.true;
197
+ };
198
+
199
+ const job = limiter.limit(handler, noop, onTimeout, noop);
200
+
201
+ // @ts-ignore
202
+ job(...args);
203
+
204
+ limiter.addEventListener('end', () => {
205
+ try {
206
+ expect(hooks.after.args[0][0]).to.have.property('status', 'timedout');
207
+ } catch (e) {
208
+ return reject(e);
209
+ }
210
+ resolve(undefined);
211
+ });
212
+ }));
218
213
 
219
214
  it('allows overriding the timeouts', async () => {
220
215
  const config = new Config();
@@ -224,12 +219,7 @@ describe(`Limiter`, () => {
224
219
  config.setQueued(0);
225
220
  config.setTimeout(1);
226
221
 
227
- const limiter = new Limiter(
228
- config,
229
- metrics,
230
- monitoring,
231
- webHooks as unknown as WebHooks,
232
- );
222
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
233
223
  const onTimeout = spy();
234
224
  const handler = async () => new Promise((r) => setTimeout(r, 10));
235
225
 
@@ -252,12 +242,7 @@ describe(`Limiter`, () => {
252
242
  config.setQueued(0);
253
243
  config.setTimeout(20);
254
244
 
255
- const limiter = new Limiter(
256
- config,
257
- metrics,
258
- monitoring,
259
- webHooks as unknown as WebHooks,
260
- );
245
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
261
246
  const handler = () => new Promise((r) => setTimeout(r, 1));
262
247
  const timeout = spy();
263
248
  const job = limiter.limit(handler, noop, timeout, noop);
@@ -276,12 +261,7 @@ describe(`Limiter`, () => {
276
261
  config.setQueued(0);
277
262
  config.setTimeout(-1);
278
263
 
279
- const limiter = new Limiter(
280
- config,
281
- metrics,
282
- monitoring,
283
- webHooks as unknown as WebHooks,
284
- );
264
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
285
265
 
286
266
  const handler = spy();
287
267
  const job = limiter.limit(handler, noop, noop, noop);
@@ -312,12 +292,7 @@ describe(`Limiter`, () => {
312
292
  config.setQueued(10);
313
293
  config.setTimeout(-1);
314
294
 
315
- const limiter = new Limiter(
316
- config,
317
- metrics,
318
- monitoring,
319
- webHooks as unknown as WebHooks,
320
- );
295
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
321
296
 
322
297
  const handler = spy();
323
298
  const job = limiter.limit(handler, noop, noop, noop);
@@ -343,12 +318,7 @@ describe(`Limiter`, () => {
343
318
  config.setQueued(10);
344
319
  config.setTimeout(-1);
345
320
 
346
- const limiter = new Limiter(
347
- config,
348
- metrics,
349
- monitoring,
350
- webHooks as unknown as WebHooks,
351
- );
321
+ const limiter = new Limiter(config, metrics, monitoring, webHooks, hooks);
352
322
 
353
323
  const handler = spy();
354
324
  const job = limiter.limit(handler, noop, noop, noop);