Release 1.5.3
This commit is contained in:
+76
-15
@@ -2,6 +2,7 @@ const fs = require('fs');
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const { getSharedSecret, createPageAuthBundle, verifyPageAuthToken, verifyRequestAuth } = require('../request-auth');
|
||||
const { buildThumbnailPreviewData } = require('./thumbnail-preview');
|
||||
|
||||
const TRANSIENT_DB_ERROR_CODES = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT', 'EPIPE', 'ENOTFOUND', 'PROTOCOL_CONNECTION_LOST', 'POOL_CLOSED', 'ERR_POOL_CLOSED'];
|
||||
|
||||
@@ -58,6 +59,21 @@ function registerPlayerRoutes(app, options) {
|
||||
next();
|
||||
}
|
||||
|
||||
function resolveMediaFilePath(fileName) {
|
||||
const relativePath = path.normalize(String(fileName || '').trim()).replace(/^([\\/])+/, '');
|
||||
if (!relativePath || relativePath === '.' || relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const resolvedMediaDir = path.resolve(mediaDir);
|
||||
const resolvedFilePath = path.resolve(mediaDir, relativePath);
|
||||
if (resolvedFilePath !== resolvedMediaDir && !resolvedFilePath.startsWith(resolvedMediaDir + path.sep)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolvedFilePath;
|
||||
}
|
||||
|
||||
app.use('/assets', express.static(assetDir));
|
||||
app.use('/media', express.static(mediaDir));
|
||||
app.use('/assets/vendor', express.static(path.join(__dirname, '..', '..', 'node_modules', 'hls.js', 'dist')));
|
||||
@@ -93,21 +109,21 @@ function registerPlayerRoutes(app, options) {
|
||||
|
||||
app.get('/api/media/config', requireRequestAuth, function (_req, res) {
|
||||
res.json({
|
||||
mediaDir: mediaDir
|
||||
mediaDir: mediaDir,
|
||||
uploadDir: path.join(mediaDir, 'uploads')
|
||||
});
|
||||
});
|
||||
|
||||
app.put('/api/media/:filename', express.raw({ type: '*/*', limit: '100mb' }), requireRequestAuth, async function (req, res, next) {
|
||||
try {
|
||||
const filename = require('path').basename(String(req.params.filename || '').trim());
|
||||
if (!filename) {
|
||||
const filePath = resolveMediaFilePath(req.params.filename);
|
||||
if (!filePath) {
|
||||
return res.status(400).json({ error: 'Filename is required' });
|
||||
}
|
||||
const filePath = require('path').join(mediaDir, filename);
|
||||
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body || '');
|
||||
await fs.promises.mkdir(mediaDir, { recursive: true });
|
||||
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
||||
await fs.promises.writeFile(filePath, body);
|
||||
res.json({ ok: true, filename: filename });
|
||||
res.json({ ok: true, filename: req.params.filename });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -115,11 +131,10 @@ function registerPlayerRoutes(app, options) {
|
||||
|
||||
app.delete('/api/media/:filename', requireRequestAuth, async function (req, res, next) {
|
||||
try {
|
||||
const filename = require('path').basename(String(req.params.filename || '').trim());
|
||||
if (!filename) {
|
||||
const filePath = resolveMediaFilePath(req.params.filename);
|
||||
if (!filePath) {
|
||||
return res.status(400).json({ error: 'Filename is required' });
|
||||
}
|
||||
const filePath = require('path').join(mediaDir, filename);
|
||||
try {
|
||||
await fs.promises.unlink(filePath);
|
||||
} catch (error) {
|
||||
@@ -127,7 +142,7 @@ function registerPlayerRoutes(app, options) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
res.json({ ok: true, filename: filename });
|
||||
res.json({ ok: true, filename: req.params.filename });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@@ -138,16 +153,25 @@ function registerPlayerRoutes(app, options) {
|
||||
const source = String(req.query.source || '').trim();
|
||||
const disableAudio = String(req.query.disableAudio || '').trim().toLowerCase();
|
||||
const useMutedOutput = disableAudio === '1' || disableAudio === 'true' || disableAudio === 'yes' || disableAudio === 'on';
|
||||
const session = await rtmpStreamService.ensureSession(source, useMutedOutput);
|
||||
await session.ready.catch(function () {
|
||||
return false;
|
||||
});
|
||||
const status = await rtmpStreamService.getSessionStatus(source, useMutedOutput);
|
||||
const session = status.session;
|
||||
const ready = Boolean(status.ready);
|
||||
const live = Boolean(status.live);
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
if (!ready || !live) {
|
||||
return res.status(503).json({
|
||||
ready: false,
|
||||
live: false,
|
||||
timedOut: Boolean(status.timedOut),
|
||||
stderr: String(status.stderr || '')
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
key: session.key,
|
||||
playlistUrl: session.playlistUrl,
|
||||
disableAudio: session.disableAudio,
|
||||
ready: true
|
||||
ready: true,
|
||||
live: true
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -192,6 +216,43 @@ function registerPlayerRoutes(app, options) {
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/internal/slide-thumbnails/:id/preview', requireRequestAuth, async function (req, res, next) {
|
||||
try {
|
||||
const slide = await common.fetchSlideById(pool, Number(req.params.id));
|
||||
if (!slide) {
|
||||
return res.status(404).send('Slide not found');
|
||||
}
|
||||
|
||||
const data = buildThumbnailPreviewData(slide);
|
||||
|
||||
if (typeof common.fetchRssFeedsData === 'function' && typeof common.fetchRssFeedItemsByFeedId === 'function') {
|
||||
const rssData = await common.fetchRssFeedsData(pool);
|
||||
data.rssFeeds = await Promise.all((rssData.rssFeeds || []).map(async function (feed) {
|
||||
const items = await common.fetchRssFeedItemsByFeedId(pool, feed.id);
|
||||
return Object.assign({}, feed, {
|
||||
items: items.map(function (item) {
|
||||
return typeof common.normalizeRssFeedItem === 'function' ? common.normalizeRssFeedItem(item) : item;
|
||||
})
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
if (typeof common.fetchApiSourcesData === 'function') {
|
||||
const apiData = await common.fetchApiSourcesData(pool);
|
||||
data.apiSources = (apiData.apiSources || []).map(function (source) {
|
||||
return Object.assign({}, source, {
|
||||
responseJson: typeof common.parseJsonSafe === 'function' ? common.parseJsonSafe(source.last_response_json) : null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
res.send(common.renderPlayerPage('slide-thumbnail-preview-' + slide.id, data));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/screens/:slug/playlist', requirePageAuth(['player']), async function (req, res, next) {
|
||||
try {
|
||||
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||
|
||||
Reference in New Issue
Block a user