Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

Improved flexibility of FetchMiddleware for Web3.js #22433

Closed
wants to merge 1 commit into from

Conversation

arguiot
Copy link

@arguiot arguiot commented Jan 11, 2022

Problem

Currently, the fetch client is cross-fetch which is fine in most situations, but in some cases, it can be very problematic. Personally, I'm trying to use web3.js on Cloudflare Workers which has a custom fetch client. In other cases, like React Native, the library may also not work, see: #22421

My point is that there should be an option to use another fetch function.

Proposed Solution

Currently, there is a Middleware option in the Connection constructor. The issue with this middleware is that it's very limited as we have to use the provided fetch function as a callback. I propose to have a FetchMiddleware function like that:

const fetchMiddleware = async (url, options): Promise<Response> => {
    // Your code, but here's how you would use it:
   return await fetch(url, options)
}

It really shouldn't be hard to implement it. I can probably do it, but I prefer to wait for feedback.

Summary of changes

  • Updated connection.ts
  • Updated tests (may need help to fix one test)

Fixes #22429

@mergify mergify bot added the community Community contribution label Jan 11, 2022
@mergify mergify bot requested a review from a team January 11, 2022 22:10
@Haianh9999
Copy link

@arguiot if i update connection.ts according to your code my problem will be solved

@arguiot
Copy link
Author

arguiot commented Jan 12, 2022

Yes, I think it would solve the problem for sure. I hope the Solana team will review this PR quickly so we can both get our projects running 😄!

@Haianh9999
Copy link

@arguiot Thank you this problem took me a long time these past few days

@Haianh9999
Copy link

@arguiot Can you explain to me why when i import solana/web3js my system fails to getBalance of token ?

@Haianh9999
Copy link

Screen Shot 2022-01-12 at 13 21 48
@arguiot how to import RequestInit ?

@arguiot
Copy link
Author

arguiot commented Jan 12, 2022

You can remove the as RequestInit part if you don't have it on your system. It's supposed to be in the tests, not the actual code. RequestInit is simply the interface that represents Fetch options.

For the getBalance I don't know, what errors are you getting?

@Haianh9999
Copy link

@arguiot can i fork this to use in my project ?

@arguiot
Copy link
Author

arguiot commented Jan 17, 2022

Of course!! That's open source

@Haianh9999
Copy link

@arguiot i fork solana/web3js and fix the code connect.ts and build file to 1 new lib folder then i replace tell original lib folder in my project with new folder and it still doesn't work

@arguiot
Copy link
Author

arguiot commented Jan 18, 2022

Just to make sure, did you also change the fetch middleware? Because this way you can use a function that is supported on your platform

@arguiot
Copy link
Author

arguiot commented Jan 19, 2022

This PR might be duplicate of #20063 and #21388

@PirosB3
Copy link

PirosB3 commented Feb 15, 2022

Any update on this? would love to use Solana web3.js with CF workers 😃

@stale
Copy link

stale bot commented Mar 2, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Mar 2, 2022
@beeman
Copy link
Contributor

beeman commented Mar 2, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

Please don't close this - I'd love to be able to use this on CF Workers!

@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Mar 2, 2022
@arguiot
Copy link
Author

arguiot commented Mar 3, 2022

I managed to make this run of CF workers! Just a bit of hacking, but it works well. Here's a sample code to connect without cross-fetch:

import * as web3 from "@solana/web3.js"
import RpcClient from 'jayson/lib/client/browser';

type RpcParams = {
    methodName: string;
    args: Array<any>;
};

  
type RpcRequest = (methodName: string, args: Array<any>) => any;

type RpcBatchRequest = (requests: RpcParams[]) => any;

function createRpcClient(
    url: string,
): RpcClient {
    const clientBrowser = new RpcClient(async (request, callback) => {
        const options = {
            method: 'POST',
            body: request,
            headers: Object.assign({
                'Content-Type': 'application/json',
            }),
        };

        try {
            let too_many_requests_retries = 5;
            let res: Response;
            let waitTime = 500;
            for (;;) {
                res = await fetch(url, options);

                if (res.status !== 429 /* Too many requests */ ) {
                    break;
                }
                too_many_requests_retries -= 1;
                if (too_many_requests_retries === 0) {
                    break;
                }
                console.log(
                    `Server responded with ${res.status} ${res.statusText}.  Retrying after ${waitTime}ms delay...`,
                );
                // await sleep(waitTime);
                waitTime *= 2;
            }

            const text = await res.text();
            if (res.ok) {
                callback(null, text);
            } else {
                callback(new Error(`${res.status} ${res.statusText}: ${text}`));
            }
        } catch (err) {
            if (err instanceof Error) callback(err);
        }
    }, {});

    return clientBrowser;
}

function createRpcRequest(client: RpcClient): RpcRequest {
    return (method, args) => {
        return new Promise((resolve, reject) => {
            client.request(method, args, (err: any, response: any) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(response);
            });
        });
    };
}

function createRpcBatchRequest(client: RpcClient): RpcBatchRequest {
    return (requests: RpcParams[]) => {
        return new Promise((resolve, reject) => {
            // Do nothing if requests is empty
            if (requests.length === 0) resolve([]);

            const batch = requests.map((params: RpcParams) => {
                return client.request(params.methodName, params.args);
            });

            client.request(batch, (err: any, response: any) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(response);
            });
        });
    };
}

export default async function Connect(): Promise < web3.Connection > {
    const mainnet = (global as any).ENVIRONMENT === 'production'; 
    const endpoint = web3.clusterApiUrl(mainnet ? 'mainnet-beta' : 'devnet')
    // Connect to cluster
    const connection = new web3.Connection(endpoint, {
        commitment: 'confirmed'
    });

    // Override the default RPC client to use the Workers's fetch
    const client = createRpcClient(endpoint);
    const request = createRpcRequest(client);
    const batchRequest = createRpcBatchRequest(client);
    // assign connection._rpcClient to the new client using Object.assign
    Object.assign(connection, {
        _rpcClient: client,
        _rpcRequest: request,
        _rpcBatchRequest: batchRequest,
    });

    return connection
}

The main idea is to use Object.assign after the connection object was created and replace everything with your own methods.

@stale
Copy link

stale bot commented Apr 16, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Apr 16, 2022
@jstarry
Copy link
Contributor

jstarry commented Apr 16, 2022

Support overriding the fetch function was added in #24367 and released in https://github.com/solana-labs/solana-web3.js/releases/tag/v1.39.0

Is there any more flexibility that you'd like added beyond that?

@stale stale bot removed the stale [bot only] Added to stale content; results in auto-close after a week. label Apr 16, 2022
@stale
Copy link

stale bot commented Apr 25, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@stale stale bot added the stale [bot only] Added to stale content; results in auto-close after a week. label Apr 25, 2022
@jstarry
Copy link
Contributor

jstarry commented Apr 25, 2022

Closing because I think that overriding the fetch function is probably sufficient here. @arguiot please let us know if you still need this

@jstarry jstarry closed this Apr 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community Community contribution stale [bot only] Added to stale content; results in auto-close after a week.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Web3.js - Fetch Middleware custom client
5 participants