├── SSR-catalog-example ├── reviews-mfe │ ├── reviews │ │ ├── .npmignore │ │ ├── src │ │ │ ├── Client.js │ │ │ ├── Reviews.js │ │ │ └── ReviewForm.js │ │ ├── .babelrc │ │ ├── webpack.server.js │ │ ├── webpack.client.js │ │ ├── tests │ │ │ └── unit │ │ │ │ └── test-handler.js │ │ ├── package.json │ │ ├── GET_Reviews.js │ │ ├── POST_Reviews.js │ │ └── hydrateDDB.js │ ├── events │ │ └── event.json │ ├── template.yaml │ └── README.md ├── ui-composer │ ├── cdk.context.json │ ├── .dockerignore │ ├── .npmignore │ ├── src │ │ ├── errorBehaviours.js │ │ ├── config.js │ │ ├── templates │ │ │ └── staticPages.js │ │ ├── app.js │ │ └── utils │ │ │ ├── mfe-loader.js │ │ │ └── html-transformer.js │ ├── jest.config.js │ ├── Dockerfile │ ├── template.js │ ├── bin │ │ └── ui-composer.ts │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ ├── cdk.json │ ├── static │ │ └── catalog.template │ └── lib │ │ └── ui-composer-stack.ts ├── catalog-mfe │ ├── functions │ │ └── product │ │ │ ├── .npmignore │ │ │ ├── src │ │ │ ├── Client.js │ │ │ └── Buy.js │ │ │ ├── .babelrc.json │ │ │ ├── webpack.server.js │ │ │ ├── webpack.client.js │ │ │ ├── package.json │ │ │ ├── app.js │ │ │ └── hydrateDDB.js │ ├── statemachine │ │ └── product-orchestrator.asl.json │ └── template.yaml ├── notifications-mfe │ ├── .babelrc │ ├── webpack.config.js │ ├── package.json │ └── src │ │ └── index.js └── dependencies │ ├── nanoevents.js │ ├── htm.min.js │ └── preact.min.js ├── images ├── apigw.png ├── diagram.png ├── s3-static-folder.png └── s3-templates-folder.png ├── CODE_OF_CONDUCT.md ├── LICENSE ├── CONTRIBUTING.md ├── .gitignore ├── README.md └── THIRD-PARTY-LICENSES.txt /SSR-catalog-example/reviews-mfe/reviews/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /images/apigw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ssr-micro-frontends/HEAD/images/apigw.png -------------------------------------------------------------------------------- /images/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ssr-micro-frontends/HEAD/images/diagram.png -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | lib 4 | bin 5 | cdk.out 6 | README.md -------------------------------------------------------------------------------- /images/s3-static-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ssr-micro-frontends/HEAD/images/s3-static-folder.png -------------------------------------------------------------------------------- /images/s3-templates-folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/ssr-micro-frontends/HEAD/images/s3-templates-folder.png -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/errorBehaviours.js: -------------------------------------------------------------------------------- 1 | const ERROR = "error"; 2 | const HIDE = "hide"; 3 | 4 | module.exports = { 5 | ERROR, 6 | HIDE 7 | } -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/src/Client.js: -------------------------------------------------------------------------------- 1 | import {h, hydrate} from 'preact'; 2 | import ReviewForm from './ReviewForm'; 3 | 4 | hydrate(, document.getElementById('formcontainer')); -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 node:19-bullseye-slim 2 | 3 | COPY ./ ./ 4 | 5 | ENV PORT=80 6 | ENV NODE_ENV="production" 7 | 8 | RUN npm ci 9 | 10 | EXPOSE 80 11 | 12 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/src/Client.js: -------------------------------------------------------------------------------- 1 | import {h, hydrate} from 'preact'; 2 | import Buy from './Buy'; 3 | 4 | const bookData = JSON.parse(window.__BOOK__); 5 | 6 | hydrate(, document.getElementById('buycontainer')); -------------------------------------------------------------------------------- /SSR-catalog-example/notifications-mfe/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-transform-react-jsx", { 4 | "pragma": "h", 5 | "pragmaFrag": "Fragment" 6 | }] 7 | ], 8 | "presets": ["@babel/preset-env"] 9 | } -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-transform-react-jsx", { 4 | "pragma": "h", 5 | "pragmaFrag": "Fragment" 6 | }] 7 | ], 8 | "presets": ["@babel/preset-env"] 9 | } -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["@babel/plugin-transform-react-jsx", { 4 | "pragma": "h", 5 | "pragmaFrag": "Fragment" 6 | }] 7 | ], 8 | "presets": ["@babel/preset-env"] 9 | } -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/template.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises'); 2 | const {transformTemplate} = require('./src/utils/html-transformer'); 3 | 4 | const loadHTML = async () => { 5 | try { 6 | const data = await fs.readFile(__dirname + '/static/catalog.template', { encoding: 'utf8' }); 7 | transformTemplate(data); 8 | 9 | } catch (err) { 10 | console.log(err); 11 | } 12 | } 13 | 14 | loadHTML(); 15 | -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/webpack.server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./app.js", 5 | target: "node", 6 | output: { 7 | path: path.resolve("server-build"), 8 | filename: "app.js", 9 | libraryTarget: "commonjs" 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.js$/, 15 | use: "babel-loader", 16 | } 17 | ], 18 | }, 19 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/webpack.server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./GET_Reviews.js", 5 | target: "node", 6 | output: { 7 | path: path.resolve("server-build"), 8 | filename: "GET_Reviews.js", 9 | libraryTarget: "commonjs" 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.js$/, 15 | use: "babel-loader", 16 | } 17 | ], 18 | }, 19 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/bin/ui-composer.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import 'source-map-support/register'; 3 | import { UiComposerStack } from '../lib/ui-composer-stack'; 4 | import { AwsSolutionsChecks } from 'cdk-nag' 5 | import { Aspects, App, Stack } from 'aws-cdk-lib'; 6 | 7 | const app = new App(); 8 | const stack = new UiComposerStack(app, 'UiComposerStack',{ 9 | env: { region: 'eu-west-1' } 10 | }); 11 | 12 | Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })) -------------------------------------------------------------------------------- /SSR-catalog-example/notifications-mfe/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index.js", 5 | output: { 6 | path: path.join(__dirname, "client-build"), 7 | filename: "notifications.js" 8 | }, 9 | externals: { 10 | preact: "preact" 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | loader: "babel-loader", 17 | } 18 | ] 19 | } 20 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/webpack.client.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/Client.js", 5 | output: { 6 | path: path.join(__dirname, "client-build"), 7 | filename: "reviews.js" 8 | }, 9 | externals: { 10 | preact: "preact", 11 | htm: "htm" 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | loader: "babel-loader", 18 | } 19 | ] 20 | } 21 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/webpack.client.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/Client.js", 5 | output: { 6 | path: path.join(__dirname, "client-build"), 7 | filename: "buy.js" 8 | }, 9 | externals: { 10 | preact: "preact", 11 | htm: "htm" 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.js$/, 17 | loader: "babel-loader", 18 | } 19 | ] 20 | } 21 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/dependencies/nanoevents.js: -------------------------------------------------------------------------------- 1 | let createNanoEvents = () => ({ 2 | events: {}, 3 | emit(event, ...args) { 4 | let callbacks = this.events[event] || [] 5 | for (let i = 0, length = callbacks.length; i < length; i++) { 6 | callbacks[i](...args) 7 | } 8 | }, 9 | on(event, cb) { 10 | this.events[event]?.push(cb) || (this.events[event] = [cb]) 11 | return () => { 12 | this.events[event] = this.events[event]?.filter(i => cb !== i) 13 | } 14 | } 15 | }) 16 | 17 | window.emitter = createNanoEvents(); -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | 6 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 7 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 8 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your CDK TypeScript project 2 | 3 | This is a blank project for CDK development with TypeScript. 4 | 5 | The `cdk.json` file tells the CDK Toolkit how to execute your app. 6 | 7 | ## Useful commands 8 | 9 | * `npm run build` compile typescript to js 10 | * `npm run watch` watch for changes and compile 11 | * `npm run test` perform the jest unit tests 12 | * `cdk deploy` deploy this stack to your default AWS account/region 13 | * `cdk diff` compare deployed stack with current state 14 | * `cdk synth` emits the synthesized CloudFormation template 15 | -------------------------------------------------------------------------------- /SSR-catalog-example/notifications-mfe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notifications-mfe", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "build-client": "webpack --mode=production" 7 | }, 8 | "devDependencies": { 9 | "@babel/core": "^7.18.9", 10 | "@babel/plugin-transform-react-jsx": "^7.18.6", 11 | "@babel/preset-env": "^7.18.9", 12 | "babel-loader": "^8.2.5", 13 | "webpack": "^5.74.0", 14 | "webpack-cli": "^4.10.0" 15 | }, 16 | "dependencies": { 17 | "goober": "^2.1.10", 18 | "htm": "^3.1.1", 19 | "preact": "^10.10.0", 20 | "preact-render-to-string": "^5.2.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/config.js: -------------------------------------------------------------------------------- 1 | const { SSMClient, GetParameterCommand } = require("@aws-sdk/client-ssm"); 2 | 3 | const init = async () => { 4 | 5 | const client = new SSMClient({ region: process.env.REGION }); 6 | 7 | const paramCommand = new GetParameterCommand({ 8 | Name: '/ssr-mfe/catalogpage' 9 | }) 10 | 11 | try { 12 | const MFEList = await client.send(paramCommand); 13 | return JSON.parse(MFEList.Parameter.Value); 14 | } catch (err) { 15 | console.log("error to get params from SSM", err) 16 | throw new Error(err); 17 | } 18 | } 19 | 20 | module.exports = init; 21 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/templates/staticPages.js: -------------------------------------------------------------------------------- 1 | const serverError = () => { 2 | return ` 3 | 4 | Error page MFE SSR 5 | 6 | 7 |

Error page, something went wrong...

8 | 9 | ` 10 | } 11 | 12 | const notFound = () => { 13 | return ` 14 | 15 | 404 MFE SSR 16 | 17 | 18 |

404 | Page not found

19 | 20 | ` 21 | 22 | } 23 | 24 | module.exports = { 25 | notFoundPage: notFound, 26 | serverErrorPage: serverError, 27 | } -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/tests/unit/test-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const app = require('../../app.js'); 4 | const chai = require('chai'); 5 | const expect = chai.expect; 6 | var event, context; 7 | 8 | describe('Tests index', function () { 9 | it('verifies successful response', async () => { 10 | const result = await app.lambdaHandler(event, context) 11 | 12 | expect(result).to.be.an('object'); 13 | expect(result.statusCode).to.equal(200); 14 | expect(result.body).to.be.an('string'); 15 | 16 | let response = JSON.parse(result.body); 17 | 18 | expect(response).to.be.an('object'); 19 | expect(response.message).to.be.equal("hello world"); 20 | // expect(response.location).to.be.an("string"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/src/Buy.js: -------------------------------------------------------------------------------- 1 | const { html, Component } = require('htm/preact'); 2 | 3 | class Buy extends Component { 4 | 5 | state = {} 6 | 7 | componentDidMount = () => { 8 | this.setState({book: this.props.data}) 9 | } 10 | 11 | addToCart = (e) =>{ 12 | e.preventDefault(); 13 | 14 | window.emitter.emit("AddToCartEvent", { 15 | id: this.state.book.id, 16 | title: this.state.book.title, 17 | qty: this.state.book.qty, 18 | price: this.state.book.price 19 | }) 20 | } 21 | 22 | render(props) { 23 | return html` 24 |

BUY NOW

25 |
` 26 | } 27 | 28 | } 29 | 30 | module.exports = Buy; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mfe-reviews", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "author": "SAM CLI", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@aws-sdk/client-dynamodb": "^3.137.0", 9 | "@aws-sdk/lib-dynamodb": "^3.137.0", 10 | "axios": "^1.3.4", 11 | "cross-fetch": "^3.1.5", 12 | "htm": "^3.1.1", 13 | "preact": "^10.10.0", 14 | "preact-render-to-string": "^5.2.1", 15 | "uuid": "^8.3.2" 16 | }, 17 | "scripts": { 18 | "build-server": "webpack --config webpack.server.js --mode=production", 19 | "build-client": "webpack --config webpack.client.js --mode=production" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.18.9", 23 | "@babel/plugin-transform-react-jsx": "^7.18.6", 24 | "@babel/preset-env": "^7.18.9", 25 | "babel-loader": "^8.2.5", 26 | "chai": "^4.2.0", 27 | "mocha": "^9.1.4", 28 | "webpack": "^5.74.0", 29 | "webpack-bundle-analyzer": "^4.5.0", 30 | "webpack-cli": "^4.10.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/GET_Reviews.js: -------------------------------------------------------------------------------- 1 | const {render} = require('preact-render-to-string'); 2 | const { html } = require('htm/preact'); 3 | const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); 4 | const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb"); 5 | const Reviews = require("./src/Reviews") 6 | 7 | const REGION = process.env.Region 8 | const ddbClient = new DynamoDBClient({ region: REGION}); 9 | const ddbDocClient = DynamoDBDocument.from(ddbClient); 10 | 11 | const readAllParams = { 12 | TableName: process.env.DatabaseTable 13 | }; 14 | 15 | let reviews, response 16 | 17 | exports.lambdaHandler = async (event, context) => { 18 | try { 19 | reviews = await ddbDocClient.scan(readAllParams) 20 | 21 | const reviewsMFE = render(html`<${Reviews} data="${reviews.Items}" />`) 22 | response = { 23 | statusCode: 200, 24 | headers: {"content-type": "text/html"}, 25 | body: reviewsMFE 26 | } 27 | } catch (error) { 28 | console.error("error creating the DOM"); 29 | return error; 30 | } 31 | 32 | return response 33 | }; 34 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-composer", 3 | "version": "0.1.0", 4 | "bin": { 5 | "ui-composer": "bin/ui-composer.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk", 12 | "start": "node ./src/app.js" 13 | }, 14 | "devDependencies": { 15 | "@types/jest": "^27.5.2", 16 | "@types/node": "10.17.27", 17 | "@types/prettier": "2.6.0", 18 | "aws-cdk": "2.66.1", 19 | "aws-cdk-lib": "2.66.1", 20 | "cdk-nag": "^2.22.19", 21 | "source-map-support": "^0.5.21", 22 | "ts-node": "^10.9.1", 23 | "typescript": "~3.9.7" 24 | }, 25 | "dependencies": { 26 | "@aws-sdk/client-lambda": "^3.280.0", 27 | "@aws-sdk/client-s3": "^3.279.0", 28 | "@aws-sdk/client-sfn": "^3.279.0", 29 | "@aws-sdk/client-ssm": "^3.279.0", 30 | "@fastify/error": "^3.2.0", 31 | "axios": "^0.27.2", 32 | "constructs": "^10.1.262", 33 | "fastify": "^4.13.0", 34 | "htm": "^3.1.1", 35 | "node-html-parser": "^6.1.5", 36 | "preact": "^10.13.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product", 3 | "version": "1.0.0", 4 | "description": "SSR micro-frontends product", 5 | "main": "app.js", 6 | "repository": "TODO", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-dynamodb": "^3.287.0", 11 | "@aws-sdk/lib-dynamodb": "^3.287.0", 12 | "@aws-sdk/util-dynamodb": "^3.142.0", 13 | "axios": "^1.3.4", 14 | "htm": "^3.1.1", 15 | "preact": "^10.10.0", 16 | "preact-render-to-string": "^5.2.1" 17 | }, 18 | "scripts": { 19 | "build-server": "webpack --config webpack.server.js --mode=production", 20 | "build-client": "webpack --config webpack.client.js --mode=production" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.18.9", 24 | "@babel/plugin-transform-react-jsx": "^7.18.6", 25 | "@babel/preset-env": "^7.18.9", 26 | "babel-loader": "^8.2.5", 27 | "webpack": "^5.74.0", 28 | "webpack-bundle-analyzer": "^4.5.0", 29 | "webpack-cli": "^4.10.0", 30 | "webpack-node-externals": "^3.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SSR-catalog-example/notifications-mfe/src/index.js: -------------------------------------------------------------------------------- 1 | import {h, render} from 'preact'; 2 | import { useState } from 'preact/hooks'; 3 | import { styled, setup } from 'goober'; 4 | 5 | setup(h); 6 | 7 | const Background = styled("div")(props => ` 8 | background: orange; 9 | width: 100%; 10 | height: 80px; 11 | color: white; 12 | padding: 5px; 13 | text-align: center; 14 | position: absolute; 15 | display: ${props.visible}; 16 | `) 17 | 18 | export default function Notification() { 19 | const [isVisible, setVisibility] = useState("none") 20 | const [book, setBookData] = useState({}) 21 | 22 | const emitter = window.emitter; 23 | let timeout; 24 | 25 | emitter.on("AddToCartEvent", (book) => { 26 | setBookData(book) 27 | setVisibility("block") 28 | timeout = setTimeout(() => { 29 | clearInterval(timeout) 30 | setVisibility("none"); 31 | setBookData({}) 32 | }, 8000); 33 | }) 34 | 35 | return ( 36 | 37 |

You have just added {book.title} in your cart

38 |

This book costs ${book.price}

39 |
40 | ); 41 | } 42 | 43 | render(, document.getElementById('noitificationscontainer')); -------------------------------------------------------------------------------- /SSR-catalog-example/dependencies/htm.min.js: -------------------------------------------------------------------------------- 1 | !function(){var n=function(t,e,s,u){var r;e[0]=0;for(var h=1;h=5&&((u||!n&&5===s)&&(h.push(s,0,u,e),s=6),n&&(h.push(s,n,0,e),s=6)),u=""},a=0;a"===t?(s=1,u=""):u=t+u[0]:r?t===r?r="":u+=t:'"'===t||"'"===t?r=t:">"===t?(p(),s=1):s&&("="===t?(s=5,e=u,u=""):"/"===t&&(s<5||">"===n[a][o+1])?(p(),3===s&&(h=h[0]),s=h,(h=h[0]).push(2,0,s),s=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),s=2):u+=t),3===s&&"!--"===u&&(s=4,h=h[0])}return p(),h}(e)),s),arguments,[])).length>1?s:s[0]};"undefined"!=typeof module?module.exports=e:self.htm=e}(); -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/ui-composer.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 31 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 32 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 33 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 34 | "@aws-cdk/core:target-partitions": [ 35 | "aws", 36 | "aws-cn" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/POST_Reviews.js: -------------------------------------------------------------------------------- 1 | const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); 2 | const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb"); 3 | 4 | const REGION = process.env.Region 5 | const ddbClient = new DynamoDBClient({ region: REGION }); 6 | const ddbDocClient = DynamoDBDocument.from(ddbClient); 7 | 8 | const TABLE = process.env.DatabaseTable 9 | const headers = { 10 | "Access-Control-Allow-Headers" : "*", 11 | "Access-Control-Allow-Origin": "*", 12 | "Access-Control-Allow-Methods": "*" 13 | } 14 | let response, postReviewParams 15 | 16 | exports.lambdaHandler = async (event, context) => { 17 | const review = JSON.parse(event.body); 18 | 19 | postReviewParams = { 20 | TableName: TABLE, 21 | Item:{ 22 | ID: review.id, 23 | title: review.title, 24 | description: review.description, 25 | rate: review.rate 26 | } 27 | }; 28 | 29 | try { 30 | const res = await ddbDocClient.put(postReviewParams); 31 | console.log(res) 32 | response = { 33 | statusCode: 200, 34 | headers: headers, 35 | body: JSON.stringify(review) 36 | } 37 | } catch (error) { 38 | console.error("error adding reviews in DDB") 39 | response = { 40 | statusCode: 500, 41 | headers: headers, 42 | body: error 43 | } 44 | } 45 | 46 | return response 47 | } 48 | -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/static/catalog.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AWS micro-frontends 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/app.js: -------------------------------------------------------------------------------- 1 | const {render} = require('preact-render-to-string'); 2 | const { html } = require('htm/preact'); 3 | const { unmarshall } = require("@aws-sdk/util-dynamodb"); 4 | const Buy = require('./src/Buy'); 5 | 6 | exports.lambdaHandler = async (event, context) => { 7 | 8 | const book = unmarshall(event); 9 | 10 | const state = `` 15 | 16 | const catalogMFE = render(html`
17 | 18 | 27 |

${book.title}

28 |

Author: ${book.author}

29 | ${book.title} cover 30 |
31 | <${Buy} data=${book}/> 32 |
33 | ${book.description} 34 |

Price: $${book.price}

35 |
`).concat(state).replace(/</g,"<").replace(/>/g,">"); 36 | 37 | return catalogMFE; 38 | 39 | }; -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/app.js: -------------------------------------------------------------------------------- 1 | const fastify = require('fastify')({ logger: true }) 2 | const createError = require('@fastify/error'); 3 | const {transformTemplate} = require('./utils/html-transformer'); 4 | const init = require('./config'); 5 | const { notFoundPage, serverErrorPage } = require('./templates/staticPages'); 6 | const { loadFromS3 } = require("./utils/mfe-loader"); 7 | 8 | const PORT = process.env.PORT || 3000; 9 | let MFElist, catalogTemplate 10 | 11 | fastify.get('/home', async (request, reply) => { 12 | return reply 13 | .code(200) 14 | .header('content-type', 'text/html') 15 | .send("Welcome to the Micro-Frontends in AWS example") 16 | }) 17 | 18 | fastify.setErrorHandler(function (error, request, reply) { 19 | return reply 20 | .code(500) 21 | .header('content-type', 'text/html') 22 | .send(serverErrorPage()) 23 | }) 24 | 25 | fastify.setNotFoundHandler({}, (request, reply) => { 26 | return reply 27 | .code(404) 28 | .header('content-type', 'text/html') 29 | .send(notFoundPage()) 30 | }) 31 | 32 | fastify.get('/health', async(request, reply) => { 33 | return reply 34 | .code(200) 35 | .send({healthy: true}) 36 | }) 37 | 38 | fastify.get('/productdetails', async(request, reply) => { 39 | try{ 40 | const catalogDetailspage = await transformTemplate(catalogTemplate) 41 | return reply 42 | .code(200) 43 | .header('content-type', 'text/html') 44 | .send(catalogDetailspage) 45 | } catch(err){ 46 | request.log.error(createError('500', err)) 47 | throw new Error(err) 48 | } 49 | }) 50 | 51 | const start = async () => { 52 | try { 53 | //load parameters 54 | MFElist = await init(); 55 | //load catalog template 56 | catalogTemplate = await loadFromS3(MFElist.template, MFElist.templatesBucket) 57 | await fastify.listen({ port: PORT, host: '0.0.0.0' }) 58 | } catch (err) { 59 | fastify.log.error(createError('500', err)) 60 | process.exit(1) 61 | } 62 | } 63 | 64 | start(); -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/hydrateDDB.js: -------------------------------------------------------------------------------- 1 | const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); 2 | const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb"); 3 | const axios = require('axios'); 4 | 5 | const REGION = process.env.Region; 6 | const TABLE = process.env.DatabaseTable; 7 | const ddbClient = new DynamoDBClient({ region: REGION}); 8 | const ddbDocClient = DynamoDBDocument.from(ddbClient); 9 | 10 | const buildResponse = (event, context, status) => { 11 | const { RequestId, LogicalResourceId, StackId } = event; 12 | const { logStreamName } = context; 13 | 14 | const response = { 15 | Status: status, 16 | Reason: "Check CloudWatch Log Stream: " + logStreamName, 17 | PhysicalResourceId: logStreamName, 18 | Data: {}, 19 | RequestId: RequestId, 20 | LogicalResourceId: LogicalResourceId, 21 | StackId: StackId 22 | } 23 | 24 | return JSON.stringify(response); 25 | } 26 | 27 | exports.lambdaHandler = async (event, context) => { 28 | 29 | const { RequestType, ResponseURL } = event; 30 | let response; 31 | 32 | if (RequestType.toLowerCase() === 'create') { 33 | postBookParams = { 34 | TableName: TABLE, 35 | Item:{ 36 | ID: "1234", 37 | title: "Wonderful book", 38 | description: "Probably the best book on this topic!", 39 | rate: 5 40 | } 41 | }; 42 | 43 | try { 44 | const res = await ddbDocClient.put(postBookParams); 45 | response = await axios.put(ResponseURL, buildResponse(event, context, "SUCCESS")) 46 | 47 | } catch (error) { 48 | console.error("error adding reviews in DDB from custom resource", error); 49 | response = await axios.put(ResponseURL, buildResponse(event, context, "FAILED")) 50 | } 51 | } else { 52 | 53 | response = await axios.put(ResponseURL, buildResponse(event, context, "SUCCESS")) 54 | 55 | } 56 | 57 | return response 58 | 59 | } -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/events/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": "{\"message\": \"hello world\"}", 3 | "resource": "/{proxy+}", 4 | "path": "/path/to/resource", 5 | "httpMethod": "POST", 6 | "isBase64Encoded": false, 7 | "queryStringParameters": { 8 | "foo": "bar" 9 | }, 10 | "pathParameters": { 11 | "proxy": "/path/to/resource" 12 | }, 13 | "stageVariables": { 14 | "baz": "qux" 15 | }, 16 | "headers": { 17 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 18 | "Accept-Encoding": "gzip, deflate, sdch", 19 | "Accept-Language": "en-US,en;q=0.8", 20 | "Cache-Control": "max-age=0", 21 | "CloudFront-Forwarded-Proto": "https", 22 | "CloudFront-Is-Desktop-Viewer": "true", 23 | "CloudFront-Is-Mobile-Viewer": "false", 24 | "CloudFront-Is-SmartTV-Viewer": "false", 25 | "CloudFront-Is-Tablet-Viewer": "false", 26 | "CloudFront-Viewer-Country": "US", 27 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 28 | "Upgrade-Insecure-Requests": "1", 29 | "User-Agent": "Custom User Agent String", 30 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 31 | "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", 32 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 33 | "X-Forwarded-Port": "443", 34 | "X-Forwarded-Proto": "https" 35 | }, 36 | "requestContext": { 37 | "resourceId": "123456", 38 | "stage": "prod", 39 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 40 | "requestTime": "09/Apr/2015:12:34:56 +0000", 41 | "requestTimeEpoch": 1428582896000, 42 | "identity": { 43 | "cognitoIdentityPoolId": null, 44 | "accountId": null, 45 | "cognitoIdentityId": null, 46 | "caller": null, 47 | "accessKey": null, 48 | "sourceIp": "127.0.0.1", 49 | "cognitoAuthenticationType": null, 50 | "cognitoAuthenticationProvider": null, 51 | "userArn": null, 52 | "userAgent": "Custom User Agent String", 53 | "user": null 54 | }, 55 | "path": "/prod/path/to/resource", 56 | "resourcePath": "/{proxy+}", 57 | "httpMethod": "POST", 58 | "apiId": "1234567890", 59 | "protocol": "HTTP/1.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/src/Reviews.js: -------------------------------------------------------------------------------- 1 | const { html, Component } = require('htm/preact'); 2 | const ReviewForm = require('./ReviewForm'); 3 | 4 | const Stars = (props) => { 5 | const total = 5; 6 | let stars2display = props.rate; 7 | let ui = []; 8 | let i; 9 | 10 | for(i = 0; i < total; i++){ 11 | if(i < stars2display){ 12 | ui.push("fa-solid fa-star") 13 | } else { 14 | ui.push("fa-regular fa-star") 15 | } 16 | } 17 | console.log(ui) 18 | return html`
${ 19 | ui.map(element => { 20 | return html`` 21 | })}
`; 22 | 23 | } 24 | 25 | class Reviews extends Component { 26 | 27 | render(props) { 28 | return html`
29 | 45 |

