UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

267 lines (230 loc) 7.59 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides class sap.ui.core.ws.WebSocket for WebSocket support with SAP-PCP Protocol sap.ui.define(['./WebSocket', "sap/base/Log"], function(WebSocket, Log) { "use strict"; /** * Creates a new WebSocket connection and uses the pcp-protocol for communication. * * @param {string} sUrl relative or absolute URL for WebSocket connection. * @param {array} [aProtocols] array of protocols as strings, a single protocol as a string. * Protocol(s) should be selected from {@link sap.ui.core.ws.SapPcpWebSocket.SUPPORTED_PROTOCOLS}. * * @public * * @class WebSocket class implementing the pcp-protocol. * @extends sap.ui.core.ws.WebSocket * @author SAP SE * @version 1.111.5 * @alias sap.ui.core.ws.SapPcpWebSocket */ var SapPcpWebSocket = WebSocket.extend("sap.ui.core.ws.SapPcpWebSocket", /** @lends sap.ui.core.ws.SapPcpWebSocket.prototype */ { constructor: function(sUrl, aProtocols) { WebSocket.apply(this, arguments); } }); /** * The <code>message</code> event is fired, when a message was received. * * @name sap.ui.core.ws.SapPcpWebSocket#message * @event * @param {sap.ui.base.Event} oControlEvent * @param {sap.ui.base.EventProvider} oControlEvent.getSource * @param {object} oControlEvent.getParameters * @param {string} oControlEvent.getParameters.data Received data from the server. * @param {string} oControlEvent.getParameters.pcpFields Received pcpFields as a key-value map. * @public */ /** * Fires event {@link #event:message message} to attached listeners. * * @param {object} [oParameters] Parameters to pass along with the event * @param {string} [oParameters.data] Received data from the server. * @param {string} [oParameters.pcpFields] Received pcpFields as a key-value map. * @return {this} <code>this</code> to allow method chaining * @protected * @name sap.ui.core.ws.SapPcpWebSocket#fireMessage * @function */ /** * Protocol versions. * * One (or more) of these have to be selected to create an SapPcpWebSocket connection * (or no protocol at all). * * @enum {string} * @public * @static */ SapPcpWebSocket.SUPPORTED_PROTOCOLS = { /** * Protocol v10.pcp.sap.com * @public * @name sap.ui.core.ws.SapPcpWebSocket.SUPPORTED_PROTOCOLS.v10 */ v10 : "v10.pcp.sap.com" }; /** * RegEx to get pcp-header fields. * * @private */ SapPcpWebSocket._deserializeRegexp = /((?:[^:\\]|(?:\\.))+):((?:[^:\\\n]|(?:\\.))*)/; /** * Separator between header-fields and message body. * * @private */ SapPcpWebSocket._SEPARATOR = "\n\n"; /** * pcp-action value. * * @private */ SapPcpWebSocket._MESSAGE = "MESSAGE"; /** * Internal handler for open-event. * * @private */ SapPcpWebSocket.prototype._onopen = function() { var bSuccess = false; if (this.getProtocol() === "") { bSuccess = true; } else { for (var protocol in SapPcpWebSocket.SUPPORTED_PROTOCOLS) { if (SapPcpWebSocket.SUPPORTED_PROTOCOLS.hasOwnProperty(protocol)) { if (SapPcpWebSocket.SUPPORTED_PROTOCOLS[protocol] === this.getProtocol()) { bSuccess = true; break; } } } } if (bSuccess) { this.fireOpen(); } else { Log.error("Unsupported protocol '" + this.getProtocol() + "' selected by the server. " + "Connection will be closed."); this.close("Unsupported protocol selected by the server"); } }; /** * Internal handler for message-event. * * @private */ SapPcpWebSocket.prototype._onmessage = function(oMessageEvent) { var iSplitPos = -1, oEventData = {}; if (typeof oMessageEvent.data === "string") { iSplitPos = oMessageEvent.data.indexOf(SapPcpWebSocket._SEPARATOR); } if (iSplitPos !== -1) { oEventData.pcpFields = this._extractPcpFields(oMessageEvent.data.substring(0, iSplitPos)); oEventData.data = oMessageEvent.data.substr(iSplitPos + SapPcpWebSocket._SEPARATOR.length); } else { Log.warning("Invalid PCP message received: " + oMessageEvent.data); oEventData.pcpFields = {}; oEventData.data = oMessageEvent.data; // Fall back, just pass through original data } this.fireMessage(oEventData); }; /** * Extracts the pcp-fields from a header string. * * @param {string} sHeader Header as string * @return {object} oPcpFields extracted fields as key-value map * @private */ SapPcpWebSocket.prototype._extractPcpFields = function(sHeader) { var aFields = sHeader.split("\n"), aLine = [], oPcpFields = {}; for (var i = 0; i < aFields.length; i++) { aLine = aFields[i].match(SapPcpWebSocket._deserializeRegexp); if (aLine && aLine.length === 3) { oPcpFields[this._unescape(aLine[1])] = this._unescape(aLine[2]); } } return oPcpFields; }; /** * Unescapes a string. * * @param {string} sEscaped escaped string * @return {string} Unescaped string * @private */ SapPcpWebSocket.prototype._unescape = function(sEscaped) { var aParts = sEscaped.split("\u0008"), sUnescaped = ""; for (var i = 0; i < aParts.length; i++) { // eslint-disable-next-line no-control-regex -- \x08 is used as separator in socket messages aParts[i] = aParts[i].replace(/\\\\/g, "\u0008").replace(/\\:/g, ':').replace(/\\n/g, '\n').replace(/\u0008/g, "\\"); } sUnescaped = aParts.join("\u0008"); return sUnescaped; }; /** * Serializes pcp-fields into a string. * * @param {object} oPcpFields key-value map with pcp-fields * @param {string} sMessageType message-type, one of 'string', 'blob' or 'arraybuffer'. * @param {string} sPcpAction pcp-action value * @returns {string} serialized pcp-fields * @private */ SapPcpWebSocket.prototype._serializePcpFields = function(oPcpFields, sMessageType, sPcpAction) { var oSerialized = "", sFieldName = "", sPcpBodyType = ""; if (sMessageType === 'string') { sPcpBodyType = 'text'; } else if (sMessageType === 'blob' || sMessageType === 'arraybuffer') { sPcpBodyType = 'binary'; } if (oPcpFields && typeof oPcpFields === 'object') { for (sFieldName in oPcpFields) { if (oPcpFields.hasOwnProperty(sFieldName) && sFieldName.indexOf('pcp-') !== 0) { oSerialized += this._escape(sFieldName) + ":" + this._escape(String(oPcpFields[sFieldName])) + "\n"; } } } return "pcp-action:" + sPcpAction + "\npcp-body-type:" + sPcpBodyType + "\n" + oSerialized + "\n"; }; /** * Escapes a string. * * @param {string} sUnEscaped unescaped string * @returns {string} Escaped string * @private */ SapPcpWebSocket.prototype._escape = function(sUnEscaped) { return sUnEscaped.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/\n/g, '\\n'); }; // Public Methods /** * Sends a message and optional pcp-header-fields using the pcp-protocol. * * If the connection is not yet opened, the message will be queued and sent * when the connection is established. * * @param {string|Blob|ArrayBuffer} message message to send * @param {object} [oPcpFields] additional pcp-fields as key-value map * @return {this} Reference to <code>this</code> to allow method chaining * @public */ SapPcpWebSocket.prototype.send = function(message, oPcpFields) { var sMessageType = typeof message, sPcpFields = ""; sPcpFields = this._serializePcpFields(oPcpFields, sMessageType, SapPcpWebSocket._MESSAGE); WebSocket.prototype.send.call(this, sPcpFields + message); return this; }; return SapPcpWebSocket; });