n8n-nodes-dhd-browser 1.0.0

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/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # n8n-nodes-dhd-browser
2
+
3
+ n8n community node for creating and launching browser profiles with automatic proxy authentication.
4
+
5
+ ## Features
6
+
7
+ - ✅ Create browser profiles with fingerprint configuration
8
+ - ✅ Parse proxy strings or use separate proxy fields
9
+ - ✅ Automatic proxy authentication injection (no manual login required)
10
+ - ✅ Launch Chromium/Chrome with custom profiles
11
+ - ✅ Support for fingerprinting (OS, Chrome version, User Agent, Timezone, etc.)
12
+ - ✅ Modify Chrome Preferences automatically
13
+ - ✅ Inject proxy authentication via Chrome DevTools Protocol
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install n8n-nodes-dhd-browser
19
+ ```
20
+
21
+ ## How It Works
22
+
23
+ This node implements the same approach used by professional browser automation tools:
24
+
25
+ 1. **Create Profile**:
26
+ - Parses proxy configuration (from string or separate fields)
27
+ - Generates browser fingerprint
28
+ - Creates profile directory structure
29
+ - Modifies Chrome Preferences to set proxy server
30
+ - Saves profile configuration JSON
31
+
32
+ 2. **Launch Profile**:
33
+ - Loads profile configuration
34
+ - Launches Chromium with `--proxy-server` flag
35
+ - Connects via Chrome DevTools Protocol
36
+ - Injects `Proxy-Authorization` header automatically via `Network.setExtraHTTPHeaders()`
37
+ - Browser is ready to use without manual proxy login
38
+
39
+ ## Usage
40
+
41
+ ### Create Profile
42
+
43
+ 1. Set **Operation** to "Create Profile"
44
+ 2. Provide:
45
+ - **Profile Name**: Name for your profile
46
+ - **Profile Directory**: Base directory where profiles will be stored
47
+ - **Proxy Configuration**: Choose "Proxy String" or "Separate Fields"
48
+ - **Browser Fingerprint**: Configure OS, Chrome version, User Agent, etc.
49
+
50
+ 3. Output: Profile configuration path and details
51
+
52
+ ### Launch Profile
53
+
54
+ 1. Set **Operation** to "Launch Profile"
55
+ 2. Provide:
56
+ - **Profile Config Path**: Path to the `profile.config.json` file created by "Create Profile"
57
+ - **Chrome Executable Path**: Full path to Chrome/Chromium executable
58
+ - **Headless**: Whether to run in headless mode
59
+ - **Window Size**: Browser window size (e.g., "1920x1080")
60
+
61
+ 3. Output: Browser connection information (WebSocket endpoint, debug port)
62
+
63
+ ## Proxy Format
64
+
65
+ ### Proxy String Format
66
+ ```
67
+ http://username:password@proxy.example.com:8080
68
+ socks5://user:pass@192.168.1.1:1080
69
+ ```
70
+
71
+ ### Separate Fields
72
+ - Proxy Type: http, https, socks4, socks5
73
+ - Proxy Host: IP address or hostname
74
+ - Proxy Port: Port number
75
+ - Proxy Username: Authentication username
76
+ - Proxy Password: Authentication password
77
+
78
+ ## Browser Fingerprint
79
+
80
+ Configure fingerprint settings:
81
+ - **OS**: Windows, macOS, Linux
82
+ - **Chrome Version**: Chrome version to emulate (e.g., "120.0.0.0")
83
+ - **User Agent**: Custom user agent (auto-generated if empty)
84
+ - **Screen Resolution**: e.g., "1920x1080"
85
+ - **Timezone**: e.g., "America/New_York", "Asia/Ho_Chi_Minh"
86
+ - **Language**: Browser language, e.g., "en-US", "vi-VN"
87
+
88
+ ## Technical Details
89
+
90
+ ### Automatic Proxy Authentication
91
+
92
+ The node uses Chrome DevTools Protocol to inject proxy authentication:
93
+
94
+ 1. Browser launches with `--proxy-server` flag
95
+ 2. Node connects to browser via DevTools Protocol (debug port)
96
+ 3. Enables Network domain: `Network.enable()`
97
+ 4. Sets extra HTTP headers: `Network.setExtraHTTPHeaders()` with `Proxy-Authorization: Basic base64(user:pass)`
98
+ 5. All requests automatically include authentication header
99
+
100
+ This approach ensures:
101
+ - ✅ No manual proxy login dialog
102
+ - ✅ Authentication persists for entire browser session
103
+ - ✅ Works with HTTP, HTTPS, SOCKS4, and SOCKS5 proxies
104
+
105
+ ### Chrome Preferences
106
+
107
+ The node automatically modifies Chrome Preferences file to:
108
+ - Set proxy server configuration
109
+ - Store fingerprint settings
110
+ - Configure default browser behavior
111
+
112
+ ## Requirements
113
+
114
+ - Node.js >= 18
115
+ - Chrome or Chromium browser installed
116
+ - n8n >= 1.0.0
117
+
118
+ ## Dependencies
119
+
120
+ - `puppeteer-core`: Browser automation
121
+ - `chrome-remote-interface`: Chrome DevTools Protocol client
122
+ - `fs-extra`: File system operations
123
+ - `uuid`: Generate unique profile IDs
124
+
125
+ ## License
126
+
127
+ MIT
128
+
129
+ ## Author
130
+
131
+ DHD
132
+
@@ -0,0 +1,12 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class BrowserProfile implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ private createProfile;
6
+ private launchProfile;
7
+ private parseProxyString;
8
+ private generateFingerprint;
9
+ private modifyChromePreferences;
10
+ private injectProxyAuthentication;
11
+ private findAvailablePort;
12
+ }
@@ -0,0 +1,674 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.BrowserProfile = void 0;
30
+ const n8n_workflow_1 = require("n8n-workflow");
31
+ const puppeteer = __importStar(require("puppeteer-core"));
32
+ const fs = __importStar(require("fs-extra"));
33
+ const path = __importStar(require("path"));
34
+ const net = __importStar(require("net"));
35
+ const uuid_1 = require("uuid");
36
+ // @ts-expect-error - chrome-remote-interface doesn't have TypeScript definitions
37
+ const chrome_remote_interface_1 = __importDefault(require("chrome-remote-interface"));
38
+ class BrowserProfile {
39
+ constructor() {
40
+ this.description = {
41
+ displayName: 'Browser Profile',
42
+ name: 'browserProfile',
43
+ icon: 'file:browser.svg',
44
+ group: ['transform'],
45
+ version: 1,
46
+ subtitle: '={{$parameter["operation"]}}',
47
+ description: 'Create and launch browser profile with automatic proxy authentication',
48
+ defaults: {
49
+ name: 'Browser Profile',
50
+ },
51
+ inputs: ['main'],
52
+ outputs: ['main'],
53
+ credentials: [],
54
+ properties: [
55
+ {
56
+ displayName: 'Operation',
57
+ name: 'operation',
58
+ type: 'options',
59
+ noDataExpression: true,
60
+ options: [
61
+ {
62
+ name: 'Create Profile',
63
+ value: 'create',
64
+ description: 'Create a new browser profile with fingerprint and proxy config',
65
+ action: 'Create a new browser profile',
66
+ },
67
+ {
68
+ name: 'Launch Profile',
69
+ value: 'launch',
70
+ description: 'Launch an existing browser profile with proxy authentication',
71
+ action: 'Launch an existing browser profile',
72
+ },
73
+ ],
74
+ default: 'create',
75
+ },
76
+ // CREATE PROFILE OPTIONS
77
+ {
78
+ displayName: 'Profile Name',
79
+ name: 'profileName',
80
+ type: 'string',
81
+ default: '',
82
+ required: true,
83
+ displayOptions: {
84
+ show: {
85
+ operation: ['create'],
86
+ },
87
+ },
88
+ description: 'Name of the browser profile',
89
+ },
90
+ {
91
+ displayName: 'Profile Directory',
92
+ name: 'profileDir',
93
+ type: 'string',
94
+ default: '',
95
+ required: true,
96
+ displayOptions: {
97
+ show: {
98
+ operation: ['create'],
99
+ },
100
+ },
101
+ description: 'Directory path to store the profile (will be created if not exists)',
102
+ },
103
+ {
104
+ displayName: 'Proxy Configuration',
105
+ name: 'proxyConfig',
106
+ type: 'options',
107
+ default: 'string',
108
+ options: [
109
+ {
110
+ name: 'Proxy String',
111
+ value: 'string',
112
+ },
113
+ {
114
+ name: 'Separate Fields',
115
+ value: 'fields',
116
+ },
117
+ ],
118
+ displayOptions: {
119
+ show: {
120
+ operation: ['create'],
121
+ },
122
+ },
123
+ description: 'How to provide proxy configuration',
124
+ },
125
+ {
126
+ displayName: 'Proxy String',
127
+ name: 'proxyString',
128
+ type: 'string',
129
+ default: '',
130
+ placeholder: 'http://username:password@proxy.example.com:8080',
131
+ displayOptions: {
132
+ show: {
133
+ operation: ['create'],
134
+ proxyConfig: ['string'],
135
+ },
136
+ },
137
+ description: 'Proxy string in format: type://user:pass@host:port',
138
+ },
139
+ {
140
+ displayName: 'Proxy Type',
141
+ name: 'proxyType',
142
+ type: 'options',
143
+ options: [
144
+ {
145
+ name: 'HTTP',
146
+ value: 'http',
147
+ },
148
+ {
149
+ name: 'HTTPS',
150
+ value: 'https',
151
+ },
152
+ {
153
+ name: 'SOCKS4',
154
+ value: 'socks4',
155
+ },
156
+ {
157
+ name: 'SOCKS5',
158
+ value: 'socks5',
159
+ },
160
+ ],
161
+ default: 'http',
162
+ displayOptions: {
163
+ show: {
164
+ operation: ['create'],
165
+ proxyConfig: ['fields'],
166
+ },
167
+ },
168
+ description: 'Type of proxy server',
169
+ },
170
+ {
171
+ displayName: 'Proxy Host',
172
+ name: 'proxyHost',
173
+ type: 'string',
174
+ default: '',
175
+ displayOptions: {
176
+ show: {
177
+ operation: ['create'],
178
+ proxyConfig: ['fields'],
179
+ },
180
+ },
181
+ description: 'Proxy server hostname or IP address',
182
+ },
183
+ {
184
+ displayName: 'Proxy Port',
185
+ name: 'proxyPort',
186
+ type: 'number',
187
+ default: 8080,
188
+ displayOptions: {
189
+ show: {
190
+ operation: ['create'],
191
+ proxyConfig: ['fields'],
192
+ },
193
+ },
194
+ description: 'Proxy server port',
195
+ },
196
+ {
197
+ displayName: 'Proxy Username',
198
+ name: 'proxyUser',
199
+ type: 'string',
200
+ default: '',
201
+ displayOptions: {
202
+ show: {
203
+ operation: ['create'],
204
+ proxyConfig: ['fields'],
205
+ },
206
+ },
207
+ description: 'Username for proxy authentication',
208
+ },
209
+ {
210
+ displayName: 'Proxy Password',
211
+ name: 'proxyPass',
212
+ type: 'string',
213
+ typeOptions: {
214
+ password: true,
215
+ },
216
+ default: '',
217
+ displayOptions: {
218
+ show: {
219
+ operation: ['create'],
220
+ proxyConfig: ['fields'],
221
+ },
222
+ },
223
+ description: 'Password for proxy authentication',
224
+ },
225
+ {
226
+ displayName: 'Browser Fingerprint',
227
+ name: 'fingerprint',
228
+ type: 'fixedCollection',
229
+ typeOptions: {
230
+ multipleValues: false,
231
+ },
232
+ default: {},
233
+ displayOptions: {
234
+ show: {
235
+ operation: ['create'],
236
+ },
237
+ },
238
+ description: 'Browser fingerprint configuration',
239
+ options: [
240
+ {
241
+ displayName: 'Values',
242
+ name: 'values',
243
+ values: [
244
+ {
245
+ displayName: 'Chrome Version',
246
+ name: 'chromeVersion',
247
+ type: 'string',
248
+ default: '120.0.0.0',
249
+ description: 'Chrome version to emulate',
250
+ },
251
+ {
252
+ displayName: 'Language',
253
+ name: 'language',
254
+ type: 'string',
255
+ default: 'en-US',
256
+ description: 'Browser language (e.g., en-US, vi-VN)',
257
+ },
258
+ {
259
+ displayName: 'OS',
260
+ name: 'os',
261
+ type: 'options',
262
+ options: [
263
+ { name: 'Windows 10', value: 'win10' },
264
+ { name: 'Windows 11', value: 'win' },
265
+ { name: 'macOS', value: 'mac' },
266
+ { name: 'Linux', value: 'linux' },
267
+ ],
268
+ default: 'win',
269
+ },
270
+ {
271
+ displayName: 'Screen Resolution',
272
+ name: 'resolution',
273
+ type: 'string',
274
+ default: '1920x1080',
275
+ description: 'Screen resolution (widthxheight)',
276
+ },
277
+ {
278
+ displayName: 'Timezone',
279
+ name: 'timezone',
280
+ type: 'string',
281
+ default: 'America/New_York',
282
+ description: 'Timezone (e.g., America/New_York, Asia/Ho_Chi_Minh)',
283
+ },
284
+ {
285
+ displayName: 'User Agent',
286
+ name: 'userAgent',
287
+ type: 'string',
288
+ default: '',
289
+ description: 'Custom user agent (leave empty to auto-generate)',
290
+ },
291
+ ],
292
+ },
293
+ ],
294
+ },
295
+ // LAUNCH PROFILE OPTIONS
296
+ {
297
+ displayName: 'Profile Config Path',
298
+ name: 'profileConfigPath',
299
+ type: 'string',
300
+ default: '',
301
+ required: true,
302
+ displayOptions: {
303
+ show: {
304
+ operation: ['launch'],
305
+ },
306
+ },
307
+ description: 'Path to profile config JSON file (created by Create Profile operation)',
308
+ },
309
+ {
310
+ displayName: 'Chrome Executable Path',
311
+ name: 'chromePath',
312
+ type: 'string',
313
+ default: '',
314
+ required: true,
315
+ displayOptions: {
316
+ show: {
317
+ operation: ['launch'],
318
+ },
319
+ },
320
+ description: 'Path to Chrome/Chromium executable',
321
+ },
322
+ {
323
+ displayName: 'Headless',
324
+ name: 'headless',
325
+ type: 'boolean',
326
+ default: false,
327
+ displayOptions: {
328
+ show: {
329
+ operation: ['launch'],
330
+ },
331
+ },
332
+ description: 'Whether to run browser in headless mode',
333
+ },
334
+ {
335
+ displayName: 'Window Size',
336
+ name: 'windowSize',
337
+ type: 'string',
338
+ default: '1920x1080',
339
+ displayOptions: {
340
+ show: {
341
+ operation: ['launch'],
342
+ },
343
+ },
344
+ description: 'Browser window size (widthxheight)',
345
+ },
346
+ {
347
+ displayName: 'Return Browser Info',
348
+ name: 'returnBrowserInfo',
349
+ type: 'boolean',
350
+ default: true,
351
+ displayOptions: {
352
+ show: {
353
+ operation: ['launch'],
354
+ },
355
+ },
356
+ description: 'Whether to return browser connection information in output',
357
+ },
358
+ ],
359
+ };
360
+ }
361
+ async execute() {
362
+ const items = this.getInputData();
363
+ const operation = this.getNodeParameter('operation', 0);
364
+ const returnData = [];
365
+ // Create instance helper to access private methods
366
+ const instance = new BrowserProfile();
367
+ for (let i = 0; i < items.length; i++) {
368
+ try {
369
+ if (operation === 'create') {
370
+ const result = await instance.createProfile(this, i);
371
+ returnData.push({
372
+ json: result,
373
+ });
374
+ }
375
+ else if (operation === 'launch') {
376
+ const result = await instance.launchProfile(this, i);
377
+ returnData.push({
378
+ json: result,
379
+ });
380
+ }
381
+ }
382
+ catch (error) {
383
+ if (this.continueOnFail()) {
384
+ returnData.push({
385
+ json: {
386
+ error: error instanceof Error ? error.message : String(error),
387
+ },
388
+ });
389
+ continue;
390
+ }
391
+ throw error;
392
+ }
393
+ }
394
+ return [returnData];
395
+ }
396
+ async createProfile(context, itemIndex) {
397
+ const profileName = context.getNodeParameter('profileName', itemIndex);
398
+ const profileDir = context.getNodeParameter('profileDir', itemIndex);
399
+ const proxyConfig = context.getNodeParameter('proxyConfig', itemIndex);
400
+ // Parse proxy configuration
401
+ let proxyData;
402
+ if (proxyConfig === 'string') {
403
+ const proxyString = context.getNodeParameter('proxyString', itemIndex);
404
+ proxyData = this.parseProxyString(context, proxyString);
405
+ }
406
+ else {
407
+ proxyData = {
408
+ type: context.getNodeParameter('proxyType', itemIndex),
409
+ host: context.getNodeParameter('proxyHost', itemIndex),
410
+ port: context.getNodeParameter('proxyPort', itemIndex),
411
+ username: context.getNodeParameter('proxyUser', itemIndex),
412
+ password: context.getNodeParameter('proxyPass', itemIndex),
413
+ };
414
+ }
415
+ // Get fingerprint configuration
416
+ const fingerprintData = context.getNodeParameter('fingerprint.values', itemIndex, {});
417
+ // Generate fingerprint if needed
418
+ const fingerprint = this.generateFingerprint(fingerprintData);
419
+ // Create profile directory
420
+ const fullProfileDir = path.resolve(profileDir, profileName);
421
+ await fs.ensureDir(fullProfileDir);
422
+ await fs.ensureDir(path.join(fullProfileDir, 'Default'));
423
+ // Create profile config
424
+ const profileId = (0, uuid_1.v4)();
425
+ const profileConfig = {
426
+ id: profileId,
427
+ name: profileName,
428
+ profileDir: fullProfileDir,
429
+ proxy: {
430
+ type: proxyData.type,
431
+ host: proxyData.host,
432
+ port: proxyData.port,
433
+ username: proxyData.username,
434
+ password: proxyData.password,
435
+ },
436
+ fingerprint: fingerprint,
437
+ createdAt: new Date().toISOString(),
438
+ };
439
+ // Save profile config
440
+ const configPath = path.join(fullProfileDir, 'profile.config.json');
441
+ await fs.writeJSON(configPath, profileConfig, { spaces: 2 });
442
+ // Modify Chrome Preferences to set proxy
443
+ await this.modifyChromePreferences(fullProfileDir, proxyData);
444
+ return {
445
+ success: true,
446
+ profileId: profileId,
447
+ profileName: profileName,
448
+ profileDir: fullProfileDir,
449
+ configPath: configPath,
450
+ proxy: {
451
+ type: proxyData.type,
452
+ host: proxyData.host,
453
+ port: proxyData.port,
454
+ },
455
+ fingerprint: fingerprint,
456
+ };
457
+ }
458
+ async launchProfile(context, itemIndex) {
459
+ const configPath = context.getNodeParameter('profileConfigPath', itemIndex);
460
+ const chromePath = context.getNodeParameter('chromePath', itemIndex);
461
+ const headless = context.getNodeParameter('headless', itemIndex);
462
+ const windowSize = context.getNodeParameter('windowSize', itemIndex);
463
+ const returnBrowserInfo = context.getNodeParameter('returnBrowserInfo', itemIndex);
464
+ // Load profile config
465
+ const profileConfig = await fs.readJSON(configPath);
466
+ const { profileDir, proxy, fingerprint } = profileConfig;
467
+ // Parse window size
468
+ const [width, height] = windowSize.split('x').map(Number);
469
+ // Find available debug port
470
+ const debugPort = await this.findAvailablePort(context, 9222);
471
+ // Build proxy server string
472
+ const proxyServer = `${proxy.type}://${proxy.host}:${proxy.port}`;
473
+ // Launch browser with puppeteer
474
+ const browser = await puppeteer.launch({
475
+ executablePath: chromePath,
476
+ headless: headless,
477
+ userDataDir: profileDir,
478
+ args: [
479
+ `--proxy-server=${proxyServer}`,
480
+ `--remote-debugging-port=${debugPort}`,
481
+ `--window-size=${width},${height}`,
482
+ '--no-sandbox',
483
+ '--disable-setuid-sandbox',
484
+ '--disable-dev-shm-usage',
485
+ '--disable-blink-features=AutomationControlled',
486
+ ],
487
+ defaultViewport: {
488
+ width: width,
489
+ height: height,
490
+ },
491
+ });
492
+ // Get browser pages
493
+ const pages = await browser.pages();
494
+ const page = pages[0] || (await browser.newPage());
495
+ // Set user agent and other fingerprint settings
496
+ if (fingerprint.userAgent) {
497
+ await page.setUserAgent(fingerprint.userAgent);
498
+ }
499
+ if (fingerprint.timezone) {
500
+ await page.emulateTimezone(fingerprint.timezone);
501
+ }
502
+ // Set extra HTTP headers including proxy authentication
503
+ const extraHeaders = {};
504
+ if (fingerprint.language) {
505
+ extraHeaders['Accept-Language'] = fingerprint.language;
506
+ }
507
+ // Add proxy authentication header
508
+ if (proxy.username && proxy.password) {
509
+ const credentials = Buffer.from(`${proxy.username}:${proxy.password}`).toString('base64');
510
+ extraHeaders['Proxy-Authorization'] = `Basic ${credentials}`;
511
+ }
512
+ if (Object.keys(extraHeaders).length > 0) {
513
+ await page.setExtraHTTPHeaders(extraHeaders);
514
+ }
515
+ // Also inject via DevTools Protocol for maximum compatibility
516
+ if (proxy.username && proxy.password) {
517
+ await this.injectProxyAuthentication(context, debugPort, proxy.username, proxy.password);
518
+ }
519
+ // Get browser info
520
+ const browserInfo = {
521
+ success: true,
522
+ profileId: profileConfig.id,
523
+ profileName: profileConfig.name,
524
+ debugPort: debugPort,
525
+ wsEndpoint: browser.wsEndpoint(),
526
+ };
527
+ if (returnBrowserInfo) {
528
+ return browserInfo;
529
+ }
530
+ // Return basic info and keep browser running
531
+ return {
532
+ success: true,
533
+ message: 'Browser launched successfully with proxy authentication',
534
+ profileId: profileConfig.id,
535
+ debugPort: debugPort,
536
+ };
537
+ }
538
+ parseProxyString(context, proxyString) {
539
+ // Parse proxy string format: type://user:pass@host:port or type://host:port
540
+ const urlMatch = proxyString.match(/^(\w+):\/\/(?:([^:]+):([^@]+)@)?([^:]+):(\d+)$/);
541
+ if (!urlMatch) {
542
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid proxy string format: ${proxyString}. Expected format: type://user:pass@host:port or type://host:port`);
543
+ }
544
+ return {
545
+ type: urlMatch[1],
546
+ username: urlMatch[2] || '',
547
+ password: urlMatch[3] || '',
548
+ host: urlMatch[4],
549
+ port: parseInt(urlMatch[5], 10),
550
+ };
551
+ }
552
+ generateFingerprint(fingerprintData) {
553
+ const os = fingerprintData.os || 'win';
554
+ const chromeVersion = fingerprintData.chromeVersion || '120.0.0.0';
555
+ const resolution = fingerprintData.resolution || '1920x1080';
556
+ // Generate user agent based on OS and Chrome version
557
+ let userAgent = fingerprintData.userAgent;
558
+ if (!userAgent) {
559
+ if (os === 'win') {
560
+ userAgent = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
561
+ }
562
+ else if (os === 'mac') {
563
+ userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
564
+ }
565
+ else {
566
+ userAgent = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
567
+ }
568
+ }
569
+ return {
570
+ os: os,
571
+ chromeVersion: chromeVersion,
572
+ userAgent: userAgent,
573
+ resolution: resolution,
574
+ timezone: (fingerprintData.timezone || 'America/New_York'),
575
+ language: (fingerprintData.language || 'en-US'),
576
+ };
577
+ }
578
+ async modifyChromePreferences(profileDir, proxyData) {
579
+ const prefsPath = path.join(profileDir, 'Default', 'Preferences');
580
+ let preferences = {};
581
+ // Try to read existing preferences
582
+ try {
583
+ if (await fs.pathExists(prefsPath)) {
584
+ const prefs = await fs.readJSON(prefsPath);
585
+ preferences = prefs;
586
+ }
587
+ }
588
+ catch (error) {
589
+ // If file doesn't exist or is invalid, create new preferences
590
+ preferences = {};
591
+ }
592
+ // Set proxy configuration in preferences
593
+ const proxyServer = `${proxyData.type}://${proxyData.host}:${proxyData.port}`;
594
+ preferences.proxy = {
595
+ mode: 'fixed_servers',
596
+ server: proxyServer,
597
+ bypass_list: [],
598
+ };
599
+ // Set fingerprint-related preferences
600
+ if (!preferences.profile) {
601
+ preferences.profile = {};
602
+ }
603
+ // Set default page zoom
604
+ preferences.default_zoom_level = 0;
605
+ // Save preferences
606
+ await fs.writeJSON(prefsPath, preferences, { spaces: 2 });
607
+ }
608
+ async injectProxyAuthentication(context, debugPort, username, password) {
609
+ if (!username || !password) {
610
+ // No authentication needed
611
+ return;
612
+ }
613
+ try {
614
+ // Wait for browser to be fully ready
615
+ await new Promise((resolve) => setTimeout(resolve, 3000));
616
+ // Connect to Chrome DevTools Protocol
617
+ const client = await (0, chrome_remote_interface_1.default)({ port: debugPort });
618
+ try {
619
+ // Enable Network domain
620
+ await client.Network.enable();
621
+ // Create base64 encoded credentials
622
+ const credentials = Buffer.from(`${username}:${password}`).toString('base64');
623
+ const authHeader = `Basic ${credentials}`;
624
+ // Set extra HTTP headers with Proxy-Authorization
625
+ // This is the primary method - adds the header to all outgoing requests
626
+ await client.Network.setExtraHTTPHeaders({
627
+ headers: {
628
+ 'Proxy-Authorization': authHeader,
629
+ },
630
+ });
631
+ // Keep client reference to prevent garbage collection
632
+ // Store it in a way that keeps the connection alive
633
+ // The connection will be maintained as long as the browser is running
634
+ // Note: We don't close the client here because:
635
+ // 1. The connection needs to stay alive for the proxy auth to work
636
+ // 2. The browser process will clean up when it closes
637
+ // 3. Closing it immediately would stop the proxy authentication
638
+ }
639
+ catch (cdpError) {
640
+ // Try to close client if there's an error during setup
641
+ try {
642
+ await client.close();
643
+ }
644
+ catch {
645
+ // Ignore close errors
646
+ }
647
+ throw cdpError;
648
+ }
649
+ }
650
+ catch (error) {
651
+ // If CDP connection fails, throw error - proxy auth won't work without it
652
+ const errorMessage = error instanceof Error ? error.message : String(error);
653
+ throw new n8n_workflow_1.NodeApiError(context.getNode(), {
654
+ message: `Failed to inject proxy authentication via Chrome DevTools Protocol: ${errorMessage}. Make sure the browser is running and debug port ${debugPort} is accessible.`,
655
+ });
656
+ }
657
+ }
658
+ async findAvailablePort(context, startPort) {
659
+ for (let port = startPort; port < startPort + 100; port++) {
660
+ const isAvailable = await new Promise((resolve) => {
661
+ const server = net.createServer();
662
+ server.listen(port, () => {
663
+ server.close(() => resolve(true));
664
+ });
665
+ server.on('error', () => resolve(false));
666
+ });
667
+ if (isAvailable) {
668
+ return port;
669
+ }
670
+ }
671
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Could not find available port for debugging. Please try again or check if ports are in use.');
672
+ }
673
+ }
674
+ exports.BrowserProfile = BrowserProfile;
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="2" y="4" width="20" height="16" rx="2"/>
3
+ <path d="M2 8h20"/>
4
+ <circle cx="6" cy="6" r="1" fill="currentColor"/>
5
+ <circle cx="9" cy="6" r="1" fill="currentColor"/>
6
+ </svg>
7
+
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "n8n-nodes-dhd-browser",
3
+ "version": "1.0.0",
4
+ "description": "n8n node for creating and launching browser profiles with automatic proxy authentication",
5
+ "keywords": [
6
+ "n8n-community-node-package",
7
+ "browser",
8
+ "profile",
9
+ "proxy",
10
+ "chromium",
11
+ "automation"
12
+ ],
13
+ "license": "MIT",
14
+ "homepage": "",
15
+ "author": {
16
+ "name": "DHD",
17
+ "email": ""
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": ""
22
+ },
23
+ "main": "dist/BrowserProfile.node.js",
24
+ "scripts": {
25
+ "build": "tsc && gulp && gulp move:files",
26
+ "dev": "tsc --watch",
27
+ "format": "prettier nodes credentials --write",
28
+ "lint": "eslint nodes package.json --ignore-pattern credentials",
29
+ "lintfix": "eslint nodes package.json --ignore-pattern credentials --fix",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "n8n": {
36
+ "n8nNodesApiVersion": 1,
37
+ "nodes": [
38
+ "dist/nodes/BrowserProfile/BrowserProfile.node.js"
39
+ ]
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/node": "^20.10.0",
44
+ "@types/uuid": "^9.0.8",
45
+ "@typescript-eslint/eslint-plugin": "^6.15.0",
46
+ "@typescript-eslint/parser": "^6.15.0",
47
+ "eslint-plugin-n8n-nodes-base": "~1.12.0",
48
+ "gulp": "^4.0.2",
49
+ "n8n-workflow": "*",
50
+ "prettier": "^3.1.1",
51
+ "typescript": "~5.3.2"
52
+ },
53
+ "dependencies": {
54
+ "chrome-remote-interface": "^0.33.0",
55
+ "fs-extra": "^11.2.0",
56
+ "puppeteer-core": "^22.8.1",
57
+ "uuid": "^9.0.1"
58
+ }
59
+ }