Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,32 @@ agentpay-frontend/
└── ci.yml # CI: build, test
```

## Environment variables

| Variable | Visibility | Default | Purpose |
|----------|------------|---------|---------|
| `NEXT_PUBLIC_AGENTPAY_API_BASE` | public (bundled into client JS) | `http://localhost:3001` | Base URL for the AgentPay backend. Validated by `resolveApiBase()` in `src/lib/resolveApiBase.ts` and rejected in production if non-https except for `localhost` / `127.0.0.1`. |

Because the variable is `NEXT_PUBLIC_*`, its value is exposed to the browser. Never put API secrets in it - it is used only for routing public HTTP requests.

## Security headers

A baseline security header set (CSP, `X-Frame-Options: DENY`, `Referrer-Policy`, `X-Content-Type-Options`, `Permissions-Policy`, HSTS) is wired up in `next.config.ts` via `src/lib/securityHeaders.ts`. The CSP `connect-src` directive tracks `NEXT_PUBLIC_AGENTPAY_API_BASE` automatically; `<a href>` links to external sites (`https://stellar.org`, etc.) remain navigable.

## Event log rendering

The `/events` page renders server-supplied JSON payloads. Each payload is serialised through `safeStringify` (`src/lib/format.ts`) with a hard cap (`EVENT_PAYLOAD_MAX_CHARS`, default 5,000 chars) and a visible `…(truncated)` marker. Circular references, `BigInt`, functions, and malformed timestamps are replaced with safe sentinels so a bad payload can't crash the page.

## Commands

| Command | Description |
|--------|-------------|
| `npm run build` | Production build |
| `npm test` | Run Jest tests |
| `npm run test:coverage` | Run Jest with coverage |
| `npm run dev` | Development server |
| `npm run lint` | Run ESLint |
| `npm run typecheck` | Run the TypeScript compiler |

## CI/CD

Expand Down
24 changes: 24 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
import "@testing-library/jest-dom";

// Minimal Response polyfill for the jsdom test environment: jest-environment-jsdom
// 29 sometimes strips the native Response global, so a tiny shim lets the
// apiFetch tests use `new Response(JSON.stringify(body), { status })` without
// pulling in undici.
if (typeof global.Response === "undefined") {
class ResponsePolyfill {
body: string;
status: number;
constructor(body?: BodyInit | null, init?: ResponseInit) {
this.body =
typeof body === "string" ? body : body == null ? "" : String(body);
this.status = init?.status ?? 200;
}
get ok() {
return this.status >= 200 && this.status < 300;
}
async json() {
return JSON.parse(this.body || "null");
}
}
(global as unknown as { Response: typeof ResponsePolyfill }).Response =
ResponsePolyfill as unknown as typeof global.Response;
}
25 changes: 24 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
import type { NextConfig } from "next";
import { defaultSecurityHeaders } from "./src/lib/securityHeaders";
import { resolveApiBase } from "./src/lib/resolveApiBase";

// Resolve the API base once at build time so the CSP matches what the client
// will fetch against at runtime.
const apiBase = resolveApiBase();

const nextConfig: NextConfig = {
/* config options here */
// Apply the security headers to every response served by Next.js. Routes
// can override individually later, but the baseline locks the dashboard
// down by default.
async headers() {
const headers = defaultSecurityHeaders({
apiBase,
isDev: process.env.NODE_ENV !== "production",
});
return [
{
source: "/:path*",
headers: Object.entries(headers).map(([key, value]) => ({
key,
value,
})),
},
];
},
};

export default nextConfig;
30 changes: 12 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading