Skip to content

Commit

Permalink
Add beacon client methods
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain committed Dec 8, 2023
1 parent 25201d4 commit 19c5b80
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 314 deletions.
46 changes: 0 additions & 46 deletions packages/api/src/beacon/client/beacon.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/api/src/beacon/client/config.ts

This file was deleted.

60 changes: 0 additions & 60 deletions packages/api/src/beacon/client/debug.ts

This file was deleted.

95 changes: 51 additions & 44 deletions packages/api/src/beacon/client/events.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,65 @@
import {ChainForkConfig} from "@lodestar/config";
import {Api, BeaconEvent, routesData, getEventSerdes} from "../routes/events.js";
import {stringifyQuery} from "../../utils/client/format.js";
import {BeaconEvent, getEventSerdes, Endpoints, definitions} from "../routes/events.js";
import {getEventSource} from "../../utils/client/eventSource.js";
import {HttpStatusCode} from "../../utils/client/httpStatusCode.js";
import {IHttpClient} from "../../utils/client/httpClient.js";
import {ApiClientMethods} from "../../utils/client/method.js";
import {compileRouteUrlFormater} from "../../utils/urlFormat.js";

/**
* REST HTTP client for events routes
*/
export function getClient(config: ChainForkConfig, baseUrl: string): Api {
export function getClient(config: ChainForkConfig, client: IHttpClient): ApiClientMethods<Endpoints> {
const eventSerdes = getEventSerdes(config);

const urlFormatter = compileRouteUrlFormater(definitions.eventstream.url);
const eventstreamDefinitionExtended = {
...definitions.eventstream,
urlFormatter,
operationId: "eventstream",
};

return {
eventstream: async (topics, signal, onEvent) => {
const query = stringifyQuery({topics});
// TODO: Use a proper URL formatter
const url = `${baseUrl}${routesData.eventstream.url}?${query}`;
// eslint-disable-next-line @typescript-eslint/naming-convention
const EventSource = await getEventSource();
const eventSource = new EventSource(url);

try {
await new Promise<void>((resolve, reject) => {
for (const topic of topics) {
eventSource.addEventListener(topic, ((event: MessageEvent) => {
const message = eventSerdes.fromJson(topic, JSON.parse(event.data));
onEvent({type: topic, message} as BeaconEvent);
}) as EventListener);
eventstream: async (args, init) => {
const fetch = async (input: RequestInfo | URL): Promise<Response> => {
const url = input instanceof Request ? input.url : input;
// eslint-disable-next-line @typescript-eslint/naming-convention
const EventSource = await getEventSource();
const eventSource = new EventSource(url);

const {topics, signal, onEvent, onError, onClose} = args;

const close = (): void => {
eventSource.close();
onClose?.();
signal.removeEventListener("abort", close);
};
signal.addEventListener("abort", close, {once: true});

for (const topic of topics) {
eventSource.addEventListener(topic, ((event: MessageEvent) => {
const message = eventSerdes.fromJson(topic, JSON.parse(event.data));
onEvent({type: topic, message} as BeaconEvent);
}) as EventListener);
}

// EventSource will try to reconnect always on all errors
// `eventSource.onerror` events are informative but don't indicate the EventSource closed
// The only way to abort the connection from the client is via eventSource.close()
eventSource.onerror = function onerror(err) {
const errEs = err as unknown as EventSourceError;
onError?.(errEs);
// Consider 400 and 500 status errors unrecoverable, close the eventsource
if (errEs.status === 400 || errEs.status === 500) {
close();
}
// TODO: else log the error somewhere
// console.log("eventstream client error", errEs);
};

return new Response();
};

// EventSource will try to reconnect always on all errors
// `eventSource.onerror` events are informative but don't indicate the EventSource closed
// The only way to abort the connection from the client is via eventSource.close()
eventSource.onerror = function onerror(err) {
const errEs = err as unknown as EventSourceError;
// Consider 400 and 500 status errors unrecoverable, close the eventsource
if (errEs.status === 400) {
reject(Error(`400 Invalid topics: ${errEs.message}`));
}
if (errEs.status === 500) {
reject(Error(`500 Internal Server Error: ${errEs.message}`));
}

// TODO: else log the error somewhere
// console.log("eventstream client error", errEs);
};

// And abort resolve the promise so finally {} eventSource.close()
signal.addEventListener("abort", () => resolve(), {once: true});
});
} finally {
eventSource.close();
}

return {ok: true, response: undefined, status: HttpStatusCode.OK};
return client.request(eventstreamDefinitionExtended, args, init ?? {}, fetch);
},
};
}
Expand Down
52 changes: 32 additions & 20 deletions packages/api/src/beacon/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import {ChainForkConfig} from "@lodestar/config";
import {Api} from "../routes/index.js";
import {IHttpClient, HttpClient, HttpClientOptions, HttpClientModules} from "../../utils/client/index.js";
import {
IHttpClient,
HttpClient,
HttpClientOptions,
HttpClientModules,
createApiClientMethods,
ApiClientMethods,
} from "../../utils/client/index.js";

import * as beacon from "./beacon.js";
import * as configApi from "./config.js";
import * as debug from "./debug.js";
import {
Endpoints,
beacon,
config as configApi,
debug,
lightclient,
lodestar,
node,
proof,
validator,
} from "../routes/index.js";
import * as events from "./events.js";
import * as lightclient from "./lightclient.js";
import * as lodestar from "./lodestar.js";
import * as node from "./node.js";
import * as proof from "./proof.js";
import * as validator from "./validator.js";

type ClientModules = HttpClientModules & {
config: ChainForkConfig;
Expand All @@ -20,19 +29,22 @@ type ClientModules = HttpClientModules & {
/**
* REST HTTP client for all routes
*/
export function getClient(opts: HttpClientOptions, modules: ClientModules): Api {
export function getClient(
opts: HttpClientOptions,
modules: ClientModules
): {[K in keyof Endpoints]: ApiClientMethods<Endpoints[K]>} {
const {config} = modules;
const httpClient = modules.httpClient ?? new HttpClient(opts, modules);

return {
beacon: beacon.getClient(config, httpClient),
config: configApi.getClient(config, httpClient),
debug: debug.getClient(config, httpClient),
events: events.getClient(config, httpClient.baseUrl),
lightclient: lightclient.getClient(config, httpClient),
lodestar: lodestar.getClient(config, httpClient),
node: node.getClient(config, httpClient),
proof: proof.getClient(config, httpClient),
validator: validator.getClient(config, httpClient),
beacon: createApiClientMethods(beacon.getDefinitions(config), httpClient),
config: createApiClientMethods(configApi.definitions, httpClient),
debug: createApiClientMethods(debug.definitions, httpClient),
events: events.getClient(config, httpClient),
lightclient: createApiClientMethods(lightclient.getDefinitions(config), httpClient),
lodestar: createApiClientMethods(lodestar.definitions, httpClient),
node: createApiClientMethods(node.definitions, httpClient),
proof: createApiClientMethods(proof.definitions, httpClient),
validator: createApiClientMethods(validator.definitions, httpClient),
};
}
13 changes: 0 additions & 13 deletions packages/api/src/beacon/client/lightclient.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/api/src/beacon/client/lodestar.ts

This file was deleted.

13 changes: 0 additions & 13 deletions packages/api/src/beacon/client/node.ts

This file was deleted.

Loading

0 comments on commit 19c5b80

Please sign in to comment.