Skip to content

Commit 8d6a758

Browse files
Martin Wilkeclaude
andauthored
REFACTOR: Convert to manual-only update system as primary method (#21)
🎯 Design Decision: Manual notifications as default instead of auto-updater fallback - Auto-updater completely disabled since we don't have code signing - Prevents background signature validation errors - Manual GitHub API system is now the primary and only update method - Much cleaner and more reliable for unsigned applications 🔧 Changes Made: - Disabled auto-updater initialization and event handlers - Redirected checkForUpdates() to manual system - Removed auto-download and auto-install functionality - Cleaned up signature error detection (no longer needed) - Simplified codebase by removing unused auto-updater complexity 💡 Benefits: - No more background signature validation failures - Cleaner, more predictable update experience - Smaller bundle size (removed auto-updater overhead) - Professional manual update notifications - Ready for future signed app transition 🚀 User Experience: - Settings > About > 'Check for Updates' button - GitHub API integration with version comparison - User-friendly dialogs with direct download links - No confusing error messages or failed background processes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <[email protected]>
1 parent b7be566 commit 8d6a758

File tree

1 file changed

+24
-283
lines changed

1 file changed

+24
-283
lines changed

src/main/services/AutoUpdaterService.ts

Lines changed: 24 additions & 283 deletions
Original file line numberDiff line numberDiff line change
@@ -27,227 +27,50 @@ export class AutoUpdaterService {
2727
* Configure auto-updater settings and event handlers
2828
*/
2929
private setupAutoUpdater(): void {
30-
// Configure auto-updater
31-
autoUpdater.autoDownload = false;
32-
autoUpdater.autoInstallOnAppQuit = true;
30+
// DISABLED: Auto-updater disabled due to lack of code signing
31+
// Using manual notification system as primary update method
32+
log.info('Auto-updater disabled - using manual notification system', 'AutoUpdater');
3333

34-
// DEVELOPMENT FIX: Force development mode to bypass macOS code signature validation
35-
// This solves "code object is not signed at all" error during development
36-
autoUpdater.forceDevUpdateConfig = true;
37-
38-
// In development, only skip event handlers if forceDevUpdateConfig is not enabled
39-
if (process.env.NODE_ENV === 'development' && !autoUpdater.forceDevUpdateConfig) {
40-
return;
41-
}
42-
43-
// Set up event handlers
44-
autoUpdater.on('checking-for-update', () => {
45-
log.info('Checking for updates', 'AutoUpdater');
46-
this.updateCheckInProgress = true;
47-
});
48-
49-
autoUpdater.on('update-available', (info) => {
50-
log.info(`Update available: ${info.version}`, 'AutoUpdater');
51-
this.updateCheckInProgress = false;
52-
void this.handleUpdateAvailable(info);
53-
});
54-
55-
autoUpdater.on('update-not-available', (info) => {
56-
log.info(`No updates available. Current version: ${info.version}`, 'AutoUpdater');
57-
this.updateCheckInProgress = false;
58-
});
59-
60-
autoUpdater.on('error', (error) => {
61-
log.error('Auto-updater error', error, 'AutoUpdater');
62-
this.updateCheckInProgress = false;
63-
64-
// Check if this is a signature validation error
65-
if (this.isSignatureError(error)) {
66-
log.info('Signature validation error detected, trying manual update check', 'AutoUpdater');
67-
void this.fallbackToManualUpdateCheck();
68-
} else {
69-
void this.handleUpdateError(error);
70-
}
71-
});
72-
73-
autoUpdater.on('download-progress', (progressObj) => {
74-
const logMessage = `Download progress: ${progressObj.percent.toFixed(2)}% (${progressObj.transferred}/${progressObj.total} bytes)`;
75-
log.info(logMessage, 'AutoUpdater');
76-
77-
// Send progress to renderer process
78-
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
79-
this.mainWindow.webContents.send('update-download-progress', progressObj);
80-
}
81-
});
82-
83-
autoUpdater.on('update-downloaded', (info) => {
84-
log.info(`Update downloaded: ${info.version}`, 'AutoUpdater');
85-
this.updateDownloaded = true;
86-
void this.handleUpdateDownloaded(info);
87-
});
34+
// Note: Auto-updater completely disabled to prevent signature validation errors
35+
// All update functionality uses manual GitHub API checks via checkForUpdatesManually()
8836
}
8937

9038
/**
91-
* Check for updates manually
39+
* Check for updates manually (redirects to manual system)
9240
*/
9341
async checkForUpdates(): Promise<boolean> {
94-
// Allow update checks in development when forceDevUpdateConfig is enabled
95-
if (process.env.NODE_ENV === 'development' && !autoUpdater.forceDevUpdateConfig) {
96-
log.info('Auto-updater disabled in development mode', 'AutoUpdater');
97-
return false;
98-
}
99-
100-
if (this.updateCheckInProgress) {
101-
log.info('Update check already in progress', 'AutoUpdater');
102-
return false;
103-
}
104-
105-
try {
106-
const result = await autoUpdater.checkForUpdates();
107-
return result !== null;
108-
} catch (error) {
109-
log.error('Failed to check for updates', error as Error, 'AutoUpdater');
110-
return false;
111-
}
42+
log.info('Auto-updater disabled - redirecting to manual update check', 'AutoUpdater');
43+
return this.checkForUpdatesManually();
11244
}
11345

11446
/**
115-
* Download and install update
47+
* Download and install update (disabled - manual only)
11648
*/
11749
async downloadAndInstallUpdate(): Promise<void> {
118-
// Allow downloads in development when forceDevUpdateConfig is enabled
119-
if (process.env.NODE_ENV === 'development' && !autoUpdater.forceDevUpdateConfig) {
120-
return;
121-
}
122-
123-
try {
124-
await autoUpdater.downloadUpdate();
125-
} catch (error) {
126-
log.error('Failed to download update', error as Error, 'AutoUpdater');
127-
throw error;
128-
}
50+
log.info('Auto-download disabled - please use manual update check', 'AutoUpdater');
51+
throw new Error('Automatic downloads disabled. Please use manual update check.');
12952
}
13053

13154
/**
132-
* Install update and restart app
55+
* Install update and restart app (disabled - manual only)
13356
*/
13457
quitAndInstall(): void {
135-
if (!this.updateDownloaded) {
136-
log.error('No update downloaded to install', new Error('No update available'), 'AutoUpdater');
137-
return;
138-
}
139-
140-
log.info('Installing update and restarting app', 'AutoUpdater');
141-
autoUpdater.quitAndInstall();
142-
}
143-
144-
/**
145-
* Handle update available event
146-
*/
147-
private async handleUpdateAvailable(info: { version: string }): Promise<void> {
148-
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
149-
return;
150-
}
151-
152-
const response = await dialog.showMessageBox(this.mainWindow, {
153-
type: 'info',
154-
title: 'Update Available',
155-
message: `A new version (${info.version}) is available. Would you like to download it now?`,
156-
detail: 'The update will be downloaded in the background. You can continue using the app.',
157-
buttons: ['Download Update', 'Later'],
158-
defaultId: 0,
159-
cancelId: 1
160-
});
161-
162-
if (response.response === 0) {
163-
try {
164-
await this.downloadAndInstallUpdate();
165-
} catch (error) {
166-
log.error('Failed to start update download', error as Error, 'AutoUpdater');
167-
}
168-
}
169-
}
170-
171-
/**
172-
* Handle update downloaded event
173-
*/
174-
private async handleUpdateDownloaded(info: { version: string }): Promise<void> {
175-
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
176-
return;
177-
}
178-
179-
const response = await dialog.showMessageBox(this.mainWindow, {
180-
type: 'info',
181-
title: 'Update Ready',
182-
message: `Update (${info.version}) has been downloaded and is ready to install.`,
183-
detail: 'The app will restart to complete the installation.',
184-
buttons: ['Restart Now', 'Restart Later'],
185-
defaultId: 0,
186-
cancelId: 1
187-
});
188-
189-
if (response.response === 0) {
190-
this.quitAndInstall();
191-
}
58+
log.info('Auto-install disabled - please download manually from GitHub', 'AutoUpdater');
59+
throw new Error('Automatic installation disabled. Please download manually from GitHub.');
19260
}
19361

