channel-worker 1.2.0 → 1.3.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/lib/api-client.js CHANGED
@@ -62,12 +62,32 @@ class ApiClient {
62
62
 
63
63
  // Commands
64
64
  async getNextCommand(workerId) {
65
- return this.request('GET', `/workers/commands?worker_id=${workerId}`);
65
+ const workerTypes = 'launch_profile,close_profile,save_file,set_thumbnail,set_tags,set_file_input,click_and_upload,type_text,verify_logins,update_extension';
66
+ return this.request('GET', `/workers/commands?worker_id=${workerId}&types=${encodeURIComponent(workerTypes)}`);
66
67
  }
67
68
 
68
69
  async updateCommand(commandId, data) {
69
70
  return this.request('PUT', `/workers/commands/${commandId}`, data);
70
71
  }
72
+
73
+ // Extension download
74
+ async getExtensionVersion() {
75
+ const data = await this.request('GET', '/extension-download/version');
76
+ return data.version;
77
+ }
78
+
79
+ async downloadExtension(destPath) {
80
+ const url = `${this.baseUrl}/extension-download/zip`;
81
+ const res = await fetch(url, {
82
+ headers: { 'x-worker-token': this.workerToken },
83
+ });
84
+ if (!res.ok) throw new Error(`Download failed: ${res.status}`);
85
+
86
+ const fs = require('fs');
87
+ const buffer = Buffer.from(await res.arrayBuffer());
88
+ fs.writeFileSync(destPath, buffer);
89
+ return destPath;
90
+ }
71
91
  }
72
92
 
73
93
  module.exports = { ApiClient };
@@ -1,4 +1,5 @@
1
1
  const { NstManager } = require('./nst-manager');
2
+ const { checkAndUpdateExtension } = require('./extension-updater');
2
3
 
