Master proxy with 4h tokens via our folxlive zone - rewrite folxplay -> folxlive
This commit is contained in:
parent
b91870106b
commit
e950aed6b8
@ -5,11 +5,13 @@ const express = require('express');
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Our Bunny CDN pull zone (separate from Rok's folxplay zone)
|
||||
// Our independent Bunny CDN pull zone — reads from same storage as Rok's,
|
||||
// but with our own SecurityKey and our own tokenization.
|
||||
const BUNNY_HOST = 'https://folxlive.b-cdn.net';
|
||||
const BUNNY_KEY = process.env.BUNNY_SECURITY_KEY || '492fb7b3-8a1d-4a78-8e9e-fae8c07af195';
|
||||
const TOKEN_TTL = parseInt(process.env.TOKEN_TTL || '14400', 10); // 4 hours default
|
||||
|
||||
function signBunnyUrl(urlPath, expiresInSeconds = 3600) {
|
||||
function signBunnyUrl(urlPath, expiresInSeconds = TOKEN_TTL) {
|
||||
const expires = Math.floor(Date.now() / 1000) + expiresInSeconds;
|
||||
const hash = crypto.createHash('sha256').update(BUNNY_KEY + urlPath + expires).digest();
|
||||
const token = hash.toString('base64')
|
||||
@ -19,8 +21,12 @@ function signBunnyUrl(urlPath, expiresInSeconds = 3600) {
|
||||
return { token, expires, url: `${BUNNY_HOST}${urlPath}?token=${token}&expires=${expires}` };
|
||||
}
|
||||
|
||||
// Default: stream1 master (linear, non-DVR)
|
||||
const DEFAULT_HLS_PATH = process.env.HLS_PATH || '/live/stream1_master.m3u8';
|
||||
// Map stream number → CDN path. CDN serves rewritten/tokenized version of master.
|
||||
// /live/stream1_master.m3u8 has variant URLs already pointing to folxplay.b-cdn.net (with tokens).
|
||||
// We fetch this, then rewrite folxplay → folxlive with our own tokens.
|
||||
function getMasterCdnPath(n) {
|
||||
return `/live/stream${n}_master.m3u8`;
|
||||
}
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '..', 'views'));
|
||||
@ -31,31 +37,93 @@ app.use(express.static(path.join(__dirname, '..', 'public'), {
|
||||
}));
|
||||
|
||||
app.get('/', (_req, res) => {
|
||||
const { url } = signBunnyUrl(DEFAULT_HLS_PATH, 3600);
|
||||
res.render('index', { hlsUrl: url });
|
||||
// Use our proxy route for the player — it always returns fresh tokens
|
||||
res.render('index', { hlsUrl: '/stream/1/master.m3u8' });
|
||||
});
|
||||
|
||||
app.get('/test-embed', (_req, res) => {
|
||||
res.render('test-embed');
|
||||
});
|
||||
|
||||
// Token-signed master proxy: GET /stream/:n/master.m3u8 → signed Bunny URL
|
||||
// Optional ?dvr=1 for DVR variant
|
||||
app.get('/stream/:n/master.m3u8', (req, res) => {
|
||||
/**
|
||||
* Master proxy: fetches raw master from our Bunny zone, rewrites variant URLs to point
|
||||
* to our zone with our tokens (4-hour TTL), and serves the result.
|
||||
*
|
||||
* GET /stream/:n/master.m3u8
|
||||
* Returns rewritten master.m3u8 with all variant URLs pointing to folxlive.b-cdn.net
|
||||
* with fresh tokens (4 hours).
|
||||
*/
|
||||
app.get('/stream/:n/master.m3u8', async (req, res) => {
|
||||
const n = req.params.n;
|
||||
if (!/^[1-6]$/.test(n)) return res.status(400).send('Invalid stream number');
|
||||
const dvr = req.query.dvr === '1' ? '_dvr' : '';
|
||||
const streamPath = `/live/stream${n}_master${dvr}.m3u8`;
|
||||
const { url } = signBunnyUrl(streamPath, 3600);
|
||||
|
||||
const masterCdnPath = getMasterCdnPath(n);
|
||||
const { url: signedMasterUrl } = signBunnyUrl(masterCdnPath, 600); // short TTL on internal fetch
|
||||
|
||||
try {
|
||||
const response = await fetch(signedMasterUrl, {
|
||||
headers: { 'User-Agent': 'folx-live-proxy/1.0' },
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`[stream/${n}] origin ${response.status}: ${signedMasterUrl}`);
|
||||
return res.status(502).send(`Origin returned ${response.status}`);
|
||||
}
|
||||
let text = await response.text();
|
||||
|
||||
// Rewrite variant URLs from folxplay → folxlive zone, replacing CDN-edge-script tokens
|
||||
// with our own tokens (4-hour TTL):
|
||||
// https://folxplay.b-cdn.net/live/stream1_1080p.m3u8?token=ABC&expires=N
|
||||
// → https://folxlive.b-cdn.net/live/stream1_1080p.m3u8?token=OURXYZ&expires=M
|
||||
text = text.replace(
|
||||
/https:\/\/folxplay\.b-cdn\.net(\/[^\s?]+\.m3u8)(?:\?[^\s]*)?/g,
|
||||
(_match, urlPath) => {
|
||||
const { url } = signBunnyUrl(urlPath, TOKEN_TTL);
|
||||
return url;
|
||||
}
|
||||
);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'application/vnd.apple.mpegurl',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
});
|
||||
res.send(text);
|
||||
} catch (err) {
|
||||
console.error(`[stream/${n}] error:`, err.message);
|
||||
res.status(502).send('Upstream error');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Variant proxy: fetches a variant manifest (e.g. stream_1080p.m3u8) and rewrites .ts URLs
|
||||
* to absolute folxlive paths (no token needed for .ts thanks to edge rule).
|
||||
*
|
||||
* Actually — variant manifests use relative paths so we don't need to rewrite anything.
|
||||
* Player will resolve them against folxlive.b-cdn.net which is correct.
|
||||
*
|
||||
* So we just redirect to the signed origin URL with our token.
|
||||
*/
|
||||
app.get('/stream/:n/variant/:filename', (req, res) => {
|
||||
const n = req.params.n;
|
||||
const filename = req.params.filename;
|
||||
if (!/^[1-6]$/.test(n)) return res.status(400).send('Invalid stream number');
|
||||
if (!/^stream(\d+)?_\d+p\.m3u8$/.test(filename)) return res.status(400).send('Invalid variant');
|
||||
const variantPath = `/live/${filename}`;
|
||||
const { url } = signBunnyUrl(variantPath, TOKEN_TTL);
|
||||
res.redirect(302, url);
|
||||
});
|
||||
|
||||
app.get('/api/health', (_req, res) => {
|
||||
res.json({ ok: true, host: BUNNY_HOST, defaultPath: DEFAULT_HLS_PATH, ts: Date.now() });
|
||||
res.json({
|
||||
ok: true,
|
||||
host: BUNNY_HOST,
|
||||
tokenTtlSeconds: TOKEN_TTL,
|
||||
ts: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`[folx-live] listening on :${PORT}`);
|
||||
console.log(`[folx-live] CDN host: ${BUNNY_HOST}`);
|
||||
console.log(`[folx-live] Default stream path: ${DEFAULT_HLS_PATH}`);
|
||||
console.log(`[folx-live] Token TTL: ${TOKEN_TTL}s (${TOKEN_TTL / 3600}h)`);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user