channel-worker 1.0.5 → 1.0.7

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.
@@ -46,6 +46,9 @@ class CommandPoller {
46
46
  case 'close_profile':
47
47
  await this.handleCloseProfile(command);
48
48
  break;
49
+ case 'scan_facebook_pages':
50
+ await this.handleScanFacebookPages(command);
51
+ break;
49
52
  case 'verify_logins':
50
53
  await this.handleVerifyLogins(command);
51
54
  break;
@@ -83,8 +86,9 @@ class CommandPoller {
83
86
  } catch { /* ignore */ }
84
87
  }
85
88
 
86
- const result = await this.nst.launchProfile(profile_id, { proxy });
87
- console.log(`[commands] Profile ${profile_id} launched${proxy ? ' (proxy: ' + proxy + ')' : ''}`);
89
+ const extensionPath = this.config.extension_path || '';
90
+ const result = await this.nst.launchProfile(profile_id, { proxy, extensionPath });
91
+ console.log(`[commands] Profile ${profile_id} launched${proxy ? ' (proxy: ' + proxy + ')' : ''}${extensionPath ? ' (ext: ' + extensionPath + ')' : ''}`);
88
92
 
89
93
  await this.api.updateCommand(command._id, {
90
94
  status: 'done',
@@ -99,6 +103,59 @@ class CommandPoller {
99
103
  }
100
104
  }
101
105
 
106
+ async handleScanFacebookPages(command) {
107
+ const { profile_id } = command.payload || {};
108
+ console.log(`[commands] Scanning Facebook pages via profile: ${profile_id}`);
109
+
110
+ try {
111
+ if (!this.nst) {
112
+ const apiKey = await this.api.getSetting('nst_api_key');
113
+ if (apiKey) {
114
+ this.nst = new NstManager(apiKey);
115
+ } else {
116
+ throw new Error('Nstbrowser API key not configured.');
117
+ }
118
+ }
119
+
120
+ // Launch profile and get CDP endpoint
121
+ const { profileId, wsEndpoint } = await this.nst.connectProfile(profile_id);
122
+
123
+ // TODO: Use Puppeteer/Playwright to navigate facebook.com/pages and scrape page list
124
+ // For now, placeholder — worker needs puppeteer-core to actually scrape
125
+ console.log(`[commands] Browser connected at ${wsEndpoint}`);
126
+ console.log(`[commands] TODO: Navigate to facebook.com/pages and scrape page list`);
127
+
128
+ // Placeholder result — in real implementation, scrape pages from Facebook
129
+ const pages = [];
130
+ // const puppeteer = require('puppeteer-core');
131
+ // const browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
132
+ // const page = await browser.newPage();
133
+ // await page.goto('https://www.facebook.com/pages/?category=your_pages');
134
+ // pages = await page.evaluate(() => { /* scrape page names/urls */ });
135
+
136
+ // Send results to API
137
+ if (pages.length > 0) {
138
+ await this.api.request('POST', '/facebook-pages/scan-result', {
139
+ pages,
140
+ scanned_from_profile: profile_id,
141
+ });
142
+ }
143
+
144
+ await this.api.updateCommand(command._id, {
145
+ status: 'done',
146
+ result: { profile_id: profileId, pages_found: pages.length },
147
+ });
148
+
149
+ console.log(`[commands] Facebook scan complete — ${pages.length} pages found`);
150
+ } catch (err) {
151
+ console.error(`[commands] Facebook scan failed: ${err.message}`);
152
+ await this.api.updateCommand(command._id, {
153
+ status: 'failed',
154
+ error: err.message,
155
+ });
156
+ }
157
+ }
158
+
102
159
  async handleCloseProfile(command) {
103
160
  const { profile_id } = command.payload || {};
104
161
  console.log(`[commands] Closing profile: ${profile_id}`);
@@ -57,7 +57,7 @@ class NstManager {
57
57
  }
58
58
 
59
59
  // Create profile if not exists, return profileId
60
- async ensureProfile(name) {
60
+ async ensureProfile(name, options = {}) {
61
61
  const existing = await this.findProfile(name);
62
62
  if (existing) {
63
63
  console.log(`[nst] Profile "${name}" exists: ${existing}`);
@@ -65,7 +65,7 @@ class NstManager {
65
65
  }
66
66
 
67
67
  console.log(`[nst] Creating profile "${name}"...`);
68
- const res = await this.client.profiles().createProfile({
68
+ const profileData = {
69
69
  name,
70
70
  platform: 'Windows',
71
71
  kernelMilestone: '132',
@@ -80,7 +80,17 @@ class NstManager {
80
80
  hardwareConcurrency: 8,
81
81
  deviceMemory: 8,
82
82
  },
83
- });
83
+ };
84
+
85
+ // Add extension load arg if provided
86
+ if (options.extensionPath) {
87
+ profileData.args = {
88
+ '--load-extension': options.extensionPath,
89
+ '--disable-extensions-except': options.extensionPath,
90
+ };
91
+ }
92
+
93
+ const res = await this.client.profiles().createProfile(profileData);
84
94
 
85
95
  const profileId = res?.data?.profileId || res?.data?._id;
86
96
  if (!profileId) throw new Error('Failed to create profile — no ID returned');
@@ -101,7 +111,7 @@ class NstManager {
101
111
  async launchProfile(profileIdOrName, options = {}) {
102
112
  let profileId = profileIdOrName;
103
113
  if (!this.isUUID(profileIdOrName)) {
104
- profileId = await this.ensureProfile(profileIdOrName);
114
+ profileId = await this.ensureProfile(profileIdOrName, { extensionPath: options.extensionPath });
105
115
  }
106
116
 
107
117
  // Check if already running
@@ -118,6 +128,39 @@ class NstManager {
118
128
  console.log(`[nst] Starting browser for profile: ${profileId}`);
119
129
  const res = await this.client.browsers().startBrowser(profileId);
120
130
  console.log(`[nst] Browser started`);
131
+
132
+ // Set nstProfileId in chrome.storage via CDP
133
+ try {
134
+ // Wait for browser to fully start
135
+ await new Promise(r => setTimeout(r, 3000));
136
+
137
+ const debugInfo = await this.client.browsers().getBrowserDebugger(profileId);
138
+ const wsUrl = debugInfo?.data?.webSocketDebuggerUrl;
139
+ if (wsUrl) {
140
+ const WebSocket = require('ws');
141
+ const ws = new WebSocket(wsUrl);
142
+ await new Promise((resolve, reject) => {
143
+ const timeout = setTimeout(() => { ws.close(); reject(new Error('CDP timeout')); }, 10000);
144
+ ws.on('open', () => {
145
+ // Navigate a new tab to set storage
146
+ ws.send(JSON.stringify({
147
+ id: 1,
148
+ method: 'Runtime.evaluate',
149
+ params: {
150
+ expression: `chrome.storage && chrome.storage.local ? chrome.storage.local.set({nstProfileId: "${profileIdOrName}"}) : null`,
151
+ awaitPromise: true,
152
+ },
153
+ }));
154
+ setTimeout(() => { clearTimeout(timeout); ws.close(); resolve(); }, 2000);
155
+ });
156
+ ws.on('error', (e) => { clearTimeout(timeout); reject(e); });
157
+ });
158
+ console.log(`[nst] Profile ID "${profileIdOrName}" set in extension storage`);
159
+ }
160
+ } catch (e) {
161
+ console.log(`[nst] Could not set profile ID in storage: ${e.message}`);
162
+ }
163
+
121
164
  return { profileId, response: res };
122
165
  }
123
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
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": {
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "node-fetch": "^3.3.2",
19
- "nstbrowser-sdk-node": "^0.1.1"
19
+ "nstbrowser-sdk-node": "^0.1.1",
20
+ "ws": "^8.18.0"
20
21
  },
21
22
  "engines": {
22
23
  "node": ">=18"