Skip to content

Commit 7d2159e

Browse files
release: v0.7.19
refactor(updater): replace in-app download with browser-based download - Remove download/install IPC handlers from main process - Simplify preload bridge (check + openRelease only) - Banner and Settings open direct installer URL per platform/arch - Add arch to appInfo for correct macOS dmg selection
1 parent 169bb6a commit 7d2159e

9 files changed

Lines changed: 39 additions & 190 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,6 +1,6 @@
11
{
22
"name": "netcopilot",
3-
"version": "0.7.18",
3+
"version": "0.7.19",
44
"description": "NetCopilot – AI-powered SSH/Telnet terminal for network engineers",
55
"main": "./out/main/index.js",
66
"author": {

src/main/updater.ts

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ipcMain, BrowserWindow, shell } from 'electron'
2-
import { autoUpdater, UpdateInfo, ProgressInfo } from 'electron-updater'
2+
import { autoUpdater, UpdateInfo } from 'electron-updater'
33
import log from 'electron-log'
44

55
autoUpdater.logger = log
@@ -9,11 +9,8 @@ autoUpdater.autoInstallOnAppQuit = false
99
const isDev = !!process.env['ELECTRON_RENDERER_URL']
1010

1111
export function setupAutoUpdater(getWindow: () => BrowserWindow | null): void {
12-
// In dev mode, register no-op handlers — app-update.yml doesn't exist in dev
1312
if (isDev) {
14-
ipcMain.handle('updater:check', async () => ({ success: false, error: 'Not available in dev mode' }))
15-
ipcMain.handle('updater:download', async () => ({ success: false, error: 'Not available in dev mode' }))
16-
ipcMain.handle('updater:install', () => {})
13+
ipcMain.handle('updater:check', async () => ({ success: false, error: 'Not available in dev mode' }))
1714
ipcMain.handle('updater:open-release', (_e, url: string) => { shell.openExternal(url) })
1815
return
1916
}
@@ -24,7 +21,7 @@ export function setupAutoUpdater(getWindow: () => BrowserWindow | null): void {
2421

2522
autoUpdater.on('update-available', (info: UpdateInfo) => {
2623
send('updater:update-available', {
27-
version: info.version,
24+
version: info.version,
2825
releaseDate: info.releaseDate,
2926
releaseNotes: info.releaseNotes ?? null,
3027
})
@@ -34,25 +31,11 @@ export function setupAutoUpdater(getWindow: () => BrowserWindow | null): void {
3431
send('updater:update-not-available')
3532
})
3633

37-
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
38-
send('updater:download-progress', {
39-
percent: Math.round(progress.percent),
40-
transferred: progress.transferred,
41-
total: progress.total,
42-
bytesPerSecond: progress.bytesPerSecond,
43-
})
44-
})
45-
46-
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
47-
send('updater:update-downloaded', { version: info.version })
48-
})
49-
5034
autoUpdater.on('error', (err: Error) => {
5135
log.error('Auto-updater error:', err)
5236
send('updater:error', err.message)
5337
})
5438

