10 Commits
main ... v1.0.3

Author SHA1 Message Date
estebanthi
24b0ddf666 Add optional full cookie response
All checks were successful
CI / release (push) Successful in 1m38s
2026-02-04 10:01:50 +01:00
estebanthi
3c73bccb81 Add optional HTML response for IUAM
All checks were successful
CI / release (push) Successful in 2m44s
2026-02-04 09:36:52 +01:00
estebanthi
85d380d8e4 Add CACHE_ENABLED toggle for IUAM cache
All checks were successful
CI / release (push) Successful in 1m15s
2026-01-23 17:22:18 +01:00
estebanthi
44660055af Switch to alpine chromium image
All checks were successful
CI / release (push) Successful in 2m17s
2026-01-18 15:07:34 +01:00
estebanthi
c14052556a fixed dockerfile
Some checks failed
CI / release (push) Failing after 2m16s
2026-01-18 14:34:32 +01:00
estebanthi
34822965d2 fixed byuild
All checks were successful
CI / release (push) Successful in 1m59s
2026-01-18 14:25:52 +01:00
estebanthi
2b65730d77 fixed
All checks were successful
CI / release (push) Successful in 1m45s
2026-01-18 14:17:43 +01:00
estebanthi
6557f28aab fixed dependencies
All checks were successful
CI / release (push) Successful in 1m54s
2026-01-18 14:10:33 +01:00
estebanthi
c8bfc9c219 fixed dependencies
Some checks failed
CI / release (push) Failing after 2m12s
2026-01-18 14:01:07 +01:00
estebanthi
0b019eaa04 fixed vulnerability
Some checks failed
CI / release (push) Failing after 1m55s
2026-01-18 13:54:38 +01:00
5 changed files with 139 additions and 64 deletions

View File

@@ -1,43 +1,48 @@
FROM node:20.12.2-slim FROM node:20-alpine AS build
ENV NODE_ENV=production ENV NODE_ENV=production
# Install Chrome and dependencies RUN apk add --no-cache python3 make g++
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates wget gnupg xvfb fonts-liberation \
&& wget -qO- https://dl.google.com/linux/linux_signing_key.pub \
| gpg --dearmor -o /usr/share/keyrings/google-linux-signing-keyring.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux-signing-keyring.gpg] https://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends google-chrome-stable \
&& apt-get purge -y --auto-remove wget gnupg \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable RUN corepack enable
# Create a non-root user for running the app
RUN useradd --create-home --home-dir /app --shell /bin/sh appuser
# Set working directory
WORKDIR /app WORKDIR /app
# Entrypoint script COPY package.json pnpm-lock.yaml ./
COPY docker-entrypoint.sh /usr/local/bin/ RUN corepack prepare pnpm@10.28.0 --activate \
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh
# Copy and install dependencies
COPY --chown=appuser:appuser package.json pnpm-lock.yaml ./
USER appuser
RUN corepack prepare pnpm@9.0.0 --activate \
&& pnpm install --frozen-lockfile --prod \ && pnpm install --frozen-lockfile --prod \
&& pnpm store prune && pnpm store prune
# Copy app code COPY . .
COPY --chown=appuser:appuser . .
FROM node:20-alpine
ENV NODE_ENV=production
ENV CHROME_PATH=/usr/bin/chromium-browser
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
RUN apk add --no-cache chromium nss freetype harfbuzz ttf-freefont
# Remove npm/corepack to shrink attack surface and avoid bundled CVEs.
RUN rm -rf /usr/local/lib/node_modules/npm \
/usr/local/bin/npm \
/usr/local/bin/npx \
/usr/local/lib/node_modules/corepack \
/usr/local/bin/corepack
WORKDIR /app
COPY --from=build /app/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod 755 /usr/local/bin/docker-entrypoint.sh
COPY --from=build --chown=node:node /app/package.json /app/package.json
COPY --from=build --chown=node:node /app/index.js /app/index.js
COPY --from=build --chown=node:node /app/endpoints /app/endpoints
COPY --from=build --chown=node:node /app/node_modules /app/node_modules
RUN mkdir -p /app/cache && chown -R node:node /app
USER node
# Expose port (match your app's port)
EXPOSE 10000 EXPOSE 10000
# Start Xvfb and run the bot
CMD ["/usr/local/bin/docker-entrypoint.sh"] CMD ["/usr/local/bin/docker-entrypoint.sh"]

View File

