diff --git a/.gitignore b/.gitignore index c9fc5d0..25d69a4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ signaling.log streaming-agent/node_modules/ dxvk-cache mesa-shader-cache +.env diff --git a/start-signaling.sh b/start-signaling.sh index 058a38f..86d954a 100755 --- a/start-signaling.sh +++ b/start-signaling.sh @@ -1,7 +1,34 @@ #!/bin/bash -cd ~/webclient_css_ze/streaming-agent -CF_APP_ID= CF_APP_TOKEN= NAME_SALT= CONTAINER_NAME=css-test node signaling-server.js &> ../signaling.log & -echo "Signaling server started (PID $!), logs at webclient_css_ze/signaling.log" +# Starts the signaling server. Secrets come from .env next to this script +set -e +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="$SCRIPT_DIR/.env" -#pkill -f signaling-server.js -#pgrep -f signaling-server.js +if [ ! -f "$ENV_FILE" ]; then + echo "Missing $ENV_FILE — copy .env.example to .env and fill in the values." >&2 + exit 1 +fi + +# Export every variable defined in .env +set -a +source "$ENV_FILE" +set +a + +for var in CF_APP_ID CF_APP_TOKEN NAME_SALT; do + if [ -z "${!var}" ]; then + echo "$var is not set in $ENV_FILE" >&2 + exit 1 + fi +done +export CONTAINER_NAME="${CONTAINER_NAME:-css-test}" + +# Stop any old instance and wait until it is gone (frees port 3000) +pkill -f signaling-server.js || true +for i in $(seq 1 10); do + pgrep -f signaling-server.js > /dev/null || break + sleep 0.5 +done + +cd "$SCRIPT_DIR/streaming-agent" +node signaling-server.js &> "$SCRIPT_DIR/signaling.log" & +echo "Signaling server started (PID $!), logs at $SCRIPT_DIR/signaling.log" diff --git a/streaming-agent/package.json b/streaming-agent/package.json index 074a580..6238ddb 100644 --- a/streaming-agent/package.json +++ b/streaming-agent/package.json @@ -8,8 +8,10 @@ "signaling": "node signaling-server.js" }, "dependencies": { - "node-fetch": "^2.7.0", "ws": "^8.18.0", "express": "^4.21.0" + }, + "engines": { + "node": ">=18" } } diff --git a/streaming-agent/signaling-server.js b/streaming-agent/signaling-server.js index 6cdc570..2860180 100644 --- a/streaming-agent/signaling-server.js +++ b/streaming-agent/signaling-server.js @@ -21,7 +21,7 @@ console.error = (...args) => _origError(`[${new Date().toISOString()}]`, ...args const express = require('express'); const http = require('http'); const WebSocket = require('ws'); -const fetch = require('node-fetch'); +// fetch is built into Node 18+ (node-fetch 2.x threw 'Premature close' on CF responses) const path = require('path'); const { spawn, execSync } = require('child_process'); const dns = require('dns').promises; @@ -40,27 +40,37 @@ if (!CF_APP_ID || !CF_APP_TOKEN) { } // ── Cloudflare API helpers ──────────────────────────────────────────────────── -async function cfPost(path, body) { - const res = await fetch(`${CF_BASE}${path}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CF_APP_TOKEN}` }, - body: JSON.stringify(body), - }); - const json = await res.json(); - if (json.errorCode) throw new Error(`CF API error: ${json.errorDescription}`); +async function cfRequest(method, path, body) { + const started = Date.now(); + let res, text; + try { + res = await fetch(`${CF_BASE}${path}`, { + method, + headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CF_APP_TOKEN}` }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(15000), + }); + text = await res.text(); + } catch (e) { + console.error(`[sig] CF ${method} ${path} failed after ${Date.now() - started}ms: ${e.name}: ${e.message}`); + throw new Error('Stream service unreachable, please try again'); + } + let json; + try { + json = JSON.parse(text); + } catch { + console.error(`[sig] CF ${method} ${path} returned non-JSON (HTTP ${res.status}): ${text.slice(0, 300)}`); + throw new Error('Stream service returned an invalid response'); + } + if (json.errorCode) { + console.error(`[sig] CF ${method} ${path} error (HTTP ${res.status}): ${json.errorCode} ${json.errorDescription}`); + throw new Error('Stream setup failed, please try again'); + } return json; } -async function cfPut(path, body) { - const res = await fetch(`${CF_BASE}${path}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CF_APP_TOKEN}` }, - body: JSON.stringify(body), - }); - const json = await res.json(); - if (json.errorCode) throw new Error(`CF API error: ${json.errorDescription}`); - return json; -} +const cfPost = (path, body) => cfRequest('POST', path, body); +const cfPut = (path, body) => cfRequest('PUT', path, body); // ── Express + WebSocket ─────────────────────────────────────────────────────── const app = express();