@@ -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 => / ^ m a i n \. .+ \. j s $ / . 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 => / ^ m a i n \. .+ \. c s s $ / . 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