2022-02-24 02:30:12 +01:00
import Connection from "./Connection" ;
2022-03-12 14:15:30 +01:00
import { hashCode } from "./utils" ;
2022-02-24 02:30:12 +01:00
2022-03-11 21:17:12 +01:00
/ * *
* The connection manager keeps track of active connections ( WebSocket connections , see Connection ) .
*
* Its refresh ( ) method reconciles state changes with the target state by closing / opening connections
* as required . This is done pretty much exactly the same way as in the Android app .
* /
2022-02-26 05:25:04 +01:00
class ConnectionManager {
2022-02-24 02:30:12 +01:00
constructor ( ) {
2022-03-04 02:07:35 +01:00
this . connections = new Map ( ) ; // ConnectionId -> Connection (hash, see below)
2022-03-04 17:08:32 +01:00
this . stateListener = null ; // Fired when connection state changes
2023-01-12 03:38:10 +01:00
this . messageListener = null ; // Fired when new notifications arrive
2022-02-24 02:30:12 +01:00
}
2022-03-04 17:08:32 +01:00
registerStateListener ( listener ) {
this . stateListener = listener ;
}
resetStateListener ( ) {
this . stateListener = null ;
}
2023-01-12 03:38:10 +01:00
registerMessageListener ( listener ) {
this . messageListener = listener ;
2022-03-04 17:08:32 +01:00
}
2023-01-12 03:38:10 +01:00
resetMessageListener ( ) {
this . messageListener = null ;
2022-03-04 17:08:32 +01:00
}
/ * *
* This function figures out which websocket connections should be running by comparing the
* current state of the world ( connections ) with the target state ( targetIds ) .
*
* It uses a "connectionId" , which is sha256 ( $subscriptionId | $username | $password ) to identify
* connections . If any of them change , the connection is closed / replaced .
* /
async refresh ( subscriptions , users ) {
2022-03-02 03:23:12 +01:00
if ( ! subscriptions || ! users ) {
return ;
}
2022-02-24 02:30:12 +01:00
console . log ( ` [ConnectionManager] Refreshing connections ` ) ;
2022-03-04 02:07:35 +01:00
const subscriptionsWithUsersAndConnectionId = await Promise . all ( subscriptions
. map ( async s => {
const [ user ] = users . filter ( u => u . baseUrl === s . baseUrl ) ;
const connectionId = await makeConnectionId ( s , user ) ;
return { ... s , user , connectionId } ;
} ) ) ;
2022-03-04 17:08:32 +01:00
const targetIds = subscriptionsWithUsersAndConnectionId . map ( s => s . connectionId ) ;
const deletedIds = Array . from ( this . connections . keys ( ) ) . filter ( id => ! targetIds . includes ( id ) ) ;
2022-02-24 02:30:12 +01:00
// Create and add new connections
2022-03-04 02:07:35 +01:00
subscriptionsWithUsersAndConnectionId . forEach ( subscription => {
const subscriptionId = subscription . id ;
const connectionId = subscription . connectionId ;
const added = ! this . connections . get ( connectionId )
2022-02-24 02:30:12 +01:00
if ( added ) {
2022-02-24 15:52:49 +01:00
const baseUrl = subscription . baseUrl ;
const topic = subscription . topic ;
2022-03-04 02:07:35 +01:00
const user = subscription . user ;
2022-02-28 01:29:17 +01:00
const since = subscription . last ;
2022-03-04 17:08:32 +01:00
const connection = new Connection (
connectionId ,
subscriptionId ,
baseUrl ,
topic ,
user ,
since ,
( subscriptionId , notification ) => this . notificationReceived ( subscriptionId , notification ) ,
( subscriptionId , state ) => this . stateChanged ( subscriptionId , state )
) ;
2022-03-04 02:07:35 +01:00
this . connections . set ( connectionId , connection ) ;
console . log ( ` [ConnectionManager] Starting new connection ${ connectionId } (subscription ${ subscriptionId } with user ${ user ? user . username : "anonymous" } ) ` ) ;
2022-02-24 02:30:12 +01:00
connection . start ( ) ;
}
} ) ;
// Delete old connections
deletedIds . forEach ( id => {
console . log ( ` [ConnectionManager] Closing connection ${ id } ` ) ;
const connection = this . connections . get ( id ) ;
this . connections . delete ( id ) ;
2022-02-24 15:52:49 +01:00
connection . close ( ) ;
2022-02-24 02:30:12 +01:00
} ) ;
}
2022-03-04 17:08:32 +01:00
stateChanged ( subscriptionId , state ) {
if ( this . stateListener ) {
2022-03-07 03:39:20 +01:00
try {
this . stateListener ( subscriptionId , state ) ;
} catch ( e ) {
console . error ( ` [ConnectionManager] Error updating state of ${ subscriptionId } to ${ state } ` , e ) ;
}
2022-03-04 17:08:32 +01:00
}
}
notificationReceived ( subscriptionId , notification ) {
2023-01-12 03:38:10 +01:00
if ( this . messageListener ) {
2022-03-07 03:39:20 +01:00
try {
2023-01-12 03:38:10 +01:00
this . messageListener ( subscriptionId , notification ) ;
2022-03-07 03:39:20 +01:00
} catch ( e ) {
console . error ( ` [ConnectionManager] Error handling notification for ${ subscriptionId } ` , e ) ;
}
2022-03-04 17:08:32 +01:00
}
}
2022-02-24 02:30:12 +01:00
}
2022-03-04 02:07:35 +01:00
const makeConnectionId = async ( subscription , user ) => {
2022-03-12 14:15:30 +01:00
return ( user )
2022-12-27 03:27:07 +01:00
? hashCode ( ` ${ subscription . id } | ${ user . username } | ${ user . password ? ? "" } | ${ user . token ? ? "" } ` )
2022-03-12 14:15:30 +01:00
: hashCode ( ` ${ subscription . id } ` ) ;
2022-03-04 02:07:35 +01:00
}
2022-02-24 02:30:12 +01:00
const connectionManager = new ConnectionManager ( ) ;
export default connectionManager ;