channel-worker 2.5.70 → 2.5.72

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/daemon.js CHANGED
@@ -14,6 +14,12 @@ class Daemon {
14
14
  // Nstbrowser agent of THIS Windows session. On a multi-user box the second
15
15
  // session's agent binds 8849+ because 8848 is taken machine-wide.
16
16
  if (config.nst_api_address) process.env.NST_API_ADDRESS = config.nst_api_address;
17
+ // Tell every NstManager we are pinned to one Windows user's agent, so an
18
+ // unresolved address REFUSES commands instead of defaulting to 8848
19
+ // (= the other session's agent on a multi-user box).
20
+ if (config.nst_agent_user && !config.nst_api_address) {
21
+ process.env.NST_AGENT_PINNED_USER = config.nst_agent_user;
22
+ }
17
23
  this.api = new ApiClient(config.api_url, config.worker_token);
18
24
  this.heartbeat = new Heartbeat(this.api, config.worker_id, 30000, config);
19
25
  this.poller = new JobPoller(this.api, config);
@@ -46,9 +52,16 @@ class Daemon {
46
52
  return;
47
53
  }
48
54
  if (!found) {
49
- if (!quiet) {
55
+ // The pinned user's agent is GONE. Drop a previously resolved address —
56
+ // after a relog the same port can belong to the other session's agent,
57
+ // and driving it opens blank profiles. Fail-closed until it re-appears.
58
+ if (process.env.NST_API_ADDRESS) {
59
+ console.warn(`[daemon] NST agent for "${user}" disappeared `
60
+ + `(was ${process.env.NST_API_ADDRESS}) — refusing NST commands until it is back.`);
61
+ delete process.env.NST_API_ADDRESS;
62
+ } else if (!quiet) {
50
63
  console.warn(`[daemon] No Nstbrowser agent for Windows user "${user}" — `
51
- + 'NST commands will fail until that user is logged in.');
64
+ + 'NST commands are refused (fail-closed) until that user is logged in.');
52
65
  }
53
66
  return;
54
67
  }
@@ -73,7 +86,10 @@ class Daemon {
73
86
  ╚══════════════════════════════════════╝
74
87
  `);
75
88
  console.log(`[daemon] cache server port: ${this.config.cache_port || 8849}`);
76
- console.log(`[daemon] NST agent: ${process.env.NST_API_ADDRESS || 'http://localhost:8848/api/v2 (default)'}`);
89
+ console.log(`[daemon] NST agent: ${process.env.NST_API_ADDRESS
90
+ || (this.config.nst_agent_user
91
+ ? `UNRESOLVED — refusing NST commands until agent of "${this.config.nst_agent_user}" is found`
92
+ : 'http://localhost:8848/api/v2 (default)')}`);
77
93
 
78
94
  // Start local cache server (extension shares this across all profiles)
79
95
  try {
@@ -24,10 +24,31 @@ function nstLaunchError(response, { profileId = '', runningCount = 0 } = {}) {
24
24
  class NstManager {
25
25
  constructor(apiKey, options = {}) {
26
26
  this.apiKey = apiKey;
27
- // Multi-user Windows: each logged-in session runs its OWN Nstbrowser agent,
28
- // and the second one lands on 8849+ because 8848 is already taken machine-wide.
29
- // NST_API_ADDRESS lets a daemon target its own session's agent.
30
- this.baseUrl = options.apiAddress || process.env.NST_API_ADDRESS || 'http://localhost:8848/api/v2';
27
+ this._apiAddress = options.apiAddress || null;
28
+ }
29
+
30
+ // Multi-user Windows: each logged-in session runs its OWN Nstbrowser agent,
31
+ // and the second one lands on 8849+ because 8848 is already taken machine-wide.
32
+ // Resolved at CALL time, not construct time: the daemon's agent watcher
33
+ // rewrites NST_API_ADDRESS whenever ports move, and an instance built before
34
+ // the first resolution must not keep a stale target forever.
35
+ //
36
+ // Fail-closed: when the daemon is pinned to a Windows user
37
+ // (NST_AGENT_PINNED_USER) and that user's agent hasn't been located, REFUSE
38
+ // instead of falling back to 8848 — on a two-session box 8848 is the OTHER
39
+ // user's agent, and opening a profile there starts a blank Chrome: FB shows
40
+ // a login form and fb-guard wrongly flags the account as logged out
41
+ // (35 channels hit 2026-08-31…09-02).
42
+ get baseUrl() {
43
+ if (this._apiAddress) return this._apiAddress;
44
+ if (process.env.NST_API_ADDRESS) return process.env.NST_API_ADDRESS;
45
+ const pinned = process.env.NST_AGENT_PINNED_USER;
46
+ if (pinned) {
47
+ throw new Error(`NST_AGENT_UNRESOLVED: chưa thấy Nstbrowser agent của Windows user "${pinned}" `
48
+ + '— không fallback :8848 (agent của session khác → profile trắng → báo logout oan). '
49
+ + 'Lệnh CHƯA chạm nền tảng; sẽ tự chạy lại khi agent của user đó lên.');
50
+ }
51
+ return 'http://localhost:8848/api/v2';
31
52
  }
32
53
 
33
54
  async api(path, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "channel-worker",
3
- "version": "2.5.70",
3
+ "version": "2.5.72",
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": {
@@ -574,6 +574,30 @@ async function isThumbnailEditorVisible(page) {
574
574
  }, THUMBNAIL_EDITOR_TEXT_MARKERS).catch(() => false);
575
575
  }
576
576
 
577
+ // Bằng chứng "thumb tuỳ chỉnh ĐANG được áp" không thể nhầm: nút "Gỡ hình thu
578
+ // nhỏ tùy chỉnh khỏi thước phim" chỉ tồn tại khi đã có thumb tuỳ chỉnh. Cần
579
+ // cho biến thể INLINE (bắt 03/09, idea 6a965a32 fail 5 lượt liên tiếp):
580
+ // editor "nested" nằm HẲN trong bước "Cài đặt thước phim" và không bao giờ
581
+ // "đóng" — marker "Chỉnh sửa hình thu nhỏ" thường trực trong innerText của
582
+ // dialog nên isThumbnailEditorVisible kẹt true, vòng chờ đóng cạn 60s rồi
583
+ // ESC+Hủy phá lượt dù thumb đã dán xong (diag 158386: Gỡ/Lưu/Hủy + Đăng
584
+ // cùng hiện trên một bước).
585
+ async function isCustomThumbApplied(page) {
586
+ return page.evaluate(() => {
587
+ const vis = (el) => {
588
+ const r = el.getBoundingClientRect();
589
+ if (r.width < 8 || r.height < 8) return false;
590
+ const cs = getComputedStyle(el);
591
+ return cs.visibility !== 'hidden' && cs.display !== 'none' && cs.opacity !== '0';
592
+ };
593
+ for (const el of document.querySelectorAll('[aria-label]')) {
594
+ const al = el.getAttribute('aria-label') || '';
595
+ if (/Gỡ hình thu nhỏ tùy chỉnh|Remove( your)? custom thumbnail/i.test(al) && vis(el)) return true;
596
+ }
597
+ return false;
598
+ }).catch(() => false);
599
+ }
600
+
577
601
  const SAFE_RETRY_COMPOSER_CLOSED = 'FB_SAFE_RETRY_COMPOSER_CLOSED';
578
602
 
579
603
  function safeComposerRetryError(message) {
@@ -1654,6 +1678,7 @@ async function runOnce({ page, payload, log }) {
1654
1678
  const closeDeadline = Date.now() + 60_000;
1655
1679
  let modalClosed = false;
1656
1680
  let closedPolls = 0;
1681
+ let appliedPolls = 0;
1657
1682
  let retryCount = 0;
1658
1683
  const RETRY_AFTER_MS = 12_000;
1659
1684
  let lastRetryAt = Date.now();
@@ -1663,6 +1688,23 @@ async function runOnce({ page, payload, log }) {
1663
1688
  // nested form (no own dialog — its upload button, aria
1664
1689
  // "Tải hình thu nhỏ tùy chỉnh lên…", only exists while the
1665
1690
  // editor is showing).
1691
+ // Biến thể inline: panel không bao giờ đóng nhưng nút "Gỡ hình
1692
+ // thu nhỏ tùy chỉnh" đã hiện = thumb ĐÃ áp. Chỉ tin ở nhánh
1693
+ // nested (modal thật vẫn phải đóng sau Lưu như cũ) và đòi 2
1694
+ // mẫu liên tiếp như luật closedPolls; bấm Lưu chốt một nhịp
1695
+ // cuối phòng cohort nào đó vẫn cần Lưu để giữ trạng thái.
1696
+ if (thumbEditorVia === 'nested' && await isCustomThumbApplied(page)) {
1697
+ if (++appliedPolls >= 2) {
1698
+ for (const sel of saveCandidates) {
1699
+ const btn = await firstVisible(page.locator(sel), 1);
1700
+ if (btn) { await btn.click({ timeout: 1500 }).catch(() => {}); break; }
1701
+ }
1702
+ log('info', '[fb-pw] page-wall thumb — panel inline không đóng nhưng nút "Gỡ hình thu nhỏ" đang hiện → coi như dán xong');
1703
+ modalClosed = true; break;
1704
+ }
1705
+ } else {
1706
+ appliedPolls = 0;
1707
+ }
1666
1708
  const still = await isThumbnailEditorVisible(page);
1667
1709
  if (!still) {
1668
1710
  // Require two consecutive closed samples. Facebook briefly
@@ -1702,6 +1744,11 @@ async function runOnce({ page, payload, log }) {
1702
1744
  }
1703
1745
  thumbApplied = true;
1704
1746
  log('info', `[fb-pw] page-wall thumb — modal closed after save (retries=${retryCount})`);
1747
+ } else if (thumbEditorVia === 'nested' && await isCustomThumbApplied(page)) {
1748
+ // Hết giờ chờ đóng nhưng bằng chứng áp thumb còn đó — tuyệt
1749
+ // đối không ESC/Hủy (Hủy trên panel inline là GỠ thumb).
1750
+ log('info', '[fb-pw] page-wall thumb — hết giờ chờ đóng nhưng nút "Gỡ hình thu nhỏ" đang hiện → giữ panel, coi như dán xong');
1751
+ thumbApplied = true;
1705
1752
  } else {
1706
1753
  log('warn', '[fb-pw] page-wall thumb — modal still open 60s after Lưu click; trying ESC + click Hủy fallback');
1707
1754
  await page.keyboard.press('Escape').catch(() => {});
@@ -2077,13 +2124,22 @@ async function runOnce({ page, payload, log }) {
2077
2124
  // output. (Observed on reel 1506614811005729: step 1 → click Tiếp
2078
2125
  // → publish, the thumb-edit pill never appeared.)
2079
2126
  if (thumbPath && !customThumbDone) {
2080
- // Defensive: we normally fail at the caption step (before any Tiếp)
2081
- // when a required custom thumb can't be applied. If we somehow reach
2082
- // here without it, refuse to publishbut BEFORE clicking Đăng, so
2083
- // nothing ships and a retry can't double-post. (The v2.5.14 double-post
2084
- // came from failing AFTER publish; this throw is pre-publish.)
2085
- await dumpFailure(page, 'thumb-required-at-publish', log);
2086
- throw new Error('Custom thumbnail required but not applied — refusing to publish with FB auto-thumb');
2127
+ // Cứu theo BẰNG CHỨNG trước khi chặn: biến thể inline có thể làm cờ
2128
+ // customThumbDone lỡ false trong khi nút "Gỡ hình thu nhỏ tùy chỉnh"
2129
+ // ngay trên bước này chứng minh thumb ĐANG được áp cả 5 lượt fail
2130
+ // 02-03/09 của idea 6a965a32 đều trạng thái này (diag 158386).
2131
+ if (await isCustomThumbApplied(page)) {
2132
+ log('info', '[fb-pw] pre-publish: nút "Gỡ hình thu nhỏ tùy chỉnh" đang hiện — thumb đã áp dù cờ chưa bật, tiếp tục đăng');
2133
+ customThumbDone = true;
2134
+ } else {
2135
+ // Defensive: we normally fail at the caption step (before any Tiếp)
2136
+ // when a required custom thumb can't be applied. If we somehow reach
2137
+ // here without it, refuse to publish — but BEFORE clicking Đăng, so
2138
+ // nothing ships and a retry can't double-post. (The v2.5.14 double-post
2139
+ // came from failing AFTER publish; this throw is pre-publish.)
2140
+ await dumpFailure(page, 'thumb-required-at-publish', log);
2141
+ throw new Error('Custom thumbnail required but not applied — refusing to publish with FB auto-thumb');
2142
+ }
2087
2143
  }
2088
2144
  log('info', `[fb-pw] click publish "${pub.verb}" via "${pub.sel}" (step ${step + 1})`);
2089
2145
  // Snapshot the captured-IDs list RIGHT BEFORE the publish click. The
@@ -2850,6 +2906,7 @@ module.exports.__testables = {
2850
2906
  PUBLISH_ENABLE_TIMEOUT_MS,
2851
2907
  PUBLISH_CTA_ABSENT_GRACE_MS,
2852
2908
  THUMBNAIL_EDITOR_TEXT_MARKERS,
2909
+ isCustomThumbApplied,
2853
2910
  REEL_CREATE_SELECTORS,
2854
2911
  REEL_ENTRY_SELECTORS,
2855
2912
  ADD_VIDEO_SELECTORS,