chore: 禁用 native auto-updater,改为引导用户从 GitHub 下载
macOS 上 ad-hoc 签名无法通过 ShipIt 的 kSecCSStrictValidate 校验, 导致热更新始终失败。在获取 Apple 开发者证书之前,暂时禁用 native auto-updater,改为 browser-mode 检查 GitHub Releases API: - electron/updater.ts: 注释掉 electron-updater 逻辑,initAutoUpdater 和 setUpdaterWindow 改为 no-op - electron/preload.ts: 注释掉 updater IPC bridge,前端不再暴露 window.electronAPI.updater - AppShell.tsx: isNativeUpdater 硬编码为 false,移除 native IPC 订阅 和 native check/download/quitAndInstall 逻辑;保留 browser-mode 的 GitHub API 检查(mount 时 + 每 8 小时),发现新版本时弹窗引导 用户跳转 GitHub Release 页面下载 设置页 UpdateCard 保持不变:「检查更新」按钮通过 /api/app/updates 查询 GitHub,有新版本时显示「查看发布」按钮跳转下载。 TODO: 获取 Apple 开发者证书后,取消注释恢复 native auto-updater。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
819f9f401a
commit
ad34fe387b
3 files changed
+45
-248
No files matched your search
+13
-10
@@ -25,14 +25,17 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
return () => { ipcRenderer.removeListener('install:progress', listener); };
|
||||
},
|
||||
},
|
||||
updater: {
|
||||
checkForUpdates: () => ipcRenderer.invoke('updater:check'),
|
||||
downloadUpdate: () => ipcRenderer.invoke('updater:download'),
|
||||
quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'),
|
||||
onStatus: (callback: (data: unknown) => void) => {
|
||||
const listener = (_event: unknown, data: unknown) => callback(data);
|
||||
ipcRenderer.on('updater:status', listener);
|
||||
return () => { ipcRenderer.removeListener('updater:status', listener); };
|
||||
},
|
||||
},
|
||||
// Native auto-updater bridge — disabled (code signature issues on macOS).
|
||||
// Users are directed to download from GitHub Releases.
|
||||
// TODO: Re-enable after obtaining Apple Developer certificate.
|
||||
// updater: {
|
||||
// checkForUpdates: () => ipcRenderer.invoke('updater:check'),
|
||||
// downloadUpdate: () => ipcRenderer.invoke('updater:download'),
|
||||
// quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'),
|
||||
// onStatus: (callback: (data: unknown) => void) => {
|
||||
// const listener = (_event: unknown, data: unknown) => callback(data);
|
||||
// ipcRenderer.on('updater:status', listener);
|
||||
// return () => { ipcRenderer.removeListener('updater:status', listener); };
|
||||
// },
|
||||
// },
|
||||
});
|
||||
+20
-127
@@ -1,132 +1,25 @@
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
// =============================================================================
|
||||
// Native auto-updater (electron-updater) — DISABLED
|
||||
//
|
||||
// Temporarily disabled due to macOS code signature validation failures with
|
||||
// ad-hoc signing. Users are directed to download from GitHub Releases instead.
|
||||
// The browser-mode update check (via /api/app/updates) remains active in the
|
||||
// frontend to notify users of new versions.
|
||||
//
|
||||
// TODO: Re-enable after obtaining an Apple Developer certificate for proper
|
||||
// code signing, then uncomment this file and the calls in main.ts / preload.ts.
|
||||
// =============================================================================
|
||||
|
||||
// import { autoUpdater } from 'electron-updater';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import { ipcMain, session } from 'electron';
|
||||
// import { ipcMain, session } from 'electron';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
function sendStatus(data: Record<string, unknown>) {
|
||||
mainWindow?.webContents.send('updater:status', data);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function initAutoUpdater(_win: BrowserWindow) {
|
||||
console.log('[updater] Native auto-updater is disabled. Users should download updates from GitHub Releases.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve system proxy for GitHub and inject into electron-updater
|
||||
* so that VPN / proxy tools are respected during update downloads.
|
||||
*/
|
||||
async function configureProxy() {
|
||||
try {
|
||||
const proxy = await session.defaultSession.resolveProxy('https://github.com');
|
||||
// proxy returns "DIRECT" or "PROXY host:port" / "SOCKS5 host:port" etc.
|
||||
if (proxy && proxy !== 'DIRECT') {
|
||||
const match = proxy.match(/^(?:PROXY|HTTPS)\s+(.+)/i);
|
||||
if (match) {
|
||||
process.env.HTTPS_PROXY = `http://${match[1]}`;
|
||||
console.log('[updater] Using system proxy:', process.env.HTTPS_PROXY);
|
||||
}
|
||||
const socksMatch = proxy.match(/^SOCKS5?\s+(.+)/i);
|
||||
if (socksMatch) {
|
||||
process.env.HTTPS_PROXY = `socks5://${socksMatch[1]}`;
|
||||
console.log('[updater] Using system SOCKS proxy:', process.env.HTTPS_PROXY);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[updater] Failed to resolve proxy:', err);
|
||||
}
|
||||
}
|
||||
|
||||
export function initAutoUpdater(win: BrowserWindow) {
|
||||
mainWindow = win;
|
||||
|
||||
// Configuration — don't auto-download, let user trigger manually
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
// Resolve and apply system proxy for update downloads
|
||||
configureProxy();
|
||||
|
||||
// --- Events ---
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
sendStatus({ status: 'checking' });
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
sendStatus({
|
||||
status: 'available',
|
||||
info: {
|
||||
version: info.version,
|
||||
releaseNotes: info.releaseNotes,
|
||||
releaseName: info.releaseName,
|
||||
releaseDate: info.releaseDate,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
sendStatus({ status: 'not-available' });
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
sendStatus({
|
||||
status: 'downloading',
|
||||
progress: {
|
||||
percent: progress.percent,
|
||||
bytesPerSecond: progress.bytesPerSecond,
|
||||
transferred: progress.transferred,
|
||||
total: progress.total,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
sendStatus({
|
||||
status: 'downloaded',
|
||||
info: {
|
||||
version: info.version,
|
||||
releaseNotes: info.releaseNotes,
|
||||
releaseName: info.releaseName,
|
||||
releaseDate: info.releaseDate,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
sendStatus({ status: 'error', error: err.message });
|
||||
});
|
||||
|
||||
// --- IPC handlers ---
|
||||
ipcMain.handle('updater:check', async () => {
|
||||
return autoUpdater.checkForUpdates();
|
||||
});
|
||||
|
||||
ipcMain.handle('updater:download', async () => {
|
||||
try {
|
||||
return await autoUpdater.downloadUpdate();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error('[updater] Download failed:', message);
|
||||
sendStatus({ status: 'error', error: message });
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('updater:quit-and-install', () => {
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
// Initial check after 10 seconds
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates().catch((err) => {
|
||||
console.warn('[updater] Initial check failed:', err.message);
|
||||
});
|
||||
}, 10_000);
|
||||
|
||||
// Periodic check every 4 hours
|
||||
setInterval(() => {
|
||||
autoUpdater.checkForUpdates().catch((err) => {
|
||||
console.warn('[updater] Periodic check failed:', err.message);
|
||||
});
|
||||
}, 4 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
export function setUpdaterWindow(win: BrowserWindow) {
|
||||
mainWindow = win;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function setUpdaterWindow(_win: BrowserWindow) {
|
||||
// no-op while native updater is disabled
|
||||
}
|
||||
@@ -368,9 +368,12 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
|
||||
const isNativeUpdater = typeof window !== "undefined" && !!window.electronAPI?.updater;
|
||||
// Native auto-updater disabled — always use browser-mode (GitHub API) check.
|
||||
// Users are directed to download from GitHub Releases.
|
||||
// TODO: Re-enable native updater after obtaining Apple Developer certificate.
|
||||
const isNativeUpdater = false;
|
||||
|
||||
// --- Browser-mode update check (fallback) ---
|
||||
// --- Browser-mode update check ---
|
||||
const checkForUpdatesBrowser = useCallback(async () => {
|
||||
setChecking(true);
|
||||
try {
|
||||
@@ -399,130 +402,28 @@ export function AppShell({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// --- Electron native updater check ---
|
||||
const checkForUpdatesNative = useCallback(async () => {
|
||||
setChecking(true);
|
||||
setUpdateInfo((prev) => prev ? { ...prev, lastError: null } : prev);
|
||||
try {
|
||||
await window.electronAPI?.updater?.checkForUpdates();
|
||||
} catch {
|
||||
setChecking(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const checkForUpdates = isNativeUpdater ? checkForUpdatesNative : checkForUpdatesBrowser;
|
||||
|
||||
// Subscribe to native updater IPC events
|
||||
useEffect(() => {
|
||||
if (!isNativeUpdater) return;
|
||||
|
||||
const unsubscribe = window.electronAPI!.updater!.onStatus((event) => {
|
||||
switch (event.status) {
|
||||
case 'checking':
|
||||
setChecking(true);
|
||||
break;
|
||||
|
||||
case 'available':
|
||||
setChecking(false);
|
||||
setUpdateInfo((prev) => {
|
||||
const releaseNotes = typeof event.info?.releaseNotes === 'string'
|
||||
? event.info.releaseNotes
|
||||
: '';
|
||||
const newInfo: UpdateInfo = {
|
||||
updateAvailable: true,
|
||||
latestVersion: event.info?.version ?? '',
|
||||
currentVersion: prev?.currentVersion ?? (process.env.NEXT_PUBLIC_APP_VERSION || '0.0.0'),
|
||||
releaseName: event.info?.releaseName ?? `v${event.info?.version}`,
|
||||
releaseNotes,
|
||||
releaseUrl: `https://github.com/op7418/CodePilot/releases/tag/v${event.info?.version}`,
|
||||
publishedAt: event.info?.releaseDate ?? '',
|
||||
downloadProgress: prev?.downloadProgress ?? null,
|
||||
readyToInstall: prev?.readyToInstall ?? false,
|
||||
isNativeUpdate: true,
|
||||
lastError: null,
|
||||
};
|
||||
return newInfo;
|
||||
});
|
||||
// Show dialog if not dismissed
|
||||
if (event.info?.version) {
|
||||
const dismissed = localStorage.getItem(DISMISSED_VERSION_KEY);
|
||||
if (dismissed !== event.info.version) {
|
||||
setShowDialog(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'not-available':
|
||||
setChecking(false);
|
||||
break;
|
||||
|
||||
case 'downloading':
|
||||
setUpdateInfo((prev) => prev ? {
|
||||
...prev,
|
||||
downloadProgress: event.progress?.percent ?? null,
|
||||
lastError: null,
|
||||
} : prev);
|
||||
break;
|
||||
|
||||
case 'downloaded':
|
||||
setUpdateInfo((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
readyToInstall: true,
|
||||
downloadProgress: 100,
|
||||
lastError: null,
|
||||
};
|
||||
});
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
setChecking(false);
|
||||
// Reset download progress so the download button re-appears
|
||||
setUpdateInfo((prev) => prev ? {
|
||||
...prev,
|
||||
downloadProgress: null,
|
||||
lastError: event.error ?? 'Update failed',
|
||||
} : prev);
|
||||
console.warn('[updater] Error:', event.error);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [isNativeUpdater]);
|
||||
// Native updater check disabled — always use browser-mode check
|
||||
const checkForUpdates = checkForUpdatesBrowser;
|
||||
|
||||
// Browser mode: check on mount + every 8 hours
|
||||
useEffect(() => {
|
||||
if (isNativeUpdater) return;
|
||||
checkForUpdatesBrowser();
|
||||
const id = setInterval(checkForUpdatesBrowser, CHECK_INTERVAL);
|
||||
return () => clearInterval(id);
|
||||
}, [isNativeUpdater, checkForUpdatesBrowser]);
|
||||
}, [checkForUpdatesBrowser]);
|
||||
|
||||
const dismissUpdate = useCallback(() => {
|
||||
setShowDialog(false);
|
||||
}, []);
|
||||
|
||||
// Native download/install disabled — no-ops while native updater is disabled.
|
||||
// The UI shows "View Release" (opens GitHub) instead.
|
||||
const downloadUpdate = useCallback(async () => {
|
||||
// Immediately show downloading state so user gets feedback
|
||||
setUpdateInfo((prev) => prev ? { ...prev, downloadProgress: 0, lastError: null } : prev);
|
||||
try {
|
||||
await window.electronAPI?.updater?.downloadUpdate();
|
||||
} catch (err) {
|
||||
console.warn('[updater] Download failed:', err);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
// Reset progress so the download button re-appears
|
||||
setUpdateInfo((prev) => prev ? {
|
||||
...prev,
|
||||
downloadProgress: null,
|
||||
lastError: message,
|
||||
} : prev);
|
||||
}
|
||||
// no-op: native updater disabled
|
||||
}, []);
|
||||
|
||||
const quitAndInstall = useCallback(() => {
|
||||
window.electronAPI?.updater?.quitAndInstall();
|
||||
// no-op: native updater disabled
|
||||
}, []);
|
||||
|
||||
const updateContextValue = useMemo(
|
||||
|
||||
Reference in new issue
Block a user