@citrineos/ocpprouter
Version:
The ocpprouter module for OCPP v2.0.1. This module is not intended to be used directly, but rather as a dependency for other modules.
578 lines • 31.4 kB
JavaScript
import { AbstractMessageRouter, AbstractModule, BOOT_STATUS, CacheNamespace, createIdentifier, ErrorCode, EventGroup, getStationIdFromIdentifier, getTenantIdFromIdentifier, mapToCallAction, MessageOrigin, MessageState, MessageTypeId, NO_ACTION, OCPP2_0_1, OCPP2_0_1_CallAction, OcppError, OCPPValidator, OCPPVersion, RequestBuilder, RetryMessageError, } from '@citrineos/base';
import { sequelize } from '@citrineos/data';
import { OidcTokenProvider } from '@citrineos/util';
import { Logger } from 'tslog';
import { v4 as uuidv4 } from 'uuid';
import { WebhookDispatcher } from './webhook.dispatcher.js';
/**
* Implementation of the ocpp router
*/
export class MessageRouterImpl extends AbstractMessageRouter {
/**
* Fields
*/
_webhookDispatcher;
_cache;
_sender;
_handler;
_networkHook;
_locationRepository;
_oidcTokenProvider;
/**
* Constructor for the class.
*
* @param {BootstrapConfig & SystemConfig} config - the system configuration
* @param {ICache} cache - the cache object
* @param {IMessageSender} [sender] - the message sender
* @param {IMessageHandler} [handler] - the message handler
* @param {WebhookDispatcher} [dispatcher] - the webhook dispatcher
* @param {Function} networkHook - the network hook needed to send messages to chargers
* @param {ILocationRepository} [locationRepository] - An optional parameter of type {@link ILocationRepository} which
* represents a repository for accessing and manipulating variable data.
* If no `locationRepository` is provided, a default {@link locationRepository} instance is created and used.
* @param {Logger<ILogObj>} [logger] - the logger object (optional)
* @param {OCPPValidator} [ocppValidator] - the OCPPValidator instance, for message validation (optional)
*/
constructor(config, cache, sender, handler, dispatcher, networkHook, logger, ocppValidator, locationRepository) {
super(config, cache, handler, sender, networkHook, logger, ocppValidator);
this._cache = cache;
this._sender = sender;
this._handler = handler;
this._webhookDispatcher = dispatcher;
this._networkHook = networkHook;
this._locationRepository =
locationRepository || new sequelize.SequelizeLocationRepository(config, logger);
if (this._config.oidcClient) {
this._oidcTokenProvider = new OidcTokenProvider(this._config.oidcClient, this._logger);
}
}
async doesChargingStationExistByStationId(tenantId, stationId) {
return await this._locationRepository.doesChargingStationExistByStationId(tenantId, stationId);
}
// TODO: Below method should lock these tables so that a rapid connect-disconnect cannot result in race condition.
async registerConnection(tenantId, stationId, protocol) {
const dispatcherRegistration = this._webhookDispatcher.register(tenantId, stationId);
const connectionIdentifier = createIdentifier(tenantId, stationId);
const requestSubscription = this._handler.subscribe(connectionIdentifier, undefined, {
tenantId: tenantId.toString(),
stationId,
state: MessageState.Request.toString(),
origin: MessageOrigin.ChargingStationManagementSystem.toString(),
});
const responseSubscription = this._handler.subscribe(connectionIdentifier, undefined, {
tenantId: tenantId.toString(),
stationId,
state: MessageState.Response.toString(),
origin: MessageOrigin.ChargingStationManagementSystem.toString(),
});
const onlineCharger = this._locationRepository.setChargingStationIsOnlineAndOCPPVersion(tenantId, stationId, true, protocol);
return Promise.all([
dispatcherRegistration,
requestSubscription,
responseSubscription,
onlineCharger,
])
.then((resolvedArray) => resolvedArray[1] && resolvedArray[2])
.catch((error) => {
this._logger.error(`Error registering connection for ${connectionIdentifier}: ${error}`);
return false;
});
}
async deregisterConnection(tenantId, stationId) {
this._webhookDispatcher.deregister(tenantId, stationId).catch((err) => {
this._logger.error('_webhookDispatcher deregister failed', err);
});
let protocol = null;
try {
const chargingStation = await this._locationRepository.readChargingStationByStationId(tenantId, stationId);
if (chargingStation?.protocol) {
protocol = chargingStation.protocol;
}
}
catch (e) {
this._logger?.warn?.(`Could not read charging station ${stationId} of tenant ${tenantId} to determine protocol: ${e.message}`);
}
await this._locationRepository.setChargingStationIsOnlineAndOCPPVersion(tenantId, stationId, false, protocol);
const connectionIdentifier = createIdentifier(tenantId, stationId);
// TODO: ensure that all queue implementations in 02_Util only unsubscribe 1 queue per call
// ...which will require refactoring this method to unsubscribe request and response queues separately
return await this._handler.unsubscribe(connectionIdentifier);
}
async onMessage(identifier, message, timestamp, protocol) {
const tenantId = getTenantIdFromIdentifier(identifier);
const stationId = getStationIdFromIdentifier(identifier);
let success = true;
let rpcMessage;
let messageTypeId = undefined;
let messageId = '-1'; // OCPP 2.0.1 part 4, section 4.2.3, When also the MessageId cannot be read, the CALLERROR SHALL contain "-1" as MessageId.
try {
try {
rpcMessage = JSON.parse(message);
}
catch (error) {
this._logger.error(`Error parsing ${message} from websocket, unable to reply: ${JSON.stringify(error)}`);
throw error;
}
messageTypeId = rpcMessage[0];
messageId = rpcMessage[1];
switch (messageTypeId) {
case MessageTypeId.Call: {
await this._onCall(identifier, rpcMessage, timestamp, protocol);
break;
}
case MessageTypeId.CallResult: {
await this._onCallResult(identifier, rpcMessage, timestamp, protocol);
break;
}
case MessageTypeId.CallError: {
await this._onCallError(identifier, rpcMessage, timestamp, protocol);
break;
}
default: {
let errorCode;
switch (protocol) {
case 'ocpp1.6': {
errorCode = ErrorCode.FormationViolation;
break;
}
case 'ocpp2.0.1': {
errorCode = ErrorCode.FormatViolation;
break;
}
default: {
throw new Error('Unknown protocol: ' + protocol);
}
}
throw new OcppError(messageId, errorCode, 'Unknown message type id: ' + messageTypeId, {});
}
}
}
catch (error) {
success = false; // ensure we return false in case of an error
this._logger.error('Error processing message:', message, error);
const action = this.getActionFromIncompletelyParsedRpcMessage(rpcMessage, messageTypeId);
if (messageTypeId != MessageTypeId.CallResult && messageTypeId != MessageTypeId.CallError) {
const callError = error instanceof OcppError
? error.asCallError()
: [
MessageTypeId.CallError,
messageId,
ErrorCode.InternalError,
'Unable to process message',
{ error: error },
];
const rawMessage = JSON.stringify(callError);
await this._sendMessage(identifier, protocol, action, MessageState.Response, rawMessage, callError);
}
let state = MessageState.Unknown;
switch (messageTypeId) {
case MessageTypeId.Call:
state = MessageState.Request;
break;
case MessageTypeId.CallResult:
case MessageTypeId.CallError:
state = MessageState.Response;
break;
default: // keep as Unknown
break;
}
await this._webhookDispatcher.dispatchMessageReceivedUnparsed(tenantId, stationId, message, timestamp.toISOString(), protocol, action, state);
}
// Update latestOcppMessageTimestamp for any incoming OCPP message (non-blocking, single query)
this._locationRepository
.updateChargingStationTimestamp(tenantId, stationId, timestamp.toISOString())
.catch((error) => {
this._logger.error(`Failed to update latestOcppMessageTimestamp for ${identifier}:`, error);
});
return success;
}
/**
* Sends a Call message to a charging station with given identifier.
*
* @param {string} stationId - The identifier of the station.
* @param {number} tenantId - The identifier of the tenant.
* @param {OCPPVersionType} protocol The OCPP protocol version of the message.
* @param {CallAction} action - The action to be called.
* @param {OcppRequest} payload - The payload of the call.
* @param {string} correlationId - The correlation ID of the message.
* @param {MessageOrigin} _origin - The origin of the message.
* @return {Promise<boolean>} A promise that resolves to a boolean indicating if the call was sent successfully.
*/
async sendCall(stationId, tenantId, protocol, action, payload, correlationId = uuidv4(), _origin) {
const identifier = createIdentifier(tenantId, stationId);
const transactionNamespace = CacheNamespace.Transactions + identifier;
const message = [MessageTypeId.Call, correlationId, action, payload];
if (await this._sendCallIsAllowed(identifier, protocol, message)) {
if (!(await this._cache.existsAnyInNamespace(transactionNamespace))) {
const cacheTimestamp = new Date();
await this._cache.set(correlationId, `${action}@${cacheTimestamp.toISOString()}`, transactionNamespace, this._config.maxCallLengthSeconds);
const rawMessage = JSON.stringify(message);
const successTimestamp = await this._sendMessage(identifier, protocol, action, MessageState.Request, rawMessage, message);
if (successTimestamp != undefined) {
this._logger.debug(`Call sent successfully with ${successTimestamp.getTime() - cacheTimestamp.getTime()} ms of lag between cache and send ${correlationId}`, identifier, message);
}
else {
const removed = await this._cache.remove(correlationId, transactionNamespace);
this._logger.warn(`Failed to send call, removed from cache: ${removed}`, identifier, message);
}
return { success: !!successTimestamp };
}
else {
this._logger.info('Call already in progress, throwing retry exception', identifier, message);
throw new RetryMessageError('Call already in progress');
}
}
else {
this._logger.info('RegistrationStatus Rejected, unable to send', identifier, message);
return { success: false };
}
}
/**
* Sends the CallResult to a charging station with given identifier.
*
* @param {string} correlationId - The correlation ID of the message.
* @param {string} stationId - The identifier of the charging station.
* @param {number} tenantId - The identifier of the tenant.
* @param {OCPPVersionType} protocol The OCPP protocol version of the message.
* @param {CallAction} action - The action to be called.
* @param {OcppRequest} payload - The payload of the call.
* @param {MessageOrigin} _origin - The origin of the message.
* @return {Promise<boolean>} A promise that resolves to true if the call result was sent successfully, or false otherwise.
*/
async sendCallResult(correlationId, stationId, tenantId, protocol, action, payload, _origin) {
const message = [MessageTypeId.CallResult, correlationId, payload];
const identifier = createIdentifier(tenantId, stationId);
const cachedActionTimestamp = await this._cache.get(correlationId, CacheNamespace.Transactions + identifier);
if (!cachedActionTimestamp) {
this._logger.error('Failed to send callResult due to missing message id', identifier, message);
return { success: false };
}
const [cachedAction, cachedTimestamp] = cachedActionTimestamp?.split(/@(.*)/) ?? []; // Returns all characters after first '@'
if (cachedAction === action) {
const rawMessage = JSON.stringify(message);
const success = await Promise.all([
this._sendMessage(identifier, protocol, cachedAction, MessageState.Response, rawMessage, message, cachedTimestamp),
this._cache.remove(correlationId, CacheNamespace.Transactions + identifier),
]).then((successes) => successes.every(Boolean));
this._logger.debug(`CallResult sent successfully ${correlationId}`, identifier, message);
return { success };
}
else {
this._logger.error('Failed to send callResult due to mismatched action', identifier, cachedActionTimestamp, message);
return { success: false };
}
}
/**
* Sends a CallError message to a charging station with given identifier.
*
* @param {string} correlationId - The correlation ID of the message.
* @param {string} stationId - The identifier of the charging station.
* @param {number} tenantId - The identifier of the tenant.
* @param {OCPPVersionType} protocol The OCPP protocol version of the message.
* @param {CallAction} _action - The action to be called.
* @param {OcppError} error - The error of the call.
* @param {MessageOrigin} _origin - The origin of the message.
* @return {Promise<boolean>} - A promise that resolves to true if the message was sent successfully.
*/
async sendCallError(correlationId, stationId, tenantId, protocol, action, error, _origin) {
const message = error.asCallError();
const identifier = createIdentifier(tenantId, stationId);
const cachedActionTimestamp = await this._cache.get(correlationId, CacheNamespace.Transactions + identifier);
if (!cachedActionTimestamp) {
this._logger.error('Failed to send callError due to missing message id', identifier, message);
return { success: false };
}
const [cachedAction, cachedTimestamp] = cachedActionTimestamp?.split(/@(.*)/) ?? []; // Returns all characters after first '@'
if (cachedAction === action) {
const rawMessage = JSON.stringify(message);
const success = await Promise.all([
this._sendMessage(identifier, protocol, cachedAction, MessageState.Response, rawMessage, message, cachedTimestamp),
this._cache.remove(correlationId, CacheNamespace.Transactions + identifier),
]).then((successes) => successes.every(Boolean));
return { success };
}
else {
this._logger.error('Failed to send callError due to mismatched action', identifier, cachedActionTimestamp, cachedAction, message);
return { success: false };
}
}
async shutdown() {
await this._sender.shutdown();
await this._handler.shutdown();
}
/**
* Private Methods
*/
/**
* Handles an incoming Call message from a client connection.
*
* @param {string} identifier - The client identifier.
* @param {Call} message - The Call message received.
* @param {Date} timestamp Time at which the message was received from the charger.
* @param {string} protocol The OCPP protocol version of the message
* @return {void}
*/
async _onCall(identifier, message, timestamp, protocol) {
const messageId = message[1];
const tenantId = getTenantIdFromIdentifier(identifier);
const stationId = getStationIdFromIdentifier(identifier);
let action = message[2];
this._logger.debug('_onCall:', identifier, message, timestamp.toISOString(), protocol);
action = mapToCallAction(protocol, action);
const isAllowed = await this._onCallIsAllowed(action, identifier);
if (!isAllowed) {
throw new OcppError(messageId, ErrorCode.SecurityError, `Action ${action} not allowed`);
}
// Run schema validation for incoming Call message
const { isValid, errors } = this._validateCall(identifier, message, protocol);
if (!isValid || errors) {
throw new OcppError(messageId, ErrorCode.FormatViolation, 'Invalid message format', {
errors: errors,
});
}
this._cache
.existsAnyInNamespace(CacheNamespace.Transactions + identifier)
.then((exists) => {
if (exists) {
this._logger.debug('Another call is already in progress, processing call anyways', identifier, message);
}
})
.catch((error) => {
this._logger.error('Failed to check if another call is in progress:', identifier, message, error);
});
this._cache
.setIfNotExist(messageId, `${action}@${timestamp.toISOString()}`, CacheNamespace.Transactions + identifier, this._config.maxCallLengthSeconds)
.then((success) => {
if (!success) {
this._logger.debug('Another call with same messageId is already in progress, processing call anyways', identifier, message);
}
})
.catch((error) => {
this._logger.error('Failed to set call in cache:', identifier, message, error);
});
try {
// Route call
const confirmation = await this._routeCall(identifier, message, timestamp, protocol);
if (!confirmation.success) {
throw new OcppError(messageId, ErrorCode.InternalError, 'Call failed', {
details: confirmation.payload,
});
}
}
catch (error) {
const callError = error instanceof OcppError
? error
: new OcppError(messageId, ErrorCode.InternalError, 'Call failed', {
details: error,
});
this.sendCallError(messageId, stationId, tenantId, protocol, action, callError)
.catch((err) => {
this._logger.error('sendCallError failed', err);
})
.finally(() => {
this._cache.remove(messageId, CacheNamespace.Transactions + identifier).catch((err) => {
this._logger.error('cache remove failed', err);
});
});
}
}
/**
* Handles a CallResult made by the client.
*
* @param {string} identifier - The client identifier that made the call.
* @param {CallResult} message - The OCPP CallResult message.
* @param {Date} timestamp Time at which the message was received from the charger.
* @param {OCPPVersionType} protocol The OCPP protocol version of the message
*/
async _onCallResult(identifier, message, timestamp, protocol) {
const messageId = message[1];
this._logger.debug('_onCallResult:', identifier, message, timestamp.toISOString(), protocol);
const cachedActionTimestamp = await this._cache.get(messageId, CacheNamespace.Transactions + identifier);
await this._cache.remove(messageId, CacheNamespace.Transactions + identifier).catch((err) => {
this._logger.error('_onCallResult cache remove failed', err);
});
if (!cachedActionTimestamp) {
throw new OcppError(messageId, ErrorCode.InternalError, 'MessageId not found, call may have timed out', { maxCallLengthSeconds: this._config.maxCallLengthSeconds });
}
const [action, cachedTimestamp] = cachedActionTimestamp.split(/@(.*)/); // Returns all characters after first '@'
this._logger.debug(`Message received. Time taken since sent: ${timestamp.getTime() - new Date(cachedTimestamp).getTime()} ms`, identifier, message);
// Run schema validation for incoming CallResult message
const { isValid, errors } = this._validateCallResult(identifier, mapToCallAction(protocol, action), message, protocol);
if (!isValid || errors) {
throw new OcppError(messageId, ErrorCode.FormatViolation, 'Invalid message format', {
errors: errors,
});
}
// Route call result
const confirmation = await this._routeCallResult(identifier, message, mapToCallAction(protocol, action), timestamp, protocol);
if (!confirmation.success) {
throw new OcppError(messageId, ErrorCode.InternalError, 'CallResult failed', {
details: confirmation.payload,
});
}
}
/**
* Handles the CallError that may have occured during a Call exchange.
*
* @param {string} identifier - The client identifier.
* @param {CallError} message - The error message.
* @param {Date} timestamp Time at which the message was received from the charger.
* @param {OCPPVersionType} protocol The OCPP protocol version of the message
*/
async _onCallError(identifier, message, timestamp, protocol) {
const messageId = message[1];
this._logger.debug('_onCallError:', identifier, message, timestamp.toISOString(), protocol);
const cachedActionTimestamp = await this._cache.get(messageId, CacheNamespace.Transactions + identifier);
// Always remove pending call transaction
await this._cache.remove(messageId, CacheNamespace.Transactions + identifier).catch((err) => {
this._logger.error('_onCallError cache remove failed', err);
});
if (!cachedActionTimestamp) {
throw new OcppError(messageId, ErrorCode.InternalError, 'MessageId not found, call may have timed out', { maxCallLengthSeconds: this._config.maxCallLengthSeconds });
}
const [action, cachedTimestamp] = cachedActionTimestamp.split(/@(.*)/); // Returns all characters after first '@'
this._logger.debug(`Message received. Time taken since sent: ${timestamp.getTime() - new Date(cachedTimestamp).getTime()} ms`, identifier, message);
const confirmation = await this._routeCallError(identifier, message, mapToCallAction(protocol, action), timestamp, protocol);
if (!confirmation.success) {
// Below code commented out with debug log because currently there is no error routing implemented, so this block will always be reached for CallErrors.
// Once error routing is implemented, this block can be uncommented to throw an error if the CallError routing fails.
this._logger.debug('Unable to route call error: ', confirmation);
// throw new OcppError(messageId, ErrorCode.InternalError, 'CallError failed', {
// details: confirmation.payload,
// });
}
}
/**
* Determine if the given action for identifier is allowed.
*
* @param {CallAction} action - The action to be checked.
* @param {string} identifier - The identifier to be checked.
* @return {Promise<boolean>} A promise that resolves to a boolean indicating if the action and identifier are allowed.
*/
_onCallIsAllowed(action, identifier) {
return this._cache.exists(action, identifier).then((blacklisted) => !blacklisted);
}
/**
*
* @param {string} identifier - The identifier of the client, e.g. "tenantId:stationId".
* @param {OCPPVersionType} protocol - The OCPP protocol version.
* @param {string} action - The OCPP CallAction to be sent. See {@link CallAction}.
* @param {MessageState} state - The state of the message. Used for dispatching in webhook.
* @param {string} rawMessage - The raw message string to be sent, i.e. the stringified version of the rpc message. Used for sending in webhook and logging.
* @param {any} rpcMessage - the rpc message json object, i.e. [MessageTypeId, messageId, action, payload] for Call or [MessageTypeId, messageId, payload] for CallResult. Used for logging and dispatching in webhook.
* @param {string} receivedIsoTimestamp - The ISO timestamp of when the Call was received, if this is a response to a Call. Used for logging the time taken for the message to be sent since it was received.
* @returns {Promise<Date | undefined>} A promise that resolves to the timestamp of when the message was sent or undefined if the message failed to send.
*/
async _sendMessage(identifier, protocol, action, state, rawMessage, rpcMessage, receivedIsoTimestamp) {
try {
await this._networkHook(identifier, rawMessage); // Throws an error if the message is not sent, or returns void
}
catch (error) {
this._logger.error('Failed to send message:', identifier, rawMessage, error);
// Don't dispatch if the message was not sent
return undefined;
}
const sentTimestamp = new Date();
if (receivedIsoTimestamp) {
const receivedTimestamp = new Date(receivedIsoTimestamp);
this._logger.debug(`Message sent successfully. Time taken since received: ${sentTimestamp.getTime() - receivedTimestamp.getTime()} ms`, identifier, rpcMessage);
}
this._webhookDispatcher
.dispatchMessageSent(identifier, action, state, sentTimestamp.toISOString(), protocol, rpcMessage)
.catch((err) => {
this._logger.error('dispatchMessageSent failed', err);
});
return sentTimestamp;
}
async _sendCallIsAllowed(identifier, protocol, message) {
const status = await this._cache.get(BOOT_STATUS, identifier);
if (status === OCPP2_0_1.RegistrationStatusEnumType.Rejected &&
// TriggerMessage<BootNotification> is the only message allowed to be sent during Rejected BootStatus B03.FR.08
!(mapToCallAction(protocol, message[2]) === OCPP2_0_1_CallAction.TriggerMessage &&
message[3].requestedMessage ==
OCPP2_0_1.MessageTriggerEnumType.BootNotification)) {
return false;
}
return true;
}
async _routeCall(connectionIdentifier, message, timestamp, protocol) {
const messageId = message[1];
const action = mapToCallAction(protocol, message[2]);
const payload = message[3];
const tenantId = getTenantIdFromIdentifier(connectionIdentifier);
const stationId = getStationIdFromIdentifier(connectionIdentifier);
const _message = RequestBuilder.buildCall(stationId, messageId, tenantId, action, payload, EventGroup.Router, MessageOrigin.ChargingStation, protocol, timestamp);
return this.emitMessage(_message, message);
}
async _routeCallResult(connectionIdentifier, message, action, timestamp, protocol) {
const messageId = message[1];
const payload = message[2];
const tenantId = getTenantIdFromIdentifier(connectionIdentifier);
const stationId = getStationIdFromIdentifier(connectionIdentifier);
const _message = RequestBuilder.buildCallResult(stationId, messageId, tenantId, action, payload, EventGroup.Router, MessageOrigin.ChargingStation, protocol, timestamp);
return this.emitMessage(_message, message);
}
async _routeCallError(connectionIdentifier, message, action, timestamp, protocol) {
const messageId = message[1];
const payload = new OcppError(messageId, message[2], message[3], message[4]);
const tenantId = getTenantIdFromIdentifier(connectionIdentifier);
const stationId = getStationIdFromIdentifier(connectionIdentifier);
const _message = RequestBuilder.buildCallError(stationId, messageId, tenantId, action, payload, EventGroup.Router, MessageOrigin.ChargingStation, protocol, timestamp);
// Fulfill callback for api, if needed
this._handleMessageApiCallback(_message).catch((err) => {
this._logger.error('_handleMessageApiCallback failed', err);
});
return this.emitMessage(_message, message);
}
async emitMessage(message, rpcMessage) {
let confirmation;
if (message.payload instanceof OcppError) {
// No error routing currently done
this._logger.warn('OCPP Error routing not implemented');
confirmation = { success: false };
}
else {
confirmation = await this._sender.send(message);
}
await this._webhookDispatcher.dispatchMessageReceived(message.context.tenantId, message.context.stationId, message.context.timestamp, message.protocol, message.action, message.state, rpcMessage);
return confirmation;
}
async _handleMessageApiCallback(message) {
const url = await this._cache.get(message.context.correlationId, AbstractModule.CALLBACK_URL_CACHE_PREFIX + message.context.stationId);
if (url) {
const headers = {
'Content-Type': 'application/json',
};
if (this._oidcTokenProvider) {
try {
const token = await this._oidcTokenProvider.getToken();
headers['Authorization'] = `Bearer ${token}`;
}
catch (error) {
this._logger.error('Failed to get OIDC token for callback:', error);
return;
}
}
await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(message.payload),
});
}
}
getActionFromIncompletelyParsedRpcMessage(rpcMessage, messageTypeId) {
let action;
switch (messageTypeId) {
case MessageTypeId.Call:
action = rpcMessage && rpcMessage.length > 2 ? rpcMessage[2] : NO_ACTION;
break;
case MessageTypeId.CallResult:
case MessageTypeId.CallError:
default:
action = NO_ACTION;
break;
}
return action;
}
}
//# sourceMappingURL=router.js.map