Grpc Web Client

You can install @pbkit/grpc-web-client:

# yarn
yarn add @pbkit/grpc-web-client
# npm
npm i @pbkit/grpc-web-client

Here is the example using @pbkit/grpc-web-client:

import {
  createGrpcWebClientImpl,
  CreateGrpcWebClientImplConfig,
} from "@pbkit/grpc-web-client";
import { createServiceClient as createPingPongService } from "generated/services/pbkit/pingpong/PingPongService";
import {
  createServiceClient as createThrowService,
  CreateServiceClientConfig,
} from "generated/services/pbkit/pingpong/ThrowService";

export const createServices = () => {
  const config: CreateGrpcWebClientImplConfig = {
    host: ENV_HOST,
  };
  const grpcWebClient = createGrpcWebClientImpl(config);
  const serviceConfig: CreateServiceClientConfig = {
    devtools: env.NODE_ENV === "development",
  };
  return {
    PingPongService: createPingPongService(grpcWebClient, config),
    ThrowService: createThrowService(grpcWebClient, config),
  };
};

Advanced Usage

Write middleware for service client.

Think express middleware that intercepts requests and responses and does something. Middleware can perform the following tasks:

  • Execute any codes with requests and responses.
  • Make changes to the request and the response.
  • e.g. Retrying, Logging, Update authentication information, etc.

You have to write your own client implementation that acts like exactly what a genuine rpc client did. We provide an example to implement it fast. Here you can find the logic handling with devtools flag in createServiceClient in generated service code.

export function createServiceClient<TMetadata, THeader, TTrailer>(
  rpcClientImpl: RpcClientImpl<TMetadata, THeader, TTrailer>,
  config?: CreateServiceClientConfig,
): Service<[] | [TMetadata], [] | [THeader, Promise<TTrailer>]> {
  let _rpcClientImpl = rpcClientImpl;
  const responseOnly = config?.responseOnly ?? true;
  const devtools = config?.devtools ?? false;
  if (devtools) {
    const tags = devtools === true ? [] : devtools.tags;
    const devtoolsConfig = getDevtoolsConfig();
    // Wrapping to send request/response events to chrome devtools.
    _rpcClientImpl = wrapRpcClientImpl({ rpcClientImpl, devtoolsConfig, tags });
  }
  // ...
}

Like how devtools implementation did, you can also wrap your serviceClient using your own wrapRpcClientImpl outside the createServiceClient.

import { createGrpcWebClientImpl } from "@pbkit/grpc-web-client";
import { defer } from "pbkit/core/runtime/async/observer";
import { first, fromSingle } from "pbkit/core/runtime/async/async-generator";
import { RpcClientImpl } from "pbkit/core/runtime/rpc";

export interface WrapRpcClientImplConfig<TMetadata, THeader, TTrailer> {
  rpcClientImpl: RpcClientImpl<TMetadata, THeader, TTrailer>;
}

// Implement your own `wrapMyRpcClientImpl`
function wrapMyRpcClientImpl<TMetadata, THeader, TTrailer>(
  config: WrapRpcClientImplConfig<TMetadata, THeader, TTrailer>,
): RpcClientImpl<TMetadata, THeader, TTrailer> {
  return function myRpcClientImpl(methodDescriptor) {
    const { rpcClientImpl } = config;
    const rpcMethodImpl = rpcClientImpl(methodDescriptor);
    return function myRpcMethodImpl(request, metadata) {
      const headerPromise = defer<THeader>();
      const trailerPromise = defer<TTrailer>();
      const resAsyncGenerator = (async function* () {
        // Do something in here with payload
        // Below example assumes that this is only for unary call.
        const requestPayload = await first(request);

        const rpcMethodResult = rpcMethodImpl(
          fromSingle(requestPayload),
          metadata,
        );
        // Get the result and you can re-request or modify logic here.
        const [header, payload, trailer] = await Promise.all([
          rpcMethodResult[1],
          first(rpcMethodResult[0]),
          rpcMethodResult[2],
        ]);

        headerPromise.resolve(header);
        yield payload;
        trailerPromise.resolve(trailer);
      })();
      return [resAsyncGenerator, headerPromise, trailerPromise];
    };
  };
}

export const createGrpcServices = () => {
  const config: CreateGrpcWebClientImplConfig = {
    host: ENV_HOST,
    metadata: "SOME_METADATA",
  };
  const grpcWebClient = wrapMyRpcClientImpl({
    rpcClientImpl: createGrpcWebClientImpl(config),
  });
};

As you can see, we recommend using async utils in pbkit/runtime to implement your own wrapRpcClientImpl. You can use it by installing pbkit or @pbkit/runtime. The pbkit package includes async utils along with parser, codegen related codes, and pb-gen-ts cli. If you don't need others, you can install @pbkit/runtime only.

# yarn
yarn add pbkit
# npm
npm i pbkit

or

# yarn
yarn add @pbkit/runtime
# npm
npm i @pbkit/runtime