channel-worker 2.5.14 → 2.5.15

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.14",
3
+ "version": "2.5.15",
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": {
@@ -180,6 +180,54 @@ async function dismissBsOnboarding(page, log) {
180
180
  }
181
181
  }
182
182
 
183
+ // ── Large-file upload bypass ───────────────────────────────────────────────
184
+ // Playwright caps file transfers at 50MB when the browser is reached over
185
+ // connectOverCDP (our Nstbrowser case):
186
+ // "Cannot transfer files larger than 50Mb to a browser not connected to the
187
+ // server"
188
+ // Reup videos are routinely 60–200MB, so the native setInputFiles()/
189
+ // fileChooser.setFiles() throws and the upload never starts. Bypass via the raw
190
+ // CDP command DOM.setFileInputFiles: the browser reads the path off its OWN
191
+ // local disk (worker + Nstbrowser are co-located on the same machine), so no
192
+ // bytes cross the wire and there is no size cap. Unlike an in-page fetch() this
193
+ // is also immune to facebook.com's strict connect-src CSP. We tag the exact
194
+ // <input> with a unique attribute so CDP resolves its nodeId unambiguously
195
+ // (the page carries several file inputs).
196
+ async function cdpSetInputFiles(page, inputHandle, filePath, log, tag) {
197
+ const ATTR = 'data-cm-bigfile';
198
+ let cdp;
199
+ try {
200
+ await inputHandle.evaluate((el, a) => el.setAttribute(a, '1'), ATTR);
201
+ cdp = await page.context().newCDPSession(page);
202
+ const { root } = await cdp.send('DOM.getDocument', { depth: 0 });
203
+ const { nodeId } = await cdp.send('DOM.querySelector', {
204
+ nodeId: root.nodeId,
205
+ selector: `input[${ATTR}="1"]`,
206
+ });
207
+ if (!nodeId) throw new Error('CDP querySelector: tagged input not found');
208
+ await cdp.send('DOM.setFileInputFiles', { files: [filePath], nodeId });
209
+ log('info', `[${tag}] file set via CDP DOM.setFileInputFiles (>50MB bypass)`);
210
+ } finally {
211
+ await inputHandle.evaluate((el, a) => el.removeAttribute(a), ATTR).catch(() => {});
212
+ if (cdp) await cdp.detach().catch(() => {});
213
+ }
214
+ }
215
+
216
+ // Set a video file on a file <input>, transparently bypassing the CDP 50MB cap.
217
+ // Tries Playwright's native path first (fine for <50MB / thumbnails), then
218
+ // falls back to the CDP command only on the size error.
219
+ async function setVideoFile(page, inputHandle, filePath, log, tag = 'fb-pw') {
220
+ if (!inputHandle) throw new Error('setVideoFile: no input handle');
221
+ try {
222
+ await inputHandle.setInputFiles(filePath); // native — works for <50MB
223
+ return;
224
+ } catch (e) {
225
+ if (!/larger than 50\s?mb|not connected to the server/i.test(String(e.message || ''))) throw e;
226
+ log('info', `[${tag}] native setInputFiles hit the 50MB CDP cap — switching to DOM.setFileInputFiles…`);
227
+ }
228
+ await cdpSetInputFiles(page, inputHandle, filePath, log, tag);
229
+ }
230
+
183
231
  async function run({ page, payload, log }) {
184
232
  const {
185
233
  video_url, title, description = '', tags = [],
@@ -187,7 +235,7 @@ async function run({ page, payload, log }) {
187
235
  } = payload || {};
188
236
  if (!video_url) throw new Error('No video_url provided');
189
237
 
190
- log('info', '[fb-pw] selectors version=2026.06.12a-thumb-miss-soft');
238
+ log('info', '[fb-pw] selectors version=2026.06.14a-bigfile-cdp');
191
239
 
192
240
  page.on('dialog', (d) => { d.accept().catch(() => {}); });
193
241
 
@@ -389,7 +437,7 @@ async function run({ page, payload, log }) {
389
437
  page.waitForEvent('filechooser', { timeout: 8000 }),
390
438
  btn.click({ timeout: 3000 }),
391
439
  ]);
392
- await chooser.setFiles(videoPath);
440
+ await setVideoFile(page, chooser.element(), videoPath, log);
393
441
  videoSet = true;
394
442
  log('info', `[fb-pw] video file set via modal-scoped "${(await btn.innerText().catch(() => '')).slice(0, 30)}" button`);
395
443
  } catch (e) {
@@ -402,7 +450,7 @@ async function run({ page, payload, log }) {
402
450
  const fi = reelsDialog.locator("input[type='file']").last();
403
451
  if (await fi.count().catch(() => 0) > 0) {
404
452
  try {
405
- await fi.setInputFiles(videoPath);
453
+ await setVideoFile(page, await fi.elementHandle(), videoPath, log);
406
454
  videoSet = true;
407
455
  log('info', '[fb-pw] video file set via modal-scoped fallback input[type=file]');
408
456
  } catch (e) { log('info', `[fb-pw] modal fallback input setInputFiles failed: ${e.message.slice(0, 80)}`); }