Skip to content

Commit 1210ec3

Browse files
committed
fix(classic-dashboard): skip insecure WebSocket on HTTPS (GitHub Pages)
Browsers block ws:// from https:// pages (mixed content). Default ws://host:4001 was resolving to github.io:4001. Return null from classicWsPath on HTTPS unless REACT_APP_CLASSIC_WS_BASE is wss://. Dashboard and CustomGraph skip WebSocket when URL is null. Rebuild public/classic-dashboard bundle. Made-with: Cursor
1 parent f57abb6 commit 1210ec3

16 files changed

Lines changed: 54 additions & 27 deletions

.github/workflows/deploy-pages.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
# - EXPO_PUBLIC_INFLUX_WEB_PROXY (optional): CORS proxy URL for web; see scripts/influx-web-proxy.worker.example.js.
1010
# - EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY (if the app needs it at build time).
1111
# - MAPBOX_ACCESS_TOKEN (optional; also in app.config extra).
12+
# Classic dashboard WebSocket: on HTTPS, ws:// is blocked. To stream from a bridge on Pages builds,
13+
# add a workflow step that builds classic-dashboard with REACT_APP_CLASSIC_WS_BASE=wss://... (secret).
1214
# 3. Clerk Dashboard → Native applications → allowlist SSO redirect URLs, e.g.
1315
# https://<owner>.github.io/<repo>/oauth-native-callback
1416
# https://<owner>.github.io/<repo>/ (after sign-out)

classic-dashboard/build/asset-manifest.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"files": {
33
"main.css": "./static/css/main.2ae47eeb.chunk.css",
4-
"main.js": "./static/js/main.490b1527.chunk.js",
5-
"main.js.map": "./static/js/main.490b1527.chunk.js.map",
4+
"main.js": "./static/js/main.79d39870.chunk.js",
5+
"main.js.map": "./static/js/main.79d39870.chunk.js.map",
66
"runtime-main.js": "./static/js/runtime-main.0b7785a0.js",
77
"runtime-main.js.map": "./static/js/runtime-main.0b7785a0.js.map",
88
"static/css/2.f4c56af9.chunk.css": "./static/css/2.f4c56af9.chunk.css",
@@ -51,6 +51,6 @@
5151
"static/css/2.f4c56af9.chunk.css",
5252
"static/js/2.bf1f622e.chunk.js",
5353
"static/css/main.2ae47eeb.chunk.css",
54-
"static/js/main.490b1527.chunk.js"
54+
"static/js/main.79d39870.chunk.js"
5555
]
5656
}

classic-dashboard/build/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-server"/><link rel="apple-touch-icon" href="./favicon.ico"/><link rel="manifest" href="./manifest.json"/><title>Chase Car Dashboard</title><link href="./static/css/2.f4c56af9.chunk.css" rel="stylesheet"><link href="./static/css/main.2ae47eeb.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this server.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,u,c=r[0],i=r[1],f=r[2],s=0,p=[];s<c.length;s++)u=c[s],Object.prototype.hasOwnProperty.call(o,u)&&o[u]&&p.push(o[u][0]),o[u]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(l&&l(r);p.length;)p.shift()();return a.push.apply(a,f||[]),t()}function t(){for(var e,r=0;r<a.length;r++){for(var t=a[r],n=!0,c=1;c<t.length;c++){var i=t[c];0!==o[i]&&(n=!1)}n&&(a.splice(r--,1),e=u(u.s=t[0]))}return e}var n={},o={1:0},a=[];function u(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,u),t.l=!0,t.exports}u.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var a,c=document.createElement("script");c.charset="utf-8",c.timeout=120,u.nc&&c.setAttribute("nonce",u.nc),c.src=function(e){return u.p+"static/js/"+({}[e]||e)+"."+{3:"5b1da16d",4:"2f340a22",5:"748631ed",6:"cbbad1a5",7:"28689cba",8:"f83cf0a3",9:"41676568",10:"a9424c8e"}[e]+".chunk.js"}(e);var i=new Error;a=function(r){c.onerror=c.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;i.message="Loading chunk "+e+" failed.\n("+n+": "+a+")",i.name="ChunkLoadError",i.type=n,i.request=a,t[1](i)}o[e]=void 0}};var f=setTimeout((function(){a({type:"timeout",target:c})}),12e4);c.onerror=c.onload=a,document.head.appendChild(c)}return Promise.all(r)},u.m=e,u.c=n,u.d=function(e,r,t){u.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},u.t=function(e,r){if(1&r&&(e=u(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(u.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)u.d(t,n,function(r){return e[r]}.bind(null,n));return t},u.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(r,"a",r),r},u.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},u.p="./",u.oe=function(e){throw console.error(e),e};var c=this["webpackJsonpchase-car-dashboard-frontend"]=this["webpackJsonpchase-car-dashboard-frontend"]||[],i=c.push.bind(c);c.push=r,c=c.slice();for(var f=0;f<c.length;f++)r(c[f]);var l=i;t()}([])</script><script src="./static/js/2.bf1f622e.chunk.js"></script><script src="./static/js/main.490b1527.chunk.js"></script></body></html>
1+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="./favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-server"/><link rel="apple-touch-icon" href="./favicon.ico"/><link rel="manifest" href="./manifest.json"/><title>Chase Car Dashboard</title><link href="./static/css/2.f4c56af9.chunk.css" rel="stylesheet"><link href="./static/css/main.2ae47eeb.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this server.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,u,c=r[0],i=r[1],f=r[2],s=0,p=[];s<c.length;s++)u=c[s],Object.prototype.hasOwnProperty.call(o,u)&&o[u]&&p.push(o[u][0]),o[u]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(l&&l(r);p.length;)p.shift()();return a.push.apply(a,f||[]),t()}function t(){for(var e,r=0;r<a.length;r++){for(var t=a[r],n=!0,c=1;c<t.length;c++){var i=t[c];0!==o[i]&&(n=!1)}n&&(a.splice(r--,1),e=u(u.s=t[0]))}return e}var n={},o={1:0},a=[];function u(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,u),t.l=!0,t.exports}u.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var a,c=document.createElement("script");c.charset="utf-8",c.timeout=120,u.nc&&c.setAttribute("nonce",u.nc),c.src=function(e){return u.p+"static/js/"+({}[e]||e)+"."+{3:"5b1da16d",4:"2f340a22",5:"748631ed",6:"cbbad1a5",7:"28689cba",8:"f83cf0a3",9:"41676568",10:"a9424c8e"}[e]+".chunk.js"}(e);var i=new Error;a=function(r){c.onerror=c.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),a=r&&r.target&&r.target.src;i.message="Loading chunk "+e+" failed.\n("+n+": "+a+")",i.name="ChunkLoadError",i.type=n,i.request=a,t[1](i)}o[e]=void 0}};var f=setTimeout((function(){a({type:"timeout",target:c})}),12e4);c.onerror=c.onload=a,document.head.appendChild(c)}return Promise.all(r)},u.m=e,u.c=n,u.d=function(e,r,t){u.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},u.t=function(e,r){if(1&r&&(e=u(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(u.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)u.d(t,n,function(r){return e[r]}.bind(null,n));return t},u.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(r,"a",r),r},u.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},u.p="./",u.oe=function(e){throw console.error(e),e};var c=this["webpackJsonpchase-car-dashboard-frontend"]=this["webpackJsonpchase-car-dashboard-frontend"]||[],i=c.push.bind(c);c.push=r,c=c.slice();for(var f=0;f<c.length;f++)r(c[f]);var l=i;t()}([])</script><script src="./static/js/2.bf1f622e.chunk.js"></script><script src="./static/js/main.79d39870.chunk.js"></script></body></html>

classic-dashboard/build/static/js/main.490b1527.chunk.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

classic-dashboard/build/static/js/main.490b1527.chunk.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

classic-dashboard/build/static/js/main.79d39870.chunk.js

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

classic-dashboard/build/static/js/main.79d39870.chunk.js.map

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

