optimize: crash guard, yt-dlp url validation, dead-key cleanup
전체 코드 재검토 기반의 안전한 개선 1차 배치. - 메인 설치기에 전역 uncaughtException/unhandledRejection 가드 추가. nat-upnp (detectExternalIpUpnp)의 비동기 소켓 오류로 앱이 조용히 종료되던 잠재 크래시 방지(포트포워딩 도구 v0.3.18 과 동일 대비). - 서버: fetchVideoMeta/fetchPlaylistEntries 에 http(s) URL 검증 추가(운영자 입력이 yt-dlp 플래그로 오인되는 인자 주입 차단). /file/mods/:folder/index.json 의 async throw → next(error) 로 위임(unhandledRejection 방지). index 라우트 pack 정의 병렬 로드. - 죽은 i18n 키 정리: installer 로케일의 UPnP/run.bat 관련 19개 + pf 로케일 2개 제거(코드에서 미참조 확인). 크래시 가드용 log.internalError, youtube.invalidUrl 추가. v0.3.23. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
@@ -143,7 +143,7 @@ app.use((req, res, next) => {
|
||||
})
|
||||
|
||||
// 모드 폴더 안의 .jar 파일 목록을 JSON으로 반환. 설치기가 자동 다운로드용으로 사용.
|
||||
app.get('/file/mods/:folder/index.json', async (req, res) => {
|
||||
app.get('/file/mods/:folder/index.json', async (req, res, next) => {
|
||||
const folder = req.params.folder
|
||||
if (!/^[a-zA-Z0-9_\-]+$/.test(folder)) {
|
||||
res.status(404).json({ files: [] })
|
||||
@@ -162,7 +162,8 @@ app.get('/file/mods/:folder/index.json', async (req, res) => {
|
||||
res.status(404).json({ files: [] })
|
||||
return
|
||||
}
|
||||
throw error
|
||||
// async 핸들러의 throw 는 Express4 가 잡지 못해 unhandledRejection 이 되므로 next 로 위임.
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ indexRouter.get('/', async (_req, res, next) => {
|
||||
const manifest = await readManifest()
|
||||
const definitionMap = new Map<string, Awaited<ReturnType<typeof loadPackDefinition>>>()
|
||||
const keys = await listPackKeys()
|
||||
for (const key of keys) {
|
||||
definitionMap.set(key, await loadPackDefinition(key))
|
||||
}
|
||||
// 팩 정의를 병렬 로드(op.ts 와 동일 패턴). 순차 await 보다 빠름.
|
||||
const definitions = await Promise.all(keys.map((key) => loadPackDefinition(key)))
|
||||
keys.forEach((key, i) => definitionMap.set(key, definitions[i]))
|
||||
const packs = manifest.packs.map((entry) => ({
|
||||
name: entry.name,
|
||||
file: entry.file,
|
||||
|
||||
@@ -299,7 +299,15 @@ async function runYtDlp(args: string[], makeError: (code: string, detail: string
|
||||
* 단일 영상 URL 의 메타데이터를 가져온다.
|
||||
* `--no-playlist` 로 플레이리스트 URL 이 들어와도 단일 영상 정보만 뽑음.
|
||||
*/
|
||||
/** 운영자 입력 URL 이 yt-dlp 인자(플래그)로 오인되지 않도록 http(s) 스킴만 허용. */
|
||||
function assertHttpUrl(url: string): void {
|
||||
if (!/^https?:\/\//i.test(url.trim())) {
|
||||
throw new Error(t('youtube.invalidUrl'))
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchVideoMeta(url: string): Promise<YtPlaylistEntry | null> {
|
||||
assertHttpUrl(url)
|
||||
const stdout = await runYtDlp(
|
||||
['--dump-json', '--no-warnings', '--no-playlist', '--skip-download', url],
|
||||
(code, detail) => new Error(t('youtube.ytdlpVideoFailed', { code, detail }))
|
||||
@@ -327,6 +335,7 @@ export async function fetchVideoMeta(url: string): Promise<YtPlaylistEntry | nul
|
||||
* `--flat-playlist --dump-json` 출력은 한 줄당 한 JSON.
|
||||
*/
|
||||
export async function fetchPlaylistEntries(url: string): Promise<YtPlaylistEntry[]> {
|
||||
assertHttpUrl(url)
|
||||
const stdout = await runYtDlp(
|
||||
['--flat-playlist', '--dump-json', '--no-warnings', url],
|
||||
(code, detail) => new Error(t('youtube.ytdlpPlaylistFailed', { code, detail }))
|
||||
|
||||
Reference in New Issue
Block a user