Skip to content

lifeart/revamp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

182 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Re:Vamp

CI Docker codecov License: MIT Node.js Version TypeScript Docker Image

ReVamp logo

Legacy Browser Compatibility Proxy — Transform modern web content for older devices like iPads and iPods running iOS 9+.

Give your old iPad 2, iPad Mini, or iPod Touch a second life by making modern websites work again!

📑 Table of Contents

✨ Features

Core Proxy Features

  • 🔧 JavaScript Transpilation — Babel transforms modern JS (optional chaining, nullish coalescing, async/await) to ES5/ES6
  • 🎨 CSS Transformation — PostCSS adds vendor prefixes and transforms modern CSS features
  • 📄 HTML Modification — Injects polyfills and can remove ads/tracking scripts
  • 🖼️ Image Optimization — Converts WebP/AVIF to JPEG/PNG for legacy browser support
  • 📦 ES Module Bundling — esbuild-based bundler converts ES modules to legacy-compatible bundles
  • 🔒 HTTPS Interception — Transparent SSL/TLS interception with auto-generated certificates
  • 🧦 SOCKS5 Proxy — Device-wide traffic routing (recommended for iOS)
  • 🌐 HTTP Proxy — Alternative proxy method
  • 💾 Smart Caching — Memory + disk caching for faster repeat visits
  • 🎭 User-Agent Spoofing — Bypass browser detection (optional)
  • 🚫 Ad & Tracking Removal — Block common ad networks and trackers
  • 📱 Easy Setup — Built-in captive portal for certificate installation
  • 🔄 Remote Service Workers — Bridge server for Service Worker emulation on legacy devices
  • 📱 Multi-Device Support — Per-client configuration with IP-based settings
  • 🎯 Domain Profiles — Per-domain filtering rules with pattern matching (exact, wildcard, regex)

Polyfills for Legacy Browsers (30+)

  • Promise.finally, Promise.allSettled — Modern Promise methods
  • fetch API — Full fetch/Headers/Response polyfill
  • IntersectionObserver — Lazy loading support
  • ResizeObserver — Element resize detection
  • MutationObserver — DOM mutation detection (enhanced)
  • WeakMap/WeakSet — Weak reference collections
  • Web Components — Custom Elements v1 and basic Shadow DOM
  • Intl API — Basic DateTimeFormat and NumberFormat
  • Service Worker Bypass — Disables SW registration for compatibility
  • Lazy Loading — Polyfill for loading="lazy" attribute
  • AbortController — Request cancellation support
  • Array methods — flat, flatMap, from, includes, and more
  • Object methods — entries, values, fromEntries
  • String methods — padStart, padEnd, replaceAll
  • CustomEvent — Custom event creation and dispatch

CSS Enhancements

  • CSS Grid → Flexbox Fallback — Auto-generate flexbox fallbacks for CSS Grid
  • Dark Mode Stripping — Remove prefers-color-scheme media queries
  • Vendor Prefixes — Automatic -webkit- prefixes for Safari 9

DevOps & Monitoring

  • 🎛️ Admin Panel — Full-featured web UI at /__revamp__/admin for managing profiles and configuration
  • 📊 Metrics Dashboard — Real-time web UI at /__revamp__/metrics
  • 🐳 Docker Support — Multi-arch image on ghcr.io (amd64/arm64) plus production and development Dockerfiles
  • 📋 PAC File Generation — Auto-generate proxy config files
  • ⚙️ External Config — JSON config for blocked domains
  • 🔌 Plugin System — Extensible architecture with hooks for request/response lifecycle, transforms, and filtering

Performance Optimizations

  • 🧵 Babel & PostCSS Worker Pools — JS and CSS transforms run in parallel worker threads via tinypool, keeping the main event loop responsive
  • ⚡ Async Compression — Non-blocking gzip compression/decompression
  • 🎚️ Configurable Compression — Adjustable gzip level (1-9) for speed vs size tradeoff
  • 📈 Up to 9x speedup — Parallel compression achieves significant performance gains

🚀 Quick Start

Installation

# Clone the repository
git clone https://github.com/lifeart/revamp.git
cd revamp

# Install dependencies (pnpm recommended)
pnpm install

# Start the proxy
pnpm start

# Or in development mode (auto-reload)
pnpm dev

Docker

Run the published image (no build needed)

A multi-arch image (linux/amd64 + linux/arm64) is published to GitHub Container Registry on every push to master, with a signed build-provenance attestation:

docker run -d --name revamp \
  -p 1080:1080 -p 8080:8080 -p 8888:8888 \
  -v revamp-certs:/app/.revamp-certs \
  -v revamp-cache:/app/.revamp-cache \
  ghcr.io/lifeart/revamp:latest

Then open http://<host-ip>:8888 on your device to install the certificate (see Device Setup). Mounting the two named volumes persists the generated CA certificate and the transform cache across restarts — without them the CA is regenerated on each run and every device has to re-trust it.

Available tags: latest (newest master), master, and the commit SHA. Verify the provenance of an image with:

gh attestation verify oci://ghcr.io/lifeart/revamp:latest --repo lifeart/revamp

Configure via environment variables

Every CLI flag has a matching REVAMP_-prefixed environment variable (see CLI Options):

docker run -d --name revamp \
  -p 9090:9090 -p 1080:1080 -p 8888:8888 \
  -e REVAMP_HTTP_PROXY_PORT=9090 \
  -e REVAMP_LOG_LEVEL=debug \
  -e REVAMP_REMOVE_ADS=false \
  ghcr.io/lifeart/revamp:latest

Build from source

# Build and run locally
docker build -t revamp .
docker run -p 1080:1080 -p 8080:8080 -p 8888:8888 revamp

# Or use Docker Compose (persists certs/cache in named volumes,
# mounts ./config read-only, and restarts unless stopped)
docker-compose up -d

# Development mode with hot-reload
docker-compose --profile dev up revamp-dev

Device Setup

  1. Start Revamp on your computer
  2. Find your local IP: After pnpm start, look at the terminal for 🌐 Your Local IP Address(es): 192.168.x.x. Use that IP on your iPad.
  3. Open the setup page on your legacy device by navigating to http://<that IP>:8888
  4. Install the certificate and enable trust (see detailed instructions below)
  5. Configure proxy in Wi-Fi settings

📱 Detailed Setup

Installing the CA Certificate

When you start Revamp, a CA certificate is generated at .revamp-certs/ca.crt.

On iOS:

The captive portal sniffs your User-Agent and renders the right copy automatically. The two paths:

iOS 9 / 10.0–10.2 (e.g. iPad 2):

  1. Open http://<your IP>:8888 in Safari
  2. Tap "Download Certificate"
  3. Go to Settings → General → Profile
  4. Tap on Revamp Proxy CA and tap Install

(There is no Trust Settings step on these versions — installing the profile grants trust outright.)

iOS 10.3+ (including iOS 12.2 hardening):

  1. Open http://<your IP>:8888 in Safari
  2. Tap "Download Certificate"
  3. Go to Settings → General → VPN & Device Management (older iOS: Settings → General → Profile)
  4. Install the downloaded profile
  5. Go to Settings → General → About → Certificate Trust Settings
  6. Enable full trust for Revamp Proxy CA

On macOS:

  1. Open the .revamp-certs/ca.crt file
  2. Add to Keychain Access
  3. Find "Revamp Proxy CA", double-click, expand Trust
  4. Set "When using this certificate" to "Always Trust"

Configuring the Proxy

SOCKS5 (Recommended for iOS):

  • Settings → Wi-Fi → [Your Network] → Configure Proxy
  • Select Manual
  • Server: your IP, see the 🌐 Your Local IP Address(es) banner printed by pnpm start (also covered in Device Setup)
  • Port: 1080

HTTP Proxy (Alternative):

  • Server: your IP, see the 🌐 Your Local IP Address(es) banner printed by pnpm start (also covered in Device Setup)
  • Port: 8080

🧰 Configuration

CLI Options

Every flag has a matching REVAMP_-prefixed environment variable. Precedence: CLI flag > environment variable > built-in default.

# Pick different ports and keep the proxy local-only
pnpm start -- --http-proxy-port 9090 --bind-address 127.0.0.1

# Same overrides via environment variables
REVAMP_HTTP_PROXY_PORT=9090 REVAMP_BIND_ADDRESS=127.0.0.1 pnpm start
Flag Env var Default Description
--socks5-port <port> REVAMP_SOCKS5_PORT 1080 SOCKS5 proxy port
--http-proxy-port <port> REVAMP_HTTP_PROXY_PORT 8080 HTTP proxy port
--captive-portal-port <port> REVAMP_CAPTIVE_PORTAL_PORT 8888 Captive portal (certificate download) port
--bind-address <addr> REVAMP_BIND_ADDRESS 0.0.0.0 0.0.0.0 for LAN access, 127.0.0.1 for localhost only
--cache-dir <dir> REVAMP_CACHE_DIR ./.revamp-cache Cache directory
--cert-dir <dir> REVAMP_CERT_DIR ./.revamp-certs Certificate directory
--log-level <level> REVAMP_LOG_LEVEL info Log verbosity: debug, info, warn, error, silent

Every feature toggle from the example below (transformJs, removeAds, cacheEnabled, …) is also available as a kebab-case boolean flag, negatable with a no- prefix: e.g. --no-remove-ads or REVAMP_REMOVE_ADS=false. Run pnpm start -- --help for the full list of all 21 options with their defaults and env vars; --version prints the version.

Note: ports, bind address, and the cache/cert directories are read once at startup — changing them via the runtime config API logs a warning and requires a restart to take effect.

Programmatic Configuration

Edit src/config/index.ts or pass options when creating the server:

import { createRevampServer } from "revamp";

const server = createRevampServer({
  // Server ports
  socks5Port: 1080,
  httpProxyPort: 8080,
  captivePortalPort: 8888,

  // Target browsers (Browserslist format)
  targets: ["safari 9", "ios 9"],

  // Feature toggles
  transformJs: true, // Babel transpilation
  transformCss: true, // PostCSS transformation
  transformHtml: true, // HTML polyfill injection
  bundleEsModules: true, // Bundle ES modules for legacy browsers
  emulateServiceWorkers: true, // Service Worker bypass/emulation
  remoteServiceWorkers: true, // Remote Service Worker bridge (default: false, requires Playwright)
  removeAds: true, // Block ad domains
  removeTracking: true, // Block tracking domains
  injectPolyfills: true, // Add polyfills for missing APIs
  spoofUserAgent: true, // Send modern User-Agent to servers
  spoofUserAgentInJs: true, // Override navigator.userAgent

  // Cache settings
  cacheEnabled: true,
  cacheTTL: 3600, // seconds

  // Performance tuning
  compressionLevel: 4, // gzip level 1-9 (1=fastest, 9=smallest)

  // TLS / upstream certificate validation (T8). Default: validate upstream
  // TLS certs and surface failures as a 502. Set this to `true` ONLY if you
  // knowingly need to talk to self-signed dev/staging servers — Revamp
  // re-signs upstream traffic with its own CA, so accepting an invalid
  // upstream cert silently launders any Wi-Fi MITM into a green padlock on
  // the iPad.
  allowInsecureUpstream: false,
});

server.start();

Embedding

createRevampServer returns a handle with start(), stop(), getConfig(), updateConfig(), clearCache(), and getCacheStats(), so Re:Vamp can run inside a host application. By default logs go to the console; embedders can swap the backend with setLoggerBackend (level filtering via logLevel / setLogLevel still applies before the backend is invoked):

import { createRevampServer, setLoggerBackend } from "revamp";

// Route Revamp's output into your own logger
setLoggerBackend({
  debug: (...args) => myLogger.debug(...args),
  info: (...args) => myLogger.info(...args),
  warn: (...args) => myLogger.warn(...args),
  error: (...args) => myLogger.error(...args),
});

const server = createRevampServer({
  bindAddress: "127.0.0.1",
  logLevel: "warn",
});
server.start();
// ... later
server.stop();

Pass consoleLoggerBackend (also exported) to restore the default sink.

Runtime Configuration API

You can change settings at runtime via the config API:

// From your legacy device's browser console or code:
fetch("http://any-proxied-site/__revamp__/config", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    transformJs: false, // Disable JS transformation
    removeAds: false, // Allow ads
  }),
});

🧱 Architecture

Request Lifecycle

Both proxy stacks converge on the same pipeline:

Legacy Device
   │
   ├─ SOCKS5 (port 1080) ──┐  SOCKS5 handshake / HTTP CONNECT, then TLS
   │                       │  interception with a per-domain certificate
   └─ HTTP proxy (8080) ───┤  minted from the Revamp CA
                           ▼
              ┌────────────────────────────┐        ┌──────────────────────┐
              │ Is it /__revamp__/* ?      │─ yes ─▶│ API Router           │
              └────────────┬───────────────┘        │ (admin, config,      │
                           │ no                     │  domains, metrics,   │
                           ▼                        │  PAC, SW, plugins)   │
   1. Resolve client IP, effective config, and      │ NO transforms        │
      domain profile (resolved once per request,    │ NO caching           │
      then threaded through the whole pipeline)     │ NO filtering         │
   2. `request:pre` plugin hooks · domain/URL       └──────────────────────┘
      blocking (ads, tracking, custom filters)
   3. Cache lookup (memory LRU → disk)
   4. Upstream fetch (TLS validated by default;
      optional User-Agent spoofing)
   5. Transform pipeline: binary lane (images) →
      charset decode → `transform:pre` hooks →
      transformer registry (js / css / html) →
      `transform:post` hooks
   6. Cache store · `response:post` plugin hooks
   7. Compress (gzip) and respond

/__revamp__/* requests are matched before the proxy pipeline runs, so the admin panel, config API, and metrics always work untouched — regardless of how aggressive the filtering configuration is.

API Router

All /__revamp__/* endpoints dispatch through a single shared router (src/proxy/api-router.ts). Both proxy stacks normalize their requests into one common shape and route through the same table, so every endpoint is defined exactly once: adding an endpoint is one router.register(...) line in the owning module (core, config, domain rules, or plugins).

Transformer Registry

Content transformation is dispatched through an ordered registry (src/transformers/registry.ts) with two lanes:

  • Text lane — string in, string out: the built-in js (Babel), css (PostCSS), and html (Cheerio) transformers.
  • Binary lane — Buffer in, { data, contentType } out: the built-in image transformer (WebP/AVIF → JPEG/PNG).

Built-ins are plain registry entries. Plugins add their own transformers via context.registerTransformer; plugin transformers run before built-ins, the first match wins, and a throwing plugin transformer is logged and skipped so the response is never broken.

Plugin Hooks

Plugins observe and modify the lifecycle through 10 hooks: request:pre, response:post, transform:pre, transform:post, filter:decision, config:resolution, domain:lifecycle, cache:get, cache:set, and metrics:record. See Plugin System for the full surface.

Worker Pools

Babel (JS) and PostCSS (CSS) transforms run in tinypool worker threads (js-worker.ts, css-worker.ts), keeping the main event loop free for concurrent connections. Moving PostCSS off the main thread cut event-loop blocking on large CSS files from ~880ms to ~11.5ms. Inline <script> blocks in an HTML document are transformed concurrently across the pool.

Cache Tiers

Two tiers: an in-memory LRU for hot data (100 MB cap) backed by a persistent disk cache. Keys incorporate the client IP, HTTP method, config and domain-profile hashes (memoized by object identity), Vary-named request header values, and content type. Responses carrying Set-Cookie / Cache-Control: private|no-store — and requests carrying Cookie / Authorization — are never cached. The ESM bundler keeps its own bounded LRU module cache.

Module Map

Directory Responsibility
src/config/ Config defaults and getters, CLI/env parsing (cli.ts), per-client config, domain profiles
src/proxy/ HTTP + SOCKS5 stacks, TLS interception, API router, focused helpers (cors, compression, charset, content-type, blocking, transform-pipeline, user-agent, client-ip)
src/transformers/ Transformer registry, js/css/html/image transformers, worker pools, ESM bundler (esm/), SW bundler, polyfills
src/plugins/ Plugin manifests, loader, registry, sandboxed context, hook executor, plugin REST API, testing utilities
src/cache/ Memory LRU + disk cache
src/certs/ CA generation + per-domain certificate minting
src/logger/ Leveled swappable logger, log sanitization, JSON request logging
src/metrics/ Metrics collection + dashboard
src/pac/ PAC file generation
src/portal/ Captive portal (certificate install, PAC URLs)
src/filters/ Ad/tracking pattern management

📁 Project Structure

src/
├── index.ts              # Main entry point (CLI + createRevampServer)
├── config/               # Configuration management
│   ├── index.ts          # Config defaults and getters
│   ├── cli.ts            # CLI flag / REVAMP_* env var parsing
│   ├── client-options.ts # Single source of truth for client options
│   ├── domain-rules.ts   # Domain profile types
│   ├── domain-manager.ts # Profile CRUD and matching
│   └── storage.ts        # File persistence utilities
├── filters/              # Modular filtering system
│   └── index.ts          # Ad/tracking pattern management
├── logger/               # Leveled swappable logger
│   ├── log.ts            # log.debug/info/warn/error, setLoggerBackend
│   ├── sanitize.ts       # Log output sanitization
│   └── json-request-logger.ts # Optional JSON request logging
├── plugins/              # Plugin system
│   ├── index.ts          # Public API exports (revamp/plugin)
│   ├── types.ts          # Core types (PluginManifest, RevampPlugin, etc.)
│   ├── hooks.ts          # Hook type definitions
│   ├── registry.ts       # Plugin registry (singleton)
│   ├── loader.ts         # Plugin discovery & lifecycle
│   ├── context.ts        # Sandboxed plugin context API
│   ├── hook-executor.ts  # Interceptor chain execution
│   ├── validation.ts     # Manifest + config schema validation (ajv)
│   ├── testing.ts        # Plugin testing utilities
│   └── api.ts            # REST endpoints for plugin management
├── proxy/                # Proxy servers
│   ├── http-proxy.ts     # HTTP/HTTPS proxy
│   ├── socks5.ts         # SOCKS5 proxy
│   ├── socks5-protocol.ts # SOCKS5 protocol implementation
│   ├── http-client.ts    # HTTP request utilities
│   ├── api-router.ts     # Shared router for all /__revamp__/* endpoints
│   ├── revamp-api.ts     # Core API endpoints + shared router instance
│   ├── config-endpoint.ts # Runtime config endpoint
│   ├── domain-rules-api.ts # Domain profiles REST API
│   ├── transform-pipeline.ts # transformContent orchestrator
│   ├── proxy-hooks.ts    # request:pre / response:post hook plumbing
│   ├── blocking.ts       # Domain/URL blocking
│   ├── cors.ts           # CORS header builders/filtering
│   ├── compression.ts    # gzip/brotli/deflate helpers
│   ├── charset.ts        # Charset detection/decoding
│   ├── content-type.ts   # Content-type detection
│   ├── user-agent.ts     # User-Agent spoofing
│   ├── client-ip.ts      # Client IP resolution
│   ├── shared.ts         # Compatibility facade (re-exports the above)
│   ├── remote-sw-server.ts # Remote Service Worker bridge
│   └── types.ts          # Type definitions
├── transformers/         # Content transformation
│   ├── registry.ts       # Transformer registry (text + binary lanes)
│   ├── js.ts             # JavaScript (Babel worker pool)
│   ├── js-worker.ts      # Babel worker thread
│   ├── css.ts            # CSS (PostCSS worker pool)
│   ├── css-worker.ts     # PostCSS worker thread
│   ├── worker-pool.ts    # Shared tinypool helpers
│   ├── css-grid-fallback.ts # CSS Grid → Flexbox
│   ├── dark-mode-strip.ts # Dark mode CSS removal
│   ├── html.ts           # HTML (Cheerio)
│   ├── image.ts          # Image optimization
│   ├── esm-bundler.ts    # ES module bundler entry
│   ├── esm/              # ESM bundler internals (fetcher, import-map,
│   │                     #   top-level-await, css-module, esbuild-plugin,
│   │                     #   module-cache)
│   ├── sw-bundler.ts     # Service Worker bundler
│   └── polyfills/        # 30+ polyfill scripts
├── metrics/              # Metrics collection
├── pac/                  # PAC file generation
├── cache/                # Caching system (memory LRU + disk)
├── certs/                # Certificate generation
├── portal/               # Captive portal
└── benchmarks/           # Performance benchmarks

public/
├── revamp-logo.png       # Logo asset
└── admin/                # Admin panel web UI
    ├── index.html        # Dashboard
    ├── domains.html      # Domain profiles management
    ├── config.html       # Configuration page
    ├── plugins.html      # Plugin management
    ├── sw.html           # Service Workers status
    ├── css/admin.css     # Shared styles
    └── js/               # JavaScript modules

.revamp-plugins/          # Plugin installation directory
├── plugins.json          # Global plugin config
└── com-example-plugin/   # Individual plugin
    ├── plugin.json       # Plugin manifest
    └── index.js          # Entry point

examples/plugins/         # Runnable example plugins (start here)
tests/                    # E2E tests (Playwright)
config/                   # External configuration (blocked domains)

🌐 API Endpoints

All API endpoints are available on any proxied domain at /__revamp__/*:

Endpoint Description
/__revamp__/admin Admin panel web UI
/__revamp__/config GET/POST/DELETE proxy configuration
/__revamp__/domains GET/POST domain profiles
/__revamp__/domains/:id GET/PUT/DELETE specific profile
/__revamp__/domains/match/:host GET test which profile matches a host
/__revamp__/metrics HTML metrics dashboard
/__revamp__/metrics/json JSON metrics data
/__revamp__/metrics/dashboard HTML metrics dashboard (alias)
/__revamp__/pac/socks5 SOCKS5 PAC file download
/__revamp__/pac/http HTTP PAC file download
/__revamp__/pac/combined Combined PAC file download
/__revamp__/sw/bundle GET Service Worker bundling (URL-based)
/__revamp__/sw/inline POST Service Worker transformation
/__revamp__/sw/remote WebSocket for remote SW execution
/__revamp__/sw/remote/status GET remote SW server status
/__revamp__/plugins GET list all plugins
/__revamp__/plugins/discover GET available plugins in directory
/__revamp__/plugins/load-all POST load and activate all plugins
/__revamp__/plugins/shutdown-all POST shut down all plugins
/__revamp__/plugins/hot-reload POST toggle hot-reload
/__revamp__/plugins/:id GET plugin info, DELETE unload
/__revamp__/plugins/:id/activate POST activate a plugin
/__revamp__/plugins/:id/deactivate POST deactivate a plugin
/__revamp__/plugins/:id/reload POST reload a plugin
/__revamp__/plugins/:id/config PUT update plugin configuration
/__revamp__/plugins/metrics GET all plugin metrics, DELETE reset
/__revamp__/plugins/:id/metrics GET/DELETE plugin-specific metrics
/__revamp__/plugins/:id/{path} Custom plugin-registered endpoints

Admin Panel

Access the full-featured admin panel at http://any-proxied-site/__revamp__/admin:

  • Dashboard - System status, metrics overview, and quick actions
  • Domain Profiles - Create, edit, and delete domain-specific filtering rules
  • Configuration - Toggle transformation and filtering options
  • Service Workers - Monitor remote SW server status

The admin panel is designed to work on legacy browsers (Safari 9+, iOS 9+) with vanilla JavaScript.

Bypass Guarantees: All /__revamp__/* endpoints (including the admin panel) are handled before the proxy transformation pipeline runs. This ensures:

Feature Admin Panel Status
JavaScript transpilation (Babel) Bypassed
CSS transformation (PostCSS) Bypassed
HTML modification (polyfills) Bypassed
Ad blocking Bypassed
Tracking removal Bypassed
Proxy-level caching Bypassed
User-Agent spoofing Bypassed

The admin panel files are served directly from disk without any modifications, ensuring the UI always works correctly regardless of proxy configuration.

Metrics Dashboard

Access real-time statistics at http://any-proxied-site/__revamp__/metrics:

  • Uptime and connection stats
  • Cache hit rate
  • Transformation counts (JS/CSS/HTML/Images)
  • Bandwidth usage
  • Blocked requests count

PAC Files

PAC (Proxy Auto-Config) files make device setup easier:

PAC file URL for iOS configuration — replace <your IP> with the address from
the `🌐 Your Local IP Address(es)` banner printed by `pnpm start`
(see Device Setup above):

http://<your IP>:8888/__revamp__/pac/socks5

Configure iOS: Settings → Wi-Fi → [Network] → Configure Proxy → Automatic → Enter PAC URL

🌍 Domain Profiles

Domain profiles allow per-domain configuration of filtering rules and transformations. This enables fine-grained control over ad blocking, tracking removal, and content transformation for specific websites.

Create a profile:

curl -X POST http://any-proxied-site/__revamp__/domains \
  -H "Content-Type: application/json" \
  -d '{
    "name": "YouTube Optimization",
    "patterns": [
      { "type": "suffix", "pattern": "*.youtube.com" },
      { "type": "suffix", "pattern": "*.googlevideo.com" }
    ],
    "priority": 100,
    "transforms": {
      "transformJs": true,
      "bundleEsModules": true
    },
    "removeAds": true,
    "removeTracking": true,
    "customAdPatterns": ["ad_break", "adPlacements"],
    "customAdSelectors": [".video-ads", ".ytp-ad-module"],
    "enabled": true
  }'

Pattern types:

  • exact - Exact domain match (e.g., example.com)
  • suffix - Wildcard suffix match (e.g., *.google.com matches www.google.com, mail.google.com)
  • regex - Regular expression match (e.g., ^.*\.example\.(com|org)$)

List all profiles:

curl http://any-proxied-site/__revamp__/domains

Get a specific profile:

curl http://any-proxied-site/__revamp__/domains/youtube-profile-id

Update a profile:

curl -X PUT http://any-proxied-site/__revamp__/domains/youtube-profile-id \
  -H "Content-Type: application/json" \
  -d '{ "removeAds": false }'

Delete a profile:

curl -X DELETE http://any-proxied-site/__revamp__/domains/youtube-profile-id

Test which profile matches a domain:

curl http://any-proxied-site/__revamp__/domains/match/www.youtube.com

Example profiles:

Social Media (Facebook, Twitter, Instagram)
curl -X POST http://any-proxied-site/__revamp__/domains \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Social Media",
    "patterns": [
      { "type": "suffix", "pattern": "*.facebook.com" },
      { "type": "suffix", "pattern": "*.twitter.com" },
      { "type": "suffix", "pattern": "*.x.com" },
      { "type": "suffix", "pattern": "*.instagram.com" }
    ],
    "priority": 90,
    "transforms": {
      "transformJs": true,
      "transformCss": true
    },
    "removeAds": true,
    "removeTracking": true,
    "customAdSelectors": [
      "[data-testid=\"placementTracking\"]",
      "[data-ad-preview]",
      ".sponsored-post"
    ],
    "enabled": true
  }'
News Sites (lightweight mode)
curl -X POST http://any-proxied-site/__revamp__/domains \
  -H "Content-Type: application/json" \
  -d '{
    "name": "News Sites",
    "patterns": [
      { "type": "suffix", "pattern": "*.cnn.com" },
      { "type": "suffix", "pattern": "*.bbc.com" },
      { "type": "suffix", "pattern": "*.nytimes.com" }
    ],
    "priority": 80,
    "transforms": {
      "transformJs": true,
      "transformCss": true,
      "transformHtml": true
    },
    "removeAds": true,
    "removeTracking": true,
    "customAdSelectors": [
      ".ad-container",
      ".advertisement",
      "[data-ad-unit]"
    ],
    "enabled": true
  }'
Disable transformations for specific site
curl -X POST http://any-proxied-site/__revamp__/domains \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Banking (no transforms)",
    "patterns": [
      { "type": "suffix", "pattern": "*.mybank.com" }
    ],
    "priority": 200,
    "transforms": {
      "transformJs": false,
      "transformCss": false,
      "transformHtml": false
    },
    "removeAds": false,
    "removeTracking": false,
    "enabled": true
  }'

Configuration hierarchy:

Domain Profile (highest priority)
       ↓
Client Defaults (per-IP settings)
       ↓
Global Defaults (server-wide fallback)

Profile fields:

Field Type Description
name string Human-readable profile name
patterns array Domain matching patterns
priority number Higher = matched first (default: 0)
transforms object Override transform settings
removeAds boolean Enable ad blocking for this domain
removeTracking boolean Enable tracking removal
customAdPatterns array Additional script patterns to block
customAdSelectors array CSS selectors for ad containers
customTrackingPatterns array Additional tracking script patterns
customTrackingSelectors array CSS selectors for tracking elements
corsAllowOrigins array Origins to receive Access-Control-Allow-Origin (T9, opt-in)
corsAllowCredentials boolean Whether to emit Access-Control-Allow-Credentials: true (T9)
enabled boolean Enable/disable this profile

Per-domain CORS injection (opt-in)

By default Revamp does not inject any Access-Control-Allow-* headers into proxied responses. Earlier builds added a permissive Access-Control-Allow-Origin: <client origin> plus Access-Control-Allow-Credentials: true to every response, which made every proxied site cross-origin readable by every other proxied site (T9). To restore that behaviour for a specific domain — for example so a site under your control can fetch resources from another site you proxy — set corsAllowOrigins (and optionally corsAllowCredentials: true) on a domain profile. Use the literal "*" to allow any origin; otherwise list exact client origins. Origin matches are compared verbatim — corsAllowOrigins: ["https://example.com"] matches that origin only and does not cover subdomains such as https://api.example.com. Requests whose Origin does not match the list receive no CORS headers, so the browser correctly blocks the cross-origin read.

Note: corsAllowOrigins: ["*"] cannot be combined with corsAllowCredentials: true. The combination is rejected by browsers per the Fetch spec; Re:Vamp will refuse to inject CORS headers in that case to avoid a credential leak.

curl -X POST http://any-proxied-site/__revamp__/domains \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Example with CORS",
    "patterns": [{ "type": "exact", "pattern": "api.example.com" }],
    "priority": 10,
    "corsAllowOrigins": ["https://app.example.com"],
    "corsAllowCredentials": true,
    "enabled": true
  }'

📡 Multi-Device Support

Revamp supports multiple devices connecting simultaneously, each with their own configuration:

Per-client settings are automatically managed based on the device's IP address. Each device can have different transformation and filtering settings.

View current client config:

curl http://any-proxied-site/__revamp__/config

Update settings for current device:

curl -X POST http://any-proxied-site/__revamp__/config \
  -H "Content-Type: application/json" \
  -d '{
    "transformJs": true,
    "removeAds": true,
    "spoofUserAgent": false
  }'

Reset to defaults:

curl -X DELETE http://any-proxied-site/__revamp__/config

🧩 Plugin System

Revamp includes a powerful plugin system that allows you to extend functionality through hooks into the request/response lifecycle.

Plugin Directory:

Plugins are installed in the .revamp-plugins/ directory. Each plugin has its own subdirectory containing a plugin.json manifest and entry point.

.revamp-plugins/
├── plugins.json              # Global plugin configuration
└── com-example-my-plugin/
    ├── plugin.json           # Plugin manifest
    └── index.js              # Entry point

Runnable example plugins live under examples/plugins/ — copy one into .revamp-plugins/ as a starting point for your own.

Plugin Manifest (plugin.json):

{
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "A sample plugin",
  "author": "Your Name",
  "revampVersion": "1.0.0",
  "main": "index.js",
  "hooks": ["request:pre", "response:post"],
  "permissions": ["request:read", "request:modify", "storage:read", "storage:write"]
}

Available Hooks:

Hook Purpose Can Modify Required Permission
request:pre Before upstream request URL, headers, block request:modify
response:post After response received Body, headers, status response:modify
transform:pre Before content transform Content, skip transform response:modify
transform:post After content transform Transformed content response:modify
filter:decision Custom blocking logic Block decision request:modify
config:resolution Inject config overrides Config values config:read
domain:lifecycle Profile CRUD events (notify only) config:read
cache:get Custom cache backend Cached data cache:read
cache:set Custom cache backend (notify only) cache:write
metrics:record Custom metrics (notify only) metrics:write

Note (T15): context.registerHook(name, …) now throws if the plugin's manifest does not declare the permission required for name. This is a breaking change for plugins that previously omitted the corresponding permission. Add the required permission to your plugin.json permissions array — e.g. a plugin that registers response:post must declare response:modify. See CHANGELOG for the full migration note.

Available Permissions:

Permission Description
request:read Read request data
request:modify Modify requests
response:read Read response data
response:modify Modify responses
config:read Read configuration
config:write Write configuration
cache:read Read from cache
cache:write Write to cache
metrics:read Read metrics
metrics:write Record metrics
network:fetch Make network requests
storage:read Read plugin storage
storage:write Write plugin storage
api:register Register API endpoints

Plugin Entry Point (index.js):

Revamp is an ESM project. Plugins must use export default; the loader at src/plugins/loader.ts accepts either a default-exported object or a default- exported factory function returning a plugin object.

export default {
  async initialize(context) {
    context.log('info', 'Plugin initializing...');
  },

  async activate(context) {
    context.registerHook('request:pre', async (request) => {
      if (request.hostname.includes('blocked.com')) {
        return {
          continue: false,
          value: { blocked: true, reason: 'Custom block' },
        };
      }
      return { continue: true };
    }, 100);

    context.registerHook('response:post', async (response) => {
      return {
        continue: true,
        value: {
          headers: {
            ...response.responseHeaders,
            'x-plugin-processed': 'true',
          },
        },
      };
    });
  },

  async deactivate(context) {
    context.unregisterHook('request:pre');
    context.unregisterHook('response:post');
  },

  async shutdown(context) {
    context.log('info', 'Plugin shutting down...');
  },
};

A runnable copy of this pattern lives at examples/plugins/com-revamp-hello-world/.

Plugin Context API:

The context object provides a sandboxed API for plugins:

interface PluginContext {
  // Hook registration
  registerHook(hookName, handler, priority?): void;
  unregisterHook(hookName): void;

  // Configuration (requires permissions)
  getConfig(): Readonly<RevampConfig>;
  getEffectiveConfig(clientIp?, domain?): Readonly<RevampConfig>;
  getPluginConfig<T>(): T;
  updatePluginConfig(updates): Promise<void>;

  // Storage (sandboxed per-plugin)
  readStorage<T>(key): Promise<T | null>;
  writeStorage<T>(key, data): Promise<void>;

  // Cache
  getCached(url, contentType, clientIp?): Promise<Buffer | null>;
  setCache(url, contentType, data, clientIp?): Promise<void>;

  // Metrics
  getMetrics(): Metrics;
  recordMetric(name, value, tags?): void;

  // Network
  fetch(url, options?): Promise<Response>;

  // API endpoints (at /__revamp__/plugins/{pluginId}/{path})
  registerEndpoint(path, handler): void;
  unregisterEndpoint(path): void;

  // Content transformers (run before built-in js/css/html/image
  // transformers; first match wins; requires response:modify)
  registerTransformer(transformer): void;
  unregisterTransformer(name): void;

  // Plugin composition (ungated, read-only)
  getActivePlugins(): string[];
  isPluginActive(id): boolean;

  // Logging
  log(level, message, ...args): void;
}

Plugin Composition & Shared Per-Request Data:

Plugins can discover each other at runtime via context.getActivePlugins() (ids of all active plugins) and context.isPluginActive(id). Both are read-only and require no permission.

Hook contexts that carry pluginData: Map<string, unknown> (request:pre and response:post) share one Map per proxied request, so plugin B can read what plugin A wrote within the same request (no cross-request persistence). Entries are namespaced by the writing plugin's id using the <pluginId>:<key> convention; the setSharedPluginData / getSharedPluginData helpers exported from the plugin API implement it:

import { setSharedPluginData, getSharedPluginData } from 'revamp/plugin';

// Plugin A (higher priority) tags the request in request:pre:
setSharedPluginData(ctx.pluginData, 'com.example.a', 'trace', 'abc123');

// Plugin B reads it later in the same request (request:pre or response:post):
const trace = getSharedPluginData(ctx.pluginData, 'com.example.a', 'trace');

When a plugin hook throws or times out, the chain continues (fail-safe) but the failure is no longer silent: the chain result's errors array lists { pluginId, hookName, error, timedOut } per failure, and the proxy logs a structured warning for each failed plugin.

Custom Content Transformers:

Plugins with the response:modify permission can register their own content transformers into the transform pipeline via context.registerTransformer. A transformer declares a unique name, a matches(ctx) predicate over the dispatch context (url, detected contentType, raw content-type header, effective config, matched profile, clientIp), and a transform function. Two lanes exist: kind: 'text' transformers take and return a string (like the built-in js/css/html transformers); kind: 'binary' transformers take a Buffer and return { data, contentType, transformed } (like the built-in image transformer). Plugin transformers run before the built-ins in registration order and the first match wins; a throwing plugin transformer is logged and skipped so the built-ins (and the response) are never broken. Transformers are unregistered automatically on plugin deactivation. The ContentTransformer types are exported from revamp/plugin.

Managing Plugins via API:

# List all plugins
curl http://any-proxied-site/__revamp__/plugins

# Discover available plugins
curl http://any-proxied-site/__revamp__/plugins/discover

# Load all plugins
curl -X POST http://any-proxied-site/__revamp__/plugins/load-all

# Activate a plugin
curl -X POST http://any-proxied-site/__revamp__/plugins/my-plugin-id/activate

# Deactivate a plugin
curl -X POST http://any-proxied-site/__revamp__/plugins/my-plugin-id/deactivate

# Update plugin configuration
curl -X PUT http://any-proxied-site/__revamp__/plugins/my-plugin-id/config \
  -H "Content-Type: application/json" \
  -d '{ "customSetting": "value" }'

# Unload a plugin
curl -X DELETE http://any-proxied-site/__revamp__/plugins/my-plugin-id

Plugin Metrics & Observability:

Revamp tracks per-plugin execution statistics for monitoring and debugging:

# Get metrics for all plugins
curl http://any-proxied-site/__revamp__/plugins/metrics

# Get metrics for a specific plugin
curl http://any-proxied-site/__revamp__/plugins/my-plugin-id/metrics

# Reset metrics for all plugins
curl -X DELETE http://any-proxied-site/__revamp__/plugins/metrics

# Reset metrics for a specific plugin
curl -X DELETE http://any-proxied-site/__revamp__/plugins/my-plugin-id/metrics

Metrics include:

  • Total hook executions, successes, failures, and timeouts
  • Average execution time per plugin and per hook
  • Last execution timestamp
  • Per-hook breakdown (e.g., request:pre vs response:post)

Configuration Schema Validation:

Plugins can define a JSON Schema for their configuration to ensure type safety:

{
  "id": "com.example.my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "main": "index.js",
  "configSchema": {
    "type": "object",
    "required": ["apiKey"],
    "properties": {
      "apiKey": {
        "type": "string",
        "minLength": 10,
        "description": "API key for external service"
      },
      "timeout": {
        "type": "integer",
        "minimum": 0,
        "maximum": 60000,
        "default": 5000
      },
      "enableFeature": {
        "type": "boolean",
        "default": true
      },
      "allowedDomains": {
        "type": "array",
        "items": { "type": "string" },
        "uniqueItems": true
      }
    }
  }
}

When updatePluginConfig() is called, the configuration is validated against the schema. Invalid configurations will throw an error with details about which fields failed validation.

Supported JSON Schema features:

  • Types: string, number, integer, boolean, array, object, null
  • String constraints: minLength, maxLength, pattern
  • Number constraints: minimum, maximum
  • Array constraints: minItems, maxItems, uniqueItems, items
  • Object constraints: required, properties, additionalProperties
  • Composition: oneOf, anyOf, allOf
  • Enums: enum

Hook Execution Modes:

Hooks can be executed in different modes:

  • Sequential (default): Hooks execute one after another in priority order. Earlier hooks can stop the chain.
  • Parallel: All hooks execute concurrently. Results are collected from all plugins.

The hook executor automatically uses sequential mode for modifying hooks (request:pre, response:post, etc.) and parallel mode for notification hooks (domain:lifecycle, cache:set, metrics:record).

Hook Result Types:

// Continue to next hook
{ continue: true, value?: T }

// Stop the chain and return this value
{ continue: false, value: T }

// Stop the chain with an error
{ continue: false, error: Error }

Plugin Testing Framework:

Revamp provides testing utilities for plugin developers:

import {
  createTestContext,
  createMockRequest,
  createMockResponse,
  createTestPlugin,
  runPluginLifecycle,
  assertContinues,
  assertStops,
} from 'revamp/plugin';

// Create a test context with mocked dependencies
const context = createTestContext({
  pluginId: 'com.test.my-plugin',
  config: { mySetting: 'value' },
});

// Create realistic request/response contexts
const request = createMockRequest({
  url: 'https://example.com/api',
  method: 'POST',
  headers: { 'content-type': 'application/json' },
});

const response = createMockResponse({
  statusCode: 200,
  body: Buffer.from('{"success": true}'),
});

// Test hook behavior
const result = await myHook(request);
assertContinues(result); // Passes if hook returns { continue: true }
assertStops(result);     // Passes if hook returns { continue: false }

Plugin Lifecycle:

unloaded → loaded → initializing → initialized → activating → active
                                                       ↓
                                            deactivating → deactivated

Configuration Hierarchy (with plugins):

Plugin Hooks (highest priority)
       ↓
Domain Profile
       ↓
Client Config
       ↓
Global Defaults (lowest)

Available client options:

Option Default Description
transformJs true Babel JS transpilation
transformCss true PostCSS CSS transformation
transformHtml true HTML polyfill injection
bundleEsModules true Bundle ES modules
emulateServiceWorkers true SW bypass/emulation
remoteServiceWorkers false Remote SW bridge (requires Playwright)
removeAds true Block ad domains
removeTracking true Block tracking domains
injectPolyfills true Add polyfills
spoofUserAgent true Spoof User-Agent header
spoofUserAgentInJs true Override navigator.userAgent
cacheEnabled true Response caching

The following are server-wide settings (set at startup or via updateConfig(), not overridable per client):

Option Default Description
maxRequestBodyBytes 52428800 Maximum upstream request body size in bytes (default 50 MB). Requests exceeding this are rejected with 413.
maxResponseBodyBytes 52428800 Maximum upstream response body size in bytes (default 50 MB). Responses exceeding this are returned as 502.
allowInsecureUpstream false Skip upstream TLS certificate validation. Default false. Set to true only for development against self-signed upstreams; production proxies should leave this off.

🧪 Testing

# Unit tests
pnpm test:unit        # Single run (alias: pnpm test:unit:run)
pnpm exec vitest      # Watch mode

# E2E tests
pnpm test             # Run all
pnpm test:headed      # With browser
pnpm test:ui          # Interactive mode

# Type checking
pnpm typecheck

# Performance benchmarks
pnpm build && pnpm tsx src/benchmarks/parallel-transform.ts

Benchmark Results

On a typical machine (8-core CPU), parallel performance improvements:

Operation Sequential Parallel Speedup
JS Transform ~42ms ~40ms 1.05x
CSS Transform ~5ms ~4ms 1.37x
Gzip Compress ~0.4ms ~0.04ms 9.36x
Gzip Decompress ~0.06ms ~0.04ms 1.52x

The worker pool's main benefit is keeping the main event loop responsive during heavy concurrent load, preventing request queuing and latency spikes. Moving PostCSS into its own worker pool cut main-thread blocking while transforming large CSS files from ~880ms to ~11.5ms.

🔧 Troubleshooting

Certificate Issues

"Not Trusted" warning:

  • Ensure you've enabled trust in Settings → General → About → Certificate Trust Settings
  • Try regenerating certificates: delete .revamp-certs/ and restart

Certificate won't install:

  • Make sure you're using Safari (not Chrome) on iOS
  • The certificate must be downloaded via HTTP, not HTTPS

Connection Issues

Can't connect to proxy:

  • Verify your computer's IP address
  • Check firewall settings (ports 1080, 8080, 8888)
  • Ensure both devices are on the same network

Websites not loading:

  • Check the Revamp console for errors
  • Some sites may have additional protections
  • Try disabling transformations to isolate issues

Performance Issues

Slow page loads:

  • Enable caching if disabled
  • Consider disabling transformations for specific sites
  • Check available disk space for cache

Sharp Install Issues

Cannot find module '@img/sharp-...' means the platform-specific sharp binary is missing. Quick fixes:

  • macOS Apple Silicon: run pnpm rebuild sharp after install.
  • Alpine: ensure libvips is available (apk add vips-dev) before installing.
  • Linux glibc/musl mismatch: reinstall with the right libc, e.g. pnpm install --config.platform=linux --config.libc=musl.

For other platforms see sharp's install matrix.

📦 Dependencies

Package Purpose
@babel/core JavaScript transpilation
postcss CSS transformation
cheerio HTML parsing/manipulation
node-forge Certificate generation
sharp Image optimization
esbuild ES module bundling for legacy browsers
tinypool Worker thread pool for parallel transforms
ws WebSocket for Remote Service Worker bridge

🤝 Contributing

Contributions are welcome! Please read our Contributing Guide for details.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests
  5. Submit a pull request

📄 License

MIT © Alex Kanunnikov

🙏 Acknowledgments

  • Babel team for the amazing transpiler
  • PostCSS team for CSS tooling
  • node-forge for certificate generation
  • All contributors and users!

Give your old devices new life! 🔄

About

A Node.js SOCKS5/HTTP proxy that intercepts web traffic and transforms modern JavaScript, CSS, and HTML for legacy browsers like Safari 9 on iPad 2, iPod Touch, and iPad Mini (iOS 9+).

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

No releases published

Sponsor this project

Packages

 
 
 

Contributors