├── .gitignore ├── LICENSE ├── README.md ├── cdk ├── .gitignore ├── README.md ├── bin │ ├── cdk.d.ts │ ├── cdk.js │ └── cdk.ts ├── cdk.json ├── jest.config.js ├── lib │ ├── cdk-stack.d.ts │ ├── cdk-stack.js │ └── cdk-stack.ts ├── package-lock.json ├── package.json ├── test │ ├── cdk.test.d.ts │ ├── cdk.test.js │ └── cdk.test.ts └── tsconfig.json ├── client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── schema.graphql ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ └── setupTests.js └── yarn.lock └── simple-wsapi.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ed Lima 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 | # Simple Serverless Real-Time API (WebSockets)
with AWS AppSync 2 | ### (No GraphQL knowledge required) 3 |
4 | This is an implementation of a simple real-time API based on WebSockets where clients are subscribed to a specific channel and any JSON data can be pushed automatically to clients listening/subscribed to the channel. Clients define the channel they're subscribed to and there can be as many channels as required. Connections, scalability, fan-out and broadcasting are all automatically handled by the API. 5 |
6 |
7 | 8 | The GraphQL API is defined in code where a GraphQL schema and API definition is generated [programatically](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-appsync-readme.html#code-first-schema). Client code is also automatically generated. Hand coding GraphQL schema, queries, mutations, subscriptions is not required. If there's no need to leverage the built-in GraphQL strongly typed system, this pattern allows to send any data to connected/subscribed clients as long as it's in a valid JSON format. 9 |
10 |
11 | 12 | ## Requirements 13 | 14 | * [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. 15 | * [Git installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 16 | * [Node and NPM](https://nodejs.org/en/download/) installed 17 | * [AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) (AWS CDK) installed 18 | * [Amplify CLI](https://docs.amplify.aws/cli/start/install/), only required to generate code as the backend deployment is done via AWS CDK 19 |
20 |
21 | 22 | ## Deploy the API to the cloud 23 | 24 | 1. Clone the project to your local working directory: 25 | 26 | ```sh 27 | git clone https://github.com/awsed/simpleWSAPI 28 | ``` 29 | 30 | 2. Change the working directory to: 31 | 32 | ```sh 33 | cd simpleWSAPI/cdk 34 | ``` 35 | 36 | 3. Install the project dependencies: 37 | 38 | ```sh 39 | npm install 40 | ``` 41 | 42 | 4. Deploy the stack to your default AWS account and region. After deployment, the output of this command shows the GraphQL API endpoint, API ID, and API key. Take note of all the details as they'll be needed to setup the client: 43 | 44 | ```sh 45 | cdk deploy 46 | ``` 47 |
48 |
49 | 50 | ## Configure the React.js client 51 | 52 | 1. Change the working directory to the `client` folder: 53 | 54 | ```sh 55 | cd ../client 56 | ``` 57 | 58 | 2. Install the project dependencies: 59 | 60 | ```sh 61 | npm install 62 | ``` 63 | 3. Open the file `src/App.js` and update the [AppSync API congifuration details](https://github.com/awsed/simpleWSAPI/blob/68bc0846dc2edb5e13a75767a060153e37ceeaee/client/src/App.js#L9) based on the output of the previous `cdk deploy`. 64 | 65 | 4. Define a [name for your channel](https://github.com/awsed/simpleWSAPI/blob/68bc0846dc2edb5e13a75767a060153e37ceeaee/client/src/App.js#L22), as an optional step. 66 | 67 | 5. Generate the necessary code to interact with the API using the [Amplify CodeGen](https://docs.amplify.aws/cli/graphql-transformer/codegen/) with the API ID output of the previous `cdk deploy`. There's no need to create an Amplify CLI project, however you'll need to download the API schema from the [AWS Appsync Console](https://console.aws.amazon.com/appsync/home). Select the API `WS-API` in your account and, in the Schema section, select **Export schema**. Download and copy the schema file to the root of the `/client` folder, where you need to execute the following command accepting all defaults: 68 | 69 | ```sh 70 | amplify add codegen --apiId xxxxxxxxxxxxxxxxxxxxxx 71 | ``` 72 | 73 | 6. Execute the application and access it from multiple browser tabs/windows at http://localhost:3000 : 74 | 75 | ```bash 76 | npm start 77 | ``` 78 | 79 | 7. Send any valid JSON payload from one client and get it broadcasted in all browser windows. Since AWS AppSync automatically scales to demand, you can have thousands of clients broadcasting JSON data. You can also have multiple [channels](https://github.com/awsed/simpleWSAPI/blob/68bc0846dc2edb5e13a75767a060153e37ceeaee/client/src/App.js#L22) defined in different instances of the clients and confirm only clients subscribed to specific channels receive the JSON data. 80 |
81 |
82 | 83 | ![Screnshot](simple-wsapi.gif) 84 | -------------------------------------------------------------------------------- /cdk/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /cdk/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project! 2 | 3 | This is a blank project for TypeScript development with CDK. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /cdk/bin/cdk.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | -------------------------------------------------------------------------------- /cdk/bin/cdk.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | require("source-map-support/register"); 5 | const cdk = require("@aws-cdk/core"); 6 | const cdk_stack_1 = require("../lib/cdk-stack"); 7 | const app = new cdk.App(); 8 | new cdk_stack_1.CdkStack(app, 'WebSocketAPI', { 9 | /* If you don't specify 'env', this stack will be environment-agnostic. 10 | * Account/Region-dependent features and context lookups will not work, 11 | * but a single synthesized template can be deployed anywhere. */ 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | /* Uncomment the next line if you know exactly what Account and Region you 16 | * want to deploy the stack to. */ 17 | // env: { account: '123456789012', region: 'us-east-1' }, 18 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 19 | }); 20 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2RrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLHVDQUFxQztBQUNyQyxxQ0FBcUM7QUFDckMsZ0RBQTRDO0FBRTVDLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQzFCLElBQUksb0JBQVEsQ0FBQyxHQUFHLEVBQUUsY0FBYyxFQUFFO0FBQ2hDOztpRUFFaUU7QUFFakU7bUVBQ21FO0FBQ25FLDZGQUE2RjtBQUU3RjtrQ0FDa0M7QUFDbEMseURBQXlEO0FBRXpELDhGQUE4RjtDQUMvRixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiBub2RlXG5pbXBvcnQgJ3NvdXJjZS1tYXAtc3VwcG9ydC9yZWdpc3Rlcic7XG5pbXBvcnQgKiBhcyBjZGsgZnJvbSAnQGF3cy1jZGsvY29yZSc7XG5pbXBvcnQgeyBDZGtTdGFjayB9IGZyb20gJy4uL2xpYi9jZGstc3RhY2snO1xuXG5jb25zdCBhcHAgPSBuZXcgY2RrLkFwcCgpO1xubmV3IENka1N0YWNrKGFwcCwgJ1dlYlNvY2tldEFQSScsIHtcbiAgLyogSWYgeW91IGRvbid0IHNwZWNpZnkgJ2VudicsIHRoaXMgc3RhY2sgd2lsbCBiZSBlbnZpcm9ubWVudC1hZ25vc3RpYy5cbiAgICogQWNjb3VudC9SZWdpb24tZGVwZW5kZW50IGZlYXR1cmVzIGFuZCBjb250ZXh0IGxvb2t1cHMgd2lsbCBub3Qgd29yayxcbiAgICogYnV0IGEgc2luZ2xlIHN5bnRoZXNpemVkIHRlbXBsYXRlIGNhbiBiZSBkZXBsb3llZCBhbnl3aGVyZS4gKi9cblxuICAvKiBVbmNvbW1lbnQgdGhlIG5leHQgbGluZSB0byBzcGVjaWFsaXplIHRoaXMgc3RhY2sgZm9yIHRoZSBBV1MgQWNjb3VudFxuICAgKiBhbmQgUmVnaW9uIHRoYXQgYXJlIGltcGxpZWQgYnkgdGhlIGN1cnJlbnQgQ0xJIGNvbmZpZ3VyYXRpb24uICovXG4gIC8vIGVudjogeyBhY2NvdW50OiBwcm9jZXNzLmVudi5DREtfREVGQVVMVF9BQ0NPVU5ULCByZWdpb246IHByb2Nlc3MuZW52LkNES19ERUZBVUxUX1JFR0lPTiB9LFxuXG4gIC8qIFVuY29tbWVudCB0aGUgbmV4dCBsaW5lIGlmIHlvdSBrbm93IGV4YWN0bHkgd2hhdCBBY2NvdW50IGFuZCBSZWdpb24geW91XG4gICAqIHdhbnQgdG8gZGVwbG95IHRoZSBzdGFjayB0by4gKi9cbiAgLy8gZW52OiB7IGFjY291bnQ6ICcxMjM0NTY3ODkwMTInLCByZWdpb246ICd1cy1lYXN0LTEnIH0sXG5cbiAgLyogRm9yIG1vcmUgaW5mb3JtYXRpb24sIHNlZSBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vY2RrL2xhdGVzdC9ndWlkZS9lbnZpcm9ubWVudHMuaHRtbCAqL1xufSk7XG4iXX0= -------------------------------------------------------------------------------- /cdk/bin/cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import * as cdk from '@aws-cdk/core'; 4 | import { CdkStack } from '../lib/cdk-stack'; 5 | 6 | const app = new cdk.App(); 7 | new CdkStack(app, 'WebSocketAPI', { 8 | /* If you don't specify 'env', this stack will be environment-agnostic. 9 | * Account/Region-dependent features and context lookups will not work, 10 | * but a single synthesized template can be deployed anywhere. */ 11 | 12 | /* Uncomment the next line to specialize this stack for the AWS Account 13 | * and Region that are implied by the current CLI configuration. */ 14 | // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, 15 | 16 | /* Uncomment the next line if you know exactly what Account and Region you 17 | * want to deploy the stack to. */ 18 | // env: { account: '123456789012', region: 'us-east-1' }, 19 | 20 | /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ 21 | }); 22 | -------------------------------------------------------------------------------- /cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", 3 | "context": { 4 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 5 | "@aws-cdk/core:enableStackNameDuplicates": true, 6 | "aws-cdk:enableDiffNoFail": true, 7 | "@aws-cdk/core:stackRelativeExports": true, 8 | "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, 9 | "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, 10 | "@aws-cdk/aws-kms:defaultKeyPolicies": true, 11 | "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, 12 | "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, 13 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 14 | "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, 15 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 16 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cdk/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /cdk/lib/cdk-stack.d.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | export declare class CdkStack extends cdk.Stack { 3 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps); 4 | } 5 | -------------------------------------------------------------------------------- /cdk/lib/cdk-stack.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CdkStack = void 0; 4 | const cdk = require("@aws-cdk/core"); 5 | const aws_appsync_1 = require("@aws-cdk/aws-appsync"); 6 | class CdkStack extends cdk.Stack { 7 | constructor(scope, id, props) { 8 | super(scope, id, props); 9 | const api = new aws_appsync_1.GraphqlApi(this, 'Api', { 10 | name: 'WS-API', 11 | authorizationConfig: { 12 | defaultAuthorization: { 13 | authorizationType: aws_appsync_1.AuthorizationType.API_KEY 14 | } 15 | } 16 | }); 17 | const channel = new aws_appsync_1.ObjectType('Channel', { 18 | definition: { 19 | name: aws_appsync_1.GraphqlType.string({ isRequired: true }), 20 | data: aws_appsync_1.GraphqlType.awsJson({ isRequired: true }), 21 | }, 22 | }); 23 | api.addType(channel); 24 | api.addQuery('getChannel', new aws_appsync_1.Field({ 25 | returnType: channel.attribute() 26 | })); 27 | api.addMutation('publish2channel', new aws_appsync_1.ResolvableField({ 28 | returnType: channel.attribute(), 29 | args: { name: aws_appsync_1.GraphqlType.string({ isRequired: true }), data: aws_appsync_1.GraphqlType.awsJson({ isRequired: true }) }, 30 | dataSource: api.addNoneDataSource('pubsub'), 31 | requestMappingTemplate: aws_appsync_1.MappingTemplate.fromString(` 32 | { 33 | "version": "2017-02-28", 34 | "payload": { 35 | "name": "$context.arguments.name", 36 | "data": $util.toJson($context.arguments.data) 37 | } 38 | }`), 39 | responseMappingTemplate: aws_appsync_1.MappingTemplate.fromString(`$util.toJson($context.result)`) 40 | })); 41 | api.addSubscription('subscribe2channel', new aws_appsync_1.Field({ 42 | returnType: channel.attribute(), 43 | args: { name: aws_appsync_1.GraphqlType.string({ isRequired: true }) }, 44 | directives: [aws_appsync_1.Directive.subscribe('publish2channel')], 45 | })); 46 | new cdk.CfnOutput(this, 'graphqlUrl', { value: api.graphqlUrl }); 47 | new cdk.CfnOutput(this, 'apiKey', { value: api.apiKey }); 48 | new cdk.CfnOutput(this, 'apiId', { value: api.apiId }); 49 | new cdk.CfnOutput(this, 'region', { value: this.region }); 50 | } 51 | } 52 | exports.CdkStack = CdkStack; 53 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLXN0YWNrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2RrLXN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFDQUFxQztBQUNyQyxzREFBa0o7QUFFbEosTUFBYSxRQUFTLFNBQVEsR0FBRyxDQUFDLEtBQUs7SUFDckMsWUFBWSxLQUFvQixFQUFFLEVBQVUsRUFBRSxLQUFzQjtRQUNsRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUV4QixNQUFNLEdBQUcsR0FBRyxJQUFJLHdCQUFVLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRTtZQUN0QyxJQUFJLEVBQUUsUUFBUTtZQUNkLG1CQUFtQixFQUFFO2dCQUNuQixvQkFBb0IsRUFBRTtvQkFDcEIsaUJBQWlCLEVBQUUsK0JBQWlCLENBQUMsT0FBTztpQkFDN0M7YUFDRjtTQUNGLENBQUMsQ0FBQztRQUVILE1BQU0sT0FBTyxHQUFHLElBQUksd0JBQVUsQ0FBQyxTQUFTLEVBQUU7WUFDeEMsVUFBVSxFQUFFO2dCQUNWLElBQUksRUFBRSx5QkFBVyxDQUFDLE1BQU0sQ0FBQyxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQztnQkFDOUMsSUFBSSxFQUFFLHlCQUFXLENBQUMsT0FBTyxDQUFDLEVBQUUsVUFBVSxFQUFFLElBQUksRUFBRSxDQUFDO2FBQ2hEO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsR0FBRyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUVyQixHQUFHLENBQUMsUUFBUSxDQUFDLFlBQVksRUFBRSxJQUFJLG1CQUFLLENBQUM7WUFDbkMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxTQUFTLEVBQUU7U0FDaEMsQ0FBQyxDQUFDLENBQUM7UUFFSixHQUFHLENBQUMsV0FBVyxDQUFDLGlCQUFpQixFQUFFLElBQUksNkJBQWUsQ0FBQztZQUNyRCxVQUFVLEVBQUUsT0FBTyxDQUFDLFNBQVMsRUFBRTtZQUMvQixJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUseUJBQVcsQ0FBQyxNQUFNLENBQUMsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRyxJQUFJLEVBQUUseUJBQVcsQ0FBQyxPQUFPLENBQUMsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRztZQUMzRyxVQUFVLEVBQUUsR0FBRyxDQUFDLGlCQUFpQixDQUFDLFFBQVEsQ0FBQztZQUMzQyxzQkFBc0IsRUFBRSw2QkFBZSxDQUFDLFVBQVUsQ0FBQzs7Ozs7OztVQU8vQyxDQUNIO1lBQ0QsdUJBQXVCLEVBQUUsNkJBQWUsQ0FBQyxVQUFVLENBQUMsK0JBQStCLENBQUM7U0FDckYsQ0FBQyxDQUFDLENBQUE7UUFFSCxHQUFHLENBQUMsZUFBZSxDQUFDLG1CQUFtQixFQUFFLElBQUksbUJBQUssQ0FBQztZQUNqRCxVQUFVLEVBQUUsT0FBTyxDQUFDLFNBQVMsRUFBRTtZQUMvQixJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUseUJBQVcsQ0FBQyxNQUFNLENBQUMsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRTtZQUN4RCxVQUFVLEVBQUUsQ0FBQyx1QkFBUyxDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO1NBQ3JELENBQUMsQ0FBQyxDQUFDO1FBRUosSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxZQUFZLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUE7UUFDaEUsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxRQUFRLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLE1BQU8sRUFBRSxDQUFDLENBQUE7UUFDekQsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxPQUFPLEVBQUUsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUE7UUFDdEQsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLElBQUksRUFBRSxRQUFRLEVBQUUsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUE7SUFFN0QsQ0FBQztDQUNBO0FBdERELDRCQXNEQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGNkayBmcm9tICdAYXdzLWNkay9jb3JlJztcbmltcG9ydCB7IEdyYXBocWxBcGksIEF1dGhvcml6YXRpb25UeXBlLCBEaXJlY3RpdmUsIE9iamVjdFR5cGUsIEdyYXBocWxUeXBlLCBSZXNvbHZhYmxlRmllbGQsIEZpZWxkLCBNYXBwaW5nVGVtcGxhdGUgfSBmcm9tICdAYXdzLWNkay9hd3MtYXBwc3luYyc7XG5cbmV4cG9ydCBjbGFzcyBDZGtTdGFjayBleHRlbmRzIGNkay5TdGFjayB7XG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBjZGsuQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wcz86IGNkay5TdGFja1Byb3BzKSB7XG4gICAgc3VwZXIoc2NvcGUsIGlkLCBwcm9wcyk7XG5cbiAgICBjb25zdCBhcGkgPSBuZXcgR3JhcGhxbEFwaSh0aGlzLCAnQXBpJywge1xuICAgICAgbmFtZTogJ1dTLUFQSScsXG4gICAgICBhdXRob3JpemF0aW9uQ29uZmlnOiB7XG4gICAgICAgIGRlZmF1bHRBdXRob3JpemF0aW9uOiB7XG4gICAgICAgICAgYXV0aG9yaXphdGlvblR5cGU6IEF1dGhvcml6YXRpb25UeXBlLkFQSV9LRVlcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pO1xuXG4gICAgY29uc3QgY2hhbm5lbCA9IG5ldyBPYmplY3RUeXBlKCdDaGFubmVsJywge1xuICAgICAgZGVmaW5pdGlvbjoge1xuICAgICAgICBuYW1lOiBHcmFwaHFsVHlwZS5zdHJpbmcoeyBpc1JlcXVpcmVkOiB0cnVlIH0pLFxuICAgICAgICBkYXRhOiBHcmFwaHFsVHlwZS5hd3NKc29uKHsgaXNSZXF1aXJlZDogdHJ1ZSB9KSxcbiAgICAgIH0sXG4gICAgfSk7XG5cbiAgICBhcGkuYWRkVHlwZShjaGFubmVsKTtcblxuICAgIGFwaS5hZGRRdWVyeSgnZ2V0Q2hhbm5lbCcsIG5ldyBGaWVsZCh7XG4gICAgICByZXR1cm5UeXBlOiBjaGFubmVsLmF0dHJpYnV0ZSgpXG4gICAgfSkpO1xuXG4gICAgYXBpLmFkZE11dGF0aW9uKCdwdWJsaXNoMmNoYW5uZWwnLCBuZXcgUmVzb2x2YWJsZUZpZWxkKHtcbiAgICAgIHJldHVyblR5cGU6IGNoYW5uZWwuYXR0cmlidXRlKCksXG4gICAgICBhcmdzOiB7IG5hbWU6IEdyYXBocWxUeXBlLnN0cmluZyh7IGlzUmVxdWlyZWQ6IHRydWUgfSkgLCBkYXRhOiBHcmFwaHFsVHlwZS5hd3NKc29uKHsgaXNSZXF1aXJlZDogdHJ1ZSB9KSAgfSxcbiAgICAgIGRhdGFTb3VyY2U6IGFwaS5hZGROb25lRGF0YVNvdXJjZSgncHVic3ViJyksXG4gICAgICByZXF1ZXN0TWFwcGluZ1RlbXBsYXRlOiBNYXBwaW5nVGVtcGxhdGUuZnJvbVN0cmluZyhgXG4gICAgICAgIHtcbiAgICAgICAgICBcInZlcnNpb25cIjogXCIyMDE3LTAyLTI4XCIsXG4gICAgICAgICAgXCJwYXlsb2FkXCI6IHtcbiAgICAgICAgICAgICAgXCJuYW1lXCI6IFwiJGNvbnRleHQuYXJndW1lbnRzLm5hbWVcIixcbiAgICAgICAgICAgICAgXCJkYXRhXCI6ICR1dGlsLnRvSnNvbigkY29udGV4dC5hcmd1bWVudHMuZGF0YSlcbiAgICAgICAgICB9XG4gICAgICAgIH1gXG4gICAgICApLFxuICAgICAgcmVzcG9uc2VNYXBwaW5nVGVtcGxhdGU6IE1hcHBpbmdUZW1wbGF0ZS5mcm9tU3RyaW5nKGAkdXRpbC50b0pzb24oJGNvbnRleHQucmVzdWx0KWApXG4gICAgfSkpXG5cbiAgICBhcGkuYWRkU3Vic2NyaXB0aW9uKCdzdWJzY3JpYmUyY2hhbm5lbCcsIG5ldyBGaWVsZCh7XG4gICAgICByZXR1cm5UeXBlOiBjaGFubmVsLmF0dHJpYnV0ZSgpLFxuICAgICAgYXJnczogeyBuYW1lOiBHcmFwaHFsVHlwZS5zdHJpbmcoeyBpc1JlcXVpcmVkOiB0cnVlIH0pIH0sXG4gICAgICBkaXJlY3RpdmVzOiBbRGlyZWN0aXZlLnN1YnNjcmliZSgncHVibGlzaDJjaGFubmVsJyldLFxuICAgIH0pKTtcblxuICAgIG5ldyBjZGsuQ2ZuT3V0cHV0KHRoaXMsICdncmFwaHFsVXJsJywgeyB2YWx1ZTogYXBpLmdyYXBocWxVcmwgfSlcbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCAnYXBpS2V5JywgeyB2YWx1ZTogYXBpLmFwaUtleSEgfSlcbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCAnYXBpSWQnLCB7IHZhbHVlOiBhcGkuYXBpSWQgfSlcbiAgICBuZXcgY2RrLkNmbk91dHB1dCh0aGlzLCAncmVnaW9uJywgeyB2YWx1ZTogdGhpcy5yZWdpb24gfSlcbiAgXG59XG59XG4iXX0= -------------------------------------------------------------------------------- /cdk/lib/cdk-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from '@aws-cdk/core'; 2 | import { GraphqlApi, AuthorizationType, Directive, ObjectType, GraphqlType, ResolvableField, Field, MappingTemplate } from '@aws-cdk/aws-appsync'; 3 | 4 | export class CdkStack extends cdk.Stack { 5 | constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { 6 | super(scope, id, props); 7 | 8 | const api = new GraphqlApi(this, 'Api', { 9 | name: 'WS-API', 10 | authorizationConfig: { 11 | defaultAuthorization: { 12 | authorizationType: AuthorizationType.API_KEY 13 | } 14 | } 15 | }); 16 | 17 | const channel = new ObjectType('Channel', { 18 | definition: { 19 | name: GraphqlType.string({ isRequired: true }), 20 | data: GraphqlType.awsJson({ isRequired: true }), 21 | }, 22 | }); 23 | 24 | api.addType(channel); 25 | 26 | api.addQuery('getChannel', new Field({ 27 | returnType: channel.attribute() 28 | })); 29 | 30 | api.addMutation('publish2channel', new ResolvableField({ 31 | returnType: channel.attribute(), 32 | args: { name: GraphqlType.string({ isRequired: true }), data: GraphqlType.awsJson({ isRequired: true }) }, 33 | dataSource: api.addNoneDataSource('pubsub'), 34 | requestMappingTemplate: MappingTemplate.fromString(` 35 | { 36 | "version": "2017-02-28", 37 | "payload": { 38 | "name": "$context.arguments.name", 39 | "data": $util.toJson($context.arguments.data) 40 | } 41 | }` 42 | ), 43 | responseMappingTemplate: MappingTemplate.fromString(`$util.toJson($context.result)`) 44 | })) 45 | 46 | api.addSubscription('subscribe2channel', new Field({ 47 | returnType: channel.attribute(), 48 | args: { name: GraphqlType.string({ isRequired: true }) }, 49 | directives: [Directive.subscribe('publish2channel')], 50 | })); 51 | 52 | new cdk.CfnOutput(this, 'graphqlUrl', { value: api.graphqlUrl }) 53 | new cdk.CfnOutput(this, 'apiKey', { value: api.apiKey! }) 54 | new cdk.CfnOutput(this, 'apiId', { value: api.apiId }) 55 | new cdk.CfnOutput(this, 'region', { value: this.region }) 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk", 3 | "version": "0.1.0", 4 | "bin": { 5 | "cdk": "bin/cdk.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@aws-cdk/assertions": "1.132.0", 15 | "@types/jest": "^26.0.10", 16 | "@types/node": "10.17.27", 17 | "aws-cdk": "1.132.0", 18 | "jest": "^26.4.2", 19 | "ts-jest": "^26.2.0", 20 | "ts-node": "^9.0.0", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "@aws-cdk/aws-appsync": "^1.132.0", 25 | "@aws-cdk/core": "1.132.0", 26 | "source-map-support": "^0.5.16" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cdk/test/cdk.test.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoldBrewEd/simpleWSAPI/34907e893bbdffb7d352848f0aafbf7fe4d380c8/cdk/test/cdk.test.d.ts -------------------------------------------------------------------------------- /cdk/test/cdk.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // import { Template } from '@aws-cdk/assertions'; 3 | // import * as cdk from '@aws-cdk/core'; 4 | // import * as Cdk from '../lib/cdk-stack'; 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/cdk-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Cdk.CdkStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | // template.hasResourceProperties('AWS::SQS::Queue', { 14 | // VisibilityTimeout: 300 15 | // }); 16 | }); 17 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjZGsudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsa0RBQWtEO0FBQ2xELHdDQUF3QztBQUN4QywyQ0FBMkM7QUFFM0MsdUVBQXVFO0FBQ3ZFLHVDQUF1QztBQUN2QyxJQUFJLENBQUMsbUJBQW1CLEVBQUUsR0FBRyxFQUFFO0lBQy9CLCtCQUErQjtJQUMvQixjQUFjO0lBQ2Qsd0RBQXdEO0lBQ3hELGNBQWM7SUFDZCxnREFBZ0Q7SUFFaEQsd0RBQXdEO0lBQ3hELDZCQUE2QjtJQUM3QixRQUFRO0FBQ1IsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBpbXBvcnQgeyBUZW1wbGF0ZSB9IGZyb20gJ0Bhd3MtY2RrL2Fzc2VydGlvbnMnO1xuLy8gaW1wb3J0ICogYXMgY2RrIGZyb20gJ0Bhd3MtY2RrL2NvcmUnO1xuLy8gaW1wb3J0ICogYXMgQ2RrIGZyb20gJy4uL2xpYi9jZGstc3RhY2snO1xuXG4vLyBleGFtcGxlIHRlc3QuIFRvIHJ1biB0aGVzZSB0ZXN0cywgdW5jb21tZW50IHRoaXMgZmlsZSBhbG9uZyB3aXRoIHRoZVxuLy8gZXhhbXBsZSByZXNvdXJjZSBpbiBsaWIvY2RrLXN0YWNrLnRzXG50ZXN0KCdTUVMgUXVldWUgQ3JlYXRlZCcsICgpID0+IHtcbi8vICAgY29uc3QgYXBwID0gbmV3IGNkay5BcHAoKTtcbi8vICAgICAvLyBXSEVOXG4vLyAgIGNvbnN0IHN0YWNrID0gbmV3IENkay5DZGtTdGFjayhhcHAsICdNeVRlc3RTdGFjaycpO1xuLy8gICAgIC8vIFRIRU5cbi8vICAgY29uc3QgdGVtcGxhdGUgPSBUZW1wbGF0ZS5mcm9tU3RhY2soc3RhY2spO1xuXG4vLyAgIHRlbXBsYXRlLmhhc1Jlc291cmNlUHJvcGVydGllcygnQVdTOjpTUVM6OlF1ZXVlJywge1xuLy8gICAgIFZpc2liaWxpdHlUaW1lb3V0OiAzMDBcbi8vICAgfSk7XG59KTtcbiJdfQ== -------------------------------------------------------------------------------- /cdk/test/cdk.test.ts: -------------------------------------------------------------------------------- 1 | // import { Template } from '@aws-cdk/assertions'; 2 | // import * as cdk from '@aws-cdk/core'; 3 | // import * as Cdk from '../lib/cdk-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/cdk-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Cdk.CdkStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "aws-amplify": "^4.3.5", 10 | "react": "^17.0.2", 11 | "react-dom": "^17.0.2", 12 | "react-scripts": "4.0.3", 13 | "web-vitals": "^1.0.1" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app", 24 | "react-app/jest" 25 | ] 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 | } 40 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoldBrewEd/simpleWSAPI/34907e893bbdffb7d352848f0aafbf7fe4d380c8/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoldBrewEd/simpleWSAPI/34907e893bbdffb7d352848f0aafbf7fe4d380c8/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoldBrewEd/simpleWSAPI/34907e893bbdffb7d352848f0aafbf7fe4d380c8/client/public/logo512.png -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/schema.graphql: -------------------------------------------------------------------------------- 1 | schema { 2 | query: Query 3 | mutation: Mutation 4 | subscription: Subscription 5 | } 6 | 7 | type Channel { 8 | data: AWSJSON! 9 | name: String! 10 | } 11 | 12 | type Mutation { 13 | publish2channel(data: AWSJSON!, name: String!): Channel 14 | } 15 | 16 | type Query { 17 | getChannel: Channel 18 | } 19 | 20 | type Subscription { 21 | subscribe2channel(name: String!): Channel @aws_subscribe(mutations : ["publish2channel"]) 22 | } 23 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | .App-header { 11 | text-align: left; 12 | } 13 | 14 | @media (prefers-reduced-motion: no-preference) { 15 | .App-logo { 16 | animation: App-logo-spin infinite 20s linear; 17 | } 18 | } 19 | 20 | .App-header { 21 | background-color: #282c34; 22 | min-height: 100vh; 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | font-size: calc(10px + 2vmin); 28 | color: white; 29 | } 30 | 31 | .App-link { 32 | color: #61dafb; 33 | } 34 | 35 | @keyframes App-logo-spin { 36 | from { 37 | transform: rotate(0deg); 38 | } 39 | to { 40 | transform: rotate(360deg); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from "./logo.svg"; 2 | import "./App.css"; 3 | import React, { useEffect,useState } from "react"; 4 | import Amplify, { API, graphqlOperation } from "aws-amplify"; 5 | import * as subscriptions from "./graphql/subscriptions"; //codegen generated code 6 | import * as mutations from "./graphql/mutations"; //codegen generated code 7 | 8 | //AppSync endpoint settings 9 | const myAppConfig = { 10 | aws_appsync_graphqlEndpoint: 11 | "https://xxxxxxxxxxxxxxxxxx.appsync-api.us-west-2.amazonaws.com/graphql", 12 | aws_appsync_region: "us-west-2", 13 | aws_appsync_authenticationType: "API_KEY", 14 | aws_appsync_apiKey: "da2-xxxxxxxxxxxxxxxxxx", 15 | }; 16 | 17 | Amplify.configure(myAppConfig); 18 | 19 | function App() { 20 | const [send, setSend] = useState(""); 21 | const [received, setReceived] = useState(""); 22 | 23 | //Define the channel name here 24 | let channel = "robots"; 25 | let data = ""; 26 | 27 | //Publish data to subscribed clients 28 | async function handleSubmit(evt) { 29 | evt.preventDefault(); 30 | evt.stopPropagation(); 31 | const publish = await API.graphql( 32 | graphqlOperation(mutations.publish2channel, { name: channel, data: send }) 33 | ); 34 | setSend("Enter valid JSON here... (use quotes for keys and values)"); 35 | } 36 | 37 | useEffect(() => { 38 | //Subscribe via WebSockets 39 | const subscription = API.graphql( 40 | graphqlOperation(subscriptions.subscribe2channel, { name: channel }) 41 | ).subscribe({ 42 | next: ({ provider, value }) => { 43 | setReceived(value.data.subscribe2channel.data); 44 | }, 45 | error: (error) => console.warn(error), 46 | }); 47 | return () => subscription.unsubscribe(); 48 | }, [channel]); 49 | 50 | if (received) { 51 | data = JSON.parse(received); 52 | } 53 | 54 | //Display pushed data on browser 55 | return ( 56 |
57 |
58 |

Send/Push JSON to channel "{channel}"...

59 |
60 | 68 |
69 | 70 |
71 |

Subscribed/Listening to channel "{channel}"...

72 |
{JSON.stringify(data, null, 2)}
73 |
74 |
75 | ); 76 | } 77 | 78 | export default App; 79 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/setupTests.js: -------------------------------------------------------------------------------- 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'; 6 | -------------------------------------------------------------------------------- /simple-wsapi.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoldBrewEd/simpleWSAPI/34907e893bbdffb7d352848f0aafbf7fe4d380c8/simple-wsapi.gif --------------------------------------------------------------------------------