Closed rogerpadilla closed 3 weeks ago
Is it actually failing when you try to use it in Cloudflare? The warnings don't matter much unless they're causing issues. The only thing needed from jsdom is the document & window objects, which should work anywhere from what I can tell.
Yep, the error prevents me from deploying the code, let me attach my current code to explain my current case better.
Basically, I am following this quick tutorial https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/get-started/configuration/
When I run npx wrangler deploy --dispatch-namespace staging
without jsdom nor bgutils it does work, the thing is when I use these dependencies in the code I get the following error:
With node_compat = true
Uncaught TypeError: globalThis.XMLHttpRequest is not a constructor
at null.<anonymous>
(file:///Users/rpc/projects/variability/cf/ytdl-1/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js:20:11)
in checkTypeSupport
at null.<anonymous>
(file:///Users/rpc/projects/variability/cf/ytdl-1/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js:39:45)
in
node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js
at null.<anonymous> (index.js:15:56) in __init
at null.<anonymous>
(file:///Users/rpc/projects/variability/cf/ytdl-1/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/request.js:1:1)
in
node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/request.js
at null.<anonymous> (index.js:15:56) in __init
at null.<anonymous> (node-modules-polyfills:http:30:1) in
node-modules-polyfills:http
at null.<anonymous> (index.js:15:56) in __init
at null.<anonymous> (node-modules-polyfills-commonjs:http:2:18) in
node-modules-polyfills-commonjs:http
at null.<anonymous> (index.js:18:50) in __require2
at null.<anonymous>
(file:///Users/rpc/projects/variability/cf/ytdl-1/node_modules/jsdom/lib/jsdom/living/xhr/XMLHttpRequest-impl.js:3:27)
in node_modules/jsdom/lib/jsdom/living/xhr/XMLHttpRequest-impl.js
[code: 10021]
And if comment node_compat = true
out and add compatibility_flags = ["nodejs_compat"]
the error is different:
⛅️ wrangler 3.77.0
-------------------
✘ [ERROR] Could not resolve "http"
node_modules/agent-base/dist/helpers.js:27:34:
27 │ const http = __importStar(require("http"));
╵ ~~~~~~
The package "http" wasn't found on the file system but is built into node.
Add "node_compat = true" to your wrangler.toml file and make sure to prefix the module name with "node:" to enable Node.js compatibility.
✘ [ERROR] Could not resolve "https"
node_modules/agent-base/dist/helpers.js:28:35:
28 │ const https = __importStar(require("https"));
╵ ~~~~~~~
The package "https" wasn't found on the file system but is built into node.
Add "node_compat = true" to your wrangler.toml file and make sure to prefix the module name with "node:" to enable Node.js compatibility.
✘ [ERROR] Could not resolve "net"
node_modules/agent-base/dist/index.js:30:33:
30 │ const net = __importStar(require("net"));
wrangler.toml
#:schema node_modules/wrangler/config-schema.json
name = "ytdl-1"
main = "src/index.ts"
compatibility_date = "2024-09-09"
# compatibility_flags = ["nodejs_compat"]
node_compat = true
Worker code index.ts
:
import { UniversalCache, Innertube } from 'youtubei.js';
import { JSDOM } from 'jsdom';
import { BG, BgConfig } from 'bgutils-js';
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = await getDownloadUrl('https://www.youtube.com/watch?v=39rBzRd4M0k');
return new Response('Hello World!, url' + url);
},
} satisfies ExportedHandler<Env>;
const id = '39rBzRd4M0k';
async function getDownloadUrl(url: string) {
const yt = await getYt();
const info = await yt.getInfo(id);
// console.error('** yti getDownloadUrl info', JSON.stringify(info, null, 2));
// console.info('** yti getDownloadUrl info.playability_status', info.playability_status);
const format = chooseFormat(info);
const decipheredUrl = format.decipher(yt.session.player);
// console.info('** yti getDownloadUrl decipheredUrl', decipheredUrl);
// console.log('** yti getDownloadUrl', format);
return decipheredUrl;
}
function chooseFormat(info: VideoInfo) {
let format: ReturnType<typeof info.chooseFormat>;
try {
format = info.chooseFormat?.({ type: 'video+audio', quality: 'bestefficiency' });
} catch (err) {
console.error(err);
}
return format ?? info.streaming_data?.formats?.[0];
}
async function scrapeYtSession() {
const yt = await Innertube.create({ retrieve_player: false });
const requestKey = 'O43z0dpjhgX20SCx4KAo';
const visitorData = yt.session.context.client.visitorData;
const dom = new JSDOM();
Object.assign(globalThis, {
window: dom.window,
document: dom.window.document,
});
const bgConfig: BgConfig = {
fetch: (url, init) => fetch(url, init),
globalObj: globalThis,
identifier: visitorData,
requestKey,
};
const challenge = await BG.Challenge.create(bgConfig);
if (!challenge) throw new TypeError('Could not get challenge');
if (challenge.script) {
const script = challenge.script.find((sc) => sc !== null);
if (script) new Function(script)();
} else {
console.warn('Unable to load Botguard.');
}
const poToken = await BG.PoToken.generate({
program: challenge.challenge,
globalName: challenge.globalName,
bgConfig,
});
const data = { poToken, visitorData };
return data;
}
let _yt: Innertube & { myCreatedAt?: Date };
async function getYt() {
const randomMilliseconds = getRandomMilliseconds();
const thresholdDate = new Date(Date.now() - randomMilliseconds);
if (thresholdDate <= _yt?.myCreatedAt) {
return _yt;
}
const { poToken, visitorData } = await scrapeYtSession();
_yt = await Innertube.create({
po_token: poToken,
visitor_data: visitorData,
generate_session_locally: true,
cache: new UniversalCache(true),
});
_yt.myCreatedAt = new Date();
return _yt;
}
function getRandomMilliseconds() {
const minutes = Math.floor(Math.random() * 30) + 30;
return minutes * 60 * 1000;
}
package.json
{
"name": "ytdl-1",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.4.5",
"@cloudflare/workers-types": "^4.20240909.0",
"typescript": "^5.5.2",
"vitest": "1.5.0",
"wrangler": "^3.60.3"
},
"dependencies": {
"bgutils-js": "^2.0.1",
"jsdom": "^25.0.0",
"youtubei.js": "^10.4.0"
}
}
I've tried a lot of things, this is getting me crazy lol
Question
the idea is to use bgutils to get tokens for the deciphered URL with youtubei…
when trying to combine bgutils with youtubei in cloudflare i get a lot of errors about unsupported modules (seems to be more because jsdom relies on node js apis).
running youtubei alone in cloudflare as in the examples works fine, but i guess something as bgutils is necessary to avoid get blocked.
i also tried happy-dom but also fails there.
any ideas please?
Other details
No response
Checklist