Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
# --------------------------------------------------

Expand Down
34 changes: 3 additions & 31 deletions Polyfills/Fetch/Source/Fetch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ namespace Babylon::Polyfills::Internal
struct ResponseData
{
int statusCode{};
std::string statusText;
std::string url;
std::vector<std::pair<std::string, std::string>> headers;
std::vector<std::byte> body;
Expand Down Expand Up @@ -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<std::string> FindHeader(const ResponseData& data, std::string_view name)
{
for (const auto& header : data.headers)
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -355,6 +326,7 @@ namespace Babylon::Polyfills::Internal

auto data = std::make_shared<ResponseData>();
data->statusCode = status;
data->statusText = std::string{request->StatusText()};
data->url = std::string{request->ResponseUrl()};
for (const auto& header : request->GetAllResponseHeaders())
{
Expand Down
13 changes: 13 additions & 0 deletions Polyfills/XMLHttpRequest/Source/XMLHttpRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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<Napi::String>().Utf8Value();
Expand Down
1 change: 1 addition & 0 deletions Polyfills/XMLHttpRequest/Source/XMLHttpRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 11 additions & 2 deletions Tests/UnitTests/Scripts/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -254,8 +261,10 @@ describe("fetch", function () {
});

it("should expose statusText", async function () {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These cover the happy path well — would you be open to also porting a handful of the bun/node (undici) and WPT tests for this functional area, to validate the runtime behavior cross-platform in CI? The highest-value ones for statusText/error semantics specifically:

If you'd rather keep this PR scoped to the statusText wiring, we're happy to do the test porting in a follow-up PR instead — whichever you prefer.

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 () {
Expand Down
Loading