channel-worker 1.0.22 → 1.0.24
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/command-poller.js +71 -0
- package/package.json +1 -1
package/lib/command-poller.js
CHANGED
|
@@ -58,6 +58,9 @@ class CommandPoller {
|
|
|
58
58
|
case 'set_file_input':
|
|
59
59
|
await this.handleSetFileInput(command);
|
|
60
60
|
break;
|
|
61
|
+
case 'type_text':
|
|
62
|
+
await this.handleTypeText(command);
|
|
63
|
+
break;
|
|
61
64
|
default:
|
|
62
65
|
// Other commands (scan_facebook_pages, etc.) handled by extension
|
|
63
66
|
console.log(`[commands] Skipping ${command.type} — handled by extension`);
|
|
@@ -546,6 +549,74 @@ class CommandPoller {
|
|
|
546
549
|
}
|
|
547
550
|
}
|
|
548
551
|
|
|
552
|
+
async handleTypeText(command) {
|
|
553
|
+
const { text, profile_id, url_match } = command.payload || {};
|
|
554
|
+
console.log(`[commands] Typing text (${text?.length} chars) for profile: ${profile_id}`);
|
|
555
|
+
try {
|
|
556
|
+
const WebSocket = require('ws');
|
|
557
|
+
if (!this.nst) {
|
|
558
|
+
const apiKey = await this.api.getSetting('nst_api_key');
|
|
559
|
+
if (apiKey) this.nst = new NstManager(apiKey);
|
|
560
|
+
}
|
|
561
|
+
const browsersRes = await fetch('http://localhost:8848/api/v2/browsers', {
|
|
562
|
+
headers: { 'x-api-key': this.nst?.apiKey || '' },
|
|
563
|
+
});
|
|
564
|
+
const browser = ((await browsersRes.json())?.data || []).find(b => b.name === profile_id) || (await (await fetch('http://localhost:8848/api/v2/browsers', { headers: { 'x-api-key': this.nst?.apiKey || '' } })).json())?.data?.[0];
|
|
565
|
+
if (!browser) throw new Error('No running browser');
|
|
566
|
+
|
|
567
|
+
const pagesRes = await fetch(`http://localhost:${browser.remoteDebuggingPort}/json/list`);
|
|
568
|
+
const pages = await pagesRes.json();
|
|
569
|
+
const targetPage = pages.find(p => p.type === 'page' && (!url_match || p.url?.includes(url_match)));
|
|
570
|
+
if (!targetPage) throw new Error(`No tab matching ${url_match}`);
|
|
571
|
+
|
|
572
|
+
const result = await new Promise((resolve, reject) => {
|
|
573
|
+
const ws = new WebSocket(targetPage.webSocketDebuggerUrl);
|
|
574
|
+
let msgId = 1;
|
|
575
|
+
function send(method, params = {}) {
|
|
576
|
+
const id = msgId++;
|
|
577
|
+
return new Promise((res, rej) => {
|
|
578
|
+
const handler = (data) => {
|
|
579
|
+
const msg = JSON.parse(data);
|
|
580
|
+
if (msg.id === id) { ws.removeListener('message', handler); msg.error ? rej(new Error(msg.error.message)) : res(msg.result); }
|
|
581
|
+
};
|
|
582
|
+
ws.on('message', handler);
|
|
583
|
+
ws.send(JSON.stringify({ id, method, params }));
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
ws.on('open', async () => {
|
|
588
|
+
try {
|
|
589
|
+
await send('DOM.enable');
|
|
590
|
+
// Focus the contenteditable editor via CDP
|
|
591
|
+
await send('Runtime.evaluate', {
|
|
592
|
+
expression: `
|
|
593
|
+
const editors = document.querySelectorAll('[contenteditable="true"], [role="textbox"]');
|
|
594
|
+
let focused = false;
|
|
595
|
+
for (const e of editors) {
|
|
596
|
+
if (e.offsetHeight >= 15) { e.focus(); e.click(); focused = true; break; }
|
|
597
|
+
}
|
|
598
|
+
focused ? 'focused' : 'no_editor'
|
|
599
|
+
`,
|
|
600
|
+
});
|
|
601
|
+
// Small delay for focus to take effect
|
|
602
|
+
await new Promise(r => setTimeout(r, 300));
|
|
603
|
+
// Type text
|
|
604
|
+
await send('Input.insertText', { text });
|
|
605
|
+
ws.close();
|
|
606
|
+
resolve({ success: true, length: text.length });
|
|
607
|
+
} catch (e) { ws.close(); resolve({ success: false, error: e.message }); }
|
|
608
|
+
});
|
|
609
|
+
ws.on('error', (e) => reject(e));
|
|
610
|
+
setTimeout(() => { ws.close(); reject(new Error('Timeout')); }, 10000);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
await this.api.updateCommand(command._id, { status: result.success ? 'done' : 'failed', result, error: result.error || null });
|
|
614
|
+
} catch (err) {
|
|
615
|
+
console.error(`[commands] Type text failed: ${err.message}`);
|
|
616
|
+
await this.api.updateCommand(command._id, { status: 'failed', error: err.message });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
549
620
|
async handleCloseProfile(command) {
|
|
550
621
|
const { profile_id } = command.payload || {};
|
|
551
622
|
console.log(`[commands] Closing profile: ${profile_id}`);
|