feat: introduce support for custom HTTP headers (#16599)
* feat: introduce support for custom http headers * feat: add fetch middleware
This commit is contained in:
parent
6d160768d7
commit
c44812fa71
|
@ -622,21 +622,45 @@ export type PerfSample = {
|
||||||
samplePeriodSecs: number;
|
samplePeriodSecs: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createRpcClient(url: string, useHttps: boolean): RpcClient {
|
function createRpcClient(
|
||||||
|
url: string,
|
||||||
|
useHttps: boolean,
|
||||||
|
httpHeaders?: HttpHeaders,
|
||||||
|
fetchMiddleware?: FetchMiddleware,
|
||||||
|
): RpcClient {
|
||||||
let agentManager: AgentManager | undefined;
|
let agentManager: AgentManager | undefined;
|
||||||
if (!process.env.BROWSER) {
|
if (!process.env.BROWSER) {
|
||||||
agentManager = new AgentManager(useHttps);
|
agentManager = new AgentManager(useHttps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fetchWithMiddleware: (url: string, options: any) => Promise<Response>;
|
||||||
|
|
||||||
|
if (fetchMiddleware) {
|
||||||
|
fetchWithMiddleware = (url: string, options: any) => {
|
||||||
|
return new Promise<Response>((resolve, reject) => {
|
||||||
|
fetchMiddleware(url, options, async (url: string, options: any) => {
|
||||||
|
try {
|
||||||
|
resolve(await fetch(url, options));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const clientBrowser = new RpcClient(async (request, callback) => {
|
const clientBrowser = new RpcClient(async (request, callback) => {
|
||||||
const agent = agentManager ? agentManager.requestStart() : undefined;
|
const agent = agentManager ? agentManager.requestStart() : undefined;
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: request,
|
body: request,
|
||||||
agent,
|
agent,
|
||||||
headers: {
|
headers: Object.assign(
|
||||||
'Content-Type': 'application/json',
|
{
|
||||||
},
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
httpHeaders || {},
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -644,7 +668,12 @@ function createRpcClient(url: string, useHttps: boolean): RpcClient {
|
||||||
let res: Response;
|
let res: Response;
|
||||||
let waitTime = 500;
|
let waitTime = 500;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
res = await fetch(url, options);
|
if (fetchWithMiddleware) {
|
||||||
|
res = await fetchWithMiddleware(url, options);
|
||||||
|
} else {
|
||||||
|
res = await fetch(url, options);
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status !== 429 /* Too many requests */) {
|
if (res.status !== 429 /* Too many requests */) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1673,6 +1702,32 @@ export type ConfirmedSignatureInfo = {
|
||||||
blockTime?: number | null;
|
blockTime?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object defining headers to be passed to the RPC server
|
||||||
|
*/
|
||||||
|
export type HttpHeaders = {[header: string]: string};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback used to augment the outgoing HTTP request
|
||||||
|
*/
|
||||||
|
export type FetchMiddleware = (
|
||||||
|
url: string,
|
||||||
|
options: any,
|
||||||
|
fetch: Function,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for instantiating a Connection
|
||||||
|
*/
|
||||||
|
export type ConnectionConfig = {
|
||||||
|
/** Optional commitment level */
|
||||||
|
commitment?: Commitment;
|
||||||
|
/** Optional HTTP headers object */
|
||||||
|
httpHeaders?: HttpHeaders;
|
||||||
|
/** Optional fetch middleware callback */
|
||||||
|
fetchMiddleware?: FetchMiddleware;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A connection to a fullnode JSON RPC endpoint
|
* A connection to a fullnode JSON RPC endpoint
|
||||||
*/
|
*/
|
||||||
|
@ -1734,18 +1789,36 @@ export class Connection {
|
||||||
* Establish a JSON RPC connection
|
* Establish a JSON RPC connection
|
||||||
*
|
*
|
||||||
* @param endpoint URL to the fullnode JSON RPC endpoint
|
* @param endpoint URL to the fullnode JSON RPC endpoint
|
||||||
* @param commitment optional default commitment level
|
* @param commitmentOrConfig optional default commitment level or optional ConnectionConfig configuration object
|
||||||
*/
|
*/
|
||||||
constructor(endpoint: string, commitment?: Commitment) {
|
constructor(
|
||||||
|
endpoint: string,
|
||||||
|
commitmentOrConfig?: Commitment | ConnectionConfig,
|
||||||
|
) {
|
||||||
this._rpcEndpoint = endpoint;
|
this._rpcEndpoint = endpoint;
|
||||||
|
|
||||||
let url = urlParse(endpoint);
|
let url = urlParse(endpoint);
|
||||||
const useHttps = url.protocol === 'https:';
|
const useHttps = url.protocol === 'https:';
|
||||||
|
|
||||||
this._rpcClient = createRpcClient(url.href, useHttps);
|
let httpHeaders;
|
||||||
|
let fetchMiddleware;
|
||||||
|
if (commitmentOrConfig && typeof commitmentOrConfig === 'string') {
|
||||||
|
this._commitment = commitmentOrConfig;
|
||||||
|
} else if (commitmentOrConfig) {
|
||||||
|
this._commitment = commitmentOrConfig.commitment;
|
||||||
|
httpHeaders = commitmentOrConfig.httpHeaders;
|
||||||
|
fetchMiddleware = commitmentOrConfig.fetchMiddleware;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._rpcClient = createRpcClient(
|
||||||
|
url.href,
|
||||||
|
useHttps,
|
||||||
|
httpHeaders,
|
||||||
|
fetchMiddleware,
|
||||||
|
);
|
||||||
this._rpcRequest = createRpcRequest(this._rpcClient);
|
this._rpcRequest = createRpcRequest(this._rpcClient);
|
||||||
this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
|
this._rpcBatchRequest = createRpcBatchRequest(this._rpcClient);
|
||||||
this._commitment = commitment;
|
|
||||||
this._blockhashInfo = {
|
this._blockhashInfo = {
|
||||||
recentBlockhash: null,
|
recentBlockhash: null,
|
||||||
lastFetch: 0,
|
lastFetch: 0,
|
||||||
|
@ -1764,6 +1837,7 @@ export class Connection {
|
||||||
if (url.port !== null) {
|
if (url.port !== null) {
|
||||||
url.port = String(Number(url.port) + 1);
|
url.port = String(Number(url.port) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._rpcWebSocket = new RpcWebSocketClient(urlFormat(url), {
|
this._rpcWebSocket = new RpcWebSocketClient(urlFormat(url), {
|
||||||
autoconnect: false,
|
autoconnect: false,
|
||||||
max_reconnects: Infinity,
|
max_reconnects: Infinity,
|
||||||
|
|
|
@ -88,6 +88,49 @@ describe('Connection', () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mockServer) {
|
||||||
|
it('should pass HTTP headers to RPC', async () => {
|
||||||
|
const headers = {
|
||||||
|
Authorization: 'Bearer 123',
|
||||||
|
};
|
||||||
|
|
||||||
|
let connection = new Connection(url, {
|
||||||
|
httpHeaders: headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockRpcResponse({
|
||||||
|
method: 'getVersion',
|
||||||
|
params: [],
|
||||||
|
value: {'solana-core': '0.20.4'},
|
||||||
|
withHeaders: headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await connection.getVersion()).to.be.not.null;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow middleware to augment request', async () => {
|
||||||
|
let connection = new Connection(url, {
|
||||||
|
fetchMiddleware: (url, options, fetch) => {
|
||||||
|
options.headers = Object.assign(options.headers, {
|
||||||
|
Authorization: 'Bearer 123',
|
||||||
|
});
|
||||||
|
fetch(url, options);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockRpcResponse({
|
||||||
|
method: 'getVersion',
|
||||||
|
params: [],
|
||||||
|
value: {'solana-core': '0.20.4'},
|
||||||
|
withHeaders: {
|
||||||
|
Authorization: 'Bearer 123',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await connection.getVersion()).to.be.not.null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('get account info - not found', async () => {
|
it('get account info - not found', async () => {
|
||||||
const account = new Account();
|
const account = new Account();
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as mockttp from 'mockttp';
|
||||||
|
|
||||||
import {mockRpcMessage} from './rpc-websockets';
|
import {mockRpcMessage} from './rpc-websockets';
|
||||||
import {Account, Connection, PublicKey, Transaction} from '../../src';
|
import {Account, Connection, PublicKey, Transaction} from '../../src';
|
||||||
import type {Commitment, RpcParams} from '../../src/connection';
|
import type {Commitment, HttpHeaders, RpcParams} from '../../src/connection';
|
||||||
|
|
||||||
export const mockServer: mockttp.Mockttp | undefined =
|
export const mockServer: mockttp.Mockttp | undefined =
|
||||||
process.env.TEST_LIVE === undefined ? mockttp.getLocal() : undefined;
|
process.env.TEST_LIVE === undefined ? mockttp.getLocal() : undefined;
|
||||||
|
@ -64,12 +64,14 @@ export const mockRpcResponse = async ({
|
||||||
value,
|
value,
|
||||||
error,
|
error,
|
||||||
withContext,
|
withContext,
|
||||||
|
withHeaders,
|
||||||
}: {
|
}: {
|
||||||
method: string;
|
method: string;
|
||||||
params: Array<any>;
|
params: Array<any>;
|
||||||
value?: any;
|
value?: any;
|
||||||
error?: any;
|
error?: any;
|
||||||
withContext?: boolean;
|
withContext?: boolean;
|
||||||
|
withHeaders?: HttpHeaders;
|
||||||
}) => {
|
}) => {
|
||||||
if (!mockServer) return;
|
if (!mockServer) return;
|
||||||
|
|
||||||
|
@ -90,6 +92,7 @@ export const mockRpcResponse = async ({
|
||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
|
.withHeaders(withHeaders || {})
|
||||||
.thenReply(
|
.thenReply(
|
||||||
200,
|
200,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|
Loading…
Reference in New Issue