LLKennedy/httpgrpc

View on GitHub
client/src/client/client.ts

Summary

Maintainability
C
7 hrs
Test Coverage
import axios, { AxiosInstance as AI, AxiosRequestConfig } from "axios";
import { ClientStream, DualStream, MercuryWebSocket, ServerStream } from "../websocket";
import { ProtoJSONCompatible, Parser } from "@llkennedy/protoc-gen-tsjson";

export interface AxiosInstance extends AI { };
export const DefaultAxios = axios;

/** Client is an RPC client proxied over HTTP and websockets. It is recommended to wrap this in service-specific RPC definitions, 
 * rather than relying on end-users to use the type parameters correctly. */
export class Client {
    private axiosClient: AxiosInstance;
    private basePath: string;
    private useTLS: boolean;
    constructor(basePath: string = "localhost/api", useTLS: boolean = true, client: AxiosInstance = axios) {
        this.basePath = basePath;
        this.useTLS = useTLS;
        this.axiosClient = client;
    }
    /**
     * @param {string} endpoint The API endpoint to send the message to
     * @param {ProtoJSONCompatible} request The message to convert to protojson then send
     */
    protected async SendUnary<ReqT extends ProtoJSONCompatible, ResT = any>(endpoint: string, method: HTTPMethod, request: ReqT, parseResponse: Parser<ResT>): Promise<ResT> {
        let message = request.ToProtoJSON();
        let url = this.BuildURL(endpoint, false);
        let req: AxiosRequestConfig = {
            url: url,
        }
        switch (method) {
            case HTTPMethod.CONNECT:
                throw new Error("CONNECT not implemented");
            case HTTPMethod.TRACE:
                throw new Error("TRACE not implemented");
            case HTTPMethod.GET:
                req.params = message;
                break;
            default:
                req.data = message;
        }
        req.method = method;
        let res = await this.axiosClient.request<Object>(req);
        return parseResponse(res.data);
    }
    protected async StartClientStream<ReqT extends ProtoJSONCompatible, ResT = any>(endpoint: string, parseResponse: Parser<ResT>): Promise<ClientStream<ReqT, ResT>> {
        let url = this.BuildURL(endpoint, true);
        let ws = new MercuryWebSocket(url, parseResponse);
        // Establish the connection, set up event listeners, etc.
        await ws.init();
        return new ClientStream(ws);
    }
    protected async StartServerStream<ReqT extends ProtoJSONCompatible, ResT = any>(endpoint: string, request: ReqT, parseResponse: Parser<ResT>): Promise<ServerStream<ReqT, ResT>> {
        let url = this.BuildURL(endpoint, true);
        let ws = new MercuryWebSocket(url, parseResponse);
        // Establish the connection, set up event listeners, etc.
        await ws.init();
        let ss = new ServerStream<ReqT, ResT>(ws, request);
        return ss.init();
    }
    protected async StartDualStream<ReqT extends ProtoJSONCompatible, ResT = any>(endpoint: string, parseResponse: Parser<ResT>): Promise<DualStream<ReqT, ResT>> {
        let url = this.BuildURL(endpoint, true);
        let ws = new MercuryWebSocket(url, parseResponse);
        // Establish the connection, set up event listeners, etc.
        await ws.init();
        return new DualStream(ws);
    }
    protected BuildURL(endpoint: string, websocket: boolean = false): string {
        // First get the scheme
        let scheme: string;
        switch (websocket) {
            case true:
                switch (this.useTLS) {
                    case true:
                        scheme = "wss";
                        break;
                    case false:
                        scheme = "ws";
                        break;
                }
                break;
            case false:
                switch (this.useTLS) {
                    case true:
                        scheme = "https";
                        break;
                    case false:
                        scheme = "http";
                        break;
                }
                break;
        }
        return `${scheme}://${this.basePath}/${endpoint}`;
    }
}

export enum HTTPMethod {
    GET = "GET",
    HEAD = "HEAD",
    POST = "POST",
    PUT = "PUT",
    DELETE = "DELETE",
    CONNECT = "CONNECT",
    OPTIONS = "OPTIONS",
    TRACE = "TRACE",
    PATCH = "PATCH"
}

export default Client;