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. |
- 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