Skip to content

feat: remove WebSocket dependencies #1374

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

Merged
merged 3 commits into from
May 9, 2025
Merged

feat: remove WebSocket dependencies #1374

merged 3 commits into from
May 9, 2025

Conversation

penovicp
Copy link
Collaborator

@penovicp penovicp commented Apr 7, 2025

Motivation and Resolution

The most widely used browsers and the most recent Node.js LTS version provide native WebSocket support. This means that for most users the fallback modules should be unnecessary.

Usage related changes

Most users should be unaffected. Users working with environments without native WebSocket support can use the websocket parameter of the WebSocketChannel constructor to provide a valid WebSocket connection themselves.

Checklist:

  • Performed a self-review of the code
  • Rebased to the last commit of the target branch (or merged it into my branch)
  • Linked the issues which this PR resolves
  • Documented the changes in code (API docs will be generated automatically)
  • Updated the tests
  • All tests are passing

@penovicp penovicp marked this pull request as ready for review April 28, 2025 08:58
@penovicp penovicp requested a review from tabaktoni April 28, 2025 08:59
Comment on lines 4 to 5
(typeof window !== 'undefined' && window.fetch) ||
(typeof global !== 'undefined' && global.fetch) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(typeof window !== 'undefined' && window.fetch) ||
(typeof global !== 'undefined' && global.fetch) ||
(typeof globalThis.fetch !== 'undefined' && globalThis.fetch) ||

Otherwise a discrete "global object" check per environment needs to be added. globalThis is the more modern cross-environment option.

Copy link
Member

Choose a reason for hiding this comment

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

Until everyone becomes modern, we will leave inexpensive tests and also add globalThis

Choose a reason for hiding this comment

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

Has this been tested in browsers?

I get Failed to execute 'fetch' on 'Window': Illegal invocation

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems it's due to the missing .bind(window) as in:

export default (IS_BROWSER && window.fetch.bind(window)) || // use built-in fetch in browser if available

For @penovicp : do you plan on supporting non-fetch-supporting environments? Can we just get rid of these checks that add complexity? All modern environments support native fetch.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see why this is mandatory but will add window.fetch.bind(window)

Copy link
Contributor

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

@tabaktoni it's also needed on globalThis and global for the same reason 🙏

  (typeof globalThis !== 'undefined' && globalThis.fetch.bind(globalThis)) ||
  (typeof window !== 'undefined' && window.fetch.bind(window)) ||
  (typeof global !== 'undefined' && global.fetch.bind(global)) ||

and this first line should not exist as it will override all the others as fetch === globalThis.fetch === window.fetch === global.fetch, so this line would always win over the others and the binding won't happen:

(typeof fetch !== 'undefined' && fetch) ||

@tabaktoni tabaktoni requested review from aryzing, ondrej-secretkeylabs and PhilippeR26 and removed request for aryzing and ondrej-secretkeylabs May 7, 2025 10:14
Comment on lines +3 to +4
export default (typeof fetch !== 'undefined' && fetch) ||
(typeof globalThis !== 'undefined' && globalThis.fetch) ||
Copy link
Contributor

@aryzing aryzing May 8, 2025

Choose a reason for hiding this comment

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

Suggested change
export default (typeof fetch !== 'undefined' && fetch) ||
(typeof globalThis !== 'undefined' && globalThis.fetch) ||
export default (typeof globalThis !== 'undefined' && globalThis.fetch.bind(globalThis)) ||

The first check is practically equivalent to the second. The second form is more desirable since it wouldn't make sense to bind to globalThis without first checking for it.

What works and what doesn't:

Works:

const myFetch = fetch;
await myFetch("https://example.com"); // ok

Does not work: Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation

const myObj = {
  myFetch,
  fetch,
};

myObj.myFetch("https://example.com"); // error
myObj.fetch("https://example.com"); // error

Why? the this value in this second case is myObj.

Why does this fail with starknet? The key areas of the library's compiled code look like this:

// src/utils/connect/fetch.ts
var fetch_default =
  (typeof fetch !== "undefined" && fetch) ||
  (typeof globalThis !== "undefined" && globalThis.fetch) ||
  (typeof window !== "undefined" && window.fetch.bind(window)) ||
  (typeof global !== "undefined" && global.fetch) ||
  (() => {
    throw new LibraryError(
      "'fetch()' not detected, use the 'baseFetch' constructor parameter to set it"
    );
  });

and

this.baseFetch = baseFetch || config.get("fetch") || fetch_default;

with a random invocation looking like,

this.baseFetch(this.nodeUrl, {
  method: "POST",
  body: stringify2(rpcRequestBody),
  headers: this.headers,
});

Above, this is RpcChannel, not window or globalThis, so the call fails.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, but why are you suggesting removing support for node.js fetch?

Choose a reason for hiding this comment

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

@tabaktoni plz see this comment on why the global fetch without a context should be removed. As per the comment, the bind also needs to be applied to global and globalThis or they will break in the same way that window broke.

Copy link
Contributor

Choose a reason for hiding this comment

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

@tabaktoni, the changes suggested in the comment above don't remove support for Node.js fetch. If applied, as also suggested by @victorkirov above, the final result would look like

  (typeof globalThis !== 'undefined' && globalThis.fetch.bind(globalThis)) ||
  (typeof window !== 'undefined' && window.fetch.bind(window)) ||
  (typeof global !== 'undefined' && global.fetch.bind(global)) ||
  • globalThis --> all modern clients
  • window --> legacy browsers
  • global --> legacy node.js

@tabaktoni tabaktoni merged commit 24f9908 into develop May 9, 2025
2 of 3 checks passed
@aryzing aryzing mentioned this pull request May 9, 2025
6 tasks
Copy link

github-actions bot commented May 9, 2025

🎉 This PR is included in version 7.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Successfully merging this pull request may close these issues.

5 participants