@@ -428,11 +428,15 @@ export class GmodSixtyFour extends BaseSession {}
428428export class GmodPrerelease extends BaseSession { }
429429export class GmodDev extends BaseSession { }
430430
431+ const MAX_SESSIONS_PER_IP = 2 ;
432+
431433export class QueueDO extends DurableObject < Env > {
432434 activeSessions : Map < string , string > ; // sessionId → type
435+ sessionIPs : Map < string , string > ; // sessionId → IP
433436 waitingQueue : {
434437 ticketId : string ;
435438 sessionType : string ;
439+ ip : string ;
436440 resolve : ( value : string ) => void
437441 } [ ] ;
438442 resolvedTickets : Map < string , { sessionId : string ; sessionType : string } > ;
@@ -441,6 +445,7 @@ export class QueueDO extends DurableObject<Env> {
441445 constructor ( ctx : DurableObjectState , env : Env ) {
442446 super ( ctx , env ) ;
443447 this . activeSessions = new Map ( ) ;
448+ this . sessionIPs = new Map ( ) ;
444449 this . waitingQueue = [ ] ;
445450 this . resolvedTickets = new Map ( ) ;
446451 this . maxPerType = {
@@ -452,6 +457,14 @@ export class QueueDO extends DurableObject<Env> {
452457
453458 }
454459
460+ activeSessionCountForIP ( ip : string ) : number {
461+ let count = 0 ;
462+ for ( const [ sessionId , sessionIp ] of this . sessionIPs ) {
463+ if ( sessionIp === ip && this . activeSessions . has ( sessionId ) ) count ++ ;
464+ }
465+ return count ;
466+ }
467+
455468 activeCountForType ( type : string ) : number {
456469 let count = 0 ;
457470 for ( const t of this . activeSessions . values ( ) ) {
@@ -472,13 +485,26 @@ export class QueueDO extends DurableObject<Env> {
472485
473486 if ( url . pathname === "/api/request-session" ) {
474487 const sessionType = url . searchParams . get ( "type" ) || "public" ;
488+ const clientIP = request . headers . get ( "CF-Connecting-IP" ) || "unknown" ;
489+
490+ if ( clientIP !== "unknown" && this . activeSessionCountForIP ( clientIP ) >= MAX_SESSIONS_PER_IP ) {
491+ return new Response ( JSON . stringify ( {
492+ status : "IP_LIMIT" ,
493+ limit : MAX_SESSIONS_PER_IP ,
494+ } ) , {
495+ status : 429 ,
496+ headers : { "Content-Type" : "application/json" } ,
497+ } ) ;
498+ }
499+
475500 if ( this . hasCapacity ( sessionType ) ) {
476501 const sessionId = crypto . randomUUID ( ) ;
477502 this . activeSessions . set ( sessionId , sessionType ) ;
503+ this . sessionIPs . set ( sessionId , clientIP ) ;
478504 return new Response ( JSON . stringify ( { status : "READY" , sessionId } ) , { headers : { "Content-Type" : "application/json" } , } ) ;
479505 } else {
480506 const ticketId = crypto . randomUUID ( ) ;
481- this . waitingQueue . push ( { ticketId, sessionType, resolve : ( sessionId : string ) => {
507+ this . waitingQueue . push ( { ticketId, sessionType, ip : clientIP , resolve : ( sessionId : string ) => {
482508 this . resolvedTickets . set ( ticketId , { sessionId, sessionType } ) ;
483509 } } ) ;
484510 const position = this . waitingQueue . filter ( w => w . sessionType === sessionType ) . length ;
@@ -542,13 +568,15 @@ export class QueueDO extends DurableObject<Env> {
542568 const { sessionId } = await request . json < { sessionId : string } > ( ) ;
543569 const closedType = this . activeSessions . get ( sessionId ) ;
544570 this . activeSessions . delete ( sessionId ) ;
571+ this . sessionIPs . delete ( sessionId ) ;
545572
546573 if ( closedType ) {
547574 const idx = this . waitingQueue . findIndex ( w => w . sessionType === closedType ) ;
548575 if ( idx !== - 1 ) {
549576 const nextInLine = this . waitingQueue . splice ( idx , 1 ) [ 0 ] ;
550577 const newSessionId = crypto . randomUUID ( ) ;
551578 this . activeSessions . set ( newSessionId , nextInLine . sessionType ) ;
579+ this . sessionIPs . set ( newSessionId , nextInLine . ip ) ;
552580 nextInLine . resolve ( newSessionId ) ;
553581 }
554582 }
0 commit comments