@@ -11,6 +11,9 @@ import GuestConnection from './sockets/GuestConnection.js';
1111import AuthedConnection from './sockets/AuthedConnection.js' ;
1212import LostConnection from './sockets/LostConnection.js' ;
1313import { serializeUser } from './utils/serialize.js' ;
14+ import { ulid , encodeTime } from 'ulid' ;
15+ import { jsonb } from './utils/sqlite.js' ;
16+ import { subMinutes } from 'date-fns' ;
1417
1518const { isEmpty } = lodash ;
1619
@@ -472,7 +475,7 @@ class SocketServer {
472475 . selectAll ( )
473476 . execute ( ) ;
474477 disconnectedUsers . forEach ( ( user ) => {
475- this . add ( this . createLostConnection ( user , 'TODO: Actual session ID!!' ) ) ;
478+ this . add ( this . createLostConnection ( user , 'TODO: Actual session ID!!' , null ) ) ;
476479 } ) ;
477480 }
478481
@@ -536,15 +539,15 @@ class SocketServer {
536539 connection . on ( 'close' , ( ) => {
537540 this . remove ( connection ) ;
538541 } ) ;
539- connection . on ( 'authenticate' , async ( user , sessionID ) => {
542+ connection . on ( 'authenticate' , async ( user , sessionID , lastEventID ) => {
540543 const isReconnect = await connection . isReconnect ( sessionID ) ;
541- this . #logger. info ( { userId : user . id , isReconnect } , 'authenticated socket' ) ;
544+ this . #logger. info ( { userId : user . id , isReconnect, lastEventID } , 'authenticated socket' ) ;
542545 if ( isReconnect ) {
543546 const previousConnection = this . getLostConnection ( sessionID ) ;
544547 if ( previousConnection ) this . remove ( previousConnection ) ;
545548 }
546549
547- this . replace ( connection , this . createAuthedConnection ( socket , user , sessionID ) ) ;
550+ this . replace ( connection , this . createAuthedConnection ( socket , user , sessionID , lastEventID ) ) ;
548551
549552 if ( ! isReconnect ) {
550553 this . #uw. publish ( 'user:join' , { userID : user . id } ) ;
@@ -559,18 +562,19 @@ class SocketServer {
559562 * @param {import('ws').WebSocket } socket
560563 * @param {User } user
561564 * @param {string } sessionID
565+ * @param {string|null } lastEventID
562566 * @returns {AuthedConnection }
563567 * @private
564568 */
565- createAuthedConnection ( socket , user , sessionID ) {
566- const connection = new AuthedConnection ( this . #uw, socket , user , sessionID ) ;
567- connection . on ( 'close' , ( { banned } ) => {
569+ createAuthedConnection ( socket , user , sessionID , lastEventID ) {
570+ const connection = new AuthedConnection ( this . #uw, socket , user , sessionID , lastEventID ) ;
571+ connection . on ( 'close' , ( { banned, lastEventID } ) => {
568572 if ( banned ) {
569573 this . #logger. info ( { userId : user . id } , 'removing connection after ban' ) ;
570574 disconnectUser ( this . #uw, user . id ) ;
571575 } else if ( ! this . #closing) {
572576 this . #logger. info ( { userId : user . id } , 'lost connection' ) ;
573- this . add ( this . createLostConnection ( user , sessionID ) ) ;
577+ this . add ( this . createLostConnection ( user , sessionID , lastEventID ) ) ;
574578 }
575579 this . remove ( connection ) ;
576580 } ) ;
@@ -603,11 +607,18 @@ class SocketServer {
603607 *
604608 * @param {User } user
605609 * @param {string } sessionID
610+ * @param {string|null } lastEventID
606611 * @returns {LostConnection }
607612 * @private
608613 */
609- createLostConnection ( user , sessionID ) {
610- const connection = new LostConnection ( this . #uw, user , sessionID , this . options . timeout ) ;
614+ createLostConnection ( user , sessionID , lastEventID ) {
615+ const connection = new LostConnection (
616+ this . #uw,
617+ user ,
618+ sessionID ,
619+ lastEventID ,
620+ this . options . timeout ,
621+ ) ;
611622 connection . on ( 'close' , ( ) => {
612623 this . #logger. info ( { userId : user . id } , 'user left' ) ;
613624 this . remove ( connection ) ;
@@ -732,6 +743,40 @@ class SocketServer {
732743 this . #connections. forEach ( ( connection ) => {
733744 connection . ping ( ) ;
734745 } ) ;
746+
747+ this . #cleanupMessageQueue( ) . catch ( ( err ) => {
748+ this . #logger. error ( { err } , 'failed to clean up socket message queue' ) ;
749+ } ) ;
750+ }
751+
752+ async #cleanupMessageQueue( ) {
753+ const oldestID = encodeTime ( subMinutes ( new Date ( ) , 10 ) . getTime ( ) ) ;
754+
755+ await this . #uw. db . deleteFrom ( 'socketMessageQueue' )
756+ . where ( 'id' , '<' , oldestID )
757+ . execute ( ) ;
758+ }
759+
760+ /**
761+ * Broadcast a command to all connected clients.
762+ *
763+ * @param {string } command Command name.
764+ * @param {import('type-fest').JsonValue } data Command data.
765+ * @param {import('./schema.js').UserID | null } targetUserID
766+ */
767+ #recordMessage( command , data , targetUserID = null ) {
768+ const id = ulid ( ) ;
769+
770+ this . #uw. db . insertInto ( 'socketMessageQueue' )
771+ . values ( {
772+ id,
773+ command,
774+ data : jsonb ( data ) ,
775+ targetUserID,
776+ } )
777+ . execute ( ) ;
778+
779+ return id ;
735780 }
736781
737782 /**
@@ -741,7 +786,10 @@ class SocketServer {
741786 * @param {import('type-fest').JsonValue } data Command data.
742787 */
743788 broadcast ( command , data ) {
789+ const id = this . #recordMessage( command , data ) ;
790+
744791 this . #logger. trace ( {
792+ id,
745793 command,
746794 data,
747795 to : this . #connections. map ( ( connection ) => (
@@ -750,23 +798,24 @@ class SocketServer {
750798 } , 'broadcast' ) ;
751799
752800 this . #connections. forEach ( ( connection ) => {
753- connection . send ( command , data ) ;
801+ connection . send ( id , command , data ) ;
754802 } ) ;
755803 }
756804
757805 /**
758806 * Send a command to a single user.
759807 *
760- * @param {User|string } user User or user ID to send the command to.
808+ * @param {User|import('./schema.js').UserID } user User or user ID to send the command to.
761809 * @param {string } command Command name.
762810 * @param {import('type-fest').JsonValue } data Command data.
763811 */
764812 sendTo ( user , command , data ) {
765813 const userID = typeof user === 'object' ? user . id : user ;
814+ const id = this . #recordMessage( command , data , userID ) ;
766815
767816 this . #connections. forEach ( ( connection ) => {
768817 if ( 'user' in connection && connection . user . id === userID ) {
769- connection . send ( command , data ) ;
818+ connection . send ( id , command , data ) ;
770819 }
771820 } ) ;
772821 }
0 commit comments