Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoiding instanceof Request, etc. #2155

Open
hansottowirtz opened this issue Jun 12, 2023 · 6 comments
Open

Avoiding instanceof Request, etc. #2155

hansottowirtz opened this issue Jun 12, 2023 · 6 comments
Labels
bug Something isn't working fetch

Comments

@hansottowirtz
Copy link

hansottowirtz commented Jun 12, 2023

In Node.js 18.6, this piece of code doesn't work:

import { fetch } from "undici";

fetch(new Request("https://example.com"));

Error:

TypeError: Failed to parse URL from [object Request]
    at fetch (<>/node_modules/undici/index.js:109:13) {
  [cause]: TypeError [ERR_INVALID_URL]: Invalid URL
      at new NodeError (node:internal/errors:405:5)
      at new URL (node:internal/url:743:13)
      at new Request (<>/node_modules/undici/lib/fetch/request.js:86:21)
      at fetch (<>node_modules/undici/lib/fetch/index.js:137:21)
      at fetch (<>/node_modules/undici/index.js:107:20)
      at file://<>/test.js:3:1
      at ModuleJob.run (node:internal/modules/esm/module_job:192:25)
      at async DefaultModuleLoader.import (node:internal/modules/esm/loader:246:24)
      at async loadESM (node:internal/process/esm_loader:40:7)
      at async handleMainPromise (node:internal/modules/run_main:66:12) {
    input: '[object Request]',
    code: 'ERR_INVALID_URL'
  }
}

This is because instanceof Request fails due to the global Request and the Undici Request not being the same.

if (V instanceof Request) {

While many libraries accept a fetch argument (like ky), not many libraries accept another set of globals (Request, Response, Headers). Next to that, global.Request is a read-only property in Node.js. I think that instead of using instanceof Undici should be using another type of check for these. (e.g. request[Symbol.toStringTag] === 'Request').

I'd like to collect thoughts and then I'll create a PR.

This would also fix cloudflare/miniflare#454

@hansottowirtz hansottowirtz added the enhancement New feature or request label Jun 12, 2023
@KhafraDev
Copy link
Member

KhafraDev commented Jun 12, 2023

Unfortunately loosening that check wouldn't solve the issue; we need to access the internal Request state (via a symbol) that will not work since the global fetch's symbols are different from undici's symbols. The only way I can think of solving this is to use Object.getOwnPropertySymbols and then find one names kState/kSignal. But then we'd also have other issues:

const request = {
  [Symbol('kState')]: 'lol',
  [Symbol('kSignal')]: null,
  [Symbol.toStringTag]: 'Request'
}

I'm not sure if this can be fixed, without causing other issues.

@KhafraDev KhafraDev added bug Something isn't working fetch and removed enhancement New feature or request labels Jun 12, 2023
@ronag
Copy link
Member

ronag commented Jun 13, 2023

Use SymbolFor?

@KhafraDev
Copy link
Member

KhafraDev commented Jun 13, 2023

Possible, but creating a dozen global symbols (this would need to be done for all fetch globals, not just Request) would cause more issues like nodejs/node#43157. Alternatively we could move all undici symbols under a single global one?

globalThis[Symbol.for('undici.global.symbol')] = {
  state: Symbol('kState'),
  // etc
}

which still seems undesirable because it could be used to access the internal state of fetch objects

@mcollina
Copy link
Member

I don't think we want to expose the internals as part of our API contract. We want to be able to change them and not worry if we would break this use case or not.

I think we should just document this quirk.

@ronag ronag removed the bug Something isn't working label Jun 16, 2023
@KhafraDev KhafraDev added the bug Something isn't working label Jun 18, 2023
@KhafraDev
Copy link
Member

KhafraDev commented Jun 18, 2023

We should still consider avoiding instanceof because it can be overridden with Symbol.hasInstance. Instead we should be doing an Object.hasOwn check and making sure, for example, that the object has a kState. I've known about this for a while now, it's a pretty small issue but spans essentially every spec we implement (fetch, CacheStorage, WebSocket) because of how we use it in webidl.

an example:

Object.defineProperty(Response, Symbol.hasInstance, {
  value: () => true
})

console.log([] instanceof Response) // true

@jimmywarting
Copy link
Contributor

It's kind of a bummer that undici and builtin fetch don't play ball. i have for instance wanted to only import the CacheStorage and tree shake away the rest and hopped that i could use it together with builtin Request/Response, but it's a very strict brand checking

nikosdouvlis added a commit to clerk/javascript that referenced this issue Apr 5, 2024
…edge-runtime and undici limitations

The usual way to duplicate a request object is to
pass the original request object to the Request constructor
both as the `input` and `init` parameters, eg: super(req, req)
However, this fails in certain environments like Vercel Edge Runtime
when a framework like Remix polyfills the global Request object.
This happens because `undici` performs the following instanceof check
which, instead of testing against the global Request object, tests against
the Request class defined in the same file (local Request class).
For more details, please refer to:
nodejs/undici#2155
https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854
nikosdouvlis added a commit to clerk/javascript that referenced this issue Apr 5, 2024
…edge-runtime and undici limitations

The usual way to duplicate a request object is to
pass the original request object to the Request constructor
both as the `input` and `init` parameters, eg: super(req, req)
However, this fails in certain environments like Vercel Edge Runtime
when a framework like Remix polyfills the global Request object.
This happens because `undici` performs the following instanceof check
which, instead of testing against the global Request object, tests against
the Request class defined in the same file (local Request class).
For more details, please refer to:
nodejs/undici#2155
https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854
nikosdouvlis added a commit to clerk/javascript that referenced this issue Apr 6, 2024
…edge-runtime and undici limitations

The usual way to duplicate a request object is to
pass the original request object to the Request constructor
both as the `input` and `init` parameters, eg: super(req, req)
However, this fails in certain environments like Vercel Edge Runtime
when a framework like Remix polyfills the global Request object.
This happens because `undici` performs the following instanceof check
which, instead of testing against the global Request object, tests against
the Request class defined in the same file (local Request class).
For more details, please refer to:
nodejs/undici#2155
https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854
nikosdouvlis added a commit to clerk/javascript that referenced this issue Apr 6, 2024
…edge-runtime and undici limitations (#3129)

* fix(backend,remix): Tweak ClerkRequest initialization to work-around edge-runtime and undici limitations

The usual way to duplicate a request object is to
pass the original request object to the Request constructor
both as the `input` and `init` parameters, eg: super(req, req)
However, this fails in certain environments like Vercel Edge Runtime
when a framework like Remix polyfills the global Request object.
This happens because `undici` performs the following instanceof check
which, instead of testing against the global Request object, tests against
the Request class defined in the same file (local Request class).
For more details, please refer to:
nodejs/undici#2155
https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854

* fix(backend,remix): Tweak ClerkRequest initialization to work-around edge-runtime and undici limitations

The usual way to duplicate a request object is to
pass the original request object to the Request constructor
both as the `input` and `init` parameters, eg: super(req, req)
However, this fails in certain environments like Vercel Edge Runtime
when a framework like Remix polyfills the global Request object.
This happens because `undici` performs the following instanceof check
which, instead of testing against the global Request object, tests against
the Request class defined in the same file (local Request class).
For more details, please refer to:
nodejs/undici#2155
https://github.com/nodejs/undici/blob/7153a1c78d51840bbe16576ce353e481c3934701/lib/fetch/request.js#L854
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fetch
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants