channel-worker 1.0.32 → 1.1.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/bin/cli.js +82 -13
- package/lib/api-client.js +6 -2
- package/lib/command-poller.js +5 -1
- package/lib/daemon.js +1 -1
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -37,35 +37,88 @@ function saveConfig(config) {
|
|
|
37
37
|
const args = parseArgs(process.argv.slice(2));
|
|
38
38
|
const cmd = args._cmd || 'start';
|
|
39
39
|
|
|
40
|
-
if (cmd === '
|
|
40
|
+
if (cmd === 'pair') {
|
|
41
|
+
// Pair with dashboard using one-time code
|
|
42
|
+
const code = args.code;
|
|
43
|
+
const apiUrl = args.api || 'https://api.channel.tunasm.art';
|
|
44
|
+
const nstKey = args['nst-key'] || '';
|
|
45
|
+
const extensionPath = args.extension || '';
|
|
46
|
+
const maxConcurrent = parseInt(args.concurrent || '2', 10);
|
|
47
|
+
|
|
48
|
+
if (!code) {
|
|
49
|
+
console.error('[channel-worker] Error: --code required. Get a pairing code from the dashboard.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
(async () => {
|
|
54
|
+
try {
|
|
55
|
+
console.log(`[channel-worker] Pairing with code: ${code}`);
|
|
56
|
+
const res = await fetch(`${apiUrl}/workers/pair`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
code,
|
|
61
|
+
ip_address: getLocalIP(),
|
|
62
|
+
max_concurrent: maxConcurrent,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
const json = await res.json();
|
|
66
|
+
|
|
67
|
+
if (!json.success) {
|
|
68
|
+
console.error(`[channel-worker] Pairing failed: ${json.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const config = {
|
|
73
|
+
worker_id: json.data.worker_id,
|
|
74
|
+
worker_token: json.data.worker_token,
|
|
75
|
+
api_url: apiUrl,
|
|
76
|
+
max_concurrent: maxConcurrent,
|
|
77
|
+
nst_api_key: nstKey,
|
|
78
|
+
extension_path: extensionPath,
|
|
79
|
+
};
|
|
80
|
+
saveConfig(config);
|
|
81
|
+
|
|
82
|
+
console.log(`[channel-worker] Paired successfully!`);
|
|
83
|
+
console.log(` Worker ID: ${config.worker_id}`);
|
|
84
|
+
console.log(` Config saved to: ${CONFIG_FILE}`);
|
|
85
|
+
console.log(`\nRun: channel-worker start`);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(`[channel-worker] Error: ${err.message}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
|
|
92
|
+
} else if (cmd === 'init') {
|
|
41
93
|
const config = {
|
|
42
94
|
worker_id: args.id || `worker-${os.hostname()}`,
|
|
43
95
|
api_url: args.api || 'http://localhost:3001',
|
|
44
96
|
max_concurrent: parseInt(args.concurrent || '2', 10),
|
|
45
97
|
nst_api_key: args['nst-key'] || '',
|
|
46
98
|
extension_path: args.extension || '',
|
|
99
|
+
worker_token: args.token || '',
|
|
47
100
|
};
|
|
48
101
|
saveConfig(config);
|
|
49
102
|
console.log(`[channel-worker] Config saved to ${CONFIG_FILE}`);
|
|
50
103
|
console.log(JSON.stringify(config, null, 2));
|
|
51
104
|
console.log('\nRun: channel-worker start');
|
|
52
105
|
process.exit(0);
|
|
53
|
-
}
|
|
54
106
|
|
|
55
|
-
if (cmd === 'start') {
|
|
107
|
+
} else if (cmd === 'start') {
|
|
56
108
|
// Merge CLI args over saved config
|
|
57
109
|
const saved = loadConfig();
|
|
58
110
|
const config = {
|
|
59
111
|
worker_id: args.id || saved.worker_id || `worker-${os.hostname()}`,
|
|
60
|
-
api_url: args.api || saved.api_url || '
|
|
112
|
+
api_url: args.api || saved.api_url || 'https://api.channel.tunasm.art',
|
|
61
113
|
max_concurrent: parseInt(args.concurrent || saved.max_concurrent || '2', 10),
|
|
62
114
|
nst_api_key: args['nst-key'] || saved.nst_api_key || '',
|
|
63
115
|
extension_path: args.extension || saved.extension_path || '',
|
|
116
|
+
worker_token: args.token || saved.worker_token || '',
|
|
64
117
|
verbose: !!args.verbose,
|
|
65
118
|
};
|
|
66
119
|
|
|
67
|
-
if (!config.
|
|
68
|
-
console.error('[channel-worker] Error:
|
|
120
|
+
if (!config.worker_token) {
|
|
121
|
+
console.error('[channel-worker] Error: No worker token. Run: channel-worker pair --code <CODE> --api <URL>');
|
|
69
122
|
process.exit(1);
|
|
70
123
|
}
|
|
71
124
|
|
|
@@ -78,28 +131,44 @@ if (cmd === 'start') {
|
|
|
78
131
|
|
|
79
132
|
} else if (cmd === 'config') {
|
|
80
133
|
const config = loadConfig();
|
|
81
|
-
|
|
134
|
+
// Hide token in display
|
|
135
|
+
const display = { ...config };
|
|
136
|
+
if (display.worker_token) display.worker_token = display.worker_token.slice(0, 8) + '...';
|
|
137
|
+
console.log(JSON.stringify(display, null, 2));
|
|
82
138
|
|
|
83
139
|
} else {
|
|
84
140
|
console.log(`
|
|
85
141
|
channel-worker — Channel Manager worker daemon
|
|
86
142
|
|
|
87
143
|
Commands:
|
|
88
|
-
|
|
144
|
+
pair Pair with dashboard using a one-time code (recommended)
|
|
145
|
+
init Configure worker manually
|
|
89
146
|
start Start the daemon
|
|
90
147
|
config Show current config
|
|
91
148
|
|
|
149
|
+
Pairing (recommended):
|
|
150
|
+
channel-worker pair --code <CODE> --api <URL>
|
|
151
|
+
|
|
92
152
|
Options:
|
|
93
|
-
--
|
|
94
|
-
--api <url>
|
|
95
|
-
--concurrent <n> Max concurrent
|
|
153
|
+
--code <code> Pairing code from dashboard (for pair command)
|
|
154
|
+
--api <url> API URL (default: https://api.channel.tunasm.art)
|
|
155
|
+
--concurrent <n> Max concurrent browsers (default: 2)
|
|
96
156
|
--nst-key <key> Nstbrowser API key
|
|
97
157
|
--extension <path> Path to content-creator extension
|
|
98
158
|
--verbose Enable verbose logging
|
|
99
159
|
|
|
100
160
|
Examples:
|
|
101
|
-
channel-worker
|
|
161
|
+
channel-worker pair --code A3F1B2 --api https://api.channel.tunasm.art
|
|
102
162
|
channel-worker start
|
|
103
|
-
channel-worker start --id win-worker --api http://192.168.1.52:3001
|
|
104
163
|
`);
|
|
105
164
|
}
|
|
165
|
+
|
|
166
|
+
function getLocalIP() {
|
|
167
|
+
const interfaces = os.networkInterfaces();
|
|
168
|
+
for (const name of Object.keys(interfaces)) {
|
|
169
|
+
for (const iface of interfaces[name]) {
|
|
170
|
+
if (iface.family === 'IPv4' && !iface.internal) return iface.address;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return '127.0.0.1';
|
|
174
|
+
}
|
package/lib/api-client.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
class ApiClient {
|
|
2
|
-
constructor(baseUrl) {
|
|
2
|
+
constructor(baseUrl, workerToken) {
|
|
3
3
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
4
|
+
this.workerToken = workerToken || '';
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
async request(method, path, body = null) {
|
|
7
8
|
const url = `${this.baseUrl}${path}`;
|
|
8
9
|
const options = {
|
|
9
10
|
method,
|
|
10
|
-
headers: {
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'x-worker-token': this.workerToken,
|
|
14
|
+
},
|
|
11
15
|
};
|
|
12
16
|
if (body) options.body = JSON.stringify(body);
|
|
13
17
|
|
package/lib/command-poller.js
CHANGED
|
@@ -117,6 +117,7 @@ class CommandPoller {
|
|
|
117
117
|
fs.writeFileSync(path.join(uniqueExtPath, 'config.json'), JSON.stringify({
|
|
118
118
|
channelManagerApi: 'https://api.channel.tunasm.art',
|
|
119
119
|
profileId: profile_id,
|
|
120
|
+
workerToken: this.config.worker_token || '',
|
|
120
121
|
}));
|
|
121
122
|
extensionPath = uniqueExtPath;
|
|
122
123
|
console.log(`[commands] Extension dir: ${uniqueExtPath}`);
|
|
@@ -633,11 +634,14 @@ class CommandPoller {
|
|
|
633
634
|
await new Promise(r => setTimeout(r, 300));
|
|
634
635
|
// Type text — use char-by-char for inputs that need keystroke events (press_enter mode)
|
|
635
636
|
if (press_enter) {
|
|
636
|
-
// Type each character
|
|
637
|
+
// Type each character with delay so React processes keystrokes
|
|
637
638
|
for (const char of text) {
|
|
638
639
|
await send('Input.dispatchKeyEvent', { type: 'keyDown', key: char, text: char });
|
|
639
640
|
await send('Input.dispatchKeyEvent', { type: 'keyUp', key: char });
|
|
641
|
+
await new Promise(r => setTimeout(r, 50));
|
|
640
642
|
}
|
|
643
|
+
// Extra delay after comma for Facebook to confirm tag
|
|
644
|
+
await new Promise(r => setTimeout(r, 500));
|
|
641
645
|
} else {
|
|
642
646
|
await send('Input.insertText', { text });
|
|
643
647
|
}
|
package/lib/daemon.js
CHANGED
|
@@ -7,7 +7,7 @@ const os = require('os');
|
|
|
7
7
|
class Daemon {
|
|
8
8
|
constructor(config) {
|
|
9
9
|
this.config = config;
|
|
10
|
-
this.api = new ApiClient(config.api_url);
|
|
10
|
+
this.api = new ApiClient(config.api_url, config.worker_token);
|
|
11
11
|
this.heartbeat = new Heartbeat(this.api, config.worker_id);
|
|
12
12
|
this.poller = new JobPoller(this.api, config);
|
|
13
13
|
this.commandPoller = new CommandPoller(this.api, config);
|