Files
ulweb/web/pages/index.tsx
2026-07-12 01:09:33 +00:00

164 lines
5.3 KiB
TypeScript

import Head from 'next/head';
import { useEffect, useMemo, useState } from 'react';
interface ApiStatus {
message: string;
docs: string;
}
interface MqttMessage {
topic: string;
payload: string;
receivedAt: string;
}
declare global {
interface Window {
Paho?: {
Client: new (...args: any[]) => any;
};
}
}
export default function Home() {
const [status, setStatus] = useState<ApiStatus | null>(null);
const [error, setError] = useState<string | null>(null);
const [messages, setMessages] = useState<MqttMessage[]>([]);
const [connected, setConnected] = useState(false);
const clientId = useMemo(() => `ulhub-web-${Math.random().toString(16).slice(2)}`, []);
useEffect(() => {
fetch('/api/status')
.then((res) => res.json())
.then(setStatus)
.catch((err) => setError(err.message));
}, []);
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js';
script.async = true;
console.log('Appending Paho script to page:', script.src);
script.onerror = () => {
console.error('Failed to load Paho MQTT script from CDN');
setError('Failed to load MQTT client script');
};
script.onload = () => {
console.log('Paho script loaded', !!window.Paho);
console.log('Paho global:', window.Paho);
// connect to same-origin via nginx proxy at /mqtt so browsers use wss on HTTPS
const scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
const uri = `${scheme}://${window.location.host}/mqtt`;
console.log('Connecting Paho client to', uri);
let client: any;
try {
if (window.Paho && typeof (window.Paho as any).Client === 'function') {
client = new (window.Paho as any).Client(uri, clientId);
} else if (window.Paho && (window.Paho as any).MQTT && typeof (window.Paho as any).MQTT.Client === 'function') {
// Paho.MQTT.Client(host, port, path, clientId)
const url = new URL(uri);
const host = url.hostname;
const port = url.port ? Number(url.port) : (url.protocol === 'wss:' ? 443 : 80);
const path = url.pathname + (url.search || '');
client = new (window.Paho as any).MQTT.Client(host, port, path, clientId);
} else {
console.error('Paho client constructor not found', window.Paho);
setError('MQTT client not available in browser');
return;
}
} catch (e) {
console.error('Failed to instantiate Paho client', e);
setError(String(e));
return;
}
client.onConnectionLost = (responseObject: { errorCode: number; errorMessage: string }) => {
console.warn('Paho connection lost', responseObject);
setConnected(false);
setError(responseObject.errorMessage || 'MQTT connection lost');
};
client.onMessageArrived = (message: { topic: string; payloadString: string }) => {
setMessages((prev) => [
{
topic: message.topic,
payload: message.payloadString,
receivedAt: new Date().toLocaleTimeString(),
},
...prev,
].slice(0, 10));
};
const connectOptions: any = {
onSuccess: () => {
console.log('Paho connected');
setConnected(true);
setError(null);
client.subscribe('devices/#');
},
onFailure: (err: { errorMessage: string }) => {
console.error('Paho connect failure', err);
setConnected(false);
setError(err.errorMessage || 'Unable to connect to MQTT broker');
},
};
// force secure websocket when page is served over HTTPS
connectOptions.useSSL = window.location.protocol === 'https:';
console.log('Paho connect options:', connectOptions);
client.connect(connectOptions);
return () => {
client.disconnect();
};
};
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}, [clientId]);
return (
<>
<Head>
<title>UlHub</title>
<meta name="description" content="UlHub monolithic app" />
</Head>
<main style={{ padding: '3rem', fontFamily: 'system-ui, sans-serif', maxWidth: '900px' }}>
<h1>UlHub</h1>
<p>React + Next.js frontend with NestJS backend and live MQTT WebSocket data.</p>
<section style={{ marginTop: '1.5rem' }}>
<h2>API Status</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
{status ? (
<div>
<p>{status.message}</p>
<p>{status.docs}</p>
</div>
) : (
<p>Loading status...</p>
)}
</section>
<section style={{ marginTop: '1.5rem' }}>
<h2>MQTT WebSocket</h2>
<p>Status: {connected ? 'Connected' : 'Connecting...'}</p>
<ul>
{messages.map((msg, index) => (
<li key={`${msg.topic}-${index}`} style={{ marginBottom: '0.75rem' }}>
<strong>{msg.topic}</strong> <span style={{ color: '#666' }}>{msg.receivedAt}</span>
<br />
{msg.payload}
</li>
))}
</ul>
</section>
</main>
</>
);
}