packages/blueprints/gen-ai-chatbot/static-assets/chatbot-genai-cdk/lib/constructs/frontend.ts (176 lines of code) (raw):

import { Construct } from "constructs"; import { CfnOutput, RemovalPolicy, Stack } from "aws-cdk-lib"; import { BlockPublicAccess, Bucket, BucketEncryption, IBucket, } from "aws-cdk-lib/aws-s3"; import { CloudFrontWebDistribution, OriginAccessIdentity, } from "aws-cdk-lib/aws-cloudfront"; import { NodejsBuild } from "deploy-time-build"; import { Auth } from "./auth"; import { Idp } from "../utils/identity-provider"; import { NagSuppressions } from "cdk-nag"; export interface FrontendProps { readonly webAclId: string; readonly enableMistral: boolean; readonly accessLogBucket?: IBucket; readonly env: { account: string; region: string; }; } export class Frontend extends Construct { readonly cloudFrontWebDistribution: CloudFrontWebDistribution; readonly assetBucket: Bucket; constructor(scope: Construct, id: string, props: FrontendProps) { super(scope, id); const assetBucket = new Bucket(this, "AssetBucket", { bucketName: `${ this.node.tryGetContext("bucketNamePrefix") ?? "chatbot-frontend-assets" }-${props.env.account}-${props.env.region}`, encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, enforceSSL: true, removalPolicy: (this.node.tryGetContext("bucketRemovalPolicy") ?? "DESTROY") === "RETAIN" ? RemovalPolicy.RETAIN : RemovalPolicy.DESTROY, autoDeleteObjects: true, serverAccessLogsBucket: props.accessLogBucket, serverAccessLogsPrefix: "AssetBucket", }); const originAccessIdentity = new OriginAccessIdentity( this, "OriginAccessIdentity" ); const distribution = new CloudFrontWebDistribution(this, "Distribution", { originConfigs: [ { s3OriginSource: { s3BucketSource: assetBucket, originAccessIdentity, }, behaviors: [ { isDefaultBehavior: true, }, ], }, ], errorConfigurations: [ { errorCode: 404, errorCachingMinTtl: 0, responseCode: 200, responsePagePath: "/", }, { errorCode: 403, errorCachingMinTtl: 0, responseCode: 200, responsePagePath: "/", }, ], ...(!this.shouldSkipAccessLogging() && { loggingConfig: { bucket: props.accessLogBucket, prefix: "Frontend/", }, }), webACLId: props.webAclId, }); NagSuppressions.addResourceSuppressions(distribution, [ { id: "AwsPrototyping-CloudFrontDistributionGeoRestrictions", reason: "this asset is being used all over the world", }, ]); this.assetBucket = assetBucket; this.cloudFrontWebDistribution = distribution; } getOrigin(): string { return `https://${this.cloudFrontWebDistribution.distributionDomainName}`; } buildViteApp({ backendApiEndpoint, webSocketApiEndpoint, userPoolDomainPrefix, enableMistral, auth, idp, }: { backendApiEndpoint: string; webSocketApiEndpoint: string; userPoolDomainPrefix: string; enableMistral: boolean; auth: Auth; idp: Idp; }) { const region = Stack.of(auth.userPool).region; const cognitoDomain = `${userPoolDomainPrefix}.auth.${region}.amazoncognito.com/`; const buildEnvProps = (() => { const defaultProps = { VITE_APP_API_ENDPOINT: backendApiEndpoint, VITE_APP_WS_ENDPOINT: webSocketApiEndpoint, VITE_APP_USER_POOL_ID: auth.userPool.userPoolId, VITE_APP_USER_POOL_CLIENT_ID: auth.client.userPoolClientId, VITE_APP_ENABLE_MISTRAL: enableMistral.toString(), VITE_APP_REGION: region, VITE_APP_USE_STREAMING: "true", }; if (!idp.isExist()) return defaultProps; const oAuthProps = { VITE_APP_REDIRECT_SIGNIN_URL: this.getOrigin(), VITE_APP_REDIRECT_SIGNOUT_URL: this.getOrigin(), VITE_APP_COGNITO_DOMAIN: cognitoDomain, VITE_APP_SOCIAL_PROVIDERS: idp.getSocialProviders(), VITE_APP_CUSTOM_PROVIDER_ENABLED: idp .checkCustomProviderEnabled() .toString(), VITE_APP_CUSTOM_PROVIDER_NAME: idp.getCustomProviderName(), }; return { ...defaultProps, ...oAuthProps }; })(); new NodejsBuild(this, "ReactBuild", { assets: [ { path: "../frontend", exclude: ["node_modules", "dist"], commands: ["npm ci"], }, ], buildCommands: ["npm run build"], buildEnvironment: buildEnvProps, destinationBucket: this.assetBucket, distribution: this.cloudFrontWebDistribution, outputSourceDirectory: "dist", }); if (idp.isExist()) { new CfnOutput(this, "CognitoDomain", { value: cognitoDomain }); new CfnOutput(this, "SocialProviders", { value: idp.getSocialProviders(), }); } } /** * CloudFront does not support access log delivery in the following regions * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#access-logs-choosing-s3-bucket */ private shouldSkipAccessLogging(): boolean { const skipLoggingRegions = [ "af-south-1", "ap-east-1", "ap-south-2", "ap-southeast-3", "ap-southeast-4", "ca-west-1", "eu-south-1", "eu-south-2", "eu-central-2", "il-central-1", "me-central-1", ]; return skipLoggingRegions.includes(Stack.of(this).region); } }