├── app ├── src │ ├── index.css │ ├── actions │ │ ├── index.ts │ │ └── stories.ts │ ├── store.ts │ ├── StoryView.tsx │ ├── proto │ │ ├── hackernews_pb_service.ts │ │ ├── hackernews_pb_service.js │ │ ├── hackernews_pb.d.ts │ │ └── hackernews_pb.js │ ├── StoryList.tsx │ ├── reducers │ │ ├── stories.ts │ │ └── stories.js │ ├── Stories.tsx │ └── middleware │ │ └── grpc.ts ├── index.tsx └── index.html ├── screenshots └── grpc-web-hacker-news.png ├── server ├── README.md ├── proxy │ └── article.go ├── middleware │ └── grpcWeb.go ├── hackernews │ ├── hackernews_service.go │ └── api.go ├── main.go └── proto │ └── hackernews.pb.go ├── start.sh ├── protoc.sh ├── .gitignore ├── .gitpod.yml ├── proto └── hackernews.proto ├── .github └── workflows │ ├── go.yml │ ├── node.js.yml │ └── codeql-analysis.yml ├── .editorconfig ├── go.mod ├── LICENSE ├── package.json ├── README.md └── go.sum /app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { StoryActionTypes } from './stories'; 2 | 3 | export type RootAction = 4 | | StoryActionTypes; 5 | -------------------------------------------------------------------------------- /screenshots/grpc-web-hacker-news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easyCZ/grpc-web-hacker-news/HEAD/screenshots/grpc-web-hacker-news.png -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | ## Example Hacker News Server 2 | The server is responsible for communication with the [Hacker News API](https://github.com/HackerNews/API) 3 | 4 | ### Running 5 | ```bash 6 | go run main.go 7 | ``` -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Installing NPM dependencies with yarn" 3 | (cd app && yarn install) 4 | 5 | echo "Running go server" 6 | (cd server && go run main.go) & 7 | echo "Running frontend application" 8 | (cd app && yarn start) & 9 | -------------------------------------------------------------------------------- /protoc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | protoc \ 3 | --go_out=plugins=grpc:./server \ 4 | --plugin=protoc-gen-ts=./app/node_modules/.bin/protoc-gen-ts \ 5 | --ts_out=service=true:./app/src \ 6 | --js_out=import_style=commonjs,binary:./app/src \ 7 | ./proto/hackernews.proto 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | .idea 17 | node_modules 18 | dist/ 19 | .parcel-cache 20 | -------------------------------------------------------------------------------- /app/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import './src/index.css'; 5 | import store from './src/store'; 6 | import Stories from './src/Stories'; 7 | 8 | ReactDOM.render( 9 | 10 |
11 | 12 |
13 |
, 14 | document.getElementById('root') as HTMLElement 15 | ); 16 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - name: Go deps 7 | init: go get && go build ./... && go test ./... 8 | command: go run 9 | 10 | - name: NPM deps 11 | init: npm install 12 | command: npm run 13 | -------------------------------------------------------------------------------- /proto/hackernews.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc_web_hacker_news; 4 | 5 | message Story { 6 | int32 id = 1; 7 | int32 score = 2; 8 | string title = 3; 9 | string by = 4; 10 | int32 time = 5; 11 | string url = 6; 12 | } 13 | 14 | service HackerNewsService { 15 | rpc ListStories (ListStoriesRequest) returns (stream ListStoriesResponse); 16 | } 17 | 18 | message ListStoriesResponse { 19 | Story story = 1; 20 | } 21 | message ListStoriesRequest {} -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.sh] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | 12 | [*.{js,ts,tsx}] 13 | indent_style = space 14 | indent_size = 2 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | end_of_line = lf 19 | 20 | # Override for Makefile 21 | [{Makefile, makefile, GNUmakefile}] 22 | indent_style = tab 23 | indent_size = 4 -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HackerNews with grpc-web 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/store.ts: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, combineReducers, createStore } from 'redux'; 2 | import stories, { StoryState } from './reducers/stories'; 3 | import { newGrpcMiddleware } from './middleware/grpc'; 4 | 5 | interface StoreEnhancerState { 6 | } 7 | 8 | export interface RootState extends StoreEnhancerState { 9 | stories: StoryState; 10 | } 11 | 12 | const reducers = combineReducers({ 13 | stories, 14 | }); 15 | 16 | export default createStore( 17 | reducers, 18 | applyMiddleware( 19 | newGrpcMiddleware(), 20 | ) 21 | ); 22 | -------------------------------------------------------------------------------- /app/src/StoryView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Story } from './proto/hackernews_pb'; 3 | 4 | type StoryViewProps = { 5 | story: Story.AsObject, 6 | }; 7 | 8 | const StoryView: React.SFC = (props) => { 9 | const url = `http://localhost:8900/article-proxy?q=${encodeURIComponent(props.story.url)}`; 10 | return ( 11 |