diff --git a/CMakeLists.txt b/CMakeLists.txt index b30bd502..714e175b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ FetchContent_Declare(llhttp EXCLUDE_FROM_ALL) FetchContent_Declare(UrlLib GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git - GIT_TAG 16c7dfd88cb5e7121cb5b037b44b796b737955d4 + GIT_TAG 74985214bd4f83a4906b2c62134ac2f9ab89e1ae EXCLUDE_FROM_ALL) # -------------------------------------------------- diff --git a/Polyfills/Fetch/Source/Fetch.cpp b/Polyfills/Fetch/Source/Fetch.cpp index 618dab8c..7ffee288 100644 --- a/Polyfills/Fetch/Source/Fetch.cpp +++ b/Polyfills/Fetch/Source/Fetch.cpp @@ -24,6 +24,7 @@ namespace Babylon::Polyfills::Internal struct ResponseData { int statusCode{}; + std::string statusText; std::string url; std::vector> headers; std::vector body; @@ -51,36 +52,6 @@ namespace Babylon::Polyfills::Internal throw std::runtime_error{"Unsupported fetch method: " + method + " (only GET and POST are supported)"}; } - const char* StatusText(int statusCode) - { - switch (statusCode) - { - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 204: return "No Content"; - case 206: return "Partial Content"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 304: return "Not Modified"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 429: return "Too Many Requests"; - case 500: return "Internal Server Error"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - default: return ""; - } - } - std::optional FindHeader(const ResponseData& data, std::string_view name) { for (const auto& header : data.headers) @@ -181,7 +152,7 @@ namespace Babylon::Polyfills::Internal const bool ok = data->statusCode >= 200 && data->statusCode < 300; response.Set("ok", Napi::Boolean::New(env, ok)); response.Set("status", Napi::Number::New(env, data->statusCode)); - response.Set("statusText", Napi::String::New(env, StatusText(data->statusCode))); + response.Set("statusText", Napi::String::New(env, data->statusText)); response.Set("url", Napi::String::New(env, data->url)); response.Set("redirected", Napi::Boolean::New(env, false)); response.Set("type", Napi::String::New(env, "basic")); @@ -355,6 +326,7 @@ namespace Babylon::Polyfills::Internal auto data = std::make_shared(); data->statusCode = status; + data->statusText = std::string{request->StatusText()}; data->url = std::string{request->ResponseUrl()}; for (const auto& header : request->GetAllResponseHeaders()) { diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp index 1e1c04cf..10903cac 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp @@ -80,6 +80,7 @@ namespace Babylon::Polyfills::Internal InstanceAccessor("responseType", &XMLHttpRequest::GetResponseType, &XMLHttpRequest::SetResponseType), InstanceAccessor("responseURL", &XMLHttpRequest::GetResponseURL, nullptr), InstanceAccessor("status", &XMLHttpRequest::GetStatus, nullptr), + InstanceAccessor("statusText", &XMLHttpRequest::GetStatusText, nullptr), InstanceMethod("getAllResponseHeaders", &XMLHttpRequest::GetAllResponseHeaders), InstanceMethod("getResponseHeader", &XMLHttpRequest::GetResponseHeader), InstanceMethod("setRequestHeader", &XMLHttpRequest::SetRequestHeader), @@ -149,6 +150,18 @@ namespace Babylon::Polyfills::Internal return Napi::Value::From(Env(), arcana::underlying_cast(m_request.StatusCode())); } + Napi::Value XMLHttpRequest::GetStatusText(const Napi::CallbackInfo&) + { + // Per the XHR spec, statusText is the empty string until a response is available + // (status 0 means UNSENT/OPENED or a network error). + if (arcana::underlying_cast(m_request.StatusCode()) == 0) + { + return Napi::String::New(Env(), ""); + } + + return Napi::String::New(Env(), std::string{m_request.StatusText()}); + } + Napi::Value XMLHttpRequest::GetResponseHeader(const Napi::CallbackInfo& info) { const auto headerName = info[0].As().Utf8Value(); diff --git a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h index 24139bda..307828fd 100644 --- a/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h +++ b/Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h @@ -34,6 +34,7 @@ namespace Babylon::Polyfills::Internal Napi::Value GetAllResponseHeaders(const Napi::CallbackInfo& info); Napi::Value GetResponseURL(const Napi::CallbackInfo& info); Napi::Value GetStatus(const Napi::CallbackInfo& info); + Napi::Value GetStatusText(const Napi::CallbackInfo& info); void AddEventListener(const Napi::CallbackInfo& info); void RemoveEventListener(const Napi::CallbackInfo& info); diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index 70ae367d..e0647092 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -121,6 +121,13 @@ describe("XMLHTTPRequest", function () { expect(xhr.status).to.equal(404); }); + it("should expose statusText", async function () { + const okXhr = await createRequest("GET", "https://github.com/"); + expect(okXhr.statusText).to.equal("OK"); + const notFoundXhr = await createRequest("GET", "https://github.com/babylonJS/BabylonNative404"); + expect(notFoundXhr.statusText).to.equal("Not Found"); + }); + it("should fire 'error' event for a remote URL that returns HTTP 404", async function () { // Regression test: previously the success-only continuation in XMLHttpRequest::Send // skipped 'error' on async failures including non-2xx HTTP responses, so onerror @@ -254,8 +261,10 @@ describe("fetch", function () { }); it("should expose statusText", async function () { - const response = await fetch("https://github.com/babylonJS/BabylonNative404"); - expect(response.statusText).to.equal("Not Found"); + const okResponse = await fetch("https://github.com/"); + expect(okResponse.statusText).to.equal("OK"); + const notFoundResponse = await fetch("https://github.com/babylonJS/BabylonNative404"); + expect(notFoundResponse.statusText).to.equal("Not Found"); }); it("text() should return the body as a string", async function () {