├── .gitignore
├── .vscode
└── settings.json
├── DockerfileClient
├── DockerfileEnvoy
├── DockerfileProto
├── DockerfileServer
├── LICENSE
├── README.md
├── client
├── .eslintignore
├── .eslintrc.js
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── manifest.json
│ └── robots.txt
├── scripts
│ └── client.sh
├── src
│ ├── App.tsx
│ ├── components
│ │ ├── MessageForm.tsx
│ │ └── Messages.tsx
│ ├── containers
│ │ └── Messages
│ │ │ ├── hooks
│ │ │ ├── useMessageForm.ts
│ │ │ └── useMessages.ts
│ │ │ └── index.tsx
│ ├── gRPCClients.ts
│ ├── index.tsx
│ ├── messenger
│ │ ├── MessengerServiceClientPb.ts
│ │ ├── messenger_pb.d.ts
│ │ └── messenger_pb.js
│ ├── react-app-env.d.ts
│ ├── serviceWorker.ts
│ └── setupTests.ts
├── tsconfig.json
└── yarn.lock
├── docker-compose.yml
├── envoy.yaml
├── proto
├── messenger.proto
└── scripts
│ └── protoc.sh
├── server
├── go.mod
├── go.sum
├── main.go
├── messenger
│ └── messenger.pb.go
└── scripts
│ └── server.sh
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # production
107 | client/build
108 |
109 | # misc
110 | .DS_Store
111 | .env.local
112 | .env.development.local
113 | .env.test.local
114 | .env.production.local
115 |
116 | server/tmp
117 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": ["./client"],
3 | "editor.formatOnSave": true,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/DockerfileClient:
--------------------------------------------------------------------------------
1 | FROM node:12.16.1-alpine3.11
2 |
3 | WORKDIR /client
4 | RUN yarn
5 |
--------------------------------------------------------------------------------
/DockerfileEnvoy:
--------------------------------------------------------------------------------
1 | FROM envoyproxy/envoy:latest
2 | COPY ./envoy.yaml /etc/envoy/envoy.yaml
3 | CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
4 | EXPOSE 8080
5 |
--------------------------------------------------------------------------------
/DockerfileProto:
--------------------------------------------------------------------------------
1 | FROM golang:1.14.0
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 |
5 | ARG PROTO_VERSION=3.11.4
6 | ARG GRPCWEB_VERSION=1.0.7
7 |
8 | WORKDIR /proto
9 |
10 | RUN apt-get -qq update && apt-get -qq install -y \
11 | unzip
12 |
13 | RUN curl -sSL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTO_VERSION}/\
14 | protoc-${PROTO_VERSION}-linux-x86_64.zip -o protoc.zip && \
15 | unzip -qq protoc.zip && \
16 | cp ./bin/protoc /usr/local/bin/protoc && \
17 | cp -r ./include /usr/local
18 |
19 | RUN curl -sSL https://github.com/grpc/grpc-web/releases/download/${GRPCWEB_VERSION}/\
20 | protoc-gen-grpc-web-${GRPCWEB_VERSION}-linux-x86_64 -o /usr/local/bin/protoc-gen-grpc-web && \
21 | chmod +x /usr/local/bin/protoc-gen-grpc-web
22 |
23 | RUN go get -u github.com/golang/protobuf/protoc-gen-go
24 |
--------------------------------------------------------------------------------
/DockerfileServer:
--------------------------------------------------------------------------------
1 | FROM golang:1.14.0-alpine3.11
2 |
3 | ENV GO111MODULE=on
4 |
5 | WORKDIR /go/src/grpc-web-react-hooks
6 |
7 | RUN apk add --no-cache --update \
8 | git
9 |
10 | RUN go get github.com/pilu/fresh
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 okmttdhr
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # grpc-web-react-hooks
3 |
4 | Example of gRPC-Web + React Hooks + Go ✋
5 |
6 | Just run;
7 |
8 | ```sh
9 | docker-compose up
10 | ```
11 |
--------------------------------------------------------------------------------
/client/.eslintignore:
--------------------------------------------------------------------------------
1 | src/messenger*
2 |
--------------------------------------------------------------------------------
/client/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["react-app"]
3 | };
4 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^4.2.4",
7 | "@testing-library/react": "^9.3.2",
8 | "@testing-library/user-event": "^7.1.2",
9 | "@types/jest": "^24.0.0",
10 | "@types/node": "^12.0.0",
11 | "@types/react": "^16.9.0",
12 | "@types/react-dom": "^16.9.0",
13 | "google-protobuf": "^3.11.4",
14 | "grpc-web": "^1.0.7",
15 | "react": "^16.13.0",
16 | "react-dom": "^16.13.0",
17 | "react-scripts": "3.4.0",
18 | "typescript": "~3.7.2"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "lint": "eslint --ext js,ts,tsx src",
25 | "eject": "react-scripts eject"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | },
39 | "devDependencies": {
40 | "@types/google-protobuf": "^3.7.2"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/okmttdhr/grpc-web-react-hooks/8fcf01e1785522146216b3013ca005bf48f13594/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
26 | React App
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/scripts/client.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -xe
4 |
5 | yarn
6 | yarn start
7 |
--------------------------------------------------------------------------------
/client/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { MessagesContainer } from "containers/Messages";
3 | import { gRPCClients } from "gRPCClients";
4 |
5 | export const App = () => {
6 | return (
7 | <>
8 |
9 | >
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/client/src/components/MessageForm.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useMessageForm } from "containers/Messages/hooks/useMessageForm";
3 |
4 | type Props = ReturnType;
5 |
6 | export const MessageForm: React.FC = ({
7 | message,
8 | onChange,
9 | onSubmit
10 | }) => {
11 | return (
12 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/client/src/components/Messages.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | messages: string[];
5 | };
6 |
7 | export const Messages: React.FC = ({ messages }) => {
8 | return (
9 |
10 | {messages.map(m => (
11 |
{m}
12 | ))}
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/client/src/containers/Messages/hooks/useMessageForm.ts:
--------------------------------------------------------------------------------
1 | import { MessageRequest } from "messenger/messenger_pb";
2 | import { useState, useCallback, SyntheticEvent } from "react";
3 | import { MessengerClient } from "messenger/MessengerServiceClientPb";
4 |
5 | export const useMessageForm = (client: MessengerClient) => {
6 | const [message, setMessage] = useState("");
7 |
8 | const onChange = useCallback(
9 | (event: SyntheticEvent) => {
10 | const target = event.target as HTMLInputElement;
11 | setMessage(target.value);
12 | },
13 | [setMessage]
14 | );
15 |
16 | const onSubmit = useCallback(
17 | (event: SyntheticEvent) => {
18 | event.preventDefault();
19 | const req = new MessageRequest();
20 | req.setMessage(message);
21 | client.createMessage(req, null, res => console.log(res));
22 | setMessage("");
23 | },
24 | [client, message]
25 | );
26 |
27 | return {
28 | message,
29 | onChange,
30 | onSubmit
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/client/src/containers/Messages/hooks/useMessages.ts:
--------------------------------------------------------------------------------
1 | import { Empty } from "google-protobuf/google/protobuf/empty_pb";
2 | import { useState, useEffect } from "react";
3 | import { MessengerClient } from "messenger/MessengerServiceClientPb";
4 |
5 | export const useMessages = (client: MessengerClient) => {
6 | const [messages, setMessages] = useState([]);
7 |
8 | useEffect(() => {
9 | const stream$ = client.getMessages(new Empty());
10 | stream$.on("data", m => {
11 | setMessages(state => [...state, m.getMessage()]);
12 | });
13 | }, [client]);
14 |
15 | return {
16 | messages
17 | };
18 | };
19 |
--------------------------------------------------------------------------------
/client/src/containers/Messages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Messages } from "components/Messages";
3 | import { MessageForm } from "components/MessageForm";
4 | import { GRPCClients } from "gRPCClients";
5 | import { useMessages } from "./hooks/useMessages";
6 | import { useMessageForm } from "./hooks/useMessageForm";
7 |
8 | type Props = {
9 | clients: GRPCClients;
10 | };
11 |
12 | export const MessagesContainer: React.FC = ({ clients }) => {
13 | const messengerClient = clients.messengerClient;
14 | const messagesState = useMessages(messengerClient);
15 | const messageFormState = useMessageForm(messengerClient);
16 | return (
17 |
18 |
19 |
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/client/src/gRPCClients.ts:
--------------------------------------------------------------------------------
1 | import { MessengerClient } from "messenger/MessengerServiceClientPb";
2 |
3 | export type GRPCClients = {
4 | messengerClient: MessengerClient;
5 | };
6 |
7 | export const gRPCClients = {
8 | messengerClient: new MessengerClient(`http://localhost:8080`)
9 | };
10 |
--------------------------------------------------------------------------------
/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { App } from "./App";
4 | import * as serviceWorker from "./serviceWorker";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/client/src/messenger/MessengerServiceClientPb.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview gRPC-Web generated client stub for messenger
3 | * @enhanceable
4 | * @public
5 | */
6 |
7 | // GENERATED CODE -- DO NOT EDIT!
8 |
9 |
10 | import * as grpcWeb from 'grpc-web';
11 |
12 | import * as google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb';
13 |
14 | import {
15 | MessageRequest,
16 | MessageResponse} from './messenger_pb';
17 |
18 | export class MessengerClient {
19 | client_: grpcWeb.AbstractClientBase;
20 | hostname_: string;
21 | credentials_: null | { [index: string]: string; };
22 | options_: null | { [index: string]: string; };
23 |
24 | constructor (hostname: string,
25 | credentials?: null | { [index: string]: string; },
26 | options?: null | { [index: string]: string; }) {
27 | if (!options) options = {};
28 | if (!credentials) credentials = {};
29 | options['format'] = 'text';
30 |
31 | this.client_ = new grpcWeb.GrpcWebClientBase(options);
32 | this.hostname_ = hostname;
33 | this.credentials_ = credentials;
34 | this.options_ = options;
35 | }
36 |
37 | methodInfoGetMessages = new grpcWeb.AbstractClientBase.MethodInfo(
38 | MessageResponse,
39 | (request: google_protobuf_empty_pb.Empty) => {
40 | return request.serializeBinary();
41 | },
42 | MessageResponse.deserializeBinary
43 | );
44 |
45 | getMessages(
46 | request: google_protobuf_empty_pb.Empty,
47 | metadata?: grpcWeb.Metadata) {
48 | return this.client_.serverStreaming(
49 | this.hostname_ +
50 | '/messenger.Messenger/GetMessages',
51 | request,
52 | metadata || {},
53 | this.methodInfoGetMessages);
54 | }
55 |
56 | methodInfoCreateMessage = new grpcWeb.AbstractClientBase.MethodInfo(
57 | MessageResponse,
58 | (request: MessageRequest) => {
59 | return request.serializeBinary();
60 | },
61 | MessageResponse.deserializeBinary
62 | );
63 |
64 | createMessage(
65 | request: MessageRequest,
66 | metadata: grpcWeb.Metadata | null,
67 | callback: (err: grpcWeb.Error,
68 | response: MessageResponse) => void) {
69 | return this.client_.rpcCall(
70 | this.hostname_ +
71 | '/messenger.Messenger/CreateMessage',
72 | request,
73 | metadata || {},
74 | this.methodInfoCreateMessage,
75 | callback);
76 | }
77 |
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/client/src/messenger/messenger_pb.d.ts:
--------------------------------------------------------------------------------
1 | import * as jspb from "google-protobuf"
2 |
3 | import * as google_protobuf_empty_pb from 'google-protobuf/google/protobuf/empty_pb';
4 |
5 | export class MessageRequest extends jspb.Message {
6 | getMessage(): string;
7 | setMessage(value: string): void;
8 |
9 | serializeBinary(): Uint8Array;
10 | toObject(includeInstance?: boolean): MessageRequest.AsObject;
11 | static toObject(includeInstance: boolean, msg: MessageRequest): MessageRequest.AsObject;
12 | static serializeBinaryToWriter(message: MessageRequest, writer: jspb.BinaryWriter): void;
13 | static deserializeBinary(bytes: Uint8Array): MessageRequest;
14 | static deserializeBinaryFromReader(message: MessageRequest, reader: jspb.BinaryReader): MessageRequest;
15 | }
16 |
17 | export namespace MessageRequest {
18 | export type AsObject = {
19 | message: string,
20 | }
21 | }
22 |
23 | export class MessageResponse extends jspb.Message {
24 | getMessage(): string;
25 | setMessage(value: string): void;
26 |
27 | serializeBinary(): Uint8Array;
28 | toObject(includeInstance?: boolean): MessageResponse.AsObject;
29 | static toObject(includeInstance: boolean, msg: MessageResponse): MessageResponse.AsObject;
30 | static serializeBinaryToWriter(message: MessageResponse, writer: jspb.BinaryWriter): void;
31 | static deserializeBinary(bytes: Uint8Array): MessageResponse;
32 | static deserializeBinaryFromReader(message: MessageResponse, reader: jspb.BinaryReader): MessageResponse;
33 | }
34 |
35 | export namespace MessageResponse {
36 | export type AsObject = {
37 | message: string,
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/client/src/messenger/messenger_pb.js:
--------------------------------------------------------------------------------
1 | // source: messenger.proto
2 | /**
3 | * @fileoverview
4 | * @enhanceable
5 | * @suppress {messageConventions} JS Compiler reports an error if a variable or
6 | * field starts with 'MSG_' and isn't a translatable message.
7 | * @public
8 | */
9 | // GENERATED CODE -- DO NOT EDIT!
10 |
11 | var jspb = require('google-protobuf');
12 | var goog = jspb;
13 | var global = Function('return this')();
14 |
15 | var google_protobuf_empty_pb = require('google-protobuf/google/protobuf/empty_pb.js');
16 | goog.object.extend(proto, google_protobuf_empty_pb);
17 | goog.exportSymbol('proto.messenger.MessageRequest', null, global);
18 | goog.exportSymbol('proto.messenger.MessageResponse', null, global);
19 | /**
20 | * Generated by JsPbCodeGenerator.
21 | * @param {Array=} opt_data Optional initial data array, typically from a
22 | * server response, or constructed directly in Javascript. The array is used
23 | * in place and becomes part of the constructed object. It is not cloned.
24 | * If no data is provided, the constructed object will be empty, but still
25 | * valid.
26 | * @extends {jspb.Message}
27 | * @constructor
28 | */
29 | proto.messenger.MessageRequest = function(opt_data) {
30 | jspb.Message.initialize(this, opt_data, 0, -1, null, null);
31 | };
32 | goog.inherits(proto.messenger.MessageRequest, jspb.Message);
33 | if (goog.DEBUG && !COMPILED) {
34 | /**
35 | * @public
36 | * @override
37 | */
38 | proto.messenger.MessageRequest.displayName = 'proto.messenger.MessageRequest';
39 | }
40 | /**
41 | * Generated by JsPbCodeGenerator.
42 | * @param {Array=} opt_data Optional initial data array, typically from a
43 | * server response, or constructed directly in Javascript. The array is used
44 | * in place and becomes part of the constructed object. It is not cloned.
45 | * If no data is provided, the constructed object will be empty, but still
46 | * valid.
47 | * @extends {jspb.Message}
48 | * @constructor
49 | */
50 | proto.messenger.MessageResponse = function(opt_data) {
51 | jspb.Message.initialize(this, opt_data, 0, -1, null, null);
52 | };
53 | goog.inherits(proto.messenger.MessageResponse, jspb.Message);
54 | if (goog.DEBUG && !COMPILED) {
55 | /**
56 | * @public
57 | * @override
58 | */
59 | proto.messenger.MessageResponse.displayName = 'proto.messenger.MessageResponse';
60 | }
61 |
62 |
63 |
64 | if (jspb.Message.GENERATE_TO_OBJECT) {
65 | /**
66 | * Creates an object representation of this proto.
67 | * Field names that are reserved in JavaScript and will be renamed to pb_name.
68 | * Optional fields that are not set will be set to undefined.
69 | * To access a reserved field use, foo.pb_, eg, foo.pb_default.
70 | * For the list of reserved names please see:
71 | * net/proto2/compiler/js/internal/generator.cc#kKeyword.
72 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the
73 | * JSPB instance for transitional soy proto support:
74 | * http://goto/soy-param-migration
75 | * @return {!Object}
76 | */
77 | proto.messenger.MessageRequest.prototype.toObject = function(opt_includeInstance) {
78 | return proto.messenger.MessageRequest.toObject(opt_includeInstance, this);
79 | };
80 |
81 |
82 | /**
83 | * Static version of the {@see toObject} method.
84 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include
85 | * the JSPB instance for transitional soy proto support:
86 | * http://goto/soy-param-migration
87 | * @param {!proto.messenger.MessageRequest} msg The msg instance to transform.
88 | * @return {!Object}
89 | * @suppress {unusedLocalVariables} f is only used for nested messages
90 | */
91 | proto.messenger.MessageRequest.toObject = function(includeInstance, msg) {
92 | var f, obj = {
93 | message: jspb.Message.getFieldWithDefault(msg, 1, "")
94 | };
95 |
96 | if (includeInstance) {
97 | obj.$jspbMessageInstance = msg;
98 | }
99 | return obj;
100 | };
101 | }
102 |
103 |
104 | /**
105 | * Deserializes binary data (in protobuf wire format).
106 | * @param {jspb.ByteSource} bytes The bytes to deserialize.
107 | * @return {!proto.messenger.MessageRequest}
108 | */
109 | proto.messenger.MessageRequest.deserializeBinary = function(bytes) {
110 | var reader = new jspb.BinaryReader(bytes);
111 | var msg = new proto.messenger.MessageRequest;
112 | return proto.messenger.MessageRequest.deserializeBinaryFromReader(msg, reader);
113 | };
114 |
115 |
116 | /**
117 | * Deserializes binary data (in protobuf wire format) from the
118 | * given reader into the given message object.
119 | * @param {!proto.messenger.MessageRequest} msg The message object to deserialize into.
120 | * @param {!jspb.BinaryReader} reader The BinaryReader to use.
121 | * @return {!proto.messenger.MessageRequest}
122 | */
123 | proto.messenger.MessageRequest.deserializeBinaryFromReader = function(msg, reader) {
124 | while (reader.nextField()) {
125 | if (reader.isEndGroup()) {
126 | break;
127 | }
128 | var field = reader.getFieldNumber();
129 | switch (field) {
130 | case 1:
131 | var value = /** @type {string} */ (reader.readString());
132 | msg.setMessage(value);
133 | break;
134 | default:
135 | reader.skipField();
136 | break;
137 | }
138 | }
139 | return msg;
140 | };
141 |
142 |
143 | /**
144 | * Serializes the message to binary data (in protobuf wire format).
145 | * @return {!Uint8Array}
146 | */
147 | proto.messenger.MessageRequest.prototype.serializeBinary = function() {
148 | var writer = new jspb.BinaryWriter();
149 | proto.messenger.MessageRequest.serializeBinaryToWriter(this, writer);
150 | return writer.getResultBuffer();
151 | };
152 |
153 |
154 | /**
155 | * Serializes the given message to binary data (in protobuf wire
156 | * format), writing to the given BinaryWriter.
157 | * @param {!proto.messenger.MessageRequest} message
158 | * @param {!jspb.BinaryWriter} writer
159 | * @suppress {unusedLocalVariables} f is only used for nested messages
160 | */
161 | proto.messenger.MessageRequest.serializeBinaryToWriter = function(message, writer) {
162 | var f = undefined;
163 | f = message.getMessage();
164 | if (f.length > 0) {
165 | writer.writeString(
166 | 1,
167 | f
168 | );
169 | }
170 | };
171 |
172 |
173 | /**
174 | * optional string message = 1;
175 | * @return {string}
176 | */
177 | proto.messenger.MessageRequest.prototype.getMessage = function() {
178 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
179 | };
180 |
181 |
182 | /**
183 | * @param {string} value
184 | * @return {!proto.messenger.MessageRequest} returns this
185 | */
186 | proto.messenger.MessageRequest.prototype.setMessage = function(value) {
187 | return jspb.Message.setProto3StringField(this, 1, value);
188 | };
189 |
190 |
191 |
192 |
193 |
194 | if (jspb.Message.GENERATE_TO_OBJECT) {
195 | /**
196 | * Creates an object representation of this proto.
197 | * Field names that are reserved in JavaScript and will be renamed to pb_name.
198 | * Optional fields that are not set will be set to undefined.
199 | * To access a reserved field use, foo.pb_, eg, foo.pb_default.
200 | * For the list of reserved names please see:
201 | * net/proto2/compiler/js/internal/generator.cc#kKeyword.
202 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the
203 | * JSPB instance for transitional soy proto support:
204 | * http://goto/soy-param-migration
205 | * @return {!Object}
206 | */
207 | proto.messenger.MessageResponse.prototype.toObject = function(opt_includeInstance) {
208 | return proto.messenger.MessageResponse.toObject(opt_includeInstance, this);
209 | };
210 |
211 |
212 | /**
213 | * Static version of the {@see toObject} method.
214 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include
215 | * the JSPB instance for transitional soy proto support:
216 | * http://goto/soy-param-migration
217 | * @param {!proto.messenger.MessageResponse} msg The msg instance to transform.
218 | * @return {!Object}
219 | * @suppress {unusedLocalVariables} f is only used for nested messages
220 | */
221 | proto.messenger.MessageResponse.toObject = function(includeInstance, msg) {
222 | var f, obj = {
223 | message: jspb.Message.getFieldWithDefault(msg, 1, "")
224 | };
225 |
226 | if (includeInstance) {
227 | obj.$jspbMessageInstance = msg;
228 | }
229 | return obj;
230 | };
231 | }
232 |
233 |
234 | /**
235 | * Deserializes binary data (in protobuf wire format).
236 | * @param {jspb.ByteSource} bytes The bytes to deserialize.
237 | * @return {!proto.messenger.MessageResponse}
238 | */
239 | proto.messenger.MessageResponse.deserializeBinary = function(bytes) {
240 | var reader = new jspb.BinaryReader(bytes);
241 | var msg = new proto.messenger.MessageResponse;
242 | return proto.messenger.MessageResponse.deserializeBinaryFromReader(msg, reader);
243 | };
244 |
245 |
246 | /**
247 | * Deserializes binary data (in protobuf wire format) from the
248 | * given reader into the given message object.
249 | * @param {!proto.messenger.MessageResponse} msg The message object to deserialize into.
250 | * @param {!jspb.BinaryReader} reader The BinaryReader to use.
251 | * @return {!proto.messenger.MessageResponse}
252 | */
253 | proto.messenger.MessageResponse.deserializeBinaryFromReader = function(msg, reader) {
254 | while (reader.nextField()) {
255 | if (reader.isEndGroup()) {
256 | break;
257 | }
258 | var field = reader.getFieldNumber();
259 | switch (field) {
260 | case 1:
261 | var value = /** @type {string} */ (reader.readString());
262 | msg.setMessage(value);
263 | break;
264 | default:
265 | reader.skipField();
266 | break;
267 | }
268 | }
269 | return msg;
270 | };
271 |
272 |
273 | /**
274 | * Serializes the message to binary data (in protobuf wire format).
275 | * @return {!Uint8Array}
276 | */
277 | proto.messenger.MessageResponse.prototype.serializeBinary = function() {
278 | var writer = new jspb.BinaryWriter();
279 | proto.messenger.MessageResponse.serializeBinaryToWriter(this, writer);
280 | return writer.getResultBuffer();
281 | };
282 |
283 |
284 | /**
285 | * Serializes the given message to binary data (in protobuf wire
286 | * format), writing to the given BinaryWriter.
287 | * @param {!proto.messenger.MessageResponse} message
288 | * @param {!jspb.BinaryWriter} writer
289 | * @suppress {unusedLocalVariables} f is only used for nested messages
290 | */
291 | proto.messenger.MessageResponse.serializeBinaryToWriter = function(message, writer) {
292 | var f = undefined;
293 | f = message.getMessage();
294 | if (f.length > 0) {
295 | writer.writeString(
296 | 1,
297 | f
298 | );
299 | }
300 | };
301 |
302 |
303 | /**
304 | * optional string message = 1;
305 | * @return {string}
306 | */
307 | proto.messenger.MessageResponse.prototype.getMessage = function() {
308 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
309 | };
310 |
311 |
312 | /**
313 | * @param {string} value
314 | * @return {!proto.messenger.MessageResponse} returns this
315 | */
316 | proto.messenger.MessageResponse.prototype.setMessage = function(value) {
317 | return jspb.Message.setProto3StringField(this, 1, value);
318 | };
319 |
320 |
321 | goog.object.extend(exports, proto.messenger);
322 |
--------------------------------------------------------------------------------
/client/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/client/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/client/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "react",
17 | "baseUrl": "./src"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | proto:
4 | command: ./proto/scripts/protoc.sh
5 | build:
6 | context: .
7 | dockerfile: DockerfileProto
8 | volumes:
9 | - .:/proto
10 | server:
11 | command: ./scripts/server.sh
12 | build:
13 | context: .
14 | dockerfile: DockerfileServer
15 | volumes:
16 | - ./server:/go/src/grpc-web-react-hooks
17 | ports:
18 | - "9090:9090"
19 | depends_on:
20 | - proto
21 | envoy:
22 | build:
23 | context: .
24 | dockerfile: DockerfileEnvoy
25 | ports:
26 | - "8080:8080"
27 | links:
28 | - server
29 | client:
30 | command: ./scripts/client.sh
31 | build:
32 | context: .
33 | dockerfile: DockerfileClient
34 | volumes:
35 | - ./client:/client
36 | - /client/node_modules
37 | depends_on:
38 | - proto
39 | ports:
40 | - "3000:3000"
41 | environment:
42 | - EXTEND_ESLINT=true
43 |
--------------------------------------------------------------------------------
/envoy.yaml:
--------------------------------------------------------------------------------
1 | admin:
2 | access_log_path: /tmp/admin_access.log
3 | address:
4 | socket_address: { address: 0.0.0.0, port_value: 9901 }
5 | static_resources:
6 | listeners:
7 | - name: listener_0
8 | address:
9 | socket_address: { address: 0.0.0.0, port_value: 8080 }
10 | filter_chains:
11 | - filters:
12 | - name: envoy.http_connection_manager
13 | config:
14 | codec_type: auto
15 | stat_prefix: ingress_http
16 | route_config:
17 | name: local_route
18 | virtual_hosts:
19 | - name: local_service
20 | domains: ["*"]
21 | routes:
22 | - match: { prefix: "/" }
23 | route:
24 | cluster: message_service
25 | max_grpc_timeout: 0s
26 | cors:
27 | allow_origin_string_match:
28 | - prefix: "*"
29 | allow_methods: GET, PUT, DELETE, POST, OPTIONS
30 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
31 | max_age: "1728000"
32 | expose_headers: custom-header-1,grpc-status,grpc-message
33 | http_filters:
34 | - name: envoy.grpc_web
35 | - name: envoy.cors
36 | - name: envoy.router
37 | clusters:
38 | - name: message_service
39 | connect_timeout: 0.25s
40 | type: logical_dns
41 | http2_protocol_options: {}
42 | lb_policy: round_robin
43 | hosts: [{ socket_address: { address: host.docker.internal, port_value: 9090 }}]
44 |
--------------------------------------------------------------------------------
/proto/messenger.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | import "google/protobuf/empty.proto";
4 |
5 | package messenger;
6 |
7 | service Messenger {
8 | rpc GetMessages (google.protobuf.Empty) returns (stream MessageResponse) {}
9 | rpc CreateMessage (MessageRequest) returns (MessageResponse) {}
10 | }
11 |
12 | message MessageRequest {
13 | string message = 1;
14 | }
15 |
16 | message MessageResponse {
17 | string message = 1;
18 | }
19 |
--------------------------------------------------------------------------------
/proto/scripts/protoc.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -xe
4 |
5 | SERVER_OUTPUT_DIR=server/messenger
6 | CLIENT_OUTPUT_DIR=client/src/messenger
7 |
8 | protoc --version
9 | protoc --proto_path=proto messenger.proto \
10 | --go_out=plugins="grpc:${SERVER_OUTPUT_DIR}" \
11 | --js_out=import_style=commonjs:${CLIENT_OUTPUT_DIR} \
12 | --grpc-web_out=import_style=typescript,mode=grpcwebtext:${CLIENT_OUTPUT_DIR}
13 |
--------------------------------------------------------------------------------
/server/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/okmttdhr/grpc-web-react-hooks
2 |
3 | go 1.14
4 |
5 | require (
6 | github.com/golang/protobuf v1.3.3
7 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
8 | google.golang.org/grpc v1.27.1
9 | )
10 |
--------------------------------------------------------------------------------
/server/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
5 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
6 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
9 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
11 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
12 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
14 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
15 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
16 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
17 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
18 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
19 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
20 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
21 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
22 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
23 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
24 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
25 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
26 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
27 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
28 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
29 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
30 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
31 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
32 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
33 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
34 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
35 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
36 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
38 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
39 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
40 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
41 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
42 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
43 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
44 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
45 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
46 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
47 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
48 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
49 | google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
50 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
51 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
52 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
53 |
--------------------------------------------------------------------------------
/server/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "net"
7 | "time"
8 |
9 | "github.com/golang/protobuf/ptypes/empty"
10 | pb "github.com/okmttdhr/grpc-web-react-hooks/messenger"
11 |
12 | "google.golang.org/grpc"
13 | "google.golang.org/grpc/reflection"
14 | )
15 |
16 | const (
17 | port = ":9090"
18 | )
19 |
20 | type server struct {
21 | pb.UnimplementedMessengerServer
22 | requests []*pb.MessageRequest
23 | }
24 |
25 | func (s *server) GetMessages(_ *empty.Empty, stream pb.Messenger_GetMessagesServer) error {
26 | for _, r := range s.requests {
27 | if err := stream.Send(&pb.MessageResponse{Message: r.GetMessage()}); err != nil {
28 | return err
29 | }
30 | }
31 |
32 | previousCount := len(s.requests)
33 |
34 | for {
35 | currentCount := len(s.requests)
36 | if previousCount < currentCount && currentCount > 0 {
37 | r := s.requests[currentCount-1]
38 | log.Printf("Sent: %v", r.GetMessage())
39 | if err := stream.Send(&pb.MessageResponse{Message: r.GetMessage()}); err != nil {
40 | return err
41 | }
42 | }
43 | previousCount = currentCount
44 | }
45 | }
46 |
47 | func (s *server) CreateMessage(ctx context.Context, r *pb.MessageRequest) (*pb.MessageResponse, error) {
48 | log.Printf("Received: %v", r.GetMessage())
49 | newR := &pb.MessageRequest{Message: r.GetMessage() + ": " + time.Now().Format("2006-01-02 15:04:05")}
50 | s.requests = append(s.requests, newR)
51 | return &pb.MessageResponse{Message: r.GetMessage()}, nil
52 | }
53 |
54 | func main() {
55 | lis, err := net.Listen("tcp", port)
56 | if err != nil {
57 | log.Fatalf("failed to listen: %v", err)
58 | }
59 | s := grpc.NewServer()
60 | pb.RegisterMessengerServer(s, &server{})
61 | reflection.Register(s)
62 | if err := s.Serve(lis); err != nil {
63 | log.Fatalf("failed to serve: %v", err)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/server/messenger/messenger.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: messenger.proto
3 |
4 | package messenger
5 |
6 | import (
7 | context "context"
8 | fmt "fmt"
9 | proto "github.com/golang/protobuf/proto"
10 | empty "github.com/golang/protobuf/ptypes/empty"
11 | grpc "google.golang.org/grpc"
12 | codes "google.golang.org/grpc/codes"
13 | status "google.golang.org/grpc/status"
14 | math "math"
15 | )
16 |
17 | // Reference imports to suppress errors if they are not otherwise used.
18 | var _ = proto.Marshal
19 | var _ = fmt.Errorf
20 | var _ = math.Inf
21 |
22 | // This is a compile-time assertion to ensure that this generated file
23 | // is compatible with the proto package it is being compiled against.
24 | // A compilation error at this line likely means your copy of the
25 | // proto package needs to be updated.
26 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
27 |
28 | type MessageRequest struct {
29 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
30 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
31 | XXX_unrecognized []byte `json:"-"`
32 | XXX_sizecache int32 `json:"-"`
33 | }
34 |
35 | func (m *MessageRequest) Reset() { *m = MessageRequest{} }
36 | func (m *MessageRequest) String() string { return proto.CompactTextString(m) }
37 | func (*MessageRequest) ProtoMessage() {}
38 | func (*MessageRequest) Descriptor() ([]byte, []int) {
39 | return fileDescriptor_b99aba0cbf4e4b91, []int{0}
40 | }
41 |
42 | func (m *MessageRequest) XXX_Unmarshal(b []byte) error {
43 | return xxx_messageInfo_MessageRequest.Unmarshal(m, b)
44 | }
45 | func (m *MessageRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
46 | return xxx_messageInfo_MessageRequest.Marshal(b, m, deterministic)
47 | }
48 | func (m *MessageRequest) XXX_Merge(src proto.Message) {
49 | xxx_messageInfo_MessageRequest.Merge(m, src)
50 | }
51 | func (m *MessageRequest) XXX_Size() int {
52 | return xxx_messageInfo_MessageRequest.Size(m)
53 | }
54 | func (m *MessageRequest) XXX_DiscardUnknown() {
55 | xxx_messageInfo_MessageRequest.DiscardUnknown(m)
56 | }
57 |
58 | var xxx_messageInfo_MessageRequest proto.InternalMessageInfo
59 |
60 | func (m *MessageRequest) GetMessage() string {
61 | if m != nil {
62 | return m.Message
63 | }
64 | return ""
65 | }
66 |
67 | type MessageResponse struct {
68 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
69 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
70 | XXX_unrecognized []byte `json:"-"`
71 | XXX_sizecache int32 `json:"-"`
72 | }
73 |
74 | func (m *MessageResponse) Reset() { *m = MessageResponse{} }
75 | func (m *MessageResponse) String() string { return proto.CompactTextString(m) }
76 | func (*MessageResponse) ProtoMessage() {}
77 | func (*MessageResponse) Descriptor() ([]byte, []int) {
78 | return fileDescriptor_b99aba0cbf4e4b91, []int{1}
79 | }
80 |
81 | func (m *MessageResponse) XXX_Unmarshal(b []byte) error {
82 | return xxx_messageInfo_MessageResponse.Unmarshal(m, b)
83 | }
84 | func (m *MessageResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
85 | return xxx_messageInfo_MessageResponse.Marshal(b, m, deterministic)
86 | }
87 | func (m *MessageResponse) XXX_Merge(src proto.Message) {
88 | xxx_messageInfo_MessageResponse.Merge(m, src)
89 | }
90 | func (m *MessageResponse) XXX_Size() int {
91 | return xxx_messageInfo_MessageResponse.Size(m)
92 | }
93 | func (m *MessageResponse) XXX_DiscardUnknown() {
94 | xxx_messageInfo_MessageResponse.DiscardUnknown(m)
95 | }
96 |
97 | var xxx_messageInfo_MessageResponse proto.InternalMessageInfo
98 |
99 | func (m *MessageResponse) GetMessage() string {
100 | if m != nil {
101 | return m.Message
102 | }
103 | return ""
104 | }
105 |
106 | func init() {
107 | proto.RegisterType((*MessageRequest)(nil), "messenger.MessageRequest")
108 | proto.RegisterType((*MessageResponse)(nil), "messenger.MessageResponse")
109 | }
110 |
111 | func init() {
112 | proto.RegisterFile("messenger.proto", fileDescriptor_b99aba0cbf4e4b91)
113 | }
114 |
115 | var fileDescriptor_b99aba0cbf4e4b91 = []byte{
116 | // 183 bytes of a gzipped FileDescriptorProto
117 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xcf, 0x4d, 0x2d, 0x2e,
118 | 0x4e, 0xcd, 0x4b, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x48,
119 | 0x49, 0xa7, 0xe7, 0xe7, 0xa7, 0xe7, 0xa4, 0xea, 0x83, 0x25, 0x92, 0x4a, 0xd3, 0xf4, 0x53, 0x73,
120 | 0x0b, 0x4a, 0x2a, 0x21, 0xea, 0x94, 0xb4, 0xb8, 0xf8, 0x7c, 0x53, 0x8b, 0x8b, 0x13, 0xd3, 0x53,
121 | 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84, 0x24, 0xb8, 0xd8, 0x73, 0x21, 0x22, 0x12, 0x8c,
122 | 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x92, 0x36, 0x17, 0x3f, 0x5c, 0x6d, 0x71, 0x41, 0x7e,
123 | 0x5e, 0x71, 0x2a, 0x6e, 0xc5, 0x46, 0x73, 0x18, 0xb9, 0x38, 0x7d, 0x61, 0x6e, 0x10, 0x72, 0xe5,
124 | 0xe2, 0x76, 0x4f, 0x2d, 0x81, 0xea, 0x2e, 0x16, 0x12, 0xd3, 0x83, 0xb8, 0x49, 0x0f, 0xe6, 0x26,
125 | 0x3d, 0x57, 0x90, 0x9b, 0xa4, 0xa4, 0xf4, 0x10, 0xfe, 0x40, 0xb3, 0x4a, 0x89, 0xc1, 0x80, 0x51,
126 | 0xc8, 0x83, 0x8b, 0xd7, 0xb9, 0x28, 0x35, 0xb1, 0x24, 0x15, 0x2a, 0x29, 0x24, 0x89, 0x4d, 0x03,
127 | 0xd8, 0x1f, 0xf8, 0xcd, 0x4a, 0x62, 0x03, 0xdb, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x59,
128 | 0x34, 0x94, 0x0a, 0x39, 0x01, 0x00, 0x00,
129 | }
130 |
131 | // Reference imports to suppress errors if they are not otherwise used.
132 | var _ context.Context
133 | var _ grpc.ClientConnInterface
134 |
135 | // This is a compile-time assertion to ensure that this generated file
136 | // is compatible with the grpc package it is being compiled against.
137 | const _ = grpc.SupportPackageIsVersion6
138 |
139 | // MessengerClient is the client API for Messenger service.
140 | //
141 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
142 | type MessengerClient interface {
143 | GetMessages(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (Messenger_GetMessagesClient, error)
144 | CreateMessage(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error)
145 | }
146 |
147 | type messengerClient struct {
148 | cc grpc.ClientConnInterface
149 | }
150 |
151 | func NewMessengerClient(cc grpc.ClientConnInterface) MessengerClient {
152 | return &messengerClient{cc}
153 | }
154 |
155 | func (c *messengerClient) GetMessages(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (Messenger_GetMessagesClient, error) {
156 | stream, err := c.cc.NewStream(ctx, &_Messenger_serviceDesc.Streams[0], "/messenger.Messenger/GetMessages", opts...)
157 | if err != nil {
158 | return nil, err
159 | }
160 | x := &messengerGetMessagesClient{stream}
161 | if err := x.ClientStream.SendMsg(in); err != nil {
162 | return nil, err
163 | }
164 | if err := x.ClientStream.CloseSend(); err != nil {
165 | return nil, err
166 | }
167 | return x, nil
168 | }
169 |
170 | type Messenger_GetMessagesClient interface {
171 | Recv() (*MessageResponse, error)
172 | grpc.ClientStream
173 | }
174 |
175 | type messengerGetMessagesClient struct {
176 | grpc.ClientStream
177 | }
178 |
179 | func (x *messengerGetMessagesClient) Recv() (*MessageResponse, error) {
180 | m := new(MessageResponse)
181 | if err := x.ClientStream.RecvMsg(m); err != nil {
182 | return nil, err
183 | }
184 | return m, nil
185 | }
186 |
187 | func (c *messengerClient) CreateMessage(ctx context.Context, in *MessageRequest, opts ...grpc.CallOption) (*MessageResponse, error) {
188 | out := new(MessageResponse)
189 | err := c.cc.Invoke(ctx, "/messenger.Messenger/CreateMessage", in, out, opts...)
190 | if err != nil {
191 | return nil, err
192 | }
193 | return out, nil
194 | }
195 |
196 | // MessengerServer is the server API for Messenger service.
197 | type MessengerServer interface {
198 | GetMessages(*empty.Empty, Messenger_GetMessagesServer) error
199 | CreateMessage(context.Context, *MessageRequest) (*MessageResponse, error)
200 | }
201 |
202 | // UnimplementedMessengerServer can be embedded to have forward compatible implementations.
203 | type UnimplementedMessengerServer struct {
204 | }
205 |
206 | func (*UnimplementedMessengerServer) GetMessages(req *empty.Empty, srv Messenger_GetMessagesServer) error {
207 | return status.Errorf(codes.Unimplemented, "method GetMessages not implemented")
208 | }
209 | func (*UnimplementedMessengerServer) CreateMessage(ctx context.Context, req *MessageRequest) (*MessageResponse, error) {
210 | return nil, status.Errorf(codes.Unimplemented, "method CreateMessage not implemented")
211 | }
212 |
213 | func RegisterMessengerServer(s *grpc.Server, srv MessengerServer) {
214 | s.RegisterService(&_Messenger_serviceDesc, srv)
215 | }
216 |
217 | func _Messenger_GetMessages_Handler(srv interface{}, stream grpc.ServerStream) error {
218 | m := new(empty.Empty)
219 | if err := stream.RecvMsg(m); err != nil {
220 | return err
221 | }
222 | return srv.(MessengerServer).GetMessages(m, &messengerGetMessagesServer{stream})
223 | }
224 |
225 | type Messenger_GetMessagesServer interface {
226 | Send(*MessageResponse) error
227 | grpc.ServerStream
228 | }
229 |
230 | type messengerGetMessagesServer struct {
231 | grpc.ServerStream
232 | }
233 |
234 | func (x *messengerGetMessagesServer) Send(m *MessageResponse) error {
235 | return x.ServerStream.SendMsg(m)
236 | }
237 |
238 | func _Messenger_CreateMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
239 | in := new(MessageRequest)
240 | if err := dec(in); err != nil {
241 | return nil, err
242 | }
243 | if interceptor == nil {
244 | return srv.(MessengerServer).CreateMessage(ctx, in)
245 | }
246 | info := &grpc.UnaryServerInfo{
247 | Server: srv,
248 | FullMethod: "/messenger.Messenger/CreateMessage",
249 | }
250 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
251 | return srv.(MessengerServer).CreateMessage(ctx, req.(*MessageRequest))
252 | }
253 | return interceptor(ctx, in, info, handler)
254 | }
255 |
256 | var _Messenger_serviceDesc = grpc.ServiceDesc{
257 | ServiceName: "messenger.Messenger",
258 | HandlerType: (*MessengerServer)(nil),
259 | Methods: []grpc.MethodDesc{
260 | {
261 | MethodName: "CreateMessage",
262 | Handler: _Messenger_CreateMessage_Handler,
263 | },
264 | },
265 | Streams: []grpc.StreamDesc{
266 | {
267 | StreamName: "GetMessages",
268 | Handler: _Messenger_GetMessages_Handler,
269 | ServerStreams: true,
270 | },
271 | },
272 | Metadata: "messenger.proto",
273 | }
274 |
--------------------------------------------------------------------------------
/server/scripts/server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -xe
4 |
5 | fresh
6 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@types/google-protobuf@^3.7.2":
6 | version "3.7.2"
7 | resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.2.tgz#cd8a360c193ce4d672575a20a79f49ba036d38d2"
8 | integrity sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ==
9 |
--------------------------------------------------------------------------------