├── infrastructure ├── cdk │ ├── .npmignore │ ├── test │ │ ├── simple.test.d.ts │ │ └── simple.test.ts │ ├── bin │ │ ├── cdk.d.ts │ │ ├── cdk.ts │ │ └── cdk.js │ ├── cdk.json │ ├── lib │ │ ├── nrta.d.ts │ │ ├── layer │ │ │ ├── mainLayer.d.ts │ │ │ ├── contentDeliveryLayer.d.ts │ │ │ ├── databaseLayer.d.ts │ │ │ ├── configurationLayer.d.ts │ │ │ ├── storageLayer.d.ts │ │ │ ├── processingLayer.d.ts │ │ │ ├── ingestionConsumptionLayer.d.ts │ │ │ ├── websocketLayer.d.ts │ │ │ ├── securityLayer.d.ts │ │ │ ├── configurationLayer.ts │ │ │ ├── databaseLayer.ts │ │ │ ├── contentDeliveryLayer.ts │ │ │ ├── configurationLayer.js │ │ │ ├── storageLayer.ts │ │ │ ├── mainLayer.ts │ │ │ ├── databaseLayer.js │ │ │ └── contentDeliveryLayer.js │ │ ├── nrta.ts │ │ ├── util │ │ │ ├── utils.d.ts │ │ │ ├── utils.ts │ │ │ └── utils.js │ │ ├── nrta.js │ │ ├── resourceawarestack.d.ts │ │ └── resourceawarestack.ts │ ├── tsconfig.json │ ├── package.json │ └── lambdas │ │ ├── postRegistration │ │ └── index.js │ │ ├── gameInit │ │ └── index.js │ │ ├── websocketConnect │ │ └── index.js │ │ ├── websocketDisconnect │ │ └── index.js │ │ ├── synchronousStart │ │ └── index.js │ │ └── deallocateGamer │ │ └── index.js ├── enabledevopsguru.sh ├── config.sh ├── update-upgrade-install.sh ├── diverse │ └── howtotestyourapi.txt ├── envname.sh ├── cicd │ └── buildspec.yaml ├── resize.sh ├── deployadmin.sh ├── deploy.frontend.sh ├── fixcognito.sh └── fixwebsocket.sh ├── application ├── images │ ├── fish.png │ ├── ship.png │ ├── plane.png │ ├── favicon.ico │ ├── invader.png │ ├── underthesea.png │ └── invaders.sprite.png ├── game │ ├── sounds │ │ ├── bang.wav │ │ ├── shoot.wav │ │ ├── explosion.wav │ │ └── invaderkilled.wav │ ├── css │ │ └── game.css │ ├── index.html │ └── js │ │ └── wavefield.js ├── pages │ ├── images │ │ ├── game.png │ │ ├── manager.png │ │ ├── alienattack.login.png │ │ ├── alienattack.keyboard.png │ │ ├── alienattack.register.png │ │ ├── alienattack.welcomescreen.png │ │ ├── alienattack.registerorlogin.png │ │ ├── alienattack.registersuccess.png │ │ ├── alienattack.waitingforsession.png │ │ └── alienattack.joinorplaydisconnected.png │ └── howtoplay.html ├── resources │ ├── js │ │ ├── awsconfig.js │ │ ├── gameutils.js │ │ └── modal.js │ ├── css │ │ ├── typeography.css │ │ ├── form.css │ │ ├── core.css │ │ ├── infopages.css │ │ └── modaldialog.css │ └── libs │ │ ├── websocketAG.js │ │ └── websocket.js └── scoreboard │ ├── css │ ├── scoreboard.css │ └── typeography.css │ └── js │ ├── scoreboard.dynamoinherited.js │ └── scoreboard.kinesisinherited.js ├── LICENSE-SUMMARY ├── simulator ├── package.json ├── updateapiendpoint.sh ├── createsession.sh ├── simulator.yaml ├── createusers.sh └── processor.js ├── LICENSE-SAMPLECODE ├── .gitignore ├── README.md └── CONTRIBUTING.md /infrastructure/cdk/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts -------------------------------------------------------------------------------- /infrastructure/cdk/test/simple.test.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /infrastructure/cdk/bin/cdk.d.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | export {}; 3 | -------------------------------------------------------------------------------- /infrastructure/cdk/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/cdk.js" 3 | } -------------------------------------------------------------------------------- /application/images/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/fish.png -------------------------------------------------------------------------------- /application/images/ship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/ship.png -------------------------------------------------------------------------------- /application/images/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/plane.png -------------------------------------------------------------------------------- /application/game/sounds/bang.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/game/sounds/bang.wav -------------------------------------------------------------------------------- /application/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/favicon.ico -------------------------------------------------------------------------------- /application/images/invader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/invader.png -------------------------------------------------------------------------------- /application/game/sounds/shoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/game/sounds/shoot.wav -------------------------------------------------------------------------------- /application/images/underthesea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/underthesea.png -------------------------------------------------------------------------------- /application/pages/images/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/game.png -------------------------------------------------------------------------------- /application/game/sounds/explosion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/game/sounds/explosion.wav -------------------------------------------------------------------------------- /application/pages/images/manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/manager.png -------------------------------------------------------------------------------- /application/images/invaders.sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/images/invaders.sprite.png -------------------------------------------------------------------------------- /application/game/sounds/invaderkilled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/game/sounds/invaderkilled.wav -------------------------------------------------------------------------------- /application/pages/images/alienattack.login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.login.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.keyboard.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.register.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.welcomescreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.welcomescreen.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.registerorlogin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.registerorlogin.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.registersuccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.registersuccess.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.waitingforsession.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.waitingforsession.png -------------------------------------------------------------------------------- /application/pages/images/alienattack.joinorplaydisconnected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aiops-serverless/main/application/pages/images/alienattack.joinorplaydisconnected.png -------------------------------------------------------------------------------- /infrastructure/cdk/lib/nrta.d.ts: -------------------------------------------------------------------------------- 1 | import { ParameterAwareProps, IParameterAwareProps } from '../lib/resourceawarestack'; 2 | export declare class NRTAProps extends ParameterAwareProps { 3 | constructor(props?: IParameterAwareProps); 4 | getBucketNames(): string[]; 5 | } 6 | -------------------------------------------------------------------------------- /application/resources/js/awsconfig.js: -------------------------------------------------------------------------------- 1 | const DEBUG = true; 2 | const AWS_CONFIG = { 3 | "region" : "", 4 | "API_ENDPOINT" : "/v1/", 5 | "APPNAME" : "" 6 | }; 7 | exports.AWS_CONFIG = AWS_CONFIG; 8 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/mainLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'aws-cdk-lib'; 2 | import { IParameterAwareProps, ResourceAwareStack } from '../resourceawarestack'; 3 | export declare class MainLayer extends ResourceAwareStack { 4 | constructor(scope: App, id: string, props?: IParameterAwareProps); 5 | buildResources(): void; 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE-SUMMARY: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | The documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file. 4 | 5 | The sample code within this documentation is made available under the MIT-0 license. See the LICENSE-SAMPLECODE file. 6 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/contentDeliveryLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | export declare class ContentDeliveryLayer extends ResourceAwareConstruct { 4 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 5 | private createDistribution; 6 | } 7 | -------------------------------------------------------------------------------- /simulator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simulator", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "processor.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amazon-cognito-identity-js": "^5.2.10", 13 | "aws-sdk": "^2.1185.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/databaseLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | import DynamoDB = require('aws-cdk-lib/aws-dynamodb'); 4 | export declare class DatabaseLayer extends ResourceAwareConstruct { 5 | tables: Map; 6 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 7 | } 8 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/configurationLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 2 | import { Construct } from 'constructs'; 3 | /** 4 | * Configuration Layer is a construct designed to acquire and store configuration 5 | * data to be used by the system 6 | */ 7 | export declare class ConfigurationLayer extends ResourceAwareConstruct { 8 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 9 | private createParameter; 10 | } 11 | -------------------------------------------------------------------------------- /simulator/updateapiendpoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | ## 5 | # This script updates the simulator file with api endpoint 6 | # 7 | ## 8 | apigtw=$(eval $(echo "aws cloudformation list-exports --query 'Exports[?contains(ExportingStackId,\`$envname\`) && contains(Name,\`apigtw\`)].Value | [0]' --region ${AWS_REGION} | xargs -I {} echo {}")) 9 | apigtw=${apigtw::-1} 10 | echo Updating $apigtw 11 | sed -i -r "s##$apigtw#" simulator.yaml -------------------------------------------------------------------------------- /infrastructure/enabledevopsguru.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | echo ENABLING AMAZON DEVOPS GURU 6 | echo this must run from the 'infrastructure' folder 7 | echo ############################# 8 | ## configuring 9 | aws devops-guru update-resource-collection --region ${AWS_REGION} --action ADD --resource-collection '{"Tags": [{"AppBoundaryKey": "devops-guru-aiops", "TagValues": ["serverless"]}]}'; 10 | echo AMAZON DEVOPS GURU ENABLED 11 | echo ### DONE 12 | -------------------------------------------------------------------------------- /infrastructure/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | SKIPINPUT=${1:-N} 6 | echo CONFIGURING THE ENVIRONMENT 7 | echo this must run from the 'infrastructure' folder 8 | echo ############################# 9 | ## installing 10 | source ./update-upgrade-install.sh 11 | ## Calling the environment configuration 12 | 13 | if [ "$SKIPINPUT" == "N" ]; then 14 | source ../envname.sh 15 | else 16 | source ../envname.sh skipinput 17 | fi 18 | 19 | echo ### DONE -------------------------------------------------------------------------------- /infrastructure/cdk/lib/nrta.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { ParameterAwareProps, IParameterAwareProps } from '../lib/resourceawarestack'; 4 | 5 | 6 | export class NRTAProps extends ParameterAwareProps { 7 | 8 | constructor(props?: IParameterAwareProps) { 9 | super(props); 10 | } 11 | 12 | getBucketNames() : string[] { 13 | let result : string[] = []; 14 | result.push((this.getApplicationName()+'.raw').toLowerCase()); 15 | result.push((this.getApplicationName()+'.app').toLowerCase()); 16 | return result; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /application/game/css/game.css: -------------------------------------------------------------------------------- 1 | /* Styling needed for a fullscreen canvas and no scrollbars. */ 2 | body, html { 3 | width: 100%; 4 | height: 100%; 5 | margin: 0; 6 | padding: 0; 7 | overflow: hidden; 8 | } 9 | 10 | #wavefield { 11 | width:100%; 12 | height:100%; 13 | z-index: -1; 14 | position: absolute; 15 | left: 0px; 16 | top: 0px; 17 | } 18 | 19 | #gamecontainer { 20 | width: 800px; 21 | margin-left: auto; 22 | margin-right: auto; 23 | } 24 | 25 | #gamecanvas { 26 | width: 800px; 27 | height: 600px; 28 | } 29 | 30 | #info { 31 | width: 800px; 32 | margin-left: auto; 33 | margin-right: auto; 34 | } -------------------------------------------------------------------------------- /infrastructure/cdk/lib/util/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Utils { 2 | static bucketExists(bucketName: string): Promise; 3 | static checkforExistingBuckets(listOfBuckets: string[]): Promise; 4 | /** 5 | * Hashes the contents of a file or directory. If the argument is a directory, 6 | * it is assumed not to contain symlinks that would result in a cyclic tree. 7 | * 8 | * @param fileOrDir the path to the file or directory that should be hashed. 9 | * 10 | * @returns a SHA256 hash, base-64 encoded. 11 | * 12 | * source: https://github.com/awslabs/aws-delivlib/blob/master/lib/util.ts 13 | */ 14 | static hashFileOrDirectory(fileOrDir: string): string; 15 | } 16 | -------------------------------------------------------------------------------- /infrastructure/cdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target":"ES2018", 4 | "module": "commonjs", 5 | "lib": ["es2016", "es2017.object", "es2017.string"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization":false 20 | }, 21 | "exclude": ["cdk.out"] 22 | } -------------------------------------------------------------------------------- /application/scoreboard/css/scoreboard.css: -------------------------------------------------------------------------------- 1 | #scoreboardtable { 2 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 3 | border-collapse: collapse; 4 | width: 100%; 5 | font-size: 20px; 6 | } 7 | 8 | #scoreboardtable td, #scoreboardtable th { 9 | border: 1px solid #ddd; 10 | padding: 10pt; 11 | } 12 | 13 | #scoreboardtable th { 14 | padding-top: 12px; 15 | padding-bottom: 12px; 16 | text-align: left; 17 | background-color: #FF9900; 18 | color: white; 19 | } 20 | 21 | #scoreboardtable td { 22 | font-size: 110%; 23 | font-weight: bolder 24 | } 25 | 26 | #scoreboardtable tr:nth-child(even){background-color: #f2f2f2;} 27 | 28 | #scoreboardtable tr:hover {background-color: #ddd;} -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/storageLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | /** 4 | * StorageLayer is a construct that describes the required resources 5 | * to store the static data. That includes both S3 and SystemsManager. 6 | */ 7 | export declare class StorageLayer extends ResourceAwareConstruct { 8 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 9 | /** 10 | * This function receives the desired bucket configuration 11 | * and then creates (or imports) the bucket 12 | */ 13 | private createBucket; 14 | createBuckets(): void; 15 | getRawDataBucketArn(): string; 16 | } 17 | -------------------------------------------------------------------------------- /application/resources/css/typeography.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | line-height: 20px; 5 | color: #333333; 6 | } 7 | 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6 { 14 | margin: 10px 0; 15 | font-family: inherit; 16 | font-weight: bold; 17 | line-height: 20px; 18 | color: inherit; 19 | text-rendering: optimizelegibility; 20 | } 21 | 22 | h1, 23 | h2, 24 | h3 { 25 | line-height: 40px; 26 | } 27 | 28 | h1 { 29 | font-size: 38.5px; 30 | } 31 | 32 | h2 { 33 | font-size: 31.5px; 34 | } 35 | 36 | h3 { 37 | font-size: 24.5px; 38 | } 39 | 40 | h4 { 41 | font-size: 17.5px; 42 | } 43 | 44 | h5 { 45 | font-size: 14px; 46 | } 47 | 48 | h6 { 49 | font-size: 11.9px; 50 | } -------------------------------------------------------------------------------- /application/scoreboard/css/typeography.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | line-height: 20px; 5 | color: #333333; 6 | } 7 | 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6 { 14 | margin: 10px 0; 15 | font-family: inherit; 16 | font-weight: bold; 17 | line-height: 20px; 18 | color: inherit; 19 | text-rendering: optimizelegibility; 20 | } 21 | 22 | h1, 23 | h2, 24 | h3 { 25 | line-height: 40px; 26 | } 27 | 28 | h1 { 29 | font-size: 38.5px; 30 | } 31 | 32 | h2 { 33 | font-size: 31.5px; 34 | } 35 | 36 | h3 { 37 | font-size: 24.5px; 38 | } 39 | 40 | h4 { 41 | font-size: 17.5px; 42 | } 43 | 44 | h5 { 45 | font-size: 14px; 46 | } 47 | 48 | h6 { 49 | font-size: 11.9px; 50 | } -------------------------------------------------------------------------------- /application/resources/js/gameutils.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | 5 | class GameUtils { 6 | 7 | static scoreboardSortingFunction(playerA, playerB) { 8 | let result = null; 9 | if (playerB.Score != playerA.Score) result = playerB.Score - playerA.Score; // order per score descending 10 | else if (playerA.Lives != playerB.Lives) result = playerB.Lives - playerA.Lives; // order per lives descending 11 | else if (playerA.Shots != playerB.Shots) result = playerA.Shots - playerB.Shots; // order per shots ascending 12 | else result = ((playerA.Nickname > playerB.Nickname) ? 1 : -1); // deuce: order per nickname ascending 13 | return result; 14 | }; 15 | } -------------------------------------------------------------------------------- /application/resources/css/form.css: -------------------------------------------------------------------------------- 1 | input[type=text], select { 2 | width: 100%; 3 | padding: 12px 20px; 4 | margin: 8px 0; 5 | display: inline-block; 6 | border: 1px solid #ccc; 7 | border-radius: 4px; 8 | box-sizing: border-box; 9 | } 10 | 11 | input[type=password], select { 12 | width: 100%; 13 | padding: 12px 20px; 14 | margin: 8px 0; 15 | display: inline-block; 16 | border: 1px solid #ccc; 17 | border-radius: 4px; 18 | box-sizing: border-box; 19 | } 20 | 21 | input[type=submit] { 22 | width: 100%; 23 | background-color: #4CAF50; 24 | color: white; 25 | padding: 14px 20px; 26 | margin: 8px 0; 27 | border: none; 28 | border-radius: 4px; 29 | cursor: pointer; 30 | } 31 | 32 | input[type=submit]:hover { 33 | background-color: #45a049; 34 | } -------------------------------------------------------------------------------- /simulator/createsession.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | SESSIONDATA=${1:-defaultSession} 6 | echo Creating session 7 | echo ############################# 8 | ## creating 9 | echo $SESSIONDATA 10 | if [ $SESSIONDATA == "defaultSession" ]; then 11 | currentDate="$(date '+%Y-%m-%dT%H:%M:%S.%3NZ')" 12 | echo $currentDate 13 | sessionId="$(date '+%Y-%m-%dT%H:%M:%S')" 14 | echo $sessionId 15 | sessionData="{\"GameType\":\"MULTIPLE_TRIALS\",\"SessionId\":\"$sessionId\",\"OpeningTime\":\"$currentDate\",\"TotalSeats\":150}" 16 | else 17 | sessionData=$SESSIONDATA 18 | fi 19 | aws ssm put-parameter --name "/$envnameLowercase/session" --type "String" --value "$sessionData" --overwrite --region ${AWS_REGION} 20 | echo ### DONE -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/processingLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | import Lambda = require('aws-cdk-lib/aws-lambda'); 4 | export declare class ProcessingLayer extends ResourceAwareConstruct { 5 | private allocateFunction; 6 | getAllocateFunctionArn(): string; 7 | getAllocateFunctionRef(): Lambda.Function; 8 | private deallocateFunction; 9 | getDeallocateFunctionArn(): string; 10 | private scoreboardFunction; 11 | getScoreboardFunctionArn(): string; 12 | getScoreboardFunctionRef(): Lambda.Function; 13 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 14 | private getAllocateGamerFunction; 15 | private getDeallocateGamerFunction; 16 | private getScoreboardFunction; 17 | } 18 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/ingestionConsumptionLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | import KDS = require('aws-cdk-lib/aws-kinesis'); 4 | import KDF = require('aws-cdk-lib/aws-kinesisfirehose'); 5 | export declare class IngestionConsumptionLayer extends ResourceAwareConstruct { 6 | kinesisStreams: KDS.IStream; 7 | kinesisFirehose: KDF.CfnDeliveryStream; 8 | private rawbucketarn; 9 | private userpool; 10 | private api; 11 | private KINESIS_INTEGRATION; 12 | private FIREHOSE; 13 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 14 | createKinesis(props: IParameterAwareProps): void; 15 | createAPIGateway(props: IParameterAwareProps): void; 16 | updateUsersRoles(props: IParameterAwareProps): void; 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE-SAMPLECODE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 10 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 11 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 12 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 13 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 14 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/websocketLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | import Lambda = require('aws-cdk-lib/aws-lambda'); 4 | export declare class WebSocketLayer extends ResourceAwareConstruct { 5 | private webSocketConnectFunction; 6 | getWebSocketFunctionArn(): string; 7 | getWebSocketFunctionRef(): Lambda.Function; 8 | private webSocketSynchronizeFunction; 9 | getWebSocketSynchronizeFunctionArn(): string; 10 | getWebSocketSynchronizeFunctionRef(): Lambda.Function; 11 | private webSocketDisconnectFunction; 12 | getWebSocketDisconnectFunctionArn(): string; 13 | getWebSocketDisconnectFunctionRef(): Lambda.Function; 14 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 15 | private getWebSocketConnectFunction; 16 | private getWebSocketSynchronizeFunction; 17 | private getWebSocketDisconnectFunction; 18 | } 19 | -------------------------------------------------------------------------------- /infrastructure/cdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underthesea.workshop", 3 | "version": "1.0.0", 4 | "description": "The foundational code supporting the AWS Under The Sea", 5 | "license": "MIT-0", 6 | "bin": { 7 | "alienattack.workshop": "bin/cdk.js" 8 | }, 9 | "scripts": { 10 | "build": "tsc", 11 | "watch": "tsc -w", 12 | "cdk": "cdk", 13 | "test": "jest" 14 | }, 15 | "devDependencies": { 16 | "aws-cdk-lib": "^2.0.0", 17 | "constructs": "^10.0.0", 18 | "@types/jest": "^24.0.18", 19 | "jest": "^24.9.0", 20 | "ts-node": "^9.1.1", 21 | "typescript": "^4.2.4" 22 | }, 23 | "peerDependencies": { 24 | "aws-cdk-lib": "^2.0.0", 25 | "constructs": "^10.0.0" 26 | }, 27 | "jest": { 28 | "moduleFileExtensions": [ 29 | "js" 30 | ] 31 | }, 32 | "dependencies": { 33 | "@types/node": "^17.0.5", 34 | "aws-cdk-lib": "^2.0.0", 35 | "aws-sdk": "^2.1039.0", 36 | "constructs": "^10.0.0", 37 | "uuid": "^8.3.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /infrastructure/update-upgrade-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | echo Updating the attached instance 6 | sudo yum update -y 7 | echo -- 8 | echo Updating node to the latest version 9 | #node_version=$(nvm ls-remote --lts | grep Latest | tail -1 | grep -o 'v[.0-9]*' | sed 's/\x1b\[[0-9;]*m//g') 10 | #node_version=${node_version:1} 11 | node_version="14.15.0" 12 | nvm install $node_version 13 | nvm alias latest $node_version 14 | nvm alias default latest 15 | nvm use $node_version 16 | echo -- 17 | echo Installing Typescript 18 | npm install -g typescript@4.2.4 19 | echo -- 20 | echo Installing CDK 21 | npm install -g aws-cdk@2.3.0 22 | echo -- 23 | echo Bootstraping CDK 24 | account=${ACCOUNT_ID} 25 | region=${AWS_REGION} 26 | cdk bootstrap $account/$region 27 | echo -- 28 | echo Installing dependencies 29 | cd cdk 30 | npm install 31 | [[ $(grep "nvm use latest" ~/.bash_profile) ]] || echo nvm use latest >> ~/.bash_profile 32 | -------------------------------------------------------------------------------- /application/resources/css/core.css: -------------------------------------------------------------------------------- 1 | /* core.css */ 2 | /* lean and simple css reset, based on Tantek Celik's reset - see comments below. */ 3 | /* (c) 2004-2010 Tantek Çelik. Some Rights Reserved. http://tantek.com */ 4 | /* This style sheet is licensed under a Creative Commons License. */ 5 | /* http://creativecommons.org/licenses/by/2.0 */ 6 | 7 | /* Purpose: undo some of the default styling of common browsers */ 8 | 9 | 10 | :link,:visited,ins { text-decoration:none } 11 | nav ul,nav ol { list-style:none } 12 | dl,ul,ol,li, 13 | h1,h2,h3,h4,h5,h6, 14 | html,body,pre,p,blockquote, 15 | form,fieldset,input,label 16 | { margin:0; padding:0 } 17 | abbr, img, object, 18 | a img,:link img,:visited img, 19 | a object,:link object,:visited object 20 | { border:0 } 21 | address,abbr { font-style:normal } 22 | iframe:not(.auto-link) { display:none ! important; visibility:hidden ! important; margin-left: -10000px ! important } 23 | a { 24 | color: #0088cc; 25 | text-decoration: none; 26 | } 27 | 28 | a:hover, 29 | a:focus { 30 | color: #005580; 31 | text-decoration: underline; 32 | } -------------------------------------------------------------------------------- /infrastructure/diverse/howtotestyourapi.txt: -------------------------------------------------------------------------------- 1 | #################################################### 2 | # 3 | # HOW TO TEST THE UNDER THE SEA APIs USING CURL 4 | # 5 | #################################################### 6 | 7 | 1. Login into to Game or Scoreboard application. 8 | 9 | 2. Retrieve the JWTToken 10 | 2.1. Go to the Console of your browser 11 | 2.2. Retrieve the Java Web Token (JWT) and save it on a helper text file. 12 | 2.2.1. If you are using the Game application, type in the console: game.awsfacade.cognitoFacade.sessionData.idToken.jwtToken 13 | 2.2.2. If you are using the Scoreboard application, type in the console: Scoreboard.awsfacade.cognitoFacade.sessionData.idToken.jwtToken 14 | 15 | 3. Execute the API using curl and passing the parameters to your API. 16 | 17 | Example: 18 | Supposing that your token is 1a2b3c, for a GET you can use 19 | curl --header "Authorization : 1a2b3c" https://myapiid.execute-api.us-west-2.amazonaws.com/prod/v1/topxstatistics?sessionId=TheTestSession 20 | 21 | For a post you should add the --data parameter to the curl command and, well, some other things. Try it and check what you can learn. 22 | 23 | You can also test the API with tools like POSTMAN. 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/postRegistration/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | const AWS = require('aws-sdk'); 4 | 5 | exports.handler = (event, _, callback) => { 6 | console.log(event); 7 | if (event && event.userPoolId && event.userName) { 8 | if (['PostConfirmation_ConfirmSignUp', 'PostConfirmation_AdminConfirmSignUp' ].indexOf(event.triggerSource) > -1) { 9 | var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider(); 10 | var params = { 11 | GroupName: 'Players', 12 | UserPoolId: event.userPoolId, 13 | Username: event.userName 14 | }; 15 | cognitoidentityserviceprovider.adminAddUserToGroup(params, function(err, data) { 16 | if (err) { 17 | console.log(err); 18 | } 19 | else { 20 | console.log('Successfully added to group Players'); 21 | callback(null,event); 22 | } 23 | }); 24 | } else callback(null,event); 25 | } 26 | else { 27 | var err = "error in event structure"; 28 | console.log(err); 29 | callback(err,event); 30 | } 31 | }; -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/securityLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack'; 3 | import { Function } from 'aws-cdk-lib/aws-lambda'; 4 | import { Role } from 'aws-cdk-lib/aws-iam'; 5 | import Cognito = require('aws-cdk-lib/aws-cognito'); 6 | export declare class SecurityLayer extends ResourceAwareConstruct { 7 | userPool: Cognito.UserPool; 8 | identityPool: Cognito.CfnIdentityPool; 9 | userPoolClient: Cognito.UserPoolClient; 10 | playersRole: Role; 11 | managersRole: Role; 12 | unauthenticatedRole: Role; 13 | postRegistrationTriggerFunction: Function; 14 | postRegistrationTriggerFunctionRole: Role; 15 | getUserPoolId(): string; 16 | getUserPoolUrl(): string; 17 | getUserPoolArn(): string; 18 | getUserPoolClient(): Cognito.UserPoolClient; 19 | getUserPoolClientId(): string; 20 | getIdentityPool(): Cognito.CfnIdentityPool; 21 | getIdentityPoolId(): string; 22 | constructor(parent: Construct, name: string, props: IParameterAwareProps); 23 | private createUserPool; 24 | private createIdentityPool; 25 | private createUserPoolGroups; 26 | private configureIdentityPoolRoles; 27 | private creatPostRegistrationLambdaTrigger; 28 | } 29 | -------------------------------------------------------------------------------- /infrastructure/envname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | ## 6 | # Automatically creates the environment name 7 | ## 8 | 9 | SKIPINPUT=${1:-N} 10 | 11 | setColor=$(tput setaf 3) # Green 12 | resetColor=$(tput sgr0) # Text reset 13 | echo "**************************************************************" 14 | echo "DEFINING YOUR EXCLUSIVE ENVIRONMENT NAME" 15 | echo "" 16 | echo "When we define the exclusive environment name all resources" 17 | echo "in your infrastructure will have their IDs prefixed by this" 18 | echo "environment name." 19 | echo "This is a workaround to guarantee that this workshop can run" 20 | echo "with multiple users under a single AWS account" 21 | echo "**************************************************************" 22 | echo 23 | if [ "$SKIPINPUT" == "N" ]; then 24 | read -p "What are your initials? " initials 25 | initials=$(echo $initials | tr -cd "[a-zA-Z0-9]\n" | tr 'A-Z' 'a-z' ) 26 | else 27 | initials="AIOPS" 28 | fi 29 | 30 | 31 | ### Generating a random 6 hexadecimal digit code, like a0b1c2 32 | randomcode=$(openssl rand -hex 3) 33 | ### Defining envname - envname will be, by default, in uppercase 34 | envname=$(echo $initials"aaa"$randomcode | tr 'a-z' 'A-Z') 35 | envnameLowercase=$(echo $envname | tr 'A-Z' 'a-z' ) 36 | echo export envname=$envname >> ~/.bash_profile 37 | echo export envnameLowercase=$envnameLowercase >> ~/.bash_profile 38 | echo "Your environment name was defined as"$setColor $envname $resetColor -------------------------------------------------------------------------------- /simulator/simulator.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | target: "" 3 | phases: 4 | - duration: 300 5 | arrivalRate: 1 6 | payload: 7 | path: "players.csv" 8 | fields: 9 | - "username" 10 | - "password" 11 | skipHeader: true 12 | processor: "./processor.js" 13 | 14 | before: 15 | flow: 16 | - log: "User pool data" 17 | - get: 18 | url: "/config" 19 | afterResponse: "getUserPoolData" 20 | 21 | 22 | scenarios: 23 | - beforeScenario: "setJWTToken" 24 | flow: 25 | - post: 26 | url: "/allocate" 27 | afterResponse: "logAllocationResponse" 28 | headers: 29 | Authorization: "{{ jwttoken }}" 30 | json: 31 | Username: "{{ username }}" 32 | - get: 33 | url: "/session" 34 | afterResponse: "getSessionId" 35 | headers: 36 | Authorization: "{{ jwttoken }}" 37 | - post: 38 | url: "/updatestatus" 39 | beforeRequest: 40 | - "setDateTime" 41 | - "setGameData" 42 | afterResponse: "logUpdateStatusResponse" 43 | headers: 44 | Authorization: "{{ jwttoken }}" 45 | json: 46 | Timestamp: "{{ currentdatetime }}" 47 | SessionId: "{{ sessionid }}" 48 | Nickname: "{{ username }}" 49 | Lives: "{{ lives }}" 50 | Score: "{{ score }}" 51 | Shots: "{{ shots }}" 52 | Level: "{{ level }}" 53 | 54 | 55 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/configurationLayer.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack' 4 | import { Construct } from 'constructs'; 5 | import * as ssm from 'aws-cdk-lib/aws-ssm'; 6 | 7 | 8 | /** 9 | * Configuration Layer is a construct designed to acquire and store configuration 10 | * data to be used by the system 11 | */ 12 | export class ConfigurationLayer extends ResourceAwareConstruct { 13 | 14 | constructor(parent: Construct, name: string, props: IParameterAwareProps) { 15 | super(parent, name, props); 16 | if (props) { 17 | let parametersToBeCreated = props.getParameter('ssmParameters'); 18 | if (parametersToBeCreated) { 19 | parametersToBeCreated.forEach( (v : any, k : string) => { 20 | let parameter = this.createParameter(props.getApplicationName(),k, v); 21 | this.addResource('parameter.'+k,parameter); 22 | }); 23 | } 24 | } 25 | } 26 | 27 | private createParameter(appName : string, keyName: string, value : string) { 28 | let baseName : string = '/'+ appName.toLowerCase(); 29 | let parameter = new ssm.StringParameter(this, 'SSMParameter'+appName+keyName, { 30 | parameterName : baseName + '/'+keyName.toLowerCase(), 31 | stringValue: value 32 | }); 33 | return parameter; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /application/scoreboard/js/scoreboard.dynamoinherited.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | class ScoreboardDynamo extends Scoreboard { 4 | 5 | 6 | initializeAWSServices() { 7 | this.DynamoDB = new AWS.DynamoDB.DocumentClient(); 8 | } 9 | 10 | run() { 11 | var self = this; 12 | var preLoopFunction = function () { 13 | self.kinesis.getShardIterator({ 14 | StreamName: self.appName+'_InputStream', 15 | ShardId: 'shardId-000000000000', 16 | ShardIteratorType: 'LATEST' 17 | }, function (err, data) { 18 | if (err) console.log('ERROR getShardIterator:', err); 19 | else { 20 | self.currentShardIterator = data.ShardIterator; 21 | } 22 | }); 23 | }; 24 | super.run(preLoopFunction, 1000); 25 | } 26 | 27 | retrieveData() { 28 | var getParams = { 29 | "TableName": this.appName+"Session", 30 | "Key": { "SessionId": this.sessionDetails.SessionId }, 31 | "ConsistentRead": "true" 32 | }; 33 | var self = this; 34 | this.DynamoDB.get(getParams, function (err, data) { 35 | if (err) { 36 | console.log("ERROR"); 37 | console.log(JSON.stringify(err)) 38 | } else { 39 | data.Item.Scoreboard.forEach(function (record) { 40 | self.updateArray(record); 41 | });; 42 | } 43 | }); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /application/resources/css/infopages.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Courier New"; 3 | background-color: #1da2d8; 4 | color: white; 5 | } 6 | 7 | img { 8 | display: block; 9 | max-width: 40%; 10 | max-height: 40%; 11 | margin-left: auto; 12 | margin-right: auto; 13 | } 14 | 15 | p { 16 | margin-left: 3%; 17 | margin-right: 3%; 18 | text-indent: 35px; 19 | } 20 | 21 | ul { 22 | margin-left: 5%; 23 | margin-right: 5% 24 | } 25 | 26 | ol { 27 | margin-left: 5%; 28 | margin-right: 5%; 29 | } 30 | 31 | li { 32 | margin-bottom: 1%; 33 | } 34 | 35 | h1 { 36 | margin: 0%; 37 | text-align: center; 38 | } 39 | 40 | h2 { 41 | margin: 2%; 42 | text-align: center 43 | } 44 | 45 | h3 { 46 | margin: 1%; 47 | text-align: center 48 | } 49 | 50 | .boldText { 51 | color: #FF9900; 52 | font-weight: bold; 53 | } 54 | 55 | a:link { 56 | text-decoration: none; 57 | color: #FF9900; 58 | } 59 | 60 | a:visited { 61 | text-decoration: none; 62 | color: #FF9900; 63 | } 64 | 65 | a:hover { 66 | text-decoration: none; 67 | color: #ff5555 68 | } 69 | 70 | .btnGroup { 71 | width: 300px; 72 | align-content: center; 73 | margin: auto; 74 | } 75 | 76 | .btnGroup input { 77 | background-color: #FF9900; 78 | /* Green */ 79 | border: none; 80 | color: white; 81 | padding: 16px 32px; 82 | text-align: center; 83 | text-decoration: none; 84 | display: inline-block; 85 | font-size: 16px; 86 | margin: 4px 2px; 87 | -webkit-transition-duration: 0.4s; 88 | /* Safari */ 89 | transition-duration: 0.4s; 90 | cursor: pointer; 91 | border-radius: 12px; 92 | text-align: center; 93 | } 94 | 95 | .btnGroup input:hover { 96 | background-color: #ff5555; 97 | color: white; 98 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /application/resources/js/awsconfig.js 8 | /infrastructure/cdk/bin 9 | /infrastructure/cdk/lib 10 | /infrastructure/cdk/test 11 | /infrastructure/cdk/cdk.out 12 | /infrastructure/output.yaml 13 | /simulator/players.csv 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # IDEs and editors 40 | .idea 41 | .project 42 | .classpath 43 | .c9/ 44 | *.launch 45 | .settings/ 46 | *.sublime-workspace 47 | 48 | # IDE - VSCode 49 | .vscode/* 50 | !.vscode/settings.json 51 | !.vscode/tasks.json 52 | !.vscode/launch.json 53 | !.vscode/extensions.json 54 | 55 | # misc 56 | .sass-cache 57 | connect.lock 58 | typings 59 | 60 | # Logs 61 | logs 62 | *.log 63 | npm-debug.log* 64 | yarn-debug.log* 65 | yarn-error.log* 66 | 67 | # Dependency directories 68 | node_modules/ 69 | jspm_packages/ 70 | 71 | # Optional npm cache directory 72 | .npm 73 | 74 | # Optional eslint cache 75 | .eslintcache 76 | 77 | # Optional REPL history 78 | .node_repl_history 79 | 80 | # Output of 'npm pack' 81 | *.tgz 82 | 83 | # Yarn Integrity file 84 | .yarn-integrity 85 | 86 | # dotenv environment variables file 87 | .env 88 | 89 | # next.js build output 90 | .next 91 | 92 | # Lerna 93 | lerna-debug.log 94 | 95 | # System Files 96 | .DS_Store 97 | Thumbs.db -------------------------------------------------------------------------------- /application/resources/css/modaldialog.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .modalDialog { 4 | position: fixed; 5 | font-family: Arial, Helvetica, sans-serif; 6 | top: 0; 7 | right: 0; 8 | bottom: 0; 9 | left: 0; 10 | background: rgba(0,0,0,0.0); 11 | z-index: -99999; 12 | opacity:0; 13 | -webkit-transition: opacity 400ms ease-in; 14 | -moz-transition: opacity 400ms ease-in; 15 | transition: opacity 400ms ease-in; 16 | pointer-events: none; 17 | } 18 | 19 | .button { 20 | display: inline-block; 21 | padding: 10px 20px; 22 | cursor: pointer; 23 | text-align: center; 24 | text-decoration: none; 25 | outline: none; 26 | color: #fff; 27 | background-color: #FF9900; 28 | border: none; 29 | border-radius: 15px; 30 | box-shadow: 0 9px #999; 31 | } 32 | 33 | .button:hover {background-color: #ec7211} 34 | 35 | .button:active { 36 | background-color: #FF9900; 37 | box-shadow: 0 5px #666; 38 | transform: translateY(4px); 39 | } 40 | 41 | .modalDialog:target { 42 | opacity:1; 43 | pointer-events: auto; 44 | } 45 | 46 | .modalDialog > div { 47 | width: 400px; 48 | position: relative; 49 | margin: 10% auto; 50 | padding: 5px 20px 13px 20px; 51 | border-radius: 10px; 52 | background: #fff; 53 | background: -moz-linear-gradient(#fff, #999); 54 | background: -webkit-linear-gradient(#fff, #999); 55 | background: -o-linear-gradient(#fff, #999); 56 | } 57 | 58 | .close { 59 | background: #606061; 60 | color: #FFFFFF; 61 | line-height: 25px; 62 | position: absolute; 63 | right: -12px; 64 | text-align: center; 65 | top: -10px; 66 | width: 24px; 67 | text-decoration: none; 68 | font-weight: bold; 69 | -webkit-border-radius: 12px; 70 | -moz-border-radius: 12px; 71 | border-radius: 12px; 72 | -moz-box-shadow: 1px 1px 3px #000; 73 | -webkit-box-shadow: 1px 1px 3px #000; 74 | box-shadow: 1px 1px 3px #000; 75 | } 76 | 77 | \ 78 | 79 | .close:hover { background: #00d9ff; } 80 | 81 | 82 | -------------------------------------------------------------------------------- /infrastructure/cicd/buildspec.yaml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | #env: 4 | #variables: 5 | # key: "value" 6 | # key: "value" 7 | #parameter-store: 8 | # key: "value" 9 | # key: "value" 10 | #secrets-manager: 11 | # key: secret-id:json-key:version-stage:version-id 12 | # key: secret-id:json-key:version-stage:version-id 13 | #exported-variables: 14 | # - variable 15 | # - variable 16 | #git-credential-helper: yes 17 | #batch: 18 | #fast-fail: true 19 | #build-list: 20 | #build-matrix: 21 | #build-graph: 22 | phases: 23 | install: 24 | runtime-versions: 25 | nodejs: 14 26 | commands: 27 | - npm install -g typescript@4.2.4 28 | - npm install -g aws-cdk@2.3.0 29 | pre_build: 30 | commands: 31 | # checking cdk cli version 32 | - cdk --version 33 | # it starts on the root folder for the project 34 | - cd ./infrastructure/cdk 35 | - npm install 36 | ### Generating a random 6 hexadecimal digit code, like a0b1c2 37 | - randomcode=$(openssl rand -hex 3) 38 | ### Defining envname - envname will be, by default, in uppercase 39 | - curdate=$(echo $(date +%s)) 40 | - export envname=$(echo TEST$curdate"aaa"$randomcode | tr 'a-z' 'A-Z') 41 | - export envnameLowercase=$(echo $envname | tr 'A-Z' 'a-z' ) 42 | build: 43 | commands: 44 | - npm run build 45 | post_build: 46 | commands: 47 | # if you need to test for an error, uncomment the line below 48 | #- raise error "FORCED EXIT" 49 | - npm run test 50 | #- cdk synth --app "node ./bin/cdk.js" -c envname=$envname 51 | - cdk synth -c envname=$envname > output.yaml 52 | #reports: 53 | #report-name-or-arn: 54 | #files: 55 | # - location 56 | # - location 57 | #base-directory: location 58 | #discard-paths: yes 59 | #file-format: JunitXml | CucumberJson 60 | #artifacts: 61 | #files: 62 | # - location 63 | # - location 64 | #name: $(date +%Y-%m-%d) 65 | #discard-paths: yes 66 | #base-directory: location 67 | #cache: 68 | #paths: 69 | # - paths -------------------------------------------------------------------------------- /infrastructure/resize.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Specify the desired volume size in GiB as a command line argument. If not specified, default to 20 GiB. 4 | SIZE=${1:-20} 5 | 6 | # Get the ID of the environment host Amazon EC2 instance. 7 | INSTANCEID=$(curl http://169.254.169.254/latest/meta-data/instance-id) 8 | REGION=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/\(.*\)[a-z]/\1/') 9 | 10 | # Get the ID of the Amazon EBS volume associated with the instance. 11 | VOLUMEID=$(aws ec2 describe-instances \ 12 | --instance-id $INSTANCEID \ 13 | --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" \ 14 | --output text \ 15 | --region $REGION) 16 | 17 | # Resize the EBS volume. 18 | aws ec2 modify-volume --volume-id $VOLUMEID --size $SIZE --region $REGION 19 | 20 | # Wait for the resize to finish. 21 | while [ \ 22 | "$(aws ec2 describe-volumes-modifications \ 23 | --volume-id $VOLUMEID \ 24 | --filters Name=modification-state,Values="optimizing","completed" \ 25 | --query "length(VolumesModifications)"\ 26 | --output text --region $REGION)" != "1" ]; do 27 | sleep 1 28 | done 29 | 30 | #Check if we're on an NVMe filesystem 31 | if [[ -e "/dev/xvda" && $(readlink -f /dev/xvda) = "/dev/xvda" ]] 32 | then 33 | # Rewrite the partition table so that the partition takes up all the space that it can. 34 | sudo growpart /dev/xvda 1 35 | 36 | # Expand the size of the file system. 37 | # Check if we're on AL2 38 | STR=$(cat /etc/os-release) 39 | SUB="VERSION_ID=\"2\"" 40 | if [[ "$STR" == *"$SUB"* ]] 41 | then 42 | sudo xfs_growfs -d / 43 | else 44 | sudo resize2fs /dev/xvda1 45 | fi 46 | 47 | else 48 | # Rewrite the partition table so that the partition takes up all the space that it can. 49 | sudo growpart /dev/nvme0n1 1 50 | 51 | # Expand the size of the file system. 52 | # Check if we're on AL2 53 | STR=$(cat /etc/os-release) 54 | SUB="VERSION_ID=\"2\"" 55 | if [[ "$STR" == *"$SUB"* ]] 56 | then 57 | sudo xfs_growfs -d / 58 | else 59 | sudo resize2fs /dev/nvme0n1p1 60 | fi 61 | fi -------------------------------------------------------------------------------- /simulator/createusers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | ## 5 | # This script creates test users with password 6 | # 7 | ## 8 | SKIPINPUT=${1:-N} 9 | _DEBUG="on" 10 | 11 | function DEBUG() { 12 | [ "$_DEBUG" == "on" ] && $@ 13 | } 14 | 15 | function removeQuotes() { 16 | retval=$1 17 | retval=${retval#\"} 18 | retval=${retval%\"} 19 | echo "$retval" 20 | } 21 | 22 | echo "**************************************************************" 23 | echo "This function will create and signup players" 24 | echo "**************************************************************" 25 | echo 26 | 27 | if [ "$SKIPINPUT" == "N" ]; then 28 | read -p "Number of players:" players 29 | else 30 | players=20 31 | fi 32 | 33 | 34 | echo "#### Creating users in AWS Cognito..." 35 | echo "username,password,emailid" >> players.csv 36 | 37 | for (( i = 1; i <= $players; i++ )); do 38 | userid="player$i" 39 | userpassword="player$i@password" 40 | emailid="player$i@example.com" 41 | 42 | echo "$userid,$userpassword,$emailid" >>players.csv 43 | 44 | echo "#### Creating the user $userid" 45 | getUserPoolId=$(echo "aws cognito-idp list-user-pools --query 'UserPools[?Name == \`"$envname"\`]|[0].Id' --max-results=20 --region ${AWS_REGION}") 46 | userPoolId=$( removeQuotes $( eval $getUserPoolId ) ) 47 | # create the user 48 | aws cognito-idp admin-create-user --user-pool-id $userPoolId --username $userid --user-attributes Name=email,Value=$emailid Name=email_verified,Value=true Name=website,Value=aws.amazon.com --region ${AWS_REGION} 49 | aws cognito-idp admin-enable-user --user-pool-id $userPoolId --username $userid --region ${AWS_REGION} 50 | aws cognito-idp admin-set-user-password --user-pool-id $userPoolId --username $userid --password $userpassword --permanent --region ${AWS_REGION} 51 | # add the user to the manager's group 52 | aws cognito-idp admin-add-user-to-group --user-pool-id $userPoolId --username $userid --group-name Players --region ${AWS_REGION} 53 | # deploy the fromt-end 54 | done 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/nrta.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.NRTAProps = void 0; 4 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // SPDX-License-Identifier: MIT-0 6 | const resourceawarestack_1 = require("../lib/resourceawarestack"); 7 | class NRTAProps extends resourceawarestack_1.ParameterAwareProps { 8 | constructor(props) { 9 | super(props); 10 | } 11 | getBucketNames() { 12 | let result = []; 13 | result.push((this.getApplicationName() + '.raw').toLowerCase()); 14 | result.push((this.getApplicationName() + '.app').toLowerCase()); 15 | return result; 16 | } 17 | } 18 | exports.NRTAProps = NRTAProps; 19 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibnJ0YS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm5ydGEudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscUVBQXFFO0FBQ3JFLGlDQUFpQztBQUNqQyxrRUFBc0Y7QUFHdEYsTUFBYSxTQUFVLFNBQVEsd0NBQW1CO0lBRTlDLFlBQVksS0FBNEI7UUFDcEMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ2pCLENBQUM7SUFFRCxjQUFjO1FBQ1YsSUFBSSxNQUFNLEdBQWMsRUFBRSxDQUFDO1FBQzNCLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxNQUFNLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO1FBQzlELE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxNQUFNLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO1FBQzlELE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7Q0FFTjtBQWJELDhCQWFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IEFtYXpvbi5jb20sIEluYy4gb3IgaXRzIGFmZmlsaWF0ZXMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlULTBcbmltcG9ydCB7IFBhcmFtZXRlckF3YXJlUHJvcHMsIElQYXJhbWV0ZXJBd2FyZVByb3BzIH0gZnJvbSAnLi4vbGliL3Jlc291cmNlYXdhcmVzdGFjayc7XG5cblxuZXhwb3J0IGNsYXNzIE5SVEFQcm9wcyBleHRlbmRzIFBhcmFtZXRlckF3YXJlUHJvcHMge1xuXG4gICAgY29uc3RydWN0b3IocHJvcHM/OiBJUGFyYW1ldGVyQXdhcmVQcm9wcykge1xuICAgICAgICBzdXBlcihwcm9wcyk7XG4gICAgfVxuXG4gICAgZ2V0QnVja2V0TmFtZXMoKSA6IHN0cmluZ1tdIHtcbiAgICAgICAgbGV0IHJlc3VsdCA6IHN0cmluZ1tdID0gW107XG4gICAgICAgIHJlc3VsdC5wdXNoKCh0aGlzLmdldEFwcGxpY2F0aW9uTmFtZSgpKycucmF3JykudG9Mb3dlckNhc2UoKSk7XG4gICAgICAgIHJlc3VsdC5wdXNoKCh0aGlzLmdldEFwcGxpY2F0aW9uTmFtZSgpKycuYXBwJykudG9Mb3dlckNhc2UoKSk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7IFxuICAgICAgfVxuXG59Il19 -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/databaseLayer.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { Construct } from 'constructs'; 4 | import { RemovalPolicy } from 'aws-cdk-lib'; 5 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack' 6 | 7 | import DynamoDB = require('aws-cdk-lib/aws-dynamodb'); 8 | 9 | 10 | export class DatabaseLayer extends ResourceAwareConstruct { 11 | tables : Map = new Map(); 12 | 13 | constructor(parent: Construct, name: string, props: IParameterAwareProps) { 14 | super(parent,name, props); 15 | 16 | let sessionTable = new DynamoDB.Table(this,props.getApplicationName()+'Session', { 17 | tableName : props.getApplicationName()+'Session', 18 | partitionKey : { 19 | name : 'SessionId', 20 | type : DynamoDB.AttributeType.STRING 21 | }, 22 | billingMode : DynamoDB.BillingMode.PAY_PER_REQUEST , 23 | removalPolicy : RemovalPolicy.DESTROY , 24 | pointInTimeRecovery : true 25 | }); 26 | this.addResource('table.session',sessionTable); 27 | 28 | let sessionControlTable = new DynamoDB.Table(this,props.getApplicationName()+'SessionControl', { 29 | tableName : props.getApplicationName()+'SessionControl', 30 | partitionKey : { 31 | name : 'SessionId', 32 | type : DynamoDB.AttributeType.STRING 33 | }, 34 | billingMode : DynamoDB.BillingMode.PAY_PER_REQUEST, 35 | removalPolicy : RemovalPolicy.DESTROY , 36 | pointInTimeRecovery : true 37 | }); 38 | this.addResource('table.sessioncontrol',sessionControlTable); 39 | 40 | let sessionTopXTable = new DynamoDB.Table(this,props.getApplicationName()+'SessionTopX', { 41 | tableName : props.getApplicationName()+'SessionTopX', 42 | partitionKey : { 43 | name : 'SessionId', 44 | type : DynamoDB.AttributeType.STRING 45 | }, 46 | billingMode : DynamoDB.BillingMode.PAY_PER_REQUEST, 47 | removalPolicy : RemovalPolicy.DESTROY , 48 | pointInTimeRecovery : true 49 | }); 50 | this.addResource('table.sessiontopx',sessionTopXTable); 51 | } 52 | } -------------------------------------------------------------------------------- /application/resources/js/modal.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | /** 4 | * Modal 5 | * 6 | * Provides resources for using a pop-up modal dialog. 7 | * One must provide the proper div for the modal, within the html file. 8 | * This div MUST BE of class modalDialog (see modaldialog.css for more info) 9 | */ 10 | function Modal(modalDiv) { 11 | this.modalDiv = modalDiv; 12 | this.modalDivId = this.modalDiv.id; 13 | // hidden at start 14 | this.modalDiv.style.zIndex = -99999; 15 | // Location history, for the case when the user 16 | // wants the window move back to certain location when 17 | // the pop up is closed 18 | this.locationHistory = []; 19 | this.locationHistory.push(window.location); 20 | // configuring ESC to close the pop-up 21 | this.ESCPressed = false; 22 | var self = this; 23 | window.addEventListener("keydown", function keydown(e) { 24 | var keycode = e.which || window.event.keycode; 25 | if(keycode == 27) { 26 | self.ESCPressed = true; 27 | // supress further pressing of ESC 28 | e.preventDefault(); 29 | } 30 | }); 31 | } 32 | 33 | 34 | Modal.prototype.show = function(internalHTMLContent,params) { 35 | this.modalDiv.style.zIndex = 99999; 36 | this.locationHistory.push(window.location); 37 | window.location = "#"+this.modalDivId; 38 | var actionOnClose = ' '; 39 | if (params && params.actionOnClose) 40 | actionOnClose = 'onClick="'+params.actionOnClose+'" '; 41 | this.ESCPressed = false; 42 | this.modalDiv.innerHTML = 43 | '
'+ 44 | 'X' 45 | +internalHTMLContent+ 46 | '
'; 47 | } 48 | 49 | Modal.prototype.setInnerHTML = function(innerHTML) { 50 | this.modalDiv.innerHTML = innerHTML; 51 | } 52 | 53 | Modal.prototype.close = function(callback) { 54 | this.modalDiv.style.zIndex = -99999; 55 | let lastLocation = this.locationHistory.pop(); 56 | window.location = lastLocation; 57 | // This timeout exists only to give time to the zIndex to be updated 58 | setTimeout( function() { 59 | if (callback) callback(); 60 | },10); 61 | } 62 | 63 | Modal.prototype.hide = function() { 64 | this.modalDiv.style.zIndex = -99999; 65 | } -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/contentDeliveryLayer.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { Construct } from 'constructs'; 4 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack' 5 | import { CloudFrontWebDistribution, OriginAccessIdentity } from 'aws-cdk-lib/aws-cloudfront'; 6 | import { Bucket, BucketPolicy} from 'aws-cdk-lib/aws-s3'; 7 | import IAM = require('aws-cdk-lib/aws-iam'); 8 | 9 | 10 | export class ContentDeliveryLayer extends ResourceAwareConstruct { 11 | 12 | constructor(parent: Construct, name: string, props: IParameterAwareProps) { 13 | super(parent, name, props); 14 | this.createDistribution(props); 15 | } 16 | 17 | private createDistribution(props: IParameterAwareProps) { 18 | 19 | let s3BucketOrCnfBucket = props.getParameter('appBucket'); 20 | let appBucket = Bucket.fromBucketName(this, props.getApplicationName()+'ImportedBucket', s3BucketOrCnfBucket.bucketName); 21 | 22 | let cloudFrontAccessIdentity = new OriginAccessIdentity(this,this.properties.getApplicationName()+'CDNAccessId', { 23 | comment : "Under The Sea OAI for "+s3BucketOrCnfBucket.bucketName 24 | }); 25 | appBucket.grantRead(cloudFrontAccessIdentity); 26 | 27 | 28 | let distribution = new CloudFrontWebDistribution(this, props.getApplicationName(),{ 29 | originConfigs : [ 30 | { 31 | s3OriginSource : { 32 | s3BucketSource: appBucket, 33 | originAccessIdentity : cloudFrontAccessIdentity 34 | }, 35 | behaviors : [ {isDefaultBehavior: true}] 36 | } 37 | ] 38 | }); 39 | 40 | 41 | new BucketPolicy(this, props.getApplicationName()+'AppBucketPolicy', { 42 | bucket : appBucket, 43 | }).document.addStatements(new IAM.PolicyStatement({ 44 | actions : [ "s3:GetObject" ], 45 | effect : IAM.Effect.ALLOW, 46 | resources: [ 47 | appBucket.arnForObjects("*") 48 | ], 49 | principals : [ new IAM.ArnPrincipal("arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity "+cloudFrontAccessIdentity.originAccessIdentityName) ] 50 | }) 51 | ); 52 | 53 | this.addResource("cdndomain",distribution.distributionDomainName); 54 | } 55 | } -------------------------------------------------------------------------------- /application/resources/libs/websocketAG.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | function ApiGatewayWebSocket(awsFacade, websocketCallbacks, callback) { 5 | this.messageCallback = websocketCallbacks.messageCallback; 6 | this.closeCallback = websocketCallbacks.closeCallback; 7 | this.errorCallback = websocketCallbacks.errorCallback; 8 | let self = this; 9 | this.URL = null; 10 | awsFacade.getWebSocketEndpoint((err, data) => { 11 | if (err) { 12 | console.log('Error', err); 13 | if (callback) callback(err); 14 | } else { 15 | console.log(data.Error) 16 | if (data.Error || data == '') callback(new Error('WebSocket Param is EMPTY')); 17 | else { 18 | console.log('Success getting websocket URL', data); 19 | self.URL = data; 20 | self.ws = new WebSocket(self.URL); 21 | self.ws.onmessage = ApiGatewayWebSocket.prototype.onMessageListener.bind(self); 22 | self.ws.onclose = ApiGatewayWebSocket.prototype.onCloseListener.bind(self); 23 | self.ws.onerror = ApiGatewayWebSocket.prototype.onErrorListener.bind(self); 24 | ApiGatewayWebSocket.prototype.setURL(self.URL); 25 | } 26 | } 27 | }); 28 | if (callback) callback(); 29 | } 30 | 31 | ApiGatewayWebSocket.prototype.setURL = function(URL) { 32 | this.URL = URL; 33 | } 34 | 35 | ApiGatewayWebSocket.prototype.onMessageListener = function(message) { 36 | if (this.messageCallback) this.messageCallback(message); 37 | } 38 | 39 | ApiGatewayWebSocket.prototype.onErrorListener = function(err) { 40 | if (this.errorCallback) this.errorCallback(err); 41 | } 42 | 43 | ApiGatewayWebSocket.prototype.onCloseListener = function(closeMessage) { 44 | if (this.closeCallback) this.closeCallback(closeMessage) 45 | } 46 | 47 | ApiGatewayWebSocket.prototype.sendMessage = function(message) { 48 | if (message) { 49 | if (message.action) 50 | this.ws.send(JSON.stringify(message)); 51 | } 52 | } 53 | 54 | ApiGatewayWebSocket.prototype.close = function() { 55 | this.ws.close(); 56 | } 57 | 58 | ApiGatewayWebSocket.prototype.isOpen = function() { 59 | return this.ws != null; 60 | } 61 | 62 | ApiGatewayWebSocket.prototype.reConnect = function() { 63 | this.ws = new WebSocket(this.URL); 64 | this.ws.onmessage = ApiGatewayWebSocket.prototype.onMessageListener.bind(this); 65 | this.ws.onclose = ApiGatewayWebSocket.prototype.onCloseListener.bind(this); 66 | this.ws.onerror = ApiGatewayWebSocket.prototype.onErrorListener.bind(this); 67 | } -------------------------------------------------------------------------------- /infrastructure/deployadmin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 3 | # This script deploys the whole infrastructure (with cdn) and creates an 4 | # admin user with the provided username and password 5 | # 6 | ## 7 | 8 | SKIPINPUT=${1:-N} 9 | 10 | _DEBUG="on" 11 | 12 | function DEBUG() { 13 | [ "$_DEBUG" == "on" ] && $@ 14 | } 15 | 16 | function removeQuotes() { 17 | retval=$1 18 | retval=${retval#\"} 19 | retval=${retval%\"} 20 | echo "$retval" 21 | } 22 | 23 | echo "**************************************************************" 24 | echo "This function will deploy the Under The Sea environment" 25 | echo "creating an Admin using the username and password that" 26 | echo "you will provide" 27 | echo "**************************************************************" 28 | echo 29 | username="admin" 30 | password=$envname 31 | password2=$envname 32 | 33 | 34 | if [ "$SKIPINPUT" == "N" ]; then 35 | read -p "Admin username:" username 36 | read -s -p "Admin password:" password 37 | printf '\n' 38 | read -s -p "Confirm Admin password:" password2 39 | printf '\n' 40 | fi 41 | 42 | 43 | if [ "$password" != "$password2" ]; then 44 | echo "Passwords are not the same. Please re-run the script again" 45 | else 46 | echo "#### Deploying the environment..." 47 | cd cdk 48 | cdk deploy -c envname=$envname -c sessionparameter=true -c kinesisintegration=true -c firehose=true --require-approval never 49 | cd .. 50 | echo "#### Fixing Cognito..." 51 | echo 52 | source fixcognito.sh 53 | echo "#### Creating the user $username with the provided password" 54 | getUserPoolId=$(echo "aws cognito-idp list-user-pools --query 'UserPools[?Name == \`"$envname"\`]|[0].Id' --max-results=20 --region ${AWS_REGION}") 55 | userPoolId=$( removeQuotes $( eval $getUserPoolId ) ) 56 | echo "User pool Id" 57 | echo $userPoolId 58 | # create the user 59 | aws cognito-idp admin-create-user --user-pool-id $userPoolId --username $username --region ${AWS_REGION} 60 | aws cognito-idp admin-enable-user --user-pool-id $userPoolId --username $username --region ${AWS_REGION} 61 | aws cognito-idp admin-set-user-password --user-pool-id $userPoolId --username $username --password $password --permanent --region ${AWS_REGION} 62 | # add the user to the manager's group 63 | aws cognito-idp admin-add-user-to-group --user-pool-id $userPoolId --username $username --group-name Managers --region ${AWS_REGION} 64 | # deploy the fromt-end 65 | echo "#### Deploying the front-end" 66 | source deploy.frontend.sh 67 | echo "#### DONE" 68 | fi 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /infrastructure/cdk/bin/cdk.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | import cdk = require('aws-cdk-lib'); 5 | import { Tags } from 'aws-cdk-lib'; 6 | import { MainLayer } from '../lib/layer/mainLayer'; 7 | import { NRTAProps } from '../lib/nrta'; 8 | import { Utils } from '../lib/util/utils' 9 | 10 | 11 | const app = new cdk.App(); 12 | let envname = app.node.tryGetContext('envname'); 13 | if (!envname) { 14 | console.log("****************************************************"); 15 | console.log("ERROR: your environment name is undefined.\n"); 16 | console.log("Please run the command like this:"); 17 | console.log("cdk [synth|deploy|destroy] -c envname="); 18 | console.log("****************************************************"); 19 | process.exit(1); 20 | } 21 | else envname=envname.toUpperCase(); 22 | console.log('# Environment name:',envname); 23 | var initProps = new NRTAProps(); 24 | initProps.setApplicationName(envname); 25 | 26 | let setApplicationProperty = (propName : string, description: string) => { 27 | let envproperty = app.node.tryGetContext(propName); 28 | if (envproperty) { 29 | console.log('# '+description+' is going to be deployed: YES'); 30 | initProps.addParameter(propName,true); 31 | } else { 32 | console.log('# '+description+' is going to be deployed: NO'); 33 | }; 34 | } 35 | 36 | // Getting other possible context names 37 | // FOR THE CDN DEPLOYMENT 38 | setApplicationProperty("deploycdn","Cloudfront"); 39 | 40 | // Getting other possible context names 41 | // FOR SSM PARAMETER 42 | setApplicationProperty("sessionparameter","SSM Parameter Session"); 43 | 44 | // Getting other possible context names 45 | // FOR KINESIS DATA STREAMS INTEGRATION 46 | setApplicationProperty("kinesisintegration","Kinesis Data Streams integration"); 47 | 48 | // Getting other possible context names 49 | // FOR KINESIS FIREHOSE 50 | setApplicationProperty("firehose","Kinesis Firehose"); 51 | 52 | 53 | Utils.checkforExistingBuckets(initProps.getBucketNames()) 54 | .then((listOfExistingBuckets) => { 55 | if (listOfExistingBuckets && listOfExistingBuckets.length > 0) 56 | console.log("# The following buckets are NOT being created because they already exist: ", listOfExistingBuckets); 57 | initProps.addParameter('existingbuckets', listOfExistingBuckets); 58 | const stack = new MainLayer(app, initProps.getApplicationName(), initProps); 59 | 60 | Tags.of(stack).add('devops-guru-aiops', 'serverless'); 61 | }) 62 | .catch((errorList) => { 63 | console.log(errorList); 64 | }); 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Under the Sea - AIOps with Serverless Workshop 2 | 3 | Under the Sea is an exciting MMORPG developed by the famous entrepreneur behind Wild Rydes, the most popular unicorn taxi service in the world. This time the game design team went above and beyond to bring us a steam-punk adventure 20,000 leagues under the sea. The engineering team brought to live this technology masterpiece that can host hundreds of players simultaneously. To achieve the scalability requirements, the game's backend runs on AWS, mostly on serverless technology and containers. Under the Sea is available for PC and the most popular consoles. 4 | 5 | As expected from a MMORPG, the players can choose to play collaboratively or compete against each other. They can face daring monsters, hunt precious treasures, collect amazing equipment, and so on. On specific quests, the players can also compete against each other and score points. Once a month, the top players on the global scoreboard earn real-life prizes! There are also seasonal events, where players can get unique items. 6 | 7 | Because of the tight schedule, the engineering and ITOps teams didn't have time to properly configure their observability coverage. The game must keep its reputation of never being offline and always offer very low latency to maximize the player experience. Sometimes, there are unexpected traffic surges because of a sudden increase in the number of players. The team is still trying to figure out how to monitor the different serverless components, and detect possible throttling issues. They also want to minimize their mean time to investigate (MTTI) and mean time to recover (MTTR). 8 | 9 | **DISCLAIMER:** Under the Sea, and all its complementary resources are provided without any guarantees, and you're not recommended to use it for production-grade workloads. The intention is to provide content to build and learn. Be sure of reading the licensing terms. 10 | 11 | ## Deployment Instructions 12 | 13 | The instructions to run this workshop are on the official page of the [Under the Sea Workshop](https://studio.us-east-1.prod.workshops.aws/preview/76a8f14d-f505-44fd-b175-0cba31cfb13d/builds/fdf09967-ce5c-47fa-a419-8357ccb88e55/en-US/04-serverless). You can go through the instructions alone, but for a richer experience we recommend to have it run by an AWS SA or by an AWS Partner. 14 | 15 | ## Security 16 | 17 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 18 | 19 | ## License Summary 20 | 21 | The documentation is made available under the Creative Commons Attribution-ShareAlike 4.0 International License. See the LICENSE file. 22 | 23 | The sample code within this documentation is made available under the MIT-0 license. See the LICENSE-SAMPLECODE file. 24 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/util/utils.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import AWS = require('aws-sdk'); 4 | import crypto = require('crypto'); 5 | import fs = require('fs'); 6 | import path = require('path'); 7 | 8 | 9 | export class Utils { 10 | 11 | static async bucketExists(bucketName: string): Promise { 12 | return new Promise((resolve, reject) => { 13 | let params = { 14 | Bucket: bucketName 15 | } 16 | let sdkS3 = new AWS.S3(); 17 | sdkS3.headBucket(params, (err, _) => { 18 | if (err) { 19 | if (err.code == 'NotFound') resolve(false); 20 | else reject(err); 21 | } 22 | else resolve(true); 23 | }); 24 | }) 25 | }; 26 | 27 | static async checkforExistingBuckets(listOfBuckets: string[]) { 28 | 29 | let getListOfExistingBuckets = async function (bucketList: string[]): Promise { 30 | return new Promise(async (resolve, reject) => { 31 | 32 | let existingBuckets: string[] = []; 33 | let errorList: Error[] = []; 34 | 35 | for (let bucketName of bucketList) { 36 | await Utils.bucketExists(bucketName) 37 | .then((exists) => { 38 | if (exists) existingBuckets.push(bucketName); 39 | }) 40 | .catch((error) => { errorList.push(error) }); 41 | } 42 | if (errorList.length == 0) resolve(existingBuckets); 43 | else reject(errorList); 44 | }) 45 | } 46 | 47 | return await getListOfExistingBuckets(listOfBuckets); 48 | } 49 | 50 | 51 | /** 52 | * Hashes the contents of a file or directory. If the argument is a directory, 53 | * it is assumed not to contain symlinks that would result in a cyclic tree. 54 | * 55 | * @param fileOrDir the path to the file or directory that should be hashed. 56 | * 57 | * @returns a SHA256 hash, base-64 encoded. 58 | * 59 | * source: https://github.com/awslabs/aws-delivlib/blob/master/lib/util.ts 60 | */ 61 | static hashFileOrDirectory(fileOrDir: string): string { 62 | const hash = crypto.createHash('SHA256'); 63 | hash.update(path.basename(fileOrDir)).update('\0'); 64 | const stat = fs.statSync(fileOrDir); 65 | if (stat.isDirectory()) { 66 | for (const item of fs.readdirSync(fileOrDir).sort()) { 67 | hash.update(Utils.hashFileOrDirectory(path.join(fileOrDir, item))); 68 | } 69 | } else { 70 | hash.update(fs.readFileSync(fileOrDir)); 71 | } 72 | return hash.digest('base64'); 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /infrastructure/deploy.frontend.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## 3 | # Deploys the front-end 4 | ## 5 | 6 | txtgrn=$(tput setaf 2) # Green 7 | txtylw=$(tput setaf 3) # Yellow 8 | txtblu=$(tput setaf 4) # Blue 9 | txtpur=$(tput setaf 5) # Purple 10 | txtcyn=$(tput setaf 6) # Cyan 11 | txtwht=$(tput setaf 7) # White 12 | txtrst=$(tput sgr0) # Text reset 13 | 14 | _DEBUG="on" 15 | 16 | function EXECUTE() { 17 | [ "$_DEBUG" == "on" ] && echo $@ || $@ 18 | } 19 | 20 | function title() { 21 | tput rev 22 | showHeader $@ 23 | tput sgr0 24 | } 25 | 26 | function showHeader() { 27 | input=$@ 28 | echo ${txtgrn} 29 | printf "%0.s-" $(seq 1 ${#input}) 30 | printf "\n" 31 | echo $input 32 | printf "%0.s-" $(seq 1 ${#input}) 33 | echo ${txtrst} 34 | } 35 | 36 | function showSectionTitle() { 37 | echo 38 | echo --- ${txtblu} $@ ${txtrst} 39 | echo 40 | } 41 | 42 | envnameuppercase=$(echo $envname | tr 'a-z' 'A-Z') 43 | envnamelowercase=$(echo $envname | tr 'A-Z' 'a-z') 44 | #------------------------------------------- 45 | # Introduction 46 | #------------------------------------------- 47 | title "DEPLOYING THE FRONT-END FOR THE ENVIRONMENT $envnameuppercase" 48 | ## Fixing Cognito is required only for the workshop 49 | #showHeader Fixing Cognito 50 | #source fixcognito.sh 51 | #------------------------------------------- 52 | # Retrieving parameters from CloudFormation 53 | #------------------------------------------- 54 | apigtw=$(eval $(echo "aws cloudformation list-exports --query 'Exports[?contains(ExportingStackId,\`$envname\`) && contains(Name,\`apigtw\`)].Value | [0]' --region ${AWS_REGION} | xargs -I {} echo {}")) 55 | region=$(eval $(echo "aws cloudformation list-exports --query 'Exports[?contains(ExportingStackId,\`$envname\`) && contains(Name,\`region\`)].Value | [0]' --region ${AWS_REGION} | xargs -I {} echo {}")) 56 | url=$(eval $(echo "aws cloudformation list-exports --query 'Exports[?contains(ExportingStackId,\`$envname\`) && contains(Name,\`url\`)].Value | [0]' --region ${AWS_REGION} | xargs -I {} echo {}")) 57 | #------------------------------------------- 58 | # UPDATING /application/resources/js/awsconfig.js 59 | #------------------------------------------- 60 | showHeader "UPDATING /application/resources/js/awsconfig.js" 61 | cat < ./../application/resources/js/awsconfig.js 62 | const DEBUG = true; 63 | const AWS_CONFIG = { 64 | "region" : "$region", 65 | "API_ENDPOINT" : "$apigtw", 66 | "APPNAME" : "$envnameuppercase" 67 | }; 68 | exports.AWS_CONFIG = AWS_CONFIG; 69 | END 70 | more ./../application/resources/js/awsconfig.js 71 | 72 | #------------------------------------------- 73 | # DEPLOYING THE WEBSITE ON S3 74 | #------------------------------------------- 75 | showHeader "DEPLOYING THE WEBSITE ON S3" 76 | aws s3 cp ./../application s3://$envnamelowercase.app --recursive --region ${AWS_REGION} 77 | #------------------------------------------- 78 | # Finalization 79 | #------------------------------------------- 80 | title "Environment $envnameuppercase deployed" 81 | if [ "$url" == "" ]; then 82 | echo "You DON'T have a CloudFront distribution deployed. Please deploy it." 83 | else 84 | echo "URL: https://$url" 85 | fi -------------------------------------------------------------------------------- /infrastructure/cdk/lib/resourceawarestack.d.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from 'constructs'; 2 | import { Stack, StackProps } from 'aws-cdk-lib'; 3 | export interface IFlexNameApplication { 4 | applicationName?: string; 5 | getApplicationName(): string; 6 | } 7 | export interface IResourceAware { 8 | getResources(): Map; 9 | getResource(resourceName: string): any | undefined; 10 | addResources(resources: Map): void; 11 | addResource(map: string, resource: any): void; 12 | getResourcesNames(): IterableIterator | string[]; 13 | } 14 | export interface IParameterAware { 15 | getParameters(): Map; 16 | getParameter(parameterName: string): any | undefined; 17 | addParameters(parameters: Map): void; 18 | addParameter(map: string, resource: any): void; 19 | } 20 | export interface IDeploymentTarget { 21 | accountId?: string; 22 | region?: string; 23 | } 24 | export declare class ResourceBag implements IResourceAware { 25 | private resources; 26 | constructor(resources?: IResourceAware); 27 | getResources(): Map; 28 | addResources(resources: Map): void; 29 | addResource(key: string, resource: any): void; 30 | getResource(key: string): any | undefined; 31 | getResourcesNames(): IterableIterator | never[]; 32 | } 33 | export interface IParameterAwareProps extends StackProps, IParameterAware, IFlexNameApplication, IDeploymentTarget { 34 | } 35 | export declare class ParameterAwareProps implements IParameterAwareProps { 36 | accountId?: string; 37 | region?: string; 38 | static defaultApplicationName: string; 39 | applicationName?: string; 40 | setApplicationName(appName: string): void; 41 | getApplicationName(): string; 42 | parameters: Map; 43 | getParameters(): Map; 44 | addParameters(parameters: Map): void; 45 | addParameter(key: string, parameter: any): void; 46 | getParameter(key: string): any | undefined; 47 | constructor(props?: IParameterAwareProps); 48 | } 49 | export declare class ResourceAwareStack extends Stack implements IResourceAware { 50 | protected resources: Map; 51 | protected scope: Construct | undefined; 52 | protected properties: IParameterAwareProps; 53 | constructor(parent?: Construct, name?: string, props?: IParameterAwareProps); 54 | getResources(): Map; 55 | addResources(resources: Map): void; 56 | addResource(key: string, resource: any): void; 57 | getResource(key: string): any | undefined; 58 | getResourcesNames(): IterableIterator | never[]; 59 | getProperties(): IParameterAwareProps; 60 | } 61 | export declare class ResourceAwareConstruct extends Construct implements IResourceAware { 62 | resources: Map; 63 | protected properties: IParameterAwareProps; 64 | constructor(scope: Construct, id: string, props: IParameterAwareProps); 65 | getResources(): Map; 66 | addResources(resources: Map): void; 67 | addResource(key: string, resource: any): void; 68 | getResource(key: string): any | undefined; 69 | getResourcesNames(): IterableIterator | never[]; 70 | getProperties(): IParameterAwareProps; 71 | } 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /application/resources/libs/websocket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * utilities to do sigv4 3 | * @class SigV4Utils 4 | */ 5 | function SigV4Utils() { } 6 | 7 | SigV4Utils.prototype.getSignatureKey = function (key, date, region, service) { 8 | var kDate = AWS.util.crypto.hmac('AWS4' + key, date, 'buffer'); 9 | var kRegion = AWS.util.crypto.hmac(kDate, region, 'buffer'); 10 | var kService = AWS.util.crypto.hmac(kRegion, service, 'buffer'); 11 | var kCredentials = AWS.util.crypto.hmac(kService, 'aws4_request', 'buffer'); 12 | return kCredentials; 13 | }; 14 | 15 | SigV4Utils.prototype.getSignedUrl = function (host, region, credentials) { 16 | var datetime = AWS.util.date.iso8601(new Date()).replace(/[:\-]|\.\d{3}/g, ''); 17 | var date = datetime.substr(0, 8); 18 | 19 | var method = 'GET'; 20 | var protocol = 'wss'; 21 | var uri = '/mqtt'; 22 | var service = 'iotdevicegateway'; 23 | var algorithm = 'AWS4-HMAC-SHA256'; 24 | 25 | var credentialScope = date + '/' + region + '/' + service + '/' + 'aws4_request'; 26 | var canonicalQuerystring = 'X-Amz-Algorithm=' + algorithm; 27 | canonicalQuerystring += '&X-Amz-Credential=' + encodeURIComponent(credentials.accessKeyId + '/' + credentialScope); 28 | canonicalQuerystring += '&X-Amz-Date=' + datetime; 29 | canonicalQuerystring += '&X-Amz-SignedHeaders=host'; 30 | 31 | var canonicalHeaders = 'host:' + host + '\n'; 32 | var payloadHash = AWS.util.crypto.sha256('', 'hex') 33 | var canonicalRequest = method + '\n' + uri + '\n' + canonicalQuerystring + '\n' + canonicalHeaders + '\nhost\n' + payloadHash; 34 | 35 | var stringToSign = algorithm + '\n' + datetime + '\n' + credentialScope + '\n' + AWS.util.crypto.sha256(canonicalRequest, 'hex'); 36 | var sigv4 = new SigV4Utils(); 37 | var signingKey = sigv4.getSignatureKey(credentials.secretAccessKey, date, region, service); 38 | var signature = AWS.util.crypto.hmac(signingKey, stringToSign, 'hex'); 39 | 40 | canonicalQuerystring += '&X-Amz-Signature=' + signature; 41 | if (credentials.sessionToken) { 42 | canonicalQuerystring += '&X-Amz-Security-Token=' + encodeURIComponent(credentials.sessionToken); 43 | } 44 | 45 | var requestUrl = protocol + '://' + host + uri + '?' + canonicalQuerystring; 46 | return requestUrl; 47 | }; 48 | 49 | function WSClient(clientId,host,region,credentials,messageReceivedCallback) { 50 | var sigv4 = new SigV4Utils(); 51 | var requestURL = sigv4.getSignedUrl(host, region, credentials); 52 | this.client = new Paho.MQTT.Client(requestURL, clientId); 53 | var self = this; 54 | this.messageCallback = messageReceivedCallback; 55 | var connectOptions = { 56 | onSuccess: function () { 57 | console.log('IoT Connected with success'); 58 | self.client.subscribe("ALIENATTACK"); 59 | }, 60 | useSSL: true, 61 | timeout: 3, 62 | mqttVersion: 4, 63 | onFailure: function () { 64 | console.log('failure'); 65 | } 66 | }; 67 | this.client.connect(connectOptions); 68 | this.client.onMessageArrived = WSClient.prototype.onMessageArrived.bind(this); 69 | } 70 | 71 | WSClient.prototype.onMessageArrived = function(message) { 72 | console.log("onMessageArrived:"+message.payloadString); 73 | if (this.messageCallback) this.messageCallback(message.payloadString); 74 | }; 75 | 76 | WSClient.prototype.disconnect = function() { 77 | this.client.disconnect(); 78 | } -------------------------------------------------------------------------------- /infrastructure/fixcognito.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | ## 6 | # Fixes the configuration for the permissions to the users authenticated by Cognito 7 | ## 8 | 9 | _DEBUG="on" 10 | 11 | function DEBUG() { 12 | [ "$_DEBUG" == "on" ] && $@ 13 | } 14 | 15 | function removeQuotes() { 16 | retval=$1 17 | retval=${retval#\"} 18 | retval=${retval%\"} 19 | echo "$retval" 20 | } 21 | 22 | function setRoleMappings() { 23 | appName=$1 24 | echo 25 | echo "Setting Role Mappings for envName: "$appName 26 | 27 | getPlayersRole=$(echo aws "iam list-roles --query 'Roles[?contains(RoleName,\`"$appName"PlayersRole\`)].Arn|[0]'") 28 | playersRoleArn=$(removeQuotes $( eval $getPlayersRole )) 29 | DEBUG echo $playersRoleArn 30 | 31 | getManagersRole=$(echo aws "iam list-roles --query 'Roles[?contains(RoleName,\`"$appName"ManagersRole\`)].Arn|[0]'") 32 | managersRoleArn=$(removeQuotes $( eval $getManagersRole )) 33 | DEBUG echo $managersRoleArn 34 | 35 | getUnauthRole=$(echo aws "iam list-roles --query 'Roles[?contains(RoleName,\`"$appName"UnauthRole\`)].Arn|[0]'") 36 | unauthRoleArn=$(removeQuotes $( eval $getUnauthRole )) 37 | DEBUG echo $unauthRoleArn 38 | 39 | getIdentityPool=$(echo aws "cognito-identity list-identity-pools --max-results 60 --query 'IdentityPools[?starts_with(IdentityPoolName,\`"$appName"\`)]|[0].IdentityPoolId' --region ${AWS_REGION}") 40 | identityPoolId=$( removeQuotes $( eval $getIdentityPool ) ) 41 | DEBUG echo $identityPoolId 42 | 43 | getCognitoProviderName=$(echo "aws cognito-identity describe-identity-pool --identity-pool-id "$identityPoolId" --query 'CognitoIdentityProviders[0].ProviderName' --region ${AWS_REGION}") 44 | cognitoProviderName=$( removeQuotes $( eval $getCognitoProviderName ) ) 45 | DEBUG echo $cognitoProviderName 46 | 47 | #aws cognito-idp list-identity-providers --user-pool-id us-east-2_nMp73BoqG 48 | getUserPoolId=$(echo "aws cognito-idp list-user-pools --query 'UserPools[?Name == \`"$appName"\`]|[0].Id' --max-results=20 --region ${AWS_REGION}") 49 | userPoolId=$( removeQuotes $( eval $getUserPoolId ) ) 50 | DEBUG echo $userPoolId 51 | 52 | clientId=$( removeQuotes $(aws cognito-idp list-user-pool-clients --user-pool-id $userPoolId --query 'UserPoolClients[0].ClientId' --region ${AWS_REGION}) ) 53 | DEBUG echo $clientId 54 | playersRoleValue=$appName"PlayersRole" 55 | managersRoleValue=$appName"ManagersRole" 56 | roleMappings=$(cat <<-END 57 | { 58 | "$cognitoProviderName:$clientId": { 59 | "AmbiguousRoleResolution": "Deny", 60 | "Type": "Rules", 61 | "RulesConfiguration": { 62 | "Rules": [ 63 | { 64 | "Claim": "cognito:preferred_role", 65 | "MatchType": "Contains", 66 | "RoleARN": "$playersRoleArn", 67 | "Value": "$playersRoleValue" 68 | }, 69 | { 70 | "Claim": "cognito:preferred_role", 71 | "MatchType": "Contains", 72 | "RoleARN": "$managersRoleArn", 73 | "Value": "$managersRoleValue" 74 | } 75 | ] 76 | } 77 | } 78 | } 79 | END 80 | ) 81 | 82 | setIdentityPoolRoles=$(cat <<-END 83 | aws cognito-identity set-identity-pool-roles \ 84 | --identity-pool-id $identityPoolId 85 | --roles authenticated="$playersRoleArn",unauthenticated="$unauthRoleArn" \ 86 | --role-mappings '$roleMappings' --region ${AWS_REGION} 87 | END 88 | ) 89 | DEBUG echo $setIdentityPoolRoles 90 | eval $setIdentityPoolRoles 91 | } 92 | 93 | if [ "$envname" == "" ]; then 94 | echo 95 | echo "** ERROR**" 96 | echo Please ensure that the variable envname is defined 97 | else 98 | ## Just making sure that the environment name is going to be in uppercase 99 | envName=$(echo $envname | tr 'a-z' 'A-Z') 100 | setRoleMappings $envName 101 | fi 102 | -------------------------------------------------------------------------------- /application/game/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Under the Sea 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |

Move with arrow keys, fire with the space bar, pause on/off with P. The starfish get faster and drop 22 | more bombs as you complete each level!

23 | mute | 24 | Original application on github, by @dwmkerr | 25 | Based on alient attack workshop, by @fabianmartins | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 82 | 84 | 85 | -------------------------------------------------------------------------------- /application/pages/howtoplay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | AWS Under The Sea 10 | 11 | 12 | 13 |

14 | 15 |

HOW TO PLAY

16 |
17 |

In this section we give you a straightforward description on how to interact with this version of the game. For 18 | sure you must be anxious thinking "How do I win this thing?". Ok, let's go for it.

19 |

--- How the gamers are ranked ---

20 |
    21 |
  1. SCORE (+) - More points, better rank.
  2. 22 |
  3. LIVES (+) - More opportunity to play, better rank.
  4. 23 |
  5. SHOTS (-) - Less shots, better rank. So, for two people with the same score, and the same amount of game 24 | lives, whoever shoots less scores better (frugality).
  6. 25 |
  7. NICKNAME - Finally, if the destiny gives you a twin rank sibling, then your'e ranked in ascending 26 | order using the nickname. Please don't go crazy using a lot of @@@@ in front of your nickname, ok?
  8. 27 |
28 |

29 | IMPORTANT: Every time that you restart the game, you reset your score. ;) 30 |

