Skip to content

Commit 85040bc

Browse files
author
JooHyung Park
committed
[ai-assisted] fix: load packaged renderer via protocol
1 parent d8b1f18 commit 85040bc

3 files changed

Lines changed: 95 additions & 14 deletions

File tree

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "talktofigma-desktop",
33
"productName": "TalkToFigma Desktop",
4-
"version": "3.0.2",
4+
"version": "3.0.3",
55
"description": "Bridge between Figma and AI tools (Cursor, Claude Code) using Model Context Protocol",
66
"branding": {
77
"shortName": "TalkToFigma",

src/main.ts

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
* Use of this source code is governed by an MIT-style license that can be found in the LICENSE file
55
*/
66

7-
import { app, BrowserWindow } from 'electron';
7+
import { app, BrowserWindow, net, protocol } from 'electron';
88
import path from 'node:path';
99
import inspector from 'node:inspector';
10+
import { pathToFileURL } from 'node:url';
1011
import started from 'electron-squirrel-startup';
1112
import { initialize } from '@aptabase/electron/main';
1213
import { registerIpcHandlers, setAuthManager, emitToRenderer } from './main/ipc-handlers';
@@ -27,6 +28,19 @@ import { SseDetectionServer } from './main/server/SseDetectionServer';
2728
declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined;
2829
declare const MAIN_WINDOW_VITE_NAME: string;
2930

31+
const RENDERER_PROTOCOL = 'talktofigma-renderer';
32+
33+
protocol.registerSchemesAsPrivileged([
34+
{
35+
scheme: RENDERER_PROTOCOL,
36+
privileges: {
37+
standard: true,
38+
secure: true,
39+
supportFetchAPI: true,
40+
},
41+
},
42+
]);
43+
3044
// Initialize Aptabase before app is ready (must be before any app events)
3145
initialize(APTABASE_APP_KEY);
3246

@@ -65,6 +79,44 @@ let mainWindow: BrowserWindow | null = null;
6579
let tray: TalkToFigmaTray | null = null;
6680
let serverManager: TalkToFigmaServerManager | null = null;
6781
let service: TalkToFigmaService | null = null;
82+
let rendererProtocolRegistered = false;
83+
84+
const getRendererRoot = () => path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}`);
85+
86+
const getPackagedRendererUrl = () => `${RENDERER_PROTOCOL}://renderer/index.html`;
87+
88+
const registerRendererProtocol = () => {
89+
if (MAIN_WINDOW_VITE_DEV_SERVER_URL || rendererProtocolRegistered) {
90+
return;
91+
}
92+
93+
const rendererRoot = getRendererRoot();
94+
95+
protocol.handle(RENDERER_PROTOCOL, (request) => {
96+
const requestUrl = new URL(request.url);
97+
const requestedPath = decodeURIComponent(requestUrl.pathname);
98+
const relativePath = requestedPath === '/' || requestedPath === ''
99+
? 'index.html'
100+
: requestedPath.replace(/^\/+/, '');
101+
const filePath = path.join(rendererRoot, relativePath);
102+
const relativeToRoot = path.relative(rendererRoot, filePath);
103+
104+
if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
105+
return new Response('Not found', { status: 404 });
106+
}
107+
108+
return net.fetch(pathToFileURL(filePath).toString());
109+
});
110+
111+
rendererProtocolRegistered = true;
112+
logger.info(`Renderer protocol registered for: ${rendererRoot}`);
113+
};
114+
115+
const loadRenderer = (window: BrowserWindow) => {
116+
const rendererUrl = MAIN_WINDOW_VITE_DEV_SERVER_URL || getPackagedRendererUrl();
117+
logger.info(`Loading renderer: ${rendererUrl}`);
118+
return window.loadURL(rendererUrl);
119+
};
68120

69121
const createWindow = () => {
70122
logger.info('Creating main window');
@@ -90,18 +142,22 @@ const createWindow = () => {
90142
// Center window on screen
91143
mainWindow.center();
92144

93-
// Load the app
94-
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
95-
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
96-
} else {
97-
mainWindow.loadFile(
98-
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
99-
);
100-
}
101-
102145
// Renderer lifecycle diagnostics (critical for packaged/TestFlight blank-screen debugging)
146+
let rendererDomReady = false;
147+
148+
mainWindow.webContents.on('did-start-navigation', (_event, url, isInPlace, isMainFrame) => {
149+
if (isMainFrame && !isInPlace) {
150+
logger.info(`Renderer did-start-navigation: ${url}`);
151+
}
152+
});
153+
154+
mainWindow.webContents.on('dom-ready', () => {
155+
rendererDomReady = true;
156+
logger.info(`Renderer dom-ready: ${mainWindow?.webContents.getURL() || 'unknown'}`);
157+
});
158+
103159
mainWindow.webContents.on('did-finish-load', () => {
104-
logger.info('Renderer did-finish-load');
160+
logger.info(`Renderer did-finish-load: ${mainWindow?.webContents.getURL() || 'unknown'}`);
105161
});
106162

107163
mainWindow.webContents.on(
@@ -113,6 +169,11 @@ const createWindow = () => {
113169
}
114170
);
115171

172+
mainWindow.webContents.on('console-message', (_event, level, message, line, sourceId) => {
173+
const trimmedMessage = message.length > 500 ? `${message.slice(0, 500)}...` : message;
174+
logger.info(`Renderer console[${level}] ${sourceId}:${line} ${trimmedMessage}`);
175+
});
176+
116177
mainWindow.webContents.on('render-process-gone', (_event, details) => {
117178
logger.error(
118179
`Renderer process gone: reason=${details.reason}, exitCode=${details.exitCode}`
@@ -133,6 +194,24 @@ const createWindow = () => {
133194
logger.info('Main window responsive again');
134195
});
135196

197+
loadRenderer(mainWindow).catch((error) => {
198+
logger.error(`Renderer load failed: ${error instanceof Error ? error.message : String(error)}`);
199+
});
200+
201+
setTimeout(() => {
202+
if (!mainWindow || mainWindow.isDestroyed() || rendererDomReady) {
203+
return;
204+
}
205+
206+
logger.warn(
207+
`Renderer did not reach dom-ready after 10s; currentURL=${mainWindow.webContents.getURL() || 'empty'}, loading=${mainWindow.webContents.isLoading()}. Retrying renderer load.`
208+
);
209+
mainWindow.webContents.stop();
210+
loadRenderer(mainWindow).catch((error) => {
211+
logger.error(`Renderer retry failed: ${error instanceof Error ? error.message : String(error)}`);
212+
});
213+
}, 10_000);
214+
136215
// Register IPC handlers
137216
registerIpcHandlers(mainWindow);
138217

@@ -305,6 +384,8 @@ const initializeServers = (window: BrowserWindow) => {
305384
app.on('ready', async () => {
306385
logger.info('App ready, initializing...');
307386

387+
registerRendererProtocol();
388+
308389
// Track app start and user engagement (Kotlin-compatible)
309390
trackAppStart();
310391
trackUserEngagement();

0 commit comments

Comments
 (0)