|
| 1 | +import { Action, Dispatch, Middleware, MiddlewareAPI } from 'redux'; |
| 2 | +import { Code, grpc, Metadata, Transport } from 'grpc-web-client'; |
| 3 | +import * as jspb from 'google-protobuf'; |
| 4 | + |
| 5 | +const GRPC_WEB_REQUEST = 'GRPC_WEB_REQUEST'; |
| 6 | +// const GRPC_WEB_INVOKE = 'GRPC_WEB_INVOKE'; |
| 7 | + |
| 8 | +// Descriptor of a grpc-web payload |
| 9 | +// life-cycle methods mirror grpc-web but allow for an action to be dispatched when triggered |
| 10 | +export type GrpcActionPayload<RequestType extends jspb.Message, ResponseType extends jspb.Message> = { |
| 11 | + // The method descriptor to use for a gRPC request, equivalent to grpc.invoke(methodDescriptor, ...) |
| 12 | + methodDescriptor: grpc.MethodDefinition<RequestType, ResponseType>, |
| 13 | + // The transport to use for grpc-web, automatically selected if empty |
| 14 | + transport?: Transport, |
| 15 | + // toggle debug messages |
| 16 | + debug?: boolean, |
| 17 | + // the URL of a host this request should go to |
| 18 | + host: string, |
| 19 | + // An instance of of the request message |
| 20 | + request: RequestType, |
| 21 | + // Additional metadata to attach to the request, the same as grpc-web |
| 22 | + metadata?: Metadata.ConstructorArg, |
| 23 | + // Called immediately before the request is started, useful for toggling a loading status |
| 24 | + onStart?: () => Action | void, |
| 25 | + // Called when response headers are received |
| 26 | + onHeaders?: (headers: Metadata) => Action | void, |
| 27 | + // Called on each incoming message |
| 28 | + onMessage?: (res: ResponseType) => Action | void, |
| 29 | + // Called at the end of a request, make sure to check the exit code |
| 30 | + onEnd: (code: Code, message: string, trailers: Metadata) => Action | void, |
| 31 | +}; |
| 32 | + |
| 33 | +// Basic type for a gRPC Action |
| 34 | +export type GrpcAction<RequestType extends jspb.Message, ResponseType extends jspb.Message> = { |
| 35 | + type: typeof GRPC_WEB_REQUEST, |
| 36 | + payload: GrpcActionPayload<RequestType, ResponseType>, |
| 37 | +}; |
| 38 | + |
| 39 | +// Action creator, Use it to create a new grpc action |
| 40 | +export function grpcRequest<RequestType extends jspb.Message, ResponseType extends jspb.Message>( |
| 41 | + payload: GrpcActionPayload<RequestType, ResponseType> |
| 42 | +): GrpcAction<RequestType, ResponseType> { |
| 43 | + return { |
| 44 | + type: GRPC_WEB_REQUEST, |
| 45 | + payload, |
| 46 | + }; |
| 47 | +} |
| 48 | + |
| 49 | +/* tslint:disable:no-any*/ |
| 50 | +export function newGrpcMiddleware(): Middleware { |
| 51 | + return ({getState, dispatch}: MiddlewareAPI<{}>) => (next: Dispatch<{}>) => (action: any) => { |
| 52 | + // skip non-grpc actions |
| 53 | + if (!isGrpcWebUnaryAction(action)) { |
| 54 | + return next(action); |
| 55 | + } |
| 56 | + |
| 57 | + const payload = action.payload; |
| 58 | + |
| 59 | + if (payload.onStart) { |
| 60 | + payload.onStart(); |
| 61 | + } |
| 62 | + |
| 63 | + grpc.invoke(payload.methodDescriptor, { |
| 64 | + debug: payload.debug, |
| 65 | + host: payload.host, |
| 66 | + request: payload.request, |
| 67 | + metadata: payload.metadata, |
| 68 | + transport: payload.transport, |
| 69 | + onHeaders: headers => { |
| 70 | + if (!payload.onHeaders) { return; } |
| 71 | + const actionToDispatch = payload.onHeaders(headers); |
| 72 | + return actionToDispatch && dispatch(actionToDispatch); |
| 73 | + }, |
| 74 | + onMessage: res => { |
| 75 | + if (!payload.onMessage) { return; } |
| 76 | + const actionToDispatch = payload.onMessage(res); |
| 77 | + return actionToDispatch && dispatch(actionToDispatch); |
| 78 | + }, |
| 79 | + onEnd: (code, msg, trailers) => { |
| 80 | + const actionToDispatch = payload.onEnd(code, msg, trailers); |
| 81 | + return actionToDispatch && dispatch(actionToDispatch); |
| 82 | + }, |
| 83 | + }); |
| 84 | + |
| 85 | + return next(action); |
| 86 | + }; |
| 87 | +} |
| 88 | + |
| 89 | +function isGrpcWebUnaryAction(action: any): action is GrpcAction<jspb.Message, jspb.Message> { |
| 90 | + return action && action.type && action.type === GRPC_WEB_REQUEST && isGrpcWebPayload(action); |
| 91 | +} |
| 92 | + |
| 93 | +function isGrpcWebPayload(action: any): boolean { |
| 94 | + return action && |
| 95 | + action.payload && |
| 96 | + action.payload.methodDescriptor && |
| 97 | + action.payload.request && |
| 98 | + action.payload.onEnd && |
| 99 | + action.payload.host; |
| 100 | +} |
| 101 | + |
| 102 | +/* tslint:enable:no-any*/ |
0 commit comments