{"record":{"id":"891c12cb2c88fec0","repo":"sampotts/plyr","slug":"request-status","errorCode":null,"errorMessage":"request.status","messagePattern":"request\\.status","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/js/utils/fetch.js","lineNumber":34,"sourceCode":"        request.withCredentials = true;\n      }\n\n      request.addEventListener('load', () => {\n        if (responseType === 'text') {\n          try {\n            resolve(JSON.parse(request.responseText));\n          }\n          catch {\n            resolve(request.responseText);\n          }\n        }\n        else {\n          resolve(request.response);\n        }\n      });\n\n      request.addEventListener('error', () => {\n        throw new Error(request.status);\n      });\n\n      request.open('GET', url, true);\n      request.responseType = responseType;\n      request.send();\n    }\n    catch (error) {\n      reject(error);\n    }\n  });\n}\n","sourceCodeStart":16,"sourceCodeEnd":46,"githubUrl":"https://github.com/sampotts/plyr/blob/6520022413161d06e61d396810f48cac551fa7b5/src/js/utils/fetch.js#L16-L46","documentation":"This is the fetch() XHR wrapper's network-error path. It runs only when XMLHttpRequest fires its 'error' event (network-level failure: CORS rejection, DNS failure, connection refused, offline, mixed-content blocking) — NOT for HTTP 4xx/5xx, which fire 'load'. The handler is defective: it throws synchronously inside an async event callback, so the throw escapes the Promise constructor's try/catch. Consequence: the returned promise is never rejected (it hangs forever) and the throw becomes an uncaught global exception. Additionally, request.status on a network 'error' is a number (commonly 0), so new Error(request.status) yields the unhelpful message '0'.","triggerScenarios":"Any internal fetch(url, ...) call (VTT thumbnails, captions, etc.) whose request fails at the network layer: the 'error' event fires. Typical causes are cross-origin resources lacking Access-Control-Allow-Origin, offline/no connectivity, DNS resolution failure, connection refused, or mixed http/https content being blocked by the browser.","commonSituations":"Serving thumbnail VTT or caption files from a CDN/different origin without CORS headers; developing while offline; a CDN outage or typo'd host in the resource URL; HTTP resources referenced from an HTTPS page (mixed content); aggressive ad-blockers/firewalls dropping the sub-resource request.","solutions":["Open DevTools Network tab, find the failing request, and read the real cause (status '(failed)', CORS error, blocked:mixed-content) — then fix the underlying reachability/CORS issue.","Ensure the resource server sends Access-Control-Allow-Origin matching the page origin (and Access-Control-Allow-Credentials if withCredentials is used).","Host the resource same-origin to sidestep CORS entirely.","Race the fetch promise against a timeout so a hanging promise cannot stall the UI (the library's reject never fires).","If you maintain this code, patch the handler to call reject(new Error(...)) with a descriptive message instead of throw."],"exampleFix":"// before (library, buggy)\nrequest.addEventListener('error', () => {\n  throw new Error(request.status);\n});\n\n// after (patched)\nrequest.addEventListener('error', () => {\n  reject(new Error(`Network request failed for ${url} (status: ${request.status})`));\n});","handlingStrategy":"validation","validationCode":"// Because the library's error handler is buggy (it throws instead of reject,\n// leaving the promise pending forever), the robust defense is to pre-check the\n// URL and to race the call against a timeout so the UI never stalls.\nfunction safeFetch(url, responseType, withCredentials, timeoutMs = 8000) {\n  // Cheap static checks before issuing the request\n  try {\n    const u = new URL(url, window.location.href);\n    if (window.location.protocol === 'https:' && u.protocol === 'http:') {\n      return Promise.reject(new Error(`Mixed content blocked: ${url}`));\n    }\n  } catch {\n    return Promise.reject(new Error(`Invalid URL: ${url}`));\n  }\n\n  return Promise.race([\n    fetch(url, responseType, withCredentials),\n    new Promise((_, reject) =>\n      setTimeout(() => reject(new Error(`fetch timed out after ${timeoutMs}ms: ${url}`)), timeoutMs)\n    )\n  ]);\n}","typeGuard":"// Narrow a URL to same-origin, where CORS cannot trigger the 'error' path.\nfunction isSameOrigin(url) {\n  try {\n    const u = new URL(url, window.location.href);\n    return u.origin === window.location.origin;\n  } catch {\n    return false;\n  }\n}\n\n// Use a same-origin endpoint for thumbnails/captions whenever possible:\nif (!isSameOrigin(thumbnailUrl)) {\n  console.warn('Cross-origin resource; ensure CORS headers are present:', thumbnailUrl);\n}","tryCatchPattern":"// NOTE: the library never rejects on network failure, so .catch() alone will NOT\n// fire. Wrap the call in a timeout race (see validationCode) so you actually get\n// a rejection to handle. Then:\nsafeFetch(vttUrl, 'text', false)\n  .then(text => parseVtt(text))\n  .catch(err => {\n    // err.message tells you whether it was timeout, mixed-content, or invalid URL\n    console.warn('Thumbnail load failed, continuing without previews:', err.message);\n    return null;\n  });","preventionTips":["Serve VTT/caption/thumbnail assets from the same origin as the page to avoid the CORS 'error' path entirely.","For cross-origin assets, confirm the response includes Access-Control-Allow-Origin in the Network tab before relying on it.","Always race fetch promises against a timeout since the library's reject path is broken.","Watch the console for the uncaught 'Error: 0' — it is the signature of this code path firing.","If you vendor/fork the library, patch the 'error' listener to call reject(new Error(...)) with a descriptive message."],"tags":["network","cors","xhr","fetch","uncaught-exception","promise-leak"],"backgroundTag":null,"analyzedSha":"6520022413161d06e61d396810f48cac551fa7b5","analyzedAt":"2026-08-13T09:56:39.192Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}