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
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ Sysnode is designed to make that information easier to read, easier to verify, a

## Data Source

The frontend reads live dashboard data from the Sysnode backend API. The default production build targets:
The frontend reads live dashboard data from the Sysnode backend API. Anonymous public data can still be retargeted with `REACT_APP_API_BASE`, but the authenticated custody surface (`/auth`, `/vault`, `/gov`) is designed for same-origin production deployment:

```text
https://syscoin.dev
https://sysnode.info/ -> SPA
https://sysnode.info/auth/* -> backend
https://sysnode.info/vault/* -> backend
https://sysnode.info/gov/* -> backend
```

The backend aggregates data from a Syscoin Core node, Sentry Node RPC responses, market APIs, and supporting network datasets. For a fork or private deployment, override the API base URL at build time (no code change required):
Expand All @@ -41,7 +44,7 @@ The backend aggregates data from a Syscoin Core node, Sentry Node RPC responses,
REACT_APP_API_BASE=https://your-backend.example npm run build
```

The value is read at build time by both `src/lib/apiClient.js` (authenticated surface) and `src/lib/api.js` (anonymous surface — superblock timing, governance feed, masternode stats); without it, development builds use `http://localhost:3001` and production builds use `https://syscoin.dev`. Keeping both clients on the same override means `REACT_APP_API_BASE` retargets the entire app in a single build.
The value is read at build time by both `src/lib/apiClient.js` (authenticated surface) and `src/lib/api.js` (anonymous surface — superblock timing, governance feed, masternode stats). Without it, development authenticated requests use `http://localhost:3001`; production authenticated requests use same-origin relative paths. Keeping the authenticated API under the SPA origin preserves host-only `Secure; SameSite=Lax` cookies and lets the SPA mirror the CSRF cookie into `X-CSRF-Token` without cross-site credentialed fetches.

## Getting Started

Expand Down
29 changes: 13 additions & 16 deletions src/lib/apiClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,22 @@ import axios from 'axios';

// Default API base URL.
//
// DO NOT fall back to `window.location.origin`. The sysnode-info SPA
// and the sysnode-backend are hosted on different origins — the SPA
// server only serves static files, it does not proxy /auth/* anywhere
// — so defaulting to the current browser origin makes login, register,
// and session hydration silently hit the wrong host and fail unless
// an operator remembers to set `REACT_APP_API_BASE`. (Codex round 5
// P1.)
//
// Priority:
// 1. REACT_APP_API_BASE (build-time override for bespoke deployments)
// 2. Production builds → https://syscoin.dev (same host as the legacy
// public client in `./api.js`, which is where sysnode-backend is
// reachable)
// 2. Production builds → same-origin relative paths. Production must
// reverse-proxy /auth, /vault, and /gov under the SPA origin so
// host-only SameSite=Lax cookies and the readable csrf cookie work
// without cross-site credentialed fetches.
// 3. Development builds → http://localhost:3001 (backend dev server)
const DEFAULT_BASE =
process.env.REACT_APP_API_BASE ||
(process.env.NODE_ENV === 'production'
? 'https://syscoin.dev'
: 'http://localhost:3001');
export function resolveDefaultApiBase({
apiBase = process.env.REACT_APP_API_BASE,
nodeEnv = process.env.NODE_ENV,
} = {}) {
if (apiBase) return apiBase;
return nodeEnv === 'production' ? '' : 'http://localhost:3001';
}

const DEFAULT_BASE = resolveDefaultApiBase();

const STATE_CHANGING = /^(POST|PUT|PATCH|DELETE)$/i;

Expand Down
24 changes: 24 additions & 0 deletions src/lib/apiClient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createApiClient,
parseRetryAfter,
readCsrfCookie,
resolveDefaultApiBase,
setAuthLostHandler,
toApiError,
} from './apiClient';
Expand Down Expand Up @@ -38,6 +39,29 @@ describe('readCsrfCookie', () => {
});
});

describe('resolveDefaultApiBase', () => {
test('uses explicit REACT_APP_API_BASE override', () => {
expect(
resolveDefaultApiBase({
apiBase: 'https://api.example.test',
nodeEnv: 'production',
})
).toBe('https://api.example.test');
});

test('defaults production authenticated calls to same-origin relative paths', () => {
expect(resolveDefaultApiBase({ apiBase: '', nodeEnv: 'production' })).toBe(
''
);
});

test('keeps localhost backend default for development', () => {
expect(resolveDefaultApiBase({ apiBase: '', nodeEnv: 'development' })).toBe(
'http://localhost:3001'
);
});
});

describe('createApiClient — CSRF attachment', () => {
test('attaches X-CSRF-Token to state-changing methods', async () => {
const client = createApiClient({
Expand Down
Loading