aripbae commited on
Commit
5b1b707
·
verified ·
1 Parent(s): 38c252f

Create index.js

Browse files
Files changed (1) hide show
  1. index.js +125 -0
index.js ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { serve } from '@hono/node-server'
2
+ import { serveStatic } from '@hono/node-server/serve-static'
3
+ import { Hono } from 'hono'
4
+ import { logger } from 'hono/logger'
5
+ import os from 'node:os'
6
+ import util from 'node:util'
7
+ import { isMainThread, parentPort, Worker } from 'node:worker_threads'
8
+ import playwright from 'playwright-extra'
9
+ import prettyBytes from 'pretty-bytes'
10
+ import prettyMs from 'pretty-ms'
11
+ import pluginStealth from 'puppeteer-extra-plugin-stealth'
12
+
13
+ const TIMEOUT_MS = 60_000
14
+ const MAX_CODE_LENGTH = 50_000
15
+
16
+ if (!isMainThread) {
17
+ const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
18
+
19
+ parentPort.on('message', async (code) => {
20
+ try {
21
+ const fn = new AsyncFunction('playwright', 'pluginStealth', 'console', code)
22
+ parentPort.postMessage({ result: await fn(playwright, pluginStealth, console) })
23
+ } catch (error) {
24
+ parentPort.postMessage({ error: error?.message ?? String(error) })
25
+ }
26
+ })
27
+ } else {
28
+ const isNumber = (v) => typeof v === 'number' && !isNaN(v)
29
+
30
+ const transformObj = (obj, cb) => JSON.parse(
31
+ JSON.stringify(
32
+ obj,
33
+ (key, val) => cb(key, val) ? prettyBytes(val) : val
34
+ ).replace(/_(\w)/g, (_, g) => g.toUpperCase())
35
+ )
36
+
37
+ const getServerStats = () => {
38
+ const stats = {}
39
+ stats.uptime = prettyMs(process.uptime() * 1e3)
40
+ stats.osUptime = prettyMs(os.uptime() * 1e3)
41
+
42
+ const report = process.report?.getReport?.()
43
+ if (report) Object.assign(stats, {
44
+ header: report.header,
45
+ javascriptHeap: transformObj(
46
+ report.javascriptHeap,
47
+ (k, v) => !/(ContextCount|Garbage)/i.test(k) && isNumber(v)
48
+ ),
49
+ resourceUsage: transformObj(
50
+ report.resourceUsage,
51
+ (k, v) => !/(Percent|IO|reads|write)/i.test(k) && isNumber(v)
52
+ ),
53
+ uvthreadResourceUsage: transformObj(
54
+ report.uvthreadResourceUsage,
55
+ (k, v) => isNumber(v)
56
+ )
57
+ })
58
+
59
+ stats.memoryUsage = transformObj(
60
+ process.memoryUsage(),
61
+ (k, v) => isNumber(v)
62
+ )
63
+
64
+ return stats
65
+ }
66
+
67
+ const runBrowserScript = (code) =>
68
+ new Promise((resolve, reject) => {
69
+ const worker = new Worker(new URL(import.meta.url), {
70
+ resourceLimits: { maxOldGenerationSizeMb: 512 }
71
+ })
72
+
73
+ const timer = setTimeout(() => {
74
+ worker.terminate()
75
+ reject(new Error('Execution timeout'))
76
+ }, TIMEOUT_MS)
77
+
78
+ const cleanup = () => {
79
+ clearTimeout(timer)
80
+ worker.terminate()
81
+ }
82
+
83
+ worker.postMessage(code)
84
+ worker.on('message', ({ result, error }) => {
85
+ cleanup()
86
+ error ? reject(new Error(error)) : resolve(result)
87
+ })
88
+ worker.on('error', (err) => {
89
+ cleanup()
90
+ reject(err)
91
+ })
92
+ worker.on('exit', (exitCode) => {
93
+ clearTimeout(timer)
94
+ exitCode !== 0 && reject(new Error(`Worker exited with code ${exitCode}`))
95
+ })
96
+ })
97
+
98
+ const app = new Hono()
99
+
100
+ app.use(logger())
101
+ app.use('/file/*', serveStatic({
102
+ root: os.tmpdir(),
103
+ rewriteRequestPath: (path) => path.replace(/^\/file/, '')
104
+ }))
105
+
106
+ app.get('/', (c) => c.json(getServerStats()))
107
+
108
+ app.post('/run', async (c) => {
109
+ const body = await c.req.json().catch(() => ({}))
110
+ const { code } = body
111
+
112
+ if (!code) return c.json({ error: 'Code is required' }, 400)
113
+ if (typeof code !== 'string') return c.json({ error: 'Code must be a string' }, 400)
114
+ if (code.length > MAX_CODE_LENGTH) return c.json({ error: 'Code too long' }, 400)
115
+
116
+ try {
117
+ return c.json({ result: await runBrowserScript(code) })
118
+ } catch (e) {
119
+ return c.json({ error: util.format(e) }, 500)
120
+ }
121
+ })
122
+
123
+ const port = process.env.SPACE_ID ? 7860 : +(process.env.PORT || 3000)
124
+ serve({ fetch: app.fetch, port }, () => console.log(`Playwright API listening at http://localhost:${port}`))
125
+ }