55-
// IPC: manual check triggered from renderer
5639
ipcMain.handle('updater:check', async () => {
5740
try {
5841
const result = await autoUpdater.checkForUpdates()
@@ -72,27 +55,6 @@ export function setupAutoUpdater(getWindow: () => BrowserWindow | null): void {
7255
}
7356
})
7457

75-
// IPC: start download
76-
ipcMain.handle('updater:download', async () => {
77-
try {
78-
await autoUpdater.downloadUpdate()
79-
return { success: true }
80-
} catch (err) {
81-
return { success: false, error: String(err) }
82-
}
83-
})
84-
85-
// IPC: quit and install (falls back to website download page on failure)
86-
ipcMain.handle('updater:install', async () => {
87-
try {
88-
autoUpdater.quitAndInstall(false, true)
89-
} catch (err) {
90-
log.warn('quitAndInstall failed, opening download page:', err)
91-
shell.openExternal('https://netcopilot.app/#download')
92-
}
93-
})
94-
95-
// IPC: open release page in browser (fallback for unsigned builds)
9658
ipcMain.handle('updater:open-release', (_e, url: string) => {
9759
try {
9860
const parsed = new URL(url)
@@ -102,7 +64,7 @@ export function setupAutoUpdater(getWindow: () => BrowserWindow | null): void {
10264
} catch { /* malformed URL — ignore */ }
10365
})
10466

105-
// Auto-check on startup (production only, after 6s to not block launch)
67+
// Auto-check on startup (production only, after 6s)
10668
if (!process.env['ELECTRON_RENDERER_URL']) {
10769
setTimeout(() => {
10870
autoUpdater.checkForUpdates().catch((err) => {

src/preload/index.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const api = {
9292
chrome: process.versions.chrome
9393
},
9494
platform: process.platform,
95+
arch: process.arch,
9596
getVersion: (): Promise<string> => ipcRenderer.invoke('app:get-version'),
9697
},
9798

@@ -108,32 +109,15 @@ const api = {
108109
},
109110
},
110111

111-
// Auto-updater
112+
// Auto-updater (check only — downloads open in browser)
112113
updater: {
113114
check: () => ipcRenderer.invoke('updater:check'),
114-
download: () => ipcRenderer.invoke('updater:download'),
115-
install: () => ipcRenderer.invoke('updater:install'),
116115
openRelease: (url: string) => ipcRenderer.invoke('updater:open-release', url),
117116
onUpdateAvailable: (cb: (info: { version: string; releaseDate: string; releaseNotes: string | null }) => void) => {
118117
const handler = (_: unknown, info: { version: string; releaseDate: string; releaseNotes: string | null }) => cb(info)
119118
ipcRenderer.on('updater:update-available', handler)
120119
return () => ipcRenderer.removeListener('updater:update-available', handler)
121120
},
122-
onUpdateNotAvailable: (cb: () => void) => {
123-
const handler = () => cb()
124-
ipcRenderer.on('updater:update-not-available', handler)
125-
return () => ipcRenderer.removeListener('updater:update-not-available', handler)
126-
},
127-
onDownloadProgress: (cb: (progress: { percent: number; bytesPerSecond: number; transferred: number; total: number }) => void) => {
128-
const handler = (_: unknown, p: { percent: number; bytesPerSecond: number; transferred: number; total: number }) => cb(p)
129-
ipcRenderer.on('updater:download-progress', handler)
130-
return () => ipcRenderer.removeListener('updater:download-progress', handler)
131-
},
132-
onUpdateDownloaded: (cb: (info: { version: string }) => void) => {
133-
const handler = (_: unknown, info: { version: string }) => cb(info)
134-
ipcRenderer.on('updater:update-downloaded', handler)
135-
return () => ipcRenderer.removeListener('updater:update-downloaded', handler)
136-
},
137121
onError: (cb: (message: string) => void) => {
138122
const handler = (_: unknown, message: string) => cb(message)
139123
ipcRenderer.on('updater:error', handler)

src/renderer/src/App.tsx

Lines changed: 23 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { SettingsDialog } from './components/dialogs/SettingsDialog'
1212
import { HelpDialog } from './components/dialogs/HelpDialog'
1313
import { TitleBar } from './components/TitleBar'
1414
import { MasterPasswordLock } from './components/MasterPasswordLock'
15-
import { cn } from './lib/utils'
15+
import { cn, getInstallerUrl } from './lib/utils'
1616

1717
export default function App(): JSX.Element {
1818
const {
@@ -30,9 +30,6 @@ export default function App(): JSX.Element {
3030
const autoLockMinsRef = useRef(0)
3131
const [updateBanner, setUpdateBanner] = useState<{
3232
version: string
33-
downloaded: boolean
34-
downloading: boolean
35-
progress: number
3633
} | null>(null)
3734

3835
// Check master password on startup
@@ -51,21 +48,12 @@ export default function App(): JSX.Element {
5148
}
5249
}, [masterLocked])
5350

54-
// Listen for auto-updater events from main process
51+
// Listen for auto-updater "update available" event
5552
useEffect(() => {
5653
const offAvailable = window.api.updater.onUpdateAvailable((info) => {
57-
setUpdateBanner({ version: info.version, downloaded: false, downloading: false, progress: 0 })
54+
setUpdateBanner({ version: info.version })
5855
})
59-
const offProgress = window.api.updater.onDownloadProgress((p) => {
60-
setUpdateBanner((prev) => prev ? { ...prev, downloading: true, progress: p.percent } : prev)
61-
})
62-
const offDownloaded = window.api.updater.onUpdateDownloaded(() => {
63-
setUpdateBanner((prev) => prev ? { ...prev, downloaded: true, downloading: false, progress: 100 } : prev)
64-
})
65-
const offError = window.api.updater.onError(() => {
66-
setUpdateBanner((prev) => prev ? { ...prev, downloading: false } : prev)
67-
})
68-
return () => { offAvailable(); offProgress(); offDownloaded(); offError() }
56+
return () => { offAvailable() }
6957
}, [])
7058

7159
// Auto-lock idle timer
@@ -231,21 +219,14 @@ export default function App(): JSX.Element {
231219
'bg-background/95 backdrop-blur-sm shadow-2xl shadow-black/40',
232220
'flex flex-col gap-3 p-4'
233221
)}>
234-
{/* Header */}
235222
<div className="flex items-start justify-between gap-2">
236223
<div className="flex items-center gap-2.5">
237224
<div className="w-8 h-8 rounded-lg bg-primary/15 flex items-center justify-center shrink-0">
238225
<ArrowUpCircle className="w-4 h-4 text-primary" />
239226
</div>
240227
<div>
241228
<p className="text-sm font-semibold text-foreground">Update Available</p>
242-
<p className="text-xs text-muted-foreground">
243-
{updateBanner.downloaded
244-
? 'Ready to install'
245-
: updateBanner.downloading
246-
? `Downloading… ${updateBanner.progress}%`
247-
: `v${updateBanner.version} is ready`}
248-
</p>
229+
<p className="text-xs text-muted-foreground">v{updateBanner.version} is ready</p>
249230
</div>
250231
</div>
251232
<button
@@ -256,49 +237,24 @@ export default function App(): JSX.Element {
256237
</button>
257238
</div>
258239

259-
{/* Download progress bar */}
260-
{updateBanner.downloading && (
261-
<div className="h-1 w-full bg-muted rounded-full overflow-hidden">
262-
<div
263-
className="h-full bg-primary rounded-full transition-all duration-300"
264-
style={{ width: `${updateBanner.progress}%` }}
265-
/>
266-
</div>
267-
)}
268-
269-
{/* Actions */}
270-
{!updateBanner.downloading && (
271-
<div className="flex gap-2">
272-
{updateBanner.downloaded ? (
273-
<button
274-
onClick={() => window.api.updater.install()}
275-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
276-
>
277-
<ArrowUpCircle className="w-3.5 h-3.5" />
278-
Restart &amp; Install
279-
</button>
280-
) : (
281-
<button
282-
onClick={() => {
283-
setUpdateBanner((prev) => prev ? { ...prev, downloading: true } : prev)
284-
window.api.updater.download().catch(() => {
285-
setUpdateBanner((prev) => prev ? { ...prev, downloading: false } : prev)
286-
})
287-
}}
288-
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors"
289-
>
290-
<ArrowUpCircle className="w-3.5 h-3.5" />
291-
Download Update
292-
</button>
293-
)}
294-
<button
295-
onClick={() => setUpdateBanner(null)}
296-
className="flex-1 px-3 py-2 rounded-lg bg-secondary text-secondary-foreground text-xs font-medium hover:bg-secondary/80 transition-colors"
297-
>
298-
Later
299-
</button>
300-
</div>
301-
)}
240+
<div className="flex gap-2">
241+
<button
242+
onClick={() => {
243+
window.api.updater.openRelease(getInstallerUrl(updateBanner.version))
244+
setUpdateBanner(null)
245+
}}
246+
className="flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-lg bg-primary text-primary-foreground text-xs font-medium hover:bg-primary/90 transition-colors cursor-pointer"
247+
>
248+
<ArrowUpCircle className="w-3.5 h-3.5" />
249+
Download Update
250+
</button>
251+
<button
252+
onClick={() => setUpdateBanner(null)}
253+
className="flex-1 px-3 py-2 rounded-lg bg-secondary text-secondary-foreground text-xs font-medium hover:bg-secondary/80 transition-colors cursor-pointer"
254+
>
255+
Later
256+
</button>
257+
</div>
302258
</div>
303259
)}
304260

src/renderer/src/components/dialogs/SettingsDialog.tsx

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
RefreshCw, ArrowUpCircle, AlertCircle
88
} from 'lucide-react'
99
import { useAppStore, AiPermission, AiApproval } from '../../store'
10-
import { cn } from '../../lib/utils'
10+
import { cn, getInstallerUrl } from '../../lib/utils'
1111

1212
// ─── Settings data model ────────────────────────────────────────────────────
1313
interface AppSettings {
@@ -699,7 +699,6 @@ function AboutSection() {
699699
'idle' | 'checking' | 'up-to-date' | 'available' | 'downloading' | 'downloaded' | 'error'
700700
>('idle')
701701
const [updateVersion, setUpdateVersion] = useState<string | null>(null)
702-
const [downloadProgress, setDownloadProgress] = useState(0)
703702

704703
const platform = info?.platform === 'darwin' ? 'macOS'
705704
: info?.platform === 'win32' ? 'Windows'
@@ -714,15 +713,7 @@ function AboutSection() {
714713
setUpdateVersion(i.version)
715714
setUpdateState('available')
716715
})
717-
const offProgress = window.api.updater.onDownloadProgress((p) => {
718-
setDownloadProgress(p.percent)
719-
setUpdateState('downloading')
720-
})
721-
const offDownloaded = window.api.updater.onUpdateDownloaded((i) => {
722-
setUpdateVersion(i.version)
723-
setUpdateState('downloaded')
724-
})
725-
return () => { offAvailable(); offProgress(); offDownloaded() }
716+
return () => { offAvailable() }
726717
}, [])
727718

728719
const handleCheckUpdate = async () => {
@@ -743,13 +734,6 @@ function AboutSection() {
743734
}
744735
}
745736

746-
const handleDownload = async () => {
747-
setUpdateState('downloading')
748-
setDownloadProgress(0)
749-
const res = await window.api.updater.download()
750-
if (!res.success) setUpdateState('error')
751-
}
752-
753737
const rows = [
754738
{ label: 'Version', value: appVersion },
755739
{ label: 'Device ID', value: deviceId },
@@ -793,7 +777,7 @@ function AboutSection() {
793777
<div className="w-full space-y-2">
794778
<button
795779
onClick={handleCheckUpdate}
796-
disabled={updateState === 'checking' || updateState === 'downloading'}
780+
disabled={updateState === 'checking'}
797781
className={cn(
798782
'w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-colors cursor-pointer',
799783
'border border-border bg-card hover:bg-accent disabled:opacity-60 disabled:cursor-not-allowed'
@@ -820,47 +804,14 @@ function AboutSection() {
820804
</span>
821805
</div>
822806
<button
823-
onClick={handleDownload}
807+
onClick={() => window.api.updater.openRelease(getInstallerUrl(updateVersion))}
824808
className="shrink-0 text-xs px-3 py-1.5 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors font-medium cursor-pointer"
825809
>
826810
Download →
827811
</button>
828812
</div>
829813
)}
830814

831-
{updateState === 'downloading' && (
832-
<div className="space-y-1.5 px-3 py-2.5 rounded-lg bg-primary/10 border border-primary/25">
833-
<div className="flex items-center justify-between text-sm">
834-
<span className="text-foreground flex items-center gap-2">
835-
<RefreshCw className="w-3.5 h-3.5 text-primary animate-spin" />
836-
Downloading update…
837-
</span>
838-
<span className="text-primary font-mono text-xs">{Math.round(downloadProgress)}%</span>
839-
</div>
840-
<div className="h-1 w-full bg-muted rounded-full overflow-hidden">
841-
<div
842-
className="h-full bg-primary rounded-full transition-all duration-300"
843-
style={{ width: `${downloadProgress}%` }}
844-
/>
845-
</div>
846-
</div>
847-
)}
848-
849-
{updateState === 'downloaded' && (
850-
<div className="flex items-center justify-between gap-3 px-3 py-2.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20">
851-
<div className="flex items-center gap-2 text-sm text-emerald-400">
852-
<Check className="w-4 h-4 shrink-0" />
853-
<span>v{updateVersion} downloaded — restart to install</span>
854-
</div>
855-
<button
856-
onClick={() => window.api.updater.install()}
857-
className="shrink-0 text-xs px-3 py-1.5 rounded-md bg-emerald-500 text-white hover:bg-emerald-600 transition-colors font-medium"
858-
>
859-
Restart
860-
</button>
861-
</div>
862-
)}
863-
864815
{updateState === 'error' && (
865816
<div className="flex items-center gap-2 px-3 py-2.5 rounded-lg bg-red-500/10 border border-red-500/20 text-sm text-red-400">
866817
<AlertCircle className="w-4 h-4 shrink-0" />

0 commit comments

Comments
 (0)