Skip to content

Commit d81e82f

Browse files
committed
fix update ZIP missed files
1 parent 6ae7dfb commit d81e82f

File tree

3 files changed

+135
-46
lines changed

3 files changed

+135
-46
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@conet.project/conet-proxy",
33

4-
"version": "0.15.5",
4+
"version": "0.16.0",
55

66
"license": "UNLICENSED",
77
"files": [

src/localServer/localServer.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ const joinMetadata = (metadata: any ) => {
172172
let _proxyServer: proxyServer
173173

174174

175-
const startSilentPass = (vpnObj: Native_StartVPNObj, currentVer: UpdateInfo, reactFolder: string) => {
175+
const startSilentPass = (vpnObj: Native_StartVPNObj, currentVer: UpdateInfo, reactFolder: string, restart: () => Promise<void> ) => {
176176
logger(inspect(vpnObj, false, 3, true))
177177

178178
_proxyServer = new proxyServer((3002).toString(), vpnObj.entryNodes, vpnObj.exitNode, vpnObj.privateKey, true, '')
179-
runUpdater(vpnObj.entryNodes, currentVer, reactFolder)
179+
runUpdater(vpnObj.entryNodes, currentVer, reactFolder, restart)
180180
return true
181181
}
182182

@@ -235,15 +235,17 @@ export class Daemon {
235235

236236
public _proxyServer: proxyServer|null = null
237237

238-
public end = () => new Promise (resolve => {
239-
240-
this.localserver.close(err => {
241-
242-
})
243-
setTimeout(() => {
244-
resolve(true)
245-
}, 5000)
246-
})
238+
public end = (): Promise<void> => new Promise(resolve => {
239+
if (this.localserver) {
240+
this.localserver.close(err => {
241+
if (err) {
242+
logger(Colors.red('关闭服务器时出错:'), err)
243+
}
244+
})
245+
}
246+
// 即使服务器不存在或关闭出错,也继续执行
247+
resolve()
248+
})
247249

248250
public postMessageToLocalDevice ( device: string, encryptedMessage: string ) {
249251
const index = this.connect_peer_pool.findIndex ( n => n.publicKeyID === device )
@@ -297,6 +299,10 @@ export class Daemon {
297299
return this.initialize ()
298300
})
299301

302+
app.get('/ver'), ( req, res ) => {
303+
res.end({ver: this.currentVer?.ver})
304+
}
305+
300306
app.post ( '/rule', ( req: any, res: any ) => {
301307
const vpnObj = req.body.data
302308
try {
@@ -483,7 +489,7 @@ export class Daemon {
483489

484490
app.get('/ver', (req, res) => {
485491
logger (`APP get ${req.url}`)
486-
res.json({ver})
492+
res.json({ver: this.currentVer?.ver})
487493
})
488494

489495
app.get('/getAllRegions',async (req, res) => {
@@ -500,7 +506,7 @@ export class Daemon {
500506
return res.status(400).send({ error: "No country selected" })
501507
}
502508
if (this.currentVer) {
503-
startSilentPass (vpnObj, this.currentVer, this.reactBuildFolder)
509+
startSilentPass (vpnObj, this.currentVer, this.reactBuildFolder, this.restart)
504510
}
505511

506512

@@ -565,6 +571,28 @@ export class Daemon {
565571
])
566572
})
567573
}
574+
575+
// 将 restart 方法改为箭头函数属性
576+
public restart = async (): Promise<void> => {
577+
logger(Colors.magenta('🔄 开始热启动本地 Web 服务器...'))
578+
579+
// 1. 确保当前有服务器正在运行
580+
if (!this.localserver) {
581+
logger(Colors.yellow('服务器未运行,无需重启。将直接进行初始化。'))
582+
await this.initialize()
583+
return
584+
}
585+
586+
// 2. 关闭现有服务器,并等待其完全关闭
587+
logger('🚀 正在关闭现有服务器...')
588+
await this.end()
589+
logger('✅ 旧服务器已成功关闭。')
590+
591+
// 3. 再次调用 initialize 方法
592+
logger('🚀 正在使用新配置重新初始化服务器...')
593+
await this.initialize()
594+
logger(Colors.green('🎉 服务器热启动完成!'))
595+
}
568596
}
569597

570598

src/localServer/updateProcess.ts

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -99,51 +99,109 @@ const getRandomNode = (nodes: nodes_info[]): nodes_info => {
9999
* @param folderPath 要检查的文件夹根路径
100100
* @returns 如果验证通过则返回 true,否则返回 false
101101
*/
102-
const validateUpdateContents = async (folderPath: string, ver: string): Promise<boolean> => {
102+
const validateUpdateContents = async (folderPath: string, ver: string, nodes: nodes_info[]): Promise<boolean> => {
103+
logger('🔍 开始验证和修复更新内容...')
104+
const manifestPath = join(folderPath, 'asset-manifest.json')
105+
103106
try {
104-
// 1. 检查根目录下的 index.html
105-
const hasIndexHtml = fs.existsSync(join(folderPath, 'index.html'))
106-
if (!hasIndexHtml) {
107-
logger('🔴 验证失败: 未找到 index.html')
108-
return false;
107+
// 1. 检查并读取 asset-manifest.json
108+
if (!fs.existsSync(manifestPath)) {
109+
logger('🔴 验证失败: 关键文件 asset-manifest.json 未找到!')
110+
// 如果清单都不存在,可以尝试下载它本身
111+
const randomNode = getRandomNode(nodes)
112+
const manifestUrl = `http://${randomNode.ip_addr}/silentpass-rpc/asset-manifest.json`
113+
logger(`尝试下载缺失的 asset-manifest.json from ${manifestUrl}`)
114+
await downloadSingleFileHttp(manifestUrl, manifestPath)
109115
}
110116

111-
// 2. 检查 static/js 目录下的 main.*.js
112-
const jsFolderPath = join(folderPath, 'static', 'js')
113-
if (!fs.existsSync(jsFolderPath)) {
114-
logger('🔴 验证失败: 未找到目录 static/js')
115-
return false;
116-
}
117-
const jsFiles = fs.readdirSync(jsFolderPath);
118-
const hasMainJs = jsFiles.some(file => /^main\..+\.js$/.test(file))
119-
if (!hasMainJs) {
120-
logger('🔴 验证失败: 在 static/js/ 中未找到 main.*.js 文件')
121-
return false;
117+
const manifestContent = fs.readFileSync(manifestPath, 'utf8')
118+
const manifest = JSON.parse(manifestContent)
119+
120+
if (!manifest.files || typeof manifest.files !== 'object') {
121+
logger('🔴 验证失败: asset-manifest.json 格式不正确或不包含 "files" 对象。')
122+
return false
122123
}
123124

124-
// 3. 检查 static/css 目录下的 main.*.css
125-
const cssFolderPath = join(folderPath, 'static', 'css')
126-
if (!fs.existsSync(cssFolderPath)) {
127-
logger('🔴 验证失败: 未找到目录 static/css')
128-
return false;
125+
// 2. 收集所有缺失的文件,并准备下载
126+
const downloadPromises: Promise<void>[] = []
127+
const filePaths = Object.values(manifest.files) as string[]
128+
129+
for (const filePath of filePaths) {
130+
// Create React App 的路径通常以 / 开头,需要移除
131+
const localFilePath = filePath.startsWith('/') ? filePath.substring(1) : filePath
132+
const fullPath = join(folderPath, localFilePath)
133+
134+
if (!fs.existsSync(fullPath)) {
135+
logger(`🟡 文件缺失: ${localFilePath}。准备下载...`)
136+
137+
const randomNode = getRandomNode(nodes)
138+
const downloadUrl = `http://${randomNode.ip_addr}/silentpass-rpc/${localFilePath}`
139+
140+
// 将下载任务的 Promise 添加到数组中
141+
downloadPromises.push(downloadSingleFileHttp(downloadUrl, fullPath))
142+
}
129143
}
130-
const cssFiles = fs.readdirSync(cssFolderPath)
131-
const hasMainCss = cssFiles.some(file => /^main\..+\.css$/.test(file))
132-
if (!hasMainCss) {
133-
logger('🔴 验证失败: 在 static/css/ 中未找到 main.*.css 文件')
134-
return false
144+
145+
// 3. 如果有缺失的文件,则并行下载它们
146+
if (downloadPromises.length > 0) {
147+
logger(`发现 ${downloadPromises.length} 个缺失文件,开始并行下载修复...`)
148+
await Promise.all(downloadPromises)
149+
logger('✅ 所有缺失文件已下载完成!')
150+
} else {
151+
logger('✅ 所有文件均存在,无需修复。')
135152
}
136153

137-
await readUpdateInfo(folderPath, ver)
154+
// 4. 所有文件都就绪后,执行最终的检查(例如读取 update.json)
155+
await readUpdateInfo(folderPath, ver)
138156

139-
logger('✅ 更新内容验证通过,文件结构正确!')
157+
logger('✅ 更新内容验证和修复成功!')
140158
return true
159+
141160
} catch (error) {
142-
logger('🔴 验证过程中发生错误:', error)
161+
logger('🔴 验证或修复过程中发生严重错误:', error);
143162
return false
144163
}
145164
}
146165

166+
/**
167+
* 下载单个文件(支持 HTTPS)并保存到指定路径。
168+
* @param downloadUrl 文件的完整下载 URL (https://...)
169+
* @param destinationPath 本地保存的完整路径
170+
*/
171+
const downloadSingleFileHttp = (downloadUrl: string, destinationPath: string): Promise<void> => {
172+
return new Promise((resolve, reject) => {
173+
174+
const dir = join(destinationPath, '..')
175+
if (!fs.existsSync(dir)) {
176+
fs.mkdirSync(dir, { recursive: true })
177+
}
178+
const options = { agent: httpAgent }
179+
const fileStream = fs.createWriteStream(destinationPath)
180+
logger(`Download ${downloadUrl}`)
181+
http.get(downloadUrl, options, (response) => {
182+
if (response.statusCode !== 200) {
183+
fileStream.close();
184+
fs.unlink(destinationPath, () => {}); // 清理不完整的文件
185+
return reject(new Error(`下载单个文件失败 [${response.statusCode}]: ${downloadUrl}`));
186+
}
187+
response.pipe(fileStream)
188+
}).on('error', (err) => {
189+
fs.unlink(destinationPath, () => {})
190+
reject(err)
191+
})
192+
193+
fileStream.on('finish', () => {
194+
fileStream.close()
195+
resolve()
196+
})
197+
198+
fileStream.on('error', (err) => {
199+
fs.unlink(destinationPath, () => {})
200+
reject(err)
201+
})
202+
})
203+
}
204+
147205
/**
148206
* Reads and parses the update.json file from a specified folder.
149207
*
@@ -187,10 +245,12 @@ export const readUpdateInfo = async (staticFolder: string, ver: string): Promise
187245
})
188246

189247

248+
249+
190250
/**
191251
* 主更新函数
192252
*/
193-
export const runUpdater = async (nodes: nodes_info[], currentVer: UpdateInfo, reactFolder: string ) => {
253+
export const runUpdater = async (nodes: nodes_info[], currentVer: UpdateInfo, reactFolder: string, restart: () => Promise<void> ) => {
194254

195255

196256
logger('🚀 开始执行动态节点更新程序...')
@@ -248,7 +308,7 @@ export const runUpdater = async (nodes: nodes_info[], currentVer: UpdateInfo, re
248308
logger(`🎉 成功下载并解压文件到 ${tempUpdatePath}`)
249309

250310
// 2. 验证内容
251-
if (!(await validateUpdateContents(tempUpdatePath, currentVer.ver))) {
311+
if (!(await validateUpdateContents(tempUpdatePath, updateInfo.ver, nodes))) {
252312
throw new Error('下载的内容无效或不完整,已终止更新。')
253313
}
254314

@@ -272,6 +332,7 @@ export const runUpdater = async (nodes: nodes_info[], currentVer: UpdateInfo, re
272332
fs.rmSync(backupPath, { recursive: true, force: true })
273333
logger(`旧的备份目录已清理。`)
274334
}
335+
await restart()
275336

276337
} catch (error) {
277338
console.error('❌ 更新过程中发生错误:', error instanceof Error ? error.message : error)

0 commit comments

Comments
 (0)