Reviews

46 |
47 | ${ 48 | props.data.map(item => { 49 | 50 | return html`
51 |

${item.title}

52 |

${item.description}

53 | <${Stars} rate=${item.rate} /> 54 |
` 55 | }) 56 | } 57 |
58 |
59 | <${ReviewForm} /> 60 |
61 |
` 62 | } 63 | 64 | } 65 | 66 | module.exports = Reviews; -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/statemachine/product-orchestrator.asl.json: -------------------------------------------------------------------------------- 1 | { 2 | "Comment": "A state machine that chaches a micro-frontends using step functions", 3 | "StartAt": "CheckDB", 4 | "States": { 5 | "CheckDB":{ 6 | "Type": "Task", 7 | "Resource": "arn:aws:states:::dynamodb:getItem", 8 | "Parameters": { 9 | "TableName": "${TableName}", 10 | "Key": { 11 | "ID": "book-0001" 12 | } 13 | }, 14 | "ResultPath": "$.cache", 15 | "Next": "if no data" 16 | }, 17 | "if no data":{ 18 | "Type": "Choice", 19 | "Default": "Return cached page", 20 | "Choices":[ 21 | { 22 | "Variable": "$.cache.SdkHttpMetadata.HttpHeaders.Content-Length", 23 | "StringEquals": "2", 24 | "Next": "Get Product Data" 25 | } 26 | ] 27 | }, 28 | "Get Product Data":{ 29 | "Type": "Task", 30 | "Resource": "arn:aws:states:::dynamodb:getItem", 31 | "Parameters": { 32 | "TableName": "${ProductsTable}", 33 | "Key": { 34 | "ID": "book-0001" 35 | } 36 | }, 37 | "ResultPath": "$.product", 38 | "OutputPath": "$.product.Item", 39 | "Next": "Product Rendering" 40 | }, 41 | "Product Rendering":{ 42 | "Type": "Task", 43 | "Resource": "${ProductFunctionArn}", 44 | "InputPath": "$", 45 | "ResultPath": "$.cache", 46 | "Next": "Cache MFE" 47 | }, 48 | "Cache MFE":{ 49 | "Type": "Task", 50 | "Resource": "arn:aws:states:::dynamodb:putItem", 51 | "InputPath": "$.cache", 52 | "Parameters": { 53 | "TableName": "${TableName}", 54 | "Item": { 55 | "ID": "book-0001", 56 | "cached.$": "$" 57 | } 58 | }, 59 | "ResultPath": "$.db.result", 60 | "OutputPath": "$.cache", 61 | "End": true 62 | }, 63 | "Return cached page": { 64 | "Type": "Succeed", 65 | "OutputPath": "$.cache.Item.cached.S" 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | reviews-mfe 5 | 6 | Sample SAM Template for reviews-mfe 7 | 8 | Globals: 9 | Function: 10 | Timeout: 30 11 | Tracing: Active 12 | Runtime: nodejs16.x 13 | MemorySize: 256 14 | Architectures: 15 | - arm64 16 | Environment: 17 | Variables: 18 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1 19 | Region: !Ref AWS::Region 20 | DatabaseTable: !Ref ReviewsTable 21 | Api: 22 | TracingEnabled: True 23 | Cors: 24 | AllowMethods: "'GET,POST,OPTIONS'" 25 | AllowHeaders: "'*'" 26 | AllowOrigin: "'*'" 27 | EndpointConfiguration: 28 | Type: REGIONAL 29 | 30 | Resources: 31 | ReviewsTable: 32 | Type: AWS::DynamoDB::Table 33 | Properties: 34 | AttributeDefinitions: 35 | - AttributeName: ID 36 | AttributeType: S 37 | KeySchema: 38 | - AttributeName: ID 39 | KeyType: HASH 40 | BillingMode: PAY_PER_REQUEST 41 | SubmitReviewsFunction: 42 | Type: AWS::Serverless::Function 43 | Properties: 44 | CodeUri: reviews/ 45 | Handler: POST_Reviews.lambdaHandler 46 | Policies: 47 | - DynamoDBCrudPolicy: 48 | TableName: !Ref ReviewsTable 49 | Events: 50 | ReviewsGateway: 51 | Type: Api 52 | Properties: 53 | Path: /review 54 | Method: post 55 | ReviewsMFEFunction: 56 | Type: AWS::Serverless::Function 57 | Properties: 58 | CodeUri: reviews/server-build/ 59 | Handler: GET_Reviews.lambdaHandler 60 | Policies: 61 | - DynamoDBReadPolicy: 62 | TableName: !Ref ReviewsTable 63 | ServiceDiscoveryID: 64 | Type: AWS::SSM::Parameter 65 | Properties: 66 | Name: /ssr-mfe/reviewARN 67 | Type: String 68 | Value: !Ref ReviewsMFEFunction 69 | HydrateDDBFunction: 70 | Type: AWS::Serverless::Function 71 | Properties: 72 | CodeUri: reviews 73 | Handler: hydrateDDB.lambdaHandler 74 | Policies: 75 | - DynamoDBCrudPolicy: 76 | TableName: !Ref ReviewsTable 77 | 78 | DeploymentCustomResource: 79 | Type: Custom::AppConfiguration 80 | Properties: 81 | ServiceToken: !GetAtt HydrateDDBFunction.Arn 82 | 83 | Outputs: 84 | ReviewsGatewayApi: 85 | Description: "API Gateway endpoint URL for Prod stage for reviews function" 86 | Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: SSR-micro-frontends 4 | 5 | Globals: 6 | Function: 7 | Runtime: nodejs16.x 8 | MemorySize: 256 9 | Timeout: 15 10 | Tracing: Active 11 | Architectures: 12 | - arm64 13 | 14 | Parameters: 15 | DDBCachedTable: 16 | Type: String 17 | Default: CachedProductsTable 18 | DDBProductsTable: 19 | Type: String 20 | Default: ProductsTable 21 | 22 | Resources: 23 | 24 | DynamoTable: 25 | Type: AWS::DynamoDB::Table 26 | Properties: 27 | TableName: !Ref DDBCachedTable 28 | AttributeDefinitions: 29 | - AttributeName: ID 30 | AttributeType: S 31 | KeySchema: 32 | - AttributeName: ID 33 | KeyType: HASH 34 | BillingMode: PAY_PER_REQUEST 35 | 36 | ProductsTable: 37 | Type: AWS::DynamoDB::Table 38 | Properties: 39 | TableName: !Ref DDBProductsTable 40 | AttributeDefinitions: 41 | - AttributeName: ID 42 | AttributeType: S 43 | KeySchema: 44 | - AttributeName: ID 45 | KeyType: HASH 46 | BillingMode: PAY_PER_REQUEST 47 | 48 | ProductStateMachine: 49 | Type: AWS::Serverless::StateMachine 50 | Properties: 51 | Type: EXPRESS 52 | Tracing: 53 | Enabled: true 54 | DefinitionUri: statemachine/product-orchestrator.asl.json 55 | DefinitionSubstitutions: 56 | ProductFunctionArn: !GetAtt ProductFunction.Arn 57 | TableName: !Ref DynamoTable 58 | ProductsTable: !Ref ProductsTable 59 | Policies: 60 | - LambdaInvokePolicy: 61 | FunctionName: !Ref ProductFunction 62 | - DynamoDBCrudPolicy: 63 | TableName: !Ref DynamoTable 64 | - DynamoDBReadPolicy: 65 | TableName: !Ref ProductsTable 66 | 67 | ProductFunction: 68 | Type: AWS::Serverless::Function 69 | Properties: 70 | AutoPublishAlias: prod 71 | CodeUri: functions/product 72 | Handler: server-build/app.lambdaHandler 73 | 74 | ServiceDiscoveryID: 75 | Type: AWS::SSM::Parameter 76 | Properties: 77 | Name: /ssr-mfe/catalogARN 78 | Type: String 79 | Value: !Ref ProductStateMachine 80 | 81 | HydrateDDBFunction: 82 | Type: AWS::Serverless::Function 83 | Properties: 84 | Environment: 85 | Variables: 86 | Region: !Ref AWS::Region 87 | DatabaseTable: !Ref ProductsTable 88 | AutoPublishAlias: prod 89 | CodeUri: functions/product 90 | Handler: hydrateDDB.lambdaHandler 91 | Policies: 92 | - DynamoDBCrudPolicy: 93 | TableName: !Ref ProductsTable 94 | 95 | DeploymentCustomResource: 96 | Type: Custom::AppConfiguration 97 | Properties: 98 | ServiceToken: !GetAtt HydrateDDBFunction.Arn -------------------------------------------------------------------------------- /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 | 61 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/reviews/src/ReviewForm.js: -------------------------------------------------------------------------------- 1 | const { html, Component } = require('htm/preact'); 2 | const fetch = require('cross-fetch'); 3 | const {v4: uuidv4} = require("uuid"); 4 | 5 | const REVIEW_URL = "https://xxxxxxxx.execute-api.REGION.amazonaws.com/Prod/review"; // set the endpoint for posting a new review in the interface 6 | 7 | class Form extends Component { 8 | state = {} 9 | 10 | componentDidMount = () => { 11 | this.setState({id: uuidv4()}) 12 | } 13 | 14 | submitReview = async (e) => { 15 | e.preventDefault(); 16 | const form = this.state; 17 | const body = { 18 | id: form.id, 19 | title: form.title, 20 | description: form.review, 21 | rate: form.rate 22 | } 23 | 24 | const res = await fetch( 25 | REVIEW_URL, 26 | { 27 | method: "POST", 28 | body: JSON.stringify(body), 29 | headers: { 30 | "Content-Type": "application/json" 31 | } 32 | } 33 | ); 34 | 35 | if (res.status >= 400) { 36 | throw new Error("Bad response from server"); 37 | } 38 | 39 | const data = await res.json(); 40 | 41 | location.reload(); 42 | } 43 | 44 | cancelReview = (e) => { 45 | e.preventDefault(); 46 | this.setState({}); 47 | } 48 | 49 | updateState = (e) => { 50 | this.setState({ [e.target.id]: e.target.value }); 51 | } 52 | 53 | render(){ 54 | return html`
55 | 64 |
65 |
66 | 67 |
68 |
69 | 70 |
71 |
72 | 79 |

80 | 81 | 82 |
83 |
` 84 | } 85 | } 86 | 87 | class ReviewForm extends Component { 88 | 89 | state = { showForm: false }; 90 | 91 | onClick = () => { 92 | this.setState({ showForm: true }); 93 | } 94 | 95 | render(props){ 96 | return html`
97 | 98 | ${this.state.showForm ? html`<${Form} />`: ""} 99 | 100 |
` 101 | } 102 | } 103 | 104 | module.exports = ReviewForm; -------------------------------------------------------------------------------- /SSR-catalog-example/catalog-mfe/functions/product/hydrateDDB.js: -------------------------------------------------------------------------------- 1 | const { DynamoDBClient } = require("@aws-sdk/client-dynamodb"); 2 | const { DynamoDBDocument } = require("@aws-sdk/lib-dynamodb"); 3 | const axios = require('axios'); 4 | 5 | const REGION = process.env.Region; 6 | const TABLE = process.env.DatabaseTable; 7 | const ddbClient = new DynamoDBClient({ region: REGION}); 8 | const ddbDocClient = DynamoDBDocument.from(ddbClient); 9 | 10 | const buildResponse = (event, context, status) => { 11 | const { RequestId, LogicalResourceId, StackId } = event; 12 | const { logStreamName } = context; 13 | 14 | const response = { 15 | Status: status, 16 | Reason: "Check CloudWatch Log Stream: " + logStreamName, 17 | PhysicalResourceId: logStreamName, 18 | Data: {}, 19 | RequestId: RequestId, 20 | LogicalResourceId: LogicalResourceId, 21 | StackId: StackId 22 | } 23 | 24 | return JSON.stringify(response); 25 | } 26 | 27 | exports.lambdaHandler = async (event, context) => { 28 | 29 | const { RequestType, ResponseURL } = event; 30 | let response; 31 | 32 | if (RequestType.toLowerCase() === 'create') { 33 | 34 | postBookParams = { 35 | TableName: TABLE, 36 | Item:{ 37 | ID: "book-0001", 38 | title: "Building Micro-Frontends", 39 | price: "39.99", 40 | author: "Luca Mezzalira", 41 | cover: "./static/book.jpg", 42 | description: `

