@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
453 lines (413 loc) • 13.4 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/ui/core/EventBus",
"sap/base/util/includes",
"sap/base/util/isPlainObject",
"sap/base/Log"
],
function (
EventBus,
includes,
isPlainObject,
Log
) {
"use strict";
var oInstance;
var s_UI5_MARKER = '______UI5______';
/**
* @class
* Responsible for the communication between different window objects.
*
* <h3>Overview</h3>
* This class is a singleton. The class instance can be retrieved as follows:
* <ul>
* <li>via the constructor <code>new sap.ui.core.postmessage.Bus()</code></li>
* <li>via the static method <code>sap.ui.core.postmessage.Bus.getInstance()</code></li>
* </ul>
*
* For supported data types for payload messages, see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm}.
*
* @extends sap.ui.core.EventBus
* @alias sap.ui.core.postmessage.Bus
* @author SAP SE
* @since 1.56.0
* @version 1.87.1
* @private
* @ui5-restricted sap.ui.core.support, sap.ui.support, sap.ui.rta
*/
var PostMessageBus = EventBus.extend("sap.ui.core.postmessage.Bus", {
constructor: function () {
if (oInstance) {
return oInstance;
}
oInstance = this;
EventBus.apply(this, arguments);
this._aAcceptedOrigins = [
// Accept messages from the same origin automatically
window.location.origin
];
this._aDeclinedOrigins = [];
this._oPendingProcess = null;
this._aEventQueue = [];
this._receiver = this._receiver.bind(this);
window.addEventListener('message', this._receiver);
}
});
PostMessageBus.event = {
CONNECT: '______CONNECT______',
READY: '______READY______',
ACCEPTED: '______ACCEPTED______',
DECLINED: '______DECLINED______'
};
PostMessageBus.prototype.destroy = function () {
window.removeEventListener('message', this._receiver);
this._aEventQueue = []; // Erase event queue
EventBus.prototype.destroy.apply(this, arguments);
oInstance = undefined;
this.bIsDestroyed = true;
};
/**
* Returns an instance of the class.
* @returns {sap.ui.core.postmessage.Bus} An instance of the class.
* @static
* @public
*/
PostMessageBus.getInstance = function () {
if (!oInstance) {
oInstance = new PostMessageBus();
}
return oInstance;
};
/**
* Publish message into cross-window communication channel
* @param {object} mParameters - Map with params
* @param {Window} mParameters.target - Window object of receiving window
* @param {string} mParameters.origin - Origin of the receiving window, e.g. http://example.com
* @param {string} mParameters.channelId - Channel identifier
* @param {string} mParameters.eventId - Event identifier
* @param {*} [mParameters.data] - Payload data. For supported data types see - {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm}
* @throws {TypeError} when invalid data is specified
* @public
*/
PostMessageBus.prototype.publish = function (mParameters) {
var oTarget = mParameters.target;
var sOrigin = mParameters.origin;
var sChannelId = mParameters.channelId;
var sEventId = mParameters.eventId;
var vData = mParameters.data;
// Defaults for READY event
if (sEventId === PostMessageBus.event.READY) {
if (!oTarget) {
if (window.opener && window.opener !== window) {
oTarget = window.opener;
} else if (window.parent !== window) {
oTarget = window.parent;
} else {
return; // Ignore ready event when there is no valid target
}
}
if (!sOrigin) {
sOrigin = '*';
}
}
// Validation
if (
(typeof window === "undefined") || !(oTarget != null && oTarget === oTarget.window)
|| oTarget === window // avoid self-messaging
) {
throw TypeError("Target must be a window object and has to differ from current window");
}
if (typeof sOrigin !== "string") {
throw TypeError("Origin must be a string");
}
if (typeof sChannelId !== "string") {
throw TypeError("ChannelId must be a string");
}
if (typeof sEventId !== "string") {
throw TypeError("EventId must be a string");
}
// Accept host immediately when message is sent
if (
!includes(
[
PostMessageBus.event.READY,
PostMessageBus.event.ACCEPTED,
PostMessageBus.event.DECLINED
],
sEventId
)
&& sOrigin !== '*'
&& !includes(this._aAcceptedOrigins, sOrigin)
) {
this._aAcceptedOrigins.push(sOrigin);
}
var mMessage = {
origin: sOrigin,
channelId: sChannelId,
eventId: sEventId,
data: vData
};
mMessage[s_UI5_MARKER] = true;
oTarget.postMessage(mMessage, sOrigin);
};
/**
* Attaches an event handler to the event with the given identifier on the given event channel
*
* @name sap.ui.core.postmessage.Bus.prototype.subscribe
* @function
*
* @param {string}
* [sChannelId] Channel of the event to subscribe to. If not given, the default channel is used.
* The channel <code>"sap.ui"</code> is reserved by the UI5 framework. An application can listen to
* events on this channel, but is not allowed to publish its own events there.
* @param {string}
* sEventId Identifier of the event to listen for
* @param {function}
* fnFunction Handler function to call when the event occurs; this function is called in the context of the
* <code>oListener</code> instance (if present) or on the event bus instance. Arguments:
* <pre>
* - {string} sChannelId - Channel ID
* - {string} sEventId - Event ID
* - {object} mParameters - Event parameters
* - {Event} originalEvent - Post message original event
* - {string} channelId - Channel ID
* - {string} eventId - Event ID
* - {Window} source - Sender window
* - {string} origin - Sender origin, e.g. https://example.com
* - {*} [data] - Payload data
* </pre>
* @param {object}
* [oListener] Object that wants to be notified when the event occurs (<code>this</code> context within the
* handler function). If it is not specified, the handler function is called in the context of the event bus.
* @return {this} Returns <code>this</code> to allow method chaining
* @public
*/
PostMessageBus.prototype._callListener = function (fnCallback, oListener, sChannelId, sEventId, mData) {
fnCallback.call(oListener, mData);
};
/**
* Gets translation from the message bundle
* @param {string} sKey - Translation key
* @param {string[]} [aParameters] - Translation placeholders
* @return {string} - returns resolved string
* @private
*/
PostMessageBus.prototype._getText = function (sKey, aParameters) {
return sap.ui.getCore().getLibraryResourceBundle(true)
.then(function(oLibraryResourceBundle) { return oLibraryResourceBundle.getText(sKey, aParameters); });
};
/**
* Event handler for receiving incoming post messages
* @param {Event} oEvent - Browser post message event
* @private
*/
PostMessageBus.prototype._receiver = function (oEvent) {
var mData = oEvent.data;
if (
// Ignore non-UI5 messages
!isPlainObject(mData)
|| !mData.hasOwnProperty(s_UI5_MARKER)
) {
return;
}
if (this._oPendingProcess) {
this._aEventQueue.push(oEvent);
} else {
this._oPendingProcess = this._processEvent(oEvent);
}
};
/**
* Process specified event
* @param {Event} oEvent - Browser post message event
* @returns {Promise} - resolves when finished
* @private
*/
PostMessageBus.prototype._processEvent = function (oEvent) {
return new Promise(function (fnResolve, fnReject) {
var mData = oEvent.data;
var sOrigin = oEvent.origin;
// Ignore messages from disabled hosts
if (includes(this._aDeclinedOrigins, sOrigin)) {
fnResolve();
return;
}
switch (mData.eventId) {
case PostMessageBus.event.CONNECT: {
if (typeof mData.data !== "string") {
this.publish({
target: oEvent.source,
origin: oEvent.origin,
channelId: mData.channelId,
eventId: PostMessageBus.event.DECLINED
});
fnResolve();
} else if (includes(this._aAcceptedOrigins, sOrigin)) {
this.publish({
target: oEvent.source,
origin: oEvent.origin,
channelId: mData.channelId,
eventId: PostMessageBus.event.ACCEPTED
});
fnResolve();
} else {
// Show dialog
sap.ui.require(["sap/ui/core/postmessage/confirmationDialog"], function (confirmationDialog) {
this._getText('PostMessage.Message', [mData.data, sOrigin])
.then(function(sText) {
return confirmationDialog(sText);
})
.then(
function () {
this.addAcceptedOrigin(sOrigin);
this.publish({
target: oEvent.source,
origin: oEvent.origin,
channelId: mData.channelId,
eventId: PostMessageBus.event.ACCEPTED
});
}.bind(this),
function () {
this.addDeclinedOrigin(sOrigin);
this.publish({
target: oEvent.source,
origin: oEvent.origin,
channelId: mData.channelId,
eventId: PostMessageBus.event.DECLINED
});
}.bind(this)
)
.then(fnResolve);
}.bind(this), fnReject);
}
break;
}
case PostMessageBus.event.ACCEPTED:
case PostMessageBus.event.DECLINED:
case PostMessageBus.event.READY: {
oEvent.data.data = undefined; // sanitize data payload for system events
this._emitMessage(oEvent);
fnResolve();
break;
}
default: {
if (includes(this._aAcceptedOrigins, sOrigin)) {
this._emitMessage(oEvent);
}
fnResolve();
}
}
}.bind(this))
.catch(function (vError) {
var sMessage;
var sDetail;
if (typeof vError === 'string') {
sMessage = vError;
} else if (vError instanceof Error) {
sMessage = vError.message;
sDetail = vError.stack;
} else {
sMessage = 'Some unexpected error happened during post message processing';
}
Log.error(sMessage, sDetail, 'sap.ui.core.postmessage.Bus');
})
.then(function () {
this._oPendingProcess = (
this._aEventQueue.length > 0
? this._processEvent(this._aEventQueue.shift())
: null
);
}.bind(this));
};
/**
* Sends transformed event to subscribers
* @param {Event} oEvent - Browser post message event
* @private
*/
PostMessageBus.prototype._emitMessage = function (oEvent) {
var sChannelId = oEvent.data.channelId;
var sEventId = oEvent.data.eventId;
EventBus.prototype.publish.call(this, sChannelId, sEventId, {
originalEvent: oEvent,
channelId: sChannelId,
eventId: sEventId,
source: oEvent.source,
origin: oEvent.origin,
data: oEvent.data.data
});
};
/**
* Get list of accepted origins
* @return {string[]} - List of origins, e.g. ['http://example.com:8080', 'http://example.com', ...]
*/
PostMessageBus.prototype.getAcceptedOrigins = function () {
return this._aAcceptedOrigins.slice();
};
/**
* Replace current list with accepted origins with a new one
* @param {string[]} aOrigins - List of origins, e.g. ['http://example.com:8080', 'http://example.com', ...]
*/
PostMessageBus.prototype.setAcceptedOrigins = function (aOrigins) {
if (!Array.isArray(aOrigins)) {
throw new TypeError('Expected an array, but got ' + typeof aOrigins);
}
this._aAcceptedOrigins = aOrigins.slice();
};
/**
* Add specified origin to the existing list of accepted origins
* @param {string} sOrigin - Origin name, e.g. http://example.com:8080
*/
PostMessageBus.prototype.addAcceptedOrigin = function (sOrigin) {
if (typeof sOrigin !== 'string') {
throw new TypeError('Expected a string, but got ' + typeof sOrigin);
}
if (!includes(this._aAcceptedOrigins, sOrigin)) {
this._aAcceptedOrigins.push(sOrigin);
}
};
/**
* Erase list of accepted origins
*/
PostMessageBus.prototype.resetAcceptedOrigins = function () {
this.setAcceptedOrigins([]);
};
/**
* Get list of declined origins
* @return {string[]} - List of origins, e.g. ['http://example.com:8080', 'http://example.com', ...]
*/
PostMessageBus.prototype.getDeclinedOrigins = function () {
return this._aDeclinedOrigins.slice();
};
/**
* Replace current list with declined origins with a new one
* @param {string[]} aOrigins - List of origins, e.g. ['http://example.com:8080', 'http://example.com', ...]
*/
PostMessageBus.prototype.setDeclinedOrigins = function (aOrigins) {
if (!Array.isArray(aOrigins)) {
throw new TypeError('Expected an array, but got ' + typeof aOrigins);
}
this._aDeclinedOrigins = aOrigins.slice();
};
/**
* Add specified origin to the existing list of declined origins
* @param {string} sOrigin - Origin name, e.g. http://example.com:8080
*/
PostMessageBus.prototype.addDeclinedOrigin = function (sOrigin) {
if (typeof sOrigin !== 'string') {
throw new TypeError('Expected a string, but got ' + typeof sOrigin);
}
if (!includes(this._aDeclinedOrigins, sOrigin)) {
this._aDeclinedOrigins.push(sOrigin);
}
};
/**
* Erase list of declined origins
*/
PostMessageBus.prototype.resetDeclinedOrigins = function () {
this.setDeclinedOrigins([]);
};
return PostMessageBus;
});