channel-worker 1.0.0 → 1.0.1

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/lib/api-client.js CHANGED
@@ -59,6 +59,15 @@ class ApiClient {
59
59
  const data = await this.request('GET', `/settings/${key}`);
60
60
  return data?.value;
61
61
  }
62
+
63
+ // Commands
64
+ async getNextCommand(workerId) {
65
+ return this.request('GET', `/workers/commands?worker_id=${workerId}`);
66
+ }
67
+
68
+ async updateCommand(commandId, data) {
69
+ return this.request('PUT', `/workers/commands/${commandId}`, data);
70
+ }
62
71
  }
63
72
 
64
73
  module.exports = { ApiClient };
@@ -0,0 +1,89 @@
1
+ class CommandPoller {
2
+ constructor(api, config) {
3
+ this.api = api;
4
+ this.config = config;
5
+ this.timer = null;
6
+ this.pollIntervalMs = 3000; // check commands every 3s
7
+ }
8
+
9
+ start() {
10
+ console.log('[commands] Polling for commands (every 3s)');
11
+ this.poll();
12
+ this.timer = setInterval(() => this.poll(), this.pollIntervalMs);
13
+ }
14
+
15
+ stop() {
16
+ if (this.timer) {
17
+ clearInterval(this.timer);
18
+ this.timer = null;
19
+ }
20
+ }
21
+
22
+ async poll() {
23
+ try {
24
+ const command = await this.api.getNextCommand(this.config.worker_id);
25
+ if (!command) return;
26
+
27
+ console.log(`[commands] Received: ${command.type} (${command._id})`);
28
+
29
+ switch (command.type) {
30
+ case 'launch_profile':
31
+ await this.handleLaunchProfile(command);
32
+ break;
33
+ case 'close_profile':
34
+ await this.handleCloseProfile(command);
35
+ break;
36
+ case 'verify_logins':
37
+ await this.handleVerifyLogins(command);
38
+ break;
39
+ default:
40
+ console.warn(`[commands] Unknown command type: ${command.type}`);
41
+ await this.api.updateCommand(command._id, { status: 'failed', error: 'Unknown command type' });
42
+ }
43
+ } catch (err) {
44
+ if (this.config.verbose) {
45
+ console.error(`[commands] Poll error: ${err.message}`);
46
+ }
47
+ }
48
+ }
49
+
50
+ async handleLaunchProfile(command) {
51
+ const { profile_id } = command.payload || {};
52
+ console.log(`[commands] Launching Nstbrowser profile: ${profile_id}`);
53
+
54
+ try {
55
+ // TODO: Integrate with Nstbrowser SDK
56
+ // const nstApi = require('./nst-manager');
57
+ // await nstApi.launchProfile(profile_id);
58
+
59
+ console.log(`[commands] Profile ${profile_id} launched — user can now login via RDP`);
60
+
61
+ await this.api.updateCommand(command._id, {
62
+ status: 'done',
63
+ result: { profile_id, launched_at: new Date().toISOString() },
64
+ });
65
+ } catch (err) {
66
+ console.error(`[commands] Failed to launch profile: ${err.message}`);
67
+ await this.api.updateCommand(command._id, {
68
+ status: 'failed',
69
+ error: err.message,
70
+ });
71
+ }
72
+ }
73
+
74
+ async handleCloseProfile(command) {
75
+ const { profile_id } = command.payload || {};
76
+ console.log(`[commands] Closing profile: ${profile_id}`);
77
+ // TODO: close Nstbrowser profile
78
+ await this.api.updateCommand(command._id, { status: 'done' });
79
+ }
80
+
81
+ async handleVerifyLogins(command) {
82
+ const { profile_id, channel_id } = command.payload || {};
83
+ console.log(`[commands] Verifying logins for profile: ${profile_id}`);
84
+ // TODO: launch headless, check cookies for Google/Facebook/TikTok
85
+ await this.api.updateCommand(command._id, { status: 'done' });
86
+ }
87
+ }
88
+
89
+ module.exports = { CommandPoller };
package/lib/daemon.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { ApiClient } = require('./api-client');
2
2
  const { Heartbeat } = require('./heartbeat');
3
3
  const { JobPoller } = require('./job-poller');
4
+ const { CommandPoller } = require('./command-poller');
4
5
  const os = require('os');
5
6
 
6
7
  class Daemon {
@@ -9,6 +10,7 @@ class Daemon {
9
10
  this.api = new ApiClient(config.api_url);
10
11
  this.heartbeat = new Heartbeat(this.api, config.worker_id);
11
12
  this.poller = new JobPoller(this.api, config);
13
+ this.commandPoller = new CommandPoller(this.api, config);
12
14
  }
13
15
 
14
16
  async start() {
@@ -45,13 +47,18 @@ class Daemon {
45
47
  // Start job polling
46
48
  this.poller.start();
47
49
  console.log('[daemon] Job poller started (every 5s)');
48
- console.log('[daemon] Waiting for jobs...\n');
50
+
51
+ // Start command polling
52
+ this.commandPoller.start();
53
+ console.log('[daemon] Command poller started (every 3s)');
54
+ console.log('[daemon] Waiting for jobs & commands...\n');
49
55
 
50
56
  // Graceful shutdown
51
57
  const shutdown = async () => {
52
58
  console.log('\n[daemon] Shutting down...');
53
59
  this.heartbeat.stop();
54
60
  this.poller.stop();
61
+ this.commandPoller.stop();
55
62
 
56
63
  // Mark offline
57
64
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Channel Manager worker daemon — runs on remote machines to execute video pipeline jobs",
5
5
  "main": "lib/daemon.js",
6
6
  "bin": {