Skip to content

Commit 41e7fa1

Browse files
committed
fix: prevent fabricated status data
1 parent 546ad71 commit 41e7fa1

4 files changed

Lines changed: 38 additions & 86 deletions

File tree

‎package.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
"@stellar/stellar-sdk": "^13.3.0",
2424
"@wraith-protocol/sdk": "^1.4.5",
2525
"i18next": "^26.2.0",
26-
"lucide-react": "^1.34.0",
2726
"react": "^19.2.5",
2827
"react-dom": "^19.2.5",
2928
"react-helmet-async": "^3.0.0",

‎pnpm-lock.yaml‎

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/hooks/useStatus.ts‎

Lines changed: 22 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useState, useEffect, useCallback } from 'react';
22
import incidentsData from '../data/incidents.json';
33

4+
const statusApiUrl = import.meta.env.VITE_STATUS_API_URL || '';
5+
46
export type ComponentStatus = {
57
id: string;
68
name: string;
@@ -26,73 +28,33 @@ export type StatusData = {
2628
lastUpdated: string;
2729
};
2830

29-
const MOCK_COMPONENTS: ComponentStatus[] = [
30-
{
31-
id: 'rpc-eth',
32-
name: 'Ethereum RPC',
33-
status: 'operational',
34-
uptime90Days: Array(90).fill(1),
35-
latencyMs: 42,
36-
},
37-
{
38-
id: 'rpc-sol',
39-
name: 'Solana RPC',
40-
status: 'operational',
41-
uptime90Days: Array(90).fill(1),
42-
latencyMs: 28,
43-
},
44-
{
45-
id: 'scanner',
46-
name: 'Wraith Scanner',
47-
status: 'operational',
48-
uptime90Days: Array(90).fill(1),
49-
latencyMs: 65,
50-
},
51-
{
52-
id: 'docs',
53-
name: 'Documentation',
54-
status: 'operational',
55-
uptime90Days: Array(90).fill(1),
56-
latencyMs: 15,
57-
},
58-
{
59-
id: 'marketing',
60-
name: 'Marketing Web',
61-
status: 'operational',
62-
uptime90Days: Array(90).fill(1),
63-
latencyMs: 20,
64-
},
65-
];
66-
6731
export function useStatus() {
6832
const [data, setData] = useState<StatusData | null>(null);
6933
const [loading, setLoading] = useState(true);
7034
const [error, setError] = useState<string | null>(null);
71-
const [lastUpdated, setLastUpdated] = useState<string>(new Date().toISOString());
35+
const [lastUpdated, setLastUpdated] = useState<string | null>(null);
7236

7337
const fetchStatus = useCallback(async () => {
38+
if (!statusApiUrl) {
39+
setData(null);
40+
setError('Status endpoint unavailable');
41+
setLoading(false);
42+
setLastUpdated(null);
43+
return;
44+
}
45+
7446
try {
75-
// Respect DNT header if enabled in browser
76-
const isDnt =
77-
navigator.doNotTrack === '1' ||
78-
(window as unknown as { doNotTrack?: string }).doNotTrack === '1';
7947
const headers: HeadersInit = {
8048
Accept: 'application/json',
8149
};
82-
if (isDnt) {
83-
// DNT honored, ensure no cookies are sent/requested
84-
}
85-
86-
// Try fetching from public status endpoint or fallback gracefully to mock data
87-
let apiComponents = MOCK_COMPONENTS;
88-
try {
89-
const res = await fetch('/api/status', { headers, credentials: 'omit' });
90-
if (res.ok) {
91-
const json = await res.json();
92-
if (json.components) apiComponents = json.components;
93-
}
94-
} catch {
95-
// Fallback gracefully if endpoint is unreachable without spinner-forever
50+
// Status polling sends no telemetry and never includes cookies.
51+
const res = await fetch(statusApiUrl, { headers, credentials: 'omit' });
52+
if (!res.ok) throw new Error(`Status endpoint returned ${res.status}`);
53+
const json = await res.json();
54+
if (!Array.isArray(json.components)) throw new Error('Status response has no components');
55+
const apiComponents = json.components as ComponentStatus[];
56+
if (!apiComponents.some((component) => /stellar/i.test(component.name))) {
57+
throw new Error('Status response is missing Stellar');
9658
}
9759

9860
setData({
@@ -106,11 +68,13 @@ export function useStatus() {
10668
lastUpdated: new Date().toISOString(),
10769
});
10870
setError(null);
71+
setLastUpdated(new Date().toISOString());
10972
} catch (err) {
73+
setData(null);
11074
setError(err instanceof Error ? err.message : 'Failed to fetch status data');
75+
setLastUpdated(null);
11176
} finally {
11277
setLoading(false);
113-
setLastUpdated(new Date().toISOString());
11478
}
11579
}, []);
11680

‎src/pages/Status.tsx‎

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useStatus } from '../hooks/useStatus';
2-
import { CheckCircle2, AlertTriangle, XCircle, RefreshCw, Clock } from 'lucide-react';
32

43
export default function Status() {
54
const { data, loading, error, refetch, lastUpdated } = useStatus();
@@ -8,20 +7,20 @@ export default function Status() {
87
switch (status) {
98
case 'operational':
109
return (
11-
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-500">
12-
<CheckCircle2 className="w-3.5 h-3.5" /> Operational
10+
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-green-500/10 text-green-500">
11+
Operational
1312
</span>
1413
);
1514
case 'degraded':
1615
return (
17-
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-amber-500/10 text-amber-500">
18-
<AlertTriangle className="w-3.5 h-3.5" /> Degraded Performance
16+
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-amber-500/10 text-amber-500">
17+
Degraded Performance
1918
</span>
2019
);
2120
case 'outage':
2221
return (
23-
<span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium bg-red-500/10 text-red-500">
24-
<XCircle className="w-3.5 h-3.5" /> Partial Outage
22+
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-red-500/10 text-red-500">
23+
Partial Outage
2524
</span>
2625
);
2726
}
@@ -38,25 +37,27 @@ export default function Status() {
3837
</p>
3938
</div>
4039
<div className="flex items-center gap-3">
41-
<span className="text-xs text-on-surface-muted flex items-center gap-1">
42-
<Clock className="w-3.5 h-3.5" /> Updated {new Date(lastUpdated).toLocaleTimeString()}
43-
</span>
40+
{lastUpdated && (
41+
<span className="text-xs text-on-surface-muted">
42+
Updated {new Date(lastUpdated).toLocaleTimeString()}
43+
</span>
44+
)}
4445
<button
4546
onClick={() => refetch()}
4647
className="p-2 rounded-lg border border-surface-border hover:bg-surface-hover transition-colors"
4748
title="Refresh status"
4849
>
49-
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
50+
{loading ? 'Refreshing...' : 'Refresh'}
5051
</button>
5152
</div>
5253
</div>
5354

5455
{/* Overall Banner */}
5556
{error && !data ? (
5657
<div className="p-4 rounded-xl bg-amber-500/10 border border-amber-500/20 text-amber-600 dark:text-amber-400 mb-8">
57-
<p className="font-medium">Unable to connect to live telemetry endpoints.</p>
58+
<p className="font-medium">Status unavailable.</p>
5859
<p className="text-xs mt-1 opacity-80">
59-
Displaying last known cached statuses. Retrying automatically...
60+
Live component data could not be loaded. Please try again later.
6061
</p>
6162
</div>
6263
) : data ? (
@@ -69,9 +70,9 @@ export default function Status() {
6970
>
7071
<div className="flex items-center gap-3">
7172
{data.overall === 'operational' ? (
72-
<CheckCircle2 className="w-6 h-6 text-green-500" />
73+
<span className="h-2 w-2 rounded-full bg-green-500" aria-hidden="true" />
7374
) : (
74-
<AlertTriangle className="w-6 h-6 text-amber-500" />
75+
<span className="h-2 w-2 rounded-full bg-amber-500" aria-hidden="true" />
7576
)}
7677
<span className="font-semibold text-lg">
7778
{data.overall === 'operational'

0 commit comments

Comments
 (0)