Browse Source

Better rpc error handling

update-deps
Alex Mikhalev 7 years ago
parent
commit
1acd60435f
  1. 10
      app/sprinklers/websocket.ts
  2. 38
      common/sprinklers/websocketData.ts
  3. 52
      server/websocket/index.ts

10
app/sprinklers/websocket.ts

@ -36,9 +36,7 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
} }
async subscribe() { async subscribe() {
if (!this.api.socket) { await this.api.authenticate("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcHJpbmtsZXJzMyIsImF1ZCI6IjA4NDQ4N2Q1LWU1NzktNDQ5YS05MzI5LTU5NWJlNGJjMmJiYyIsIm5hbWUiOiJBbGV4IE1pa2hhbGV2IiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTUzMDQxNzU3MCwiaWF0IjoxNTMwNDE1NzcwfQ.fRGiN_X1j3Hwe8a5y68wXLx1DQPtTkQr9h6Uh848dFM");
throw new Error("WebSocket not connected");
}
const subscribeRequest: ws.IDeviceSubscribeRequest = { const subscribeRequest: ws.IDeviceSubscribeRequest = {
deviceId: this.id, deviceId: this.id,
}; };
@ -47,7 +45,7 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
this.connectionState.serverToBroker = true; this.connectionState.serverToBroker = true;
this.connectionState.clientToServer = true; this.connectionState.clientToServer = true;
} catch (err) { } catch (err) {
if ((err as ws.Error).code === ErrorCode.NoPermission) { if ((err as ws.IError).code === ErrorCode.NoPermission) {
this.connectionState.hasPermission = false; this.connectionState.hasPermission = false;
} else { } else {
log.error({ err }); log.error({ err });
@ -117,7 +115,7 @@ export class WebSocketApiClient implements s.ISprinklersApi {
// args must all be JSON serializable // args must all be JSON serializable
async makeDeviceCall(deviceId: string, request: deviceRequests.Request): Promise<deviceRequests.Response> { async makeDeviceCall(deviceId: string, request: deviceRequests.Request): Promise<deviceRequests.Response> {
if (this.socket == null) { if (this.socket == null) {
const error: ws.Error = { const error: ws.IError = {
code: ErrorCode.ServerDisconnected, code: ErrorCode.ServerDisconnected,
message: "the server is not connected", message: "the server is not connected",
}; };
@ -264,7 +262,7 @@ export class WebSocketApiClient implements s.ISprinklersApi {
} }
update(schema.sprinklersDevice, device, data.data); update(schema.sprinklersDevice, device, data.data);
}, },
error: (data: ws.Error) => { error: (data: ws.IError) => {
log.warn({ err: data }, "server error"); log.warn({ err: data }, "server error");
}, },
}; };

38
common/sprinklers/websocketData.ts

@ -1,6 +1,7 @@
import * as rpc from "../jsonRpc/index"; import * as rpc from "../jsonRpc/index";
import { Response as ResponseData } from "@common/sprinklers/deviceRequests"; import { Response as ResponseData } from "@common/sprinklers/deviceRequests";
import { ErrorCode } from "@common/sprinklers/ErrorCode";
export interface IAuthenticateRequest { export interface IAuthenticateRequest {
accessToken: string; accessToken: string;
@ -55,22 +56,41 @@ export interface IDeviceUpdate {
export interface IServerNotificationTypes { export interface IServerNotificationTypes {
"brokerConnectionUpdate": IBrokerConnectionUpdate; "brokerConnectionUpdate": IBrokerConnectionUpdate;
"deviceUpdate": IDeviceUpdate; "deviceUpdate": IDeviceUpdate;
"error": Error; "error": IError;
} }
export type ServerNotificationMethod = keyof IServerNotificationTypes; export type ServerNotificationMethod = keyof IServerNotificationTypes;
export type Error = rpc.DefaultErrorType; export type IError = rpc.DefaultErrorType;
export type ErrorData = rpc.ErrorData<Error>; export type ErrorData = rpc.ErrorData<IError>;
export type ServerMessage = rpc.Message<{}, IServerResponseTypes, Error, IServerNotificationTypes>; export class RpcError extends Error implements IError {
name = "RpcError";
code: number;
data: any;
constructor(message: string, code: number = ErrorCode.BadRequest, data: any = {}) {
super(message);
this.code = code;
if (data instanceof Error) {
this.data = data.toString();
}
this.data = data;
}
toJSON(): IError {
return { code: this.code, message: this.message, data: this.data };
}
}
export type ServerMessage = rpc.Message<{}, IServerResponseTypes, IError, IServerNotificationTypes>;
export type ServerNotification = rpc.Notification<IServerNotificationTypes>; export type ServerNotification = rpc.Notification<IServerNotificationTypes>;
export type ServerResponse = rpc.Response<IServerResponseTypes, Error>; export type ServerResponse = rpc.Response<IServerResponseTypes, IError>;
export type ServerResponseData<Method extends keyof IServerResponseTypes = keyof IServerResponseTypes> = export type ServerResponseData<Method extends keyof IServerResponseTypes = keyof IServerResponseTypes> =
rpc.ResponseData<IServerResponseTypes, Error, Method>; rpc.ResponseData<IServerResponseTypes, IError, Method>;
export type ServerResponseHandlers = rpc.ResponseHandlers<IServerResponseTypes, Error>; export type ServerResponseHandlers = rpc.ResponseHandlers<IServerResponseTypes, IError>;
export type ServerNotificationHandlers = rpc.NotificationHandlers<IServerNotificationTypes>; export type ServerNotificationHandlers = rpc.NotificationHandlers<IServerNotificationTypes>;
export type ClientRequest<Method extends keyof IClientRequestTypes = keyof IClientRequestTypes> = export type ClientRequest<Method extends keyof IClientRequestTypes = keyof IClientRequestTypes> =
rpc.Request<IClientRequestTypes, Method>; rpc.Request<IClientRequestTypes, Method>;
export type ClientMessage = rpc.Message<IClientRequestTypes, {}, Error, {}>; export type ClientMessage = rpc.Message<IClientRequestTypes, {}, IError, {}>;
export type ClientRequestHandlers = rpc.RequestHandlers<IClientRequestTypes, IServerResponseTypes, Error>; export type ClientRequestHandlers = rpc.RequestHandlers<IClientRequestTypes, IServerResponseTypes, IError>;

52
server/websocket/index.ts

@ -48,31 +48,33 @@ export class WebSocketClient {
this.api.removeClient(this); this.api.removeClient(this);
} }
private checkAuthorization() {
if (!this.userId) {
throw new ws.RpcError("this WebSocket session has not been authenticated",
ErrorCode.Unauthorized);
}
}
private requestHandlers: ws.ClientRequestHandlers = { private requestHandlers: ws.ClientRequestHandlers = {
authenticate: async (data: ws.IAuthenticateRequest) => { authenticate: async (data: ws.IAuthenticateRequest) => {
if (!data.accessToken) { if (!data.accessToken) {
return { throw new ws.RpcError("no token specified", ErrorCode.BadRequest);
result: "error", error: {
code: ErrorCode.BadRequest, message: "no token specified",
},
};
} }
let decoded: TokenClaims; let decoded: TokenClaims;
try { try {
decoded = await verifyToken(data.accessToken); decoded = await verifyToken(data.accessToken);
} catch (e) { } catch (e) {
return { throw new ws.RpcError("invalid token", ErrorCode.BadToken, e);
result: "error",
error: { code: ErrorCode.BadToken, message: "invalid token", data: e },
};
} }
this.userId = decoded.aud; this.userId = decoded.aud;
log.info({ userId: decoded.aud, name: decoded.name }, "authenticated websocket client");
return { return {
result: "success", result: "success",
data: { authenticated: true, message: "authenticated" }, data: { authenticated: true, message: "authenticated" },
}; };
}, },
deviceSubscribe: async (data: ws.IDeviceSubscribeRequest) => { deviceSubscribe: async (data: ws.IDeviceSubscribeRequest) => {
this.checkAuthorization();
const deviceId = data.deviceId; const deviceId = data.deviceId;
if (deviceId !== "grinklers") { // TODO: somehow validate this device id? if (deviceId !== "grinklers") { // TODO: somehow validate this device id?
return { return {
@ -100,6 +102,7 @@ export class WebSocketClient {
return { result: "success", data: response }; return { result: "success", data: response };
}, },
deviceCall: async (data: ws.IDeviceCallRequest) => { deviceCall: async (data: ws.IDeviceCallRequest) => {
this.checkAuthorization();
try { try {
const response = await this.doDeviceCallRequest(data); const response = await this.doDeviceCallRequest(data);
const resData: ws.IDeviceCallResponse = { const resData: ws.IDeviceCallResponse = {
@ -108,13 +111,7 @@ export class WebSocketClient {
return { result: "success", data: resData }; return { result: "success", data: resData };
} catch (err) { } catch (err) {
const e: deviceRequests.ErrorResponseData = err; const e: deviceRequests.ErrorResponseData = err;
return { throw new ws.RpcError(e.message, e.code, e);
result: "error", error: {
code: e.code,
message: e.message,
data: e,
},
};
} }
}, },
}; };
@ -155,7 +152,6 @@ export class WebSocketClient {
return this.onError({ socketData, err }, "received invalid websocket message from client", return this.onError({ socketData, err }, "received invalid websocket message from client",
ErrorCode.Parse); ErrorCode.Parse);
} }
log.debug({ data }, "client message");
switch (data.type) { switch (data.type) {
case "request": case "request":
await this.handleRequest(data); await this.handleRequest(data);
@ -168,21 +164,21 @@ export class WebSocketClient {
private async handleRequest(request: ws.ClientRequest) { private async handleRequest(request: ws.ClientRequest) {
let response: ws.ServerResponseData; let response: ws.ServerResponseData;
if (!this.requestHandlers[request.method]) {
log.warn({ method: request.method }, "received invalid client request method");
response = {
result: "error", error: {
code: ErrorCode.BadRequest, message: "received invalid client request method",
},
};
} else {
try { try {
if (!this.requestHandlers[request.method]) {
// noinspection ExceptionCaughtLocallyJS
throw new ws.RpcError("received invalid client request method");
}
response = await rpc.handleRequest(this.requestHandlers, request); response = await rpc.handleRequest(this.requestHandlers, request);
} catch (err) { } catch (err) {
log.error({ method: request.method, err }, "error during processing of client request"); if (err instanceof ws.RpcError) {
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");
response = { response = {
result: "error", error: { result: "error", error: {
code: ErrorCode.Internal, message: "error during processing of client request", code: ErrorCode.Internal, message: "unhandled error during processing of client request",
data: err.toString(), data: err.toString(),
}, },
}; };
@ -193,7 +189,7 @@ export class WebSocketClient {
private onError(data: any, message: string, code: number = ErrorCode.Internal) { private onError(data: any, message: string, code: number = ErrorCode.Internal) {
log.error(data, message); log.error(data, message);
const errorData: ws.Error = { code, message, data }; const errorData: ws.IError = { code, message, data };
this.sendNotification("error", errorData); this.sendNotification("error", errorData);
} }

Loading…
Cancel
Save