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

38
common/sprinklers/websocketData.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import * as rpc from "../jsonRpc/index";
import { Response as ResponseData } from "@common/sprinklers/deviceRequests";
import { ErrorCode } from "@common/sprinklers/ErrorCode";
export interface IAuthenticateRequest {
accessToken: string;
@ -55,22 +56,41 @@ export interface IDeviceUpdate { @@ -55,22 +56,41 @@ export interface IDeviceUpdate {
export interface IServerNotificationTypes {
"brokerConnectionUpdate": IBrokerConnectionUpdate;
"deviceUpdate": IDeviceUpdate;
"error": Error;
"error": IError;
}
export type ServerNotificationMethod = keyof IServerNotificationTypes;
export type Error = rpc.DefaultErrorType;
export type ErrorData = rpc.ErrorData<Error>;
export type IError = rpc.DefaultErrorType;
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 ServerResponse = rpc.Response<IServerResponseTypes, Error>;
export type ServerResponse = rpc.Response<IServerResponseTypes, IError>;
export type ServerResponseData<Method extends keyof IServerResponseTypes = keyof IServerResponseTypes> =
rpc.ResponseData<IServerResponseTypes, Error, Method>;
export type ServerResponseHandlers = rpc.ResponseHandlers<IServerResponseTypes, Error>;
rpc.ResponseData<IServerResponseTypes, IError, Method>;
export type ServerResponseHandlers = rpc.ResponseHandlers<IServerResponseTypes, IError>;
export type ServerNotificationHandlers = rpc.NotificationHandlers<IServerNotificationTypes>;
export type ClientRequest<Method extends keyof IClientRequestTypes = keyof IClientRequestTypes> =
rpc.Request<IClientRequestTypes, Method>;
export type ClientMessage = rpc.Message<IClientRequestTypes, {}, Error, {}>;
export type ClientRequestHandlers = rpc.RequestHandlers<IClientRequestTypes, IServerResponseTypes, Error>;
export type ClientMessage = rpc.Message<IClientRequestTypes, {}, IError, {}>;
export type ClientRequestHandlers = rpc.RequestHandlers<IClientRequestTypes, IServerResponseTypes, IError>;

52
server/websocket/index.ts

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

Loading…
Cancel
Save