n8n-nodes-nvk-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/LICENSE +22 -0
- package/README.md +76 -0
- package/dist/nodes/PageInteraction/MoveAndClick/MoveAndClick.description.d.ts +2 -0
- package/dist/nodes/PageInteraction/MoveAndClick/MoveAndClick.description.js +132 -0
- package/dist/nodes/PageInteraction/MoveAndClick/MoveAndClick.node.d.ts +5 -0
- package/dist/nodes/PageInteraction/MoveAndClick/MoveAndClick.node.js +176 -0
- package/dist/nodes/PageInteraction/MoveAndClick/click.svg +5 -0
- package/dist/nodes/PageInteraction/MoveAndClick/code.svg +5 -0
- package/dist/nodes/PageInteraction/MoveAndClick/profile.svg +5 -0
- package/dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.description.d.ts +2 -0
- package/dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.description.js +62 -0
- package/dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.node.d.ts +5 -0
- package/dist/nodes/PageInteraction/RunJavaScript/RunJavaScript.node.js +126 -0
- package/dist/nodes/PageInteraction/RunJavaScript/click.svg +5 -0
- package/dist/nodes/PageInteraction/RunJavaScript/code.svg +5 -0
- package/dist/nodes/PageInteraction/RunJavaScript/profile.svg +5 -0
- package/dist/nodes/ProfileManagement/CreateProfile/CreateProfile.description.d.ts +2 -0
- package/dist/nodes/ProfileManagement/CreateProfile/CreateProfile.description.js +61 -0
- package/dist/nodes/ProfileManagement/CreateProfile/CreateProfile.node.d.ts +5 -0
- package/dist/nodes/ProfileManagement/CreateProfile/CreateProfile.node.js +124 -0
- package/dist/nodes/ProfileManagement/CreateProfile/click.svg +5 -0
- package/dist/nodes/ProfileManagement/CreateProfile/code.svg +5 -0
- package/dist/nodes/ProfileManagement/CreateProfile/profile.svg +5 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/DeleteProfile.description.d.ts +2 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/DeleteProfile.description.js +19 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/DeleteProfile.node.d.ts +5 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/DeleteProfile.node.js +123 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/click.svg +5 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/code.svg +5 -0
- package/dist/nodes/ProfileManagement/DeleteProfile/profile.svg +5 -0
- package/dist/nodes/ProfileManagement/StartProfile/StartProfile.description.d.ts +2 -0
- package/dist/nodes/ProfileManagement/StartProfile/StartProfile.description.js +122 -0
- package/dist/nodes/ProfileManagement/StartProfile/StartProfile.node.d.ts +5 -0
- package/dist/nodes/ProfileManagement/StartProfile/StartProfile.node.js +140 -0
- package/dist/nodes/ProfileManagement/StartProfile/click.svg +5 -0
- package/dist/nodes/ProfileManagement/StartProfile/code.svg +5 -0
- package/dist/nodes/ProfileManagement/StartProfile/profile.svg +5 -0
- package/dist/nodes/ProfileManagement/StopProfile/StopProfile.description.d.ts +2 -0
- package/dist/nodes/ProfileManagement/StopProfile/StopProfile.description.js +19 -0
- package/dist/nodes/ProfileManagement/StopProfile/StopProfile.node.d.ts +5 -0
- package/dist/nodes/ProfileManagement/StopProfile/StopProfile.node.js +115 -0
- package/dist/nodes/ProfileManagement/StopProfile/click.svg +5 -0
- package/dist/nodes/ProfileManagement/StopProfile/code.svg +5 -0
- package/dist/nodes/ProfileManagement/StopProfile/profile.svg +5 -0
- package/dist/utils/BrowserManager.d.ts +18 -0
- package/dist/utils/BrowserManager.js +174 -0
- package/dist/utils/ExtensionHandler.d.ts +9 -0
- package/dist/utils/ExtensionHandler.js +86 -0
- package/dist/utils/ProfileManager.d.ts +14 -0
- package/dist/utils/ProfileManager.js +128 -0
- package/dist/utils/ProxyHandler.d.ts +5 -0
- package/dist/utils/ProxyHandler.js +50 -0
- package/dist/utils/types.d.ts +47 -0
- package/dist/utils/types.js +2 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# n8n-nodes-nvk-browser
|
|
2
|
+
|
|
3
|
+
n8n custom nodes for managing Chrome browser profiles and page interactions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Profile Management
|
|
8
|
+
- **Create Profile**: Create new browser profiles with proxy, extensions, and notes
|
|
9
|
+
- **Delete Profile**: Delete existing profiles
|
|
10
|
+
- **Start Profile**: Launch Chrome with a specific profile
|
|
11
|
+
- **Stop Profile**: Stop a running browser instance
|
|
12
|
+
|
|
13
|
+
### Page Interaction
|
|
14
|
+
- **Move and Click**: Click on page elements using different methods (GhostCursor, Puppeteer, JavaScript)
|
|
15
|
+
- **Run JavaScript**: Execute JavaScript code in browser tabs
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
1. Install dependencies:
|
|
20
|
+
```bash
|
|
21
|
+
npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. Build the package:
|
|
25
|
+
```bash
|
|
26
|
+
npm run build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. Copy the `dist` folder to your n8n custom nodes directory:
|
|
30
|
+
```bash
|
|
31
|
+
cp -r dist /path/to/n8n/custom/nodes/
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Set the following environment variables (optional, defaults are provided):
|
|
37
|
+
|
|
38
|
+
- `NVK_BROWSER_PATH`: Path to Chrome executable (default: `browser-142/chrome.exe`)
|
|
39
|
+
- `NVK_PROFILES_DIR`: Path to profiles directory (default: `profiles`)
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Create Profile
|
|
44
|
+
1. Add "Create Profile" node to your workflow
|
|
45
|
+
2. Enter Profile Name (required)
|
|
46
|
+
3. Optionally add Proxy, Note, and Extensions
|
|
47
|
+
4. Execute to create a new profile
|
|
48
|
+
|
|
49
|
+
### Start Profile
|
|
50
|
+
1. Add "Start Profile" node
|
|
51
|
+
2. Enter Profile ID (required)
|
|
52
|
+
3. Configure window settings (scale, position, size, headless mode)
|
|
53
|
+
4. Execute to launch Chrome with the profile
|
|
54
|
+
|
|
55
|
+
### Move and Click
|
|
56
|
+
1. Add "Move and Click" node
|
|
57
|
+
2. Enter Profile ID and CSS Selector
|
|
58
|
+
3. Choose click method (GhostCursor, Puppeteer, or JavaScript)
|
|
59
|
+
4. Configure additional options if using JavaScript click
|
|
60
|
+
|
|
61
|
+
### Run JavaScript
|
|
62
|
+
1. Add "Run JavaScript" node
|
|
63
|
+
2. Enter Profile ID and JavaScript code
|
|
64
|
+
3. Specify tab index if needed
|
|
65
|
+
4. Execute to run the code
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
- Node.js 18+
|
|
70
|
+
- n8n
|
|
71
|
+
- Chrome browser (included in `browser-142` folder)
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
76
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moveAndClickFields = void 0;
|
|
4
|
+
exports.moveAndClickFields = [
|
|
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: ['moveAndClick'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Selector',
|
|
21
|
+
name: 'selector',
|
|
22
|
+
type: 'string',
|
|
23
|
+
required: true,
|
|
24
|
+
default: '',
|
|
25
|
+
description: 'CSS selector of the element to click',
|
|
26
|
+
displayOptions: {
|
|
27
|
+
show: {
|
|
28
|
+
resource: ['page'],
|
|
29
|
+
operation: ['moveAndClick'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
displayName: 'Click Method',
|
|
35
|
+
name: 'clickMethod',
|
|
36
|
+
type: 'options',
|
|
37
|
+
options: [
|
|
38
|
+
{
|
|
39
|
+
name: 'Use GhostCursor',
|
|
40
|
+
value: 'ghostcursor',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'Use Puppeteer',
|
|
44
|
+
value: 'puppeteer',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Use Javascript Click',
|
|
48
|
+
value: 'javascript',
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
default: 'puppeteer',
|
|
52
|
+
description: 'Method to use for clicking',
|
|
53
|
+
displayOptions: {
|
|
54
|
+
show: {
|
|
55
|
+
resource: ['page'],
|
|
56
|
+
operation: ['moveAndClick'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
displayName: 'Wait For Click (Milliseconds)',
|
|
62
|
+
name: 'waitForClick',
|
|
63
|
+
type: 'number',
|
|
64
|
+
default: 0,
|
|
65
|
+
description: 'Wait time before clicking in milliseconds',
|
|
66
|
+
displayOptions: {
|
|
67
|
+
show: {
|
|
68
|
+
resource: ['page'],
|
|
69
|
+
operation: ['moveAndClick'],
|
|
70
|
+
clickMethod: ['javascript'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
displayName: 'Button',
|
|
76
|
+
name: 'button',
|
|
77
|
+
type: 'options',
|
|
78
|
+
options: [
|
|
79
|
+
{
|
|
80
|
+
name: 'Left',
|
|
81
|
+
value: 'left',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Right',
|
|
85
|
+
value: 'right',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'Middle',
|
|
89
|
+
value: 'middle',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
default: 'left',
|
|
93
|
+
description: 'Mouse button to use',
|
|
94
|
+
displayOptions: {
|
|
95
|
+
show: {
|
|
96
|
+
resource: ['page'],
|
|
97
|
+
operation: ['moveAndClick'],
|
|
98
|
+
clickMethod: ['javascript'],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
displayName: 'Click Count',
|
|
104
|
+
name: 'clickCount',
|
|
105
|
+
type: 'number',
|
|
106
|
+
default: 1,
|
|
107
|
+
typeOptions: {
|
|
108
|
+
minValue: 1,
|
|
109
|
+
},
|
|
110
|
+
description: 'Number of times to click',
|
|
111
|
+
displayOptions: {
|
|
112
|
+
show: {
|
|
113
|
+
resource: ['page'],
|
|
114
|
+
operation: ['moveAndClick'],
|
|
115
|
+
clickMethod: ['javascript'],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
displayName: 'Tab Index',
|
|
121
|
+
name: 'tabIndex',
|
|
122
|
+
type: 'number',
|
|
123
|
+
default: 0,
|
|
124
|
+
description: 'Index of the tab to interact with (0 = first tab)',
|
|
125
|
+
displayOptions: {
|
|
126
|
+
show: {
|
|
127
|
+
resource: ['page'],
|
|
128
|
+
operation: ['moveAndClick'],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
];
|
|
@@ -0,0 +1,176 @@
|
|
|
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.MoveAndClick = void 0;
|
|
27
|
+
const BrowserManager_1 = require("../../../utils/BrowserManager");
|
|
28
|
+
const MoveAndClick_description_1 = require("./MoveAndClick.description");
|
|
29
|
+
const path = __importStar(require("path"));
|
|
30
|
+
class MoveAndClick {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.description = {
|
|
33
|
+
displayName: 'Move and Click',
|
|
34
|
+
name: 'moveAndClick',
|
|
35
|
+
icon: 'file:click.svg',
|
|
36
|
+
group: ['transform'],
|
|
37
|
+
version: 1,
|
|
38
|
+
description: 'Move mouse and click on an element',
|
|
39
|
+
defaults: {
|
|
40
|
+
name: 'Move and Click',
|
|
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: 'Move and Click',
|
|
66
|
+
value: 'moveAndClick',
|
|
67
|
+
description: 'Move and click on an element',
|
|
68
|
+
action: 'Move and click',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
default: 'moveAndClick',
|
|
72
|
+
},
|
|
73
|
+
...MoveAndClick_description_1.moveAndClickFields,
|
|
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
|
+
BrowserManager_1.BrowserManager.resetInstance(); // Reset để đảm bảo dùng đúng paths
|
|
90
|
+
const browserManager = BrowserManager_1.BrowserManager.getInstance(resolvedBrowserPath, resolvedProfilesDir);
|
|
91
|
+
for (let i = 0; i < items.length; i++) {
|
|
92
|
+
try {
|
|
93
|
+
const profileId = this.getNodeParameter('profileId', i);
|
|
94
|
+
const selector = this.getNodeParameter('selector', i);
|
|
95
|
+
const clickMethod = this.getNodeParameter('clickMethod', i) || 'puppeteer';
|
|
96
|
+
const waitForClick = this.getNodeParameter('waitForClick', i) || 0;
|
|
97
|
+
const button = this.getNodeParameter('button', i) || 'left';
|
|
98
|
+
const clickCount = this.getNodeParameter('clickCount', i) || 1;
|
|
99
|
+
const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
|
|
100
|
+
const instance = browserManager.getInstance(profileId);
|
|
101
|
+
if (!instance) {
|
|
102
|
+
throw new Error(`Profile ${profileId} is not running`);
|
|
103
|
+
}
|
|
104
|
+
const page = await browserManager.getPage(profileId, tabIndex);
|
|
105
|
+
if (!page) {
|
|
106
|
+
throw new Error(`Could not get page for profile ${profileId}`);
|
|
107
|
+
}
|
|
108
|
+
// Wait for element if needed
|
|
109
|
+
await page.waitForSelector(selector, { timeout: 30000 });
|
|
110
|
+
if (clickMethod === 'javascript') {
|
|
111
|
+
// Wait before clicking
|
|
112
|
+
if (waitForClick > 0) {
|
|
113
|
+
await page.waitForTimeout(waitForClick);
|
|
114
|
+
}
|
|
115
|
+
// JavaScript click
|
|
116
|
+
const buttonMap = {
|
|
117
|
+
left: 0,
|
|
118
|
+
middle: 1,
|
|
119
|
+
right: 2,
|
|
120
|
+
};
|
|
121
|
+
await page.evaluate((sel, btn, count) => {
|
|
122
|
+
// This code runs in browser context, so DOM APIs are available
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
124
|
+
const element = document.querySelector(sel);
|
|
125
|
+
if (element) {
|
|
126
|
+
for (let i = 0; i < count; i++) {
|
|
127
|
+
const event = new MouseEvent('click', {
|
|
128
|
+
view: window,
|
|
129
|
+
bubbles: true,
|
|
130
|
+
cancelable: true,
|
|
131
|
+
button: btn,
|
|
132
|
+
});
|
|
133
|
+
element.dispatchEvent(event);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, selector, buttonMap[button] || 0, clickCount);
|
|
137
|
+
}
|
|
138
|
+
else if (clickMethod === 'puppeteer') {
|
|
139
|
+
// Puppeteer native click
|
|
140
|
+
await page.click(selector);
|
|
141
|
+
}
|
|
142
|
+
else if (clickMethod === 'ghostcursor') {
|
|
143
|
+
// GhostCursor simulation (simplified - would need ghost-cursor package)
|
|
144
|
+
const element = await page.$(selector);
|
|
145
|
+
if (element) {
|
|
146
|
+
const box = await element.boundingBox();
|
|
147
|
+
if (box) {
|
|
148
|
+
// Move mouse to center of element
|
|
149
|
+
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
150
|
+
await page.waitForTimeout(100);
|
|
151
|
+
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
returnData.push({
|
|
156
|
+
json: {
|
|
157
|
+
success: true,
|
|
158
|
+
selector,
|
|
159
|
+
method: clickMethod,
|
|
160
|
+
message: 'Click performed successfully',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
returnData.push({
|
|
166
|
+
json: {
|
|
167
|
+
success: false,
|
|
168
|
+
error: error instanceof Error ? error.message : String(error),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return [returnData];
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.MoveAndClick = MoveAndClick;
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<path d="M9 9l5 12 1.774-5.226L21 14 9 9z"></path>
|
|
3
|
+
<path d="M21 3l-3.5 3.5"></path>
|
|
4
|
+
</svg>
|
|
5
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<polyline points="16 18 22 12 16 6"></polyline>
|
|
3
|
+
<polyline points="8 6 2 12 8 18"></polyline>
|
|
4
|
+
</svg>
|
|
5
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
|
3
|
+
<circle cx="12" cy="7" r="4"></circle>
|
|
4
|
+
</svg>
|
|
5
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runJavaScriptFields = void 0;
|
|
4
|
+
exports.runJavaScriptFields = [
|
|
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: ['runJavaScript'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
displayName: 'Debug Port',
|
|
21
|
+
name: 'debugPort',
|
|
22
|
+
type: 'number',
|
|
23
|
+
default: 0,
|
|
24
|
+
description: 'Debug port (0 = use default from instance)',
|
|
25
|
+
displayOptions: {
|
|
26
|
+
show: {
|
|
27
|
+
resource: ['page'],
|
|
28
|
+
operation: ['runJavaScript'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
displayName: 'Tab Index',
|
|
34
|
+
name: 'tabIndex',
|
|
35
|
+
type: 'number',
|
|
36
|
+
default: 0,
|
|
37
|
+
description: 'Index of the tab to run JavaScript on (0 = first tab)',
|
|
38
|
+
displayOptions: {
|
|
39
|
+
show: {
|
|
40
|
+
resource: ['page'],
|
|
41
|
+
operation: ['runJavaScript'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
displayName: 'JavaScript Code',
|
|
47
|
+
name: 'javascriptCode',
|
|
48
|
+
type: 'string',
|
|
49
|
+
typeOptions: {
|
|
50
|
+
rows: 10,
|
|
51
|
+
},
|
|
52
|
+
required: true,
|
|
53
|
+
default: '',
|
|
54
|
+
description: 'JavaScript code to execute',
|
|
55
|
+
displayOptions: {
|
|
56
|
+
show: {
|
|
57
|
+
resource: ['page'],
|
|
58
|
+
operation: ['runJavaScript'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class RunJavaScript implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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.RunJavaScript = void 0;
|
|
27
|
+
const BrowserManager_1 = require("../../../utils/BrowserManager");
|
|
28
|
+
const RunJavaScript_description_1 = require("./RunJavaScript.description");
|
|
29
|
+
const path = __importStar(require("path"));
|
|
30
|
+
class RunJavaScript {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.description = {
|
|
33
|
+
displayName: 'Run JavaScript',
|
|
34
|
+
name: 'runJavaScript',
|
|
35
|
+
icon: 'file:code.svg',
|
|
36
|
+
group: ['transform'],
|
|
37
|
+
version: 1,
|
|
38
|
+
description: 'Execute JavaScript code in the browser',
|
|
39
|
+
defaults: {
|
|
40
|
+
name: 'Run JavaScript',
|
|
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: 'Run JavaScript',
|
|
66
|
+
value: 'runJavaScript',
|
|
67
|
+
description: 'Execute JavaScript code',
|
|
68
|
+
action: 'Run JavaScript',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
default: 'runJavaScript',
|
|
72
|
+
},
|
|
73
|
+
...RunJavaScript_description_1.runJavaScriptFields,
|
|
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
|
+
BrowserManager_1.BrowserManager.resetInstance(); // Reset để đảm bảo dùng đúng paths
|
|
90
|
+
const browserManager = BrowserManager_1.BrowserManager.getInstance(resolvedBrowserPath, resolvedProfilesDir);
|
|
91
|
+
for (let i = 0; i < items.length; i++) {
|
|
92
|
+
try {
|
|
93
|
+
const profileId = this.getNodeParameter('profileId', i);
|
|
94
|
+
const tabIndex = this.getNodeParameter('tabIndex', i) || 0;
|
|
95
|
+
const javascriptCode = this.getNodeParameter('javascriptCode', i);
|
|
96
|
+
const instance = browserManager.getInstance(profileId);
|
|
97
|
+
if (!instance) {
|
|
98
|
+
throw new Error(`Profile ${profileId} is not running`);
|
|
99
|
+
}
|
|
100
|
+
const page = await browserManager.getPage(profileId, tabIndex);
|
|
101
|
+
if (!page) {
|
|
102
|
+
throw new Error(`Could not get page for profile ${profileId}`);
|
|
103
|
+
}
|
|
104
|
+
// Execute JavaScript
|
|
105
|
+
const result = await page.evaluate(javascriptCode);
|
|
106
|
+
returnData.push({
|
|
107
|
+
json: {
|
|
108
|
+
success: true,
|
|
109
|
+
result: result,
|
|
110
|
+
message: 'JavaScript executed successfully',
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
returnData.push({
|
|
116
|
+
json: {
|
|
117
|
+
success: false,
|
|
118
|
+
error: error instanceof Error ? error.message : String(error),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return [returnData];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
exports.RunJavaScript = RunJavaScript;
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<path d="M9 9l5 12 1.774-5.226L21 14 9 9z"></path>
|
|
3
|
+
<path d="M21 3l-3.5 3.5"></path>
|
|
4
|
+
</svg>
|
|
5
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<polyline points="16 18 22 12 16 6"></polyline>
|
|
3
|
+
<polyline points="8 6 2 12 8 18"></polyline>
|
|
4
|
+
</svg>
|
|
5
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
|
3
|
+
<circle cx="12" cy="7" r="4"></circle>
|
|
4
|
+
</svg>
|
|
5
|
+
|