import { Replica } from 'dog' ; import type { Bindings } from './types' ; import type { Socket , Gossip } from 'dog' ; type Message = { uid : string ; type : string ; } ; type MessageData = { type : 'req:connected' ; } | { type : 'req:user:list' ; } | { type : 'msg' ; text : string ; } ; type Output = { type : string ; from ? : string ; time : number ; } ; type Note = Gossip . Message & { type : 'intra:user:list' ; } ; export class Room extends Replica < Bindings > { users = new Map < string , number > ( ) ; link ( env ) { return { parent : env . LOBBY , self : env . ROOM , } ; } async receive ( req ) { console . log ( '[ HELLO ][receive] req.url' , req . url ) ; let { pathname , searchParams } = new URL ( req . url ) ; if ( pathname === '/ws' ) { return this . connect ( req ) ; } if ( pathname === '/broadcast' ) { await this . broadcast ( { type : 'user:msg' , from : 'HTTP' , text : 'SENT VIA "/broadcast" HTTP route' , time : Date . now ( ) , } ) ; return new Response ( 'DONE' ) ; } if ( pathname === '/whisper' ) { let target = searchParams . get ( 'target' ) || 'aaa' ; await this . whisper ( target , { type : 'user:msg' , from : 'HTTP' , text : 'SENT VIA "/whisper" HTTP route' , time : Date . now ( ) , meta : 'whisper' , to : target , } ) ; return new Response ( 'DONE' ) ; } if ( pathname === '/emit' ) { this . emit ( { type : 'user:msg' , from : 'HTTP' , text : 'SENT VIA "/emit" HTTP route' , time : Date . now ( ) , meta : 'group' } ) ; return new Response ( 'DONE' ) ; } return new Response ( `PATH: "${ pathname }"` ) ; } onopen ( socket ) { console . log ( '[ HELLO ][onopen]' , socket . uid ) ; let output = { type : 'user:join' , from : socket . uid , time : Date . now ( ) , } ; socket . broadcast ( output , true ) ; this . users . set ( socket . uid , output . time ) ; } onclose ( socket ) { console . log ( '[ HELLO ][onclose]' ) ; let output = { type : 'user:exit' , from : socket . uid , time : Date . now ( ) , } ; socket . broadcast ( output ) ; this . users . delete ( socket . uid ) ; } async ongossip ( msg ) { if ( msg . type === 'intra:user:list' ) { return [ ... this . users . keys ( ) ] ; } throw new Error ( `Missing: "${ msg . type }" ongossip` ) ; } async onmessage ( socket , data ) { let input = JSON . parse ( data ) as Message & MessageData ; console . log ( '[room] onmessage' , input ) ; input . uid = input . uid || socket . uid ; if ( input . type === 'req:connected' ) { let output = { type : 'user:connected' , from : input . uid , time : Date . now ( ) , } ; this . users . set ( input . uid , output . time ) ; return socket . broadcast ( JSON . stringify ( output ) , true ) ; } if ( input . type === 'req:user:list' ) { let results = await this . gossip < Note > ( { type : 'intra:user:list' } ) as string [ ] [ ] ; let list = new Set < string > ( results . flat ( ) ) ; for ( let [ user ] of this . users ) list . add ( user ) ; let output = { type : 'user:list' , list : [ ... list ] , time : Date . now ( ) , } ; return socket . send ( JSON . stringify ( output ) ) ; } if ( input . type === 'msg' ) { let text = input . text . trim ( ) ; let output = { type : 'user:msg' , from : socket . uid , text : text , time : Date . now ( ) , } ; let match ; if ( match = /^([/](?:g|group)\s+)/ . exec ( text ) ) { output . meta = 'group' ; output . text = text . substring ( match [ 0 ] . length ) ; return socket . emit ( output , true ) ; } if ( match = /^([/](?:w|msg)\s+(?<target>[^\s]+))\s+/ . exec ( text ) ) { let target = match . groups ! . target ; output . text = text . substring ( match [ 0 ] . length ) ; output . meta = 'whisper' ; output . to = target ; await socket . whisper ( target , output ) ; return socket . send ( JSON . stringify ( output ) ) ; } if ( match = /^([/](?:a|all)\s+)/ . exec ( text ) ) { output . text = text . substring ( match [ 0 ] . length ) ; return socket . broadcast ( output , true ) ; } return socket . broadcast ( output , true ) ; } socket . broadcast ( input , true ) ; } }