@blitzbrowser/blitzbrowser 1.1.4 → 1.1.6

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.
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@blitzbrowser/blitzbrowser",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "description": "Deploying, scaling and managing headful browsers in docker.",
5
5
  "license": "Apache-2.0",
6
6
  "main": "./dist/index.js",
7
+ "repository": {
8
+ "url": "https://github.com/blitzbrowser/blitzbrowser"
9
+ },
7
10
  "dependencies": {
8
11
  "@aws-sdk/client-s3": "^3.911.0",
9
12
  "@blitzbrowser/tunnel": "2.0.3",
@@ -6,6 +6,7 @@ import { BrowserPoolService } from 'src/services/browser-pool.service';
6
6
  import { WebSocketServer } from 'ws';
7
7
  import z from 'zod';
8
8
  import { Message, Tunnel } from '@blitzbrowser/tunnel';
9
+ import { MaxBrowserReachedError } from 'src/errors/max-browser-reached.error';
9
10
 
10
11
  export const PROXY_URL_QUERY_PARAM = 'proxyUrl';
11
12
  export const TIMEZONE_QUERY_PARAM = 'timezone';
@@ -24,8 +25,11 @@ type ConnectionOptionQueryParams = z.infer<typeof ConnectionOptionQueryParams>;
24
25
  @Controller()
25
26
  export class CDPController implements OnModuleInit {
26
27
 
28
+ private static readonly WAITING_BROWSER_TIMEOUT_MS = 10_000;
29
+
27
30
  static readonly INTERNAL_SERVER_ERROR_CODE = 4000;
28
31
  static readonly BAD_REQUEST_CODE = 4002;
32
+ static readonly NO_BROWSER_INSTANCE_AVAILABLE = 4003;
29
33
 
30
34
  readonly #logger = new Logger(CDPController.name);
31
35
 
@@ -37,7 +41,9 @@ export class CDPController implements OnModuleInit {
37
41
  async onModuleInit() {
38
42
  const websocket_server = new WebSocketServer({ server: (this.http_adapter_host.httpAdapter as ExpressAdapter).getHttpServer() });
39
43
 
40
- websocket_server.on('connection', (cdp_websocket_client, req) => {
44
+ websocket_server.on('connection', async (cdp_websocket_client, req) => {
45
+ let tunnel: Tunnel;
46
+
41
47
  try {
42
48
  const url = new URL(`http://127.0.0.1${req.url}`);
43
49
  const parsed_connection_options = this.#parseConnectionOptionQueryParams(url);
@@ -47,9 +53,7 @@ export class CDPController implements OnModuleInit {
47
53
  return;
48
54
  }
49
55
 
50
- const browser_instance = this.browser_pool_service.createBrowserInstance();
51
-
52
- const tunnel = new Tunnel();
56
+ tunnel = new Tunnel();
53
57
 
54
58
  tunnel.on('message', message => {
55
59
  if (message.channel_id === BrowserInstance.CDP_CHANNEL_ID) {
@@ -65,6 +69,8 @@ export class CDPController implements OnModuleInit {
65
69
  tunnel.receiveMessage(Message.of(BrowserInstance.CDP_CHANNEL_ID, message.toString('utf8')));
66
70
  });
67
71
 
72
+ const browser_instance: BrowserInstance = await this.#getBrowserInstance();
73
+
68
74
  const ping_interval_id = setInterval(() => {
69
75
  cdp_websocket_client.ping();
70
76
  }, 3000);
@@ -89,6 +95,12 @@ export class CDPController implements OnModuleInit {
89
95
 
90
96
  this.#logger.log('Sent connection options');
91
97
  } catch (e) {
98
+ if (e instanceof MaxBrowserReachedError) {
99
+ tunnel.close();
100
+ cdp_websocket_client.close(CDPController.NO_BROWSER_INSTANCE_AVAILABLE, 'No browser instance available.');
101
+ return;
102
+ }
103
+
92
104
  this.#logger.error(`Error while handling client websocket.`, e?.stack || e)
93
105
 
94
106
  cdp_websocket_client.close(CDPController.INTERNAL_SERVER_ERROR_CODE, e?.stack || e);
@@ -106,6 +118,31 @@ export class CDPController implements OnModuleInit {
106
118
  });
107
119
  }
108
120
 
121
+ async #getBrowserInstance(): Promise<BrowserInstance> {
122
+ const start = Date.now();
123
+
124
+ while (start + CDPController.WAITING_BROWSER_TIMEOUT_MS > Date.now()) {
125
+ try {
126
+ const browser_instance = this.browser_pool_service.createBrowserInstance();
127
+
128
+ if (browser_instance) {
129
+ return browser_instance;
130
+ }
131
+ } catch (e) {
132
+ if (e instanceof MaxBrowserReachedError) {
133
+ await new Promise((resolve) => {
134
+ setTimeout(resolve, 100);
135
+ });
136
+ continue;
137
+ } else {
138
+ throw e;
139
+ }
140
+ }
141
+ }
142
+
143
+ throw new MaxBrowserReachedError();
144
+ }
145
+
109
146
  #parseConnectionOptionQueryParams(url: URL) {
110
147
  return ConnectionOptionQueryParams.safeParse({
111
148
  timezone: this.getQueryParamValue(TIMEZONE_QUERY_PARAM, url),
@@ -0,0 +1,3 @@
1
+ export class MaxBrowserReachedError extends Error {
2
+
3
+ }
package/src/main.ts CHANGED
@@ -1,5 +1,18 @@
1
1
  import { NestFactory } from '@nestjs/core';
2
2
  import { AppModule } from './app.module';
3
+ import { Logger } from '@nestjs/common';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+
7
+ const package_json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8'));
8
+ const logger = new Logger();
9
+
10
+ logger.log('#########################################################');
11
+ logger.log(`# Version: ${package_json.version}`);
12
+ logger.log(`# Website: https://blitzbrowser.com`);
13
+ logger.log(`# Github : https://github.com/blitzbrowser/blitzbrowser`);
14
+ logger.log(`# Discord: https://discord.com/invite/qZ3tCZJ2Ze`);
15
+ logger.log('#########################################################');
3
16
 
4
17
  async function bootstrap() {
5
18
  const app = await NestFactory.create(AppModule);
@@ -2,6 +2,7 @@ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2
2
  import { BrowserInstance } from 'src/components/browser-instance.component';
3
3
  import * as EventEmitter from 'events';
4
4
  import { ModuleRef } from '@nestjs/core';
5
+ import { MaxBrowserReachedError } from 'src/errors/max-browser-reached.error';
5
6
 
6
7
  type PoolServiceEvents = {
7
8
  browser_instance_created: [BrowserInstance];
@@ -23,7 +24,7 @@ export class BrowserPoolService extends EventEmitter<PoolServiceEvents> implemen
23
24
  readonly #started_at = new Date().toISOString();
24
25
  readonly #tags: { [key: string]: string; } = {};
25
26
 
26
- readonly max_browser_instances = parseInt(process.env.MAX_BROWSER_INSTANCES);
27
+ readonly max_browser_instances = parseInt(process.env.MAX_BROWSER_INSTANCES || '99');
27
28
 
28
29
  readonly #browser_instances = new Map<string, BrowserInstance>();
29
30
 
@@ -91,6 +92,10 @@ export class BrowserPoolService extends EventEmitter<PoolServiceEvents> implemen
91
92
  return;
92
93
  }
93
94
 
95
+ if(this.#browser_instances.size === this.max_browser_instances) {
96
+ throw new MaxBrowserReachedError();
97
+ }
98
+
94
99
  const browser_instance = new BrowserInstance(id, this.module_ref);
95
100
 
96
101
  this.logger.log(`Created browser instance ${browser_instance.id}.`);