n8n-nodes-nvk-browser 1.0.10 → 1.0.11

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 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.11",
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,8 @@
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"
43
44
  ]
44
45
  },
45
46
  "devDependencies": {