Created
May 21, 2018 05:32
-
-
Save lukaszx0/1755a6b92e4ce65a0e7dd67b732ce996 to your computer and use it in GitHub Desktop.
gRPC Middleware for Redux
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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