import * as http from 'http'; import * as url from 'url'; import * as path from 'path'; import { chromium } from 'playwright'; import { locks } from 'web-locks'; import crypto from 'crypto'; import fs from 'fs'; import { setTimeout } from 'timers/promises'; import { WebSocketServer } from 'ws'; // npm i playwright // npm i web-locks // npm i ws // nohup cloudflared tunnel --url http://localhost:8080 --no-autoupdate & (or setsid) // setsid node playwright.mjs (nohup doesnt work) // curl 'https://gowah44030-playwright.hf.space/.. // https://gowah44030-playwright.hf.space/screenshot?js=1&url=https://www.investing.com globalThis.state = Object.assign(globalThis.state||{}, { browser: null, context: null, }); async function text_from_url(url, cookie) { let { browser, context } = globalThis.state; if (!browser) { await locks.request('playwright_browser', async lock => { browser = globalThis.state.browser = await chromium.launch(); context = globalThis.state.context = await browser.newContext({ javaScriptEnabled: false }/*devices['iPhone 11']*/); }); } const page = await context.newPage(); if (cookie) { if (cookie.endsWith(';')) cookie = cookie.slice(0, -1); let cookies = cookie.split(';'); cookies = cookies.map(it=>{ let [name, value] = it.split('='); return {name: name.trim(), value: value.trim(), url}; }); context.addCookies(cookies); } await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); await page.goto(url); let text; let i = 0; while (true) { let new_text = await page.evaluate(() => document.body.innerText); if (i > 5 || new_text?.length > 200) { text = new_text; break; } i++; await new Promise(resolve=>setTimeout(resolve, 1000)); } await page.close(); //await context.close(); // await browser.close(); return text; } async function screenshot(url, cookie, js_enabled) { let { browser, context } = globalThis.state; if (!browser) { await locks.request('playwright_browser', async lock => { browser = globalThis.state.browser = await chromium.launch(); context = globalThis.state.context = await browser.newContext({ javaScriptEnabled: js_enabled }/*devices['iPhone 11']*/); }); } const page = await context.newPage(); if (cookie) { if (cookie.endsWith(';')) cookie = cookie.slice(0, -1); let cookies = cookie.split(';'); cookies = cookies.map(it=>{ let [name, value] = it.split('='); return {name: name.trim(), value: value.trim(), url}; }); context.addCookies(cookies); } //await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); await page.goto(url); await setTimeout(2000); // let id = crypto.randomUUID(); // let path = `/code/${id}.png`; // await page.screenshot({ path, fullPage: true }); const buffer = await page.screenshot({fullPage: true}); await page.close(); //await context.close(); // await browser.close(); return buffer; } async function evaluate(url, code) { let { browser, context } = globalThis.state; if (!browser) { await locks.request('playwright_browser', async lock => { browser = globalThis.state.browser = await chromium.launch(); context = globalThis.state.context = await browser.newContext({ javaScriptEnabled: true }/*devices['iPhone 11']*/); }); } const page = await context.newPage(); page.on('console', async (msg) => {console.log(msg);}); //await context.route("**/*.{png,jpg,jpeg,css,js}", route => route.abort()); await page.goto(url); let result = await page.evaluate(code); await page.close(); return result; } const server = http.createServer(async function(req, res) { const {query, pathname} = url.parse(req.url, true); res.setHeader('Access-Control-Allow-Origin', '*') let _url = query.url; let cookie = query.cookie; let js_enabled = query.js; try { if (pathname == '/text') { let text = await text_from_url(_url, cookie); res.end(text); } else if (pathname == '/screenshot') { let buffer = await screenshot(_url, cookie, js_enabled); res.writeHead(200,{'Content-type':'image/png'}); res.end(buffer); // const readStream = fs.createReadStream(path); // res.writeHead(200,{'Content-type':'image/png'}); // readStream.pipe(res); // res.end(); } else if (pathname == '/evaluate') { const buffers = []; for await (const chunk of req) { buffers.push(chunk); } const data = Buffer.concat(buffers).toString(); const {url, code} = JSON.parse(data); try { let result = await evaluate(url, code); res.end(''+result); } catch (e) { res.end(e.toString()); } } else { res.end('hello'); } } catch (e) { res.end(e.toString()); } }); const wss = new WebSocketServer({ server }); wss.on('connection', function connection(ws, req) { console.log('wss.connection', req.url, req.headers); ws.isAlive = true; ws.on('pong', function heartbeat(){ //'Pong messages are automatically sent in response to ping messages as required by the spec.' //console.log('pong'); this.isAlive = true; }); ws.on('message', function message(data) { console.log('received: %s', data); }); ws.addEventListener('close', function close() { console.log('ws.close'); }); //ws.send('something'); }); // https://github.com/websockets/ws const interval = setInterval(function ping() { wss.clients.forEach(function each(ws) { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; ws.ping(); ws.send('ping');//why: used by client to detect dead connection (because browser cant access ping/pong frames...(?)) }); }, 30000); server.listen(7860);