// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DocumentModel; using Amazon.DynamoDBv2.Model; using Amazon.Lambda.Core; using ManagementConsoleBackend.Common; using ManagementConsoleBackend.ManagementService.Data; using ManagementConsoleBackend.ManagementService.Lib; using Newtonsoft.Json; namespace ManagementConsoleBackend.ManagementService { public class EventHandlers { /// Handles custom EventBridge state events generated by the Step Functions poller public async Task StateEventHandler(Stream stream, ILambdaContext context) { var eventStr = ReadEventStream(stream); LambdaLogger.Log(eventStr); var stateEvent = JsonConvert.DeserializeObject(eventStr); LambdaLogger.Log(JsonConvert.SerializeObject(stateEvent, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); var serverMessage = new ServerMessageGetState { State = stateEvent?.Detail, IsDb = false, }; await ManagementService.SendToActiveConnections(serverMessage); } /// Handles GameLift Queue placement events and stores them in DDB public async Task QueuePlacementEventHandler(Stream stream, ILambdaContext context) { var dynamoDbClient = new AmazonDynamoDBClient(); var eventStr = ReadEventStream(stream); LambdaLogger.Log(eventStr); var queuePlacementEvent = JsonConvert.DeserializeObject(eventStr); LambdaLogger.Log(JsonConvert.SerializeObject(queuePlacementEvent, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); var eventLogTable = Table.LoadTable(dynamoDbClient, Environment.GetEnvironmentVariable("EventLogTableName")); LambdaLogger.Log(queuePlacementEvent.Time.DateTime.ToString("yyyy-MM-ddTHH:mm:ss")); try { var item = Document.FromJson(eventStr); item["date"] = queuePlacementEvent.Time.DateTime.ToString("yyyy-MM-dd"); item["time-id"] = queuePlacementEvent.Time.DateTime.ToString("yyyy-MM-ddTHH:mm:ss") + "-" + queuePlacementEvent.Id; item["placementId"] = queuePlacementEvent.Detail.PlacementId; if (queuePlacementEvent.Resources.Length > 0) { item["primaryResource"] = queuePlacementEvent.Resources[0]; } item["TimeToLive"] = (Utils.GetUnixTimestamp() + (86400 * 7)); await eventLogTable.PutItemAsync(item); } catch (Exception e) { LambdaLogger.Log(e.Message); } await this.HandleQueueEvent(queuePlacementEvent); } private async Task HandleQueueEvent(QueuePlacementEvent queuePlacementEvent) { // TODO - improve batch up event handling await ManagementService.SendToActiveConnections(new ServerMessageQueuePlacementEvent { QueuePlacementEventDetail = queuePlacementEvent.Detail, Resources = queuePlacementEvent.Resources }); return true; } /// Handles FlexMatch events and stores them in DDB public async Task FlexMatchEventHandler(Stream stream, ILambdaContext context) { var dynamoDbClient = new AmazonDynamoDBClient(); var eventStr = ReadEventStream(stream); var dynamoDbRequestHandler = new DynamoDbRequestHandler(dynamoDbClient); var configDocument = await dynamoDbRequestHandler.GetManagementConfig("mainConfig"); LambdaLogger.Log(eventStr); var flexMatchEvent = JsonConvert.DeserializeObject(eventStr); LambdaLogger.Log(JsonConvert.SerializeObject(flexMatchEvent, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); await StoreFlexMatchEvent(eventStr, flexMatchEvent); // handle FlexMatch Simulator events if (flexMatchEvent.Resources[0] == configDocument.FlexMatchSimulatorArn) { var flexMatchSimulator = new FlexMatchSimulator(); LambdaLogger.Log("RECEIVED FLEXMATCH SIMULATOR EVENT!"); await flexMatchSimulator.HandleSimulatorFlexMatchEvent(flexMatchEvent); LambdaLogger.Log("FINISHED PROCESSING FLEXMATCH SIMULATOR EVENT!"); } else { LambdaLogger.Log("RECEIVED FLEXMATCH EVENT!"); await HandleFlexMatchEvent(flexMatchEvent); LambdaLogger.Log("FINISHED PROCESSING FLEXMATCH EVENT!"); } } private async Task HandleFlexMatchEvent(FlexMatchEvent flexMatchEvent) { // TODO - improve batch up event handling await ManagementService.SendToActiveConnections(new ServerMessageFlexMatchEvent { FlexMatchEventDetail = flexMatchEvent.Detail, Resources = flexMatchEvent.Resources }); return true; } private async Task StoreFlexMatchEvent(string eventStr, FlexMatchEvent flexMatchEvent) { var dynamoDbClient = new AmazonDynamoDBClient(); var timeToLive = (Utils.GetUnixTimestamp() + (86400 * 7)); var ticketIds = new List(); foreach (var ticket in flexMatchEvent.Detail.Tickets) { ticketIds.Add(ticket.TicketId); // add event id to ticket log table var updateRequest = new UpdateItemRequest { TableName = Environment.GetEnvironmentVariable("TicketLogTableName"), Key = new Dictionary() {{"TicketId", new AttributeValue {S = ticket.TicketId}}}, UpdateExpression = "ADD #events :eventId SET #time = :startTime, #matchmakingConfigArn = :matchmakingConfigArn, #timeToLive = :timeToLive", ExpressionAttributeNames = new Dictionary { {"#events", "events"}, {"#time", "time"}, {"#matchmakingConfigArn", "matchmakingConfigArn"}, {"#timeToLive", "TimeToLive"}, }, ExpressionAttributeValues = new Dictionary() { {":eventId",new AttributeValue { SS = {flexMatchEvent.Id.ToString()}}}, {":startTime",new AttributeValue { S = ticket.StartTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") }}, {":matchmakingConfigArn",new AttributeValue { S = flexMatchEvent.Resources[0] }}, {":timeToLive", new AttributeValue { N = timeToLive.ToString() }}, }, }; switch (flexMatchEvent.Detail.Type) { case "MatchmakingTimedOut": case "MatchmakingFailed": case "MatchmakingCancelled": case "MatchmakingSucceeded": updateRequest.UpdateExpression = "ADD #events :eventId SET #time = :startTime, #lastEventType = :eventType, #matchmakingConfigArn = :matchmakingConfigArn, #timeToLive = :timeToLive"; updateRequest.ExpressionAttributeNames.Add("#lastEventType", "lastEventType"); updateRequest.ExpressionAttributeValues.Add(":eventType", new AttributeValue { S = flexMatchEvent.Detail.Type }); // add matchId if set if (flexMatchEvent.Detail.MatchId != null) { updateRequest.UpdateExpression += ", #matchId = :matchId"; updateRequest.ExpressionAttributeNames.Add("#matchId", "matchId"); updateRequest.ExpressionAttributeValues.Add(":matchId",new AttributeValue { S = flexMatchEvent.Detail.MatchId }); } break; } if (!String.IsNullOrEmpty(flexMatchEvent.Detail.CustomEventData)) { updateRequest.UpdateExpression += ", #customEventData = :customEventData"; updateRequest.ExpressionAttributeNames.Add("#customEventData", "customEventData"); updateRequest.ExpressionAttributeValues.Add(":customEventData", new AttributeValue { S = flexMatchEvent.Detail.CustomEventData }); } try { await dynamoDbClient.UpdateItemAsync(updateRequest); } catch (Exception e) { LambdaLogger.Log(e.Message); } } // add match to matchlog table if (flexMatchEvent.Detail.Type == "PotentialMatchCreated" && flexMatchEvent.Detail.MatchId!=null) { var matchLogTable = Table.LoadTable(dynamoDbClient, Environment.GetEnvironmentVariable("MatchLogTableName")); try { var item = new Document(); item["MatchId"] = flexMatchEvent.Detail.MatchId; item["TicketIds"] = ticketIds; if (flexMatchEvent.Resources.Length > 0) { item["primaryResource"] = flexMatchEvent.Resources[0]; } item["TimeToLive"] = timeToLive; LambdaLogger.Log("SAVING:" + JsonConvert.SerializeObject(item)); await matchLogTable.PutItemAsync(item); LambdaLogger.Log("SAVED!"); } catch (Exception e) { LambdaLogger.Log(e.Message); } } // add event to eventlog table var eventLogTable = Table.LoadTable(dynamoDbClient, Environment.GetEnvironmentVariable("EventLogTableName")); try { var item = Document.FromJson(eventStr); if (flexMatchEvent.Resources.Length > 0) { item["primaryResource"] = flexMatchEvent.Resources[0]; } item["date"] = flexMatchEvent.Time.DateTime.ToString("yyyy-MM-dd"); item["time-id"] = flexMatchEvent.Time.DateTime.ToString("s")+"Z" + "-" + flexMatchEvent.Id; item["TimeToLive"] = timeToLive; LambdaLogger.Log("SAVING:" + JsonConvert.SerializeObject(item)); await eventLogTable.PutItemAsync(item); LambdaLogger.Log("SAVED!"); } catch (Exception e) { LambdaLogger.Log(e.Message); } return true; } private string ReadEventStream(Stream stream) { string eventStr; using (StreamReader reader = new StreamReader(stream)) { eventStr = reader.ReadToEnd(); } return eventStr; } } }