Git Product home page Git Product logo

grpc-web-hacker-news's Introduction

grpc-web-hacker-news

An example app implementing a Hacker News reader. This example aims to demonstrate usage of grpc-web(v0.5.0) with React. It additionally shows how to integrate with Redux.

Running

To start both the Go backend server and the frontend React application, run the following:

./start.sh

Screenshot

The backend server is running on http://localhost:8900 while the frontend will by default start on http://localhost:3000

Contributing

Contributions are welcome, please open an Issue or raise a PR.

Notable setup points

Disable TSLint for protobuf generated classes

Gernerated proto classes do not confirm to TS lint requirements, disable linting

{
    "linterOptions": {
        "exclude": [
            "src/proto/*"
        ]
    },
}

Configure protobuf compiler script

In this example, we're using a protoc.sh script to aid compilation

protoc \
    --go_out=plugins=grpc:./server \
    --plugin=protoc-gen-ts=./app/node_modules/.bin/protoc-gen-ts \
    --ts_out=service=true:./app/src \
    --js_out=import_style=commonjs,binary:./app/src \
    ./proto/hackernews.proto

Generated proto classes with Redux

Redux favors plain objects over object instances. This complicates usage of the generated proto classes, in this example class Story. In order to use the generated classes with redux, we must use the Story.AsObject type which is the plain object representation. For example our reducer should look like this:

export type StoryState = {
  readonly stories: { [storyId: number]: Story.AsObject },
  readonly error: Error | null,
  readonly loading: boolean,
  readonly selected: Story.AsObject | null,
};

export default function (state: StoryState = initialState, action: RootAction): StoryState {

  switch (action.type) {

    case ADD_STORY:
      const story: Story.AsObject = action.payload.toObject();
      const selected = state.selected !== null ? state.selected : story;
      if (story.id && story.id) {
        return {
          ...state,
          loading: false,
          stories: {...state.stories, [story.id]: story},
          selected,
        };
      }
      return state;

    default:
      return state;
  }

}

Note the usage of Story.AsObject rather than just Story

gRPC-Web Redux Middleware

An example redux middleware is included.

import { Action, Dispatch, Middleware, MiddlewareAPI } from 'redux';
import { Code, grpc, Metadata, Transport } from 'grpc-web-client';
import * as jspb from 'google-protobuf';

const GRPC_WEB_REQUEST = 'GRPC_WEB_REQUEST';
// const GRPC_WEB_INVOKE = 'GRPC_WEB_INVOKE';

// 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<RequestType extends jspb.Message, ResponseType extends jspb.Message> = {
  // The method descriptor to use for a gRPC request, equivalent to grpc.invoke(methodDescriptor, ...)
  methodDescriptor: grpc.MethodDefinition<RequestType, ResponseType>,
  // The transport to use for grpc-web, automatically selected if empty
  transport?: Transport,
  // 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: RequestType,
  // Additional metadata to attach to the request, the same as grpc-web
  metadata?: 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: Metadata) => Action | void,
  // Called on each incoming message
  onMessage?: (res: ResponseType) => Action | void,
  // Called at the end of a request, make sure to check the exit code
  onEnd: (code: Code, message: string, trailers: Metadata) => Action | void,
};

// Basic type for a gRPC Action
export type GrpcAction<RequestType extends jspb.Message, ResponseType extends jspb.Message> = {
  type: typeof GRPC_WEB_REQUEST,
  payload: GrpcActionPayload<RequestType, ResponseType>,
};

// Action creator, Use it to create a new grpc action
export function grpcRequest<RequestType extends jspb.Message, ResponseType extends jspb.Message>(
  payload: GrpcActionPayload<RequestType, ResponseType>
): GrpcAction<RequestType, ResponseType> {
  return {
    type: GRPC_WEB_REQUEST,
    payload,
  };
}

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,
      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) => {
        const actionToDispatch = payload.onEnd(code, msg, trailers);
        return actionToDispatch && dispatch(actionToDispatch);
      },
    });

    return next(action);
  };
}

function isGrpcWebUnaryAction(action: any): action is GrpcAction<jspb.Message, jspb.Message> {
  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 &&
    action.payload.onEnd &&
    action.payload.host;
}

grpc-web-hacker-news's People

Contributors

dependabot[bot] avatar easycz avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.