Skip to content

Commit

Permalink
[web3.js] Eliminate dependency on URL class (#27349)
Browse files Browse the repository at this point in the history
* fix: `makeWebsocketUrl` no longer depends on the `URL` class
* fix: `Connection` no longer relies on the `URL` class
* fix: remove dependency on `react-native-url-polyfill`
  • Loading branch information
steveluscher authored Aug 24, 2022
1 parent 8d5e263 commit 4f2d052
Show file tree
Hide file tree
Showing 8 changed files with 23,019 additions and 33,412 deletions.
41,166 changes: 15,328 additions & 25,838 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
"jayson": "^3.4.4",
"js-sha3": "^0.8.0",
"node-fetch": "2",
"react-native-url-polyfill": "^1.3.0",
"rpc-websockets": "^7.5.0",
"secp256k1": "^4.0.2",
"superstruct": "^0.14.2",
Expand Down
20 changes: 11 additions & 9 deletions src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import {
TransactionExpiredTimeoutError,
} from './transaction/expiry-custom-errors';
import {makeWebsocketUrl} from './utils/makeWebsocketUrl';
import {URL} from './utils/url-impl';
import type {Blockhash} from './blockhash';
import type {FeeCalculator} from './fee-calculator';
import type {TransactionSignature} from './transaction';
Expand Down Expand Up @@ -305,6 +304,14 @@ export type BlockheightBasedTransactionConfirmationStrategy = {
signature: TransactionSignature;
} & BlockhashWithExpiryBlockHeight;

/* @internal */
function assertEndpointUrl(putativeUrl: string) {
if (/^https?:/.test(putativeUrl) === false) {
throw new TypeError('Endpoint URL must start with `http:` or `https:`.');
}
return putativeUrl;
}

/** @internal */
function extractCommitmentFromConfig<TConfig>(
commitmentOrConfig?: Commitment | ({commitment?: Commitment} & TConfig),
Expand Down Expand Up @@ -1117,7 +1124,6 @@ export type PerfSample = {

function createRpcClient(
url: string,
useHttps: boolean,
httpHeaders?: HttpHeaders,
customFetch?: FetchFn,
fetchMiddleware?: FetchMiddleware,
Expand All @@ -1126,7 +1132,7 @@ function createRpcClient(
const fetch = customFetch ? customFetch : fetchImpl;
let agentManager: AgentManager | undefined;
if (!process.env.BROWSER) {
agentManager = new AgentManager(useHttps);
agentManager = new AgentManager(url.startsWith('https:') /* useHttps */);
}

let fetchWithMiddleware: FetchFn | undefined;
Expand Down Expand Up @@ -2493,9 +2499,6 @@ export class Connection {
endpoint: string,
commitmentOrConfig?: Commitment | ConnectionConfig,
) {
let url = new URL(endpoint);
const useHttps = url.protocol === 'https:';

let wsEndpoint;
let httpHeaders;
let fetch;
Expand All @@ -2514,12 +2517,11 @@ export class Connection {
disableRetryOnRateLimit = commitmentOrConfig.disableRetryOnRateLimit;
}

this._rpcEndpoint = endpoint;
this._rpcEndpoint = assertEndpointUrl(endpoint);
this._rpcWsEndpoint = wsEndpoint || makeWebsocketUrl(endpoint);

this._rpcClient = createRpcClient(
url.toString(),
useHttps,
endpoint,
httpHeaders,
fetch,
fetchMiddleware,
Expand Down
2 changes: 0 additions & 2 deletions src/utils/__forks__/react-native/url-impl.ts

This file was deleted.

38 changes: 22 additions & 16 deletions src/utils/makeWebsocketUrl.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import {URL} from './url-impl';
const URL_RE = /^[^:]+:\/\/([^:[]+|\[[^\]]+\])(:\d+)?(.*)/i;

export function makeWebsocketUrl(endpoint: string) {
let url = new URL(endpoint);
const useHttps = url.protocol === 'https:';

url.protocol = useHttps ? 'wss:' : 'ws:';
url.host = '';

// Only shift the port by +1 as a convention for ws(s) only if given endpoint
// is explictly specifying the endpoint port (HTTP-based RPC), assuming
// we're directly trying to connect to solana-validator's ws listening port.
// When the endpoint omits the port, we're connecting to the protocol
// default ports: http(80) or https(443) and it's assumed we're behind a reverse
// proxy which manages WebSocket upgrade and backend port redirection.
if (url.port !== '') {
url.port = String(Number(url.port) + 1);
const matches = endpoint.match(URL_RE);
if (matches == null) {
throw TypeError(`Failed to validate endpoint URL \`${endpoint}\``);
}
return url.toString();
const [
_, // eslint-disable-line @typescript-eslint/no-unused-vars
hostish,
portWithColon,
rest,
] = matches;
const protocol = endpoint.startsWith('https:') ? 'wss:' : 'ws:';
const startPort =
portWithColon == null ? null : parseInt(portWithColon.slice(1), 10);
const websocketPort =
// Only shift the port by +1 as a convention for ws(s) only if given endpoint
// is explictly specifying the endpoint port (HTTP-based RPC), assuming
// we're directly trying to connect to solana-validator's ws listening port.
// When the endpoint omits the port, we're connecting to the protocol
// default ports: http(80) or https(443) and it's assumed we're behind a reverse
// proxy which manages WebSocket upgrade and backend port redirection.
startPort == null ? '' : `:${startPort + 1}`;
return `${protocol}//${hostish}${websocketPort}${rest}`;
}
2 changes: 0 additions & 2 deletions src/utils/url-impl.ts

This file was deleted.

52 changes: 52 additions & 0 deletions test/makeWebsocketUrl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {expect} from 'chai';

import {makeWebsocketUrl} from '../src/utils/makeWebsocketUrl';

const INVALID_URLS = [
'',
'0.0.0.0',
'localhost',
'www.no-protocol.com',
'//api.protocol.relative.com',
];
const TEST_CASES = [
// Non-https => `ws`
['http://api.devnet.solana.com/', 'ws://api.devnet.solana.com/'],
['gopher://gopher.example.com/', 'ws://gopher.example.com/'],
['http://localhost/', 'ws://localhost/'],
// `https` => `wss`
['https://api.devnet.solana.com/', 'wss://api.devnet.solana.com/'],
// IPv4 address
['https://192.168.0.1/', 'wss://192.168.0.1/'],
// IPv6 address
['https://[0:0:0:0:0:0:0:0]/', 'wss://[0:0:0:0:0:0:0:0]/'],
['https://[::]/', 'wss://[::]/'],
['https://[::1]/', 'wss://[::1]/'],
// Increment port if supplied
['https://api.devnet.solana.com:80/', 'wss://api.devnet.solana.com:81/'],
['https://192.168.0.1:443/', 'wss://192.168.0.1:444/'],
['https://[::]:8080/', 'wss://[::]:8081/'],
// No trailing slash
['http://api.devnet.solana.com', 'ws://api.devnet.solana.com'],
['https://api.devnet.solana.com', 'wss://api.devnet.solana.com'],
['https://api.devnet.solana.com:80', 'wss://api.devnet.solana.com:81'],
// Username
['https://[email protected]', 'wss://[email protected]'],
// Username/password
['https://bob:[email protected]', 'wss://bob:[email protected]'],
];

describe('makeWebsocketUrl', () => {
TEST_CASES.forEach(([inputUrl, outputUrl]) => {
it(`converts \`${inputUrl}\` to \`${outputUrl}\``, () => {
expect(makeWebsocketUrl(inputUrl)).to.equal(outputUrl);
});
});
INVALID_URLS.forEach(invalidUrl => {
it(`fatals when called with invalid url \`${invalidUrl}\``, () => {
expect(() => {
makeWebsocketUrl(invalidUrl);
}).to.throw();
});
});
});
Loading

0 comments on commit 4f2d052

Please sign in to comment.