apple / apple/app-store-server-library-node
Make urlBase protected so makeFetchRequest overrides work, and add a request timeout
- Dominant language
- TypeScript
- Stars
- 382
- Forks
- 79
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 9
Description
> **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:
```ts
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 https://github.com/nodejs/node/pull/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.
Contributor guide
Research direction
Start in index.ts at urlBase (line 203) and makeFetchRequest (line 292), then compare the existing 30-second OCSP timeout in jws_verification.ts:319 and the Python library's _execute_request behavior. Confirm the intended request-timeout behavior and verify that subclasses can use the base URL without duplicating hosts; done means both changes are covered by tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100