Module: middleware/appSetup

Express app creation, global middleware, and static file serving.

Extracted from index.js so the app instance can be imported by tests or other modules without triggering side effects (DB init, listen).

Exports

  • app — The Express application instance.
  • ARTIFACTS_DIR — Absolute path to the Playwright artifacts directory.
  • serveIndexWithNonce — SPA fallback handler that injects the CSP nonce (SEC-002).
Source:
Example
import { app, ARTIFACTS_DIR } from "./middleware/appSetup.js";

Members

(static, constant) ARTIFACTS_DIR :string

Absolute path to the Playwright artifacts directory (screenshots, videos, traces).

Type:
  • string
Source:

(static, constant) aiGenerationLimiter

AI generation limiter — 30 requests per hour per IP. Applied to: POST /api/projects/:id/tests/generate These endpoints make direct AI API calls (Claude / GPT / Gemini).

Source:

(static, constant) app :Object

The Express application instance.

Type:
  • Object
Source:

(static, constant) expensiveOpLimiter

Expensive operations limiter — 20 requests per hour per IP. Applied to: POST /api/projects/:id/crawl, POST /api/projects/:id/run, POST /api/tests/:testId/run These endpoints launch a browser instance and consume AI API quota.

Source:

(static, constant) isCrossOrigin :boolean

true when CORS_ORIGIN is set to a different origin than the backend. In that case cookies must use SameSite=None; Secure to be sent cross-site.

Compares against the backend's own origin (PORT-based), NOT APP_URL which is the frontend URL. For GitHub Pages + Render deployments, CORS_ORIGIN is the GitHub Pages URL and the backend runs on Render — these are always different origins, so cookies must use SameSite=None; Secure.

Type:
  • boolean
Source:

(inner) _indexHtmlTemplate :string|null|undefined

Cached index.html template with __CSP_NONCE__ placeholders.

Type:
  • string | null | undefined
Source:

(inner, constant) generalApiLimiter

General API rate limiter — 300 requests per 15 minutes per IP. Applied to all /api/* routes as a DoS / abuse baseline.

Source:

Methods

(static) cookieSameSite(httpOnlyopt) → {string}

Build the SameSite + Secure suffix for a Set-Cookie header. Cross-origin → SameSite=None; Secure (required by browsers). Same-origin → SameSite=Strict + Secure only in production.

Parameters:
Name Type Attributes Default Description
httpOnly boolean <optional>
false

Not used for the suffix, but kept for symmetry.

Source:
Returns:
Type
string

(static) serveIndexWithNonce(req, res)

Middleware that serves index.html with __CSP_NONCE__ replaced by the per-request nonce from res.locals.cspNonce.

Must be mounted after all API routes and static file middleware so it only catches SPA navigation requests (HTML pages, not API calls or assets).

Parameters:
Name Type Description
req Object
res Object
Source:

(static) signArtifactUrl(artifactPath) → {string}

Generate a short-lived HMAC-signed token for an artifact path.

Parameters:
Name Type Description
artifactPath string

The URL path, e.g. /artifacts/screenshots/foo.png

Source:
Returns:

The full artifact URL with ?token=…&exp=… appended.

Type
string

(static) signRunArtifacts(run) → {Object}

Deep-clone a run object and sign all artifact paths so the frontend receives fresh, non-expired URLs. Call this at read time (API responses, SSE events) — never persist signed URLs to the database.

Handles: run.tracePath, run.videoPath, run.videoSegments[], run.results[].screenshotPath, run.results[].videoPath.

Parameters:
Name Type Description
run Object

The run object from the database.

Source:
Returns:

A shallow clone with all artifact paths signed.

Type
Object

(inner) _makeRedisStore()

Create a RedisStore with a unique prefix, or return {} for in-memory fallback.

Source:

(inner) getIndexHtmlTemplate() → {string|null}

Read and cache the built index.html from the frontend dist directory. Returns null when the file does not exist (e.g. dev mode where Vite serves).

Uses undefined as the "not yet loaded" sentinel so that a failed read (empty string) is not permanently cached — the file may appear later if the build completes after the server starts.

Source:
Returns:
Type
string | null

(inner) isValidArtifactToken(artifactPath, token, exp) → {boolean}

Validate an incoming artifact request's ?token= and ?exp= query params. Returns true when the token is valid and not expired; false otherwise.

Parameters:
Name Type Description
artifactPath string

The URL path without query string.

token string | undefined
exp string | undefined
Source:
Returns:
Type
boolean