31 |

--- Register, login, and have fun ---

32 |

If this is your first time playing, then you will need to REGISTER, otherwise go straight to LOGIN.

33 | 34 |

You will use the screen below to register. All the fields are required. A valid email is required so you can receive a confirmation email. Please be sure that you have access to it.

35 | 36 |
    37 |
  • Define a good nickname. This is the only info that will be shown for other players.
  • 38 |
  • Provide a VALID email. Please be sure to have access to this email so you can receive the confirmation link. 39 |
  • Define your password. This is for fun, don't expend too much time thinking about passwords. We all hate passwords, but this is content to another chalk talk. Password needs to have 6 characters or more. We still don't have a "I forgot my password" function. So, take one simple and memorize it. 40 |
  • Provide your company's domain. This is important because we would like to know which partners and customers were here.
  • 41 |
42 | 43 |

44 | After signing in, you will be taken to a state where the game will be waiting for a session to start. This task will keep running the test until a session is opened. If no session is available, you can opt to close this screen, and subsequent pop-up screens, to get to the welcome screen and play alone. When playing alone, your score is not recorded. 45 |

46 | 47 |

48 | When a session is available, you will be taken to this screen. Here you can choose to join the session, or play alone. 49 | When you join the session, your score is resetted to zero and you will be competing again. If you decide to play alone, your score will not be considered. 50 |

