channel-worker 2.5.51 → 2.5.52

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.51",
3
+ "version": "2.5.52",
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": {
@@ -282,6 +282,11 @@ async function openPostAndRead(page, log) {
282
282
  for (const a of best.querySelectorAll("a[href*='/posts/'], a[href*='/permalink/'], a[href*='story_fbid'], a[href*='/photo']")) {
283
283
  const r = a.getBoundingClientRect();
284
284
  if (r.width < 8 || r.height < 8 || a.offsetParent === null) continue;
285
+ // Facebook ships most permalinks with target="_blank": clicked as-is the
286
+ // post lands in a NEW tab while the script keeps driving the old one, so
287
+ // every opened post leaves a dead tab behind. Force same-tab — goBack()
288
+ // below is what returns us to the feed.
289
+ a.setAttribute('target', '_self');
285
290
  a.setAttribute('__nur_open__', '1');
286
291
  return true;
287
292
  }
@@ -377,6 +382,43 @@ async function detectProfileMode(page) {
377
382
  }).catch(() => ({ isPage: false, why: 'detect failed', navSample: '' }));
378
383
  }
379
384
 
385
+ // ONE tab, always.
386
+ //
387
+ // Two things pile tabs up behind a session, and neither is ours: NSTBrowser
388
+ // restores whatever the profile had open the last time it was used (publishing
389
+ // leaves Studio/Business tabs), and Facebook opens permalinks, photos and
390
+ // notification links with target="_blank". The script drives pages()[0] and
391
+ // never looks at the rest, so they just sit there — by the third session the
392
+ // window is a wall of tabs.
393
+ //
394
+ // Closing them is safe: nurturing needs exactly one tab (the feed), and the
395
+ // permalink click is forced to same-tab navigation in openPostAndRead.
396
+ async function closeOtherTabs(context, keep, log) {
397
+ const before = context.pages().length;
398
+ let closed = 0;
399
+ for (const p of context.pages()) {
400
+ if (p === keep || p.isClosed()) continue;
401
+ try { await p.close({ runBeforeUnload: false }); closed++; } catch { /* already gone */ }
402
+ }
403
+ // Log even when nothing was closed: "no line" would otherwise read the same
404
+ // as "the fix never loaded", and that is exactly what we need to tell apart.
405
+ if (log) log('info', `[nurture-fb] tab: ${before} lúc vào → đóng ${closed} → còn ${context.pages().length}`);
406
+ return closed;
407
+ }
408
+
409
+ // Anything Facebook manages to pop open mid-session dies on arrival. Registered
410
+ // once per session; the listener is scoped to this context, which the runner
411
+ // detaches from when the script returns.
412
+ function guardSingleTab(context, keep, log) {
413
+ context.on('page', async (p) => {
414
+ if (p === keep || p.isClosed()) return;
415
+ try {
416
+ await p.close({ runBeforeUnload: false });
417
+ log('info', '[nurture-fb] tab mới bị Facebook bung ra — đã đóng ngay');
418
+ } catch { /* raced with its own close */ }
419
+ });
420
+ }
421
+
380
422
  // debug:true only — screenshot the feed and hand back a viewable URL. Cheaper
381
423
  // than another round of guessing at selectors from log lines.
382
424
  async function dumpFeedShot(page, log, tag) {
@@ -398,7 +440,7 @@ async function dumpFeedShot(page, log, tag) {
398
440
  }
399
441
 
400
442
  // ─── main ───────────────────────────────────────────────────────────────────
401
- async function run({ page, payload, log }) {
443
+ async function run({ page, context, payload, log }) {
402
444
  const t0 = Date.now();
403
445
  const cfg = payload.config || {};
404
446
  const phase = Math.max(1, Math.min(3, parseInt(payload.phase, 10) || 1));
@@ -417,6 +459,12 @@ async function run({ page, payload, log }) {
417
459
 
418
460
  page.on('dialog', (d) => { d.accept().catch(() => {}); });
419
461
 
462
+ // Clear the profile's leftovers BEFORE loading the feed, then keep it clear.
463
+ if (context) {
464
+ await closeOtherTabs(context, page, log);
465
+ guardSingleTab(context, page, log);
466
+ }
467
+
420
468
  await page.goto('https://www.facebook.com/', { waitUntil: 'domcontentloaded', timeout: 60000 });
421
469
  await page.waitForTimeout(randInt(3000, 5000));
422
470
  // Guard FIRST — everything below assumes a live logged-in session.
@@ -522,6 +570,9 @@ async function run({ page, payload, log }) {
522
570
  // moment activity trips a threshold — better to stop than to keep poking.
523
571
  if (ticks % 12 === 0) {
524
572
  await assertAccountUsable(page, log);
573
+ // Belt to the context listener: a tab opened as a separate window, or one
574
+ // that slipped in while we were navigating, still gets swept.
575
+ if (context && context.pages().length > 1) await closeOtherTabs(context, page, log);
525
576
  log('info', `[nurture-fb] …${Math.round((Date.now() - t0) / 1000)}s: posts=${postsSeen} likes=${likes}/${likesTarget} videos=${videos} scrollY=${lastScan.scrollY} units=${lastScan.articlesInDom} sel=${lastScan.selector}`);
526
577
  }
527
578