@ -45,40 +45,50 @@ export class WebSocketConnection {
@@ -45,40 +45,50 @@ export class WebSocketConnection {
stop = ( ) = > {
this . socket . close ( ) ;
}
} ;
onClose = ( code : number , reason : string ) = > {
log . debug ( { code , reason } , "WebSocketConnection closing" ) ;
this . disposers . forEach ( ( disposer ) = > disposer ( ) ) ;
this . deviceSubscriptions . forEach ( ( disposer ) = > disposer ( ) ) ;
this . disposers . forEach ( disposer = > disposer ( ) ) ;
this . deviceSubscriptions . forEach ( disposer = > disposer ( ) ) ;
this . api . removeClient ( this ) ;
}
} ;
subscribeBrokerConnection() {
this . disposers . push ( autorun ( ( ) = > {
this . disposers . push (
autorun ( ( ) = > {
const updateData : ws.IBrokerConnectionUpdate = {
brokerConnected : this.state.mqttClient.connected ,
brokerConnected : this.state.mqttClient.connected
} ;
this . sendNotification ( "brokerConnectionUpdate" , updateData ) ;
} ) ) ;
} )
) ;
}
checkAuthorization() {
if ( ! this . userId || ! this . user ) {
throw new RpcError ( "this WebSocket session has not been authenticated" ,
ErrorCode . Unauthorized ) ;
throw new RpcError (
"this WebSocket session has not been authenticated" ,
ErrorCode . Unauthorized
) ;
}
}
checkDevice ( devId : string ) {
const userDevice = this . user ! . devices ! . find ( ( dev ) = > dev . deviceId === devId ) ;
const userDevice = this . user ! . devices ! . find ( dev = > dev . deviceId === devId ) ;
if ( userDevice == null ) {
throw new RpcError ( "you do not have permission to subscribe to device" ,
ErrorCode . NoPermission , { id : devId } ) ;
throw new RpcError (
"you do not have permission to subscribe to device" ,
ErrorCode . NoPermission ,
{ id : devId }
) ;
}
const deviceId = userDevice . deviceId ;
if ( ! deviceId ) {
throw new RpcError ( "device has no associated device prefix" , ErrorCode . Internal ) ;
throw new RpcError (
"device has no associated device prefix" ,
ErrorCode . Internal
) ;
}
return userDevice ;
}
@ -89,25 +99,28 @@ export class WebSocketConnection {
@@ -89,25 +99,28 @@ export class WebSocketConnection {
sendNotification < Method extends ws.ServerNotificationMethod > (
method : Method ,
data : ws.IServerNotificationTypes [ Method ] ) {
data : ws.IServerNotificationTypes [ Method ]
) {
this . sendMessage ( { type : "notification" , method , data } ) ;
}
sendResponse < Method extends ws.ClientRequestMethods > (
method : Method ,
id : number ,
data : ws.ServerResponseData < Method > ) {
data : ws.ServerResponseData < Method >
) {
this . sendMessage ( { type : "response" , method , id , . . . data } ) ;
}
handleSocketMessage = ( socketData : WebSocket.Data ) = > {
this . doHandleSocketMessage ( socketData )
. catch ( ( err ) = > {
this . doHandleSocketMessage ( socketData ) . catch ( err = > {
this . onError ( { err } , "unhandled error on handling socket message" ) ;
} ) ;
}
} ;
async doDeviceCallRequest ( requestData : ws.IDeviceCallRequest ) : Promise < deviceRequests.Response > {
async doDeviceCallRequest (
requestData : ws.IDeviceCallRequest
) : Promise < deviceRequests.Response > {
const userDevice = this . checkDevice ( requestData . deviceId ) ;
const deviceId = userDevice . deviceId ! ;
const device = this . state . mqttClient . acquireDevice ( deviceId ) ;
@ -121,23 +134,32 @@ export class WebSocketConnection {
@@ -121,23 +134,32 @@ export class WebSocketConnection {
private async doHandleSocketMessage ( socketData : WebSocket.Data ) {
if ( typeof socketData !== "string" ) {
return this . onError ( { type : typeof socketData } ,
"received invalid socket data type from client" , ErrorCode . Parse ) ;
return this . onError (
{ type : typeof socketData } ,
"received invalid socket data type from client" ,
ErrorCode . Parse
) ;
}
let data : ws.ClientMessage ;
try {
data = JSON . parse ( socketData ) ;
} catch ( err ) {
return this . onError ( { socketData , err } , "received invalid websocket message from client" ,
ErrorCode . Parse ) ;
return this . onError (
{ socketData , err } ,
"received invalid websocket message from client" ,
ErrorCode . Parse
) ;
}
switch ( data . type ) {
case "request" :
await this . handleRequest ( data ) ;
break ;
default :
return this . onError ( { data } , "received invalid message type from client" ,
ErrorCode . BadRequest ) ;
return this . onError (
{ data } ,
"received invalid message type from client" ,
ErrorCode . BadRequest
) ;
}
}
@ -154,19 +176,28 @@ export class WebSocketConnection {
@@ -154,19 +176,28 @@ export class WebSocketConnection {
log . debug ( { err } , "rpc error" ) ;
response = { result : "error" , error : err.toJSON ( ) } ;
} else {
log . error ( { method : request.method , err } , "unhandled error during processing of client request" ) ;
log . error (
{ method : request.method , err } ,
"unhandled error during processing of client request"
) ;
response = {
result : "error" , error : {
code : ErrorCode.Internal , message : "unhandled error during processing of client request" ,
data : err.toString ( ) ,
} ,
result : "error" ,
error : {
code : ErrorCode.Internal ,
message : "unhandled error during processing of client request" ,
data : err.toString ( )
}
} ;
}
}
this . sendResponse ( request . method , request . id , response ) ;
}
private onError ( data : any , message : string , code : number = ErrorCode . Internal ) {
private onError (
data : any ,
message : string ,
code : number = ErrorCode . Internal
) {
log . error ( data , message ) ;
const errorData : ws.IError = { code , message , data } ;
this . sendNotification ( "error" , errorData ) ;
@ -174,8 +205,10 @@ export class WebSocketConnection {
@@ -174,8 +205,10 @@ export class WebSocketConnection {
}
class WebSocketRequestHandlers implements ws . ClientRequestHandlers {
async authenticate ( this : WebSocketConnection , data : ws.IAuthenticateRequest ) :
Promise < ws.ServerResponseData < " authenticate " > > {
async authenticate (
this : WebSocketConnection ,
data : ws.IAuthenticateRequest
) : Promise < ws.ServerResponseData < " authenticate " > > {
if ( ! data . accessToken ) {
throw new RpcError ( "no token specified" , ErrorCode . BadRequest ) ;
}
@ -186,34 +219,51 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
@@ -186,34 +219,51 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
throw new RpcError ( "invalid token" , ErrorCode . BadToken , e ) ;
}
this . userId = claims . aud ;
this . user = await this . state . database . users .
findById ( this . userId , { devices : true } ) || null ;
this . user =
( await this . state . database . users . findById ( this . userId , {
devices : true
} ) ) || null ;
if ( ! this . user ) {
throw new RpcError ( "user no longer exists" , ErrorCode . BadToken ) ;
}
log . debug ( { userId : claims.aud , name : claims.name } , "authenticated websocket client" ) ;
log . debug (
{ userId : claims.aud , name : claims.name } ,
"authenticated websocket client"
) ;
this . subscribeBrokerConnection ( ) ;
return {
result : "success" ,
data : { authenticated : true , message : "authenticated" , user : this.user.toJSON ( ) } ,
data : {
authenticated : true ,
message : "authenticated" ,
user : this.user.toJSON ( )
}
} ;
}
async deviceSubscribe ( this : WebSocketConnection , data : ws.IDeviceSubscribeRequest ) :
Promise < ws.ServerResponseData < " deviceSubscribe " > > {
async deviceSubscribe (
this : WebSocketConnection ,
data : ws.IDeviceSubscribeRequest
) : Promise < ws.ServerResponseData < " deviceSubscribe " > > {
this . checkAuthorization ( ) ;
const userDevice = this . checkDevice ( data . deviceId ) ;
const deviceId = userDevice . deviceId ! ;
if ( ! this . deviceSubscriptions . has ( deviceId ) ) {
const device = this . state . mqttClient . acquireDevice ( deviceId ) ;
log . debug ( { deviceId , userId : this.userId } , "websocket client subscribed to device" ) ;
log . debug (
{ deviceId , userId : this.userId } ,
"websocket client subscribed to device"
) ;
const autorunDisposer = autorun ( ( ) = > {
const autorunDisposer = autorun (
( ) = > {
const json = serialize ( schema . sprinklersDevice , device ) ;
log . trace ( { device : json } ) ;
const updateData : ws.IDeviceUpdate = { deviceId , data : json } ;
this . sendNotification ( "deviceUpdate" , updateData ) ;
} , { delay : 100 } ) ;
} ,
{ delay : 100 }
) ;
this . deviceSubscriptions . set ( deviceId , ( ) = > {
autorunDisposer ( ) ;
@ -223,13 +273,15 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
@@ -223,13 +273,15 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
}
const response : ws.IDeviceSubscribeResponse = {
deviceId ,
deviceId
} ;
return { result : "success" , data : response } ;
}
async deviceUnsubscribe ( this : WebSocketConnection , data : ws.IDeviceSubscribeRequest ) :
Promise < ws.ServerResponseData < " deviceUnsubscribe " > > {
async deviceUnsubscribe (
this : WebSocketConnection ,
data : ws.IDeviceSubscribeRequest
) : Promise < ws.ServerResponseData < " deviceUnsubscribe " > > {
this . checkAuthorization ( ) ;
const userDevice = this . checkDevice ( data . deviceId ) ;
const deviceId = userDevice . deviceId ! ;
@ -240,18 +292,20 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
@@ -240,18 +292,20 @@ class WebSocketRequestHandlers implements ws.ClientRequestHandlers {
}
const response : ws.IDeviceSubscribeResponse = {
deviceId ,
deviceId
} ;
return { result : "success" , data : response } ;
}
async deviceCall ( this : WebSocketConnection , data : ws.IDeviceCallRequest ) :
Promise < ws.ServerResponseData < " deviceCall " > > {
async deviceCall (
this : WebSocketConnection ,
data : ws.IDeviceCallRequest
) : Promise < ws.ServerResponseData < " deviceCall " > > {
this . checkAuthorization ( ) ;
try {
const response = await this . doDeviceCallRequest ( data ) ;
const resData : ws.IDeviceCallResponse = {
data : response ,
data : response
} ;
return { result : "success" , data : resData } ;
} catch ( err ) {