51 | 52 |

53 | The welcome screen is the starting point for the game. Press the "space bar" to begin the game. 54 |

55 | 56 |

57 | Use your keyboard. Use the SPACE BAR to fire, and the LEFT AND RIGHT keys to move your ship. 58 |

59 | 60 |
61 |
62 |

Click, and have fun!

63 |
64 | 65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/gameInit/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 'use strict'; 4 | const AWS = require('aws-sdk'); 5 | if (!AWS.config.region) AWS.config.region = 'us-east-1'; 6 | const COGNITO = new AWS.CognitoIdentityServiceProvider(); 7 | const SSM = new AWS.SSM(); 8 | 9 | class UnderTheSeaGameInit { 10 | 11 | constructor() { 12 | this.UserPoolId = null; 13 | }; 14 | 15 | getUserPoolId(callback) { 16 | if (this.UserPoolId == null) { 17 | var self = this; 18 | SSM.getParameters( { 'Names' : [ 'underthesea.userpoolid' ] }, (err,data) => { 19 | if (err) { 20 | console.log('Error reading underthesea.userpoolid'); 21 | console.log(err); 22 | callback(null); 23 | } else { 24 | if (data.Parameters.length == 0) console.log('Error - parameter name not found'); 25 | else { 26 | self.UserPoolId = data.Parameters[0].Value; 27 | } 28 | callback(self.UserPoolId); 29 | } 30 | }); 31 | } 32 | else callback(this.UserPoolId); 33 | } 34 | 35 | 36 | resetUsers(callback) { 37 | var self = this; 38 | var resetUserAttributes = function(userlist, cbk, withError,withSuccess) { 39 | if (!withError) withError = []; 40 | if (!withSuccess) withSuccess = []; 41 | if (userlist.length==0) { 42 | var response = { 43 | 'withError' : withError, 44 | 'withSuccess' : withSuccess 45 | }; 46 | if (withError.length == 0) cbk(null,response); 47 | else cbk('error',response); 48 | } 49 | else { 50 | var user = userlist.pop(); 51 | var param = { 52 | 'UserPoolId' : self.UserPoolId, 53 | 'Username' : user.Username, 54 | 'UserAttributes' : [ 55 | { Name : 'custom:hasAlreadyPlayed', Value : '0' } 56 | ] 57 | }; 58 | COGNITO.adminUpdateUserAttributes(param,function(err,data) { 59 | if (err) { 60 | console.log('Could not update user attribute for user ', user.Username); 61 | withError.push( { 62 | 'Username': user.Username, 63 | 'Error' : err 64 | }); 65 | resetUserAttributes(userlist,cbk, withError, withSuccess); 66 | } else { 67 | console.log('User attribute reset with success:', user.Username); 68 | withSuccess.push( { 69 | 'Username': user.Username 70 | }); 71 | resetUserAttributes(userlist,cbk, withError, withSuccess); 72 | }; 73 | }); 74 | } 75 | }; 76 | 77 | this.getUserPoolId( function(result) { 78 | if (!result) console.log('Could not get UserPoolId'); 79 | else { 80 | var listUserParameters = { 81 | 'UserPoolId' : result 82 | ,'AttributesToGet' : [] 83 | ,'Filter' : 'cognito:user_status = \"confirmed\"' 84 | } 85 | COGNITO.listUsers( listUserParameters, function(err,data) { 86 | if (err) { 87 | console.log('Error listing users'); 88 | console.log(err); 89 | } else { 90 | resetUserAttributes(data.Users,callback); 91 | }; 92 | }) 93 | } 94 | }) 95 | } 96 | 97 | init() { 98 | this.resetUsers(function(err,data) { 99 | if (err) { 100 | console.log('FAILURE'); 101 | console.log(err); 102 | } 103 | else { 104 | if (data.withError.length == 0) console.log('TOTAL SUCCESS'); 105 | else console.log('PARTIAL SUCCESS'); 106 | console.log(data); 107 | } 108 | }) 109 | } 110 | }; 111 | 112 | var gi = new UnderTheSeaGameInit(); 113 | gi.init(); -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/configurationLayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ConfigurationLayer = void 0; 4 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // SPDX-License-Identifier: MIT-0 6 | const resourceawarestack_1 = require("./../resourceawarestack"); 7 | const ssm = require("aws-cdk-lib/aws-ssm"); 8 | /** 9 | * Configuration Layer is a construct designed to acquire and store configuration 10 | * data to be used by the system 11 | */ 12 | class ConfigurationLayer extends resourceawarestack_1.ResourceAwareConstruct { 13 | constructor(parent, name, props) { 14 | super(parent, name, props); 15 | if (props) { 16 | let parametersToBeCreated = props.getParameter('ssmParameters'); 17 | if (parametersToBeCreated) { 18 | parametersToBeCreated.forEach((v, k) => { 19 | let parameter = this.createParameter(props.getApplicationName(), k, v); 20 | this.addResource('parameter.' + k, parameter); 21 | }); 22 | } 23 | } 24 | } 25 | createParameter(appName, keyName, value) { 26 | let baseName = '/' + appName.toLowerCase(); 27 | let parameter = new ssm.StringParameter(this, 'SSMParameter' + appName + keyName, { 28 | parameterName: baseName + '/' + keyName.toLowerCase(), 29 | stringValue: value 30 | }); 31 | return parameter; 32 | } 33 | } 34 | exports.ConfigurationLayer = ConfigurationLayer; 35 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlndXJhdGlvbkxheWVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY29uZmlndXJhdGlvbkxheWVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFFQUFxRTtBQUNyRSxpQ0FBaUM7QUFDakMsZ0VBQXNGO0FBRXRGLDJDQUEyQztBQUczQzs7O0dBR0c7QUFDSCxNQUFhLGtCQUFtQixTQUFRLDJDQUFzQjtJQUUxRCxZQUFZLE1BQWlCLEVBQUUsSUFBWSxFQUFFLEtBQTJCO1FBQ3BFLEtBQUssQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQzNCLElBQUksS0FBSyxFQUFFO1lBQ1AsSUFBSSxxQkFBcUIsR0FBRyxLQUFLLENBQUMsWUFBWSxDQUFDLGVBQWUsQ0FBQyxDQUFDO1lBQ2hFLElBQUkscUJBQXFCLEVBQUU7Z0JBQ3ZCLHFCQUFxQixDQUFDLE9BQU8sQ0FBRSxDQUFDLENBQU8sRUFBRSxDQUFVLEVBQUUsRUFBRTtvQkFDbkQsSUFBSSxTQUFTLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsRUFBQyxDQUFDLEVBQVUsQ0FBQyxDQUFDLENBQUM7b0JBQzlFLElBQUksQ0FBQyxXQUFXLENBQUMsWUFBWSxHQUFDLENBQUMsRUFBQyxTQUFTLENBQUMsQ0FBQztnQkFDL0MsQ0FBQyxDQUFDLENBQUM7YUFDTjtTQUNKO0lBQ0wsQ0FBQztJQUVPLGVBQWUsQ0FBQyxPQUFnQixFQUFFLE9BQWUsRUFBRSxLQUFjO1FBQ3JFLElBQUksUUFBUSxHQUFZLEdBQUcsR0FBRSxPQUFPLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkQsSUFBSSxTQUFTLEdBQUcsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDLElBQUksRUFBRSxjQUFjLEdBQUMsT0FBTyxHQUFDLE9BQU8sRUFBRTtZQUMxRSxhQUFhLEVBQUcsUUFBUSxHQUFHLEdBQUcsR0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFO1lBQ3BELFdBQVcsRUFBRSxLQUFLO1NBQ3JCLENBQUMsQ0FBQztRQUNILE9BQU8sU0FBUyxDQUFDO0lBQ3JCLENBQUM7Q0FDSjtBQXZCRCxnREF1QkMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgQW1hem9uLmNvbSwgSW5jLiBvciBpdHMgYWZmaWxpYXRlcy4gQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNSVQtMFxuaW1wb3J0IHsgUmVzb3VyY2VBd2FyZUNvbnN0cnVjdCwgSVBhcmFtZXRlckF3YXJlUHJvcHMgfSBmcm9tICcuLy4uL3Jlc291cmNlYXdhcmVzdGFjaydcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuaW1wb3J0ICogYXMgc3NtIGZyb20gJ2F3cy1jZGstbGliL2F3cy1zc20nOyAgXG5cblxuLyoqXG4gKiBDb25maWd1cmF0aW9uIExheWVyIGlzIGEgY29uc3RydWN0IGRlc2lnbmVkIHRvIGFjcXVpcmUgYW5kIHN0b3JlIGNvbmZpZ3VyYXRpb25cbiAqIGRhdGEgdG8gYmUgdXNlZCBieSB0aGUgc3lzdGVtXG4gKi9cbmV4cG9ydCBjbGFzcyBDb25maWd1cmF0aW9uTGF5ZXIgZXh0ZW5kcyBSZXNvdXJjZUF3YXJlQ29uc3RydWN0IHtcblxuICAgIGNvbnN0cnVjdG9yKHBhcmVudDogQ29uc3RydWN0LCBuYW1lOiBzdHJpbmcsIHByb3BzOiBJUGFyYW1ldGVyQXdhcmVQcm9wcykge1xuICAgICAgICBzdXBlcihwYXJlbnQsIG5hbWUsIHByb3BzKTtcbiAgICAgICAgaWYgKHByb3BzKSB7XG4gICAgICAgICAgICBsZXQgcGFyYW1ldGVyc1RvQmVDcmVhdGVkID0gcHJvcHMuZ2V0UGFyYW1ldGVyKCdzc21QYXJhbWV0ZXJzJyk7XG4gICAgICAgICAgICBpZiAocGFyYW1ldGVyc1RvQmVDcmVhdGVkKSB7XG4gICAgICAgICAgICAgICAgcGFyYW1ldGVyc1RvQmVDcmVhdGVkLmZvckVhY2goICh2IDogYW55LCBrIDogc3RyaW5nKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGxldCBwYXJhbWV0ZXIgPSB0aGlzLmNyZWF0ZVBhcmFtZXRlcihwcm9wcy5nZXRBcHBsaWNhdGlvbk5hbWUoKSxrLDxzdHJpbmc+IHYpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmFkZFJlc291cmNlKCdwYXJhbWV0ZXIuJytrLHBhcmFtZXRlcik7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9ICAgICAgIFxuXG4gICAgcHJpdmF0ZSBjcmVhdGVQYXJhbWV0ZXIoYXBwTmFtZSA6IHN0cmluZywga2V5TmFtZTogc3RyaW5nLCB2YWx1ZSA6IHN0cmluZykgeyAgICBcbiAgICAgICAgbGV0IGJhc2VOYW1lIDogc3RyaW5nID0gJy8nKyBhcHBOYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgICAgIGxldCBwYXJhbWV0ZXIgPSBuZXcgc3NtLlN0cmluZ1BhcmFtZXRlcih0aGlzLCAnU1NNUGFyYW1ldGVyJythcHBOYW1lK2tleU5hbWUsIHtcbiAgICAgICAgICAgIHBhcmFtZXRlck5hbWUgOiBiYXNlTmFtZSArICcvJytrZXlOYW1lLnRvTG93ZXJDYXNlKCksXG4gICAgICAgICAgICBzdHJpbmdWYWx1ZTogdmFsdWVcbiAgICAgICAgfSk7XG4gICAgICAgIHJldHVybiBwYXJhbWV0ZXI7XG4gICAgfVxufVxuIl19 -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/websocketConnect/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | /** 4 | * Purpose of this function is to write the connectionID of users who open 5 | * a websocket connection. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const AWS = require('aws-sdk'); 11 | const DynamoDB = new AWS.DynamoDB.DocumentClient(); 12 | const SSM = new AWS.SSM(); 13 | 14 | const readSessionFromSSM = function (callback) { 15 | let param = { 16 | "Name": process.env.SESSION_PARAMETER 17 | }; 18 | SSM.getParameter(param, 19 | function (error, sessionParamResponse) { 20 | if (error) { 21 | let errorMessage = "Error reading from SSM"; 22 | console.log(errorMessage); 23 | console.log(error); 24 | let responseError = new Error(errorMessage); 25 | responseError.code = "ErrorReadingSSM"; 26 | responseError.details = error; 27 | callback(responseError,500); 28 | } else { 29 | let sessionData = null; 30 | try { 31 | sessionData = JSON.parse(sessionParamResponse.Parameter.Value); 32 | callback(null, sessionData); 33 | } catch (error) { 34 | let errorMessage = "Error parsing session data from SSM"; 35 | console.log(errorMessage); 36 | console.log(error); 37 | let responseError = new Error(errorMessage); 38 | responseError.code = "ErrorReadingFromSSM"; 39 | responseError.details = error; 40 | console.log(sessionData); 41 | callback(responseError, 500); 42 | } 43 | } 44 | }); 45 | }; 46 | 47 | const recordConnectiontoSession = function(session, connectionId, callback) { 48 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 49 | let params = { 50 | 'TableName': tableName, 51 | 'Key': {'SessionId': session}, 52 | 'ExpressionAttributeNames': {'#connections': 'connections'}, 53 | 'ExpressionAttributeValues': { 54 | ':connections': [connectionId], 55 | ':empty_list': [] 56 | }, 57 | 'UpdateExpression': 'set #connections = list_append(if_not_exists(#connections, :empty_list), :connections)' 58 | } 59 | DynamoDB.update(params, (err, data) => { 60 | if (err) { 61 | console.log('error placing connection', err); 62 | let message = 'Error in placing connectionID'; 63 | callback(new Error(message), 422); 64 | } else callback(null, ('Success adding connection')); 65 | }); 66 | }; 67 | 68 | exports.handler = (event, context, callback) => { 69 | console.log(event); 70 | // Update table with connectionID 71 | const { connectionId } = event.requestContext; 72 | let response = null; 73 | 74 | readSessionFromSSM((err, session) => { 75 | if (err) { 76 | console.log(err); 77 | callback({ 78 | statusCode: 400, 79 | isBase64Encoded: false, 80 | body: JSON.stringify({ "errorMessage" : err.details, "code" : err.code }) 81 | }); 82 | } 83 | // Make sure that the session is valid 84 | if (!session || !session.SessionId || session.SessionId.trim() == '') { 85 | response = { 86 | statusCode: 400, 87 | isBase64Encoded: false, 88 | body: JSON.stringify({ 89 | "errorMessage": "Invalid request. Session not provided.", 90 | "errorCode" : 400 91 | }) 92 | }; 93 | console.log(response); 94 | callback(null, response); 95 | } 96 | recordConnectiontoSession(session.SessionId, connectionId, (err, _) => { 97 | if (err) { 98 | response = { 99 | statusCode: err.statusCode, 100 | isBase64Encoded: false, 101 | body: JSON.stringify({ 102 | "errorMessage": err.errorMessage, 103 | "errorCode": err.errorCode 104 | }) 105 | }; 106 | console.log(response); 107 | callback(null, response); 108 | } else { 109 | callback(null, { 110 | statusCode: 200, 111 | isBase64Encoded: false, 112 | body: JSON.stringify({ 113 | "success": true 114 | }) 115 | }); 116 | } 117 | }); 118 | }); 119 | }; 120 | -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/websocketDisconnect/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | /** 4 | * Purpose of this function is to delete any stale 5 | * connectionIDs from closed websockets 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const AWS = require('aws-sdk'); 11 | const DynamoDB = new AWS.DynamoDB.DocumentClient(); 12 | const SSM = new AWS.SSM(); 13 | 14 | const readSessionFromSSM = function (callback) { 15 | let param = { 16 | "Name": process.env.SESSION_PARAMETER 17 | }; 18 | SSM.getParameter(param, 19 | function (error, sessionParamResponse) { 20 | if (error) { 21 | let errorMessage = "Error reading from SSM"; 22 | console.log(errorMessage); 23 | console.log(error); 24 | let responseError = new Error(errorMessage); 25 | responseError.code = "ErrorReadingSSM"; 26 | responseError.details = error; 27 | callback(responseError,500); 28 | } else { 29 | let sessionData = null; 30 | try { 31 | sessionData = JSON.parse(sessionParamResponse.Parameter.Value); 32 | callback(null, sessionData); 33 | } catch (error) { 34 | let errorMessage = "Error parsing session data from SSM"; 35 | console.log(errorMessage); 36 | console.log(error); 37 | let responseError = new Error(errorMessage); 38 | responseError.code = "ErrorReadingFromSSM"; 39 | responseError.details = error; 40 | console.log(sessionData); 41 | callback(responseError, 500); 42 | } 43 | } 44 | } 45 | ); 46 | }; 47 | 48 | const getConnectionIndexDynamo = (session, callback) => { 49 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 50 | let params = { 51 | TableName: tableName, 52 | Key: {'SessionId': session}, 53 | AttributesToGet: ['connections'] 54 | }; 55 | DynamoDB.get(params, (err, data) => { 56 | if (err) callback(err); 57 | else { 58 | console.log(data); 59 | callback(null, data); 60 | } 61 | }); 62 | }; 63 | 64 | const deleteConnectionDynamo = (session, index, callback) => { 65 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 66 | const deleteParams = { 67 | TableName: tableName, 68 | Key: {SessionId: session}, 69 | UpdateExpression: 'REMOVE #connections[' + index + ']', 70 | ExpressionAttributeNames: { 71 | '#connections': 'connections' 72 | } 73 | }; 74 | DynamoDB.update(deleteParams, (err, _) => { 75 | if (err) callback(err); 76 | else callback(); 77 | }); 78 | }; 79 | 80 | exports.handler = (event, context, callback) => { 81 | const { connectionId } = event.requestContext; 82 | let response = null; 83 | 84 | readSessionFromSSM((err, session) => { 85 | if (err) { 86 | console.log(err); 87 | callback({ 88 | statusCode: 400, 89 | isBase64Encoded: false, 90 | body: JSON.stringify({ "errorMessage" : err.details, "code" : err.code }) 91 | }); 92 | } 93 | // Make sure that the session is valid 94 | if (!session || !session.SessionId || session.SessionId.trim() == '') { 95 | response = { 96 | statusCode: 400, 97 | isBase64Encoded: false, 98 | body: JSON.stringify({ 99 | "errorMessage": "Invalid request. Session not provided.", 100 | "errorCode" : 400 101 | }) 102 | }; 103 | console.log(response); 104 | callback(null, response); 105 | } 106 | getConnectionIndexDynamo(session.SessionId, (err, connections) => { 107 | if (err) { 108 | response = { 109 | statusCode: err.statusCode, 110 | isBase64Encoded: false, 111 | body: JSON.stringify({ 112 | "errorMessage": err.errorMessage, 113 | "errorCode": err.errorCode 114 | }) 115 | }; 116 | console.log(response); 117 | callback(null, response); 118 | } 119 | // handle the connections array 120 | connections = connections.Item.connections.map((e) => {return e}); 121 | let index = connections.indexOf(connectionId); 122 | deleteConnectionDynamo(session.SessionId, index, (err, _) => { 123 | if (err) console.log(err); 124 | }); 125 | 126 | }); 127 | }); 128 | }; 129 | -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/storageLayer.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { Construct } from 'constructs'; 4 | import { RemovalPolicy } from 'aws-cdk-lib'; 5 | import { ResourceAwareConstruct, IParameterAwareProps } from './../resourceawarestack' 6 | import { IBucket, Bucket, BucketProps, HttpMethods } from 'aws-cdk-lib/aws-s3'; 7 | 8 | 9 | interface IBucketCreationProps { 10 | bucketName : string, 11 | isWeb? : boolean, 12 | alreadyExists: boolean, 13 | retain : boolean 14 | } 15 | 16 | /** 17 | * StorageLayer is a construct that describes the required resources 18 | * to store the static data. That includes both S3 and SystemsManager. 19 | */ 20 | export class StorageLayer extends ResourceAwareConstruct { 21 | 22 | constructor(parent: Construct, name: string, props: IParameterAwareProps) { 23 | super(parent, name, props); 24 | this.createBuckets(); 25 | } 26 | 27 | /** 28 | * This function receives the desired bucket configuration 29 | * and then creates (or imports) the bucket 30 | */ 31 | private createBucket(props: IBucketCreationProps) : IBucket { 32 | let bucket : IBucket; 33 | if (props.alreadyExists) { 34 | bucket = Bucket.fromBucketArn(this, props.bucketName,'arn:aws:s3:::'+props.bucketName); 35 | } else { 36 | var bucketProperties : BucketProps; 37 | if (props.isWeb) { 38 | if (props.retain) 39 | bucketProperties = { 40 | bucketName : props.bucketName 41 | ,cors : [ 42 | { 43 | allowedHeaders : ["*"] 44 | ,allowedMethods : [ 45 | HttpMethods.GET, 46 | HttpMethods.PUT, 47 | HttpMethods.DELETE, 48 | HttpMethods.POST 49 | ] 50 | ,allowedOrigins : ["*"] 51 | } 52 | ] 53 | ,websiteIndexDocument : 'index.html' 54 | ,websiteErrorDocument : 'error.html' 55 | ,removalPolicy : RemovalPolicy.RETAIN 56 | } 57 | else 58 | bucketProperties = { 59 | bucketName : props.bucketName 60 | ,cors : [ 61 | { 62 | allowedHeaders : ["*"] 63 | ,allowedMethods : [ 64 | HttpMethods.GET, 65 | HttpMethods.PUT, 66 | HttpMethods.DELETE, 67 | HttpMethods.POST 68 | ] 69 | ,allowedOrigins : ["*"] 70 | } 71 | ] 72 | ,websiteIndexDocument : 'index.html' 73 | ,websiteErrorDocument : 'error.html' 74 | ,removalPolicy : RemovalPolicy.DESTROY 75 | }; 76 | bucket = new Bucket(this, props.bucketName, bucketProperties ); 77 | } else { 78 | if (props.retain) 79 | bucketProperties = { 80 | bucketName : props.bucketName 81 | ,removalPolicy : RemovalPolicy.RETAIN 82 | }; 83 | else 84 | bucketProperties = { 85 | bucketName : props.bucketName 86 | ,removalPolicy : RemovalPolicy.DESTROY 87 | }; 88 | bucket = new Bucket(this,props.bucketName,bucketProperties); 89 | } 90 | } 91 | return bucket; 92 | } 93 | 94 | createBuckets() { 95 | let appBucketName = this.properties.getApplicationName().toLowerCase() + '.app'; 96 | let rawDataBucketName = this.properties.getApplicationName().toLowerCase() + '.raw'; 97 | 98 | let appBucket = this.createBucket( { 99 | bucketName : appBucketName 100 | ,isWeb : true 101 | ,alreadyExists : this.properties.getParameter('existingbuckets').includes(appBucketName) 102 | ,retain : true 103 | }); 104 | this.addResource('appBucket',appBucket); 105 | 106 | 107 | let rawDataBucket = this.createBucket({ 108 | bucketName : rawDataBucketName 109 | ,alreadyExists : this.properties.getParameter('existingbuckets').includes(rawDataBucketName) 110 | ,retain : true 111 | }); 112 | this.addResource('rawDataBucket',rawDataBucket); 113 | } 114 | 115 | getRawDataBucketArn() : string { 116 | let rawDataBucketName = this.properties.getApplicationName().toLowerCase() + '.raw'; 117 | return 'arn:aws:s3:::'+rawDataBucketName; 118 | } 119 | } -------------------------------------------------------------------------------- /application/scoreboard/js/scoreboard.kinesisinherited.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | const LOOPING_INTERVAL_IN_MS = 1500; 4 | 5 | class ScoreboardKinesis extends Scoreboard { 6 | 7 | constructor(document) { 8 | super(document); 9 | this.updateHasFinished = true; 10 | } 11 | 12 | initializeAWSServices() { 13 | super.initializeAWSServices(); 14 | this.kinesis = this.awsfacade.getKinesisDataStream(); 15 | } 16 | 17 | getInitializeShardIteratorFunction() { 18 | var self = this; 19 | var initializeShardIteratorFunction = function () { 20 | self.kinesis.getShardIterator({ 21 | StreamName: self.appName+'_InputStream', 22 | ShardId: 'shardId-000000000000', 23 | ShardIteratorType: 'LATEST' 24 | }, function (err, data) { 25 | if (err) console.log('ERROR getShardIterator:', err); 26 | else { 27 | console.log("Iterator renewed"); 28 | self.currentShardIterator = data.ShardIterator; 29 | } 30 | }); 31 | } 32 | return initializeShardIteratorFunction; 33 | } 34 | 35 | run() { 36 | var self = this; 37 | super.run(self.getInitializeShardIteratorFunction(), LOOPING_INTERVAL_IN_MS); 38 | } 39 | 40 | /** 41 | * This function asynchronously updates the scoreboard. 42 | * We work to reduce the amount of data to be updated by keeping only the latest 43 | * updated data for each user. 44 | * @param {*} records 45 | */ 46 | async updateScoreboard(records) { 47 | console.log("Updating scoreboard") 48 | // activating semaphore for updates 49 | this.updateHasFinished = false; 50 | // because for every reading you can get many different records from the same user 51 | // we want just the most recent one; 52 | let arrayToBePublished = []; 53 | let recordsCleanup = function(record) { 54 | let recordAsObject = JSON.parse(new String(record.Data)); 55 | let currentIdx = arrayToBePublished.findIndex( (r) => { return r.Nickname == recordAsObject.Nickname }); 56 | if (currentIdx!=-1) { 57 | if (arrayToBePublished[currentIdx].Timestamp <= recordAsObject.Timestamp) 58 | arrayToBePublished.splice(currentIdx,1,recordAsObject); 59 | } else arrayToBePublished.push(recordAsObject); 60 | }; 61 | records.forEach(record => { 62 | recordsCleanup(record); 63 | }); 64 | for (const record of arrayToBePublished) { 65 | await this.updateArray(this.normalizeRecord(record)); 66 | 67 | } 68 | // deactivating semaphore for updates 69 | this.updateHasFinished = true; 70 | } 71 | 72 | retrieveData(callback) { 73 | var self = this; 74 | if (this.updateHasFinished) { 75 | if (this.currentShardIterator) { 76 | var params = { 77 | ShardIterator: self.currentShardIterator 78 | }; 79 | self.kinesis.getRecords(params, function (err, data) { 80 | if (err) { 81 | console.log(err.code); 82 | console.log(err); 83 | switch(err.code) { 84 | case "ExpiredIteratorException": 85 | self.getInitializeShardIteratorFunction()(); 86 | callback(null,null); 87 | break; 88 | case "CredentialsError": 89 | self.awsfacade.refreshSession((err,data) => { 90 | if (err) { 91 | console.log(err.code); 92 | console.log(err); 93 | console.log("FAILURE REFRESHING SESSION"); 94 | callback(err,null); 95 | } else { 96 | // initialize services with new credentials 97 | console.log("SESSION CREDENTIALS refreshed") 98 | self.initializeAWSServices(); 99 | callback(null,data); 100 | } 101 | }); 102 | break; 103 | default: 104 | callback(err,null); 105 | }; 106 | } 107 | else { 108 | if (data) { 109 | if (data.Records && data.Records.length > 0) 110 | self.updateScoreboard(data.Records); 111 | self.currentShardIterator = data.NextShardIterator; 112 | callback(null,data); 113 | } 114 | } 115 | }); 116 | } else { 117 | var err = new Error("CurrentShardIterator is null."); 118 | err.code = "NullShardIterator"; 119 | callback(err,null); 120 | } 121 | } else callback(null,null); 122 | }; 123 | } 124 | -------------------------------------------------------------------------------- /simulator/processor.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | const {AWS_CONFIG} = require('./../application/resources/js/awsconfig.js'); 4 | const AWS = require('aws-sdk') 5 | const AmazonCognitoIdentity = require('amazon-cognito-identity-js'); 6 | 7 | var gameShots = {}; 8 | var gameLevel = {}; 9 | var gameScore = {}; 10 | 11 | module.exports = { 12 | setJWTToken: setJWTToken, 13 | getUserPoolData: getUserPoolData, 14 | getSessionId: getSessionId, 15 | logAllocationResponse: logAllocationResponse, 16 | logUpdateStatusResponse: logUpdateStatusResponse, 17 | setDateTime, 18 | setGameData 19 | } 20 | 21 | async function setJWTToken(context, ee, next) { 22 | 23 | var username = context.vars.username; 24 | var password = context.vars.password; 25 | var poolid = context.vars.poolId; 26 | var clientid = context.vars.clientId; 27 | var region = AWS_CONFIG.region; 28 | 29 | await loginUser(username, password,poolid,clientid,region) 30 | .then(resopnse => { 31 | console.error("Login successful for user: " + username) 32 | context.vars.jwttoken = resopnse; 33 | }) 34 | .catch(err => { 35 | console.error("Login failed for user: " + username + ". Error" + err) 36 | }) 37 | 38 | return next(); // MUST be called for the scenario to continue 39 | } 40 | 41 | function logAllocationResponse(requestParams, response, context, ee, next) { 42 | console.log("Player: " + context.vars.username + " Status: " + response.body); 43 | return next(); // MUST be called for the scenario to continue 44 | } 45 | 46 | function setDateTime(requestParams, context, ee, next) { 47 | console.log((new Date()).toJSON()); 48 | context.vars.currentdatetime = (new Date()).toJSON(); 49 | return next(); // MUST be called for the scenario to continue 50 | } 51 | 52 | function setGameData(requestParams, context, ee, next) { 53 | 54 | var username = context.vars.username; 55 | 56 | var lives = 3 ; 57 | var level = gameLevel[username] ? gameLevel[username] : 1 ; 58 | var score = gameScore[username] ? gameScore[username] : 0 ; 59 | var shots = gameShots[username] ? gameShots[username] : 0 ; 60 | 61 | console.log("Before:" + shots) 62 | 63 | var randomShots = getRandomInt(0,6); 64 | var randomMissedShots = getRandomInt(0,4); 65 | 66 | shots = shots+ randomShots; 67 | score = randomMissedShots ==0 ? score : score + (randomShots*5); 68 | if (score >= 250){ 69 | level = level + 1; 70 | } 71 | 72 | console.log("After:" + shots ) 73 | console.log("Score:" + score ) 74 | context.vars.lives = lives; 75 | context.vars.level = level; 76 | context.vars.score = score; 77 | context.vars.shots = shots 78 | 79 | 80 | gameShots[username] = shots; 81 | gameLevel[username] = level; 82 | gameScore[username] = score; 83 | return next(); // MUST be called for the scenario to continue 84 | } 85 | 86 | 87 | function getRandomInt(min, max) 88 | { 89 | min = Math.ceil(min); 90 | max = Math.floor(max); 91 | return Math.floor(Math.random() * (max - min) + min); //The maximum is exclusive and the minimum is inclusive 92 | } 93 | 94 | function logUpdateStatusResponse(requestParams, response, context, ee, next) { 95 | console.log("Status updated: " + response.body); 96 | return next(); // MUST be called for the scenario to continue 97 | } 98 | 99 | function getUserPoolData(requestParams, response, context, ee, next) { 100 | 101 | let dataAsJSON = JSON.parse(response.body); 102 | console.log('Parameters' + dataAsJSON.Parameters[1].Name); 103 | let userPoolConfiguration = dataAsJSON.Parameters; 104 | 105 | let poolid = userPoolConfiguration.filter((e) => { return e.Name.indexOf("userpoolid") > -1 }); 106 | context.vars.poolId= poolid[0].Value; 107 | let clientid = userPoolConfiguration.filter((e) => { return e.Name.indexOf("clientid") > -1 }); 108 | context.vars.clientId= clientid[0].Value; 109 | let userpoolurl = userPoolConfiguration.filter((e) => { return e.Name.indexOf("userpoolurl") > -1 }); 110 | context.vars.userPoolURL= userpoolurl[0].Value 111 | return next(); 112 | } 113 | 114 | function getSessionId(requestParams, response, context, ee, next) { 115 | 116 | //Adding try catch to continue with scenario even though artillery is throwing error while parsing session in first line 117 | try{ 118 | let dataAsJSON = JSON.parse(response.body); 119 | if (typeof dataAsJSON == 'string') dataAsJSON = JSON.parse(dataAsJSON); 120 | let sessionId = dataAsJSON.SessionId; 121 | context.vars.sessionid = sessionId; 122 | 123 | } 124 | catch(err){ 125 | console.log("Error while getting session id: " + err) 126 | } 127 | finally{ 128 | return next(); 129 | } 130 | } 131 | 132 | 133 | 134 | async function loginUser(username, password, poolid, clientid,region) { 135 | var usernameForLogin = username.toLowerCase(); 136 | var loginUserParams = { 'Username': usernameForLogin, 'Password': password }; 137 | var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(loginUserParams); 138 | var poolData = { 139 | UserPoolId: poolid, 140 | ClientId: clientid 141 | }; 142 | var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData) 143 | var userData = { 144 | Pool: userPool, 145 | Username: usernameForLogin 146 | }; 147 | var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData); 148 | 149 | return new Promise((resolve, reject) =>( 150 | cognitoUser.authenticateUser(authenticationDetails, { 151 | onSuccess: (result) => resolve(result.getIdToken().getJwtToken()), 152 | onFailure: (err) => reject(err) 153 | 154 | } 155 | ) 156 | 157 | 158 | )); 159 | 160 | } -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/mainLayer.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { App, CfnOutput } from 'aws-cdk-lib'; 4 | import { IParameterAwareProps, ParameterAwareProps, ResourceAwareStack } from '../resourceawarestack'; 5 | 6 | import { SecurityLayer } from './securityLayer'; 7 | import { ConfigurationLayer } from './configurationLayer'; 8 | import { StorageLayer } from './storageLayer'; 9 | import { DatabaseLayer } from './databaseLayer'; 10 | import { IngestionConsumptionLayer } from './ingestionConsumptionLayer'; 11 | import { ProcessingLayer } from './processingLayer'; 12 | import { WebSocketLayer } from './websocketLayer'; 13 | 14 | import { ContentDeliveryLayer } from './contentDeliveryLayer'; 15 | 16 | var DEPLOY_CDN : boolean = false; 17 | var SESSION_PARAMETER : boolean = false; 18 | 19 | 20 | export class MainLayer extends ResourceAwareStack { 21 | 22 | constructor(scope: App, id: string, props?: IParameterAwareProps) { 23 | super(scope, id, props); 24 | if (props && props.getParameter("deploycdn")) DEPLOY_CDN = true; 25 | if (props && props.getParameter("sessionparameter")) SESSION_PARAMETER=true; 26 | this.buildResources(); 27 | } 28 | 29 | buildResources() { 30 | 31 | // security layer 32 | let securityLayer = 33 | new SecurityLayer(this, 'SecurityLayer', this.properties); 34 | 35 | // configuration layer 36 | let configLayerProps = new ParameterAwareProps(this.properties); 37 | 38 | let ssmProperties = new Map(); 39 | ssmProperties.set("Region", this.region); 40 | ssmProperties.set("ClientId", securityLayer.getUserPoolClientId()); 41 | ssmProperties.set("UserpoolId", securityLayer.getUserPoolId()); 42 | ssmProperties.set("UserPoolURL", securityLayer.getUserPoolUrl()); 43 | ssmProperties.set("IdentityPoolId", securityLayer.getIdentityPoolId()); 44 | 45 | if (SESSION_PARAMETER) ssmProperties.set("Session", "null"); 46 | configLayerProps.addParameter('ssmParameters', ssmProperties); 47 | 48 | let configLayer = 49 | new ConfigurationLayer(this, 'ConfigurationLayer', configLayerProps); 50 | 51 | // storage layer 52 | let storageLayer = 53 | new StorageLayer(this, 'StorageStorage', this.properties); 54 | 55 | let cdnLayer = null; 56 | if (DEPLOY_CDN) { 57 | let cdnLayerProps = new ParameterAwareProps(this.properties); 58 | cdnLayerProps.addParameter('appbucket', storageLayer.getResource('appbucket')); 59 | cdnLayer = new ContentDeliveryLayer(this, 'ContentDeliveryLayer', cdnLayerProps); 60 | } 61 | 62 | 63 | // database layer 64 | let databaseLayer = 65 | new DatabaseLayer(this, 'DatabaseLayer', this.properties); 66 | 67 | 68 | // processing layer 69 | let processingLayerProps = new ParameterAwareProps(this.properties); 70 | if (SESSION_PARAMETER) processingLayerProps.addParameter('parameter.session', configLayer.getResource('parameter.session')); 71 | 72 | processingLayerProps.addParameter('table.sessionControl', databaseLayer.getResource('table.sessionControl')); 73 | processingLayerProps.addParameter('table.sessionTopX', databaseLayer.getResource('table.sessionTopX')); 74 | processingLayerProps.addParameter('table.session', databaseLayer.getResource('table.session')); 75 | let processingLayer = new ProcessingLayer(this, 'ProcessingLayer', processingLayerProps); 76 | 77 | // WebSocket Layer 78 | let webSocketLayerProps = new ParameterAwareProps(this.properties); 79 | webSocketLayerProps.addParameter('table.sessionControl', databaseLayer.getResource('table.sessionControl')); 80 | new WebSocketLayer(this, 'WebSocketLayer', webSocketLayerProps); 81 | 82 | // Ingestion/consumption layer 83 | let ingestionConsumptionLayerProps = new ParameterAwareProps(processingLayerProps); 84 | ingestionConsumptionLayerProps.addParameter('rawbucketarn', storageLayer.getRawDataBucketArn()); 85 | ingestionConsumptionLayerProps.addParameter('userpool',securityLayer.getUserPoolArn()); 86 | ingestionConsumptionLayerProps.addParameter('userpoolid', securityLayer.getUserPoolId()); 87 | ingestionConsumptionLayerProps.addParameter('table.session',databaseLayer.getResource('table.session')); 88 | ingestionConsumptionLayerProps.addParameter('table.sessiontopx',databaseLayer.getResource('table.sessiontopx')); 89 | ingestionConsumptionLayerProps.addParameter('lambda.allocate',processingLayer.getAllocateFunctionRef()); 90 | ingestionConsumptionLayerProps.addParameter('lambda.deallocate',processingLayer.getDeallocateFunctionArn()); 91 | ingestionConsumptionLayerProps.addParameter('lambda.scoreboard',processingLayer.getScoreboardFunctionRef()); 92 | ingestionConsumptionLayerProps.addParameter('security.playersrole', securityLayer.getResource('security.playersrole')); 93 | ingestionConsumptionLayerProps.addParameter('security.managersrole', securityLayer.getResource('security.managersrole')); 94 | let icl = new IngestionConsumptionLayer(this, 'IngestionConsumptionLayer',ingestionConsumptionLayerProps); 95 | 96 | new CfnOutput(this, "apigtw", { 97 | description : "API Gateway URL", 98 | value : icl.getResource("apigtw.url"), 99 | exportName : this.properties.getApplicationName().toLocaleLowerCase()+":apigtw" 100 | }); 101 | 102 | new CfnOutput(this, "region", { 103 | description : "region", 104 | value : this.region, 105 | exportName : this.properties.getApplicationName().toLocaleLowerCase()+":region" 106 | }); 107 | 108 | new CfnOutput(this, "envname", { 109 | description : "Environment name", 110 | value : this.properties.getApplicationName(), 111 | exportName : this.properties.getApplicationName().toLocaleLowerCase()+":envname" 112 | }); 113 | 114 | if (cdnLayer) { 115 | new CfnOutput(this, "url", { 116 | description : "Cloudfront domain for the website (Cloudfront distribution)", 117 | value : cdnLayer.getResource("cdndomain"), 118 | exportName : this.properties.getApplicationName().toLocaleLowerCase()+":url" 119 | }).node.addDependency(cdnLayer); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /infrastructure/fixwebsocket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: MIT-0 4 | # 5 | # This Script deploys a websocket with the correct lambda routes 6 | # This script will also change various IAM Roles to invoke the websocket 7 | # source fixwebsocket.sh 8 | 9 | function removeQuotes() { 10 | retval=$1 11 | retval=${retval#\"} 12 | retval=${retval%\"} 13 | echo "$retval" 14 | } 15 | 16 | function createWebSocket() { 17 | envName=$1 18 | region=$2 19 | accountId=$3 20 | 21 | websocketCreateCommand=$(echo aws apigatewayv2 --region "$region" create-api --name "$envName"WebSocket --protocol-type WEBSOCKET --route-selection-expression '\$request.body.action' --query ApiId --output text) 22 | websocketApiId=$(removeQuotes $( eval $websocketCreateCommand )) 23 | 24 | getApiRole=$(echo aws "iam list-roles --query 'Roles[?contains(RoleName,\`"$envName"API\`)].Arn|[0]'") 25 | apiRoleArn=$(removeQuotes $( eval $getApiRole )) 26 | 27 | connectIntegration=$(aws apigatewayv2 create-integration --api-id $websocketApiId --integration-type AWS_PROXY --integration-method POST\ 28 | --integration-uri arn:aws:apigateway:"$region":lambda:path/2015-03-31/functions/arn:aws:lambda:"$region":"$accountId":function:"$envName"WebSocketConnect/invocations\ 29 | --query IntegrationId --output text --credentials-arn "$apiRoleArn") 30 | 31 | connectId=$(aws apigatewayv2 --region "$region" create-route --api-id "$websocketApiId"\ 32 | --route-key \$connect --output text --query RouteId --target integrations/"$connectIntegration") 33 | 34 | startGameIntegration=$(aws apigatewayv2 create-integration --api-id $websocketApiId --integration-type AWS_PROXY --integration-method POST\ 35 | --integration-uri arn:aws:apigateway:"$region":lambda:path/2015-03-31/functions/arn:aws:lambda:"$region":"$accountId":function:"$envName"WebSocketSynchronizeStart/invocations\ 36 | --query IntegrationId --output text --credentials-arn "$apiRoleArn") 37 | 38 | startGameId=$(aws apigatewayv2 --region "$region" create-route --api-id "$websocketApiId"\ 39 | --route-key start-game --output text --query RouteId --target integrations/"$startGameIntegration") 40 | 41 | disconnectIntegration=$(aws apigatewayv2 create-integration --api-id $websocketApiId --integration-type AWS_PROXY --integration-method POST\ 42 | --integration-uri arn:aws:apigateway:"$region":lambda:path/2015-03-31/functions/arn:aws:lambda:"$region":"$accountId":function:"$envName"WebSocketDisconnect/invocations\ 43 | --query IntegrationId --output text --credentials-arn "$apiRoleArn") 44 | 45 | disconnectId=$(aws apigatewayv2 --region "$region" create-route --api-id "$websocketApiId"\ 46 | --route-key \$disconnect --output text --query RouteId --target integrations/"$disconnectIntegration") 47 | 48 | deploymentId=$(aws apigatewayv2 --region "$region" create-deployment --api-id "$websocketApiId" --query DeploymentId --output text) 49 | 50 | stageId=$(aws apigatewayv2 --region "$region" create-stage --api-id "$websocketApiId" --deployment-id "$deploymentId" --stage-name production) 51 | 52 | echo ${websocketApiId} 53 | 54 | } 55 | 56 | function createUrlParam() { 57 | echo "" 58 | echo "#### Creating the SSM Parameter /"$(echo $envName | tr 'A-Z' 'a-z')"/websocket API" 59 | echo "" 60 | URL=$2 61 | envName=$(echo $1 | tr 'A-Z' 'a-z') 62 | success=$(aws ssm put-parameter --name /"$envName"/websocket --value $URL --type String --overwrite) 63 | } 64 | 65 | function adjustLambdaIamRole() { 66 | region=$2 67 | accountId=$3 68 | apiId=$4 69 | roleName="$1"WebSocketSynchronizeStartFn_Role 70 | echo "" 71 | echo "### Adjusting the Role "$(echo $1 | tr 'a-z' 'A-Z')"WebSocketSynchronizeStartFn_Role" 72 | echo "" 73 | # Create JSON variable that stores in-line policy 74 | apiArn="arn:aws:execute-api:"$region":"$accountId":"$apiId"/*" 75 | inlinePolicy=$(cat <<-EOF 76 | { 77 | "Version": "2012-10-17", 78 | "Statement": [ 79 | { 80 | "Effect": "Allow", 81 | "Action": [ 82 | "execute-api:Invoke", 83 | "execute-api:ManageConnections" 84 | ], 85 | "Resource": "$apiArn" 86 | } 87 | ] 88 | } 89 | EOF 90 | ) 91 | if [ "$C9_HOSTNAME" != "" ]; then 92 | echo "*******************************************" 93 | echo "*********** IMPORTANT ***********" 94 | echo "*******************************************" 95 | echo "You are running the workshop under Cloud9." 96 | echo "By default, Cloud9 can't do the adjustment to the $roleName" 97 | echo "Please go to IAM, find that role and add the following policy, naming it as Invoke-Api-Policy" 98 | echo $inlinePolicy 99 | else 100 | putIamRole=$(cat <<-END 101 | aws iam put-role-policy --role-name '$roleName'\ 102 | --policy-name Invoke-Api-Policy\ 103 | --policy-document '$inlinePolicy' 104 | END 105 | ) 106 | echo $putIamRole 107 | eval $putIamRole 108 | fi 109 | } 110 | 111 | if [ "$envname" == "" ]; then 112 | echo 113 | echo "** ERROR**" 114 | echo Please ensure that the variable envname is defined 115 | else 116 | envName=$(echo $envname | tr 'a-z' 'A-Z') 117 | existingAPI=$(aws apigatewayv2 get-apis --query 'Items[?contains("Name",`'$envname'WebSocket`)] | [0].ApiId' --output text) 118 | if [ "$existingAPI" == "" ] || [ "$existingAPI" == "None" ]; then 119 | echo "*******************************************" 120 | echo " Initiating Websocket configuration..." 121 | echo "*******************************************" 122 | region=$(aws configure get region) 123 | accountId=$(aws sts get-caller-identity --output text --query 'Account') 124 | echo "" 125 | echo "### Creating the WebSocket API" 126 | echo "" 127 | apiId=$( createWebSocket $envName $region $accountId ) 128 | URL="wss://${apiId}.execute-api.${region}.amazonaws.com/production" 129 | createUrlParam $envName $URL 130 | adjustLambdaIamRole $envName $region $accountId $apiId 131 | echo "*******************************************" 132 | echo "finishing..." 133 | echo WebSocket ARN: "arn:aws:execute-api:"$region":"$accountId":"$apiId"/*" 134 | echo "*******************************************" 135 | else 136 | echo " ### ERROR: You already have an existing API with your environment name: "${envname}"Websocket" 137 | fi 138 | fi -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/databaseLayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.DatabaseLayer = void 0; 4 | const aws_cdk_lib_1 = require("aws-cdk-lib"); 5 | const resourceawarestack_1 = require("./../resourceawarestack"); 6 | const DynamoDB = require("aws-cdk-lib/aws-dynamodb"); 7 | class DatabaseLayer extends resourceawarestack_1.ResourceAwareConstruct { 8 | constructor(parent, name, props) { 9 | super(parent, name, props); 10 | this.tables = new Map(); 11 | let sessionTable = new DynamoDB.Table(this, props.getApplicationName() + 'Session', { 12 | tableName: props.getApplicationName() + 'Session', 13 | partitionKey: { 14 | name: 'SessionId', 15 | type: DynamoDB.AttributeType.STRING 16 | }, 17 | billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST, 18 | removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY 19 | }); 20 | this.addResource('table.session', sessionTable); 21 | let sessionControlTable = new DynamoDB.Table(this, props.getApplicationName() + 'SessionControl', { 22 | tableName: props.getApplicationName() + 'SessionControl', 23 | partitionKey: { 24 | name: 'SessionId', 25 | type: DynamoDB.AttributeType.STRING 26 | }, 27 | billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST, 28 | removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY 29 | }); 30 | this.addResource('table.sessioncontrol', sessionControlTable); 31 | let sessionTopXTable = new DynamoDB.Table(this, props.getApplicationName() + 'SessionTopX', { 32 | tableName: props.getApplicationName() + 'SessionTopX', 33 | partitionKey: { 34 | name: 'SessionId', 35 | type: DynamoDB.AttributeType.STRING 36 | }, 37 | billingMode: DynamoDB.BillingMode.PAY_PER_REQUEST, 38 | removalPolicy: aws_cdk_lib_1.RemovalPolicy.DESTROY 39 | }); 40 | this.addResource('table.sessiontopx', sessionTopXTable); 41 | } 42 | } 43 | exports.DatabaseLayer = DatabaseLayer; 44 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YWJhc2VMYXllci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRhdGFiYXNlTGF5ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR0EsNkNBQTZDO0FBQzdDLGdFQUFzRjtBQUV0RixxREFBc0Q7QUFHdEQsTUFBYSxhQUFjLFNBQVEsMkNBQXNCO0lBR3JELFlBQVksTUFBaUIsRUFBRSxJQUFZLEVBQUUsS0FBMkI7UUFDcEUsS0FBSyxDQUFDLE1BQU0sRUFBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFIOUIsV0FBTSxHQUFnQyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBSzVDLElBQUksWUFBWSxHQUFHLElBQUksUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLEdBQUMsU0FBUyxFQUFFO1lBQzdFLFNBQVMsRUFBRyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxTQUFTO1lBQ2hELFlBQVksRUFBRztnQkFDWCxJQUFJLEVBQUcsV0FBVztnQkFDbEIsSUFBSSxFQUFHLFFBQVEsQ0FBQyxhQUFhLENBQUMsTUFBTTthQUN2QztZQUNELFdBQVcsRUFBRyxRQUFRLENBQUMsV0FBVyxDQUFDLGVBQWU7WUFDbEQsYUFBYSxFQUFHLDJCQUFhLENBQUMsT0FBTztTQUN4QyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsV0FBVyxDQUFDLGVBQWUsRUFBQyxZQUFZLENBQUMsQ0FBQztRQUUvQyxJQUFJLG1CQUFtQixHQUFHLElBQUksUUFBUSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLEdBQUMsZ0JBQWdCLEVBQUU7WUFDM0YsU0FBUyxFQUFHLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxHQUFDLGdCQUFnQjtZQUN2RCxZQUFZLEVBQUc7Z0JBQ1gsSUFBSSxFQUFHLFdBQVc7Z0JBQ2xCLElBQUksRUFBRyxRQUFRLENBQUMsYUFBYSxDQUFDLE1BQU07YUFDdkM7WUFDRCxXQUFXLEVBQUcsUUFBUSxDQUFDLFdBQVcsQ0FBQyxlQUFlO1lBQ2xELGFBQWEsRUFBRywyQkFBYSxDQUFDLE9BQU87U0FDeEMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxDQUFDLFdBQVcsQ0FBQyxzQkFBc0IsRUFBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBRTdELElBQUksZ0JBQWdCLEdBQUcsSUFBSSxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksRUFBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxhQUFhLEVBQUU7WUFDckYsU0FBUyxFQUFHLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxHQUFDLGFBQWE7WUFDcEQsWUFBWSxFQUFHO2dCQUNYLElBQUksRUFBRyxXQUFXO2dCQUNsQixJQUFJLEVBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxNQUFNO2FBQ3ZDO1lBQ0QsV0FBVyxFQUFHLFFBQVEsQ0FBQyxXQUFXLENBQUMsZUFBZTtZQUNsRCxhQUFhLEVBQUcsMkJBQWEsQ0FBQyxPQUFPO1NBQ3hDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxXQUFXLENBQUMsbUJBQW1CLEVBQUMsZ0JBQWdCLENBQUMsQ0FBQztJQUMzRCxDQUFDO0NBQ0o7QUF2Q0Qsc0NBdUNDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IEFtYXpvbi5jb20sIEluYy4gb3IgaXRzIGFmZmlsaWF0ZXMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlULTBcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gJ2NvbnN0cnVjdHMnO1xuaW1wb3J0IHsgIFJlbW92YWxQb2xpY3kgfSBmcm9tICdhd3MtY2RrLWxpYic7XG5pbXBvcnQgeyBSZXNvdXJjZUF3YXJlQ29uc3RydWN0LCBJUGFyYW1ldGVyQXdhcmVQcm9wcyB9IGZyb20gJy4vLi4vcmVzb3VyY2Vhd2FyZXN0YWNrJ1xuXG5pbXBvcnQgRHluYW1vREIgPSByZXF1aXJlKCdhd3MtY2RrLWxpYi9hd3MtZHluYW1vZGInKTtcblxuXG5leHBvcnQgY2xhc3MgRGF0YWJhc2VMYXllciBleHRlbmRzIFJlc291cmNlQXdhcmVDb25zdHJ1Y3Qge1xuICAgIHRhYmxlcyA6IE1hcDxzdHJpbmcsRHluYW1vREIuVGFibGU+ID0gbmV3IE1hcCgpO1xuXG4gICAgY29uc3RydWN0b3IocGFyZW50OiBDb25zdHJ1Y3QsIG5hbWU6IHN0cmluZywgcHJvcHM6IElQYXJhbWV0ZXJBd2FyZVByb3BzKSB7XG4gICAgICAgIHN1cGVyKHBhcmVudCxuYW1lLCBwcm9wcyk7XG4gICAgICAgIFxuICAgICAgICBsZXQgc2Vzc2lvblRhYmxlID0gbmV3IER5bmFtb0RCLlRhYmxlKHRoaXMscHJvcHMuZ2V0QXBwbGljYXRpb25OYW1lKCkrJ1Nlc3Npb24nLCB7XG4gICAgICAgICAgICB0YWJsZU5hbWUgOiBwcm9wcy5nZXRBcHBsaWNhdGlvbk5hbWUoKSsnU2Vzc2lvbicsXG4gICAgICAgICAgICBwYXJ0aXRpb25LZXkgOiB7XG4gICAgICAgICAgICAgICAgbmFtZSA6ICdTZXNzaW9uSWQnLFxuICAgICAgICAgICAgICAgIHR5cGUgOiBEeW5hbW9EQi5BdHRyaWJ1dGVUeXBlLlNUUklOR1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGJpbGxpbmdNb2RlIDogRHluYW1vREIuQmlsbGluZ01vZGUuUEFZX1BFUl9SRVFVRVNUICAgLFxuICAgICAgICAgICAgcmVtb3ZhbFBvbGljeSA6IFJlbW92YWxQb2xpY3kuREVTVFJPWSAgIFxuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5hZGRSZXNvdXJjZSgndGFibGUuc2Vzc2lvbicsc2Vzc2lvblRhYmxlKTtcblxuICAgICAgICBsZXQgc2Vzc2lvbkNvbnRyb2xUYWJsZSA9IG5ldyBEeW5hbW9EQi5UYWJsZSh0aGlzLHByb3BzLmdldEFwcGxpY2F0aW9uTmFtZSgpKydTZXNzaW9uQ29udHJvbCcsIHtcbiAgICAgICAgICAgIHRhYmxlTmFtZSA6IHByb3BzLmdldEFwcGxpY2F0aW9uTmFtZSgpKydTZXNzaW9uQ29udHJvbCcsXG4gICAgICAgICAgICBwYXJ0aXRpb25LZXkgOiB7XG4gICAgICAgICAgICAgICAgbmFtZSA6ICdTZXNzaW9uSWQnLFxuICAgICAgICAgICAgICAgIHR5cGUgOiBEeW5hbW9EQi5BdHRyaWJ1dGVUeXBlLlNUUklOR1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGJpbGxpbmdNb2RlIDogRHluYW1vREIuQmlsbGluZ01vZGUuUEFZX1BFUl9SRVFVRVNULFxuICAgICAgICAgICAgcmVtb3ZhbFBvbGljeSA6IFJlbW92YWxQb2xpY3kuREVTVFJPWSAgIFxuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5hZGRSZXNvdXJjZSgndGFibGUuc2Vzc2lvbmNvbnRyb2wnLHNlc3Npb25Db250cm9sVGFibGUpO1xuXG4gICAgICAgIGxldCBzZXNzaW9uVG9wWFRhYmxlID0gbmV3IER5bmFtb0RCLlRhYmxlKHRoaXMscHJvcHMuZ2V0QXBwbGljYXRpb25OYW1lKCkrJ1Nlc3Npb25Ub3BYJywge1xuICAgICAgICAgICAgdGFibGVOYW1lIDogcHJvcHMuZ2V0QXBwbGljYXRpb25OYW1lKCkrJ1Nlc3Npb25Ub3BYJyxcbiAgICAgICAgICAgIHBhcnRpdGlvbktleSA6IHtcbiAgICAgICAgICAgICAgICBuYW1lIDogJ1Nlc3Npb25JZCcsXG4gICAgICAgICAgICAgICAgdHlwZSA6IER5bmFtb0RCLkF0dHJpYnV0ZVR5cGUuU1RSSU5HXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgYmlsbGluZ01vZGUgOiBEeW5hbW9EQi5CaWxsaW5nTW9kZS5QQVlfUEVSX1JFUVVFU1QsXG4gICAgICAgICAgICByZW1vdmFsUG9saWN5IDogUmVtb3ZhbFBvbGljeS5ERVNUUk9ZXG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLmFkZFJlc291cmNlKCd0YWJsZS5zZXNzaW9udG9weCcsc2Vzc2lvblRvcFhUYWJsZSk7XG4gICAgfVxufSJdfQ== -------------------------------------------------------------------------------- /infrastructure/cdk/lib/layer/contentDeliveryLayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ContentDeliveryLayer = void 0; 4 | const resourceawarestack_1 = require("./../resourceawarestack"); 5 | const aws_cloudfront_1 = require("aws-cdk-lib/aws-cloudfront"); 6 | const aws_s3_1 = require("aws-cdk-lib/aws-s3"); 7 | const IAM = require("aws-cdk-lib/aws-iam"); 8 | class ContentDeliveryLayer extends resourceawarestack_1.ResourceAwareConstruct { 9 | constructor(parent, name, props) { 10 | super(parent, name, props); 11 | this.createDistribution(props); 12 | } 13 | createDistribution(props) { 14 | let s3BucketOrCnfBucket = props.getParameter('appBucket'); 15 | let appBucket = aws_s3_1.Bucket.fromBucketName(this, props.getApplicationName() + 'ImportedBucket', s3BucketOrCnfBucket.bucketName); 16 | let cloudFrontAccessIdentity = new aws_cloudfront_1.OriginAccessIdentity(this, this.properties.getApplicationName() + 'CDNAccessId', { 17 | comment: "Under The Sea OAI for " + s3BucketOrCnfBucket.bucketName 18 | }); 19 | appBucket.grantRead(cloudFrontAccessIdentity); 20 | let distribution = new aws_cloudfront_1.CloudFrontWebDistribution(this, props.getApplicationName(), { 21 | originConfigs: [ 22 | { 23 | s3OriginSource: { 24 | s3BucketSource: appBucket, 25 | originAccessIdentity: cloudFrontAccessIdentity 26 | }, 27 | behaviors: [{ isDefaultBehavior: true }] 28 | } 29 | ] 30 | }); 31 | new aws_s3_1.BucketPolicy(this, props.getApplicationName() + 'AppBucketPolicy', { 32 | bucket: appBucket, 33 | }).document.addStatements(new IAM.PolicyStatement({ 34 | actions: ["s3:GetObject"], 35 | effect: IAM.Effect.ALLOW, 36 | resources: [ 37 | appBucket.arnForObjects("*") 38 | ], 39 | principals: [new IAM.ArnPrincipal("arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity " + cloudFrontAccessIdentity.originAccessIdentityName)] 40 | })); 41 | this.addResource("cdndomain", distribution.distributionDomainName); 42 | } 43 | } 44 | exports.ContentDeliveryLayer = ContentDeliveryLayer; 45 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGVudERlbGl2ZXJ5TGF5ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb250ZW50RGVsaXZlcnlMYXllci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFHQSxnRUFBc0Y7QUFDdEYsK0RBQTZGO0FBQzdGLCtDQUF5RDtBQUN6RCwyQ0FBNEM7QUFHNUMsTUFBYSxvQkFBcUIsU0FBUSwyQ0FBc0I7SUFFNUQsWUFBWSxNQUFpQixFQUFFLElBQVksRUFBRSxLQUEyQjtRQUNwRSxLQUFLLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMzQixJQUFJLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDbkMsQ0FBQztJQUVPLGtCQUFrQixDQUFDLEtBQTJCO1FBRWxELElBQUksbUJBQW1CLEdBQUcsS0FBSyxDQUFDLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUMxRCxJQUFJLFNBQVMsR0FBWSxlQUFNLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxnQkFBZ0IsRUFBRSxtQkFBbUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUVsSSxJQUFJLHdCQUF3QixHQUFHLElBQUkscUNBQW9CLENBQUMsSUFBSSxFQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsa0JBQWtCLEVBQUUsR0FBQyxhQUFhLEVBQUU7WUFDN0csT0FBTyxFQUFHLHdCQUF3QixHQUFDLG1CQUFtQixDQUFDLFVBQVU7U0FDcEUsQ0FBQyxDQUFDO1FBQ0gsU0FBUyxDQUFDLFNBQVMsQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO1FBRzlDLElBQUksWUFBWSxHQUFHLElBQUksMENBQXlCLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxrQkFBa0IsRUFBRSxFQUFDO1lBQzlFLGFBQWEsRUFBRztnQkFDWjtvQkFDSSxjQUFjLEVBQUc7d0JBQ2IsY0FBYyxFQUFFLFNBQVM7d0JBQ3pCLG9CQUFvQixFQUFHLHdCQUF3QjtxQkFDbEQ7b0JBQ0QsU0FBUyxFQUFHLENBQUUsRUFBQyxpQkFBaUIsRUFBRSxJQUFJLEVBQUMsQ0FBQztpQkFDM0M7YUFDSjtTQUNKLENBQUMsQ0FBQztRQUdILElBQUkscUJBQVksQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLGtCQUFrQixFQUFFLEdBQUMsaUJBQWlCLEVBQUU7WUFDakUsTUFBTSxFQUFHLFNBQVM7U0FDckIsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxHQUFHLENBQUMsZUFBZSxDQUFDO1lBQzlDLE9BQU8sRUFBRyxDQUFFLGNBQWMsQ0FBRTtZQUM1QixNQUFNLEVBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxLQUFLO1lBQzFCLFNBQVMsRUFBRTtnQkFDUCxTQUFTLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQzthQUMvQjtZQUNELFVBQVUsRUFBRyxDQUFFLElBQUksR0FBRyxDQUFDLFlBQVksQ0FBQyxpRUFBaUUsR0FBQyx3QkFBd0IsQ0FBQyx3QkFBd0IsQ0FBQyxDQUFFO1NBQzdKLENBQUMsQ0FDRCxDQUFDO1FBRUYsSUFBSSxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUMsWUFBWSxDQUFDLHNCQUFzQixDQUFDLENBQUM7SUFDdEUsQ0FBQztDQUNKO0FBN0NELG9EQTZDQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCBBbWF6b24uY29tLCBJbmMuIG9yIGl0cyBhZmZpbGlhdGVzLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVC0wXG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tICdjb25zdHJ1Y3RzJztcbmltcG9ydCB7IFJlc291cmNlQXdhcmVDb25zdHJ1Y3QsIElQYXJhbWV0ZXJBd2FyZVByb3BzIH0gZnJvbSAnLi8uLi9yZXNvdXJjZWF3YXJlc3RhY2snXG5pbXBvcnQgeyBDbG91ZEZyb250V2ViRGlzdHJpYnV0aW9uLCBPcmlnaW5BY2Nlc3NJZGVudGl0eSB9IGZyb20gJ2F3cy1jZGstbGliL2F3cy1jbG91ZGZyb250JztcbmltcG9ydCB7IEJ1Y2tldCwgQnVja2V0UG9saWN5fSBmcm9tICdhd3MtY2RrLWxpYi9hd3MtczMnO1xuaW1wb3J0IElBTSA9IHJlcXVpcmUoJ2F3cy1jZGstbGliL2F3cy1pYW0nKTtcblxuXG5leHBvcnQgY2xhc3MgQ29udGVudERlbGl2ZXJ5TGF5ZXIgZXh0ZW5kcyBSZXNvdXJjZUF3YXJlQ29uc3RydWN0IHtcblxuICAgIGNvbnN0cnVjdG9yKHBhcmVudDogQ29uc3RydWN0LCBuYW1lOiBzdHJpbmcsIHByb3BzOiBJUGFyYW1ldGVyQXdhcmVQcm9wcykge1xuICAgICAgICBzdXBlcihwYXJlbnQsIG5hbWUsIHByb3BzKTtcbiAgICAgICAgdGhpcy5jcmVhdGVEaXN0cmlidXRpb24ocHJvcHMpO1xuICAgIH1cblxuICAgIHByaXZhdGUgY3JlYXRlRGlzdHJpYnV0aW9uKHByb3BzOiBJUGFyYW1ldGVyQXdhcmVQcm9wcykge1xuXG4gICAgICAgIGxldCBzM0J1Y2tldE9yQ25mQnVja2V0ID0gcHJvcHMuZ2V0UGFyYW1ldGVyKCdhcHBCdWNrZXQnKTtcbiAgICAgICAgbGV0IGFwcEJ1Y2tldCA9IDxCdWNrZXQ+IEJ1Y2tldC5mcm9tQnVja2V0TmFtZSh0aGlzLCBwcm9wcy5nZXRBcHBsaWNhdGlvbk5hbWUoKSsnSW1wb3J0ZWRCdWNrZXQnLCBzM0J1Y2tldE9yQ25mQnVja2V0LmJ1Y2tldE5hbWUpO1xuICAgICAgICBcbiAgICAgICAgbGV0IGNsb3VkRnJvbnRBY2Nlc3NJZGVudGl0eSA9IG5ldyBPcmlnaW5BY2Nlc3NJZGVudGl0eSh0aGlzLHRoaXMucHJvcGVydGllcy5nZXRBcHBsaWNhdGlvbk5hbWUoKSsnQ0ROQWNjZXNzSWQnLCB7XG4gICAgICAgICAgICBjb21tZW50IDogXCJVbmRlciBUaGUgU2VhIE9BSSBmb3IgXCIrczNCdWNrZXRPckNuZkJ1Y2tldC5idWNrZXROYW1lXG4gICAgICAgIH0pO1xuICAgICAgICBhcHBCdWNrZXQuZ3JhbnRSZWFkKGNsb3VkRnJvbnRBY2Nlc3NJZGVudGl0eSk7XG4gICAgICAgIFxuICAgICAgICBcbiAgICAgICAgbGV0IGRpc3RyaWJ1dGlvbiA9IG5ldyBDbG91ZEZyb250V2ViRGlzdHJpYnV0aW9uKHRoaXMsIHByb3BzLmdldEFwcGxpY2F0aW9uTmFtZSgpLHtcbiAgICAgICAgICAgIG9yaWdpbkNvbmZpZ3MgOiBbXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICBzM09yaWdpblNvdXJjZSA6IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHMzQnVja2V0U291cmNlOiBhcHBCdWNrZXQsXG4gICAgICAgICAgICAgICAgICAgICAgICBvcmlnaW5BY2Nlc3NJZGVudGl0eSA6IGNsb3VkRnJvbnRBY2Nlc3NJZGVudGl0eVxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICBiZWhhdmlvcnMgOiBbIHtpc0RlZmF1bHRCZWhhdmlvcjogdHJ1ZX1dXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgXVxuICAgICAgICB9KTtcbiAgICAgICAgXG4gICAgICAgICAgICAgICAgXG4gICAgICAgIG5ldyBCdWNrZXRQb2xpY3kodGhpcywgcHJvcHMuZ2V0QXBwbGljYXRpb25OYW1lKCkrJ0FwcEJ1Y2tldFBvbGljeScsIHtcbiAgICAgICAgICAgIGJ1Y2tldCA6IGFwcEJ1Y2tldCxcbiAgICAgICAgfSkuZG9jdW1lbnQuYWRkU3RhdGVtZW50cyhuZXcgSUFNLlBvbGljeVN0YXRlbWVudCh7XG4gICAgICAgICAgICBhY3Rpb25zIDogWyBcInMzOkdldE9iamVjdFwiIF0sXG4gICAgICAgICAgICBlZmZlY3QgOiAgSUFNLkVmZmVjdC5BTExPVyxcbiAgICAgICAgICAgIHJlc291cmNlczogW1xuICAgICAgICAgICAgICAgIGFwcEJ1Y2tldC5hcm5Gb3JPYmplY3RzKFwiKlwiKVxuICAgICAgICAgICAgXSxcbiAgICAgICAgICAgIHByaW5jaXBhbHMgOiBbIG5ldyBJQU0uQXJuUHJpbmNpcGFsKFwiYXJuOmF3czppYW06OmNsb3VkZnJvbnQ6dXNlci9DbG91ZEZyb250IE9yaWdpbiBBY2Nlc3MgSWRlbnRpdHkgXCIrY2xvdWRGcm9udEFjY2Vzc0lkZW50aXR5Lm9yaWdpbkFjY2Vzc0lkZW50aXR5TmFtZSkgXVxuICAgICAgICB9KVxuICAgICAgICApO1xuXG4gICAgICAgIHRoaXMuYWRkUmVzb3VyY2UoXCJjZG5kb21haW5cIixkaXN0cmlidXRpb24uZGlzdHJpYnV0aW9uRG9tYWluTmFtZSk7XG4gICAgfVxufSJdfQ== -------------------------------------------------------------------------------- /application/game/js/wavefield.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | /* 4 | Wavefield lets you take a div and turn it into a Wavefield. 5 | 6 | */ 7 | 8 | 9 | 10 | // Define the Wavefield class. 11 | function Wavefield() { 12 | this.fps = 30; 13 | this.canvas = null; 14 | this.width = 0; 15 | this.width = 0; 16 | this.minVelocity = 15; 17 | this.maxVelocity = 30; 18 | this.waves = 100; 19 | this.intervalId = 0; 20 | this.scoreboard = []; 21 | /* 22 | this.scoreboard = [ 23 | { 24 | "Level": 1, 25 | "Lives": 3, 26 | "Nickname": "fabian.martins", 27 | "Score": 0, 28 | "Shots": 0, 29 | "Timestamp": "2018-10-31T20:04:10.245Z" 30 | }, 31 | { 32 | "Level": 1, 33 | "Lives": 0, 34 | "Nickname": "chicobento", 35 | "Score": 170, 36 | "Shots": 37, 37 | "Timestamp": "2018-10-29T16:51:25.814Z" 38 | }, 39 | { 40 | "Level": 1, 41 | "Lives": 3, 42 | "Nickname": "malkavian", 43 | "Score": 15, 44 | "Shots": 3, 45 | "Timestamp": "2018-10-29T17:39:42.858Z" 46 | }, 47 | { 48 | "Level": 3, 49 | "Lives": 0, 50 | "Nickname": "mark", 51 | "Score": 775, 52 | "Shots": 247, 53 | "Timestamp": "2018-10-29T21:25:17.768Z" 54 | }, 55 | { 56 | "Level": 5, 57 | "Lives": 0, 58 | "Nickname": "jurema", 59 | "Score": 1590, 60 | "Shots": 299, 61 | "Timestamp": "2018-10-30T00:43:07.071Z" 62 | } 63 | ]; 64 | */ 65 | } 66 | 67 | // The main function - initialises the Wavefield. 68 | Wavefield.prototype.initialise = function (div) { 69 | var self = this; 70 | 71 | // Store the div. 72 | this.containerDiv = div; 73 | self.width = window.innerWidth; 74 | self.height = window.innerHeight; 75 | 76 | window.onresize = function (event) { 77 | self.width = window.innerWidth; 78 | self.height = window.innerHeight; 79 | self.canvas.width = self.width; 80 | self.canvas.height = self.height; 81 | self.draw(); 82 | } 83 | 84 | // Create the canvas. 85 | var canvas = document.createElement('canvas'); 86 | div.appendChild(canvas); 87 | this.canvas = canvas; 88 | this.canvas.width = this.width; 89 | this.canvas.height = this.height; 90 | }; 91 | 92 | Wavefield.prototype.start = function () { 93 | 94 | // Create the waves. 95 | var waves = []; 96 | for (var i = 0; i < this.waves; i++) { 97 | waves[i] = new Wave(Math.random() * this.width, Math.random() * this.height, Math.random() * 3 + 1, 98 | (Math.random() * (this.maxVelocity - this.minVelocity)) + this.minVelocity); 99 | } 100 | this.waves = waves; 101 | 102 | var self = this; 103 | // wavet the timer. 104 | this.intervalId = setInterval(function () { 105 | self.update(); 106 | self.draw(); 107 | }, 1000 / this.fps); 108 | }; 109 | 110 | Wavefield.prototype.stop = function () { 111 | clearInterval(this.intervalId); 112 | }; 113 | 114 | Wavefield.prototype.update = function () { 115 | var dt = 1 / this.fps; 116 | 117 | for (var i = 0; i < this.waves.length; i++) { 118 | var wave = this.waves[i]; 119 | wave.y += dt * wave.velocity; 120 | // If the wave has moved from the bottom of the screen, spawn it at the top. 121 | if (wave.y > this.height) { 122 | this.waves[i] = new Wave(Math.random() * this.width, 0, Math.random() * 3 + 1, 123 | (Math.random() * (this.maxVelocity - this.minVelocity)) + this.minVelocity); 124 | } 125 | } 126 | }; 127 | 128 | Wavefield.prototype.setScoreboard = function (scoreboard) { 129 | this.scoreboard = scoreboard; 130 | } 131 | 132 | Wavefield.prototype.draw = function () { 133 | 134 | // Get the drawing context. 135 | var ctx = this.canvas.getContext("2d"); 136 | 137 | // Draw the background 138 | 139 | //add gradient 140 | var grd = ctx.createLinearGradient(0,0,this.width,this.height); 141 | grd.addColorStop(0,"#1DCCD8"); 142 | grd.addColorStop(1,"#1DA2D8"); 143 | ctx.fillStyle=grd; 144 | //ctx.fillStyle = '#1da2d8'; 145 | ctx.fillRect(0, 0, this.width, this.height); 146 | 147 | // Draw waves. 148 | ctx.fillStyle = "rgba(255, 255, 255, 0.0)"; 149 | for (var i = 0; i < this.waves.length; i++) { 150 | var wave = this.waves[i]; 151 | ctx.beginPath(); 152 | ctx.arc(wave.x, wave.y, wave.size*2, 0, 2 * Math.PI,false); 153 | ctx.lineWidth=1.5; 154 | ctx.strokeStyle="#ffffff"; 155 | ctx.stroke(); 156 | ctx.fillRect(wave.x, wave.y, wave.size, wave.size); 157 | } 158 | 159 | this.drawScoreboard(ctx); 160 | }; 161 | 162 | 163 | Wavefield.prototype.drawScoreboard = function (ctx) { 164 | 165 | fontSize = 11; 166 | ctx.font = fontSize + "px Arial"; 167 | ctx.fillStyle = '#ff9900' 168 | ctx.textBaseline = "middle"; 169 | ctx.textAlign = "left"; 170 | 171 | let self = this; 172 | let sizeLimits = { 173 | "Order": 3, 174 | "Nickname": 15, 175 | "Score": 6, 176 | "Lives": 8, 177 | "Shots": 12, 178 | "Level": 8 179 | }; 180 | 181 | let printGamerScore = function (gamer, gamerPosition) { 182 | let verticalPosition = 40 + fontSize * gamerPosition; 183 | let horizontalPosition = self.width - 350; 184 | let tabPositions = 0; 185 | 186 | let gamerPositionTxt = (gamerPosition + 1) + ". " 187 | horizontalPosition = horizontalPosition + tabPositions; 188 | ctx.textAlign = "left"; 189 | ctx.fillText(gamerPositionTxt, horizontalPosition, verticalPosition); 190 | tabPositions = Math.floor(sizeLimits["Order"] * fontSize / 2); 191 | 192 | let gamerIdentification = gamer.Nickname.substring(0, sizeLimits["Nickname"]); 193 | horizontalPosition = horizontalPosition + tabPositions; 194 | ctx.textAlign = "left"; 195 | ctx.fillText(gamerIdentification, horizontalPosition, verticalPosition); 196 | tabPositions = Math.floor(sizeLimits["Nickname"] * fontSize / 2); 197 | 198 | horizontalPosition = horizontalPosition + tabPositions + sizeLimits["Score"] * Math.floor(fontSize) / 2; 199 | ctx.textAlign = "right"; 200 | ctx.fillText(gamer.Score, horizontalPosition, verticalPosition); 201 | tabPositions = Math.floor(1 * fontSize / 2); 202 | 203 | horizontalPosition = horizontalPosition + tabPositions; 204 | let lives = "Lives: " + gamer.Lives; 205 | ctx.textAlign = "left"; 206 | ctx.fillText(lives, horizontalPosition, verticalPosition); 207 | tabPositions = Math.floor(sizeLimits["Lives"] * fontSize / 2); 208 | 209 | horizontalPosition = horizontalPosition + tabPositions; 210 | let shots = "Shots: " + gamer.Shots; 211 | ctx.textAlign = "left"; 212 | ctx.fillText(shots, horizontalPosition, verticalPosition); 213 | tabPositions = Math.floor(sizeLimits["Shots"] * fontSize / 2); 214 | 215 | horizontalPosition = horizontalPosition + tabPositions; 216 | let level = "Level: " + gamer.Level; 217 | ctx.textAlign = "left"; 218 | ctx.fillText(level, horizontalPosition, verticalPosition) 219 | 220 | return; 221 | } 222 | 223 | if (this.scoreboard && this.scoreboard.length > 0) { 224 | ctx.fillStyle = '#333231' 225 | ctx.textBaseline = "middle"; 226 | ctx.textAlign = "left"; 227 | 228 | ctx.font = "16px Arial"; 229 | ctx.fillText("TOP PLAYERS FOR THIS SESSION", this.width - 350, 20); 230 | 231 | fontSize = 11; 232 | ctx.font = fontSize + "px Arial"; 233 | // only the 1st 25 players will be shown 234 | limit = ((this.scoreboard.length > 25) ? 25 : this.scoreboard.length); 235 | for (i = 0; i < limit; i++) { 236 | printGamerScore(this.scoreboard[i], i) 237 | }; 238 | } 239 | return; 240 | } 241 | 242 | function Wave(x, y, size, velocity) { 243 | this.x = x; 244 | this.y = y; 245 | this.size = size; 246 | this.velocity = velocity; 247 | } -------------------------------------------------------------------------------- /infrastructure/cdk/bin/cdk.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // SPDX-License-Identifier: MIT-0 6 | const cdk = require("aws-cdk-lib"); 7 | const mainLayer_1 = require("../lib/layer/mainLayer"); 8 | const nrta_1 = require("../lib/nrta"); 9 | const utils_1 = require("../lib/util/utils"); 10 | const app = new cdk.App(); 11 | let envname = app.node.tryGetContext('envname'); 12 | if (!envname) { 13 | console.log("****************************************************"); 14 | console.log("ERROR: your environment name is undefined.\n"); 15 | console.log("Please run the command like this:"); 16 | console.log("cdk [synth|deploy|destroy] -c envname="); 17 | console.log("****************************************************"); 18 | process.exit(1); 19 | } 20 | else 21 | envname = envname.toUpperCase(); 22 | console.log('# Environment name:', envname); 23 | var initProps = new nrta_1.NRTAProps(); 24 | initProps.setApplicationName(envname); 25 | let setApplicationProperty = (propName, description) => { 26 | let envproperty = app.node.tryGetContext(propName); 27 | if (envproperty) { 28 | console.log('# ' + description + ' is going to be deployed: YES'); 29 | initProps.addParameter(propName, true); 30 | } 31 | else { 32 | console.log('# ' + description + ' is going to be deployed: NO'); 33 | } 34 | ; 35 | }; 36 | // Getting other possible context names 37 | // FOR THE CDN DEPLOYMENT 38 | setApplicationProperty("deploycdn", "Cloudfront"); 39 | // Getting other possible context names 40 | // FOR SSM PARAMETER 41 | setApplicationProperty("sessionparameter", "SSM Parameter Session"); 42 | // Getting other possible context names 43 | // FOR KINESIS DATA STREAMS INTEGRATION 44 | setApplicationProperty("kinesisintegration", "Kinesis Data Streams integration"); 45 | // Getting other possible context names 46 | // FOR KINESIS FIREHOSE 47 | setApplicationProperty("firehose", "Kinesis Firehose"); 48 | utils_1.Utils.checkforExistingBuckets(initProps.getBucketNames()) 49 | .then((listOfExistingBuckets) => { 50 | if (listOfExistingBuckets && listOfExistingBuckets.length > 0) 51 | console.log("# The following buckets are NOT being created because they already exist: ", listOfExistingBuckets); 52 | initProps.addParameter('existingbuckets', listOfExistingBuckets); 53 | new mainLayer_1.MainLayer(app, initProps.getApplicationName(), initProps); 54 | }) 55 | .catch((errorList) => { 56 | console.log(errorList); 57 | }); 58 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2RrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLHFFQUFxRTtBQUNyRSxpQ0FBaUM7QUFDakMsbUNBQW9DO0FBRXBDLHNEQUFtRDtBQUNuRCxzQ0FBd0M7QUFDeEMsNkNBQXlDO0FBR3pDLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxDQUFDLEdBQUcsRUFBRSxDQUFDO0FBQzFCLElBQUksT0FBTyxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ2hELElBQUksQ0FBQyxPQUFPLEVBQUU7SUFDVixPQUFPLENBQUMsR0FBRyxDQUFDLHNEQUFzRCxDQUFDLENBQUM7SUFDcEUsT0FBTyxDQUFDLEdBQUcsQ0FBQyw4Q0FBOEMsQ0FBQyxDQUFDO0lBQzVELE9BQU8sQ0FBQyxHQUFHLENBQUMsbUNBQW1DLENBQUMsQ0FBQztJQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLCtEQUErRCxDQUFDLENBQUM7SUFDN0UsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzREFBc0QsQ0FBQyxDQUFDO0lBQ3BFLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7Q0FDbkI7O0lBQ0ksT0FBTyxHQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQztBQUNuQyxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixFQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQzNDLElBQUksU0FBUyxHQUFHLElBQUksZ0JBQVMsRUFBRSxDQUFDO0FBQ2hDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUV0QyxJQUFJLHNCQUFzQixHQUFHLENBQUMsUUFBaUIsRUFBRSxXQUFtQixFQUFFLEVBQUU7SUFDcEUsSUFBSSxXQUFXLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbkQsSUFBSSxXQUFXLEVBQUU7UUFDYixPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksR0FBQyxXQUFXLEdBQUMsK0JBQStCLENBQUMsQ0FBQztRQUM5RCxTQUFTLENBQUMsWUFBWSxDQUFDLFFBQVEsRUFBQyxJQUFJLENBQUMsQ0FBQztLQUN6QztTQUFNO1FBQ0gsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEdBQUMsV0FBVyxHQUFDLDhCQUE4QixDQUFDLENBQUM7S0FDaEU7SUFBQSxDQUFDO0FBQ04sQ0FBQyxDQUFBO0FBRUQsdUNBQXVDO0FBQ3ZDLHlCQUF5QjtBQUN6QixzQkFBc0IsQ0FBQyxXQUFXLEVBQUMsWUFBWSxDQUFDLENBQUM7QUFFakQsdUNBQXVDO0FBQ3ZDLG9CQUFvQjtBQUNwQixzQkFBc0IsQ0FBQyxrQkFBa0IsRUFBQyx1QkFBdUIsQ0FBQyxDQUFDO0FBRW5FLHVDQUF1QztBQUN2Qyx1Q0FBdUM7QUFDdkMsc0JBQXNCLENBQUMsb0JBQW9CLEVBQUMsa0NBQWtDLENBQUMsQ0FBQztBQUVoRix1Q0FBdUM7QUFDdkMsdUJBQXVCO0FBQ3ZCLHNCQUFzQixDQUFDLFVBQVUsRUFBQyxrQkFBa0IsQ0FBQyxDQUFDO0FBR3RELGFBQUssQ0FBQyx1QkFBdUIsQ0FBQyxTQUFTLENBQUMsY0FBYyxFQUFFLENBQUM7S0FDcEQsSUFBSSxDQUFDLENBQUMscUJBQXFCLEVBQUUsRUFBRTtJQUM1QixJQUFJLHFCQUFxQixJQUFJLHFCQUFxQixDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQ3pELE9BQU8sQ0FBQyxHQUFHLENBQUMsNEVBQTRFLEVBQUUscUJBQXFCLENBQUMsQ0FBQztJQUNySCxTQUFTLENBQUMsWUFBWSxDQUFDLGlCQUFpQixFQUFFLHFCQUFxQixDQUFDLENBQUM7SUFDakUsSUFBSSxxQkFBUyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsa0JBQWtCLEVBQUUsRUFBRSxTQUFTLENBQUMsQ0FBQztBQUN0RSxDQUFDLENBQUM7S0FDRyxLQUFLLENBQUMsQ0FBQyxTQUFTLEVBQUUsRUFBRTtJQUNqQixPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQy9CLENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiIyEvdXNyL2Jpbi9lbnYgbm9kZVxuLy8gQ29weXJpZ2h0IEFtYXpvbi5jb20sIEluYy4gb3IgaXRzIGFmZmlsaWF0ZXMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlULTBcbmltcG9ydCBjZGsgPSByZXF1aXJlKCdhd3MtY2RrLWxpYicpO1xuXG5pbXBvcnQgeyBNYWluTGF5ZXIgfSBmcm9tICcuLi9saWIvbGF5ZXIvbWFpbkxheWVyJztcbmltcG9ydCB7IE5SVEFQcm9wcyB9IGZyb20gJy4uL2xpYi9ucnRhJztcbmltcG9ydCB7IFV0aWxzIH0gZnJvbSAnLi4vbGliL3V0aWwvdXRpbHMnXG5cblxuY29uc3QgYXBwID0gbmV3IGNkay5BcHAoKTtcbmxldCBlbnZuYW1lID0gYXBwLm5vZGUudHJ5R2V0Q29udGV4dCgnZW52bmFtZScpO1xuaWYgKCFlbnZuYW1lKSB7XG4gICAgY29uc29sZS5sb2coXCIqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqXCIpO1xuICAgIGNvbnNvbGUubG9nKFwiRVJST1I6IHlvdXIgZW52aXJvbm1lbnQgbmFtZSBpcyB1bmRlZmluZWQuXFxuXCIpO1xuICAgIGNvbnNvbGUubG9nKFwiUGxlYXNlIHJ1biB0aGUgY29tbWFuZCBsaWtlIHRoaXM6XCIpO1xuICAgIGNvbnNvbGUubG9nKFwiY2RrIFtzeW50aHxkZXBsb3l8ZGVzdHJveV0gLWMgZW52bmFtZT08eW91ciBlbnZpcm9ubWVudCBuYW1lPlwiKTtcbiAgICBjb25zb2xlLmxvZyhcIioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKipcIik7XG4gICAgcHJvY2Vzcy5leGl0KDEpO1xufVxuZWxzZSBlbnZuYW1lPWVudm5hbWUudG9VcHBlckNhc2UoKTtcbmNvbnNvbGUubG9nKCcjIEVudmlyb25tZW50IG5hbWU6JyxlbnZuYW1lKTtcbnZhciBpbml0UHJvcHMgPSBuZXcgTlJUQVByb3BzKCk7XG5pbml0UHJvcHMuc2V0QXBwbGljYXRpb25OYW1lKGVudm5hbWUpO1xuXG5sZXQgc2V0QXBwbGljYXRpb25Qcm9wZXJ0eSA9IChwcm9wTmFtZSA6IHN0cmluZywgZGVzY3JpcHRpb246IHN0cmluZykgPT4ge1xuICAgIGxldCBlbnZwcm9wZXJ0eSA9IGFwcC5ub2RlLnRyeUdldENvbnRleHQocHJvcE5hbWUpO1xuICAgIGlmIChlbnZwcm9wZXJ0eSkge1xuICAgICAgICBjb25zb2xlLmxvZygnIyAnK2Rlc2NyaXB0aW9uKycgaXMgZ29pbmcgdG8gYmUgZGVwbG95ZWQ6IFlFUycpO1xuICAgICAgICBpbml0UHJvcHMuYWRkUGFyYW1ldGVyKHByb3BOYW1lLHRydWUpO1xuICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnNvbGUubG9nKCcjICcrZGVzY3JpcHRpb24rJyBpcyBnb2luZyB0byBiZSBkZXBsb3llZDogTk8nKTtcbiAgICB9O1xufVxuXG4vLyBHZXR0aW5nIG90aGVyIHBvc3NpYmxlIGNvbnRleHQgbmFtZXNcbi8vIEZPUiBUSEUgQ0ROIERFUExPWU1FTlRcbnNldEFwcGxpY2F0aW9uUHJvcGVydHkoXCJkZXBsb3ljZG5cIixcIkNsb3VkZnJvbnRcIik7XG5cbi8vIEdldHRpbmcgb3RoZXIgcG9zc2libGUgY29udGV4dCBuYW1lc1xuLy8gRk9SIFNTTSBQQVJBTUVURVJcbnNldEFwcGxpY2F0aW9uUHJvcGVydHkoXCJzZXNzaW9ucGFyYW1ldGVyXCIsXCJTU00gUGFyYW1ldGVyIFNlc3Npb25cIik7XG5cbi8vIEdldHRpbmcgb3RoZXIgcG9zc2libGUgY29udGV4dCBuYW1lc1xuLy8gRk9SIEtJTkVTSVMgREFUQSBTVFJFQU1TIElOVEVHUkFUSU9OXG5zZXRBcHBsaWNhdGlvblByb3BlcnR5KFwia2luZXNpc2ludGVncmF0aW9uXCIsXCJLaW5lc2lzIERhdGEgU3RyZWFtcyBpbnRlZ3JhdGlvblwiKTtcblxuLy8gR2V0dGluZyBvdGhlciBwb3NzaWJsZSBjb250ZXh0IG5hbWVzXG4vLyBGT1IgS0lORVNJUyBGSVJFSE9TRVxuc2V0QXBwbGljYXRpb25Qcm9wZXJ0eShcImZpcmVob3NlXCIsXCJLaW5lc2lzIEZpcmVob3NlXCIpO1xuXG5cblV0aWxzLmNoZWNrZm9yRXhpc3RpbmdCdWNrZXRzKGluaXRQcm9wcy5nZXRCdWNrZXROYW1lcygpKVxuICAgIC50aGVuKChsaXN0T2ZFeGlzdGluZ0J1Y2tldHMpID0+IHtcbiAgICAgICAgaWYgKGxpc3RPZkV4aXN0aW5nQnVja2V0cyAmJiBsaXN0T2ZFeGlzdGluZ0J1Y2tldHMubGVuZ3RoID4gMClcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiIyBUaGUgZm9sbG93aW5nIGJ1Y2tldHMgYXJlIE5PVCBiZWluZyBjcmVhdGVkIGJlY2F1c2UgdGhleSBhbHJlYWR5IGV4aXN0OiBcIiwgbGlzdE9mRXhpc3RpbmdCdWNrZXRzKTtcbiAgICAgICAgaW5pdFByb3BzLmFkZFBhcmFtZXRlcignZXhpc3RpbmdidWNrZXRzJywgbGlzdE9mRXhpc3RpbmdCdWNrZXRzKTtcbiAgICAgICAgbmV3IE1haW5MYXllcihhcHAsIGluaXRQcm9wcy5nZXRBcHBsaWNhdGlvbk5hbWUoKSwgaW5pdFByb3BzKTtcbn0pXG4gICAgLmNhdGNoKChlcnJvckxpc3QpID0+IHtcbiAgICAgICAgY29uc29sZS5sb2coZXJyb3JMaXN0KTtcbn0pOyJdfQ== -------------------------------------------------------------------------------- /infrastructure/cdk/lib/resourceawarestack.ts: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | import { Construct } from 'constructs'; 4 | import { Stack, StackProps } from 'aws-cdk-lib'; 5 | 6 | export interface IFlexNameApplication { 7 | applicationName? : string, 8 | getApplicationName() : string 9 | } 10 | 11 | export interface IResourceAware { 12 | getResources() : Map; 13 | getResource(resourceName: string) : any | undefined; 14 | addResources(resources : Map) : void; 15 | addResource(map: string, resource:any) : void; 16 | getResourcesNames() : IterableIterator | string[]; 17 | } 18 | 19 | export interface IParameterAware { 20 | getParameters() : Map; 21 | getParameter(parameterName: string) : any | undefined; 22 | addParameters(parameters : Map) : void; 23 | addParameter(map: string, resource:any) : void; 24 | } 25 | 26 | export interface IDeploymentTarget { 27 | accountId? : string, 28 | region? : string 29 | } 30 | 31 | export class ResourceBag implements IResourceAware { 32 | 33 | private resources : Map; 34 | 35 | constructor(resources? : IResourceAware) { 36 | if (resources && resources.getResources()) { 37 | if (!this.resources) this.resources = new Map(); 38 | resources.getResources().forEach( (v,k) => { 39 | this.resources.set(k.toLowerCase(),v); 40 | }) 41 | }; 42 | } 43 | 44 | getResources() : Map { 45 | return this.resources; 46 | }; 47 | 48 | addResources(resources : Map) { 49 | if (resources) { 50 | if (!this.resources) this.resources = new Map(); 51 | for (let resourceName of resources.keys()) { 52 | let name = resourceName.toLowerCase(); 53 | this.resources.set(name, resources.get(name)); 54 | } 55 | } 56 | }; 57 | 58 | addResource(key: string, resource:any) : void { 59 | if (resource) { 60 | if (!this.resources) this.resources = new Map(); 61 | this.resources.set(key.toLowerCase(),resource); 62 | } 63 | } 64 | 65 | getResource(key: string) : any | undefined { 66 | return this.resources.get(key.toLowerCase()); 67 | } 68 | 69 | getResourcesNames() { 70 | if (this.resources) return this.resources.keys(); 71 | else return []; 72 | } 73 | 74 | } 75 | 76 | export interface IParameterAwareProps extends StackProps, IParameterAware, IFlexNameApplication, IDeploymentTarget { 77 | } 78 | 79 | export class ParameterAwareProps implements IParameterAwareProps { 80 | 81 | accountId? : string; 82 | region? : string; 83 | 84 | 85 | // handling/defining the application name. 86 | // Default is NRTA - Near Real-Time Application 87 | static defaultApplicationName : string = 'NRTA'; 88 | applicationName? : string; 89 | setApplicationName(appName : string) { 90 | if (appName && appName.length>0) this.applicationName = appName.toUpperCase(); 91 | } 92 | getApplicationName() { 93 | let appName = this.applicationName ? this.applicationName : ParameterAwareProps.defaultApplicationName; 94 | return appName; 95 | } 96 | 97 | parameters : Map; 98 | 99 | getParameters() : Map { 100 | return this.parameters; 101 | }; 102 | 103 | addParameters(parameters : Map) { 104 | if (parameters) { 105 | if (!this.parameters) this.parameters = new Map(); 106 | for (let parameterName of parameters.keys()) { 107 | this.parameters.set(parameterName.toLowerCase(), parameters.get(parameterName)) 108 | } 109 | } 110 | }; 111 | 112 | addParameter(key: string, parameter:any) : void { 113 | if (parameter) { 114 | if (!this.parameters) this.parameters = new Map(); 115 | this.parameters.set(key.toLowerCase(),parameter); 116 | } 117 | } 118 | 119 | getParameter(key: string) : any | undefined { 120 | if (!this.parameters) this.parameters = new Map(); 121 | return this.parameters.get(key.toLowerCase()); 122 | } 123 | 124 | constructor(props?: IParameterAwareProps) { 125 | this.applicationName = (props && props.applicationName && props.applicationName.length > 0) ? props.applicationName : ParameterAwareProps.defaultApplicationName; 126 | if (props) { 127 | this.region = props.region; 128 | this.accountId = props.accountId; 129 | if (props.getParameters()) props.getParameters().forEach( (v,k) => this.addParameter(k,v) ); 130 | } 131 | } 132 | } 133 | 134 | 135 | export class ResourceAwareStack extends Stack implements IResourceAware { 136 | 137 | protected resources : Map; 138 | protected scope: Construct | undefined; 139 | protected properties : IParameterAwareProps; 140 | 141 | constructor(parent?: Construct, name?: string, props?: IParameterAwareProps) { 142 | super(parent,name,props); 143 | if (this.scope) 144 | this.scope = parent; 145 | if (!this.properties) this.properties = new ParameterAwareProps(props); 146 | if (!this.properties.accountId) this.properties.accountId = this.account; 147 | if (!this.properties.region) this.properties.region = this.region; 148 | } 149 | 150 | getResources() : Map { 151 | return this.resources; 152 | }; 153 | 154 | addResources(resources : Map) { 155 | if (resources) { 156 | if (!this.resources) this.resources = new Map(); 157 | for (let resourceName of resources.keys()) { 158 | let name = resourceName.toLowerCase(); 159 | this.resources.set(name, resources.get(name)); 160 | } 161 | } 162 | }; 163 | 164 | addResource(key: string, resource:any) : void { 165 | if (resource) { 166 | if (!this.resources) this.resources = new Map(); 167 | this.resources.set(key.toLowerCase(),resource); 168 | } 169 | } 170 | 171 | getResource(key: string) : any | undefined { 172 | if (!this.resources) this.resources = new Map(); 173 | return this.resources.get(key.toLowerCase()); 174 | } 175 | 176 | getResourcesNames() { 177 | if (this.resources) return this.resources.keys(); 178 | else return []; 179 | } 180 | 181 | getProperties() { 182 | return this.properties; 183 | } 184 | } 185 | 186 | 187 | export class ResourceAwareConstruct extends Construct implements IResourceAware { 188 | 189 | resources : Map; 190 | protected properties : IParameterAwareProps; 191 | 192 | constructor(scope: Construct, id: string, props: IParameterAwareProps) { 193 | super(scope,id); 194 | this.properties = props; 195 | } 196 | 197 | getResources() : Map { 198 | return this.resources; 199 | }; 200 | 201 | addResources(resources : Map) { 202 | if (resources) { 203 | if (!this.resources) this.resources = new Map(); 204 | for (let resourceName of resources.keys()) { 205 | let name = resourceName.toLowerCase(); 206 | this.resources.set(name, resources.get(name)); 207 | } 208 | } 209 | }; 210 | 211 | addResource(key: string, resource:any) : void { 212 | if (resource) { 213 | if (!this.resources) this.resources = new Map(); 214 | this.resources.set(key.toLowerCase(),resource); 215 | } 216 | } 217 | 218 | getResource(key: string) : any | undefined { 219 | return this.resources.get(key.toLowerCase()); 220 | } 221 | 222 | getResourcesNames() { 223 | if (this.resources) return this.resources.keys(); 224 | else return []; 225 | } 226 | 227 | getProperties() { 228 | return this.properties; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/synchronousStart/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | /** 4 | * Purpose of this function is to post to every connection 5 | * and let them know the game has started 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const AWS = require('aws-sdk'); 11 | const DynamoDB = new AWS.DynamoDB.DocumentClient(); 12 | const SSM = new AWS.SSM(); 13 | const APIGatewayManagement = new AWS.ApiGatewayManagementApi({apiVersion: '2018-11-29'}); 14 | 15 | const readSessionFromSSM = function (callback) { 16 | let param = { 17 | "Name": process.env.SESSION_PARAMETER 18 | }; 19 | SSM.getParameter(param, 20 | function (error, sessionParamResponse) { 21 | if (error) { 22 | let errorMessage = "Error reading from SSM"; 23 | console.log(errorMessage); 24 | console.log(error); 25 | let responseError = new Error(errorMessage); 26 | responseError.code = "ErrorReadingSSM"; 27 | responseError.details = error; 28 | callback(responseError,500); 29 | } else { 30 | let sessionData = null; 31 | try { 32 | sessionData = JSON.parse(sessionParamResponse.Parameter.Value); 33 | callback(null, sessionData); 34 | } catch (error) { 35 | let errorMessage = "Error parsing session data from SSM"; 36 | console.log(errorMessage); 37 | console.log(error); 38 | let responseError = new Error(errorMessage); 39 | responseError.code = "ErrorReadingFromSSM"; 40 | responseError.details = error; 41 | console.log(sessionData); 42 | callback(responseError, 500); 43 | } 44 | } 45 | } 46 | ); 47 | }; 48 | 49 | const postSynchronousStart = (session, callback) => { 50 | session.SynchronizeTime = (new Date()).toJSON(); 51 | let payload = JSON.stringify(session); 52 | let param = { 53 | "Name": process.env.SESSION_PARAMETER, 54 | "Type": 'String', 55 | "Value": payload, 56 | "Overwrite": true, 57 | 'Description': 'Currently opened or recently closed session', 58 | }; 59 | SSM.putParameter(param, (err, _) => { 60 | if (err) { 61 | console.log(err); 62 | callback(null, err); 63 | } 64 | callback(); 65 | }); 66 | }; 67 | 68 | const readConnectionsFromDynamo = (session, callback) => { 69 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 70 | console.log(session); 71 | let params = { 72 | TableName: tableName, 73 | Key: {'SessionId': session.SessionId}, 74 | ConsistentRead: true 75 | }; 76 | DynamoDB.get(params, (err, data) => { 77 | if (err) { 78 | console.log(err); 79 | callback(new Error("Error reading connections ")); 80 | } else { 81 | console.log(data); 82 | let connections = data.Item.connections.map((elem) => {return elem;}); 83 | console.log(connections); 84 | callback(null, connections); 85 | } 86 | }); 87 | } 88 | 89 | const deleteStaleConnection = (count, session, callback) => { 90 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 91 | const updateParams = { 92 | TableName: tableName, 93 | Key: { 'SessionId': session.SessionId}, 94 | UpdateExpression: 'REMOVE #connections[' + count + ']', 95 | ExpressionAttributeNames: { 96 | '#connections': 'connections' 97 | } 98 | }; 99 | DynamoDB.update(updateParams, (err, _) => { 100 | if (err) callback(err); 101 | else callback(); 102 | }); 103 | } 104 | 105 | const dispatchToConnections = async (connections, session, callback) => { 106 | console.log(APIGatewayManagement); 107 | let count = 0; 108 | for (let connection of connections) { 109 | try { 110 | console.log('posting to connection: ', connection); 111 | await APIGatewayManagement.postToConnection({ 112 | ConnectionId: connection, 113 | Data: 'start' 114 | }).promise(); 115 | } catch (e) { 116 | console.log(e); 117 | if (e.statusCode == 410) { 118 | deleteStaleConnection(count, session, (err,_) => { 119 | if (err) console.alarm('Error deleting stale connection', err); 120 | }); 121 | count--; 122 | } 123 | } 124 | count++; 125 | } 126 | 127 | console.log('Sent to all connections') 128 | callback(null, 'success'); 129 | } 130 | 131 | 132 | exports.handler = (event, context, callback) => { 133 | APIGatewayManagement.endpoint = event.requestContext.domainName + '/' + event.requestContext.stage; 134 | let response = null; 135 | readSessionFromSSM((err, session) => { 136 | if (err) { 137 | response = { 138 | isBase64Encoded: false, 139 | statusCode: err.errorCode, 140 | body: JSON.stringify({'errorMessage':err.errorMessage, 'errorCode': err.errorCode}) 141 | }; 142 | callback(null, err); 143 | } else { 144 | if (!session || typeof session != 'string' || session.trim() == '') { 145 | response = { 146 | isBase64Encoded: false, 147 | statusCode: 400, 148 | body: JSON.stringify({ 149 | 'errorMessage':'no session available', 150 | 'errorCode': 400 151 | }) 152 | }; 153 | callback(null, response); 154 | } 155 | // Write to SSM and update paramater. 156 | postSynchronousStart(session, (err,_) => { 157 | if (err) { 158 | response = { 159 | isBase64Encoded: false, 160 | statusCode: 400, 161 | body: JSON.stringify({ 162 | 'error': err 163 | }) 164 | }; 165 | callback(null, response); 166 | } 167 | readConnectionsFromDynamo(session, (err, connections) => { 168 | if (err) { 169 | response = { 170 | isBase64Encoded: false, 171 | statusCode: 400, 172 | body: JSON.stringify({ 173 | 'error': err 174 | }) 175 | }; 176 | callback(null, response); 177 | } else { 178 | if (!connections || connections.length == 0) { 179 | response = { 180 | isBase64Encoded: false, 181 | statusCode: 400, 182 | body: JSON.stringify({ 183 | 'errorMessage': 'There are no connections', 184 | 'errorCode': 400 185 | }) 186 | }; 187 | } 188 | dispatchToConnections(connections, session, (err,_) => { 189 | if (err) { 190 | response = { 191 | isBase64Encoded: false, 192 | statusCode: 400, 193 | body: JSON.stringify({ 194 | 'error': err 195 | }) 196 | }; 197 | callback(null, response); 198 | } else { 199 | response = { 200 | isBase64Encoded: false, 201 | statusCode: 200 202 | }; 203 | callback(null, response); 204 | } 205 | }); 206 | } 207 | }); 208 | }); 209 | } 210 | }); 211 | } 212 | -------------------------------------------------------------------------------- /infrastructure/cdk/lambdas/deallocateGamer/index.js: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 'use strict'; 4 | 5 | const AWS = require('aws-sdk'); 6 | const DynamoDB = new AWS.DynamoDB.DocumentClient(); 7 | const SSM = new AWS.SSM(); 8 | 9 | const readSessionFromSSM = function (callback) { 10 | let param = { 11 | "Name": process.env.SESSION_PARAMETER 12 | }; 13 | SSM.getParameter(param, 14 | function (err, sessionParamResponse) { 15 | if (err) { 16 | console.log("Error reading from SSM"); 17 | console.log(err); 18 | callback(new Error("Internal error reading SSM"),500); 19 | } else { 20 | let sessionData = null; 21 | try { 22 | sessionData = JSON.parse(sessionParamResponse.Parameter.Value); 23 | callback(null, sessionData); 24 | } catch (error) { 25 | console.log("ERROR parsing sessionData."); 26 | console.log(sessionData); 27 | callback(new Error("Error with session configuration."), 500); 28 | } 29 | } 30 | }); 31 | }; 32 | 33 | const readSessionControlFromDynamoDB = function (session, callback) { 34 | let getParams = { 35 | "TableName": process.env.SESSION_CONTROL_TABLENAME, 36 | "Key": { "SessionId": session }, 37 | "ConsistentRead": true 38 | }; 39 | DynamoDB.get(getParams, function (err, data) { 40 | if (err) { 41 | let errorDetails = { 42 | "Error": err, 43 | "ParametersToDynamoDB": getParams, 44 | "ResponseFromDynamoDB": data, 45 | }; 46 | console.log(errorDetails); 47 | callback(new Error("Error reading sessionData from database."), 500); 48 | } 49 | else { 50 | console.log("Success in readSessionControlFromDynamoDB"); 51 | console.log(data); 52 | callback(null, data.Item); 53 | } 54 | }); 55 | }; 56 | 57 | const deallocateGamer = function(gamerUsername,callback) { 58 | readSessionFromSSM( (ssmErr,session) => { 59 | if (ssmErr) callback(ssmErr,null); 60 | else { 61 | readSessionControlFromDynamoDB(session.SessionId, (scErr, sessionControl) => { 62 | if (scErr) callback(scErr); 63 | else { 64 | let playingIdx = sessionControl.PlayingGamers.findIndex((e) => { return e == gamerUsername }); 65 | if (playingIdx == -1) { 66 | let message = "User "+gamerUsername+" not found in the list of playing gamers. Discarded."; 67 | callback(null,message); 68 | } 69 | else { 70 | sessionControl.PlayingGamers.splice(playingIdx,1); 71 | // This control is to prevent side effects of crashes 72 | let finishedIdx = sessionControl.FinishedGamers.findIndex((e) => { return e == gamerUsername }); 73 | if (finishedIdx!=-1) sessionControl.FinishedGamers.push(gamerUsername); 74 | let newNumberOfOccupiedSeats = sessionControl.OccupiedSeats - 1; 75 | let tableName = process.env.SESSION_CONTROL_TABLENAME; 76 | let params = { 77 | "TableName": tableName, 78 | "Key": { "SessionId": sessionControl.SessionId }, 79 | "UpdateExpression": "SET OccupiedSeats = :n, PlayingGamers = :p, FinishedGamers = :f", 80 | "ConditionExpression": "OccupiedSeats = :o", 81 | 'ExpressionAttributeValues': { 82 | ":n": newNumberOfOccupiedSeats, 83 | ":p": sessionControl.PlayingGamers, 84 | ":o": sessionControl.OccupiedSeats, 85 | ":f": sessionControl.FinishedGamers 86 | } 87 | }; 88 | DynamoDB.update(params, function (err, data) { 89 | if (err) { 90 | let message = "Error executing DynamoDB.Update"; 91 | callback(new Error(message),422); 92 | } 93 | else { 94 | callback(null, "Success deallocating " + gamerUsername); 95 | } 96 | }); 97 | } 98 | } 99 | }); 100 | } 101 | }); 102 | }; 103 | 104 | 105 | exports.handler = (event, context, callback) => { 106 | console.log('START TIME:', new Date()); 107 | console.log(event); 108 | let response = null; 109 | let input = null; 110 | try { 111 | input = JSON.parse(event.body); 112 | console.log('Converted body'); 113 | console.log(input); 114 | } 115 | catch(conversionError) { 116 | console.log("Input conversion error."); 117 | response = { 118 | statusCode: 400, 119 | isBase64Encoded : false, 120 | headers : { 121 | "X-Amzn-ErrorType":"Error", 122 | "Access-Control-Allow-Origin":"*", 123 | "Access-Control-Allow-Methods":"POST,OPTIONS", 124 | "Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", 125 | "Content-Type":"application/json" 126 | }, 127 | body: JSON.stringify({ 128 | "errorMessage": "Invalid request.", 129 | }) 130 | }; 131 | callback(null,response); 132 | } 133 | if (!input || !input.Username || typeof input.Username!="string" || input.Username.trim()=="") { 134 | console.log("Invalid event"); 135 | response = { 136 | statusCode: 400, 137 | isBase64Encoded : false, 138 | headers : { 139 | "X-Amzn-ErrorType":"Error", 140 | "Access-Control-Allow-Origin":"*", 141 | "Access-Control-Allow-Methods":"POST,OPTIONS", 142 | "Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", 143 | "Content-Type":"application/json" 144 | }, 145 | body: JSON.stringify({ 146 | "errorMessage": "Invalid request. Username not provided.", 147 | }) 148 | }; 149 | console.log(response); 150 | console.log("FAILURE"); 151 | callback(null,response); 152 | } else { 153 | // input.Username = { Username } 154 | deallocateGamer(input.Username, function (err, data) { 155 | if (err) { 156 | console.log(">>>",err); 157 | response = { 158 | isBase64Encoded : false, 159 | statusCode: 502, 160 | headers : { 161 | "X-Amzn-ErrorType":"Error", 162 | "Access-Control-Allow-Origin":"*", 163 | "Access-Control-Allow-Methods":"POST,OPTIONS", 164 | "Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", 165 | "Content-Type":"application/json" 166 | }, 167 | body: JSON.stringify( { 168 | "errorMessage": err.message, 169 | "errorCode" : data 170 | }) 171 | }; 172 | console.log("FAILURE"); 173 | console.log(response); 174 | callback(null,response); 175 | } 176 | else { 177 | response = { 178 | isBase64Encoded : false, 179 | statusCode: 200, 180 | headers : { 181 | "Access-Control-Allow-Origin":"*", 182 | "Access-Control-Allow-Methods":"POST,OPTIONS", 183 | "Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", 184 | "Content-Type":"application/json" 185 | }, 186 | body: JSON.stringify({"successMessage" : data}) 187 | }; 188 | console.log("SUCCESS"); 189 | console.log(response); 190 | callback(null, response); 191 | } 192 | }); 193 | } 194 | }; -------------------------------------------------------------------------------- /infrastructure/cdk/lib/util/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Utils = void 0; 4 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | // SPDX-License-Identifier: MIT-0 6 | const AWS = require("aws-sdk"); 7 | const crypto = require("crypto"); 8 | const fs = require("fs"); 9 | const path = require("path"); 10 | class Utils { 11 | static async bucketExists(bucketName) { 12 | return new Promise((resolve, reject) => { 13 | let params = { 14 | Bucket: bucketName 15 | }; 16 | let sdkS3 = new AWS.S3(); 17 | sdkS3.headBucket(params, (err, _) => { 18 | if (err) { 19 | if (err.code == 'NotFound') 20 | resolve(false); 21 | else 22 | reject(err); 23 | } 24 | else 25 | resolve(true); 26 | }); 27 | }); 28 | } 29 | ; 30 | static async checkforExistingBuckets(listOfBuckets) { 31 | let getListOfExistingBuckets = async function (bucketList) { 32 | return new Promise(async (resolve, reject) => { 33 | let existingBuckets = []; 34 | let errorList = []; 35 | for (let bucketName of bucketList) { 36 | await Utils.bucketExists(bucketName) 37 | .then((exists) => { 38 | if (exists) 39 | existingBuckets.push(bucketName); 40 | }) 41 | .catch((error) => { errorList.push(error); }); 42 | } 43 | if (errorList.length == 0) 44 | resolve(existingBuckets); 45 | else 46 | reject(errorList); 47 | }); 48 | }; 49 | return await getListOfExistingBuckets(listOfBuckets); 50 | } 51 | /** 52 | * Hashes the contents of a file or directory. If the argument is a directory, 53 | * it is assumed not to contain symlinks that would result in a cyclic tree. 54 | * 55 | * @param fileOrDir the path to the file or directory that should be hashed. 56 | * 57 | * @returns a SHA256 hash, base-64 encoded. 58 | * 59 | * source: https://github.com/awslabs/aws-delivlib/blob/master/lib/util.ts 60 | */ 61 | static hashFileOrDirectory(fileOrDir) { 62 | const hash = crypto.createHash('SHA256'); 63 | hash.update(path.basename(fileOrDir)).update('\0'); 64 | const stat = fs.statSync(fileOrDir); 65 | if (stat.isDirectory()) { 66 | for (const item of fs.readdirSync(fileOrDir).sort()) { 67 | hash.update(Utils.hashFileOrDirectory(path.join(fileOrDir, item))); 68 | } 69 | } 70 | else { 71 | hash.update(fs.readFileSync(fileOrDir)); 72 | } 73 | return hash.digest('base64'); 74 | } 75 | } 76 | exports.Utils = Utils; 77 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFBcUU7QUFDckUsaUNBQWlDO0FBQ2pDLCtCQUFnQztBQUNoQyxpQ0FBa0M7QUFDbEMseUJBQTBCO0FBQzFCLDZCQUE4QjtBQUc5QixNQUFhLEtBQUs7SUFFZCxNQUFNLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxVQUFrQjtRQUN4QyxPQUFPLElBQUksT0FBTyxDQUFVLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQzVDLElBQUksTUFBTSxHQUFHO2dCQUNULE1BQU0sRUFBRSxVQUFVO2FBQ3JCLENBQUE7WUFDRCxJQUFJLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUN6QixLQUFLLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxDQUFDLEVBQUUsRUFBRTtnQkFDaEMsSUFBSSxHQUFHLEVBQUU7b0JBQ0wsSUFBSSxHQUFHLENBQUMsSUFBSSxJQUFJLFVBQVU7d0JBQUUsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDOzt3QkFDdEMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2lCQUNwQjs7b0JBQ0ksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3ZCLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQyxDQUFDLENBQUE7SUFDTixDQUFDO0lBQUEsQ0FBQztJQUVGLE1BQU0sQ0FBQyxLQUFLLENBQUMsdUJBQXVCLENBQUMsYUFBdUI7UUFFeEQsSUFBSSx3QkFBd0IsR0FBRyxLQUFLLFdBQVcsVUFBb0I7WUFDL0QsT0FBTyxJQUFJLE9BQU8sQ0FBVyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO2dCQUVuRCxJQUFJLGVBQWUsR0FBYSxFQUFFLENBQUM7Z0JBQ25DLElBQUksU0FBUyxHQUFZLEVBQUUsQ0FBQztnQkFFNUIsS0FBSyxJQUFJLFVBQVUsSUFBSSxVQUFVLEVBQUU7b0JBQy9CLE1BQU0sS0FBSyxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUM7eUJBQy9CLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO3dCQUNiLElBQUksTUFBTTs0QkFBRSxlQUFlLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO29CQUNqRCxDQUFDLENBQUM7eUJBQ0QsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFBLENBQUMsQ0FBQyxDQUFDLENBQUM7aUJBQ3BEO2dCQUNELElBQUksU0FBUyxDQUFDLE1BQU0sSUFBSSxDQUFDO29CQUFFLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQzs7b0JBQy9DLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQztZQUMzQixDQUFDLENBQUMsQ0FBQTtRQUNOLENBQUMsQ0FBQTtRQUVELE9BQU8sTUFBTSx3QkFBd0IsQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBR0Q7Ozs7Ozs7OztHQVNEO0lBQ0MsTUFBTSxDQUFDLG1CQUFtQixDQUFDLFNBQWlCO1FBQ3hDLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDekMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ25ELE1BQU0sSUFBSSxHQUFHLEVBQUUsQ0FBQyxRQUFRLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDcEMsSUFBSSxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUU7WUFDcEIsS0FBSyxNQUFNLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFO2dCQUNqRCxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDdEU7U0FDSjthQUFNO1lBQ0gsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7U0FDM0M7UUFDRCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7SUFFakMsQ0FBQztDQUNKO0FBbEVELHNCQWtFQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCBBbWF6b24uY29tLCBJbmMuIG9yIGl0cyBhZmZpbGlhdGVzLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVC0wXG5pbXBvcnQgQVdTID0gcmVxdWlyZSgnYXdzLXNkaycpO1xuaW1wb3J0IGNyeXB0byA9IHJlcXVpcmUoJ2NyeXB0bycpO1xuaW1wb3J0IGZzID0gcmVxdWlyZSgnZnMnKTtcbmltcG9ydCBwYXRoID0gcmVxdWlyZSgncGF0aCcpO1xuXG5cbmV4cG9ydCBjbGFzcyBVdGlscyB7XG5cbiAgICBzdGF0aWMgYXN5bmMgYnVja2V0RXhpc3RzKGJ1Y2tldE5hbWU6IHN0cmluZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gbmV3IFByb21pc2U8Ym9vbGVhbj4oKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgbGV0IHBhcmFtcyA9IHtcbiAgICAgICAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldE5hbWVcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxldCBzZGtTMyA9IG5ldyBBV1MuUzMoKTtcbiAgICAgICAgICAgIHNka1MzLmhlYWRCdWNrZXQocGFyYW1zLCAoZXJyLCBfKSA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKGVycikge1xuICAgICAgICAgICAgICAgICAgICBpZiAoZXJyLmNvZGUgPT0gJ05vdEZvdW5kJykgcmVzb2x2ZShmYWxzZSk7XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgcmVqZWN0KGVycik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgcmVzb2x2ZSh0cnVlKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KVxuICAgIH07XG5cbiAgICBzdGF0aWMgYXN5bmMgY2hlY2tmb3JFeGlzdGluZ0J1Y2tldHMobGlzdE9mQnVja2V0czogc3RyaW5nW10pIHtcblxuICAgICAgICBsZXQgZ2V0TGlzdE9mRXhpc3RpbmdCdWNrZXRzID0gYXN5bmMgZnVuY3Rpb24gKGJ1Y2tldExpc3Q6IHN0cmluZ1tdKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICAgICAgICAgICAgcmV0dXJuIG5ldyBQcm9taXNlPHN0cmluZ1tdPihhc3luYyAocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG5cbiAgICAgICAgICAgICAgICBsZXQgZXhpc3RpbmdCdWNrZXRzOiBzdHJpbmdbXSA9IFtdO1xuICAgICAgICAgICAgICAgIGxldCBlcnJvckxpc3Q6IEVycm9yW10gPSBbXTtcblxuICAgICAgICAgICAgICAgIGZvciAobGV0IGJ1Y2tldE5hbWUgb2YgYnVja2V0TGlzdCkge1xuICAgICAgICAgICAgICAgICAgICBhd2FpdCBVdGlscy5idWNrZXRFeGlzdHMoYnVja2V0TmFtZSlcbiAgICAgICAgICAgICAgICAgICAgICAgIC50aGVuKChleGlzdHMpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZXhpc3RzKSBleGlzdGluZ0J1Y2tldHMucHVzaChidWNrZXROYW1lKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgICAgICAgICAuY2F0Y2goKGVycm9yKSA9PiB7IGVycm9yTGlzdC5wdXNoKGVycm9yKSB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKGVycm9yTGlzdC5sZW5ndGggPT0gMCkgcmVzb2x2ZShleGlzdGluZ0J1Y2tldHMpO1xuICAgICAgICAgICAgICAgIGVsc2UgcmVqZWN0KGVycm9yTGlzdCk7XG4gICAgICAgICAgICB9KVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGF3YWl0IGdldExpc3RPZkV4aXN0aW5nQnVja2V0cyhsaXN0T2ZCdWNrZXRzKTtcbiAgICB9XG5cblxuICAgIC8qKlxuICogSGFzaGVzIHRoZSBjb250ZW50cyBvZiBhIGZpbGUgb3IgZGlyZWN0b3J5LiBJZiB0aGUgYXJndW1lbnQgaXMgYSBkaXJlY3RvcnksXG4gKiBpdCBpcyBhc3N1bWVkIG5vdCB0byBjb250YWluIHN5bWxpbmtzIHRoYXQgd291bGQgcmVzdWx0IGluIGEgY3ljbGljIHRyZWUuXG4gKlxuICogQHBhcmFtIGZpbGVPckRpciB0aGUgcGF0aCB0byB0aGUgZmlsZSBvciBkaXJlY3RvcnkgdGhhdCBzaG91bGQgYmUgaGFzaGVkLlxuICpcbiAqIEByZXR1cm5zIGEgU0hBMjU2IGhhc2gsIGJhc2UtNjQgZW5jb2RlZC5cbiAqIFxuICogc291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vYXdzbGFicy9hd3MtZGVsaXZsaWIvYmxvYi9tYXN0ZXIvbGliL3V0aWwudHNcbiAqL1xuICAgIHN0YXRpYyBoYXNoRmlsZU9yRGlyZWN0b3J5KGZpbGVPckRpcjogc3RyaW5nKTogc3RyaW5nIHtcbiAgICAgICAgY29uc3QgaGFzaCA9IGNyeXB0by5jcmVhdGVIYXNoKCdTSEEyNTYnKTtcbiAgICAgICAgaGFzaC51cGRhdGUocGF0aC5iYXNlbmFtZShmaWxlT3JEaXIpKS51cGRhdGUoJ1xcMCcpO1xuICAgICAgICBjb25zdCBzdGF0ID0gZnMuc3RhdFN5bmMoZmlsZU9yRGlyKTtcbiAgICAgICAgaWYgKHN0YXQuaXNEaXJlY3RvcnkoKSkge1xuICAgICAgICAgICAgZm9yIChjb25zdCBpdGVtIG9mIGZzLnJlYWRkaXJTeW5jKGZpbGVPckRpcikuc29ydCgpKSB7XG4gICAgICAgICAgICAgICAgaGFzaC51cGRhdGUoVXRpbHMuaGFzaEZpbGVPckRpcmVjdG9yeShwYXRoLmpvaW4oZmlsZU9yRGlyLCBpdGVtKSkpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgaGFzaC51cGRhdGUoZnMucmVhZEZpbGVTeW5jKGZpbGVPckRpcikpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBoYXNoLmRpZ2VzdCgnYmFzZTY0Jyk7XG5cbiAgICB9XG59Il19 -------------------------------------------------------------------------------- /infrastructure/cdk/test/simple.test.ts: -------------------------------------------------------------------------------- 1 | import { Template } from "aws-cdk-lib/assertions"; 2 | import * as configLayer from './../lib/layer/configurationLayer'; 3 | import * as databaseLayer from './../lib/layer/databaseLayer'; 4 | import * as securityLayer from './../lib/layer/securityLayer'; 5 | import * as storageLayer from './../lib/layer/storageLayer'; 6 | import * as processingLayer from './../lib/layer/processingLayer'; 7 | import * as websocketLayer from './../lib/layer/websocketLayer'; 8 | import * as ingestionConsumptionLayer from './../lib/layer/ingestionConsumptionLayer'; 9 | import { ResourceAwareStack } from './../lib/resourceawarestack'; 10 | import { NRTAProps } from './../lib/nrta'; 11 | 12 | /** 13 | * These tests are built using https://jestjs.io/ 14 | * 15 | * To prepare the enviroment, you need to: 16 | * npm install --save-dev jest @types/jest @aws-cdk/assert 17 | */ 18 | 19 | // This interface specifies the test function that we need to have in place to test Under The Sea stacks 20 | interface TestFunction { 21 | (stack : ResourceAwareStack, props: NRTAProps) : void; 22 | } 23 | 24 | // This is the helper class which instantiates the essential resources and then pass them to the testFunction 25 | class UnderTheSeaTest { 26 | static test(testFunction: TestFunction) { 27 | if (!testFunction) throw new Error("Test function was not defined"); 28 | const stack = new ResourceAwareStack(); 29 | const props = new NRTAProps(); 30 | props.region = process.env.region; 31 | props.accountId = process.env.account; 32 | props.setApplicationName('TEST'); 33 | testFunction(stack,props); 34 | } 35 | } 36 | 37 | 38 | // TO-DO need to implement this test 39 | /* 40 | describe("SecurityLayer", () => { 41 | test("Synthesizes the security layer", () => { 42 | const stack = new ResourceAwareStack(); 43 | const props = new NRTAProps(); 44 | props.region = process.env.region; 45 | props.accountId = process.env.account; 46 | props.setApplicationName('TEST_SECURITY'); 47 | new securityLayer.SecurityLayer(stack, 'SecurityLayer', props); 48 | const template = Template.fromStack(stack); 49 | 50 | let expectedResources = [ 51 | 'AWS::IAM::Role', 52 | 'AWS::IAM::Policy', 53 | 'AWS::Lambda::Function', 54 | 'AWS::Cognito::UserPool', 55 | 'AWS::Cognito::UserPoolClient', 56 | 'AWS::Lambda::Permission', 57 | 'AWS::Cognito::IdentityPool', 58 | 'AWS::Cognito::UserPoolGroup', 59 | 'AWS::Cognito::IdentityPoolRoleAttachment' 60 | ]; 61 | expectedResources.forEach( (resource) => { 62 | template.findResources(resource); 63 | }); 64 | }); 65 | }); 66 | */ 67 | 68 | describe("ConfigurationLayer", () => { 69 | test("ConfigurationLayer validation (Systems Manager Parameters)", () => { 70 | const stack = new ResourceAwareStack(); 71 | const props = new NRTAProps(); 72 | props.region = process.env.region; 73 | props.accountId = process.env.account; 74 | props.setApplicationName('TEST_CONFIG'); 75 | let ssmParameters = new Map(); 76 | ssmParameters.set("parameter1", "value1"); 77 | props.addParameter("ssmParameters",ssmParameters); 78 | new configLayer.ConfigurationLayer(stack,'ConfigLayer',props); 79 | const template = Template.fromStack(stack); 80 | 81 | template.findResources("AWS::SSM::Parameter"); 82 | }); 83 | }); 84 | 85 | describe('StorageLayer validation', () => { 86 | test('StorageLayer validation', () => { 87 | const stack = new ResourceAwareStack(); 88 | const props = new NRTAProps(); 89 | props.region = process.env.region; 90 | props.accountId = process.env.account; 91 | props.setApplicationName('TEST_CONFIG'); 92 | const template = Template.fromStack(stack); 93 | template.findResources("AWS::S3::Bucket"); 94 | }); 95 | }); 96 | 97 | // TO-DO need to implement this test 98 | /* 99 | describe('Content Delivery', () => { 100 | test('Content Delivery', () => { 101 | const stack = new ResourceAwareStack(); 102 | const props = new NRTAProps(); 103 | props.region = process.env.region; 104 | props.accountId = process.env.account; 105 | props.addParameter('appBucket','appbucket'); 106 | new contentDeliveryLayer.ContentDeliveryLayer(stack, 'ContentDeliveryLayer', props); 107 | const template = Template.fromStack(stack); 108 | template.findResources('AWS::CloudFront::CloudFrontOriginAccessIdentity'); 109 | template.findResources('AWS::CloudFront::Distribution'); 110 | template.findResources('AWS::S3::BucketPolicy'); 111 | }); 112 | }); 113 | */ 114 | 115 | describe('DatabaseLayer validation', () => { 116 | test('DatabaseLayer validation', () => { 117 | const stack = new ResourceAwareStack(); 118 | const props = new NRTAProps(); 119 | props.region = process.env.region; 120 | props.accountId = process.env.account; 121 | new databaseLayer.DatabaseLayer(stack, 'DatabaseLayer', props); 122 | const template = Template.fromStack(stack); 123 | template.findResources('AWS::DynamoDB::Table'); 124 | }); 125 | }); 126 | 127 | describe('ProcessingLayer validation', () => { 128 | test('ProcessingLayer validation', () => { 129 | const stack = new ResourceAwareStack(); 130 | const props = new NRTAProps(); 131 | props.region = process.env.region; 132 | props.accountId = process.env.account; 133 | props.addParameter('table.sessioncontrol','TBLSESSIONCONTROL'); 134 | props.addParameter('table.sessionTopX','TBLSESSIONTOP'); 135 | props.addParameter('table.session','TBLSESSION'); 136 | new processingLayer.ProcessingLayer(stack, 'ProcessingLayer', props); 137 | let expectedResources = [ 138 | 'AWS::IAM::Role', 139 | 'AWS::Lambda::Function', 140 | 'AWS::SQS::Queue' 141 | ]; 142 | const template = Template.fromStack(stack); 143 | expectedResources.forEach( (resource) => { 144 | template.findResources(resource); 145 | }); 146 | }); 147 | }); 148 | 149 | describe('WebsocketLayer validation', () => { 150 | test('WebsocketLayer validation', () => { 151 | const stack = new ResourceAwareStack(); 152 | const props = new NRTAProps(); 153 | props.region = process.env.region; 154 | props.accountId = process.env.account; 155 | props.addParameter('table.sessioncontrol','TBL_TEST_SESSIONCONTROL'); 156 | new websocketLayer.WebSocketLayer(stack, 'WebSocketLayer', props); 157 | const template = Template.fromStack(stack); 158 | template.findResources('AWS::Lambda::Function'); 159 | template.findResources('AWS::IAM::Role'); 160 | }); 161 | }); 162 | 163 | describe('IngestionConsumptionLayer validation', () => { 164 | test('IngestionConsumptionLayer validation', () => { 165 | const stack = new ResourceAwareStack(); 166 | const props = new NRTAProps(); 167 | props.region = process.env.region; 168 | props.accountId = process.env.account; 169 | 170 | props.addParameter('kinesisintegration', true); 171 | props.addParameter('firehose',true); 172 | let secl = new securityLayer.SecurityLayer(stack, 'SecurityLayer', props); 173 | props.addParameter('existingbuckets',[]); 174 | let stol = new storageLayer.StorageLayer(stack, 'StorageLayer', props); 175 | props.addParameter('rawbucketarn',stol.getRawDataBucketArn()); 176 | let dbl = new databaseLayer.DatabaseLayer(stack, 'DatabaseLayer', props); 177 | props.addParameter('table.sessionTopX',dbl.getResource('table.sessiontopx')); 178 | props.addParameter('table.session',dbl.getResource('table.session')); 179 | props.addParameter('table.sessionControl',dbl.getResource('table.sessioncontrol')); 180 | let pl = new processingLayer.ProcessingLayer(stack, 'ProcessingLayer', props); 181 | props.addParameter('rawbucketarn', stol.getRawDataBucketArn()); 182 | props.addParameter('userpool',secl.getUserPoolArn()); 183 | props.addParameter('userpoolid', secl.getUserPoolId()); 184 | props.addParameter('table.session',dbl.getResource('table.session')); 185 | props.addParameter('table.sessiontopx',dbl.getResource('table.sessiontopx')); 186 | props.addParameter('lambda.allocate',pl.getAllocateFunctionRef()); 187 | props.addParameter('lambda.deallocate',pl.getDeallocateFunctionArn()); 188 | props.addParameter('lambda.scoreboard',pl.getScoreboardFunctionRef()); 189 | props.addParameter('security.playersrole', secl.getResource('security.playersrole')); 190 | props.addParameter('security.managersrole', secl.getResource('security.managersrole')); 191 | new ingestionConsumptionLayer.IngestionConsumptionLayer(stack, 'IngestionConsumptionLayer',props); 192 | let expectedResources = [ 193 | 'AWS::Kinesis::Stream', 194 | 'AWS::KinesisFirehose::DeliveryStream', 195 | 'AWS::IAM::Role', 196 | 'AWS::ApiGateway::RestApi', 197 | 'AWS::ApiGateway::GatewayResponse', 198 | 'AWS::ApiGateway::Authorizer', 199 | 'AWS::ApiGateway::Model', 200 | 'AWS::ApiGateway::Resource', 201 | 'AWS::ApiGateway::Method', 202 | 'AWS::ApiGateway::Deployment' 203 | ]; 204 | const template = Template.fromStack(stack); 205 | expectedResources.forEach( (resource) => { 206 | template.findResources(resource); 207 | }); 208 | }); 209 | }); 210 | 211 | --------------------------------------------------------------------------------