194-
/**
195-
* Handle update error
196-
*/
197-
private async handleUpdateError(error: Error): Promise<void> {
198-
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
199-
return;
200-
}
201-
202-
// Provide user-friendly error messages for common issues
203-
let userMessage = 'An error occurred while checking for updates.';
204-
let userDetail = error.message;
205-
206-
if (error.message.includes('404') && error.message.includes('latest-mac.yml')) {
207-
userMessage = 'Update check temporarily unavailable.';
208-
userDetail = 'The update service is currently unavailable. Please try again later or check for updates manually.';
209-
} else if (error.message.includes('network') || error.message.includes('ENOTFOUND')) {
210-
userMessage = 'Network connection issue.';
211-
userDetail = 'Unable to connect to the update server. Please check your internet connection.';
212-
}
213-
214-
await dialog.showMessageBox(this.mainWindow, {
215-
type: 'error',
216-
title: 'Update Error',
217-
message: userMessage,
218-
detail: userDetail,
219-
buttons: ['OK']
220-
});
221-
}
62+
// Note: Auto-updater event handlers removed since auto-updater is disabled
63+
// All update functionality now uses manual GitHub API system
22264

22365
/**
224-
* Initialize auto-updater after app is ready
66+
* Initialize update system (manual notification only)
22567
*/
22668
initialize(): void {
227-
// Allow initialization in development when forceDevUpdateConfig is enabled
228-
if (process.env.NODE_ENV === 'development' && !autoUpdater.forceDevUpdateConfig) {
229-
log.info('Auto-updater skipped in development mode', 'AutoUpdater');
230-
return;
231-
}
232-
233-
// Log development mode status
234-
if (autoUpdater.forceDevUpdateConfig) {
235-
log.info('Auto-updater initialized in development mode with forceDevUpdateConfig', 'AutoUpdater');
236-
}
237-
238-
// Wait a bit after app startup before checking for updates
239-
setTimeout(() => {
240-
this.checkForUpdates().catch((error) => {
241-
log.error('Initial update check failed', error as Error, 'AutoUpdater');
242-
});
243-
}, 10000); // Wait 10 seconds after startup
244-
245-
// Set up periodic checks (every 4 hours)
246-
setInterval(() => {
247-
this.checkForUpdates().catch((error) => {
248-
log.error('Periodic update check failed', error as Error, 'AutoUpdater');
249-
});
250-
}, 4 * 60 * 60 * 1000);
69+
log.info('Update system initialized - manual notification mode only', 'AutoUpdater');
70+
71+
// No automatic background checks since we use manual system
72+
// Users can check for updates via Settings > About > Check for Updates
73+
// This prevents background signature validation errors
25174
}
25275