classic-dashboard/src/Components/Dashboard/Dashboard.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ export default function Dashboard(props) {
3535
const [state, setState] = useState({ data: null });
3636
const ws = useRef(null);
3737

38-
// // open websocket on mount
3938
useEffect(() => {
40-
ws.current = new WebSocket(classicWsPath('/single-values'));
39+
const url = classicWsPath('/single-values');
40+
if (!url) {
41+
return undefined;
42+
}
43+
44+
ws.current = new WebSocket(url);
4145

4246
ws.current.onopen = () => {
4347
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
@@ -58,13 +62,12 @@ export default function Dashboard(props) {
5862
console.error('WebSocket error:', error);
5963
};
6064

61-
// close connection on unmount
6265
return () => {
6366
if (ws.current) {
6467
ws.current.close();
6568
}
6669
};
67-
},[])
70+
}, [])
6871

6972
useEffect(() => {
7073
const pollMs = Number(process.env.REACT_APP_CLASSIC_POLL_MS || 2000);

classic-dashboard/src/Components/Graph/CustomGraph.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,13 @@ function Graph(props) {
207207
const fetchDelay = 500;
208208
const ws = useRef(null);
209209

210-
// open websocket on mount
211210
useEffect(() => {
212-
ws.current = new WebSocket(classicWsPath('/components/graph'));
211+
const url = classicWsPath('/components/graph');
212+
if (!url) {
213+
return undefined;
214+
}
215+
216+
ws.current = new WebSocket(url);
213217

214218
ws.current.onmessage = (event) => {
215219
try {
@@ -224,13 +228,12 @@ function Graph(props) {
224228
console.error('WebSocket error:', error);
225229
};
226230

227-
// close connection on unmount
228231
return () => {
229232
if (ws.current) {
230233
ws.current.close();
231234
}
232235
};
233-
},[])
236+
}, [])
234237

235238
useEffect(() => {
236239
const intervalId = setInterval(() => {
Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
/**
22
* WebSocket base for the classic-dashboard bridge (same repo: `npm run classic-bridge`).
33
*
4-
* CRA: set REACT_APP_CLASSIC_WS_BASE to a full ws origin without path, e.g. ws://192.168.1.10:4001
5-
* Defaults to current page host + port 4001 so LAN dev works.
4+
* CRA: set REACT_APP_CLASSIC_WS_BASE to a full ws/wss origin without path, e.g. ws://192.168.1.10:4001
5+
* On HTTPS (GitHub Pages, etc.) browsers block ws:// (mixed content). Use wss://your-bridge.example.com
6+
* or omit — the dashboard will skip WebSocket and show empty data until a bridge is configured.
67
*/
78
export function getClassicWsBase() {
89
if (typeof window === 'undefined') {
910
return 'ws://localhost:4001';
1011
}
1112
const fromEnv = process.env.REACT_APP_CLASSIC_WS_BASE;
1213
if (fromEnv && typeof fromEnv === 'string') {
13-
return fromEnv.replace(/\/$/, '');
14+
const trimmed = fromEnv.replace(/\/$/, '');
15+
if (window.location.protocol === 'https:' && trimmed.startsWith('ws://')) {
16+
if (typeof console !== 'undefined' && console.warn) {
17+
console.warn(
18+
'Classic dashboard: on HTTPS, REACT_APP_CLASSIC_WS_BASE must use wss:// (ws:// is blocked as mixed content).'
19+
);
20+
}
21+
return null;
22+
}
23+
return trimmed;
24+
}
25+
// Static HTTPS hosts (e.g. github.io) have no local bridge; ws:// same-host:4001 is invalid + mixed content
26+
if (window.location.protocol === 'https:') {
27+
return null;
1428
}
1529
const port = process.env.REACT_APP_CLASSIC_WS_PORT || '4001';
1630
const host = window.location.hostname || 'localhost';
17-
// Bridge (`npm run classic-bridge`) serves plain ws on 4001 — no TLS unless you set REACT_APP_CLASSIC_WS_BASE=wss://...
1831
return `ws://${host}:${port}`;
1932
}
2033

21-
/** @param {string} suffix e.g. '/single-values' or 'single-values' */
34+
/**
35+
* @param {string} suffix e.g. '/single-values' or 'single-values'
36+
* @returns {string|null} full WebSocket URL, or null if connection should not be opened
37+
*/
2238
export function classicWsPath(suffix) {
2339
const base = getClassicWsBase();
40+
if (!base) {
41+
return null;
42+
}
2443
const p = suffix.startsWith('/') ? suffix : `/${suffix}`;
2544
return `${base}${p}`;
2645
}

0 commit comments

Comments
 (0)