mirror of
https://github.com/TxtDot/webder.git
synced 2024-11-05 13:13:59 +03:00
fix: docker, docs, workflows
This commit is contained in:
parent
50a8c0aed8
commit
b3f6fb715d
13 changed files with 503 additions and 366 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
12
.env.example
12
.env.example
|
@ -1,5 +1,9 @@
|
|||
HOST="0.0.0.0"
|
||||
PORT=8080
|
||||
TIMEOUT=0
|
||||
WAIT_FOR_PAGE_LOAD_TIMEOUT=2000
|
||||
REVERSE_PROXY=false
|
||||
PORT=8080
|
||||
TIMEOUT=0 # Max timout for server response, 0 means no timeout
|
||||
REVERSE_PROXY=false # Set true if app running behind reverse_proxy
|
||||
|
||||
# Maximum timeout for waiting for page load, once the page is
|
||||
# loaded or the timeout is exceeded, rendering is terminated
|
||||
# and the page is rendered
|
||||
WAIT_FOR_PAGE_LOAD_TIMEOUT=2000
|
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
|
@ -5,15 +5,19 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
|
||||
- name: Start build
|
||||
run: npm run build
|
||||
run: pnpm run build
|
||||
|
|
14
.github/workflows/deploy.yml
vendored
14
.github/workflows/deploy.yml
vendored
|
@ -3,6 +3,10 @@ name: Create and publish a Docker image
|
|||
|
||||
# Configures this workflow to run every time a change is pushed to the branch called `release`.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
@ -20,13 +24,13 @@ jobs:
|
|||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
#
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
@ -34,14 +38,14 @@ jobs:
|
|||
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
|
||||
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
16
.github/workflows/format-check.yml
vendored
16
.github/workflows/format-check.yml
vendored
|
@ -5,15 +5,19 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: pnpm install
|
||||
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
- name: Start build
|
||||
run: pnpm run format:check
|
||||
|
|
31
Dockerfile
31
Dockerfile
|
@ -1,12 +1,27 @@
|
|||
FROM node:18-alpine as build
|
||||
FROM node:20-alpine as base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM node:18-alpine as run
|
||||
FROM base AS prod-deps
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
FROM base AS build
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
RUN pnpm run build
|
||||
|
||||
FROM node:20-alpine as run
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/dist/ /app/package*.json ./
|
||||
RUN npm install --omit=dev
|
||||
CMD npm run start:docker
|
||||
|
||||
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/dist /app/dist/
|
||||
COPY --from=build /app/package.json /app/package.json
|
||||
|
||||
EXPOSE 8080
|
||||
CMD [ "pnpm", "start" ]
|
13
README.md
13
README.md
|
@ -2,8 +2,9 @@
|
|||
|
||||
Proxy that renders client-side JavaScript apps (e.g. React apps) on server and returns the resulting HTML code.
|
||||
|
||||
- JS is disabled/unsupported, no proxy: blank page or "Enable JS to continue"
|
||||
- With webder: all desired content, no need to use JS interpreter
|
||||
| Response without proxy | With webder |
|
||||
| ----------------------------------------------------------------- | -------------------------------------------------- |
|
||||
| JS is disabled/unsupported, blank page or "Enable JS to continue" | all desired content, no need to use JS interpreter |
|
||||
|
||||
> [!WARNING]
|
||||
> No HTML purification is performed, so passing code directly from webder can lead to XSS attacks. Local network requests are not blocked too, check domains/IPs to avoid SSRF attacks.
|
||||
|
@ -11,13 +12,13 @@ Proxy that renders client-side JavaScript apps (e.g. React apps) on server and r
|
|||
|
||||
## Usage
|
||||
|
||||
`/render?url=...`
|
||||
The proxy is accessible at `/render?url=...` by default.
|
||||
|
||||
For available config fields, check `.env.example`.
|
||||
Docker is supported.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm start
|
||||
pnpm install
|
||||
pnpm run build
|
||||
pnpm start
|
||||
```
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
version: "3"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
txtdot:
|
||||
image: ghcr.io/txtdot/webder:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
- '8080:8080'
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ".env:/app/.env"
|
||||
- '.env:/app/.env'
|
||||
|
|
17
package.json
17
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "webder",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Http/s proxy that render pages with js and returns HTML",
|
||||
"main": "dist/app.js",
|
||||
"private": true,
|
||||
|
@ -23,19 +23,20 @@
|
|||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/node": "^20.11.30",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"@types/node": "^20.12.12",
|
||||
"@typescript-eslint/eslint-plugin": "^7.9.0",
|
||||
"@typescript-eslint/parser": "^7.9.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"eslint": "^8.57.0",
|
||||
"tsc-watch": "^6.0.4",
|
||||
"typescript": "^5.4.3"
|
||||
"tsc-watch": "^6.2.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/one-line-logger": "^1.3.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"fastify": "^4.26.2",
|
||||
"fastify": "^4.27.0",
|
||||
"prettier": "^3.2.5",
|
||||
"puppeteer": "^22.6.0",
|
||||
"puppeteer": "^22.9.0",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-adblocker": "^2.13.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2"
|
||||
|
|
710
pnpm-lock.yaml
generated
710
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
26
src/app.ts
26
src/app.ts
|
@ -1,7 +1,7 @@
|
|||
import { ConfigService } from "./config/config.service";
|
||||
import Fastify from "fastify";
|
||||
import getConfig from "./config/main";
|
||||
import { puppeteer } from "./puppeteer";
|
||||
import { ConfigService } from './config/config.service';
|
||||
import Fastify from 'fastify';
|
||||
import getConfig from './config/main';
|
||||
import { puppeteer } from './puppeteer';
|
||||
|
||||
class App {
|
||||
config: ConfigService;
|
||||
|
@ -10,21 +10,25 @@ class App {
|
|||
}
|
||||
listen() {
|
||||
const fastify = Fastify({
|
||||
logger: true,
|
||||
logger: {
|
||||
transport: {
|
||||
target: '@fastify/one-line-logger',
|
||||
},
|
||||
},
|
||||
trustProxy: this.config.reverse_proxy,
|
||||
connectionTimeout: this.config.timeout,
|
||||
});
|
||||
|
||||
fastify.get<{ Querystring: { url: string } }>(
|
||||
"/render",
|
||||
'/render',
|
||||
{
|
||||
schema: {
|
||||
querystring: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: "string" },
|
||||
url: { type: 'string' },
|
||||
},
|
||||
required: ["url"],
|
||||
required: ['url'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -40,14 +44,14 @@ class App {
|
|||
idleTime: 100,
|
||||
});
|
||||
} catch {
|
||||
reply.header("x-message", "Page load timeout");
|
||||
reply.header('x-message', 'Page load timeout');
|
||||
}
|
||||
|
||||
const data = await page.content();
|
||||
|
||||
browser.close();
|
||||
|
||||
reply.type("text/html");
|
||||
reply.type('text/html');
|
||||
return data;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { config } from "dotenv";
|
||||
import { config } from 'dotenv';
|
||||
|
||||
export class ConfigService {
|
||||
public readonly host: string;
|
||||
|
@ -9,7 +9,7 @@ export class ConfigService {
|
|||
constructor() {
|
||||
config();
|
||||
|
||||
this.host = process.env.HOST || "0.0.0.0";
|
||||
this.host = process.env.HOST || '0.0.0.0';
|
||||
this.port = Number(process.env.PORT) || 8080;
|
||||
|
||||
this.timeout = Number(process.env.TIMEOUT) || 0;
|
||||
|
@ -21,6 +21,6 @@ export class ConfigService {
|
|||
|
||||
parseBool(value: string | undefined, def: boolean): boolean {
|
||||
if (!value) return def;
|
||||
return value === "true" || value === "1";
|
||||
return value === 'true' || value === '1';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ConfigService } from "./config.service";
|
||||
import { ConfigService } from './config.service';
|
||||
|
||||
let configSvc: ConfigService | undefined;
|
||||
|
||||
|
|
Loading…
Reference in a new issue