Skip to content

Instantly share code, notes, and snippets.

@lukaszx0
Created May 21, 2018 05:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukaszx0/1755a6b92e4ce65a0e7dd67b732ce996 to your computer and use it in GitHub Desktop.
Save lukaszx0/1755a6b92e4ce65a0e7dd67b732ce996 to your computer and use it in GitHub Desktop.
gRPC Middleware for Redux
import { grpc } from "grpc-web-client";
// Use evn var if specified (used in development) or relative path to api otherwise.
export const API_URL = process.env.REACT_APP_API_URL ? process.env.REACT_APP_API_URL : "/api";
const GRPC_WEB_REQUEST = "GRPC_WEB_REQUEST";
// Descriptor of a grpc-web payload
// life-cycle methods mirror grpc-web but allow for an action to be dispatched when triggered
export type GrpcActionPayload<ReqT extends grpc.ProtobufMessage, RespT extends grpc.ProtobufMessage> = {
// The method descriptor to use for a gRPC request, equivalent to grpc.invoke(methodDescriptor, ...)
methodDescriptor: grpc.MethodDefinition<ReqT, RespT>,
// The transport to use for grpc-web, automatically selected if empty
transport?: grpc.TransportConstructor,
// toggle debug messages
debug?: boolean,
// the URL of a host this request should go to
host?: string,
// An instance of of the request message
request: ReqT,
// Additional metadata to attach to the request, the same as grpc-web
metadata?: grpc.Metadata.ConstructorArg,
// Called immediately before the request is started, useful for toggling a loading status
onStart?: () => Action | void,
// Called when response headers are received
onHeaders?: (headers: grpc.Metadata) => Action | void,
// Called on each incoming message
onMessage?: (res: RespT) => Action | void,
// Called at the end of a request, make sure to check the exit code
onEnd?: (code: grpc.Code, message: string, trailers: grpc.Metadata) => Action | void,
};
// Basic type for a gRPC Action
export type GrpcAction<ReqT extends grpc.ProtobufMessage, RespT extends grpc.ProtobufMessage> = {
type: typeof GRPC_WEB_REQUEST,
payload: GrpcActionPayload<ReqT, RespT>,
};
// Action creator, Use it to create a new grpc action
export function grpcRequestAction<ReqT extends grpc.ProtobufMessage, RespT extends grpc.ProtobufMessage>(
payload: GrpcActionPayload<ReqT, RespT>
): GrpcAction<ReqT, RespT> {
return {
type: GRPC_WEB_REQUEST,
payload,
};
}
/* tslint:disable:no-any*/
export function newGrpcMiddleware(): Middleware {
return ({ getState, dispatch }: MiddlewareAPI<{}>) => (next: Dispatch<{}>) => (action: any) => {
// skip non-grpc actions
if (!isGrpcWebUnaryAction(action)) {
return next(action);
}
const payload = action.payload;
if (payload.onStart) {
payload.onStart();
}
grpc.invoke(payload.methodDescriptor, {
debug: payload.debug,
host: payload.host ? payload.host : API_URL,
request: payload.request,
metadata: payload.metadata,
transport: payload.transport,
onHeaders: headers => {
if (!payload.onHeaders) {
return;
}
const actionToDispatch = payload.onHeaders(headers);
return actionToDispatch && dispatch(actionToDispatch);
},
onMessage: res => {
if (!payload.onMessage) {
return;
}
const actionToDispatch = payload.onMessage(res);
return actionToDispatch && dispatch(actionToDispatch);
},
onEnd: (code, msg, trailers) => {
if (!payload.onEnd) {
return;
}
const actionToDispatch = payload.onEnd(code, msg, trailers);
return actionToDispatch && dispatch(actionToDispatch);
},
});
return next(action);
};
}
function isGrpcWebUnaryAction(action: any): action is GrpcAction<grpc.ProtobufMessage, grpc.ProtobufMessage> {
return action
&& action.type
&& action.type === GRPC_WEB_REQUEST
&& isGrpcWebPayload(action);
}
function isGrpcWebPayload(action: any): boolean {
return action
&& action.payload
&& action.payload.methodDescriptor
&& action.payload.request;
}
/* tslint:enable:no-any*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment