fix: detect upstream API errors in stream endpoint to prevent null URL responses

- Added upstream error detection in streamInfo.extractor.js (status >= 400)
- Added non-JSON error string detection from upstream API
- Updated streamInfo.controller.js to return HTTP 502 for upstream failures
- Fixes /api/stream returning null URL when upstream returns error responses
This commit is contained in:
Shinei Nouzen
2026-08-14 06:36:37 +05:30
parent 107e3c71e7
commit 320bf0915b
3 changed files with 39 additions and 2 deletions
+10
View File
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.2] - 2026-08-14
### Fixed
- **CRITICAL**: Fixed `/api/stream` returning `null` URL when upstream API returns error responses (e.g. `{status: 500, result: "Bad request"}`) — extractor now detects upstream error status codes and throws meaningful errors instead of silently returning null
- Fixed `streamInfo.extractor.js` `extractStreamInfo` to detect upstream API error responses (`data.status >= 400`) and throw descriptive errors
- Fixed `streamInfo.extractor.js` `extractStreamInfo` to detect non-JSON error strings from upstream API (e.g. "Bad request") and throw errors instead of silently swallowing them
- Fixed `streamInfo.extractor.js` `extractServerList` to detect upstream API error responses and throw descriptive errors
- Fixed `streamInfo.controller.js` `getStreamInfo` to return HTTP 502 (Bad Gateway) with error message for upstream API failures instead of silently returning null URL
- Fixed `streamInfo.controller.js` `getServerList` to properly propagate upstream errors via error handler
## [2.2.1] - 2026-08-12 ## [2.2.1] - 2026-08-12
### Fixed ### Fixed
+6 -1
View File
@@ -53,7 +53,12 @@ const getStreamInfo = async (req, res, next) => {
if (cached) { if (cached) {
return res.json({ success: true, results: cached }); return res.json({ success: true, results: cached });
} }
const data = await extractStreamInfo(id); let data;
try {
data = await extractStreamInfo(id);
} catch (upstreamError) {
return res.status(502).json({ success: false, message: `Upstream API error: ${upstreamError.message}` });
}
if (!data.url) { if (!data.url) {
return res.status(404).json({ success: false, message: "Stream URL not found for this link ID", results: data }); return res.status(404).json({ success: false, message: "Stream URL not found for this link ID", results: data });
} }
+23 -1
View File
@@ -59,11 +59,27 @@ const extractStreamInfo = async (linkId, watchSlug = null) => {
try { data = JSON.parse(raw); } catch { data = {}; } try { data = JSON.parse(raw); } catch { data = {}; }
} }
// NOTE: Detect upstream API error responses (e.g. {status: 500, result: "Bad request"})
if (data && typeof data === "object" && data.status && Number(data.status) >= 400) {
throw new Error(`Upstream API error (${data.status}): ${data.result || "Unknown error"}`);
}
if (!data || !data.result) { if (!data || !data.result) {
return { linkId, url: null, type: null, skipData: null }; return { linkId, url: null, type: null, skipData: null };
} }
const result = typeof data.result === "string" ? (() => { try { return JSON.parse(data.result); } catch { return {}; } })() : data.result; // NOTE: If result is a non-JSON string (error message), treat as upstream failure
const result = typeof data.result === "string"
? (() => {
try { return JSON.parse(data.result); }
catch {
if (!data.result.startsWith("{") && !data.result.startsWith("[")) {
throw new Error(`Upstream returned invalid data: ${data.result}`);
}
return {};
}
})()
: data.result;
const streamUrl = result.url || null; const streamUrl = result.url || null;
const streamType = streamUrl?.includes(".m3u8") ? "hls" : streamUrl ? "mp4" : null; const streamType = streamUrl?.includes(".m3u8") ? "hls" : streamUrl ? "mp4" : null;
@@ -115,6 +131,12 @@ const extractServerList = async (episodeIds, watchSlug = null) => {
if (typeof raw === "string") { if (typeof raw === "string") {
try { parsed = JSON.parse(raw); } catch { parsed = {}; } try { parsed = JSON.parse(raw); } catch { parsed = {}; }
} }
// NOTE: Detect upstream API error responses
if (parsed && typeof parsed === "object" && parsed.status && Number(parsed.status) >= 400) {
throw new Error(`Upstream API error (${parsed.status}): ${parsed.result || "Unknown error"}`);
}
const html = parsed?.result || ""; const html = parsed?.result || "";
const $ = cheerio.load(html); const $ = cheerio.load(html);