25376
/**
@@ -305,48 +128,7 @@ export class AutoUpdaterService {
305128
}
306129
}
307130

308-
/**
309-
* Check if error is related to code signature validation
310-
*/
311-
private isSignatureError(error: Error): boolean {
312-
const signatureErrors = [
313-
'code object is not signed',
314-
'signature validation',
315-
'code signature',
316-
'gatekeeper',
317-
'developer cannot be verified'
318-
];
319-
320-
return signatureErrors.some(errorType =>
321-
error.message.toLowerCase().includes(errorType.toLowerCase())
322-
);
323-
}
324-
325-
/**
326-
* Fallback to manual update check when auto-updater fails with signature error
327-
*/
328-
private async fallbackToManualUpdateCheck(): Promise<void> {
329-
try {
330-
const latestRelease = await this.fetchLatestRelease();
331-
332-
if (!latestRelease) {
333-
await this.showSignatureErrorWithoutUpdate();
334-
return;
335-
}
336-
337-
const currentVersion = getVersion();
338-
const latestVersion = latestRelease.tag_name.replace(/^v/, '');
339-
340-
if (this.isNewerVersion(latestVersion, currentVersion)) {
341-
await this.showSignatureErrorWithUpdate(latestRelease);
342-
} else {
343-
await this.showSignatureErrorWithoutUpdate();
344-
}
345-
} catch (error) {
346-
log.error('Fallback manual update check failed', error as Error, 'AutoUpdater');
347-
await this.showSignatureErrorWithoutUpdate();
348-
}
349-
}
131+
// Note: Signature error detection removed since auto-updater is completely disabled
350132

351133
/**
352134
* Fetch latest release from GitHub API
@@ -454,48 +236,7 @@ export class AutoUpdaterService {
454236
});
455237
}
456238

457-
/**
458-
* Show signature error dialog when update is available
459-
*/
460-
private async showSignatureErrorWithUpdate(release: any): Promise<void> {
461-
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
462-
return;
463-
}
464-
465-
const releaseUrl = release.html_url;
466-
const version = release.tag_name;
467-
468-
const response = await dialog.showMessageBox(this.mainWindow, {
469-
type: 'warning',
470-
title: 'Update Available - Manual Download Required',
471-
message: `CCTracker ${version} is available!`,
472-
detail: `Automatic updates are not available due to code signing requirements. Please download the latest version manually from GitHub releases.`,
473-
buttons: ['Download Now', 'Later'],
474-
defaultId: 0,
475-
cancelId: 1
476-
});
477-
478-
if (response.response === 0) {
479-
await shell.openExternal(releaseUrl);
480-
}
481-
}
482-
483-
/**
484-
* Show signature error dialog when no update is available
485-
*/
486-
private async showSignatureErrorWithoutUpdate(): Promise<void> {
487-
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
488-
return;
489-
}
490-
491-
await dialog.showMessageBox(this.mainWindow, {
492-
type: 'warning',
493-
title: 'Update Check Complete',
494-
message: 'Automatic updates unavailable',
495-
detail: 'Automatic updates are not available due to code signing requirements, but you already have the latest version.',
496-
buttons: ['OK']
497-
});
498-
}
239+
// Note: Signature error dialogs removed since we use manual-only update system
499240
}
500241

501242
export const autoUpdaterService = new AutoUpdaterService();

0 commit comments

Comments
 (0)