@@ -63,6 +63,7 @@ npm start # Production mode
| `authToken` | `null` | API authentication token | | `authToken` | `null` | API authentication token |
| `browserLimit` | `20` | Max concurrent browsers | | `browserLimit` | `20` | Max concurrent browsers |
| `timeOut` | `60000` | Global timeout (ms) | | `timeOut` | `60000` | Global timeout (ms) |
| `CACHE_ENABLED` | `true` | Enable IUAM response cache |
## 📡 API Endpoints ## 📡 API Endpoints
@@ -75,7 +76,9 @@ Bypass Cloudflare protection and get cookies/tokens.
{ {
"mode": "iuam", "mode": "iuam",
"domain": "https://olamovies.watch/generate", "domain": "https://olamovies.watch/generate",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"include_html": true,
"include_cookies": true
} }
``` ```
@@ -83,6 +86,8 @@ Bypass Cloudflare protection and get cookies/tokens.
- **`mode`** (required): `"iuam"` or `"turnstile"` - **`mode`** (required): `"iuam"` or `"turnstile"`
- **`domain`** (required): Target website URL - **`domain`** (required): Target website URL
- **`user_agent`** (optional): User-Agent to use for the browser session - **`user_agent`** (optional): User-Agent to use for the browser session
- **`include_html`** (optional): `true` to include the final page HTML in IUAM responses
- **`include_cookies`** (optional): `true` to include all cookies in IUAM responses
- **`proxy`** (optional): Proxy configuration object - **`proxy`** (optional): Proxy configuration object
#### Response (Success) #### Response (Success)
@@ -90,7 +95,11 @@ Bypass Cloudflare protection and get cookies/tokens.
{ {
"cf_clearance": "eNm9UOgqoNDTP.fmAK9JfvirEmLVpmd.ZWIfdqQxuTc-1758610092-1.2-2NwZwW6nK23HrAH71MtvOek9vCiiS7pUBGIPtra_gSBxYxY2csa6hW0j7i...", "cf_clearance": "eNm9UOgqoNDTP.fmAK9JfvirEmLVpmd.ZWIfdqQxuTc-1758610092-1.2-2NwZwW6nK23HrAH71MtvOek9vCiiS7pUBGIPtra_gSBxYxY2csa6hW0j7i...",
"user_agent": "Mozilla/5.0...", "user_agent": "Mozilla/5.0...",
"elapsed_time": 3.05 "elapsed_time": 3.05,
"html": "<!DOCTYPE html><html>...</html>",
"cookies": [
{ "name": "cf_clearance", "value": "...", "domain": ".example.com" }
]
} }
``` ```

View File

@@ -1,33 +1,46 @@
#!/bin/sh #!/bin/sh
set -eu
rm -f /tmp/.X99-lock resolve_chrome_path() {
Xvfb :99 -screen 0 1024x768x24 & if [ -n "${CHROME_PATH:-}" ] && [ -x "$CHROME_PATH" ]; then
xvfb_pid=$! return 0
fi
export DISPLAY=:99 for candidate in \
/usr/bin/chromium-browser \
/usr/bin/chromium \
/usr/bin/google-chrome-stable \
/usr/bin/google-chrome \
/opt/google/chrome/google-chrome; do
if [ -x "$candidate" ]; then
CHROME_PATH="$candidate"
return 0
fi
done
npm start & if command -v chromium-browser >/dev/null 2>&1; then
app_pid=$! CHROME_PATH="$(command -v chromium-browser)"
return 0
fi
if command -v chromium >/dev/null 2>&1; then
CHROME_PATH="$(command -v chromium)"
return 0
fi
if command -v google-chrome-stable >/dev/null 2>&1; then
CHROME_PATH="$(command -v google-chrome-stable)"
return 0
fi
if command -v google-chrome >/dev/null 2>&1; then
CHROME_PATH="$(command -v google-chrome)"
return 0
fi
term_handler() { echo "No Chrome/Chromium executable found. Set CHROME_PATH to a valid binary." >&2
kill "$app_pid" 2>/dev/null || true exit 1
kill "$xvfb_pid" 2>/dev/null || true
} }
trap term_handler INT TERM resolve_chrome_path
export CHROME_PATH
export PUPPETEER_EXECUTABLE_PATH="${PUPPETEER_EXECUTABLE_PATH:-$CHROME_PATH}"
while kill -0 "$app_pid" 2>/dev/null; do exec node index.js
if ! kill -0 "$xvfb_pid" 2>/dev/null; then
echo "Xvfb exited; stopping app." >&2
kill "$app_pid" 2>/dev/null || true
wait "$app_pid" 2>/dev/null || true
exit 1
fi
sleep 1
done
wait "$app_pid"
app_status=$?
kill "$xvfb_pid" 2>/dev/null || true
wait "$xvfb_pid" 2>/dev/null || true
exit "$app_status"

View File

