159 lines
6.1 KiB
JavaScript
159 lines
6.1 KiB
JavaScript
const fs = require('fs');
|
|
const express = require('express');
|
|
|
|
function registerPlayerRoutes(app, options) {
|
|
const pool = options && options.pool ? options.pool : null;
|
|
const common = options && options.common ? options.common : null;
|
|
const uploadDir = options && options.uploadDir ? options.uploadDir : null;
|
|
const assetDir = options && options.assetDir ? options.assetDir : null;
|
|
const playerRuntime = options && options.playerRuntime ? options.playerRuntime : null;
|
|
const playerPlaylistService = options && options.playerPlaylistService ? options.playerPlaylistService : null;
|
|
|
|
if (!app || !pool || !common || !uploadDir || !assetDir || !playerRuntime || !playerPlaylistService) {
|
|
throw new Error('registerPlayerRoutes requires app, pool, common, uploadDir, assetDir, playerRuntime, and playerPlaylistService.');
|
|
}
|
|
|
|
app.use('/assets', express.static(assetDir));
|
|
app.use('/uploads', express.static(uploadDir));
|
|
|
|
app.get('/api/uploads/config', function (_req, res) {
|
|
res.json({
|
|
uploadDir: uploadDir
|
|
});
|
|
});
|
|
|
|
app.put('/api/uploads/:filename', express.raw({ type: '*/*', limit: '100mb' }), async function (req, res, next) {
|
|
try {
|
|
const filename = require('path').basename(String(req.params.filename || '').trim());
|
|
if (!filename) {
|
|
return res.status(400).json({ error: 'Filename is required' });
|
|
}
|
|
const filePath = require('path').join(uploadDir, filename);
|
|
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body || '');
|
|
await fs.promises.mkdir(uploadDir, { recursive: true });
|
|
await fs.promises.writeFile(filePath, body);
|
|
res.json({ ok: true, filename: filename });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.delete('/api/uploads/:filename', async function (req, res, next) {
|
|
try {
|
|
const filename = require('path').basename(String(req.params.filename || '').trim());
|
|
if (!filename) {
|
|
return res.status(400).json({ error: 'Filename is required' });
|
|
}
|
|
const filePath = require('path').join(uploadDir, filename);
|
|
try {
|
|
await fs.promises.unlink(filePath);
|
|
} catch (error) {
|
|
if (!error || error.code !== 'ENOENT') {
|
|
throw error;
|
|
}
|
|
}
|
|
res.json({ ok: true, filename: filename });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.get('/screen/:slug', function (req, res) {
|
|
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
res.set('Pragma', 'no-cache');
|
|
playerPlaylistService.buildScreenPlaylist(req.params.slug).then(function (data) {
|
|
res.send(common.renderPlayerPage(req.params.slug, data));
|
|
}).catch(function (error) {
|
|
console.error(error);
|
|
res.status(500).send('Internal server error');
|
|
});
|
|
});
|
|
|
|
app.get('/api/screens/:slug/playlist', async function (req, res, next) {
|
|
try {
|
|
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
|
const data = await playerPlaylistService.buildScreenPlaylist(req.params.slug);
|
|
if (!data.screen) {
|
|
return res.status(404).json({ error: 'Screen not found' });
|
|
}
|
|
const etag = '"' + String(data.revision || '') + '"';
|
|
res.set('ETag', etag);
|
|
if (String(req.headers['if-none-match'] || '').split(',').map(function (value) {
|
|
return String(value || '').trim();
|
|
}).includes(etag)) {
|
|
return res.status(304).end();
|
|
}
|
|
res.json(data);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.get(['/api/screens/:slug/connections', '/api/screens/:slug/clients'], async function (req, res, next) {
|
|
try {
|
|
const [screenRows] = await pool.query('SELECT id, name, slug FROM screens WHERE slug = ?', [req.params.slug]);
|
|
if (!screenRows.length) {
|
|
return res.status(404).json({ error: 'Screen not found' });
|
|
}
|
|
const connections = playerRuntime.snapshotConnections(req.params.slug);
|
|
res.json({
|
|
screen: screenRows[0],
|
|
screenSlug: req.params.slug,
|
|
count: connections.length,
|
|
connections: connections
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
app.post('/api/screens/:slug/commands', async function (req, res, next) {
|
|
try {
|
|
const command = String((req.body && req.body.command) || req.query.command || '').trim().toLowerCase();
|
|
const connectionId = String((req.body && (req.body.connectionId || req.body.clientId)) || req.query.connectionId || req.query.clientId || '').trim();
|
|
const blackoutValue = req.body && Object.prototype.hasOwnProperty.call(req.body, 'blackout')
|
|
? req.body.blackout
|
|
: req.query.blackout;
|
|
if (!command) {
|
|
return res.status(400).json({ error: 'Command is required' });
|
|
}
|
|
if (['refresh', 'reload', 'redirect', 'pause', 'blackout', 'previous', 'next', 'left', 'right', 'setclientname'].indexOf(command) === -1) {
|
|
return res.status(400).json({ error: 'Unsupported command' });
|
|
}
|
|
|
|
const isRedirectCommand = command === 'redirect';
|
|
let screenRows = [];
|
|
if (!isRedirectCommand) {
|
|
[screenRows] = await pool.query('SELECT id, name, slug FROM screens WHERE slug = ?', [req.params.slug]);
|
|
if (!screenRows.length) {
|
|
return res.status(404).json({ error: 'Screen not found' });
|
|
}
|
|
}
|
|
|
|
const commandPayload = req.body && typeof req.body === 'object' && !Array.isArray(req.body)
|
|
? Object.assign({}, req.body, { command: command })
|
|
: command;
|
|
if (command === 'blackout' && blackoutValue !== undefined && commandPayload && typeof commandPayload === 'object') {
|
|
commandPayload.blackout = blackoutValue;
|
|
}
|
|
|
|
const sent = connectionId
|
|
? await playerRuntime.sendCommandToConnection(req.params.slug, connectionId, commandPayload)
|
|
: await playerRuntime.broadcastCommand(req.params.slug, commandPayload);
|
|
|
|
res.json({
|
|
screen: screenRows[0] || null,
|
|
screenSlug: req.params.slug,
|
|
command: command,
|
|
connectionId: connectionId || null,
|
|
sent: sent
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
registerPlayerRoutes: registerPlayerRoutes
|
|
}; |