Playwright / server.js
nekoyama12's picture
Rename Dockerfile to server.js
ecfea87 verified
Raw
History Blame Contribute Delete
8.85 kB
const express = require('express')
const { chromium } = require('playwright-extra')
const StealthPlugin = require('puppeteer-extra-plugin-stealth')
const crypto = require('crypto')
chromium.use(StealthPlugin())
const app = express()
app.use(express.json())
const sessions = new Map()
const SESSION_TIMEOUT = 10 * 60 * 1000
function makeId() {
return crypto.randomBytes(8).toString('hex')
}
function touchSession(id) {
const s = sessions.get(id)
if (!s) return
clearTimeout(s.timer)
s.timer = setTimeout(async () => {
await s.browser.close().catch(() => {})
sessions.delete(id)
}, SESSION_TIMEOUT)
}
async function getSession(id, res) {
const s = sessions.get(id)
if (!s) {
res.status(404).json({ error: 'Session not found or expired' })
return null
}
touchSession(id)
return s
}
app.post('/session/create', async (req, res) => {
try {
const {
headless = true,
userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
viewport = { width: 1280, height: 720 },
locale = 'en-US',
extraArgs = []
} = req.body || {}
const browser = await chromium.launch({
headless,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-blink-features=AutomationControlled',
...extraArgs
]
})
const context = await browser.newContext({ userAgent, viewport, locale })
const page = await context.newPage()
const id = makeId()
const timer = setTimeout(async () => {
await browser.close().catch(() => {})
sessions.delete(id)
}, SESSION_TIMEOUT)
sessions.set(id, { browser, context, page, timer, createdAt: Date.now() })
res.json({ ok: true, sessionId: id })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/goto', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { url, waitUntil = 'networkidle', timeout = 30000 } = req.body
if (!url) return res.status(400).json({ error: 'Missing url' })
const response = await s.page.goto(url, { waitUntil, timeout })
res.json({ ok: true, status: response?.status(), url: s.page.url() })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/content', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const content = await s.page.content()
const url = s.page.url()
res.json({ ok: true, url, content })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/cookies', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const cookies = await s.context.cookies()
const cfClearance = cookies.find(c => c.name === 'cf_clearance')
const cookieHeader = cookies.map(c => `${c.name}=${c.value}`).join('; ')
res.json({ ok: true, cookies, cfClearance: cfClearance?.value || null, cookieHeader })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/set-cookies', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { cookies } = req.body
if (!Array.isArray(cookies)) return res.status(400).json({ error: 'cookies must be array' })
await s.context.addCookies(cookies)
res.json({ ok: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/eval', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { script } = req.body
if (!script) return res.status(400).json({ error: 'Missing script' })
const result = await s.page.evaluate(script)
res.json({ ok: true, result })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/click', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { selector, timeout = 5000 } = req.body
if (!selector) return res.status(400).json({ error: 'Missing selector' })
await s.page.click(selector, { timeout })
res.json({ ok: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/type', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { selector, text, delay = 50 } = req.body
if (!selector || !text) return res.status(400).json({ error: 'Missing selector or text' })
await s.page.type(selector, text, { delay })
res.json({ ok: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/wait', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { selector, timeout = 10000, state = 'visible' } = req.body
if (!selector) return res.status(400).json({ error: 'Missing selector' })
await s.page.waitForSelector(selector, { timeout, state })
res.json({ ok: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/screenshot', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { fullPage = false } = req.body || {}
const buf = await s.page.screenshot({ fullPage, type: 'png' })
res.set('Content-Type', 'image/png')
res.send(buf)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/fetch', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { url, method = 'GET', headers = {}, body } = req.body
if (!url) return res.status(400).json({ error: 'Missing url' })
const result = await s.page.evaluate(async ({ url, method, headers, body }) => {
const r = await fetch(url, { method, headers, body })
const text = await r.text()
let data
try { data = JSON.parse(text) } catch { data = text }
return { status: r.status, ok: r.ok, data }
}, { url, method, headers, body })
res.json({ ok: true, ...result })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.post('/session/:id/headers', async (req, res) => {
const s = await getSession(req.params.id, res)
if (!s) return
try {
const { headers } = req.body
if (!headers) return res.status(400).json({ error: 'Missing headers' })
await s.context.setExtraHTTPHeaders(headers)
res.json({ ok: true })
} catch (err) {
res.status(500).json({ error: err.message })
}
})
app.get('/session/:id/status', async (req, res) => {
const s = sessions.get(req.params.id)
if (!s) return res.status(404).json({ error: 'Session not found' })
res.json({
ok: true,
sessionId: req.params.id,
url: s.page.url(),
createdAt: s.createdAt,
age: Date.now() - s.createdAt
})
})
app.delete('/session/:id', async (req, res) => {
const s = sessions.get(req.params.id)
if (!s) return res.status(404).json({ error: 'Session not found' })
clearTimeout(s.timer)
await s.browser.close().catch(() => {})
sessions.delete(req.params.id)
res.json({ ok: true, message: 'Session closed' })
})
app.get('/sessions', (req, res) => {
const list = [...sessions.entries()].map(([id, s]) => ({
sessionId: id,
url: s.page.url(),
createdAt: s.createdAt,
age: Date.now() - s.createdAt
}))
res.json({ ok: true, count: list.length, sessions: list })
})
app.get('/', (req, res) => {
res.json({
ok: true,
name: 'Playwright Browser API',
endpoints: {
'POST /session/create': 'Create new browser session',
'POST /session/:id/goto': 'Navigate to URL { url, waitUntil, timeout }',
'POST /session/:id/content': 'Get page HTML content',
'POST /session/:id/cookies': 'Get all cookies + cf_clearance',
'POST /session/:id/set-cookies': 'Set cookies { cookies: [] }',
'POST /session/:id/eval': 'Execute JS { script }',
'POST /session/:id/click': 'Click element { selector }',
'POST /session/:id/type': 'Type text { selector, text, delay }',
'POST /session/:id/wait': 'Wait for selector { selector, state, timeout }',
'POST /session/:id/screenshot': 'Get screenshot (returns image/png)',
'POST /session/:id/fetch': 'Fetch URL from browser context { url, method, headers, body }',
'POST /session/:id/headers': 'Set extra HTTP headers { headers: {} }',
'GET /session/:id/status': 'Session info',
'DELETE /session/:id': 'Close session',
'GET /sessions': 'List all active sessions'
}
})
})
app.listen(7860, () => console.log('Playwright API running on :7860'))