n8n-nodes-nvk-browser 1.0.10 → 1.0.12

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.
@@ -0,0 +1,2 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+ export declare const browserHttpRequestFields: INodeProperties[];
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.browserHttpRequestFields = void 0;
4
+ exports.browserHttpRequestFields = [
5
+ {
6
+ displayName: 'Profile ID',
7
+ name: 'profileId',
8
+ type: 'string',
9
+ required: true,
10
+ default: '',
11
+ description: 'ID of the running profile to use for cookies and headers',
12
+ displayOptions: {
13
+ show: {
14
+ resource: ['page'],
15
+ operation: ['browserHttpRequest'],
16
+ },
17
+ },
18
+ },
19
+ {
20
+ displayName: 'Method',
21
+ name: 'method',
22
+ type: 'options',
23
+ options: [
24
+ {
25
+ name: 'GET',
26
+ value: 'GET',
27
+ },
28
+ {
29
+ name: 'POST',
30
+ value: 'POST',
31
+ },
32
+ ],
33
+ default: 'GET',
34
+ description: 'HTTP method to use',
35
+ displayOptions: {
36
+ show: {
37
+ resource: ['page'],
38
+ operation: ['browserHttpRequest'],
39
+ },
40
+ },
41
+ },
42
+ {
43
+ displayName: 'URL',
44
+ name: 'url',
45
+ type: 'string',
46
+ required: true,
47
+ default: '',
48
+ description: 'The URL to make the request to',
49
+ displayOptions: {
50
+ show: {
51
+ resource: ['page'],
52
+ operation: ['browserHttpRequest'],
53
+ },
54
+ },
55
+ },
56
+ {
57
+ displayName: 'Send Body',
58
+ name: 'sendBody',
59
+ type: 'boolean',
60
+ default: false,
61
+ description: 'Whether to send a body in the request',
62
+ displayOptions: {
63
+ show: {
64
+ resource: ['page'],
65
+ operation: ['browserHttpRequest'],
66
+ method: ['POST'],
67
+ },
68
+ },
69
+ },
70
+ {
71
+ displayName: 'Body Content Type',
72
+ name: 'bodyContentType',
73
+ type: 'options',
74
+ options: [
75
+ {
76
+ name: 'JSON',
77
+ value: 'json',
78
+ },
79
+ {
80
+ name: 'Raw',
81
+ value: 'raw',
82
+ },
83
+ {
84
+ name: 'Form Data',
85
+ value: 'formData',
86
+ },
87
+ ],
88
+ default: 'json',
89
+ description: 'Content type of the body',
90
+ displayOptions: {
91
+ show: {
92
+ resource: ['page'],
93
+ operation: ['browserHttpRequest'],
94
+ method: ['POST'],
95
+ sendBody: [true],
96
+ },
97
+ },
98
+ },
99
+ {
100
+ displayName: 'JSON Body',
101
+ name: 'jsonBody',
102
+ type: 'string',
103
+ typeOptions: {
104
+ rows: 4,
105
+ },
106
+ default: '',
107
+ description: 'JSON body to send',
108
+ displayOptions: {
109
+ show: {
110
+ resource: ['page'],
111
+ operation: ['browserHttpRequest'],
112
+ method: ['POST'],
113
+ sendBody: [true],
114
+ bodyContentType: ['json'],
115
+ },
116
+ },
117
+ },
118
+ {
119
+ displayName: 'Raw Body',
120
+ name: 'rawBody',
121
+ type: 'string',
122
+ typeOptions: {
123
+ rows: 4,
124
+ },
125
+ default: '',
126
+ description: 'Raw body to send',
127
+ displayOptions: {
128
+ show: {
129
+ resource: ['page'],
130
+ operation: ['browserHttpRequest'],
131
+ method: ['POST'],
132
+ sendBody: [true],
133
+ bodyContentType: ['raw'],
134
+ },
135
+ },
136
+ },
137
+ {
138
+ displayName: 'Form Data',
139
+ name: 'formData',
140
+ type: 'fixedCollection',
141
+ typeOptions: {
142
+ multipleValues: true,
143
+ },
144
+ default: {},
145
+ description: 'Form data to send',
146
+ displayOptions: {
147
+ show: {
148
+ resource: ['page'],
149
+ operation: ['browserHttpRequest'],
150
+ method: ['POST'],
151
+ sendBody: [true],
152
+ bodyContentType: ['formData'],
153
+ },
154
+ },
155
+ options: [
156
+ {
157
+ displayName: 'Parameter',
158
+ name: 'parameter',
159
+ values: [
160
+ {
161
+ displayName: 'Name',
162
+ name: 'name',
163
+ type: 'string',
164
+ default: '',
165
+ },
166
+ {
167
+ displayName: 'Value',
168
+ name: 'value',
169
+ type: 'string',
170
+ default: '',
171
+ },
172
+ ],
173
+ },
174
+ ],
175
+ },
176
+ {
177
+ displayName: 'Query Parameters',
178
+ name: 'queryParameters',
179
+ type: 'fixedCollection',
180
+ typeOptions: {
181
+ multipleValues: true,
182
+ },
183
+ default: {},
184
+ description: 'Query parameters to send',
185
+ displayOptions: {
186
+ show: {
187
+ resource: ['page'],
188
+ operation: ['browserHttpRequest'],
189
+ },
190
+ },
191
+ options: [
192
+ {
193
+ displayName: 'Parameter',
194
+ name: 'parameter',
195
+ values: [
196
+ {
197
+ displayName: 'Name',
198
+ name: 'name',
199
+ type: 'string',
200
+ default: '',
201
+ },
202
+ {
203
+ displayName: 'Value',
204
+ name: 'value',
205
+ type: 'string',
206
+ default: '',
207
+ },
208
+ ],
209
+ },
210
+ ],
211
+ },
212
+ {
213
+ displayName: 'Additional Headers',
214
+ name: 'additionalHeaders',
215
+ type: 'fixedCollection',
216
+ typeOptions: {
217
+ multipleValues: true,
218
+ },
219
+ default: {},
220
+ description: 'Additional headers to send (will be merged with browser headers)',
221
+ displayOptions: {
222
+ show: {
223
+ resource: ['page'],
224
+ operation: ['browserHttpRequest'],
225
+ },
226
+ },
227
+ options: [
228
+ {
229
+ displayName: 'Header',
230
+ name: 'header',
231
+ values: [
232
+ {
233
+ displayName: 'Name',
234
+ name: 'name',
235
+ type: 'string',
236
+ default: '',
237
+ },
238
+ {
239
+ displayName: 'Value',
240
+ name: 'value',
241
+ type: 'string',
242
+ default: '',
243
+ },
244
+ ],
245
+ },
246
+ ],
247
+ },
248
+ {
249
+ displayName: 'Tab Index',
250
+ name: 'tabIndex',
251
+ type: 'number',
252
+ default: 0,
253
+ description: 'Index of the tab to get cookies and headers from (0 = first tab)',
254
+ displayOptions: {
255
+ show: {
256
+ resource: ['page'],
257
+ operation: ['browserHttpRequest'],
258
+ },
259
+ },
260
+ },
261
+ {
262
+ displayName: 'Auto Start Profile',
263
+ name: 'autoStart',
264
+ type: 'boolean',
265
+ default: false,
266
+ description: 'Automatically start the profile if it is not running',
267
+ displayOptions: {
268
+ show: {
269
+ resource: ['page'],
270
+ operation: ['browserHttpRequest'],
271
+ },
272
+ },
273
+ },
274
+ ];
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class BrowserHttpRequest implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,244 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.BrowserHttpRequest = void 0;
27
+ const BrowserManager_1 = require("../../../utils/BrowserManager");
28
+ const BrowserHttpRequest_description_1 = require("./BrowserHttpRequest.description");
29
+ const path = __importStar(require("path"));
30
+ class BrowserHttpRequest {
31
+ constructor() {
32
+ this.description = {
33
+ displayName: 'Browser HTTP Request',
34
+ name: 'browserHttpRequest',
35
+ icon: 'file:http.svg',
36
+ group: ['transform'],
37
+ version: 1,
38
+ description: 'Make HTTP requests using browser profile cookies and headers to bypass Cloudflare',
39
+ defaults: {
40
+ name: 'Browser HTTP Request',
41
+ },
42
+ inputs: ['main'],
43
+ outputs: ['main'],
44
+ properties: [
45
+ {
46
+ displayName: 'Resource',
47
+ name: 'resource',
48
+ type: 'options',
49
+ noDataExpression: true,
50
+ options: [
51
+ {
52
+ name: 'Page',
53
+ value: 'page',
54
+ },
55
+ ],
56
+ default: 'page',
57
+ },
58
+ {
59
+ displayName: 'Operation',
60
+ name: 'operation',
61
+ type: 'options',
62
+ noDataExpression: true,
63
+ options: [
64
+ {
65
+ name: 'Browser HTTP Request',
66
+ value: 'browserHttpRequest',
67
+ description: 'Make HTTP request with browser cookies and headers',
68
+ action: 'Make HTTP request',
69
+ },
70
+ ],
71
+ default: 'browserHttpRequest',
72
+ },
73
+ ...BrowserHttpRequest_description_1.browserHttpRequestFields,
74
+ ],
75
+ };
76
+ }
77
+ async execute() {
78
+ const items = this.getInputData();
79
+ const returnData = [];
80
+ const workspacePath = process.cwd();
81
+ const profilesDir = process.env.NVK_PROFILES_DIR || path.join(workspacePath, 'profiles');
82
+ const browserPath = process.env.NVK_BROWSER_PATH || path.join(workspacePath, 'browser-142', 'chrome.exe');
83
+ const resolvedBrowserPath = path.isAbsolute(browserPath)
84
+ ? browserPath
85
+ : path.resolve(workspacePath, browserPath);
86
+ const resolvedProfilesDir = path.isAbsolute(profilesDir)
87
+ ? profilesDir
88
+ : path.resolve(workspacePath, profilesDir);
89
+ const browserManager = BrowserManager_1.BrowserManager.getInstance(resolvedBrowserPath, resolvedProfilesDir);
90
+ for (let i = 0; i < items.length; i++) {
91
+ try {
92
+ const profileId = this.getNodeParameter('profileId', i);
93
+ const method = this.getNodeParameter('method', i) || 'GET';
94
+ const url = this.getNodeParameter('url', i);
95
+ const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
96
+ const autoStart = this.getNodeParameter('autoStart', i) || false;
97
+ let instance = browserManager.getInstance(profileId);
98
+ // Auto start profile nếu chưa chạy và option được bật
99
+ if (!instance && autoStart) {
100
+ instance = await browserManager.startProfileIfNotRunning(profileId);
101
+ }
102
+ if (!instance) {
103
+ throw new Error(`Profile ${profileId} is not running. Enable "Auto Start Profile" to start it automatically.`);
104
+ }
105
+ const page = await browserManager.getPage(profileId, tabIndex);
106
+ if (!page) {
107
+ throw new Error(`Could not get page for profile ${profileId}`);
108
+ }
109
+ // Get cookies from browser
110
+ const cookies = await page.cookies();
111
+ const cookieString = cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ');
112
+ // Get user agent from browser
113
+ const userAgent = await page.evaluate(() => navigator.userAgent);
114
+ // Get additional headers from browser context
115
+ const browserHeaders = {
116
+ 'User-Agent': userAgent,
117
+ 'Accept': '*/*',
118
+ 'Accept-Language': 'en-US,en;q=0.9',
119
+ 'Accept-Encoding': 'gzip, deflate, br',
120
+ 'Connection': 'keep-alive',
121
+ 'Sec-Fetch-Dest': 'empty',
122
+ 'Sec-Fetch-Mode': 'cors',
123
+ 'Sec-Fetch-Site': 'same-origin',
124
+ 'Cookie': cookieString,
125
+ };
126
+ // Get additional headers from node parameters
127
+ const additionalHeadersParam = this.getNodeParameter('additionalHeaders', i);
128
+ if (additionalHeadersParam?.header) {
129
+ additionalHeadersParam.header.forEach((header) => {
130
+ if (header.name && header.value) {
131
+ browserHeaders[header.name] = header.value;
132
+ }
133
+ });
134
+ }
135
+ // Build URL with query parameters
136
+ const queryParametersParam = this.getNodeParameter('queryParameters', i);
137
+ let finalUrl = url;
138
+ if (queryParametersParam?.parameter && queryParametersParam.parameter.length > 0) {
139
+ const urlObj = new URL(url);
140
+ queryParametersParam.parameter.forEach((param) => {
141
+ if (param.name && param.value) {
142
+ urlObj.searchParams.append(param.name, param.value);
143
+ }
144
+ });
145
+ finalUrl = urlObj.toString();
146
+ }
147
+ // Prepare request body for POST
148
+ let body;
149
+ let contentType;
150
+ if (method === 'POST') {
151
+ const sendBody = this.getNodeParameter('sendBody', i);
152
+ if (sendBody) {
153
+ const bodyContentType = this.getNodeParameter('bodyContentType', i);
154
+ if (bodyContentType === 'json') {
155
+ const jsonBody = this.getNodeParameter('jsonBody', i);
156
+ body = jsonBody;
157
+ contentType = 'application/json';
158
+ }
159
+ else if (bodyContentType === 'raw') {
160
+ const rawBody = this.getNodeParameter('rawBody', i);
161
+ body = rawBody;
162
+ contentType = 'text/plain';
163
+ }
164
+ else if (bodyContentType === 'formData') {
165
+ const formDataParam = this.getNodeParameter('formData', i);
166
+ if (formDataParam?.parameter) {
167
+ const formData = new URLSearchParams();
168
+ formDataParam.parameter.forEach((param) => {
169
+ if (param.name && param.value) {
170
+ formData.append(param.name, param.value);
171
+ }
172
+ });
173
+ body = formData.toString();
174
+ contentType = 'application/x-www-form-urlencoded';
175
+ }
176
+ }
177
+ }
178
+ }
179
+ // Set Content-Type header if body is provided
180
+ if (contentType) {
181
+ browserHeaders['Content-Type'] = contentType;
182
+ }
183
+ // Make HTTP request using page.evaluate to use browser's fetch with all cookies and headers
184
+ const response = await page.evaluate(async (requestData) => {
185
+ const fetchOptions = {
186
+ method: requestData.method,
187
+ headers: requestData.headers,
188
+ };
189
+ if (requestData.body) {
190
+ fetchOptions.body = requestData.body;
191
+ }
192
+ const response = await fetch(requestData.url, fetchOptions);
193
+ // Get response headers
194
+ const responseHeaders = {};
195
+ response.headers.forEach((value, key) => {
196
+ responseHeaders[key] = value;
197
+ });
198
+ // Get response body
199
+ let responseBody;
200
+ const contentType = response.headers.get('content-type') || '';
201
+ if (contentType.includes('application/json')) {
202
+ responseBody = await response.json();
203
+ }
204
+ else {
205
+ responseBody = await response.text();
206
+ }
207
+ return {
208
+ status: response.status,
209
+ statusText: response.statusText,
210
+ headers: responseHeaders,
211
+ body: responseBody,
212
+ url: response.url,
213
+ };
214
+ }, {
215
+ url: finalUrl,
216
+ method,
217
+ headers: browserHeaders,
218
+ body,
219
+ });
220
+ returnData.push({
221
+ json: {
222
+ success: true,
223
+ statusCode: response.status,
224
+ statusMessage: response.statusText,
225
+ headers: response.headers,
226
+ body: response.body,
227
+ url: response.url,
228
+ message: 'Request completed successfully',
229
+ },
230
+ });
231
+ }
232
+ catch (error) {
233
+ returnData.push({
234
+ json: {
235
+ success: false,
236
+ error: error instanceof Error ? error.message : String(error),
237
+ },
238
+ });
239
+ }
240
+ }
241
+ return [returnData];
242
+ }
243
+ }
244
+ exports.BrowserHttpRequest = BrowserHttpRequest;
@@ -0,0 +1,2 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+ export declare const getNetworkResponseFields: INodeProperties[];
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getNetworkResponseFields = void 0;
4
+ exports.getNetworkResponseFields = [
5
+ {
6
+ displayName: 'Profile ID',
7
+ name: 'profileId',
8
+ type: 'string',
9
+ required: true,
10
+ default: '',
11
+ description: 'ID of the running profile',
12
+ displayOptions: {
13
+ show: {
14
+ resource: ['page'],
15
+ operation: ['getNetworkResponse'],
16
+ },
17
+ },
18
+ },
19
+ {
20
+ displayName: 'Request Name or URL',
21
+ name: 'requestFilter',
22
+ type: 'string',
23
+ required: true,
24
+ default: '',
25
+ description: 'Name of the request (e.g., "create") or URL pattern to match. Can be partial match.',
26
+ displayOptions: {
27
+ show: {
28
+ resource: ['page'],
29
+ operation: ['getNetworkResponse'],
30
+ },
31
+ },
32
+ },
33
+ {
34
+ displayName: 'Match Type',
35
+ name: 'matchType',
36
+ type: 'options',
37
+ options: [
38
+ {
39
+ name: 'Contains',
40
+ value: 'contains',
41
+ description: 'Request name or URL contains the filter text',
42
+ },
43
+ {
44
+ name: 'Exact',
45
+ value: 'exact',
46
+ description: 'Request name or URL exactly matches the filter text',
47
+ },
48
+ {
49
+ name: 'Starts With',
50
+ value: 'startsWith',
51
+ description: 'Request name or URL starts with the filter text',
52
+ },
53
+ {
54
+ name: 'Ends With',
55
+ value: 'endsWith',
56
+ description: 'Request name or URL ends with the filter text',
57
+ },
58
+ {
59
+ name: 'Regex',
60
+ value: 'regex',
61
+ description: 'Request name or URL matches the regex pattern',
62
+ },
63
+ ],
64
+ default: 'contains',
65
+ description: 'How to match the request',
66
+ displayOptions: {
67
+ show: {
68
+ resource: ['page'],
69
+ operation: ['getNetworkResponse'],
70
+ },
71
+ },
72
+ },
73
+ {
74
+ displayName: 'Timeout (Milliseconds)',
75
+ name: 'timeout',
76
+ type: 'number',
77
+ default: 30000,
78
+ description: 'Maximum time to wait for the request to appear (in milliseconds)',
79
+ displayOptions: {
80
+ show: {
81
+ resource: ['page'],
82
+ operation: ['getNetworkResponse'],
83
+ },
84
+ },
85
+ },
86
+ {
87
+ displayName: 'Wait For Request',
88
+ name: 'waitForRequest',
89
+ type: 'boolean',
90
+ default: true,
91
+ description: 'Wait for the request to appear if it has not been made yet',
92
+ displayOptions: {
93
+ show: {
94
+ resource: ['page'],
95
+ operation: ['getNetworkResponse'],
96
+ },
97
+ },
98
+ },
99
+ {
100
+ displayName: 'Tab Index',
101
+ name: 'tabIndex',
102
+ type: 'number',
103
+ default: 0,
104
+ description: 'Index of the tab to monitor (0 = first tab)',
105
+ displayOptions: {
106
+ show: {
107
+ resource: ['page'],
108
+ operation: ['getNetworkResponse'],
109
+ },
110
+ },
111
+ },
112
+ {
113
+ displayName: 'Auto Start Profile',
114
+ name: 'autoStart',
115
+ type: 'boolean',
116
+ default: false,
117
+ description: 'Automatically start the profile if it is not running',
118
+ displayOptions: {
119
+ show: {
120
+ resource: ['page'],
121
+ operation: ['getNetworkResponse'],
122
+ },
123
+ },
124
+ },
125
+ ];
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class GetNetworkResponse implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,265 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.GetNetworkResponse = void 0;
27
+ const BrowserManager_1 = require("../../../utils/BrowserManager");
28
+ const GetNetworkResponse_description_1 = require("./GetNetworkResponse.description");
29
+ const path = __importStar(require("path"));
30
+ class GetNetworkResponse {
31
+ constructor() {
32
+ this.description = {
33
+ displayName: 'Get Network Response',
34
+ name: 'getNetworkResponse',
35
+ icon: 'file:network.svg',
36
+ group: ['transform'],
37
+ version: 1,
38
+ description: 'Capture and return the response of a network request by name or URL',
39
+ defaults: {
40
+ name: 'Get Network Response',
41
+ },
42
+ inputs: ['main'],
43
+ outputs: ['main'],
44
+ properties: [
45
+ {
46
+ displayName: 'Resource',
47
+ name: 'resource',
48
+ type: 'options',
49
+ noDataExpression: true,
50
+ options: [
51
+ {
52
+ name: 'Page',
53
+ value: 'page',
54
+ },
55
+ ],
56
+ default: 'page',
57
+ },
58
+ {
59
+ displayName: 'Operation',
60
+ name: 'operation',
61
+ type: 'options',
62
+ noDataExpression: true,
63
+ options: [
64
+ {
65
+ name: 'Get Network Response',
66
+ value: 'getNetworkResponse',
67
+ description: 'Capture network request response',
68
+ action: 'Get network response',
69
+ },
70
+ ],
71
+ default: 'getNetworkResponse',
72
+ },
73
+ ...GetNetworkResponse_description_1.getNetworkResponseFields,
74
+ ],
75
+ };
76
+ }
77
+ async execute() {
78
+ const items = this.getInputData();
79
+ const returnData = [];
80
+ const workspacePath = process.cwd();
81
+ const profilesDir = process.env.NVK_PROFILES_DIR || path.join(workspacePath, 'profiles');
82
+ const browserPath = process.env.NVK_BROWSER_PATH || path.join(workspacePath, 'browser-142', 'chrome.exe');
83
+ const resolvedBrowserPath = path.isAbsolute(browserPath)
84
+ ? browserPath
85
+ : path.resolve(workspacePath, browserPath);
86
+ const resolvedProfilesDir = path.isAbsolute(profilesDir)
87
+ ? profilesDir
88
+ : path.resolve(workspacePath, profilesDir);
89
+ const browserManager = BrowserManager_1.BrowserManager.getInstance(resolvedBrowserPath, resolvedProfilesDir);
90
+ for (let i = 0; i < items.length; i++) {
91
+ try {
92
+ const profileId = this.getNodeParameter('profileId', i);
93
+ const requestFilter = this.getNodeParameter('requestFilter', i);
94
+ const matchType = this.getNodeParameter('matchType', i) || 'contains';
95
+ const timeout = this.getNodeParameter('timeout', i) || 30000;
96
+ const waitForRequest = this.getNodeParameter('waitForRequest', i) !== false;
97
+ const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
98
+ const autoStart = this.getNodeParameter('autoStart', i) || false;
99
+ let instance = browserManager.getInstance(profileId);
100
+ // Auto start profile nếu chưa chạy và option được bật
101
+ if (!instance && autoStart) {
102
+ instance = await browserManager.startProfileIfNotRunning(profileId);
103
+ }
104
+ if (!instance) {
105
+ throw new Error(`Profile ${profileId} is not running. Enable "Auto Start Profile" to start it automatically.`);
106
+ }
107
+ const page = await browserManager.getPage(profileId, tabIndex);
108
+ if (!page) {
109
+ throw new Error(`Could not get page for profile ${profileId}`);
110
+ }
111
+ // Enable network domain in CDP
112
+ const client = await page.target().createCDPSession();
113
+ await client.send('Network.enable');
114
+ // Function to match request
115
+ const matchesRequest = (requestUrl, requestName) => {
116
+ const searchText = requestFilter.toLowerCase();
117
+ const url = requestUrl.toLowerCase();
118
+ const name = requestName.toLowerCase();
119
+ switch (matchType) {
120
+ case 'exact':
121
+ return url === searchText || name === searchText;
122
+ case 'startsWith':
123
+ return url.startsWith(searchText) || name.startsWith(searchText);
124
+ case 'endsWith':
125
+ return url.endsWith(searchText) || name.endsWith(searchText);
126
+ case 'regex':
127
+ try {
128
+ const regex = new RegExp(requestFilter, 'i');
129
+ return regex.test(url) || regex.test(name);
130
+ }
131
+ catch (error) {
132
+ throw new Error(`Invalid regex pattern: ${requestFilter}`);
133
+ }
134
+ case 'contains':
135
+ default:
136
+ return url.includes(searchText) || name.includes(searchText);
137
+ }
138
+ };
139
+ // Store matched requests
140
+ const matchedRequests = new Map();
141
+ // Promise to capture the response
142
+ let capturedResponse = null;
143
+ const responsePromise = new Promise((resolve, reject) => {
144
+ const timeoutId = setTimeout(() => {
145
+ client.off('Network.responseReceived', responseHandler);
146
+ client.off('Network.loadingFinished', loadingHandler);
147
+ reject(new Error(`Timeout: Request matching "${requestFilter}" not found within ${timeout}ms`));
148
+ }, timeout);
149
+ const responseHandler = (event) => {
150
+ try {
151
+ const responseUrl = event.response.url || '';
152
+ const responseName = responseUrl.split('/').pop()?.split('?')[0] || '';
153
+ if (matchesRequest(responseUrl, responseName)) {
154
+ // Store response info, will get body when loading finishes
155
+ matchedRequests.set(event.requestId, {
156
+ url: responseUrl,
157
+ status: event.response.status,
158
+ statusText: event.response.statusText,
159
+ headers: event.response.headers,
160
+ mimeType: event.response.mimeType,
161
+ requestId: event.requestId,
162
+ timestamp: Date.now(),
163
+ });
164
+ }
165
+ }
166
+ catch (error) {
167
+ // Ignore errors in handler
168
+ }
169
+ };
170
+ const loadingHandler = async (event) => {
171
+ if (matchedRequests.has(event.requestId)) {
172
+ try {
173
+ const responseInfo = matchedRequests.get(event.requestId);
174
+ // Get response body
175
+ const responseBody = await client.send('Network.getResponseBody', {
176
+ requestId: event.requestId,
177
+ });
178
+ // Try to parse as JSON if possible
179
+ let parsedBody = responseBody.body;
180
+ try {
181
+ if (responseInfo.mimeType?.includes('application/json')) {
182
+ parsedBody = JSON.parse(responseBody.body);
183
+ }
184
+ }
185
+ catch {
186
+ // Not JSON or parse failed, keep as string
187
+ }
188
+ capturedResponse = {
189
+ ...responseInfo,
190
+ body: parsedBody,
191
+ bodyText: responseBody.body,
192
+ };
193
+ clearTimeout(timeoutId);
194
+ client.off('Network.responseReceived', responseHandler);
195
+ client.off('Network.loadingFinished', loadingHandler);
196
+ resolve();
197
+ }
198
+ catch (error) {
199
+ // Response body might not be available, try to resolve with what we have
200
+ if (matchedRequests.has(event.requestId)) {
201
+ capturedResponse = matchedRequests.get(event.requestId);
202
+ clearTimeout(timeoutId);
203
+ client.off('Network.responseReceived', responseHandler);
204
+ client.off('Network.loadingFinished', loadingHandler);
205
+ resolve();
206
+ }
207
+ }
208
+ }
209
+ };
210
+ client.on('Network.responseReceived', responseHandler);
211
+ client.on('Network.loadingFinished', loadingHandler);
212
+ });
213
+ if (waitForRequest) {
214
+ // Wait for the request to appear
215
+ await responsePromise;
216
+ }
217
+ else {
218
+ // Don't wait, just check if request already exists
219
+ // This is a simplified approach - in a real scenario, you might want to
220
+ // check existing network logs or use a different strategy
221
+ try {
222
+ await Promise.race([
223
+ responsePromise,
224
+ new Promise((resolve) => setTimeout(resolve, 1000)), // Quick check
225
+ ]);
226
+ }
227
+ catch {
228
+ // Request not found, that's okay if waitForRequest is false
229
+ }
230
+ }
231
+ await client.detach();
232
+ if (capturedResponse) {
233
+ returnData.push({
234
+ json: {
235
+ success: true,
236
+ response: capturedResponse,
237
+ message: 'Network response captured successfully',
238
+ },
239
+ });
240
+ }
241
+ else if (waitForRequest) {
242
+ throw new Error(`Request matching "${requestFilter}" not found`);
243
+ }
244
+ else {
245
+ returnData.push({
246
+ json: {
247
+ success: false,
248
+ message: `Request matching "${requestFilter}" not found (waitForRequest is disabled)`,
249
+ },
250
+ });
251
+ }
252
+ }
253
+ catch (error) {
254
+ returnData.push({
255
+ json: {
256
+ success: false,
257
+ error: error instanceof Error ? error.message : String(error),
258
+ },
259
+ });
260
+ }
261
+ }
262
+ return [returnData];
263
+ }
264
+ }
265
+ exports.GetNetworkResponse = GetNetworkResponse;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nvk-browser",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "n8n nodes for managing Chrome browser profiles and page interactions",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
@@ -39,7 +39,9 @@
39
39
  "dist/nodes/ProfileManagement/StartProfile/StartProfile.node.js",
40
40
  "dist/nodes/ProfileManagement/StopProfile/StopProfile.node.js",
41
41
  "dist/nodes/PageInteraction/MoveAndClick/MoveAndClick.node.js",
42
- "dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.node.js"
42
+ "dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.node.js",
43
+ "dist/nodes/PageInteraction/GetNetworkResponse/GetNetworkResponse.node.js",
44
+ "dist/nodes/PageInteraction/BrowserHttpRequest/BrowserHttpRequest.node.js"
43
45
  ]
44
46
  },
45
47
  "devDependencies": {