fix: huddles not launching reliably

Closes #160
Race condition preventing proper initialization.
This commit is contained in:
talksik
2026-04-14 07:58:45 -07:00
parent 91a973b7fb
commit fe2a003e94
4 changed files with 29 additions and 34 deletions
-1
View File
@@ -19,7 +19,6 @@ declare global {
closeHuddle: () => void; closeHuddle: () => void;
}; };
electronHuddle: { electronHuddle: {
onConnect: (callback: (data: { token: string; serverUrl: string }) => void) => () => void;
getScreenSources: () => Promise<ScreenSource[]>; getScreenSources: () => Promise<ScreenSource[]>;
}; };
electronAutoplay: { electronAutoplay: {
+9 -8
View File
@@ -24,14 +24,15 @@ import { RoomEvent, Track } from 'livekit-client';
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef, useCallback } from 'react';
import { ScreenSourcePicker } from '@/components/screen-source-picker'; import { ScreenSourcePicker } from '@/components/screen-source-picker';
export function HuddleApp() { function readConnectionFromHash(): { token: string; serverUrl: string } | null {
const [connection, setConnection] = useState<{ token: string; serverUrl: string } | null>(null); const params = new URLSearchParams(window.location.hash.slice(1));
const token = params.get('token');
const serverUrl = params.get('serverUrl');
return token && serverUrl ? { token, serverUrl } : null;
}
useEffect(() => { export function HuddleApp() {
return window.electronHuddle.onConnect((data) => { const [connection] = useState(readConnectionFromHash);
setConnection(data);
});
}, []);
const handleDisconnected = () => { const handleDisconnected = () => {
window.electronWindow.closeHuddle(); window.electronWindow.closeHuddle();
@@ -40,7 +41,7 @@ export function HuddleApp() {
if (!connection) { if (!connection) {
return ( return (
<div className="flex h-screen items-center justify-center bg-background text-foreground"> <div className="flex h-screen items-center justify-center bg-background text-foreground">
<p className="text-muted-foreground text-sm">Connecting to huddle...</p> <p className="text-muted-foreground text-sm">Missing huddle connection data.</p>
</div> </div>
); );
} }
+20 -20
View File
@@ -162,10 +162,7 @@ const createAutoplayWindow = () => {
}; };
const createHuddleWindow = () => { const createHuddleWindow = () => {
if (huddleWindow) { if (huddleWindow) return;
huddleWindow.focus();
return;
}
huddleWindow = new BrowserWindow({ huddleWindow = new BrowserWindow({
width: 1024, width: 1024,
@@ -180,19 +177,28 @@ const createHuddleWindow = () => {
}); });
hardenWindow(huddleWindow); hardenWindow(huddleWindow);
if (HUDDLE_WINDOW_VITE_DEV_SERVER_URL) {
huddleWindow.loadURL(HUDDLE_WINDOW_VITE_DEV_SERVER_URL);
} else {
huddleWindow.loadFile(
path.join(__dirname, `../renderer/${HUDDLE_WINDOW_VITE_NAME}/index.html`),
);
}
huddleWindow.on('closed', () => { huddleWindow.on('closed', () => {
huddleWindow = null; huddleWindow = null;
}); });
}; };
// Connection data is passed via URL hash so it's available synchronously on
// renderer mount — avoids the IPC race where `huddle:connect` could be sent
// before React attached its listener.
const loadHuddleWindow = (data: { token: string; serverUrl: string }) => {
if (!huddleWindow) return;
const params = new URLSearchParams({ token: data.token, serverUrl: data.serverUrl });
const hash = params.toString();
if (HUDDLE_WINDOW_VITE_DEV_SERVER_URL) {
huddleWindow.loadURL(`${HUDDLE_WINDOW_VITE_DEV_SERVER_URL}#${hash}`);
} else {
huddleWindow.loadFile(
path.join(__dirname, `../renderer/${HUDDLE_WINDOW_VITE_NAME}/index.html`),
{ hash },
);
}
};
function positionAutoplayWindow() { function positionAutoplayWindow() {
if (!autoplayWindow) return; if (!autoplayWindow) return;
const { width } = screen.getPrimaryDisplay().workAreaSize; const { width } = screen.getPrimaryDisplay().workAreaSize;
@@ -261,14 +267,8 @@ ipcMain.on('window:fullscreen', (event) => {
// Secondary window IPC handlers // Secondary window IPC handlers
ipcMain.on('window:open-huddle', (_event, data: { token: string; serverUrl: string }) => { ipcMain.on('window:open-huddle', (_event, data: { token: string; serverUrl: string }) => {
createHuddleWindow(); createHuddleWindow();
// Send connection data once the huddle window is ready loadHuddleWindow(data);
huddleWindow?.webContents.once('did-finish-load', () => { huddleWindow?.focus();
huddleWindow?.webContents.send('huddle:connect', data);
});
// If already loaded, send immediately
if (!huddleWindow?.webContents.isLoading()) {
huddleWindow?.webContents.send('huddle:connect', data);
}
}); });
ipcMain.on('window:close-huddle', () => { ipcMain.on('window:close-huddle', () => {
huddleWindow?.close(); huddleWindow?.close();
-5
View File
@@ -12,11 +12,6 @@ contextBridge.exposeInMainWorld('electronWindow', {
}); });
contextBridge.exposeInMainWorld('electronHuddle', { contextBridge.exposeInMainWorld('electronHuddle', {
onConnect: (callback: (data: { token: string; serverUrl: string }) => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: { token: string; serverUrl: string }) => callback(data);
ipcRenderer.on('huddle:connect', handler);
return () => { ipcRenderer.removeListener('huddle:connect', handler); };
},
getScreenSources: () => ipcRenderer.invoke('screen:get-sources'), getScreenSources: () => ipcRenderer.invoke('screen:get-sources'),
}); });