Skip to content

Copilot/fix http login issue#3

Open
MikManenti wants to merge 97 commits into
Parallel-7:mainfrom
MikManenti:copilot/fix-http-login-issue
Open

Copilot/fix http login issue#3
MikManenti wants to merge 97 commits into
Parallel-7:mainfrom
MikManenti:copilot/fix-http-login-issue

Conversation

@MikManenti

Copy link
Copy Markdown

No description provided.

Copilot AI and others added 30 commits June 4, 2026 19:15
…-camera

feat: add Home Assistant add-on (ha-addon/flashforge-dashboard)
…sitory

fix: avoid filesystem read in wildcard route handler
Bump Home Assistant base image from nodejs:20 to nodejs:22
Use public Node base image and drop deprecated build.yaml for add-on builds
fix: use HA base image to resolve /run.sh not found on container start
…error

fix: fast-fail printer timeout + surface connection errors in the UI
…hboard

Fix HA ingress API path resolution and prevent file-list JSON parse failures
Decouple camera stream from 4s polling and optimize dashboard for mobile-first layout
…ing-camera

UI redesign: single-column layout, direct MJPEG camera stream, pairs grid
MikManenti and others added 29 commits June 19, 2026 23:06
Refactor server.js to improve readability and structure.
…-issues

Restore server.js: MQTT Discovery, Ingress injection, and API routes lost in refactor
Fix frontend bootstrap regression preventing API reads
Added 'mode=mse' parameter to WebSocket URL for stream.
Removed fetch calls for camera control actions.
Restore dashboard data flow by fixing app.js parse error

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request transitions the repository from a documentation-focused structure to a Home Assistant Add-on repository hosting the FlashForge Dashboard. The new implementation includes a Node.js backend and a frontend dashboard to monitor and control FlashForge printers, featuring live camera streaming, MQTT integration, and GCode uploading. The code review identified several important issues to address: a potential socket leak in the MJPEG proxy, a lack of error handling before parsing JSON in the frontend's status polling, brittle URL parsing when the protocol is omitted, a risk of overlapping polling requests from using setInterval, and a potential header injection vulnerability during file uploads due to unsanitized filenames.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +641 to +669
const proxyReq = http.request(
{ host, port, path: upstreamPath, method: 'GET' },
(proxyRes) => {
if (proxyRes.statusCode !== 200) {
res.status(proxyRes.statusCode || 502).send('Upstream stream not found');
proxyRes.resume(); // drain so the socket is released
return;
}

// Forward Content-Type exactly as go2rtc sends it (includes boundary).
res.setHeader(
'Content-Type',
proxyRes.headers['content-type'] || 'multipart/x-mixed-replace; boundary=ffboundary',
);
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
res.setHeader('Connection', 'keep-alive');
// Tell nginx (used by HA Ingress) not to buffer this streaming response.
res.setHeader('X-Accel-Buffering', 'no');
res.flushHeaders(); // send headers immediately, before any body data

// Pipe every chunk straight to the browser without buffering.
proxyRes.pipe(res, { end: true });

// If the browser disconnects, tear down the upstream request too.
req.on('close', () => proxyReq.destroy());
},
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a potential resource leak in the MJPEG proxy. The req.on('close') listener is registered inside the http.request callback. If the client (browser) disconnects before the upstream server starts responding, the req.on('close') listener is never registered, and the upstream request proxyReq is never destroyed. This can lead to hanging sockets and socket exhaustion under load. Register the listener immediately after creating the request.

  const proxyReq = http.request(
    { host, port, path: upstreamPath, method: 'GET' },
    (proxyRes) => {
      if (proxyRes.statusCode !== 200) {
        res.status(proxyRes.statusCode || 502).send('Upstream stream not found');
        proxyRes.resume(); // drain so the socket is released
        return;
      }

      // Forward Content-Type exactly as go2rtc sends it (includes boundary).
      res.setHeader(
        'Content-Type',
        proxyRes.headers['content-type'] || 'multipart/x-mixed-replace; boundary=ffboundary',
      );
      res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
      res.setHeader('Pragma', 'no-cache');
      res.setHeader('Expires', '0');
      res.setHeader('Connection', 'keep-alive');
      // Tell nginx (used by HA Ingress) not to buffer this streaming response.
      res.setHeader('X-Accel-Buffering', 'no');
      res.flushHeaders(); // send headers immediately, before any body data

      // Pipe every chunk straight to the browser without buffering.
      proxyRes.pipe(res, { end: true });
    },
  );

  // If the browser disconnects, tear down the upstream request too.
  req.on('close', () => proxyReq.destroy());

Comment on lines +93 to +103
async function fetchStatus() {
try {
const res = await fetch(`${BASE}/api/status`);
const json = await res.json();
if (!res.ok || json.error) {
badge.textContent = 'Errore connessione';
badge.className = 'badge badge--error';
lastUpdate.textContent = `Errore: ${json.error || res.statusText}`;
return;
}
if (json.detail) updateUI(json.detail);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In fetchStatus, res.json() is called before checking res.ok. If the server returns a non-JSON error page (e.g., an HTML error page on 502/503), res.json() will throw a SyntaxError, obscuring the actual connection/HTTP error. Check res.ok first before attempting to parse the response as JSON.

async function fetchStatus() {
  try {
    const res = await fetch(BASE + '/api/status');
    if (!res.ok) {
      badge.textContent = 'Errore connessione';
      badge.className = 'badge badge--error';
      lastUpdate.textContent = 'Errore: HTTP ' + res.status;
      return;
    }
    const json = await res.json();
    if (json.error) {
      badge.textContent = 'Errore connessione';
      badge.className = 'badge badge--error';
      lastUpdate.textContent = 'Errore: ' + json.error;
      return;
    }
    if (json.detail) updateUI(json.detail);

Comment on lines +620 to +627
function parseGo2rtcUrl() {
try {
const u = new URL(GO2RTC_URL);
return { host: u.hostname, port: Number(u.port) || 1984 };
} catch (_) {
return { host: 'ccab4aaf-frigate', port: 1984 };
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In parseGo2rtcUrl, if GO2RTC_URL is configured without a protocol (e.g., 192.168.1.100:1984), new URL() will throw an error and fall back to the default ccab4aaf-frigate host. Prepending http:// when a protocol is missing makes the URL parsing more robust.

Suggested change
function parseGo2rtcUrl() {
try {
const u = new URL(GO2RTC_URL);
return { host: u.hostname, port: Number(u.port) || 1984 };
} catch (_) {
return { host: 'ccab4aaf-frigate', port: 1984 };
}
}
function parseGo2rtcUrl() {
try {
const urlStr = GO2RTC_URL.includes('://') ? GO2RTC_URL : 'http://' + GO2RTC_URL;
const u = new URL(urlStr);
return { host: u.hostname, port: Number(u.port) || 1984 };
} catch (_) {
return { host: 'ccab4aaf-frigate', port: 1984 };
}
}

Comment on lines +901 to +905
if (MQTT_ENABLED) {
mqttPollingTimer = setInterval(() => {
refreshPrinterState();
}, MQTT_POLL_INTERVAL_MS);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using setInterval for periodic asynchronous polling can cause overlapping requests if a request takes longer than the interval. It is safer to use recursive setTimeout to ensure the next poll is only scheduled after the current one completes.

  if (MQTT_ENABLED) {
    const poll = async () => {
      await refreshPrinterState();
      mqttPollingTimer = setTimeout(poll, MQTT_POLL_INTERVAL_MS);
    };
    poll();
  }

Comment on lines +541 to +548
const boundary = `----FormBoundary${Date.now()}`;
const preamble = [
`--${boundary}`,
`Content-Disposition: form-data; name="gcodeFile"; filename="${req.file.originalname}"`,
`Content-Type: application/octet-stream`,
'',
'',
].join('\r\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

In /api/upload, req.file.originalname is embedded directly into the multipart form header without sanitization. If the filename contains double quotes or other special characters, it can lead to header injection or malformed multipart payloads. Sanitizing the filename before embedding it is highly recommended.

Suggested change
const boundary = `----FormBoundary${Date.now()}`;
const preamble = [
`--${boundary}`,
`Content-Disposition: form-data; name="gcodeFile"; filename="${req.file.originalname}"`,
`Content-Type: application/octet-stream`,
'',
'',
].join('\r\n');
const safeFilename = req.file.originalname.replace(/[^a-zA-Z0-9._-]/g, '_');
const boundary = '----FormBoundary' + Date.now();
const preamble = [
'--' + boundary,
'Content-Disposition: form-data; name="gcodeFile"; filename="' + safeFilename + '"',
'Content-Type: application/octet-stream',
'',
'',
].join('\r\n');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants