33
44import { DynamoDBClient } from '@aws-sdk/client-dynamodb' ;
55import { DynamoDBDocumentClient , PutCommand , GetCommand , UpdateCommand , DeleteCommand , QueryCommand , QueryCommandOutput , BatchWriteCommand , QueryCommandInput } from '@aws-sdk/lib-dynamodb' ;
6- import { SQSClient , SendMessageCommand , SendMessageRequest } from "@aws-sdk/client-sqs" ;
6+ import { SQSClient , SendMessageCommand , SendMessageCommandOutput , SendMessageRequest } from "@aws-sdk/client-sqs" ;
77import { v4 as uuid } from 'uuid' ;
88import { gameinfo , GameFactory , GameBase , GameBaseSimultaneous , type APGamesInformation } from '@abstractplay/gameslib' ;
99import { SESClient , SendEmailCommand } from '@aws-sdk/client-ses' ;
@@ -170,6 +170,7 @@ type MeData = {
170170 challengesAccepted ?: FullChallenge [ ] ;
171171 standingChallenges ?: FullChallenge [ ] ;
172172 realStanding ?: StandingChallenge [ ] ;
173+ connections : number ;
173174}
174175
175176type Rating = {
@@ -405,6 +406,14 @@ type StandingChallengeRec = {
405406 standing : StandingChallenge [ ] ;
406407} ;
407408
409+ type WsMsgBody = {
410+ domainName : string ;
411+ stage : string ;
412+ verb : string ;
413+ payload ?: any ;
414+ exclude ?: string [ ] ;
415+ } ;
416+
408417const sleep = ( ms : number ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
409418
410419async function sendCommandWithRetry ( command : any , maxRetries = 8 , initialDelay = 100 , maxDelay = 5000 ) {
@@ -527,7 +536,7 @@ async function verifyAndCorrectRatingsCountWithData(metaGame: string, actualRati
527536
528537function setsEqual ( a : Set < any > , b : Set < any > ) : boolean {
529538 if ( a . size !== b . size ) return false ;
530- for ( let item of a ) {
539+ for ( const item of a ) {
531540 if ( ! b . has ( item ) ) return false ;
532541 }
533542 return true ;
@@ -1767,6 +1776,9 @@ async function me(claim: PartialClaims, pars: { size: string, vars: string, upda
17671776 UpdateExpression : "set lastSeen = :dt"
17681777 } ) ) ;
17691778
1779+ // Get number of current connections
1780+ const connections = await countByPk ( "wsConnections" ) ;
1781+
17701782 let data = null ;
17711783 console . log ( `Fetching challenges` ) ;
17721784 let tagData , paletteData , standingData ;
@@ -1832,6 +1844,7 @@ async function me(claim: PartialClaims, pars: { size: string, vars: string, upda
18321844 "challengesAccepted" : ( data [ 2 ] as any [ ] ) . map ( d => d . Item ) ,
18331845 "standingChallenges" : ( data [ 3 ] as any [ ] ) . map ( d => d . Item ) ,
18341846 "realStanding" : realStanding ,
1847+ "connections" : connections ,
18351848 } as MeData , Set_toJSON ) ,
18361849 headers
18371850 } ;
@@ -1853,6 +1866,7 @@ async function me(claim: PartialClaims, pars: { size: string, vars: string, upda
18531866 tags,
18541867 palettes,
18551868 "realStanding" : realStanding ,
1869+ "connections" : connections ,
18561870 } as MeData , Set_toJSON ) ,
18571871 headers
18581872 }
@@ -3413,7 +3427,7 @@ async function submitMove(userid: string, pars: { id: string, move: string, draw
34133427 const simultaneous = flags !== undefined && flags . includes ( 'simultaneous' ) ;
34143428 const lastMoveTime = ( new Date ( engine . stack [ engine . stack . length - 1 ] . _timestamp ) ) . getTime ( ) ;
34153429 let autoMoves = 0 ;
3416- let list : Promise < any > [ ] = [ ] ;
3430+ const list : Promise < any > [ ] = [ ] ;
34173431
34183432 try {
34193433 if ( pars . move === "resign" ) {
@@ -3609,6 +3623,9 @@ async function submitMove(userid: string, pars: { id: string, move: string, draw
36093623 await realPingBot ( game . metaGame , game . id , game ) ;
36103624 await Promise . all ( list ) ;
36113625
3626+ // broadcasting that state has updated
3627+ await wsBroadcast ( "game" , { "meta" : pars . metaGame , "id" : pars . id } , [ userid ] ) ;
3628+
36123629 // TODO: Rehydrate state, run it through the stripper, and then replace with the new, stripped state
36133630 if ( game . gameEnded === undefined ) {
36143631 const engine = GameFactory ( game . metaGame , game . state ) ;
@@ -4344,7 +4361,7 @@ function applyMove(
43444361 }
43454362 }
43464363
4347- let work : Promise < any > [ ] = [ ] ;
4364+ const work : Promise < any > [ ] = [ ] ;
43484365 if ( ! engine . gameover ) {
43494366 if ( explorations [ 0 ] && explorations [ 0 ] . length > 0 ) {
43504367 // save back the updated exploration for player 0
@@ -4442,7 +4459,7 @@ async function updateLastChatForPlayers(
44424459 metaGame : string ,
44434460 players : { [ k : string ] : any ; id : string } [ ] ,
44444461 currentUserId : string ,
4445- allowReAdd : boolean = false // Only true for completed games via saveExploration
4462+ allowReAdd = false // Only true for completed games via saveExploration
44464463) {
44474464 console . log ( `Updating lastChat for all players of game ${ gameId } ` ) ;
44484465
@@ -4630,6 +4647,11 @@ async function submitComment(userid: string, pars: { id: string; players?: {[k:
46304647 false // Don't re-add game to list for in-game comments
46314648 ) ;
46324649 }
4650+
4651+ if ( pars . metaGame ) {
4652+ // broadcasting that state has updated
4653+ await wsBroadcast ( "game" , { "meta" : pars . metaGame , "id" : pars . id } , [ userid ] ) ;
4654+ }
46334655}
46344656
46354657async function saveExploration ( userid : string , pars : { public : boolean , game : string ; metaGame : string ; move : number ; version : number ; tree : Exploration ; updateCommentedFlag ?: number ; gameEnded ?: number ; updateLastChat ?: boolean ; players ?: { [ k : string ] : any ; id : string } [ ] ; } ) {
@@ -7628,7 +7650,7 @@ async function sendPush(opts: PushOptions) {
76287650 }
76297651
76307652 if ( subscription !== undefined ) {
7631- // treat both 410 and 404 as permanent. There are a LOT of 404 web push errors in the logs and apparently these are because since June 2024 the Chrome/FCM push service has changed the way it reports stale or deleted web‑push registrations.
7653+ // treat both 410 and 404 as permanent. There are a LOT of 404 web push errors in the logs and apparently these are because since June 2024 the Chrome/FCM push service has changed the way it reports stale or deleted web‑push registrations.
76327654 const PERMANENT_FAILURES = new Set ( [ 404 , 410 ] ) ;
76337655
76347656 try {
@@ -8687,3 +8709,48 @@ const getAllUsers = async (): Promise<FullUser[]> => {
86878709 return result
86888710}
86898711
8712+ /**
8713+ * Count all items for a given partition key, paginating until complete.
8714+ */
8715+ async function countByPk (
8716+ pkValue : string
8717+ ) : Promise < number > {
8718+ let total = 0 ;
8719+ let lastKey : Record < string , any > | undefined = undefined ;
8720+
8721+ do {
8722+ const params : QueryCommandInput = {
8723+ TableName : process . env . ABSTRACT_PLAY_TABLE ,
8724+ KeyConditionExpression : `pk = :pk` ,
8725+ ExpressionAttributeValues : {
8726+ ":pk" : { S : pkValue } ,
8727+ } ,
8728+ Select : "COUNT" ,
8729+ ExclusiveStartKey : lastKey ,
8730+ } ;
8731+
8732+ const result = await ddbDocClient . send ( new QueryCommand ( params ) ) ;
8733+
8734+ total += result . Count ?? 0 ;
8735+ lastKey = result . LastEvaluatedKey ;
8736+ } while ( lastKey ) ;
8737+
8738+ return total ;
8739+ }
8740+
8741+ async function wsBroadcast ( verb : string , payload : any , exclude ?: string [ ] ) : Promise < SendMessageCommandOutput > {
8742+ // construct message
8743+ const body : WsMsgBody = {
8744+ domainName : process . env . WEBSOCKET_DOMAIN ! ,
8745+ stage : process . env . WEBSOCKET_STAGE ! ,
8746+ verb,
8747+ payload,
8748+ exclude,
8749+ }
8750+ const input : SendMessageRequest = {
8751+ QueueUrl : process . env . WEBSOCKET_SQS ,
8752+ MessageBody : JSON . stringify ( body ) ,
8753+ }
8754+ const cmd = new SendMessageCommand ( input ) ;
8755+ return sqsClient . send ( cmd ) ;
8756+ }
0 commit comments