187 lines
7.0 KiB
JavaScript
187 lines
7.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* check-whip-obs-live.js
|
|
*
|
|
* Verifica de forma E2E que un publisher WHIP (ej. OBS) está enviando al
|
|
* endpoint proporcionado. Realiza:
|
|
* - OPTIONS al endpoint WHIP (comprobación básica)
|
|
* - GET al endpoint /whip/<key>/sdp (si está disponible)
|
|
* - Polling a Core: GET <CORE_URL>/api/v3/whip para detectar el stream activo
|
|
*
|
|
* Uso:
|
|
* node scripts/check-whip-obs-live.js --url "http://192.168.1.15:8555/whip/06a2..." --token heavy666 --core http://192.168.1.15:8080 --timeout 30
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const http = require('http');
|
|
const https = require('https');
|
|
|
|
function parseArgs() {
|
|
const out = {};
|
|
const argv = process.argv.slice(2);
|
|
for (let i = 0; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a.startsWith('--')) {
|
|
const k = a.replace(/^--/, '');
|
|
const v = (argv[i+1] && !argv[i+1].startsWith('--')) ? argv[++i] : 'true';
|
|
out[k] = v;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function request(url, { method = 'GET', headers = {}, body, timeout = 15000 } = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
const u = new URL(url);
|
|
const lib = u.protocol === 'https:' ? https : http;
|
|
const raw = body;
|
|
const opts = {
|
|
hostname: u.hostname,
|
|
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
path: u.pathname + (u.search || ''),
|
|
method,
|
|
headers: { ...headers },
|
|
timeout,
|
|
rejectUnauthorized: false,
|
|
};
|
|
|
|
const req = lib.request(opts, (res) => {
|
|
let buf = '';
|
|
res.on('data', (c) => (buf += c));
|
|
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: buf }));
|
|
});
|
|
req.on('error', reject);
|
|
req.on('timeout', () => { req.destroy(new Error('timeout')); });
|
|
if (raw) req.write(raw);
|
|
req.end();
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs();
|
|
|
|
const WHIP_URL = args.url || process.env.WHIP_URL;
|
|
const TOKEN = args.token || process.env.WHIP_TOKEN || '';
|
|
const CORE_URL = args.core || process.env.CORE_URL || process.env.REACT_APP_CORE_URL || 'http://127.0.0.1:8080';
|
|
const TIMEOUT = parseInt(args.timeout || process.env.WHIP_CHECK_TIMEOUT || '30', 10);
|
|
const INTERVAL = parseInt(args.interval || process.env.WHIP_CHECK_INTERVAL || '3', 10);
|
|
|
|
if (!WHIP_URL) {
|
|
console.error('Usage: node scripts/check-whip-obs-live.js --url <WHIP_URL> [--token <token>] [--core <CORE_URL>]');
|
|
process.exit(2);
|
|
}
|
|
|
|
console.log('\nWHIP E2E live check');
|
|
console.log(` WHIP URL: ${WHIP_URL}`);
|
|
if (TOKEN) console.log(` token: ${TOKEN}`);
|
|
console.log(` Core: ${CORE_URL}`);
|
|
console.log(` Poll timeout: ${TIMEOUT}s (interval ${INTERVAL}s)`);
|
|
|
|
// Extract stream key
|
|
let streamKey = null;
|
|
try {
|
|
const u = new URL(WHIP_URL);
|
|
const parts = u.pathname.split('/').filter(Boolean);
|
|
streamKey = parts[parts.length - 1];
|
|
} catch (e) {
|
|
console.error('Invalid WHIP_URL:', e.message);
|
|
process.exit(2);
|
|
}
|
|
|
|
// OPTIONS (OBS does this first)
|
|
try {
|
|
const urlWithToken = TOKEN ? (WHIP_URL + (WHIP_URL.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(TOKEN)) : WHIP_URL;
|
|
console.log('\n1) OPTIONS → WHIP endpoint (discovery)');
|
|
const opt = await request(urlWithToken, {
|
|
method: 'OPTIONS',
|
|
headers: {
|
|
'Origin': args.origin || 'http://localhost:3000',
|
|
'Access-Control-Request-Method': 'POST',
|
|
'Access-Control-Request-Headers': 'content-type',
|
|
},
|
|
timeout: 10000,
|
|
});
|
|
console.log(` HTTP ${opt.status}`);
|
|
console.log(' Allow:', opt.headers['allow'] || opt.headers['access-control-allow-methods'] || '(none)');
|
|
} catch (e) {
|
|
console.error(' OPTIONS failed:', e.message);
|
|
}
|
|
|
|
// GET /whip/<key>/sdp (relay SDP) — may or may not be implemented
|
|
try {
|
|
const base = (() => { const u = new URL(WHIP_URL); return u.origin; })();
|
|
const sdpUrl = base + '/whip/' + encodeURIComponent(streamKey) + '/sdp' + (TOKEN ? ('?token=' + encodeURIComponent(TOKEN)) : '');
|
|
console.log('\n2) GET /whip/<key>/sdp (relay SDP)');
|
|
const r = await request(sdpUrl, { method: 'GET', timeout: 8000 });
|
|
console.log(` GET ${sdpUrl} → HTTP ${r.status}`);
|
|
if (r.body && r.body.length) console.log(' Body length:', r.body.length);
|
|
} catch (e) {
|
|
console.error(' GET /sdp failed:', e.message);
|
|
}
|
|
|
|
// Poll Core /api/v3/whip for active publishers
|
|
console.log('\n3) Poll Core /api/v3/whip looking for active stream key');
|
|
const start = Date.now();
|
|
let found = false;
|
|
const whipApi = (CORE_URL.replace(/\/$/, '') + '/api/v3/whip');
|
|
// Build optional auth header
|
|
const headers = {};
|
|
if (process.env.CORE_AUTH) {
|
|
headers['Authorization'] = process.env.CORE_AUTH;
|
|
} else if (process.env.CORE_USER && process.env.CORE_PASS) {
|
|
const token = Buffer.from(process.env.CORE_USER + ':' + process.env.CORE_PASS).toString('base64');
|
|
headers['Authorization'] = 'Basic ' + token;
|
|
}
|
|
|
|
while ((Date.now() - start) / 1000 < TIMEOUT) {
|
|
try {
|
|
const res = await request(whipApi, { method: 'GET', headers, timeout: 5000 });
|
|
if (res.status === 200) {
|
|
let list = [];
|
|
try { list = JSON.parse(res.body); } catch (e) { /* ignore */ }
|
|
if (Array.isArray(list)) {
|
|
const hit = list.find((it) => String(it.name) === String(streamKey));
|
|
if (hit) {
|
|
console.log(` ✅ Stream detected in Core /api/v3/whip: ${streamKey}`);
|
|
console.log(' → published_at:', hit.published_at || '(unknown)');
|
|
found = true;
|
|
break;
|
|
} else {
|
|
process.stdout.write('.');
|
|
}
|
|
} else {
|
|
process.stdout.write('?');
|
|
}
|
|
} else {
|
|
process.stdout.write('x');
|
|
}
|
|
} catch (e) {
|
|
process.stdout.write('!');
|
|
}
|
|
|
|
await new Promise((r) => setTimeout(r, INTERVAL * 1000));
|
|
}
|
|
|
|
console.log('');
|
|
if (!found) {
|
|
console.error(`\n✗ Stream not detected within ${TIMEOUT}s. Si OBS ya está enviando, verifica:`);
|
|
console.error('- Que el stream key usado en OBS coincida exactamente');
|
|
console.error('- Que Core esté exponiendo /api/v3/whip sin autenticación o proveas credenciales');
|
|
console.error('- Logs del Core/egress para errores de ingest');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('\nE2E check completo — stream activo.');
|
|
process.exit(0);
|
|
}
|
|
|
|
main().catch((e) => {
|
|
console.error('Fatal:', e.message || e);
|
|
process.exit(2);
|
|
});
|