Repository navigation
Conversation
|
This pull request's reviews are posted as pull request reviews: see the review of |
andre-j3sus
left a comment
There was a problem hiding this comment.
Thanks for this! I built the branch and ran latency, download and upload against speed.cloudflare.com on Node 26, and it completes with sensible results. Browser behavior is unchanged since globalThis is window there.
A few things before we merge:
- Rebase: this conflicts with
mainsince #176 landed. Thewindow.location.origininBandwidthEnginemoved into a new URL helper, so that's where the change needs to go now. - Tests: right now the suite would still pass if either fix were reverted. Could you add:
- one engine test that runs with absolute URLs and no
locationstub, so the "no DOM" path is actually exercised beyond the auth helper; - one fake timing entry without
nextHopProtocol. Node leaves that field out, and every current fixture sets it to'h2'.
- one engine test that runs with absolute URLs and no
- README: it still says the library relies on the browser
PerformanceResourceTimingAPI. A short note would help: Node 18+ is supported on a best-effort basis for latency, download and upload, packet loss still needs a browser (RTCPeerConnection), and API URLs must be absolute outside a browser.
Resolve relative API URLs against `globalThis.location` instead of `window.location`. In a browser these are the same object; outside one, `location` is undefined, so the absolute default URLs resolve as before and relative ones are treated as unresolvable instead of throwing on `window`. Treat a resource timing entry without `nextHopProtocol` as an unknown HTTP version rather than throwing. Tests now stub `fetch` and the page origin through `globalThis` / `location` instead of `window`, matching what the source reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Outside a browser there's no page origin, so a relative downloadApiUrl or uploadApiUrl made the first request throw a TypeError that nothing caught. MeasurementEngine now finds such a URL when it's created and, when the test starts, reports it through onError and ends the test. Add tests that run BandwidthEngine without a location stub and with a timing entry that has no nextHopProtocol, as Node records them, plus tests for relative API URLs in and outside a browser. Document Node.js support in the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6ee7af5 to
0a6832a
Compare
|
Thanks for testing it on Node 26! I've addressed the review:
|
andre-j3sus
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround, this looks good! I re-ran it on Node 26: absolute URLs work, and a relative downloadApiUrl now ends cleanly through onError with no crash.
One correction on the README: Node 18+ doesn't actually work. Before undici 6.1.0 (nodejs/undici#2481), Node's fetch reported requestStart and responseStart as 0 (nodejs/undici#2515). So hasDetailedTiming rejects every sample, with a misleading Timing-Allow-Origin error, and the summary comes back as zeros. I checked raw timing entries across versions:
| Node | undici | Works |
|---|---|---|
| 18.20.8 | 5.29.0 | ❌ |
| 20.12.2 | 5.28.4 | ❌ |
| 20.13.0 | 6.13.0 | ✅ |
| 22.0.0 | 6.11.1 | ✅ |
So the minimum is Node 20.13. Suggested wording is inline.
Could you also update the PR description so it covers the second commit? It should mention the up-front relative URL check reported through onError and the README note. It also still says 197 tests and "no automated test that runs the full library under Node.js".
Optional, nice to have: a test that calls pause() right after construction with a relative URL and checks that onError doesn't fire until play().
Earlier versions report requestStart and responseStart as 0 for fetch, so every sample is rejected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pausing right after construction must hold the deferred onError report until play(), which the #running check in the microtask does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for re-running it and tracking down the Node versions!
|
|
clanky review |
There was a problem hiding this comment.
✅ Approved — No issues found. The change from window to globalThis keeps browser behaviour the same, and outside a browser a relative API URL is reported through onError without throwing.
The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.
✅ All checks passed
🔒 Security: ✅ LGTM
No security issues. The authorization token is still withheld from relative URLs outside a browser.
📋 Code Quality: ✅ LGTM
I found no defects. The relative-URL check, the deferred onError report with its pause/restart guard, the handling of a missing nextHopProtocol and the authorization helper all work correctly with no location.
⚡ Performance: ✅ LGTM
No performance concerns.
📝 Documentation: ✅ LGTM
The README note on Node.js support matches the behaviour in the code.
Reviewed commit: b1e85bb4
mangeshraut712
left a comment
There was a problem hiding this comment.
Replacing window.location.origin with globalThis.location and failing early on a relative API URL in Node is the right approach. A few holes remain around that guard.
-
Only
downloadApiUrl/uploadApiUrlare checked. The README says latency is also supported in Node, but#apiUrlErrordoes not look at a latency (or reachability) URL. A relative latency endpoint still goes throughnew URL(apiUrl, globalThis?.location?.origin)inLoadNetworkEngine/BandwidthEngine. With nolocation, a relative first argument and an undefined base throw aTypeErrorthat nothing reports viaonError. -
isAbsoluteUrlusesnew URL(url)with no base, so a protocol-relative URL such as//speed.example.com/apiis treated as relative (that constructor throws). In a browser it would resolve against the page origin; in Node it will be rejected or, if it reaches the engine, throw. If you want to accept those, resolve them only whenlocationexists, and treat them as invalid outside a browser with the sameonErrorpath. -
new URL(..., globalThis?.location?.origin)should not be relied on as a fallback once the constructor guard has run. Even for a download URL that passed the check, a later#nextApiUrl(dir)that returns a relative path will throw in Node. Passing an explicit base only whenlocation?.originis a non-empty string (and otherwise requiring an already-absolute URL) would make the engines fail the same way as the constructor.
|
@mangeshraut712 Thanks for taking a look! I checked these against the current head:
So I don't think any changes are needed here. |
Summary
Lets the library run outside a browser (Node.js 20.13+, for latency, download and upload) by removing the hard dependency on the
windowglobal.Changes
globalThis.locationinstead ofwindow.location(BandwidthEngine,LoadNetworkEngine,utils/authorization). In a browserglobalThisiswindow, so behavior there is unchanged. Outside a browserlocationis undefined, so absolute API URLs (every default) now resolve instead of throwing aReferenceErroronwindow. The authorization helper keeps withholding the token for relative URLs there.onError: with nolocation,MeasurementEnginechecksdownloadApiUrl/uploadApiUrlwhen it's created. A relative one is reported throughonErrorwhen the test starts, and the test ends, without logging the results, before any request is made. The report is deferred to a microtask, so withautoStartanonErrorset right after construction still gets it; if the test is paused first, it waits forplay(). In a browser the check doesn't run, and relative URLs still resolve against the page origin.nextHopProtocol(Node'sfetchleaves it out): it is treated as an unknown HTTP version (0) instead of throwing.fetch(feat: Add resource timing entries for connection, request and response nodejs/undici#2481), so every sample is rejected. Packet loss still needs a browser (RTCPeerConnection), and API URLs must be absolute outside a browser.fetchand the page origin throughglobalThis/locationinstead ofwindow, matching what the source now reads. The newtests/unit/outsideBrowser.test.tsrunsBandwidthEngineandSpeedTestwith nolocation. It covers absolute URLs, a timing entry withoutnextHopProtocol, and a relative URL reported throughonError, including when the test is paused right after construction. It also checks that, withlocationstubbed, a relative URL still resolves against the page origin.No Node.js-only APIs are introduced (no
fs,Buffer,processorrequire()), so the browser-only constraint inAGENTS.mdstill holds; this only drops the assumption thatwindowexists.Testing
pnpm test:unit: 260 passedpnpm lint(oxlint, oxfmt check, tsc): passespnpm build: passespnpm test:e2e: 72 passed (Chromium + WebKit)fetchandperformancestubbed, so no automated test makes real requests from Node.js🤖 Generated with Claude Code