@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/.github/workflows/cicd.yml +1 -1
- package/README.md +61 -1
- package/assets/logo-white.png +0 -0
- package/assets/logo.png +0 -0
- package/dist/controllers/cdp.controller.d.ts +2 -0
- package/dist/controllers/cdp.controller.js +36 -4
- package/dist/controllers/cdp.controller.js.map +1 -1
- package/dist/errors/max-browser-reached.error.d.ts +2 -0
- package/dist/errors/max-browser-reached.error.js +7 -0
- package/dist/errors/max-browser-reached.error.js.map +1 -0
- package/dist/main.js +11 -0
- package/dist/main.js.map +1 -1
- package/dist/services/browser-pool.service.js +5 -1
- package/dist/services/browser-pool.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -1
- package/src/controllers/cdp.controller.ts +41 -4
- package/src/errors/max-browser-reached.error.ts +3 -0
- package/src/main.ts +13 -0
- package/src/services/browser-pool.service.ts +6 -1
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blitzbrowser/blitzbrowser",
|
|
3
|
-
"version": "1.1.
|
|
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
|
-
|
|
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),
|
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}.`);
|