Skip to content
This repository was archived by the owner on Apr 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/electron-app/src/main/board-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import type { Edge, Node } from '@xyflow/react';
import { fork, ChildProcess } from 'child_process';
import { sendMessageToRenderer } from './window';
import { Board, IpcResponse, UploadedCodeMessage } from '../common/types';
import { Board, IpcResponse, UploadedCodeMessage, Pin } from '../common/types';
import { getRandomMessage } from '../common/messages';
import log from 'electron-log/node';
import { existsSync } from 'fs';
Expand All @@ -30,6 +30,7 @@ const ipRegex = new RegExp(
let runnerProcess: ChildProcess | undefined;
let lastUsedPinsHash: string | null = null;
let lastFlow: { nodes: Node[]; edges: Edge[]; ip?: string } | null = null;
let currentPins: Pin[] | undefined = undefined;

/**
* Gets the current runner process
Expand All @@ -45,6 +46,7 @@ export async function killRunnerProcess() {
runnerProcess?.kill('SIGKILL');
runnerProcess = undefined;
setConnectedPort(undefined);
currentPins = undefined;
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for the process to die
}

Expand Down Expand Up @@ -146,11 +148,13 @@ export function getCurrentConnectionState(): Board | null {
type: 'ready',
port: connectedPort.path,
message: 'Board connected',
pins: currentPins,
};
} else {
// Runner process died, clear the connection
log.debug('[STATE] <get>', 'Runner process died, clearing connection');
setConnectedPort(undefined);
currentPins = undefined;
return null;
}
} else if (connectedPort && !runnerProcess) {
Expand All @@ -160,6 +164,7 @@ export function getCurrentConnectionState(): Board | null {
type: 'ready',
port: connectedPort.path,
message: 'Reconnecting to board',
pins: currentPins,
};
} else {
// No connection state
Expand Down Expand Up @@ -428,6 +433,7 @@ async function checkBoardOnPort(port: Pick<PortInfo, 'path'>, board: BoardName)
break;
case 'ready':
log.debug(`[RUNNER] <${data.type}>`, runnerProcess?.pid, timer.duration);
currentPins = data.pins;
sendMessageToRenderer<Board>('ipc-board', {
success: true,
data: { type: 'ready', port: port.path, pins: data.pins },
Expand Down
25 changes: 13 additions & 12 deletions packages/mqtt-provider/src/stores/mqtt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export const useMqttStore = create<MqttStore>((set, get) => {
protocol: (protocol ?? 'wss') as 'ws' | 'wss',
host,
port: port ? parseInt(String(port), 10) : (protocol ?? 'wss') === 'wss' ? 8883 : 1883,
path: path ?? '/mqtt',
path: path ? (path.startsWith('/') ? path : `/${path}`) : '/mqtt',
};
} catch (error) {
throw new Error(
Expand All @@ -312,31 +312,31 @@ export const useMqttStore = create<MqttStore>((set, get) => {
const { protocol, host, port, path } = parseMqttUrl(config.url);
const clientId = `microflow_${appName}_${config.uniqueId}_${Date.now().toString(36)}`;

// Construct the full URL for WebSocket connections
// mqtt.js requires the URL as the first argument for proper clientId handling
const url = `${protocol}://${host}:${port}${path}`;

console.debug('[MQTT] <connect> Parsed URL:', {
input: config.url,
protocol,
host,
port,
path,
constructedUrl: url,
clientId,
});

// Build connection options (without protocol/host/port/path when using URL)
// Build connection options
// For WebSocket connections, use object form to ensure clientId is properly set
const connectionOptions: mqtt.IClientOptions = {
username: config.username,
password: config.password,
protocol: protocol === 'wss' ? 'wss' : protocol === 'ws' ? 'ws' : 'wss',
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The protocol variable is already typed as 'ws' | 'wss' from parseMqttUrl, so this ternary expression is redundant. You can simplify this to just protocol: protocol or use the shorthand protocol.

Suggested change
protocol: protocol === 'wss' ? 'wss' : protocol === 'ws' ? 'ws' : 'wss',
protocol,

Copilot uses AI. Check for mistakes.
hostname: host,
port: port,
path: path,
...(config.username && { username: config.username }),
...(config.password && { password: config.password }),
clientId,
// connectTimeout: 30000, // 30 seconds
// keepalive: 60, // 60 seconds
// clean: true, // Start with a clean session
// reconnectPeriod: 1000, // Reconnect after 1 second
// For WSS connections, ensure proper SSL handling
...(protocol === 'wss'
...(protocol === 'wss' || protocol === 'ws'
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

SSL options like rejectUnauthorized should only be applied to secure WebSocket connections ('wss'), not to regular WebSocket connections ('ws'). The condition should be protocol === 'wss' only, not include 'ws'.

Suggested change
...(protocol === 'wss' || protocol === 'ws'
...(protocol === 'wss'

Copilot uses AI. Check for mistakes.
? {
// Allow self-signed certificates (common for public brokers)
rejectUnauthorized: false,
Expand All @@ -355,8 +355,9 @@ export const useMqttStore = create<MqttStore>((set, get) => {
},
};

console.debug('[MQTT] <connect>', config, appName, url, connectionOptions);
client = mqtt.connect(url, connectionOptions);
console.debug('[MQTT] <connect>', config, appName, connectionOptions);
// Use object form instead of URL string to ensure clientId is properly set
client = mqtt.connect(connectionOptions);

// Handle status messages from other clients
const statusHandler = (topic: string, payload: Buffer) => {
Expand Down
39 changes: 24 additions & 15 deletions packages/runtime/src/_utils/transformUnknownValues.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
export function transformValueToBoolean(value: unknown) {
if (typeof value === 'boolean') return value;

if (typeof value === 'number') return value > 0;

const isTruthy = ['1', 'true', 'on', 'yes'].includes(String(value).toLowerCase());
const isSecretlyPositiveNumber = !Number.isNaN(Number(value)) && Number(value) > 0;

return isTruthy || isSecretlyPositiveNumber;
switch (typeof value) {
case 'boolean':
return value;
case 'number':
return value > 0;
case 'string':
const isTruthy = ['1', 'true', 'on', 'yes'].includes(String(value).toLowerCase());
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The value is already confirmed to be a string by the switch statement, so using String(value) is redundant. Consider using value.toLowerCase() directly.

Suggested change
const isTruthy = ['1', 'true', 'on', 'yes'].includes(String(value).toLowerCase());
const isTruthy = ['1', 'true', 'on', 'yes'].includes(value.toLowerCase());

Copilot uses AI. Check for mistakes.
const isSecretlyPositiveNumber = !Number.isNaN(Number(value)) && Number(value) > 0;
return isTruthy || isSecretlyPositiveNumber;
default:
return false;
}
}

export function transformValueToNumber(value: unknown) {
if (typeof value === 'number') return value;

if (typeof value === 'boolean') return value ? 1 : 0;

const parsed = parseFloat(String(value));

return Number.isNaN(parsed) ? 0 : parsed;
switch (typeof value) {
case 'number':
return value;
case 'boolean':
return value ? 1 : 0;
case 'string':
const parsed = parseFloat(value);
if (Number.isNaN(parsed)) return transformValueToBoolean(value) ? 1 : 0;
return parsed;
default:
return 0;
}
}
11 changes: 5 additions & 6 deletions packages/runtime/src/pixel/pixel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ export class Pixel extends Hardware<Value, Data, pixel.Strip> {
}

private forward(amount: number = 1) {
const parsedAmount = transformValueToNumber(amount);
const normalizedAmount = Math.round(parsedAmount);
const newValue = this.value.map((_color, index) => {
const newIndex = (index - amount + this.data.length) % this.data.length;
const newIndex = (index - normalizedAmount + this.data.length) % this.data.length;
return this.value[newIndex];
});
this.component?.shift(amount, pixel.FORWARD, true);
this.component?.shift(normalizedAmount, pixel.FORWARD, true);
this.flush(newValue);
}

Expand Down Expand Up @@ -82,10 +84,7 @@ export class Pixel extends Hardware<Value, Data, pixel.Strip> {
if (this.flushTimeout) clearTimeout(this.flushTimeout);
this.flushTimeout = setTimeout(
() => {
if (!this.component) {
console.warn('[PIXEL] <not_ready> flushing too early');
return;
}
if (!this.component) return;
this.lastFlushTime = Date.now();
this.value = color;
this.component?.show();
Expand Down
Loading