@robotical/ricjs
Version:
Javascript/TS library for Robotical RIC
440 lines • 22.6 kB
JavaScript
;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RICJS
// Communications Library
//
// Rob Dobson & Chris Greening 2020-2022
// (C) 2020-2022
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Object.defineProperty(exports, "__esModule", { value: true });
exports.RICMsgResultCode = exports.RICCommsMsgProtocol = exports.RICCommsMsgTypeCode = exports.RICRESTElemCode = void 0;
const tslib_1 = require("tslib");
const RICMsgTrackInfo_1 = require("./RICMsgTrackInfo");
const RICLog_1 = tslib_1.__importDefault(require("./RICLog"));
const RICUtils_1 = tslib_1.__importDefault(require("./RICUtils"));
const RICROSSerial_1 = require("./RICROSSerial");
const RICProtocolDefs_1 = require("./RICProtocolDefs");
const RICMiniHDLC_1 = tslib_1.__importDefault(require("./RICMiniHDLC"));
// Protocol enums
var RICRESTElemCode;
(function (RICRESTElemCode) {
RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_URL"] = 0] = "RICREST_ELEM_CODE_URL";
RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_CMDRESPJSON"] = 1] = "RICREST_ELEM_CODE_CMDRESPJSON";
RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_BODY"] = 2] = "RICREST_ELEM_CODE_BODY";
RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_COMMAND_FRAME"] = 3] = "RICREST_ELEM_CODE_COMMAND_FRAME";
RICRESTElemCode[RICRESTElemCode["RICREST_ELEM_CODE_FILEBLOCK"] = 4] = "RICREST_ELEM_CODE_FILEBLOCK";
})(RICRESTElemCode = exports.RICRESTElemCode || (exports.RICRESTElemCode = {}));
var RICCommsMsgTypeCode;
(function (RICCommsMsgTypeCode) {
RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_COMMAND"] = 0] = "MSG_TYPE_COMMAND";
RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_RESPONSE"] = 1] = "MSG_TYPE_RESPONSE";
RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_PUBLISH"] = 2] = "MSG_TYPE_PUBLISH";
RICCommsMsgTypeCode[RICCommsMsgTypeCode["MSG_TYPE_REPORT"] = 3] = "MSG_TYPE_REPORT";
})(RICCommsMsgTypeCode = exports.RICCommsMsgTypeCode || (exports.RICCommsMsgTypeCode = {}));
var RICCommsMsgProtocol;
(function (RICCommsMsgProtocol) {
RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_ROSSERIAL"] = 0] = "MSG_PROTOCOL_ROSSERIAL";
RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_RESERVED_1"] = 1] = "MSG_PROTOCOL_RESERVED_1";
RICCommsMsgProtocol[RICCommsMsgProtocol["MSG_PROTOCOL_RICREST"] = 2] = "MSG_PROTOCOL_RICREST";
})(RICCommsMsgProtocol = exports.RICCommsMsgProtocol || (exports.RICCommsMsgProtocol = {}));
// Message results
var RICMsgResultCode;
(function (RICMsgResultCode) {
RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_TIMEOUT"] = 0] = "MESSAGE_RESULT_TIMEOUT";
RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_OK"] = 1] = "MESSAGE_RESULT_OK";
RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_FAIL"] = 2] = "MESSAGE_RESULT_FAIL";
RICMsgResultCode[RICMsgResultCode["MESSAGE_RESULT_UNKNOWN"] = 3] = "MESSAGE_RESULT_UNKNOWN";
})(RICMsgResultCode = exports.RICMsgResultCode || (exports.RICMsgResultCode = {}));
class RICMsgHandler {
// Constructor
constructor(commsStats, addOnManager) {
// Message numbering and tracking
this._currentMsgNumber = 1;
this._currentMsgHandle = 1;
this._msgTrackInfos = new Array(RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM + 1);
this._msgTrackTimerMs = 50;
this._msgTrackLastCheckIdx = 0;
// report message callback dictionary. Add a callback to subscribe to report messages
this._reportMsgCallbacks = new Map();
// Interface to inform of message results
this._msgResultHandler = null;
// Interface to send messages
this._msgSender = null;
this._commsStats = commsStats;
this._addOnManager = addOnManager;
RICLog_1.default.debug('RICMsgHandler constructor');
// Message tracking
for (let i = 0; i < this._msgTrackInfos.length; i++) {
this._msgTrackInfos[i] = new RICMsgTrackInfo_1.RICMsgTrackInfo();
}
// Timer for checking messages
setTimeout(async () => {
this._onMsgTrackTimer(true);
}, this._msgTrackTimerMs);
// HDLC used to encode/decode the RICREST protocol
this._miniHDLC = new RICMiniHDLC_1.default();
this._miniHDLC.setOnRxFrame(this._onHDLCFrameDecode.bind(this));
}
registerForResults(msgResultHandler) {
this._msgResultHandler = msgResultHandler;
}
registerMsgSender(RICMessageSender) {
this._msgSender = RICMessageSender;
}
handleNewRxMsg(rxMsg) {
this._miniHDLC.addRxBytes(rxMsg);
// RICLog.verbose(`handleNewRxMsg len ${rxMsg.length} ${RICUtils.bufferToHex(rxMsg)}`)
}
reportMsgCallbacksSet(callbackName, callback) {
this._reportMsgCallbacks.set(callbackName, callback);
}
reportMsgCallbacksDelete(callbackName) {
this._reportMsgCallbacks.delete(callbackName);
}
_onHDLCFrameDecode(rxMsg) {
// Add to stats
this._commsStats.msgRx();
// Validity
if (rxMsg.length < RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS) {
this._commsStats.msgTooShort();
return;
}
// RICLog.verbose(`_onHDLCFrameDecode len ${rxMsg.length}`);
// Decode the RICFrame header
const rxMsgNum = rxMsg[RICProtocolDefs_1.RICSERIAL_MSG_NUM_POS] & 0xff;
const rxProtocol = rxMsg[RICProtocolDefs_1.RICSERIAL_PROTOCOL_POS] & 0x3f;
const rxMsgType = (rxMsg[RICProtocolDefs_1.RICSERIAL_PROTOCOL_POS] >> 6) & 0x03;
// Decode payload
if (rxProtocol == RICProtocolDefs_1.PROTOCOL_RICREST) {
RICLog_1.default.verbose(`_onHDLCFrameDecode RICREST rx msgNum ${rxMsgNum} msgDirn ${rxMsgType} ${RICUtils_1.default.bufferToHex(rxMsg)}`);
// Extract payload
const ricRestElemCode = rxMsg[RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS + RICProtocolDefs_1.RICREST_REST_ELEM_CODE_POS] & 0xff;
if (ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_URL ||
ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_CMDRESPJSON ||
ricRestElemCode == RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME) {
// These are all text-based messages
const restStr = RICUtils_1.default.getStringFromBuffer(rxMsg, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS, rxMsg.length - RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS - RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS - 1);
RICLog_1.default.verbose(`_onHDLCFrameDecode RICREST rx elemCode ${ricRestElemCode} ${restStr}`);
// Check message types
if (rxMsgType == RICCommsMsgTypeCode.MSG_TYPE_RESPONSE) {
// Handle response messages
this._handleResponseMessages(restStr, rxMsgNum);
}
else if (rxMsgType == RICCommsMsgTypeCode.MSG_TYPE_REPORT) {
// Handle report messages
this._handleReportMessages(restStr);
}
}
else {
const binMsgLen = rxMsg.length - RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS - RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS;
RICLog_1.default.debug(`_onHDLCFrameDecode RICREST rx binary message elemCode ${ricRestElemCode} len ${binMsgLen}`);
}
}
else if (rxProtocol == RICCommsMsgProtocol.MSG_PROTOCOL_ROSSERIAL) {
// Extract ROSSerial messages - decoded messages returned via _msgResultHandler
RICROSSerial_1.RICROSSerial.decode(rxMsg, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS, this._msgResultHandler, this._commsStats, this._addOnManager);
}
else {
RICLog_1.default.warn(`_onHDLCFrameDecode unsupported protocol ${rxProtocol}`);
}
}
_handleResponseMessages(restStr, rxMsgNum) {
try {
let msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_UNKNOWN;
const msgRsltJsonObj = JSON.parse(restStr);
if ('rslt' in msgRsltJsonObj) {
const rsltStr = msgRsltJsonObj.rslt.toLowerCase();
if (rsltStr === 'ok') {
RICLog_1.default.verbose(`_handleResponseMessages RICREST rslt Ok ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${msgRsltJsonObj.rslt}`);
msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_OK;
}
else if (rsltStr === 'fail') {
msgRsltCode = RICMsgResultCode.MESSAGE_RESULT_FAIL;
RICLog_1.default.warn(`_handleResponseMessages RICREST rslt fail ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} resp ${restStr}`);
}
else {
RICLog_1.default.warn(`_handleResponseMessages RICREST rslt not recognized ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`);
}
}
else {
RICLog_1.default.warn(`_handleResponseMessages RICREST response doesn't contain rslt ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()}resp ${restStr}`);
}
// Handle matching of request and response
this.msgTrackingRxRespMsg(rxMsgNum, msgRsltCode, msgRsltJsonObj);
}
catch (excp) {
if (excp instanceof Error) {
RICLog_1.default.warn(`_handleResponseMessages Failed to parse JSON ${rxMsgNum == 0 ? "unnumbered" : "msgNum " + rxMsgNum.toString()} JSON STR ${restStr} resp ${excp.toString()}`);
}
}
}
_handleReportMessages(restStr) {
try {
const reportMsg = JSON.parse(restStr);
reportMsg.timeReceived = Date.now();
RICLog_1.default.debug(`_handleReportMessages ${JSON.stringify(reportMsg)}`);
this._reportMsgCallbacks.forEach((callback) => callback(reportMsg));
}
catch (excp) {
if (excp instanceof Error) {
RICLog_1.default.warn(`_handleReportMessages Failed to parse JSON report ${excp.toString()}`);
}
}
}
async sendRICRESTURL(cmdStr, msgTimeoutMs = undefined) {
// Send
return this.sendRICREST(cmdStr, RICRESTElemCode.RICREST_ELEM_CODE_URL, msgTimeoutMs);
}
async sendRICRESTCmdFrame(cmdStr, msgTimeoutMs = undefined) {
// Send
return this.sendRICREST(cmdStr, RICRESTElemCode.RICREST_ELEM_CODE_COMMAND_FRAME, msgTimeoutMs);
}
async sendRICREST(cmdStr, ricRESTElemCode, msgTimeoutMs = undefined) {
// Put cmdStr into buffer
const cmdStrTerm = new Uint8Array(cmdStr.length + 1);
RICUtils_1.default.addStringToBuffer(cmdStrTerm, cmdStr, 0);
cmdStrTerm[cmdStrTerm.length - 1] = 0;
// Send
return this.sendRICRESTBytes(cmdStrTerm, ricRESTElemCode, true, msgTimeoutMs);
}
async sendRICRESTBytes(cmdBytes, ricRESTElemCode, withResponse, msgTimeoutMs = undefined) {
// Form message
const cmdMsg = new Uint8Array(cmdBytes.length + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS);
cmdMsg[RICProtocolDefs_1.RICREST_REST_ELEM_CODE_POS] = ricRESTElemCode;
cmdMsg.set(cmdBytes, RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS);
// Send
return this.sendMsgAndWaitForReply(cmdMsg, RICCommsMsgTypeCode.MSG_TYPE_COMMAND, RICCommsMsgProtocol.MSG_PROTOCOL_RICREST, withResponse, msgTimeoutMs);
}
async sendMsgAndWaitForReply(msgPayload, msgDirection, msgProtocol, withResponse, msgTimeoutMs) {
// Check there is a sender
if (!this._msgSender) {
throw new Error('sendMsgAndWaitForReply failed no sender');
}
// Frame the message
const framedMsg = this.frameCommsMsg(msgPayload, msgDirection, msgProtocol, true);
if (!framedMsg) {
throw new Error('sendMsgAndWaitForReply failed to frame message');
}
// Debug
// RICLog.verbose(
// `sendMsgAndWaitForReply ${RICUtils.bufferToHex(framedMsg)}`,
// );
// Return a promise that will be resolved when a reply is received or timeout occurs
const promise = new Promise((resolve, reject) => {
// Update message tracking
this.msgTrackingTxCmdMsg(framedMsg, withResponse, msgTimeoutMs, resolve, reject);
this._currentMsgHandle++;
});
return promise;
}
frameCommsMsg(msgPayload, msgDirection, msgProtocol, isNumbered) {
// Header
const msgBuf = new Uint8Array(msgPayload.length + RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS);
msgBuf[0] = isNumbered ? this._currentMsgNumber & 0xff : 0;
msgBuf[1] = (msgDirection << 6) + msgProtocol;
// Payload
msgBuf.set(msgPayload, RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS);
// Wrap into HDLC
return this._miniHDLC.encode(msgBuf);
}
msgTrackingTxCmdMsg(msgFrame, withResponse, msgTimeoutMs, resolve, reject) {
// Record message re-use of number
if (this._msgTrackInfos[this._currentMsgNumber].msgOutstanding) {
this._commsStats.recordMsgNumCollision();
}
// Set tracking info
this._msgTrackInfos[this._currentMsgNumber].set(true, msgFrame, withResponse, this._currentMsgHandle, msgTimeoutMs, resolve, reject);
// Debug
RICLog_1.default.verbose(`msgTrackingTxCmdMsg msgNum ${this._currentMsgNumber} msg ${RICUtils_1.default.bufferToHex(msgFrame)} sanityCheck ${this._msgTrackInfos[this._currentMsgNumber].msgOutstanding}`);
// RICLog.debug(
// `msgTrackingTxCmdMsg msgNum ${this._currentMsgNumber} msgLen ${msgFrame.length}}`,
// );
// Stats
this._commsStats.msgTx();
// Bump msg number
if (this._currentMsgNumber == RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM) {
this._currentMsgNumber = 1;
}
else {
this._currentMsgNumber++;
}
}
msgTrackingRxRespMsg(msgNum, msgRsltCode, msgRsltJsonObj) {
// Check message number
if (msgNum == 0) {
// Callback on unnumbered message
if (this._msgResultHandler !== null)
this._msgResultHandler.onRxUnnumberedMsg(msgRsltJsonObj);
return;
}
if (msgNum > RICMsgTrackInfo_1.RICMsgTrackInfo.MAX_MSG_NUM) {
RICLog_1.default.warn('msgTrackingRxRespMsg msgNum > 255');
return;
}
if (!this._msgTrackInfos[msgNum].msgOutstanding) {
RICLog_1.default.warn(`msgTrackingRxRespMsg unmatched msgNum ${msgNum}`);
this._commsStats.recordMsgNumUnmatched();
return;
}
// Handle message
RICLog_1.default.verbose(`msgTrackingRxRespMsg Message response received msgNum ${msgNum}`);
this._commsStats.recordMsgResp(Date.now() - this._msgTrackInfos[msgNum].msgSentMs);
this._msgCompleted(msgNum, msgRsltCode, msgRsltJsonObj);
}
_msgCompleted(msgNum, msgRsltCode, msgRsltObj) {
// Lookup message in tracking
const msgHandle = this._msgTrackInfos[msgNum].msgHandle;
this._msgTrackInfos[msgNum].msgOutstanding = false;
// Check if message result handler should be informed
if (this._msgResultHandler !== null) {
this._msgResultHandler.onRxReply(msgHandle, msgRsltCode, msgRsltObj);
}
// Handle reply
// if (msgRsltCode === RICMsgResultCode.MESSAGE_RESULT_OK) {
const resolve = this._msgTrackInfos[msgNum].resolve;
if (resolve) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
RICLog_1.default.debug(`_msgCompleted resolve ${msgRsltCode} ${JSON.stringify(msgRsltObj)}`);
resolve(msgRsltObj);
}
// } else {
// const reject = this._msgTrackInfos[msgNum].reject;
// if (reject) {
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// try {
// RICLog.debug(`_msgCompleted reject rsltCode ${msgRsltCode}`);
// // (reject as any)(new Error(`Message failed msgNum ${msgNum} rslt ${msgRsltCode}`));
// } catch (excp: unknown) {
// RICLog.warn(`_msgCompleted reject ${excp}`);
// }
// }
// }
// No longer waiting for reply
this._msgTrackInfos[msgNum].resolve = null;
this._msgTrackInfos[msgNum].reject = null;
}
// Check message timeouts
async _onMsgTrackTimer(chainRecall) {
if (this._msgSender !== null) {
// Handle message tracking
for (let loopIdx = 0; loopIdx < this._msgTrackInfos.length; loopIdx++) {
// Index to check
const checkIdx = this._msgTrackLastCheckIdx;
this._msgTrackLastCheckIdx = (checkIdx + 1) % this._msgTrackInfos.length;
// Check if message is outstanding
if (!this._msgTrackInfos[checkIdx].msgOutstanding)
continue;
// Get message timeout and ensure valid
let msgTimeoutMs = this._msgTrackInfos[checkIdx].msgTimeoutMs;
if (msgTimeoutMs === undefined) {
msgTimeoutMs = RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RESPONSE_TIMEOUT_MS;
}
// Check for timeout (or never sent)
if ((this._msgTrackInfos[checkIdx].retryCount === 0) || (Date.now() > this._msgTrackInfos[checkIdx].msgSentMs + msgTimeoutMs)) {
// Debug
RICLog_1.default.debug(`msgTrackTimer msgNum ${checkIdx} ${this._msgTrackInfos[checkIdx].retryCount === 0 ? 'first send' : 'timeout - retrying'}`);
// RICLog.verbose(`msgTrackTimer msg ${RICUtils.bufferToHex(this._msgTrackInfos[i].msgFrame)}`);
// Handle timeout (or first send)
if (this._msgTrackInfos[checkIdx].retryCount < RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RETRY_COUNT) {
this._msgTrackInfos[checkIdx].retryCount++;
try {
// Send the message
if (!await this._msgSender.sendTxMsg(this._msgTrackInfos[checkIdx].msgFrame, this._msgTrackInfos[checkIdx].withResponse)) {
RICLog_1.default.warn(`msgTrackTimer Message send failed msgNum ${checkIdx}`);
this._msgCompleted(checkIdx, RICMsgResultCode.MESSAGE_RESULT_FAIL, null);
this._commsStats.recordMsgNoConnection();
}
// Message sent ok so break here
break;
}
catch (error) {
RICLog_1.default.warn(`Retry message failed ${error}`);
}
this._commsStats.recordMsgRetry();
this._msgTrackInfos[checkIdx].msgSentMs = Date.now();
}
else {
RICLog_1.default.warn(`msgTrackTimer TIMEOUT msgNum ${checkIdx} after ${RICMsgTrackInfo_1.RICMsgTrackInfo.MSG_RETRY_COUNT} retries`);
this._msgCompleted(checkIdx, RICMsgResultCode.MESSAGE_RESULT_TIMEOUT, null);
this._commsStats.recordMsgTimeout();
}
}
}
}
// Call again if required
if (chainRecall) {
setTimeout(async () => {
this._onMsgTrackTimer(true);
}, this._msgTrackTimerMs);
}
}
encodeFileStreamBlock(blockContents, blockStart, streamID) {
// Create entire message buffer (including protocol wrappers)
const msgBuf = new Uint8Array(blockContents.length + 4 + RICProtocolDefs_1.RICREST_HEADER_PAYLOAD_POS + RICProtocolDefs_1.RICSERIAL_PAYLOAD_POS);
let msgBufPos = 0;
// RICSERIAL protocol
msgBuf[msgBufPos++] = 0; // not numbered
msgBuf[msgBufPos++] =
(RICCommsMsgTypeCode.MSG_TYPE_COMMAND << 6) +
RICCommsMsgProtocol.MSG_PROTOCOL_RICREST;
// RICREST protocol
msgBuf[msgBufPos++] = RICRESTElemCode.RICREST_ELEM_CODE_FILEBLOCK;
// Buffer header
msgBuf[msgBufPos++] = streamID & 0xff;
msgBuf[msgBufPos++] = (blockStart >> 16) & 0xff;
msgBuf[msgBufPos++] = (blockStart >> 8) & 0xff;
msgBuf[msgBufPos++] = blockStart & 0xff;
// Copy block info
msgBuf.set(blockContents, msgBufPos);
return msgBuf;
}
async sendFileBlock(blockContents, blockStart) {
const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, 0);
// // Debug
// RICLog.debug(
// `sendFileBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`,
// );
// Send
try {
// Send
if (this._msgSender) {
// Wrap into HDLC
const framedMsg = this._miniHDLC.encode(msgBuf);
// Send
return this._msgSender.sendTxMsg(framedMsg, true);
}
}
catch (error) {
RICLog_1.default.warn(`RICMsgHandler sendFileBlock error${error}`);
}
return false;
}
async sendStreamBlock(blockContents, blockStart, streamID) {
// Ensure any waiting messages are sent first
await this._onMsgTrackTimer(false);
// Encode message
const msgBuf = this.encodeFileStreamBlock(blockContents, blockStart, streamID);
// // Debug
// RICLog.debug(
// `sendStreamBlock frameLen ${msgBuf.length} start ${blockStart} end ${blockEnd} len ${blockLen}`,
// );
// Send
try {
// Send
if (this._msgSender) {
// Wrap into HDLC
const framedMsg = this._miniHDLC.encode(msgBuf);
// Send
return await this._msgSender.sendTxMsg(framedMsg, true);
}
}
catch (error) {
RICLog_1.default.warn(`RICMsgHandler sendStreamBlock error${error}`);
}
return false;
}
}
exports.default = RICMsgHandler;
//# sourceMappingURL=RICMsgHandler.js.map