Im using Cloudafre workers to talk to my Frontend but im hitting the wall. I added CORS headers in my workers js but nothing works!
this is my StreamersConfigTab.tsx:
import { useState, useEffect } from 'react';
import AppStatus from '../../components/AppStatus';
import { getStreamerStats } from '../../api/getStreamerStats';
type StreamerStats = {
twitchUserId: string;
challengesProposed: number;
challengesCompleted: number;
challengesFailed: number;
totalBitsSpent: number;
lastUpdated: string;
};
type ConfigStatCardProps = {
label: string;
value: number | string;
loading: boolean;
};
function ConfigStatCard({ label, value, loading }: ConfigStatCardProps) {
return (
<div className="config-stat-card">
<span className="config-stat-label">{label}</span>
{loading ? (
<div className="skeleton-item skeleton-config-value"></div>
) : (
<span className="config-stat-value">
{typeof value === 'number' ? value.toLocaleString() : value}
</span>
)}
</div>
);
}
export default function StreamerConfigTab() {
const [broadcasterId, setBroadcasterId] = useState<string | null>(null);
const [authorized, setAuthorized] = useState(false);
const [stats, setStats] = useState<StreamerStats | null>(null);
const [status, setStatus] = useState<'checking' | 'connected' | 'disconnected'>('checking');
const [riotId, setRiotId] = useState<string | null>(null);
// ✅ Wait for Twitch.onAuthorized
useEffect(() => {
if (window.Twitch?.ext) {
window.Twitch.ext.onAuthorized(auth => {
setBroadcasterId(auth.channelId);
setAuthorized(true);
});
} else {
console.error('Twitch Helper not found.');
}
}, []);
// 🔒 Gate rendering
if (!authorized || !broadcasterId) {
return <div className="config-container">Loading Twitch config...</div>;
}
// 📊 Fetch stats every 30s
useEffect(() => {
let mounted = true;
const fetchStats = async () => {
try {
const data = await getStreamerStats(broadcasterId);
if (mounted) setStats(data);
} catch (e) {
console.error(e);
}
};
fetchStats();
const id = setInterval(fetchStats, 30_000);
return () => {
mounted = false;
clearInterval(id);
};
}, [broadcasterId]);
// 🔌 Check PC App connection every 5s
useEffect(() => {
let mounted = true;
const checkConn = async () => {
try {
const res = await fetch(`/status/connection?twitchUserId=${broadcasterId}`);
const json = await res.json();
if (mounted) setStatus(json.connected ? 'connected' : 'disconnected');
} catch {
if (mounted) setStatus('disconnected');
}
};
checkConn();
const id = setInterval(checkConn, 5_000);
return () => {
mounted = false;
clearInterval(id);
};
}, [broadcasterId]);
// 🧠 Fetch Riot ID if connected
useEffect(() => {
if (status !== 'connected') {
setRiotId(null);
return;
}
let mounted = true;
const fetchRiot = async () => {
try {
const active = await fetch(`/match/active?twitchUserId=${broadcasterId}`).then(r => r.json());
if (!mounted) return;
if (active.sessionId) {
const detail = await fetch(`/match/${active.sessionId}/status`).then(r => r.json());
if (mounted) setRiotId(detail.riotId);
} else {
setRiotId(null);
}
} catch {
if (mounted) setRiotId(null);
}
};
fetchRiot();
return () => {
mounted = false;
};
}, [status, broadcasterId]);
return (
<div className="config-container">
<h1>Streamer Configuration</h1>
<div className="config-section">
<h2 className="config-section-title">Connection Status</h2>
<div className="config-grid">
<div className="config-stat-card">
<span className="config-stat-label">PC Application</span>
<AppStatus status={status} />
</div>
{status === 'connected' && (
<div className="config-stat-card">
<span className="config-stat-label">Connected Riot ID</span>
<span className="config-stat-value">{riotId || 'N/A'}</span>
</div>
)}
</div>
</div>
<div className="config-section">
<h2 className="config-section-title">Lifetime Stats</h2>
<div className="config-grid">
<ConfigStatCard label="Challenges Proposed" value={stats?.challengesProposed ?? 0} loading={!stats} />
<ConfigStatCard label="Challenges Completed" value={stats?.challengesCompleted ?? 0} loading={!stats} />
<ConfigStatCard label="Challenges Failed" value={stats?.challengesFailed ?? 0} loading={!stats} />
<ConfigStatCard label="Bits Spent" value={stats?.totalBitsSpent ?? 0} loading={!stats} />
</div>
{stats && (
<p className="last-updated-text">
Last updated: {new Date(stats.lastUpdated).toLocaleString()}
</p>
)}
</div>
</div>
);
}