What's the answer to today's increasingly complex web applications? Micro-frontends. Inspired by the microservices model, this approach lets you break interfaces into separate features managed by different teams of developers. With this practical guide, Luca Mezzalira shows software architects, tech leads, and software developers how to build and deliver artifacts atomically rather than use a big bang deployment. 43 | 44 | You'll learn how micro-frontends enable your team to choose any library or framework. This gives your organization technical flexibility and allows you to hire and retain a broad spectrum of talent. Micro-frontends also support distributed or colocated teams more efficiently. Pick up this book and learn how to get started with this technological breakthrough right away.

45 |
    46 |
  • Explore available frontend development architectures
  • 47 |
  • Learn how microservice principles apply to frontend development
  • 48 |
  • Understand the four pillars for creating a successful micro-frontend architecture
  • 49 |
  • Examine the benefits and pitfalls of existing micro-frontend architectures
  • 50 |
  • Learn principles and best practices for creating successful automation strategies
  • 51 |
  • Discover patterns for integrating micro-frontend architectures using microservices or a monolith API layer
  • 52 |
` 53 | } 54 | }; 55 | 56 | try { 57 | const res = await ddbDocClient.put(postBookParams); 58 | response = await axios.put(ResponseURL, buildResponse(event, context, "SUCCESS")) 59 | 60 | } catch (error) { 61 | console.error("error adding product details in DDB from custom resource", error); 62 | response = await axios.put(ResponseURL, buildResponse(event, context, "FAILED")) 63 | } 64 | } else { 65 | 66 | response = await axios.put(ResponseURL, buildResponse(event, context, "SUCCESS")) 67 | 68 | } 69 | 70 | return response 71 | 72 | } -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/utils/mfe-loader.js: -------------------------------------------------------------------------------- 1 | const { LambdaClient, InvokeCommand, InvocationType } = require("@aws-sdk/client-lambda"); 2 | const { SFNClient, StartSyncExecutionCommand } = require("@aws-sdk/client-sfn"); 3 | const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3"); 4 | const axios = require('axios'); 5 | 6 | // const localdev = { 7 | // credentials: { 8 | // accessKeyId: "xxx", 9 | // secretAccessKey: "xxx" 10 | // }, 11 | // region: "eu-west-1" 12 | // } 13 | 14 | const loadFromURL = async (url, payload = {}) => { 15 | let res 16 | 17 | try { 18 | res = await axios.get(url, payload) 19 | } catch (error) { 20 | throw new Error(`Error loading ${url} ! details ${error}`) 21 | } 22 | 23 | return { 24 | statusCode: res.status, 25 | body: res.data 26 | } 27 | } 28 | const loadFromStepFunction = async (arn, payload = {}) => { 29 | try { 30 | // const client = new SFNClient(localdev); 31 | const client = new SFNClient(); 32 | const command = new StartSyncExecutionCommand({ 33 | stateMachineArn: arn, 34 | index: JSON.stringify(payload) 35 | }); 36 | 37 | const sfnResponse = await client.send(command); 38 | 39 | if(sfnResponse.status === "SUCCEEDED"){ 40 | const data = { 41 | statusCode: 200, 42 | body: JSON.parse(sfnResponse.output) 43 | } 44 | return data 45 | } else { 46 | throw new Error(`${arn} called but error ${sfnResponse.status}`); 47 | } 48 | 49 | } catch (error) { 50 | throw new Error(`Error for ${arn} step function called! details ${error}`); 51 | } 52 | } 53 | const loadFromLambda = async (arn, payload = {}) => { 54 | try { 55 | // const lambdaClient = new LambdaClient(localdev); 56 | const lambdaClient = new LambdaClient(); 57 | const invokeCommand = new InvokeCommand({ 58 | FunctionName: arn, 59 | InvocationType: InvocationType.RequestResponse, 60 | Payload: JSON.stringify(payload), 61 | }); 62 | 63 | const response = await lambdaClient.send(invokeCommand); 64 | 65 | if(response.StatusCode < 400){ 66 | const payload = Buffer.from(response.Payload).toString(); 67 | const data = { 68 | statusCode: response.StatusCode, 69 | body: JSON.parse(payload).body 70 | } 71 | return data; 72 | } else { 73 | throw new Error(`${arn} called but error ${response.StatusCode}`); 74 | } 75 | } catch (error) { 76 | throw new Error(`Error for ${arn} function called! details ${error}`); 77 | } 78 | } 79 | 80 | const loadFromS3 = async (key, bucket) => { 81 | 82 | // const client = new S3Client(localdev); 83 | const client = new S3Client(); 84 | 85 | try { 86 | const command = new GetObjectCommand({ 87 | Key: key, 88 | Bucket: bucket 89 | }); 90 | 91 | const response = await client.send(command); 92 | const stream = response.Body; 93 | 94 | return new Promise((resolve, reject) => { 95 | const chunks = [] 96 | stream.on('data', chunk => chunks.push(chunk)) 97 | stream.once('end', () => resolve(Buffer.concat(chunks).toString())) 98 | stream.once('error', reject) 99 | }) 100 | } catch (error) { 101 | console.error(error) 102 | } 103 | 104 | } 105 | 106 | module.exports = { 107 | loadFromURL, 108 | loadFromStepFunction, 109 | loadFromLambda, 110 | loadFromS3 111 | } -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/src/utils/html-transformer.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('node-html-parser'); 2 | const { SSMClient, GetParametersCommand } = require("@aws-sdk/client-ssm"); 3 | const behaviour = require('../errorBehaviours'); 4 | const {loadFromURL, loadFromStepFunction, loadFromLambda} = require("./mfe-loader"); 5 | 6 | const APP = "ssr-mfe"; 7 | const ARN = "ARN"; 8 | 9 | const STEP_FUNCTION = "stepfunction"; 10 | const LAMBDA = "lambda"; 11 | const URL = "url"; 12 | 13 | const getMfeVO = (id, errorBehaviour, loader) => { 14 | 15 | if(!id || !loader) 16 | throw new Error("MFE tag not configured correctly, ID and loader must be defined") 17 | 18 | if(!errorBehaviour) 19 | errorBehaviour = behaviour.HIDE; 20 | 21 | const ssmKey = `/${APP}/${id}${ARN}`; 22 | let chosenLoader; 23 | 24 | switch(loader.toLowerCase()){ 25 | case STEP_FUNCTION: 26 | chosenLoader = loadFromStepFunction 27 | break; 28 | case LAMBDA: 29 | chosenLoader = loadFromLambda 30 | break; 31 | case URL: 32 | chosenLoader = loadFromURL 33 | break; 34 | default: 35 | throw new Error("loader not recognised") 36 | } 37 | 38 | return { 39 | id:id, 40 | ssmKey: ssmKey, 41 | errorBehaviour: errorBehaviour, 42 | loader: chosenLoader, 43 | service: "", 44 | } 45 | } 46 | 47 | const getMfeElements = (root) => { 48 | return root.querySelectorAll("micro-frontend") || []; 49 | } 50 | 51 | const analyseMFEresponse = (response, behaviour) => { 52 | let html = ""; 53 | 54 | if(response.status !== "fulfilled"){ 55 | switch (behaviour) { 56 | case behaviour.ERROR: 57 | throw new Error() 58 | case behaviour.HIDE: 59 | default: 60 | html = "" 61 | break; 62 | } 63 | } else { 64 | html = response.value.body 65 | } 66 | 67 | return html; 68 | } 69 | 70 | const getServices = async (mfeList) => { 71 | const list = mfeList 72 | 73 | const ssmKeys = list.map(element => element.ssmKey) 74 | 75 | const client = new SSMClient({ region: process.env.REGION }); 76 | 77 | const paramCommand = new GetParametersCommand({ 78 | Names: ssmKeys 79 | }) 80 | 81 | try { 82 | const servicesList = await client.send(paramCommand); 83 | servicesList.Parameters.forEach((element, index) => list[index].service = element.Value); 84 | return list; 85 | } catch (err) { 86 | console.log("error to get params from SSM", err) 87 | throw new Error(err); 88 | } 89 | 90 | } 91 | 92 | const transformTemplate = async (html) => { 93 | try{ 94 | const root = parse(html); 95 | const mfeElements = getMfeElements(root); 96 | let mfeList = []; 97 | 98 | // generate VOs for MFEs available in a template 99 | 100 | if(mfeElements.length > 0) { 101 | mfeElements.forEach(element => { 102 | mfeList.push( 103 | getMfeVO( 104 | element.getAttribute("id"), 105 | element.getAttribute("errorbehaviour"), 106 | element.getAttribute("loader") 107 | ) 108 | ) 109 | }); 110 | } else { 111 | return "" 112 | } 113 | 114 | // retrieve service to call in Parameter Store 115 | 116 | mfeList = await getServices(mfeList); 117 | 118 | // retrieve HTML fragments 119 | 120 | const fragmentsResponses = await Promise.allSettled(mfeList.map(element => element.loader(element.service))) 121 | 122 | // analyse responses 123 | 124 | const mfeToRender = fragmentsResponses.map((element, index) => analyseMFEresponse(element, mfeList[index].errorBehaviour)) 125 | 126 | // transclusion in the template 127 | 128 | mfeElements.forEach((element, index) => element.replaceWith(mfeToRender[index])) 129 | 130 | return root.toString(); 131 | 132 | } catch(error) { 133 | console.error("page generation failed", error) 134 | } 135 | 136 | } 137 | 138 | module.exports = { 139 | transformTemplate 140 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | 10 | ### build ### 11 | client-build 12 | server-build 13 | 14 | ### Linux ### 15 | *~ 16 | 17 | # temporary files which can be created if a process still has a handle open of a deleted file 18 | .fuse_hidden* 19 | 20 | # KDE directory preferences 21 | .directory 22 | 23 | # Linux trash folder which might appear on any partition or disk 24 | .Trash-* 25 | 26 | # .nfs files are created when an open file is removed but is still being accessed 27 | .nfs* 28 | 29 | ### Node ### 30 | # Logs 31 | logs 32 | *.log 33 | npm-debug.log* 34 | yarn-debug.log* 35 | yarn-error.log* 36 | lerna-debug.log* 37 | 38 | # Diagnostic reports (https://nodejs.org/api/report.html) 39 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 40 | 41 | # Runtime data 42 | pids 43 | *.pid 44 | *.seed 45 | *.pid.lock 46 | 47 | # Directory for instrumented libs generated by jscoverage/JSCover 48 | lib-cov 49 | 50 | # Coverage directory used by tools like istanbul 51 | coverage 52 | *.lcov 53 | 54 | # nyc test coverage 55 | .nyc_output 56 | 57 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 58 | .grunt 59 | 60 | # Bower dependency directory (https://bower.io/) 61 | bower_components 62 | 63 | # node-waf configuration 64 | .lock-wscript 65 | 66 | # Compiled binary addons (https://nodejs.org/api/addons.html) 67 | build/Release 68 | 69 | # Dependency directories 70 | node_modules/ 71 | jspm_packages/ 72 | 73 | # TypeScript v1 declaration files 74 | typings/ 75 | 76 | # TypeScript cache 77 | *.tsbuildinfo 78 | 79 | # Optional npm cache directory 80 | .npm 81 | 82 | # Optional eslint cache 83 | .eslintcache 84 | 85 | # Optional stylelint cache 86 | .stylelintcache 87 | 88 | # Microbundle cache 89 | .rpt2_cache/ 90 | .rts2_cache_cjs/ 91 | .rts2_cache_es/ 92 | .rts2_cache_umd/ 93 | 94 | # Optional REPL history 95 | .node_repl_history 96 | 97 | # Output of 'npm pack' 98 | *.tgz 99 | 100 | # Yarn Integrity file 101 | .yarn-integrity 102 | 103 | # dotenv environment variables file 104 | .env 105 | .env.test 106 | .env*.local 107 | 108 | # parcel-bundler cache (https://parceljs.org/) 109 | .cache 110 | .parcel-cache 111 | 112 | # Next.js build output 113 | .next 114 | 115 | # Nuxt.js build / generate output 116 | .nuxt 117 | dist 118 | 119 | # Storybook build outputs 120 | .out 121 | .storybook-out 122 | storybook-static 123 | 124 | # rollup.js default build output 125 | dist/ 126 | 127 | # Gatsby files 128 | .cache/ 129 | # Comment in the public line in if your project uses Gatsby and not Next.js 130 | # https://nextjs.org/blog/next-9-1#public-directory-support 131 | # public 132 | 133 | # vuepress build output 134 | .vuepress/dist 135 | 136 | # Serverless directories 137 | .serverless/ 138 | 139 | # FuseBox cache 140 | .fusebox/ 141 | 142 | # DynamoDB Local files 143 | .dynamodb/ 144 | 145 | # TernJS port file 146 | .tern-port 147 | 148 | # Stores VSCode versions used for testing VSCode extensions 149 | .vscode-test 150 | 151 | # Temporary folders 152 | tmp/ 153 | temp/ 154 | 155 | ### OSX ### 156 | # General 157 | .DS_Store 158 | .AppleDouble 159 | .LSOverride 160 | 161 | # Icon must end with two \r 162 | Icon 163 | 164 | 165 | # Thumbnails 166 | ._* 167 | 168 | # Files that might appear in the root of a volume 169 | .DocumentRevisions-V100 170 | .fseventsd 171 | .Spotlight-V100 172 | .TemporaryItems 173 | .Trashes 174 | .VolumeIcon.icns 175 | .com.apple.timemachine.donotpresent 176 | 177 | # Directories potentially created on remote AFP share 178 | .AppleDB 179 | .AppleDesktop 180 | Network Trash Folder 181 | Temporary Items 182 | .apdisk 183 | 184 | ### SAM ### 185 | # Ignore build directories for the AWS Serverless Application Model (SAM) 186 | # Info: https://aws.amazon.com/serverless/sam/ 187 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 188 | 189 | **/.aws-sam 190 | **/*.toml 191 | *.toml 192 | 193 | ### Windows ### 194 | # Windows thumbnail cache files 195 | Thumbs.db 196 | Thumbs.db:encryptable 197 | ehthumbs.db 198 | ehthumbs_vista.db 199 | 200 | # Dump file 201 | *.stackdump 202 | 203 | # Folder config file 204 | [Dd]esktop.ini 205 | 206 | # Recycle Bin used on file shares 207 | $RECYCLE.BIN/ 208 | 209 | # Windows Installer files 210 | *.cab 211 | *.msi 212 | *.msix 213 | *.msm 214 | *.msp 215 | 216 | # Windows shortcuts 217 | *.lnk 218 | 219 | # Other IDE settings 220 | .idea/ 221 | -------------------------------------------------------------------------------- /SSR-catalog-example/reviews-mfe/README.md: -------------------------------------------------------------------------------- 1 | # reviews-mfe 2 | 3 | This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders. 4 | 5 | - hello-world - Code for the application's Lambda function. 6 | - events - Invocation events that you can use to invoke the function. 7 | - hello-world/tests - Unit tests for the application code. 8 | - template.yaml - A template that defines the application's AWS resources. 9 | 10 | The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. 11 | 12 | If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. 13 | The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. 14 | 15 | * [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 16 | * [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 17 | * [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 18 | * [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 19 | * [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 20 | * [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 21 | * [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 22 | * [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 23 | * [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) 24 | * [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) 25 | * [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) 26 | 27 | ## Deploy the sample application 28 | 29 | The Serverless Application Model Command Line Interface (SAM CLI) is an extension of the AWS CLI that adds functionality for building and testing Lambda applications. It uses Docker to run your functions in an Amazon Linux environment that matches Lambda. It can also emulate your application's build environment and API. 30 | 31 | To use the SAM CLI, you need the following tools. 32 | 33 | * SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) 34 | * Node.js - [Install Node.js 16](https://nodejs.org/en/), including the NPM package management tool. 35 | * Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) 36 | 37 | To build and deploy your application for the first time, run the following in your shell: 38 | 39 | ```bash 40 | sam build 41 | sam deploy --guided 42 | ``` 43 | 44 | The first command will build the source of your application. The second command will package and deploy your application to AWS, with a series of prompts: 45 | 46 | * **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. 47 | * **AWS Region**: The AWS region you want to deploy your app to. 48 | * **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. 49 | * **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. 50 | * **Save arguments to samconfig.toml**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. 51 | 52 | You can find your API Gateway Endpoint URL in the output values displayed after deployment. 53 | 54 | ## Use the SAM CLI to build and test locally 55 | 56 | Build your application with the `sam build` command. 57 | 58 | ```bash 59 | reviews-mfe$ sam build 60 | ``` 61 | 62 | The SAM CLI installs dependencies defined in `hello-world/package.json`, creates a deployment package, and saves it in the `.aws-sam/build` folder. 63 | 64 | Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. 65 | 66 | Run functions locally and invoke them with the `sam local invoke` command. 67 | 68 | ```bash 69 | reviews-mfe$ sam local invoke HelloWorldFunction --event events/event.json 70 | ``` 71 | 72 | The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. 73 | 74 | ```bash 75 | reviews-mfe$ sam local start-api 76 | reviews-mfe$ curl http://localhost:3000/ 77 | ``` 78 | 79 | The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. 80 | 81 | ```yaml 82 | Events: 83 | HelloWorld: 84 | Type: Api 85 | Properties: 86 | Path: /hello 87 | Method: get 88 | ``` 89 | 90 | ## Add a resource to your application 91 | The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. 92 | 93 | ## Fetch, tail, and filter Lambda function logs 94 | 95 | To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. 96 | 97 | `NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. 98 | 99 | ```bash 100 | reviews-mfe$ sam logs -n HelloWorldFunction --stack-name reviews-mfe --tail 101 | ``` 102 | 103 | You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). 104 | 105 | ## Unit tests 106 | 107 | Tests are defined in the `hello-world/tests` folder in this project. Use NPM to install the [Mocha test framework](https://mochajs.org/) and run unit tests. 108 | 109 | ```bash 110 | reviews-mfe$ cd hello-world 111 | hello-world$ npm install 112 | hello-world$ npm run test 113 | ``` 114 | 115 | ## Cleanup 116 | 117 | To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: 118 | 119 | ```bash 120 | aws cloudformation delete-stack --stack-name reviews-mfe 121 | ``` 122 | 123 | ## Resources 124 | 125 | See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. 126 | 127 | Next, you can use AWS Serverless Application Repository to deploy ready to use Apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/) 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Micro-Frontends in AWS 2 | 3 | ## Context 4 | 5 | Micro-Frontends are the technical representation of a business subdomain, they allow independent implementations with the same or different technology, they should minimize the code shared with other subdomains and they are own by a single team. 6 | 7 | These characteristics might seem familiar if you have built distibuted systems in the past. Micro-Frontends are the answer when you need to scale your organizations having multiple teams working together in the same project. 8 | 9 | In this repository we collect examples to implement Micro-Frontends in AWS, leveraging several AWS services that represents the building blocks for stitching together distributed architecterues not only for the backend but now for the frontend too. 10 | 11 | ## Server-side rendering Micro-Frontends 12 | 13 | In this repository we have created a basic example that leverages the building blocks to create a server-side rendering (SSR) micro-frontends implementation. 14 | 15 | The architecture characteristics we focus in these projects are: 16 | 17 | - being framework agnostic 18 | - using standards for communicating between micro-frontends and the UI composer using [HTML-over-the-wire](https://alistapart.com/article/the-future-of-web-software-is-html-over-websockets/) 19 | - using the best practices for SSR workloads such as [progressive hydration](https://www.patterns.dev/posts/progressive-hydration/) and [streaming to the browser](https://www.patterns.dev/posts/ssr/) 20 | - allowing teams to operate independently with little coordination for composing their micro-frontends inside a view 21 | - implementing best practices once a micro-frontend is hydrated using a pub/sub system for communication inside the browser 22 | - having the possibility to choose between client-side rendering and server-side rendering based on the needs 23 | 24 | The architecture in this example is represented in the following diagram: 25 | 26 | ![SSR micro-frontends](./images/diagram.png) 27 | 28 | It's important to highlight that this project is managed by several teams in an organization. Hence why the deployment is not as simple as a monolithic project but covers already the idea of a centralized team responsible for the UI-composer and several teams building individual micro-frontends. 29 | 30 | You can read an extensive description of this approach in this blog series: 31 | 32 | - [server-side rendering micro-frontends - the architecture](https://aws.amazon.com/blogs/compute/server-side-rendering-micro-frontends-the-architecture/) 33 | - [server-side rendering micro-frontends – UI composer and service discovery](https://aws.amazon.com/blogs/compute/server-side-rendering-micro-frontends-ui-composer-and-service-discovery/) 34 | 35 | More posts are written as we speak so keep an eye on the [AWS Compute Blog](https://aws.amazon.com/blogs/compute/). 36 | 37 | ### Pre-requisites 38 | 39 | - [Node.js](https://nodejs.org/en/download/) 40 | - [Docker Desktop](https://www.docker.com/products/docker-desktop/) 41 | - [AWS CLI](https://aws.amazon.com/cli/) 42 | - [AWS SAM](https://aws.amazon.com/serverless/sam/) 43 | - [AWS CDK](https://aws.amazon.com/cdk/) 44 | 45 | ### How to deploy this project 46 | 47 | We assume you have installed and configured the AWS CLI, if not please follow these [instructions](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions) 48 | 49 | As seen in the diagram above, the system is composed by different parts. 50 | The first thing to do is deploying the UI composer alongside the CloudFront distribution and related S3 bucket. 51 | 52 | In the ```SSR-catalog-example/ui-composer``` we have the implementation and configuration of these parts of the system. 53 | For this project we are using AWS CDK, so follow the [instructions](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) to install in your machine and then run the commands inside the ```SSR-catalog-example/ui-composer``` folder: 54 | 55 | ```shell 56 | npm install 57 | # this command is used just the first time. Use the cdk bootstrap command to bootstrap one or more AWS environments 58 | cdk bootstrap 59 | # this command allows you to deploy the UI-composer in an AWS account 60 | cdk deploy 61 | ``` 62 | 63 | At the end of the deployment we should have in our AWS account the following elements: 64 | 65 | - a CloudFront Distribution 66 | - an S3 bucket 67 | - an Application Load Balancer 68 | - a Fargate cluster running our application server 69 | - a VPC configured with access logs 70 | - IAM policies attached to the Fargate cluster 71 | - A parameter in parameter store with the information to retrieve the HTML templates used in the application. 72 | 73 | Now, we can deploy the micro-frontends: two of them are using the server-side rendering (```reviews-mfe``` and ```catalog-mfe```) approach and another one is leveraging client-side rendering (```notifications-mfe```). 74 | 75 | ```reviews-mfe``` and ```catalog-mfe``` are similar in terms of deployment approach. 76 | First install the Node.js dependecies running the command ```npm install``` in both projects. 77 | 78 | These server-side rendering micro-frontends use AWS SAM as IaC tool, so first you need to follow the instructions to [install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) and then run the following commands inside the folder of both projects: 79 | 80 | ```shell 81 | npm install 82 | # this command minimize the code of the lambda function(s) 83 | npm run build-server 84 | # this command build the project using SAM 85 | sam build 86 | # this command triggers a wizard for deploying a micro-frontend in the AWS account 87 | sam deploy --guided 88 | ``` 89 | 90 | All the micro-frontends in this project requires to generate a JavaScript file used for hydrating the code inside a browser to attach listeners to the user interface and run business logic after the page is rendered. 91 | For generating these static JavaScript files you have to run the following command for ```reviews-mfe```, ```catalog-mfe``` and ```notifications-mfe```: 92 | 93 | ```shell 94 | npm run build-client 95 | ``` 96 | 97 | This command will generate static files in ```client-build``` folder in every project. 98 | All these JavaScript files are now ready to be uploaded in the S3 bucket created at the beginning inside the static folder. 99 | 100 | In this project, we have also a folder dependencies that collects all the common depdencies used by different micro-frontends such as [Preact](https://preactjs.com/) as UI library or [nanoevents](https://www.npmjs.com/package/nanoevents) as event bus used for allowing the micro-frontends communications. 101 | Deploy all the files inside the ```static``` folder in the S3 bucket alongside the micro-frontends JavaScript files. 102 | 103 | The ```static``` folder should look like this screenshot now: 104 | 105 | ![static folder in S3 bucket](./images/s3-static-folder.png) 106 | 107 | Finally, add a JPG image called ```book.jpg``` in the ```static``` folder of the S3 bucket, that is the image that will be displayed by default when you deploy for the first time this project in your AWS account. 108 | 109 | We now need to create a new folder in the S3 bucket called ```templates``` and upload the file used as HTML template for rendering the final page. Upload ```SSR-catalog-example/ui-composer/static/catalog.template``` in the ```templates``` folder. 110 | 111 | ![templates folder in S3 bucket](./images/s3-templates-folder.png) 112 | 113 | ### Set endpoint for reviews service 114 | 115 | After provisioning the reviews micro-frontend in the AWS account, you get an open endpoint used only for demo purposes. 116 | 117 | **IMPORTANT**: This endpoint has no associated authorizers and is therefore is public. 118 | In a real world application, some form of authentication authentication should be used e.g. Cognito User Pools, AWS IAM or a Lambda authorizer. 119 | 120 | Get the API Gateway URL from AWS console or from the provisioning output of SAM CLI and add it to ```ReviewForm.js``` (it's inside the ```reviews-mfe``` folder) in the ```URL``` constant. 121 | 122 | ![API GW console](./images/apigw.png) 123 | 124 | The endpoint created is called ```review``` and is in the ```Prod``` stage. 125 | 126 | The final endpoint should look like this one: 127 | 128 | ```shell 129 | https://xxxxxxxx.execute-api.REGION.amazonaws.com/Prod/review 130 | ``` 131 | 132 | After this, you can run ```npm run build-client``` and upload the file in the S3 bucket inside the ```static``` folder. 133 | 134 | ### Loading the website in a browser 135 | 136 | At the end of the deployment, you can see from the CloudFormation dashboard in the output panel the URL to paste in a browser for seeing the final solution. 137 | 138 | If you are deploying the solution from your laptop, in the CLI Outputs you can find the endpoint to paste in a browser (```UiComposerStack.distributionDomainName```). 139 | 140 | ### Deleting the Solution 141 | 142 | To delete the micro-frontend stacks and UI Composer stacks via AWS Console: 143 | 144 | 1. Open the [CloudFormation Console](https://console.aws.amazon.com/cloudformation/home) page and choose the relevant stack, then choose _"Delete"_ 145 | 2. Once the confirmation modal appears, choose _"Delete stack"_. 146 | 3. Wait for the CloudFormation stack to finish updating. Completion is indicated when the _"Stack status"_ is _"DELETE_COMPLETE"_ 147 | 148 | Remember to follow these steps for the UI composer and every micro-frontend stack created. 149 | 150 | To delete a stack via the AWS CLI consult [the documentation](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/delete-stack.html). -------------------------------------------------------------------------------- /SSR-catalog-example/dependencies/preact.min.js: -------------------------------------------------------------------------------- 1 | !function(){var n,l,u,t,i,o,r,e,f={},c=[],a=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function s(n,l){for(var u in l)n[u]=l[u];return n}function h(n){var l=n.parentNode;l&&l.removeChild(n)}function v(l,u,t){var i,o,r,e={};for(r in u)"key"==r?i=u[r]:"ref"==r?o=u[r]:e[r]=u[r];if(arguments.length>2&&(e.children=arguments.length>3?n.call(arguments,2):t),"function"==typeof l&&null!=l.defaultProps)for(r in l.defaultProps)void 0===e[r]&&(e[r]=l.defaultProps[r]);return y(l,e,i,o,null)}function y(n,t,i,o,r){var e={type:n,props:t,key:i,ref:o,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==r?++u:r};return null==r&&null!=l.vnode&&l.vnode(e),e}function d(n){return n.children}function p(n,l){this.props=n,this.context=l}function _(n,l){if(null==l)return n.__?_(n.__,n.__.__k.indexOf(n)+1):null;for(var u;l0?y(m.type,m.props,m.key,null,m.__v):m)){if(m.__=u,m.__b=u.__b+1,null===(p=C[h])||p&&m.key==p.key&&m.type===p.type)C[h]=void 0;else for(v=0;v2&&(e.children=arguments.length>3?n.call(arguments,2):t),y(l.type,e,i||l.key,o||l.ref,null)},createContext:function(n,l){var u={__c:l="__cC"+r++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,t;return this.getChildContext||(u=[],(t={})[l]=this,this.getChildContext=function(){return t},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.some(k)},this.sub=function(n){u.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u.splice(u.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Provider.__=u.Consumer.contextType=u},toChildArray:function n(l,u){return u=u||[],null==l||"boolean"==typeof l||(Array.isArray(l)?l.some(function(l){n(l,u)}):u.push(l)),u},options:l},typeof module<"u"?module.exports=e:self.preact=e}(); 2 | //# sourceMappingURL=preact.min.js.map -------------------------------------------------------------------------------- /SSR-catalog-example/ui-composer/lib/ui-composer-stack.ts: -------------------------------------------------------------------------------- 1 | import {Stack, StackProps, aws_iam, App, RemovalPolicy, CfnOutput } from 'aws-cdk-lib'; 2 | import { CloudFrontWebDistribution, OriginAccessIdentity, CloudFrontAllowedMethods, CloudFrontAllowedCachedMethods, OriginProtocolPolicy } from 'aws-cdk-lib/aws-cloudfront'; 3 | import { Bucket, BlockPublicAccess, BucketEncryption } from 'aws-cdk-lib/aws-s3'; 4 | import { Cluster, ContainerImage } from 'aws-cdk-lib/aws-ecs'; 5 | import { Vpc, FlowLog, FlowLogResourceType, FlowLogDestination, CfnPrefixList, SecurityGroup, Port, Peer } from "aws-cdk-lib/aws-ec2"; 6 | import { AccountRootPrincipal, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; 7 | import { LogGroup } from 'aws-cdk-lib/aws-logs'; 8 | import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns'; 9 | 10 | import * as path from 'path'; 11 | import { NagSuppressions } from 'cdk-nag'; 12 | import { StringParameter } from 'aws-cdk-lib/aws-ssm'; 13 | 14 | export class UiComposerStack extends Stack { 15 | constructor(scope: App, id: string, props?: StackProps) { 16 | super(scope, id, props); 17 | // -------- S3 Buckets ------------ 18 | const accesslogsBucket = process.env.ACCESS_LOGS_BUCKET 19 | ? Bucket.fromBucketName(this, "access-logs-bucket", process.env.ACCESS_LOGS_BUCKET) 20 | : undefined; 21 | const sourceBucket = new Bucket(this, 'mfe-static-assets', { 22 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 23 | enforceSSL: true, 24 | serverAccessLogsBucket: accesslogsBucket, 25 | serverAccessLogsPrefix: "s3-logs", 26 | encryption: BucketEncryption.S3_MANAGED, 27 | removalPolicy: RemovalPolicy.DESTROY 28 | }); 29 | 30 | // -------- UI-COMPOSER-NETWORKING ------------ 31 | const vpc = new Vpc(this, "ui-composer-vpc", { 32 | maxAzs: 3 33 | }); 34 | 35 | const cluster = new Cluster(this, "ui-composer-cluster", { 36 | vpc: vpc, 37 | containerInsights: true, 38 | }); 39 | 40 | const vpcLogGroup = new LogGroup(this, 'VPCLogGroup'); 41 | const role = new Role(this, "vpc-log-group", { 42 | assumedBy: new ServicePrincipal('vpc-flow-logs.amazonaws.com') 43 | }); 44 | 45 | const flowlog = new FlowLog(this, 'FlowLog', { 46 | resourceType: FlowLogResourceType.fromVpc(vpc), 47 | destination: FlowLogDestination.toCloudWatchLogs(vpcLogGroup, role) 48 | }); 49 | 50 | const uiComposerSG = new SecurityGroup(this, 'ui-composer-sg', { 51 | vpc: vpc, 52 | allowAllOutbound: true, 53 | description: 'ui-composer-sg' 54 | }) 55 | 56 | // -------- UI-COMPOSER-FARGATE ------------ 57 | 58 | // ---------------------------------------- 59 | 60 | // -------- SSM Parameter Store ---------- 61 | 62 | new StringParameter(this, 'ui-composer-params', { 63 | parameterName: '/ssr-mfe/catalogpage', 64 | description: "template config for UI-composer", 65 | stringValue: `{"template": "templates/catalog.template", "templatesBucket": "${sourceBucket.bucketName}"}`, 66 | }); 67 | 68 | // -------- UI-COMPOSER ------------- 69 | 70 | const taskRole = new aws_iam.Role(this, "fargate-task-role", { 71 | assumedBy: new aws_iam.ServicePrincipal("ecs-tasks.amazonaws.com"), 72 | roleName: "fargate-task-role", 73 | description: "IAM role for consuming MFEs" 74 | }); 75 | 76 | const account = new AccountRootPrincipal() 77 | 78 | taskRole.attachInlinePolicy( 79 | new aws_iam.Policy(this, "task-access-policy", { 80 | statements: [ 81 | new aws_iam.PolicyStatement({ 82 | effect: aws_iam.Effect.ALLOW, 83 | actions: [ 84 | "ssm:GetParameter", 85 | "ssm:GetParameters" 86 | ], 87 | resources: [`arn:aws:ssm:${process.env.region || "eu-west-1"}:${account.accountId}:parameter/ssr-mfe/*`] 88 | }), 89 | new aws_iam.PolicyStatement({ 90 | effect: aws_iam.Effect.ALLOW, 91 | actions: [ 92 | "lambda:InvokeFunction", 93 | "states:StartSyncExecution" 94 | ], 95 | resources: [ 96 | `arn:aws:lambda:${process.env.region || "eu-west-1"}:${account.accountId}:function:*`, 97 | `arn:aws:states:${process.env.region || "eu-west-1"}:${account.accountId}:stateMachine:*` 98 | ] 99 | }), 100 | new aws_iam.PolicyStatement({ 101 | effect: aws_iam.Effect.ALLOW, 102 | actions: [ 103 | "s3:GetObject", 104 | "s3:ListBucket" 105 | ], 106 | resources: [ 107 | sourceBucket.bucketArn, 108 | `${sourceBucket.bucketArn}/*` 109 | ], 110 | }) 111 | ], 112 | }) 113 | ) 114 | 115 | const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(this, 'ui-composer-service', { 116 | cluster, 117 | memoryLimitMiB: 2048, 118 | desiredCount: 2, 119 | cpu: 512, 120 | listenerPort: 80, 121 | publicLoadBalancer: true, 122 | openListener: false, 123 | securityGroups: [uiComposerSG], 124 | serviceName: "ui-composer-service", 125 | circuitBreaker: { 126 | rollback: true, 127 | }, 128 | taskImageOptions:{ 129 | image: ContainerImage.fromAsset(path.resolve(__dirname, '../')), 130 | taskRole: taskRole, 131 | executionRole: taskRole, 132 | enableLogging: true, 133 | environment: { 134 | REGION: process.env.region || "eu-west-1" 135 | } 136 | }, 137 | }); 138 | 139 | const albSG = new SecurityGroup(this, 'alb-sg', { 140 | vpc: vpc, 141 | allowAllOutbound: true, 142 | description: 'alb-sg' 143 | }) 144 | 145 | const PREFIX_GLOBAL_CF_EU_WEST_1 = "pl-4fa04526"; 146 | albSG.addIngressRule(Peer.prefixList(PREFIX_GLOBAL_CF_EU_WEST_1), Port.tcp(80), 'allow ingress from CF') 147 | loadBalancedFargateService.loadBalancer.addSecurityGroup(albSG) 148 | 149 | if (accesslogsBucket !== undefined) { 150 | loadBalancedFargateService.loadBalancer.logAccessLogs(accesslogsBucket as Bucket, "alb-logs"); 151 | } 152 | 153 | loadBalancedFargateService.targetGroup.configureHealthCheck({ 154 | path: "/health", 155 | }); 156 | 157 | const scalableTarget = loadBalancedFargateService.service.autoScaleTaskCount({ 158 | minCapacity: 1, 159 | maxCapacity: 3, 160 | }); 161 | 162 | scalableTarget.scaleOnCpuUtilization('CpuScaling', { 163 | targetUtilizationPercent: 70, 164 | }); 165 | 166 | scalableTarget.scaleOnMemoryUtilization('MemoryScaling', { 167 | targetUtilizationPercent: 70, 168 | }); 169 | 170 | // ---------------------------------------- 171 | 172 | // -------- CF distro ------------ 173 | 174 | const oai = new OriginAccessIdentity(this, 'mfe-oai') 175 | let loggingConfiguration; 176 | if (accesslogsBucket !== undefined) { 177 | loggingConfiguration = { 178 | bucket: accesslogsBucket, 179 | includeCookies: false, 180 | }; 181 | } 182 | 183 | const distribution = new CloudFrontWebDistribution(this, 'mfe-distro', { 184 | loggingConfig: loggingConfiguration, 185 | originConfigs: [ 186 | { 187 | 188 | s3OriginSource: { 189 | s3BucketSource: sourceBucket, 190 | originAccessIdentity: oai 191 | }, 192 | behaviors : [ 193 | { pathPattern: '/static/*', 194 | allowedMethods: CloudFrontAllowedMethods.GET_HEAD, 195 | cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD 196 | } 197 | ], 198 | }, 199 | { 200 | customOriginSource: { 201 | domainName: loadBalancedFargateService.loadBalancer.loadBalancerDnsName, 202 | originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY, 203 | }, 204 | behaviors : [ 205 | { 206 | isDefaultBehavior: true, 207 | allowedMethods: CloudFrontAllowedMethods.ALL, 208 | forwardedValues: { 209 | queryString: true, 210 | cookies: { 211 | forward: 'all' 212 | }, 213 | headers: ['*'], 214 | } 215 | } 216 | ] 217 | } 218 | ], 219 | }); 220 | 221 | // ---------------------------------------- 222 | 223 | new CfnOutput(this, 'distributionDomainName', { 224 | value: `https://${distribution.distributionDomainName}/productdetails`, 225 | description: 'the URL to access the website in a browser', 226 | exportName: 'website-url' 227 | }); 228 | 229 | // -------- NAG suppression statements ------------ 230 | 231 | NagSuppressions.addResourceSuppressions(loadBalancedFargateService.taskDefinition, [ 232 | {id: 'AwsSolutions-ECS2', reason: 'It\'s a demo'}, 233 | ]) 234 | 235 | NagSuppressions.addStackSuppressions(this, [ 236 | {id: 'AwsSolutions-ELB2', reason: 'It\'s a demo so access logs are opt-in using the ACCESS_LOGS_BUCKET env var' }, 237 | {id: 'AwsSolutions-IAM5', reason: 'remediate with override inline policies'} 238 | ]) 239 | 240 | NagSuppressions.addResourceSuppressions(distribution, [ 241 | { id: 'AwsSolutions-CFR5', reason: 'It\'s a demo so no need to enforce SSLv3 or TLSv1' }, 242 | { id: 'AwsSolutions-CFR4', reason: 'It\'s a demo so no need to enforce SSLv3 or TLSv1' }, 243 | { id: 'AwsSolutions-CFR3', reason: 'It\'s a demo so no need to have access logs' }, 244 | { id: 'AwsSolutions-CFR2', reason: 'It\'s a demo so no need to integrate WAF on the CloudFront Distribution' }, 245 | { id: 'AwsSolutions-CFR1', reason: 'It\'s a demo so no need to implement GEO restriction rules' }, 246 | ]); 247 | } 248 | } -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES.txt: -------------------------------------------------------------------------------- 1 | ** aws-cdk; version 2.33.0 -- https://github.com/aws/aws-cdk 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2018-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | * For aws-cdk see also this required NOTICE: 206 | AWS Cloud Development Kit (AWS CDK) 207 | Copyright 2018-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 208 | 209 | 210 | ------ 211 | 212 | ** cdk-nag; version 2.22.2 -- https://github.com/cdklabs/cdk-nag 213 | 214 | Apache License 215 | Version 2.0, January 2004 216 | http://www.apache.org/licenses/ 217 | 218 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 219 | 220 | 1. Definitions. 221 | 222 | "License" shall mean the terms and conditions for use, reproduction, 223 | and distribution as defined by Sections 1 through 9 of this document. 224 | 225 | "Licensor" shall mean the copyright owner or entity authorized by 226 | the copyright owner that is granting the License. 227 | 228 | "Legal Entity" shall mean the union of the acting entity and all 229 | other entities that control, are controlled by, or are under common 230 | control with that entity. For the purposes of this definition, 231 | "control" means (i) the power, direct or indirect, to cause the 232 | direction or management of such entity, whether by contract or 233 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 234 | outstanding shares, or (iii) beneficial ownership of such entity. 235 | 236 | "You" (or "Your") shall mean an individual or Legal Entity 237 | exercising permissions granted by this License. 238 | 239 | "Source" form shall mean the preferred form for making modifications, 240 | including but not limited to software source code, documentation 241 | source, and configuration files. 242 | 243 | "Object" form shall mean any form resulting from mechanical 244 | transformation or translation of a Source form, including but 245 | not limited to compiled object code, generated documentation, 246 | and conversions to other media types. 247 | 248 | "Work" shall mean the work of authorship, whether in Source or 249 | Object form, made available under the License, as indicated by a 250 | copyright notice that is included in or attached to the work 251 | (an example is provided in the Appendix below). 252 | 253 | "Derivative Works" shall mean any work, whether in Source or Object 254 | form, that is based on (or derived from) the Work and for which the 255 | editorial revisions, annotations, elaborations, or other modifications 256 | represent, as a whole, an original work of authorship. For the purposes 257 | of this License, Derivative Works shall not include works that remain 258 | separable from, or merely link (or bind by name) to the interfaces of, 259 | the Work and Derivative Works thereof. 260 | 261 | "Contribution" shall mean any work of authorship, including 262 | the original version of the Work and any modifications or additions 263 | to that Work or Derivative Works thereof, that is intentionally 264 | submitted to Licensor for inclusion in the Work by the copyright owner 265 | or by an individual or Legal Entity authorized to submit on behalf of 266 | the copyright owner. For the purposes of this definition, "submitted" 267 | means any form of electronic, verbal, or written communication sent 268 | to the Licensor or its representatives, including but not limited to 269 | communication on electronic mailing lists, source code control systems, 270 | and issue tracking systems that are managed by, or on behalf of, the 271 | Licensor for the purpose of discussing and improving the Work, but 272 | excluding communication that is conspicuously marked or otherwise 273 | designated in writing by the copyright owner as "Not a Contribution." 274 | 275 | "Contributor" shall mean Licensor and any individual or Legal Entity 276 | on behalf of whom a Contribution has been received by Licensor and 277 | subsequently incorporated within the Work. 278 | 279 | 2. Grant of Copyright License. Subject to the terms and conditions of 280 | this License, each Contributor hereby grants to You a perpetual, 281 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 282 | copyright license to reproduce, prepare Derivative Works of, 283 | publicly display, publicly perform, sublicense, and distribute the 284 | Work and such Derivative Works in Source or Object form. 285 | 286 | 3. Grant of Patent License. Subject to the terms and conditions of 287 | this License, each Contributor hereby grants to You a perpetual, 288 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 289 | (except as stated in this section) patent license to make, have made, 290 | use, offer to sell, sell, import, and otherwise transfer the Work, 291 | where such license applies only to those patent claims licensable 292 | by such Contributor that are necessarily infringed by their 293 | Contribution(s) alone or by combination of their Contribution(s) 294 | with the Work to which such Contribution(s) was submitted. If You 295 | institute patent litigation against any entity (including a 296 | cross-claim or counterclaim in a lawsuit) alleging that the Work 297 | or a Contribution incorporated within the Work constitutes direct 298 | or contributory patent infringement, then any patent licenses 299 | granted to You under this License for that Work shall terminate 300 | as of the date such litigation is filed. 301 | 302 | 4. Redistribution. You may reproduce and distribute copies of the 303 | Work or Derivative Works thereof in any medium, with or without 304 | modifications, and in Source or Object form, provided that You 305 | meet the following conditions: 306 | 307 | (a) You must give any other recipients of the Work or 308 | Derivative Works a copy of this License; and 309 | 310 | (b) You must cause any modified files to carry prominent notices 311 | stating that You changed the files; and 312 | 313 | (c) You must retain, in the Source form of any Derivative Works 314 | that You distribute, all copyright, patent, trademark, and 315 | attribution notices from the Source form of the Work, 316 | excluding those notices that do not pertain to any part of 317 | the Derivative Works; and 318 | 319 | (d) If the Work includes a "NOTICE" text file as part of its 320 | distribution, then any Derivative Works that You distribute must 321 | include a readable copy of the attribution notices contained 322 | within such NOTICE file, excluding those notices that do not 323 | pertain to any part of the Derivative Works, in at least one 324 | of the following places: within a NOTICE text file distributed 325 | as part of the Derivative Works; within the Source form or 326 | documentation, if provided along with the Derivative Works; or, 327 | within a display generated by the Derivative Works, if and 328 | wherever such third-party notices normally appear. The contents 329 | of the NOTICE file are for informational purposes only and 330 | do not modify the License. You may add Your own attribution 331 | notices within Derivative Works that You distribute, alongside 332 | or as an addendum to the NOTICE text from the Work, provided 333 | that such additional attribution notices cannot be construed 334 | as modifying the License. 335 | 336 | You may add Your own copyright statement to Your modifications and 337 | may provide additional or different license terms and conditions 338 | for use, reproduction, or distribution of Your modifications, or 339 | for any such Derivative Works as a whole, provided Your use, 340 | reproduction, and distribution of the Work otherwise complies with 341 | the conditions stated in this License. 342 | 343 | 5. Submission of Contributions. Unless You explicitly state otherwise, 344 | any Contribution intentionally submitted for inclusion in the Work 345 | by You to the Licensor shall be under the terms and conditions of 346 | this License, without any additional terms or conditions. 347 | Notwithstanding the above, nothing herein shall supersede or modify 348 | the terms of any separate license agreement you may have executed 349 | with Licensor regarding such Contributions. 350 | 351 | 6. Trademarks. This License does not grant permission to use the trade 352 | names, trademarks, service marks, or product names of the Licensor, 353 | except as required for reasonable and customary use in describing the 354 | origin of the Work and reproducing the content of the NOTICE file. 355 | 356 | 7. Disclaimer of Warranty. Unless required by applicable law or 357 | agreed to in writing, Licensor provides the Work (and each 358 | Contributor provides its Contributions) on an "AS IS" BASIS, 359 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 360 | implied, including, without limitation, any warranties or conditions 361 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 362 | PARTICULAR PURPOSE. You are solely responsible for determining the 363 | appropriateness of using or redistributing the Work and assume any 364 | risks associated with Your exercise of permissions under this License. 365 | 366 | 8. Limitation of Liability. In no event and under no legal theory, 367 | whether in tort (including negligence), contract, or otherwise, 368 | unless required by applicable law (such as deliberate and grossly 369 | negligent acts) or agreed to in writing, shall any Contributor be 370 | liable to You for damages, including any direct, indirect, special, 371 | incidental, or consequential damages of any character arising as a 372 | result of this License or out of the use or inability to use the 373 | Work (including but not limited to damages for loss of goodwill, 374 | work stoppage, computer failure or malfunction, or any and all 375 | other commercial damages or losses), even if such Contributor 376 | has been advised of the possibility of such damages. 377 | 378 | 9. Accepting Warranty or Additional Liability. While redistributing 379 | the Work or Derivative Works thereof, You may choose to offer, 380 | and charge a fee for, acceptance of support, warranty, indemnity, 381 | or other liability obligations and/or rights consistent with this 382 | License. However, in accepting such obligations, You may act only 383 | on Your own behalf and on Your sole responsibility, not on behalf 384 | of any other Contributor, and only if You agree to indemnify, 385 | defend, and hold each Contributor harmless for any liability 386 | incurred by, or claims asserted against, such Contributor by reason 387 | of your accepting any such warranty or additional liability. 388 | 389 | END OF TERMS AND CONDITIONS 390 | 391 | APPENDIX: How to apply the Apache License to your work. 392 | 393 | To apply the Apache License to your work, attach the following 394 | boilerplate notice, with the fields enclosed by brackets "[]" 395 | replaced with your own identifying information. (Don't include 396 | the brackets!) The text should be enclosed in the appropriate 397 | comment syntax for the file format. We also recommend that a 398 | file or class name and description of purpose be included on the 399 | same "printed page" as the copyright notice for easier 400 | identification within third-party archives. 401 | 402 | Copyright [yyyy] [name of copyright owner] 403 | 404 | Licensed under the Apache License, Version 2.0 (the "License"); 405 | you may not use this file except in compliance with the License. 406 | You may obtain a copy of the License at 407 | 408 | http://www.apache.org/licenses/LICENSE-2.0 409 | 410 | Unless required by applicable law or agreed to in writing, software 411 | distributed under the License is distributed on an "AS IS" BASIS, 412 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 413 | See the License for the specific language governing permissions and 414 | limitations under the License. 415 | * For cdk-nag see also this required NOTICE: 416 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 417 | SPDX-License-Identifier: Apache-2.0 418 | 419 | ------ 420 | 421 | ** NodeJS-preact; version 10.12.1 -- https://preactjs.com/ 422 | The MIT License (MIT) 423 | 424 | Copyright (c) 2015-present Jason Miller 425 | 426 | Permission is hereby granted, free of charge, to any person obtaining a copy 427 | of this software and associated documentation files (the "Software"), to deal 428 | in the Software without restriction, including without limitation the rights 429 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 430 | copies of the Software, and to permit persons to whom the Software is 431 | furnished to do so, subject to the following conditions: 432 | 433 | The above copyright notice and this permission notice shall be included in all 434 | copies or substantial portions of the Software. 435 | 436 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 437 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 438 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 439 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 440 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 441 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 442 | SOFTWARE. 443 | ** node-html-parser; version 6.1.4 -- https://github.com/taoqf/node-html-parser 444 | Copyright 2019 Tao Qiufeng 445 | 446 | Permission is hereby granted, free of charge, to any person obtaining a copy of 447 | this software and associated documentation files (the "Software"), to deal in 448 | the Software without restriction, including without limitation the rights to 449 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 450 | the Software, and to permit persons to whom the Software is furnished to do so, 451 | subject to the following conditions: 452 | 453 | The above copyright notice and this permission notice shall be included in all 454 | copies or substantial portions of the Software. 455 | 456 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 457 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 458 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 459 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 460 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 461 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 462 | ** htm; version 2.1.1 -- https://github.com/developit/htm 463 | 464 | Apache License 465 | Version 2.0, January 2004 466 | http://www.apache.org/licenses/ 467 | 468 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 469 | 470 | 1. Definitions. 471 | 472 | "License" shall mean the terms and conditions for use, reproduction, 473 | and distribution as defined by Sections 1 through 9 of this document. 474 | 475 | "Licensor" shall mean the copyright owner or entity authorized by 476 | the copyright owner that is granting the License. 477 | 478 | "Legal Entity" shall mean the union of the acting entity and all 479 | other entities that control, are controlled by, or are under common 480 | control with that entity. For the purposes of this definition, 481 | "control" means (i) the power, direct or indirect, to cause the 482 | direction or management of such entity, whether by contract or 483 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 484 | outstanding shares, or (iii) beneficial ownership of such entity. 485 | 486 | "You" (or "Your") shall mean an individual or Legal Entity 487 | exercising permissions granted by this License. 488 | 489 | "Source" form shall mean the preferred form for making modifications, 490 | including but not limited to software source code, documentation 491 | source, and configuration files. 492 | 493 | "Object" form shall mean any form resulting from mechanical 494 | transformation or translation of a Source form, including but 495 | not limited to compiled object code, generated documentation, 496 | and conversions to other media types. 497 | 498 | "Work" shall mean the work of authorship, whether in Source or 499 | Object form, made available under the License, as indicated by a 500 | copyright notice that is included in or attached to the work 501 | (an example is provided in the Appendix below). 502 | 503 | "Derivative Works" shall mean any work, whether in Source or Object 504 | form, that is based on (or derived from) the Work and for which the 505 | editorial revisions, annotations, elaborations, or other modifications 506 | represent, as a whole, an original work of authorship. For the purposes 507 | of this License, Derivative Works shall not include works that remain 508 | separable from, or merely link (or bind by name) to the interfaces of, 509 | the Work and Derivative Works thereof. 510 | 511 | "Contribution" shall mean any work of authorship, including 512 | the original version of the Work and any modifications or additions 513 | to that Work or Derivative Works thereof, that is intentionally 514 | submitted to Licensor for inclusion in the Work by the copyright owner 515 | or by an individual or Legal Entity authorized to submit on behalf of 516 | the copyright owner. For the purposes of this definition, "submitted" 517 | means any form of electronic, verbal, or written communication sent 518 | to the Licensor or its representatives, including but not limited to 519 | communication on electronic mailing lists, source code control systems, 520 | and issue tracking systems that are managed by, or on behalf of, the 521 | Licensor for the purpose of discussing and improving the Work, but 522 | excluding communication that is conspicuously marked or otherwise 523 | designated in writing by the copyright owner as "Not a Contribution." 524 | 525 | "Contributor" shall mean Licensor and any individual or Legal Entity 526 | on behalf of whom a Contribution has been received by Licensor and 527 | subsequently incorporated within the Work. 528 | 529 | 2. Grant of Copyright License. Subject to the terms and conditions of 530 | this License, each Contributor hereby grants to You a perpetual, 531 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 532 | copyright license to reproduce, prepare Derivative Works of, 533 | publicly display, publicly perform, sublicense, and distribute the 534 | Work and such Derivative Works in Source or Object form. 535 | 536 | 3. Grant of Patent License. Subject to the terms and conditions of 537 | this License, each Contributor hereby grants to You a perpetual, 538 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 539 | (except as stated in this section) patent license to make, have made, 540 | use, offer to sell, sell, import, and otherwise transfer the Work, 541 | where such license applies only to those patent claims licensable 542 | by such Contributor that are necessarily infringed by their 543 | Contribution(s) alone or by combination of their Contribution(s) 544 | with the Work to which such Contribution(s) was submitted. If You 545 | institute patent litigation against any entity (including a 546 | cross-claim or counterclaim in a lawsuit) alleging that the Work 547 | or a Contribution incorporated within the Work constitutes direct 548 | or contributory patent infringement, then any patent licenses 549 | granted to You under this License for that Work shall terminate 550 | as of the date such litigation is filed. 551 | 552 | 4. Redistribution. You may reproduce and distribute copies of the 553 | Work or Derivative Works thereof in any medium, with or without 554 | modifications, and in Source or Object form, provided that You 555 | meet the following conditions: 556 | 557 | (a) You must give any other recipients of the Work or 558 | Derivative Works a copy of this License; and 559 | 560 | (b) You must cause any modified files to carry prominent notices 561 | stating that You changed the files; and 562 | 563 | (c) You must retain, in the Source form of any Derivative Works 564 | that You distribute, all copyright, patent, trademark, and 565 | attribution notices from the Source form of the Work, 566 | excluding those notices that do not pertain to any part of 567 | the Derivative Works; and 568 | 569 | (d) If the Work includes a "NOTICE" text file as part of its 570 | distribution, then any Derivative Works that You distribute must 571 | include a readable copy of the attribution notices contained 572 | within such NOTICE file, excluding those notices that do not 573 | pertain to any part of the Derivative Works, in at least one 574 | of the following places: within a NOTICE text file distributed 575 | as part of the Derivative Works; within the Source form or 576 | documentation, if provided along with the Derivative Works; or, 577 | within a display generated by the Derivative Works, if and 578 | wherever such third-party notices normally appear. The contents 579 | of the NOTICE file are for informational purposes only and 580 | do not modify the License. You may add Your own attribution 581 | notices within Derivative Works that You distribute, alongside 582 | or as an addendum to the NOTICE text from the Work, provided 583 | that such additional attribution notices cannot be construed 584 | as modifying the License. 585 | 586 | You may add Your own copyright statement to Your modifications and 587 | may provide additional or different license terms and conditions 588 | for use, reproduction, or distribution of Your modifications, or 589 | for any such Derivative Works as a whole, provided Your use, 590 | reproduction, and distribution of the Work otherwise complies with 591 | the conditions stated in this License. 592 | 593 | 5. Submission of Contributions. Unless You explicitly state otherwise, 594 | any Contribution intentionally submitted for inclusion in the Work 595 | by You to the Licensor shall be under the terms and conditions of 596 | this License, without any additional terms or conditions. 597 | Notwithstanding the above, nothing herein shall supersede or modify 598 | the terms of any separate license agreement you may have executed 599 | with Licensor regarding such Contributions. 600 | 601 | 6. Trademarks. This License does not grant permission to use the trade 602 | names, trademarks, service marks, or product names of the Licensor, 603 | except as required for reasonable and customary use in describing the 604 | origin of the Work and reproducing the content of the NOTICE file. 605 | 606 | 7. Disclaimer of Warranty. Unless required by applicable law or 607 | agreed to in writing, Licensor provides the Work (and each 608 | Contributor provides its Contributions) on an "AS IS" BASIS, 609 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 610 | implied, including, without limitation, any warranties or conditions 611 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 612 | PARTICULAR PURPOSE. You are solely responsible for determining the 613 | appropriateness of using or redistributing the Work and assume any 614 | risks associated with Your exercise of permissions under this License. 615 | 616 | 8. Limitation of Liability. In no event and under no legal theory, 617 | whether in tort (including negligence), contract, or otherwise, 618 | unless required by applicable law (such as deliberate and grossly 619 | negligent acts) or agreed to in writing, shall any Contributor be 620 | liable to You for damages, including any direct, indirect, special, 621 | incidental, or consequential damages of any character arising as a 622 | result of this License or out of the use or inability to use the 623 | Work (including but not limited to damages for loss of goodwill, 624 | work stoppage, computer failure or malfunction, or any and all 625 | other commercial damages or losses), even if such Contributor 626 | has been advised of the possibility of such damages. 627 | 628 | 9. Accepting Warranty or Additional Liability. While redistributing 629 | the Work or Derivative Works thereof, You may choose to offer, 630 | and charge a fee for, acceptance of support, warranty, indemnity, 631 | or other liability obligations and/or rights consistent with this 632 | License. However, in accepting such obligations, You may act only 633 | on Your own behalf and on Your sole responsibility, not on behalf 634 | of any other Contributor, and only if You agree to indemnify, 635 | defend, and hold each Contributor harmless for any liability 636 | incurred by, or claims asserted against, such Contributor by reason 637 | of your accepting any such warranty or additional liability. 638 | 639 | END OF TERMS AND CONDITIONS 640 | 641 | APPENDIX: How to apply the Apache License to your work. 642 | 643 | To apply the Apache License to your work, attach the following 644 | boilerplate notice, with the fields enclosed by brackets "[]" 645 | replaced with your own identifying information. (Don't include 646 | the brackets!) The text should be enclosed in the appropriate 647 | comment syntax for the file format. We also recommend that a 648 | file or class name and description of purpose be included on the 649 | same "printed page" as the copyright notice for easier 650 | identification within third-party archives. 651 | 652 | Copyright 2018 Google Inc. 653 | 654 | Licensed under the Apache License, Version 2.0 (the "License"); 655 | you may not use this file except in compliance with the License. 656 | You may obtain a copy of the License at 657 | 658 | http://www.apache.org/licenses/LICENSE-2.0 659 | 660 | Unless required by applicable law or agreed to in writing, software 661 | distributed under the License is distributed on an "AS IS" BASIS, 662 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 663 | See the License for the specific language governing permissions and 664 | limitations under the License. 665 | ** Fastify; version 4.13.0 -- https://www.fastify.io/ 666 | MIT License 667 | 668 | Copyright (c) 2016-2023 The Fastify Team 669 | 670 | The Fastify team members are listed at https://github.com/fastify/fastify#team 671 | and in the README file. 672 | 673 | Permission is hereby granted, free of charge, to any person obtaining a copy 674 | of this software and associated documentation files (the "Software"), to deal 675 | in the Software without restriction, including without limitation the rights 676 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 677 | copies of the Software, and to permit persons to whom the Software is 678 | furnished to do so, subject to the following conditions: 679 | 680 | The above copyright notice and this permission notice shall be included in all 681 | copies or substantial portions of the Software. 682 | 683 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 684 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 685 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 686 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 687 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 688 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 689 | SOFTWARE. 690 | ** @babel/plugin-transform-react-jsx; version 7.18.6 -- https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-jsx 691 | MIT License 692 | 693 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 694 | 695 | Permission is hereby granted, free of charge, to any person obtaining 696 | a copy of this software and associated documentation files (the 697 | "Software"), to deal in the Software without restriction, including 698 | without limitation the rights to use, copy, modify, merge, publish, 699 | distribute, sublicense, and/or sell copies of the Software, and to 700 | permit persons to whom the Software is furnished to do so, subject to 701 | the following conditions: 702 | 703 | The above copyright notice and this permission notice shall be 704 | included in all copies or substantial portions of the Software. 705 | 706 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 707 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 708 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 709 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 710 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 711 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 712 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 713 | 714 | MIT License 715 | 716 | Copyright (c) 717 | 718 | Permission is hereby granted, free of charge, to any person obtaining a copy of 719 | this software and associated documentation files (the "Software"), to deal in 720 | the Software without restriction, including without limitation the rights to 721 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 722 | the Software, and to permit persons to whom the Software is furnished to do so, 723 | subject to the following conditions: 724 | 725 | The above copyright notice and this permission notice shall be included in all 726 | copies or substantial portions of the Software. 727 | 728 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 729 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 730 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 731 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 732 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 733 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 734 | 735 | ------ 736 | 737 | ** babel-loader; version 8.2.2 -- https://github.com/babel/babel-loader 738 | Copyright (c) 2014-2019 Luís Couto 739 | 740 | Copyright (c) 2014-2019 Luís Couto 741 | 742 | MIT License 743 | 744 | Permission is hereby granted, free of charge, to any person obtaining 745 | a copy of this software and associated documentation files (the 746 | "Software"), to deal in the Software without restriction, including 747 | without limitation the rights to use, copy, modify, merge, publish, 748 | distribute, sublicense, and/or sell copies of the Software, and to 749 | permit persons to whom the Software is furnished to do so, subject to 750 | the following conditions: 751 | 752 | The above copyright notice and this permission notice shall be 753 | included in all copies or substantial portions of the Software. 754 | 755 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 756 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 757 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 758 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 759 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 760 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 761 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 762 | 763 | ------ 764 | 765 | ** axios; version 0.27.0 -- https://github.com/axios/axios 766 | Copyright (c) 2014-present Matt Zabriskie 767 | 768 | Copyright (c) 2014-present Matt Zabriskie 769 | 770 | Permission is hereby granted, free of charge, to any person obtaining a copy 771 | of this software and associated documentation files (the "Software"), to deal 772 | in the Software without restriction, including without limitation the rights 773 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 774 | copies of the Software, and to permit persons to whom the Software is 775 | furnished to do so, subject to the following conditions: 776 | 777 | The above copyright notice and this permission notice shall be included in 778 | all copies or substantial portions of the Software. 779 | 780 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 781 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 782 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 783 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 784 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 785 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 786 | THE SOFTWARE. 787 | 788 | ------ 789 | 790 | ** @babel/core; version 7.18.13 -- https://github.com/babel/babel/tree/master/packages/babel-core 791 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 792 | ** @babel/preset-env; version 7.18.10 -- https://github.com/babel/babel/tree/master/packages/babel-preset-env 793 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 794 | 795 | MIT License 796 | 797 | Copyright (c) 2014-present Sebastian McKenzie and other contributors 798 | 799 | Permission is hereby granted, free of charge, to any person obtaining 800 | a copy of this software and associated documentation files (the 801 | "Software"), to deal in the Software without restriction, including 802 | without limitation the rights to use, copy, modify, merge, publish, 803 | distribute, sublicense, and/or sell copies of the Software, and to 804 | permit persons to whom the Software is furnished to do so, subject to 805 | the following conditions: 806 | 807 | The above copyright notice and this permission notice shall be 808 | included in all copies or substantial portions of the Software. 809 | 810 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 811 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 812 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 813 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 814 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 815 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 816 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 817 | 818 | ------ 819 | 820 | ** webpack-cli; version 4.10.0 -- https://github.com/webpack/webpack-cli 821 | Copyright JS Foundation and other contributors 822 | 823 | Copyright JS Foundation and other contributors 824 | 825 | Permission is hereby granted, free of charge, to any person obtaining 826 | a copy of this software and associated documentation files (the 827 | 'Software'), to deal in the Software without restriction, including 828 | without limitation the rights to use, copy, modify, merge, publish, 829 | distribute, sublicense, and/or sell copies of the Software, and to 830 | permit persons to whom the Software is furnished to do so, subject to 831 | the following conditions: 832 | 833 | The above copyright notice and this permission notice shall be 834 | included in all copies or substantial portions of the Software. 835 | 836 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 837 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 838 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 839 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 840 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 841 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 842 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 843 | 844 | ------ 845 | 846 | ** webpack; version 5.74.0 -- https://github.com/webpack/webpack 847 | Copyright JS Foundation and other contributors 848 | 849 | Permission is hereby granted, free of charge, to any person obtaining 850 | a copy of this software and associated documentation files (the 851 | 'Software'), to deal in the Software without restriction, including 852 | without limitation the rights to use, copy, modify, merge, publish, 853 | distribute, sublicense, and/or sell copies of the Software, and to 854 | permit persons to whom the Software is furnished to do so, subject to 855 | the following conditions: 856 | 857 | The above copyright notice and this permission notice shall be 858 | included in all copies or substantial portions of the Software. 859 | 860 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 861 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 862 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 863 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 864 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 865 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 866 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 867 | --------------------------------------------------------------------------------