@@ -1,3 +1,25 @@
async function ensurePageLoaded(page, url) {
try {
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 10000 })
} catch (_) {}
}
async function getPageHtml(page) {
try {
return await page.content()
} catch (_) {
return null
}
}
async function getPageCookies(page, url) {
try {
return await page.cookies(url)
} catch (_) {
return null
}
}
async function cloudflare(data, page) { async function cloudflare(data, page) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (!data.domain) return reject(new Error("Missing domain parameter")) if (!data.domain) return reject(new Error("Missing domain parameter"))
@@ -10,11 +32,14 @@ async function cloudflare(data, page) {
if (!isResolved) { if (!isResolved) {
isResolved = true isResolved = true
const elapsedTime = (Date.now() - startTime) / 1000 const elapsedTime = (Date.now() - startTime) / 1000
resolve({ const response = {
cf_clearance: null, cf_clearance: null,
user_agent: userAgent, user_agent: userAgent,
elapsed_time: elapsedTime, elapsed_time: elapsedTime,
}) }
if (data.include_html) response.html = null
if (data.include_cookies) response.cookies = null
resolve(response)
} }
}, 20000) }, 20000)
@@ -52,11 +77,25 @@ async function cloudflare(data, page) {
isResolved = true isResolved = true
clearTimeout(cl) clearTimeout(cl)
resolve({ const response = {
cf_clearance, cf_clearance,
user_agent: userAgent, user_agent: userAgent,
elapsed_time: elapsedTime, elapsed_time: elapsedTime,
}) }
if (data.include_html || data.include_cookies) {
await ensurePageLoaded(page, data.domain)
}
if (data.include_html) {
response.html = await getPageHtml(page)
}
if (data.include_cookies) {
response.cookies = await getPageCookies(page, data.domain)
}
resolve(response)
} }
} }
} }

View File

@@ -13,6 +13,9 @@ global.timeOut = Number(process.env.timeOut) || 60000
const CACHE_DIR = path.join(__dirname, "cache") const CACHE_DIR = path.join(__dirname, "cache")
const CACHE_FILE = path.join(CACHE_DIR, "cache.json") const CACHE_FILE = path.join(CACHE_DIR, "cache.json")
const CACHE_TTL = 5 * 60 * 1000 const CACHE_TTL = 5 * 60 * 1000
const CACHE_ENABLED = process.env.CACHE_ENABLED
? !["0", "false", "no"].includes(process.env.CACHE_ENABLED.toLowerCase())
: true
function loadCache() { function loadCache() {
if (!fs.existsSync(CACHE_FILE)) return {} if (!fs.existsSync(CACHE_FILE)) return {}
@@ -59,10 +62,10 @@ if (process.env.NODE_ENV !== 'development') {
async function createBrowser(proxyServer = null) { async function createBrowser(proxyServer = null) {
const connectOptions = { const connectOptions = {
headless: false, headless: "new",
turnstile: true, turnstile: true,
connectOption: { defaultViewport: null }, connectOption: { defaultViewport: null },
disableXvfb: false, disableXvfb: true,
} }
if (proxyServer) { if (proxyServer) {
@@ -100,6 +103,12 @@ app.post('/cloudflare', async (req, res) => {
if (data.user_agent && typeof data.user_agent !== 'string') { if (data.user_agent && typeof data.user_agent !== 'string') {
return res.status(400).json({ message: 'Bad Request: invalid user_agent' }) return res.status(400).json({ message: 'Bad Request: invalid user_agent' })
} }
if (typeof data.include_html !== 'undefined' && typeof data.include_html !== 'boolean') {
return res.status(400).json({ message: 'Bad Request: invalid include_html' })
}
if (typeof data.include_cookies !== 'undefined' && typeof data.include_cookies !== 'boolean') {
return res.status(400).json({ message: 'Bad Request: invalid include_cookies' })
}
if (authToken && data.authToken !== authToken) { if (authToken && data.authToken !== authToken) {
return res.status(401).json({ message: 'Unauthorized' }) return res.status(401).json({ message: 'Unauthorized' })
} }
@@ -109,7 +118,7 @@ app.post('/cloudflare', async (req, res) => {
} }
let cacheKey, cached let cacheKey, cached
if (data.mode === "iuam") { if (CACHE_ENABLED && data.mode === "iuam" && !data.include_html && !data.include_cookies) {
cacheKey = JSON.stringify(data) cacheKey = JSON.stringify(data)
cached = readCache(cacheKey) cached = readCache(cacheKey)
@@ -147,7 +156,7 @@ app.post('/cloudflare', async (req, res) => {
.then(r => ({ ...r })) .then(r => ({ ...r }))
.catch(err => ({ code: 500, message: err.message })) .catch(err => ({ code: 500, message: err.message }))
if (!result.code || result.code === 200) { if (CACHE_ENABLED && !data.include_html && !data.include_cookies && (!result.code || result.code === 200)) {
writeCache(cacheKey, result) writeCache(cacheKey, result)
} }
break break