Skip to content

Make urlBase protected so makeFetchRequest overrides work, and add a request timeout #424

Description

@strawberryeleven

Edited 2026-08-18. This issue originally reported ERR_STREAM_PREMATURE_CLOSE as a node-fetch chunked-transfer bug. That diagnosis was wrong: the root cause was in Node core and is already fixed. I've rewritten the issue to cover what actually remains. Original context is summarised under "Background" below.

Request

Two small things I ran into while working around a transport problem in a production service.

1. makeFetchRequest is protected, but urlBase is private

makeFetchRequest (index.ts:292) is protected, so subclasses are clearly intended to be able to override the HTTP transport. But urlBase (index.ts:203) is private, so a subclass cannot read the base URL that method needs.

The result is that any override has to rebuild the URL itself and hardcode the hosts:

class NativeFetchAppStoreApiClient extends AppStoreServerAPIClient {
  private readonly nativeUrlBase: string;   // has to duplicate PRODUCTION_URL / SANDBOX_URL

  protected async makeFetchRequest(path, parsedQueryParameters, method, requestBody, headers) {
    const url = `${this.nativeUrlBase}${path}?${parsedQueryParameters.toString()}`;
    ...
  }
}

If those hostnames ever change, subclasses silently keep calling the old ones.

Making urlBase protected would be a one-line change and would make the existing extension point usable as designed.

This overlaps with #352, which asks for the same seam to attach an outbound proxy agent. Two independent use cases for controlling the transport.

2. API requests have no timeout

makeFetchRequest issues requests with no timeout, and neither node-fetch nor built-in fetch applies a default. A connection that is accepted but never answered will hang indefinitely rather than failing.

This also looks inconsistent within the project:

  • jws_verification.ts:319 sets timeout: 30000 for the OCSP request
  • the Python library's _execute_request passes timeout=30 on every request

so the API client looks like an oversight rather than a deliberate choice.

Background

For context on how I hit this — the original report, kept short:

A production service saw getAllSubscriptionStatuses fail repeatedly with ERR_STREAM_PREMATURE_CLOSE while getTransactionInfo succeeded on the same client. We worked around it by subclassing and overriding makeFetchRequest to use Node's built-in fetch, which is where I ran into both points above.

The root cause turned out to be in Node, not in this library or in node-fetch's chunked handling. Node 24.17.0 added a response-queue-poisoning guard that attached a public 'data' listener to idle http.Agent sockets. node-fetch v2 tests socket.listenerCount('data') > 0 to decide whether a response was truncated, counted that guard listener, and reported complete responses as failures. Response size was irrelevant, which is why a status-filtered smaller response still failed for us.

Fixed in Node by nodejs/node#64004, released in 22.23.1, 24.18.0 (both 2026-06-23) and 26.4.0 (2026-06-24). nodejs/node#63989 is closed. I could not reproduce the failure on any current Node release, so I'm not filing this as a bug against this library and I'm not asking for the node-fetch dependency to be changed on account of it.

Happy to open a PR for either point above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions