- server/src/demo.ts: 경량 서버 엔트리 — 정적 클라이언트 + 필수 API 2종만 (/api/elevation DEM 프록시, /api/tile 지도 타일 프록시). 영상은 브라우저 폴더 선택(로컬 파일)이라 업로드/HLS/DB(네이티브 모듈) 불필요 - scripts/build-portable.sh: esbuild 단일 번들(--minify, 소스 비공개) + client 복사 + 호스트 node.exe 동봉 + StationPlayer.bat/README(CRLF) 생성 → dist-portable/StationPlayer-Portable (약 103MB, 더블클릭 실행) - .gitignore: dist-portable/ 제외 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
54 lines
2.0 KiB
TypeScript
54 lines
2.0 KiB
TypeScript
/**
|
|
* 포터블(로컬 단독 실행) 경량 서버 — Node 만으로 실행되는 단일 번들용 엔트리.
|
|
*
|
|
* 용도: 드론 도로영상 플레이어를 웹서버 설치 없이 아무 PC 에서 더블클릭으로 실행.
|
|
* - 정적 클라이언트(client/) 서빙 + SPA 폴백
|
|
* - /api/elevation (DEM 프록시), /api/tile (지도 타일 프록시) — 클라이언트 필수 API
|
|
* - 영상은 브라우저 폴더 선택(로컬 파일)로 재생하므로 업로드/HLS/DB(네이티브 모듈) 불필요
|
|
*
|
|
* 빌드: scripts/build-portable.sh → dist-portable/GhiVideo-Portable/
|
|
* (esbuild 단일 번들 server.cjs + client/ + node.exe + GhiVideo.bat)
|
|
*/
|
|
import express from 'express';
|
|
import cors from 'cors';
|
|
import path from 'path';
|
|
import elevationRouter from './routes/elevation';
|
|
import tileRouter from './routes/tile';
|
|
|
|
const app = express();
|
|
app.use(cors({ origin: true }));
|
|
app.use(express.json());
|
|
|
|
app.use('/api/elevation', elevationRouter);
|
|
app.use('/api/tile', tileRouter);
|
|
app.get('/api/health', (_req, res) => {
|
|
res.json({ status: 'ok', mode: 'portable', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// 정적 클라이언트 — 번들(server.cjs) 옆의 client/ 폴더.
|
|
const clientDistPath = path.join(__dirname, 'client');
|
|
const cspHeader =
|
|
"default-src 'self'; " +
|
|
"script-src 'self'; " +
|
|
"style-src 'self' 'unsafe-inline'; " +
|
|
"font-src 'self' data:; " +
|
|
"img-src 'self' blob: data:; " +
|
|
"media-src 'self' blob:; " +
|
|
"connect-src 'self'; " +
|
|
"worker-src 'self' blob:;";
|
|
app.use((req, res, next) => {
|
|
if (!req.path.startsWith('/api')) res.setHeader('Content-Security-Policy', cspHeader);
|
|
next();
|
|
});
|
|
app.use(express.static(clientDistPath));
|
|
app.get('*', (req, res, next) => {
|
|
if (req.path.startsWith('/api')) return next();
|
|
res.sendFile(path.join(clientDistPath, 'index.html'));
|
|
});
|
|
|
|
const port = Number(process.env.PORT || 54000);
|
|
app.listen(port, () => {
|
|
console.log(`GhiVideo 포터블 서버 실행 중 — http://localhost:${port}`);
|
|
console.log('이 창을 닫으면 서버가 종료됩니다.');
|
|
});
|