|
1 | 1 | import { BrowserWindow, ipcMain, app } from "electron" |
2 | 2 | import log from "electron-log" |
3 | 3 | import { autoUpdater, type UpdateInfo, type ProgressInfo } from "electron-updater" |
| 4 | +import { readFileSync, writeFileSync, existsSync } from "fs" |
| 5 | +import { join } from "path" |
4 | 6 |
|
5 | 7 | /** |
6 | 8 | * IMPORTANT: Do NOT use lazy/dynamic imports for electron-updater! |
@@ -30,6 +32,38 @@ const CDN_BASE = "https://cdn.21st.dev/releases/desktop" |
30 | 32 | const MIN_CHECK_INTERVAL = 60 * 1000 // 1 minute |
31 | 33 | let lastCheckTime = 0 |
32 | 34 |
|
| 35 | +// Update channel preference file |
| 36 | +const CHANNEL_PREF_FILE = "update-channel.json" |
| 37 | + |
| 38 | +type UpdateChannel = "latest" | "beta" |
| 39 | + |
| 40 | +function getChannelPrefPath(): string { |
| 41 | + return join(app.getPath("userData"), CHANNEL_PREF_FILE) |
| 42 | +} |
| 43 | + |
| 44 | +function getSavedChannel(): UpdateChannel { |
| 45 | + try { |
| 46 | + const prefPath = getChannelPrefPath() |
| 47 | + if (existsSync(prefPath)) { |
| 48 | + const data = JSON.parse(readFileSync(prefPath, "utf-8")) |
| 49 | + if (data.channel === "beta" || data.channel === "latest") { |
| 50 | + return data.channel |
| 51 | + } |
| 52 | + } |
| 53 | + } catch { |
| 54 | + // Ignore read errors, fall back to default |
| 55 | + } |
| 56 | + return "latest" |
| 57 | +} |
| 58 | + |
| 59 | +function saveChannel(channel: UpdateChannel): void { |
| 60 | + try { |
| 61 | + writeFileSync(getChannelPrefPath(), JSON.stringify({ channel }), "utf-8") |
| 62 | + } catch (error) { |
| 63 | + log.error("[AutoUpdater] Failed to save channel preference:", error) |
| 64 | + } |
| 65 | +} |
| 66 | + |
33 | 67 | let getAllWindows: (() => BrowserWindow[]) | null = null |
34 | 68 |
|
35 | 69 | /** |
@@ -58,6 +92,11 @@ export async function initAutoUpdater(getWindows: () => BrowserWindow[]) { |
58 | 92 | // Initialize config |
59 | 93 | initAutoUpdaterConfig() |
60 | 94 |
|
| 95 | + // Set update channel from saved preference |
| 96 | + const savedChannel = getSavedChannel() |
| 97 | + autoUpdater.channel = savedChannel |
| 98 | + log.info(`[AutoUpdater] Using update channel: ${savedChannel}`) |
| 99 | + |
61 | 100 | // Configure feed URL to point to R2 CDN |
62 | 101 | // Note: We use a custom request headers to bypass CDN cache |
63 | 102 | autoUpdater.setFeedURL({ |
@@ -202,6 +241,31 @@ function registerIpcHandlers() { |
202 | 241 | currentVersion: app.getVersion(), |
203 | 242 | } |
204 | 243 | }) |
| 244 | + |
| 245 | + // Set update channel (latest = stable only, beta = stable + beta) |
| 246 | + ipcMain.handle("update:set-channel", async (_event, channel: string) => { |
| 247 | + if (channel !== "latest" && channel !== "beta") { |
| 248 | + log.warn(`[AutoUpdater] Invalid channel: ${channel}`) |
| 249 | + return false |
| 250 | + } |
| 251 | + log.info(`[AutoUpdater] Switching update channel to: ${channel}`) |
| 252 | + autoUpdater.channel = channel |
| 253 | + saveChannel(channel) |
| 254 | + // Check for updates immediately with new channel |
| 255 | + if (app.isPackaged) { |
| 256 | + try { |
| 257 | + await autoUpdater.checkForUpdates() |
| 258 | + } catch (error) { |
| 259 | + log.error("[AutoUpdater] Post-channel-switch check failed:", error) |
| 260 | + } |
| 261 | + } |
| 262 | + return true |
| 263 | + }) |
| 264 | + |
| 265 | + // Get current update channel |
| 266 | + ipcMain.handle("update:get-channel", () => { |
| 267 | + return getSavedChannel() |
| 268 | + }) |
205 | 269 | } |
206 | 270 |
|
207 | 271 | /** |
|
0 commit comments