fix: survive server restart races and surface listen failures

restartServer now rebinds inside close()'s callback instead of the same
tick, closing the close->listen race that produced EADDRINUSE. A failed
listen (EADDRINUSE or otherwise) nulls the server reference and shows a
Notice, so /ping and clips stop silently pretending the server is up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
liyachen 2026-07-18 18:07:09 -04:00
parent 596c8f87c9
commit fbcbbb0701
3 changed files with 15 additions and 5 deletions

View file

@ -46,6 +46,7 @@
"notice.sopExportExists": "{path} already exists. The path now points to it.",
"notice.savedTo": "Saved to {folder}\nWant a different location? Settings → Vault Autopilot → Storage locations",
"notice.portInUse": "Vault Autopilot: port {port} is already in use. Quit the program using it, or set the same new port in both the plugin settings and the extension settings.",
"notice.serverError": "Vault Autopilot: the local server on port {port} stopped. Toggle it off and on in settings to retry.",
"notice.sectionExists": "\"{section}\" already exists — not overwritten. To redo it, delete that section first, then clip again.",
"error.screenshotFolderNotConfigured": "Screenshots folder not configured: set it in Settings → Vault Autopilot → Storage locations → Screenshots folder.",
"error.videoFolderNotConfigured": "Video notes folder or cover images folder not configured: set them in Settings → Vault Autopilot → Storage locations.",

View file

@ -46,6 +46,7 @@
"notice.sopExportExists": "{path} 已存在,路径已指向它。",
"notice.savedTo": "已存到 {folder}\n想换位置设置 → Vault Autopilot → 存储位置",
"notice.portInUse": "Vault Autopilot端口 {port} 被占用。请关闭占用它的程序;或在插件设置和扩展设置两处改成同一个新端口。",
"notice.serverError": "Vault Autopilot端口 {port} 上的本地服务已停止。请在设置里关闭再打开重试。",
"notice.sectionExists": "「{section}」已存在,未覆盖。想重做请先删掉该小节再点。",
"error.screenshotFolderNotConfigured": "截图文件夹未配置:请在 设置 → Vault Autopilot → 存储位置 → 截图文件夹 填写。",
"error.videoFolderNotConfigured": "视频笔记文件夹或封面图片文件夹未配置:请在 设置 → Vault Autopilot → 存储位置 填写。",

View file

@ -64,9 +64,13 @@ export default class VaultAutopilotPlugin extends Plugin {
}
restartServer(): void {
this.server?.close();
const start = () => { if (this.settings.httpServer.enabled) this.startServer(); };
const old = this.server;
this.server = null;
if (this.settings.httpServer.enabled) this.startServer();
// Rebind only after the old socket has fully closed; starting on the same
// tick races the close and hits EADDRINUSE on the same port.
if (old) old.close(() => start());
else start();
}
private async ensureFolder(folderPath: string): Promise<void> {
@ -179,9 +183,13 @@ export default class VaultAutopilotPlugin extends Plugin {
this.manifest.version,
);
this.server.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
new Notice(t('notice.portInUse', { port }), 10000);
}
// A failed listen leaves a dead server; drop the reference so /ping and
// clips report honestly and the next toggle/restart can rebind.
this.server = null;
new Notice(
err.code === 'EADDRINUSE' ? t('notice.portInUse', { port }) : t('notice.serverError', { port }),
10000,
);
});
}
}