import { Duration } from 'aws-cdk-lib'; import * as path from 'path'; import { IFunction, Runtime } from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; import { AppSyncAuthProvider, AppSyncAuthorizationType, EventApi } from 'aws-cdk-lib/aws-appsync'; const __filename = import.meta.url.replace(/^file:/, ''); const __dirname = path.dirname(__filename); export class RealtimeService extends Construct { authHandler: IFunction; constructor(scope: Construct, id: string, props: { appId: string; branchId: string; publisher: NodejsFunction; bucket: string; // needed for `Secret`, which currently uses the common S3 bucket namespaces: string[]; }) { super(scope, id); let realtimeService: EventApi | undefined; const LAMBDA = AppSyncAuthorizationType.LAMBDA; const IAM = AppSyncAuthorizationType.IAM; this.authHandler = new NodejsFunction(this, 'realtime-auth', { runtime: Runtime.NODEJS_22_X, handler: 'handler', entry: path.join(__dirname, 'authorizer-lambda.ts'), timeout: Duration.seconds(30), environment: { // global storage bucket. TODO: remove? BUCKET: props.bucket, // TODO: refactor. set env variables ... better. // must match TABLE_NAME_PREFIX from outer scope. TABLE_NAME_PREFIX: `${props.appId}-${props.branchId}-`, // NOTE: These MUST equal those defined in RealtimeService resource. SECRET_SCOPE: 'wirejs-global', SECRET_ID: 'realtime-secret', } }); const lambdaProvider: AppSyncAuthProvider = { authorizationType: LAMBDA, lambdaAuthorizerConfig: { handler: this.authHandler } }; const iamProvider: AppSyncAuthProvider = { authorizationType: IAM }; realtimeService = new EventApi(this, 'realtime', { apiName: `${props.appId.slice(0, 18)}-${props.branchId.slice(0, 18)}-realtime-api`, authorizationConfig: { authProviders: [iamProvider, lambdaProvider], connectionAuthModeTypes: [LAMBDA], defaultSubscribeAuthModeTypes: [LAMBDA], defaultPublishAuthModeTypes: [IAM], }, }); // TODO: move this to backend.ts so all env vars are set in one place props.publisher.addEnvironment( 'REALTIME_WS_DOMAIN', realtimeService.realtimeDns, ); props.publisher.addEnvironment( 'REALTIME_HTTP_DOMAIN', realtimeService.httpDns, ); for (const namespace of props.namespaces) { const ns = realtimeService.addChannelNamespace(namespace); ns.grantPublish(props.publisher); } } }