AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: Amazon Chime SDK Meeting Demo Parameters: UseEventBridge: Description: Use EventBridge to process server side notifications Default: false Type: String AllowedValues: [true, false] Region: Description: The AWS SDK Chime REGION Default: "us-east-1" Type: String UseChimeSDKMeetings: Description: Use boolean to switch between Chime and ChimeSDKMeetings Client Default: true Type: String AllowedValues: [true, false] ChimeEndpoint: Description: The AWS SDK Chime endpoint Default: "https://service.chime.aws.amazon.com" Type: String ChimeServicePrincipal: Description: "The principal that will capture to the S3 bucket" Default: "chime.amazonaws.com" Type: String ChimeSDKMeetingsEndpoint: Description: The AWS Chime SDK Meetings endpoint Default: "https://service.chime.aws.amazon.com" Type: String ChimeMediaCaptureS3BucketPrefix: Description: Prefix of S3 bucket to write capture artifacts. The bucket name will be suffixed with the region. Default: "" Type: String UseFetchCredentialLambda: Description: Set to true to deploy a lambda to return AWS credentials with limited privileges. This should be disabled in most demo. Default: false Type: String AllowedValues: [ true, false ] MediaPipelinesControlRegion: Description: The AWS SDK Chime Media Pipelines control REGION Default: "us-east-1" Type: String ChimeSDKMediaPipelinesEndpoint: Description: The AWS Chime SDK Media Pipelines endpoint Default: "https://media-pipelines-chime.us-east-1.amazonaws.com" Type: String UseChimeSDKMediaPipelines: Description: Use boolean to switch between Chime and ChimeSDKMediaPipelines Client Default: true Type: String AllowedValues: [ true, false ] Conditions: ShouldUseEventBridge: !Equals [true, !Ref UseEventBridge] ShouldDeployFetchCredentialLambda: !Equals [true, !Ref UseFetchCredentialLambda] Globals: Function: Runtime: nodejs14.x Timeout: 30 MemorySize: 128 Environment: Variables: MEETINGS_TABLE_NAME: !Ref Meetings SQS_QUEUE_ARN: !GetAtt MeetingNotificationsQueue.Arn BROWSER_LOG_GROUP_NAME: !Ref ChimeBrowserLogs BROWSER_MEETING_EVENT_LOG_GROUP_NAME: !Ref ChimeBrowserMeetingEventLogs CHIME_ENDPOINT: !Ref ChimeEndpoint REGION: !Ref Region USE_CHIME_SDK_MEETINGS: !Ref UseChimeSDKMeetings CHIME_SDK_MEETINGS_ENDPOINT: !Ref ChimeSDKMeetingsEndpoint MEDIA_PIPELINES_CONTROL_REGION: !Ref MediaPipelinesControlRegion USE_CHIME_SDK_MEDIA_PIPELINES: !Ref UseChimeSDKMediaPipelines CHIME_SDK_MEDIA_PIPELINES_ENDPOINT: !Ref ChimeSDKMediaPipelinesEndpoint BROWSER_EVENT_INGESTION_LOG_GROUP_NAME: !Ref ChimeBrowserEventIngestionLogs CAPTURE_S3_DESTINATION_PREFIX: !Ref ChimeMediaCaptureS3BucketPrefix AWS_ACCOUNT_ID: !Sub "${AWS::AccountId}" Resources: ChimeMeetingsAccessPolicy: Type: AWS::IAM::Policy Properties: PolicyName: ChimeMeetingsAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - 'chime:CreateMeeting' - 'chime:TagMeeting' - 'chime:TagResource' - 'chime:DeleteMeeting' - 'chime:GetMeeting' - 'chime:ListMeetings' - 'chime:BatchCreateAttendee' - 'chime:CreateAttendee' - 'chime:DeleteAttendee' - 'chime:GetAttendee' - 'chime:ListAttendees' - 'chime:StartMeetingTranscription' - 'chime:StopMeetingTranscription' - 'chime:CreateMediaCapturePipeline' - 'chime:DeleteMediaCapturePipeline' - 'chime:CreateMediaLiveConnectorPipeline' - 'chime:DeleteMediaPipeline' - 'ivs:CreateChannel' - 'ivs:DeleteChannel' - 's3:GetBucketPolicy' - 's3:GetBucketLocation' Resource: '*' Roles: - Ref: ChimeSdkJoinLambdaRole - Ref: ChimeSdkEndLambdaRole - Ref: ChimeSdkDeleteAttendeeLambdaRole - Ref: ChimeSdkStartTranscriptionLambdaRole - Ref: ChimeSdkStopTranscriptionLambdaRole - Ref: ChimeSdkStartCaptureLambdaRole - Ref: ChimeSdkEndCaptureLambdaRole - Ref: ChimeSdkStartLiveConnectorLambdaRole - Ref: ChimeSdkEndLiveConnectorLambdaRole ChimeMessagingAccessPolicy: Type: AWS::IAM::Policy Condition: ShouldDeployFetchCredentialLambda Properties: PolicyName: ChimeMeetingsAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - 'chime:Connect' - 'chime:GetMessagingSessionEndpoint' Resource: '*' Roles: - Ref: ChimeSdkBrowserFetchCredentialsLambdaRole CloudWatchAccessPolicy: Type: AWS::IAM::Policy Properties: PolicyName: CloudWatchAccess PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - 'logs:CreateLogStream' - 'logs:PutLogEvents' - 'logs:DescribeLogStreams' Resource: '*' Roles: - Ref: ChimeSdkBrowserLogsLambdaRole - Ref: ChimeSdkBrowserMeetingEventLogsLambdaRole - Ref: ChimeSdkBrowserCreateLogStreamLambdaRole - Ref: ChimeSdkBrowserCreateBrowserEventLogStreamLambdaRole - Ref: ChimeSdkBrowserEventIngestionLogsLambdaRole - Ref: ChimeSdkBrowserEventIngestionLogStreamLambdaRole Meetings: DeletionPolicy: Delete UpdateReplacePolicy: Delete Type: AWS::DynamoDB::Table Properties: AttributeDefinitions: - AttributeName: "Title" AttributeType: "S" - AttributeName: "Passcode" AttributeType: "S" BillingMode: PAY_PER_REQUEST KeySchema: - AttributeName: "Title" KeyType: HASH GlobalSecondaryIndexes: - IndexName: "Passcode" KeySchema: - AttributeName: "Passcode" KeyType: HASH Projection: ProjectionType: ALL TimeToLiveSpecification: AttributeName: "TTL" Enabled: true ChimeKMSKey: Type: AWS::KMS::Key Properties: Description: Custom KMS Key with Chime access KeyPolicy: Version: '2012-10-17' Statement: - Sid: Allow access for Chime Service Effect: Allow Principal: Service: !Ref ChimeServicePrincipal Action: - kms:GenerateDataKey - kms:Decrypt Resource: '*' - Sid: Enable IAM User Permissions Effect: Allow Principal: AWS: !Sub arn:aws:iam::${AWS::AccountId}:root Action: kms:* Resource: '*' ChimeKMSAlias: Type: AWS::KMS::Alias Properties: AliasName: !Sub alias/ChimeKMS-${AWS::StackName} TargetKeyId: Ref: ChimeKMSKey MeetingNotificationsQueue: DeletionPolicy: Delete UpdateReplacePolicy: Delete Type: AWS::SQS::Queue Properties: KmsMasterKeyId: !Sub alias/ChimeKMS-${AWS::StackName} ChimeSdkIndexLambda: Type: 'AWS::Serverless::Function' Properties: Handler: handlers.index CodeUri: src/ Events: Api1: Type: Api Properties: Path: / Method: GET ChimeSdkAudioFileLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.audio_file CodeUri: src/ Events: Api1: Type: Api Properties: Path: /audio_file Method: GET ChimeSdkStereoAudioFileLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.stereo_audio_file CodeUri: src/ Events: Api1: Type: Api Properties: Path: /stereo_audio_file Method: GET ChimeSdkJoinLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.join CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Environment: Variables: USE_EVENT_BRIDGE: !Ref UseEventBridge Events: Api1: Type: Api Properties: Path: /join Method: POST ChimeSdkDeleteAttendeeLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.deleteAttendee CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /deleteAttendee Method: POST ChimeSdkEndLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.end CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /end Method: POST ChimeSdkStartCaptureLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.start_capture CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Environment: Variables: USE_EVENT_BRIDGE: !Ref UseEventBridge Events: Api1: Type: Api Properties: Path: /startCapture Method: POST ChimeSdkEndCaptureLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.end_capture CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /endCapture Method: POST ChimeSdkStartLiveConnectorLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.start_live_connector CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Environment: Variables: USE_EVENT_BRIDGE: !Ref UseEventBridge Events: Api1: Type: Api Properties: Path: /startLiveConnector Method: POST ChimeSdkEndLiveConnectorLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.end_live_connector CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /endLiveConnector Method: POST ChimeSQSQueueLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.sqs_handler CodeUri: src/ Events: MeetingNotificationsEvent: Type: SQS Properties: Queue: !GetAtt MeetingNotificationsQueue.Arn BatchSize: 10 Policies: - Statement: - Sid: ChimeSQSQueueLambdaPolicy Effect: Allow Action: - kms:Decrypt Resource: '*' ChimeEventBridgeLambda: Type: AWS::Serverless::Function Condition: ShouldUseEventBridge Properties: Handler: handlers.event_bridge_handler CodeUri: src/ Events: ChimeEventBridgeEvent: Type: CloudWatchEvent Properties: Pattern: source: - aws.chime detail-type: - "Chime Meeting State Change" ChimeSdkBrowserLogsLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.logs CodeUri: src/ Events: Api1: Type: Api Properties: Path: /logs Method: POST ChimeSdkBrowserMeetingEventLogsLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.log_meeting_event CodeUri: src/ Events: Api1: Type: Api Properties: Path: /log_meeting_event Method: POST ChimeSdkBrowserEventIngestionLogsLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.log_event_ingestion CodeUri: src/ Events: Api1: Type: Api Properties: Path: /log_event_ingestion Method: POST ChimeSdkBrowserEventIngestionLogStreamLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.create_browser_event_ingestion_log_stream CodeUri: src/ Events: Api1: Type: Api Properties: Path: /create_browser_event_ingestion_log_stream Method: POST ChimeSdkBrowserCreateLogStreamLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.create_log_stream CodeUri: src/ Events: Api1: Type: Api Properties: Path: /create_log_stream Method: POST ChimeSdkBrowserCreateBrowserEventLogStreamLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.create_browser_event_log_stream CodeUri: src/ Events: Api1: Type: Api Properties: Path: /create_browser_event_log_stream Method: POST ChimeSdkStartTranscriptionLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.start_transcription CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /start_transcription Method: POST ChimeSdkStopTranscriptionLambda: Type: AWS::Serverless::Function Properties: Handler: handlers.stop_transcription CodeUri: src/ Policies: - DynamoDBCrudPolicy: TableName: !Ref Meetings Events: Api1: Type: Api Properties: Path: /stop_transcription Method: POST ChimeSdkBrowserFetchCredentialsLambda: Type: AWS::Serverless::Function Condition: ShouldDeployFetchCredentialLambda Properties: Handler: handlers.fetch_credentials CodeUri: src/ Events: Api1: Type: Api Properties: Path: /fetch_credentials Method: GET ChimeNotificationsQueuePolicy: Type: AWS::SQS::QueuePolicy Properties: PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sqs:SendMessage - sqs:GetQueueUrl Principal: Service: - !Ref ChimeServicePrincipal Resource: !GetAtt MeetingNotificationsQueue.Arn Queues: - Ref: MeetingNotificationsQueue ChimeBrowserLogs: Type: AWS::Logs::LogGroup DeletionPolicy: Delete UpdateReplacePolicy: Delete ChimeBrowserMeetingEventLogs: Type: AWS::Logs::LogGroup DeletionPolicy: Delete UpdateReplacePolicy: Delete ChimeBrowserEventIngestionLogs: Type: AWS::Logs::LogGroup DeletionPolicy: Delete UpdateReplacePolicy: Delete ChimeSdkBrowserMeetingEventDashboard: Type: AWS::CloudWatch::Dashboard Properties: DashboardBody: !Sub - > { "widgets": [ { "type": "log", "x": 0, "y": 12, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingStartSucceeded\", \"meetingStartFailed\"]\n| stats count(*) as meetingJoin by name", "region": "us-east-1", "stacked": false, "title": "Meeting join success rate", "view": "pie" } }, { "type": "log", "x": 0, "y": 6, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | fields @timestamp, @message\n| filter name in [\"meetingStartRequested\"]\n| stats count(*) as startRequested by attributes.browserName as browser, attributes.browserMajorVersion as version\n| sort startRequested desc\n| limit 10", "region": "us-east-1", "stacked": false, "title": "Top 10 browsers", "view": "table" } }, { "type": "log", "x": 12, "y": 6, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | fields @timestamp, @message\n| filter name in [\"meetingStartRequested\"]\n| stats count(*) as startRequested by attributes.osName as operatingSystem\n| sort startRequested desc\n| limit 10", "region": "us-east-1", "stacked": false, "title": "Top 10 operating systems", "view": "table" } }, { "type": "log", "x": 0, "y": 30, "width": 24, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"audioInputFailed\", \"videoInputFailed\"]\n| fields\nfromMillis(@timestamp) as timestamp,\nconcat(attributes.osName, \" \", attributes.osVersion) as operatingSystem,\nconcat(attributes.browserName, \" \", attributes.browserMajorVersion) as browser,\nreplace(name, \"InputFailed\", \"\") as kind,\nconcat(attributes.audioInputErrorMessage, attributes.videoInputErrorMessage) as reason\n| sort @timestamp desc\n", "region": "us-east-1", "stacked": false, "title": "Audio and video input failures", "view": "table" } }, { "type": "log", "x": 0, "y": 18, "width": 24, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingStartFailed\"]\n| fields fromMillis(@timestamp) as timestamp,\nconcat(attributes.osName, \" \", attributes.osVersion) as operatingSystem,\nconcat(attributes.browserName, \" \", attributes.browserMajorVersion) as browser,\nattributes.meetingStatus as failedStatus,\nconcat(attributes.signalingOpenDurationMs / 1000, \"s\") as signalingOpenDurationMs,\nattributes.retryCount as retryCount\n| sort @timestamp desc\n", "region": "us-east-1", "stacked": false, "title": "Meeting join failures", "view": "table" } }, { "type": "log", "x": 0, "y": 24, "width": 24, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingFailed\"]\n| fields\nfromMillis(@timestamp) as timestamp,\nconcat(attributes.osName, \" \", attributes.osVersion) as operatingSystem,\nconcat(attributes.browserName, \" \", attributes.browserMajorVersion) as browser,\nattributes.meetingStatus as failedStatus,\nconcat(attributes.meetingDurationMs / 1000, \"s\") as meetingDurationMs,\nattributes.retryCount as retryCount,\nattributes.poorConnectionCount as poorConnectionCount\n| sort @timestamp desc\n", "region": "us-east-1", "stacked": false, "title": "Dropped attendees", "view": "table" } }, { "type": "log", "x": 12, "y": 0, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingStartRequested\"]\n| stats count(*) as startRequested by attributes.sdkName as SDK, attributes.sdkVersion as version", "region": "us-east-1", "stacked": false, "title": "SDK versions", "view": "table" } }, { "type": "log", "x": 0, "y": 0, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingStartRequested\"]\n| stats count(*) as platform by attributes.sdkName", "region": "us-east-1", "stacked": false, "title": "SDK platforms (JavaScript, iOS, and Android)", "view": "pie" } }, { "type": "text", "x": 0, "y": 42, "width": 24, "height": 12, "properties": { "markdown": "\n## How to search events for a specific attendee?\n\nThe Chime SDK serverless demo uses Amazon CloudWatch Logs Insights to search and analyze SDK events. You can view trends in the types of failures and identify where your attendees drop off.\n\n1. Click on the row number (▶) to expand a row.\n2. You can see detailed failure information.\n - **attributes.meetingErrorMessage** explains the reason for the meeting failure.\n - **attributes.audioInputErrorMessage** and **attributes.videoInputErrorMessage** indicate problems with the microphone and camera.\n - **attributes.meetingHistory** shows up to last 15 attendee actions and events.\n3. To view a specific attendee's events, take note of **attributes.attendeeId** and choose **Insights** in the navigation pane.\n4. Select your ChimeBrowserMeetingEventLogs log group that starts with your stack name.\n ```\n __your_stack_name__ChimeBrowserMeetingEventLogs-...\n ```\n5. In the query editor, delete the current contents, enter the following filter function, and then choose **Run query**.\n ```\n filter attributes.attendeeId = \"__your_attendee_id__\"\n ```\n\n The results show the number of SDK events from device selection to meeting end.\n\n You can use CloudWatch Logs Insights to count device input errors for platforms, calculate the average value of the signaling connection time, and visualize trends over time. For more information, see [Analyzing Log Data with CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html) in the *AWS CloudWatch Logs User Guide*.\n" } }, { "type": "log", "x": 0, "y": 36, "width": 24, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"signalingDropped\"]\n| fields fromMillis(@timestamp) as timestamp,\nconcat(attributes.osName, \" \", attributes.osVersion) as operatingSystem,\nconcat(attributes.browserName, \" \", attributes.browserMajorVersion) as browser,\nconcat(attributes.signalingOpenDurationMs / 1000, \"s\") as signalingOpenDurationMs,\nattributes.retryCount as retryCount\n| sort @timestamp desc\n", "region": "us-east-1", "stacked": false, "title": "WebSocket signaling failures", "view": "table" } }, { "type": "log", "x": 12, "y": 12, "width": 12, "height": 6, "properties": { "query": "SOURCE \"${Source}\" | filter name in [\"meetingStartSucceeded\"]\n| stats count(*) as meetingStart by bin(1d) as time\n| sort time asc", "region": "us-east-1", "stacked": false, "title": "Daily attendees who joined a meeting", "view": "bar" } } ] } - { Source: !Ref ChimeBrowserMeetingEventLogs } Outputs: ApiURL: Description: "API endpoint URL for Prod environment" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"