2022-02-24 01:30:12 +00:00
import Connection from "./Connection" ;
2022-03-04 01:07:35 +00:00
import { sha256 } from "./utils" ;
2022-02-24 01:30:12 +00:00
2022-03-11 20:17:12 +00: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 04:25:04 +00:00
class ConnectionManager {
2022-02-24 01:30:12 +00:00
constructor ( ) {
2022-03-04 01:07:35 +00:00
this . connections = new Map ( ) ; // ConnectionId -> Connection (hash, see below)
2022-03-04 16:08:32 +00:00
this . stateListener = null ; // Fired when connection state changes
this . notificationListener = null ; // Fired when new notifications arrive
2022-02-24 01:30:12 +00:00
}
2022-03-04 16:08:32 +00:00
registerStateListener ( listener ) {
this . stateListener = listener ;
}
resetStateListener ( ) {
this . stateListener = null ;
}
registerNotificationListener ( listener ) {
this . notificationListener = listener ;
}
resetNotificationListener ( ) {
this . notificationListener = null ;
}
/ * *
* 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 02:23:12 +00:00
if ( ! subscriptions || ! users ) {
return ;
}
2022-02-24 01:30:12 +00:00
console . log ( ` [ConnectionManager] Refreshing connections ` ) ;
2022-03-04 01:07:35 +00: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 16:08:32 +00:00
const targetIds = subscriptionsWithUsersAndConnectionId . map ( s => s . connectionId ) ;
const deletedIds = Array . from ( this . connections . keys ( ) ) . filter ( id => ! targetIds . includes ( id ) ) ;
2022-02-24 01:30:12 +00:00
// Create and add new connections
2022-03-04 01:07:35 +00:00
subscriptionsWithUsersAndConnectionId . forEach ( subscription => {
const subscriptionId = subscription . id ;
const connectionId = subscription . connectionId ;
const added = ! this . connections . get ( connectionId )
2022-02-24 01:30:12 +00:00
if ( added ) {
2022-02-24 14:52:49 +00:00
const baseUrl = subscription . baseUrl ;
const topic = subscription . topic ;
2022-03-04 01:07:35 +00:00
const user = subscription . user ;
2022-02-28 00:29:17 +00:00
const since = subscription . last ;
2022-03-04 16:08:32 +00: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 01:07:35 +00:00
this . connections . set ( connectionId , connection ) ;
console . log ( ` [ConnectionManager] Starting new connection ${ connectionId } (subscription ${ subscriptionId } with user ${ user ? user . username : "anonymous" } ) ` ) ;
2022-02-24 01:30:12 +00: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 14:52:49 +00:00
connection . close ( ) ;
2022-02-24 01:30:12 +00:00
} ) ;
}
2022-03-04 16:08:32 +00:00
stateChanged ( subscriptionId , state ) {
if ( this . stateListener ) {
2022-03-07 02:39:20 +00:00
try {
this . stateListener ( subscriptionId , state ) ;
} catch ( e ) {
console . error ( ` [ConnectionManager] Error updating state of ${ subscriptionId } to ${ state } ` , e ) ;
}
2022-03-04 16:08:32 +00:00
}
}
notificationReceived ( subscriptionId , notification ) {
if ( this . notificationListener ) {
2022-03-07 02:39:20 +00:00
try {
this . notificationListener ( subscriptionId , notification ) ;
} catch ( e ) {
console . error ( ` [ConnectionManager] Error handling notification for ${ subscriptionId } ` , e ) ;
}
2022-03-04 16:08:32 +00:00
}
}
2022-02-24 01:30:12 +00:00
}
2022-03-04 01:07:35 +00:00
const makeConnectionId = async ( subscription , user ) => {
const hash = ( user )
? await sha256 ( ` ${ subscription . id } | ${ user . username } | ${ user . password } ` )
: await sha256 ( ` ${ subscription . id } ` ) ;
return hash . substring ( 0 , 10 ) ;
}
2022-02-24 01:30:12 +00:00
const connectionManager = new ConnectionManager ( ) ;
export default connectionManager ;