3
4
  class CommandPoller {
4
5
  constructor(api, config) {
@@ -64,6 +65,9 @@ class CommandPoller {
64
65
  case 'type_text':
65
66
  await this.handleTypeText(command);
66
67
  break;
68
+ case 'update_extension':
69
+ await this.handleUpdateExtension(command);
70
+ break;
67
71
  default:
68
72
  // Other commands (scan_facebook_pages, etc.) handled by extension
69
73
  console.log(`[commands] Skipping ${command.type} — handled by extension`);
@@ -827,6 +831,25 @@ class CommandPoller {
827
831
  await this.api.updateCommand(command._id, { status: 'done' });
828
832
  }
829
833
 
834
+ async handleUpdateExtension(command) {
835
+ console.log('[commands] Updating extension...');
836
+ try {
837
+ if (!this.config.extension_path) {
838
+ throw new Error('No extension_path configured');
839
+ }
840
+ const result = await checkAndUpdateExtension(this.api, this.config.extension_path);
841
+ if (result.updated) {
842
+ console.log(`[commands] Extension updated: ${result.from || 'none'} → ${result.to}`);
843
+ } else {
844
+ console.log(`[commands] Extension already up to date (v${result.local})`);
845
+ }
846
+ await this.api.updateCommand(command._id, { status: 'done', result });
847
+ } catch (err) {
848
+ console.error(`[commands] Extension update failed: ${err.message}`);
849
+ await this.api.updateCommand(command._id, { status: 'failed', error: err.message });
850
+ }
851
+ }
852
+
830
853
  async handleVerifyLogins(command) {
831
854
  const { profile_id, channel_id } = command.payload || {};
832
855
  console.log(`[commands] Verifying logins for profile: ${profile_id}`);
package/lib/daemon.js CHANGED
@@ -3,6 +3,7 @@ const { Heartbeat } = require('./heartbeat');
3
3
  const { JobPoller } = require('./job-poller');
4
4
  const { CommandPoller } = require('./command-poller');
5
5
  const { UpdateChecker, getLocalVersion } = require('./updater');
6
+ const { checkAndUpdateExtension } = require('./extension-updater');
6
7
 
7
8
  class Daemon {
8
9
  constructor(config) {
@@ -34,6 +35,20 @@ class Daemon {
34
35
  max_concurrent: this.config.max_concurrent,
35
36
  });
36
37
  console.log('[daemon] Registered with dashboard ✓');
38
+
39
+ // Auto-update extension on startup
40
+ if (this.config.extension_path) {
41
+ try {
42
+ const result = await checkAndUpdateExtension(this.api, this.config.extension_path);
43
+ if (result.updated) {
44
+ console.log(`[daemon] Extension updated: ${result.from || 'none'} → ${result.to}`);
45
+ } else {
46
+ console.log(`[daemon] Extension up to date (v${result.local})`);
47
+ }
48
+ } catch (err) {
49
+ console.warn(`[daemon] Extension update check failed: ${err.message}`);
50
+ }
51
+ }
37
52
  } catch (err) {
38
53
  console.error(`[daemon] Failed to register: ${err.message}`);
39
54
  console.error('[daemon] Is the dashboard API running? Retrying in 10s...');
@@ -0,0 +1,78 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { execSync } = require('child_process');
5
+
6
+ function getLocalExtensionVersion(extensionPath) {
7
+ try {
8
+ const manifest = JSON.parse(fs.readFileSync(path.join(extensionPath, 'manifest.json'), 'utf8'));
9
+ return manifest.version || null;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+
15
+ function isNewer(remote, local) {
16
+ const r = remote.split('.').map(Number);
17
+ const l = local.split('.').map(Number);
18
+ for (let i = 0; i < 3; i++) {
19
+ if ((r[i] || 0) > (l[i] || 0)) return true;
20
+ if ((r[i] || 0) < (l[i] || 0)) return false;
21
+ }
22
+ return false;
23
+ }
24
+
25
+ async function checkAndUpdateExtension(api, extensionPath) {
26
+ const local = getLocalExtensionVersion(extensionPath);
27
+ const remote = await api.getExtensionVersion();
28
+
29
+ if (local && !isNewer(remote, local)) {
30
+ return { updated: false, local, remote };
31
+ }
32
+
33
+ console.log(`[ext-updater] ${local ? `Update available: ${local} → ${remote}` : `Downloading extension v${remote}...`}`);
34
+
35
+ // Download to temp
36
+ const tmpArchive = path.join(os.tmpdir(), `channel-manager-ext-${Date.now()}.tar.gz`);
37
+ const tmpExtract = path.join(os.tmpdir(), `channel-manager-ext-new-${Date.now()}`);
38
+
39
+ try {
40
+ await api.downloadExtension(tmpArchive);
41
+
42
+ // Extract
43
+ fs.mkdirSync(tmpExtract, { recursive: true });
44
+ execSync(`tar -xzf "${tmpArchive}" -C "${tmpExtract}"`, { timeout: 30000 });
45
+
46
+ // tar extracts into a subfolder named "channel-manager-ext"
47
+ const extracted = path.join(tmpExtract, 'channel-manager-ext');
48
+ const sourceDir = fs.existsSync(extracted) ? extracted : tmpExtract;
49
+
50
+ // Replace existing extension dir
51
+ if (fs.existsSync(extensionPath)) {
52
+ fs.rmSync(extensionPath, { recursive: true, force: true });
53
+ }
54
+ fs.mkdirSync(extensionPath, { recursive: true });
55
+
56
+ // Copy files
57
+ const files = fs.readdirSync(sourceDir);
58
+ for (const f of files) {
59
+ const src = path.join(sourceDir, f);
60
+ const dest = path.join(extensionPath, f);
61
+ const stat = fs.statSync(src);
62
+ if (stat.isFile()) {
63
+ fs.copyFileSync(src, dest);
64
+ } else if (stat.isDirectory()) {
65
+ fs.cpSync(src, dest, { recursive: true });
66
+ }
67
+ }
68
+
69
+ console.log(`[ext-updater] Extension updated to v${remote}`);
70
+ return { updated: true, from: local, to: remote };
71
+ } finally {
72
+ // Cleanup temp files
73
+ try { fs.unlinkSync(tmpArchive); } catch {}
74
+ try { fs.rmSync(tmpExtract, { recursive: true, force: true }); } catch {}
75
+ }
76
+ }
77
+
78
+ module.exports = { checkAndUpdateExtension, getLocalExtensionVersion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
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": {