mirror of https://github.com/jitsi/jitsi-meet
The transport module will be handling all external app communication.pull/1539/head
parent
6c676f8d5f
commit
0dff35c0db
@ -0,0 +1,7 @@ |
||||
declare var getConfigParamsFromUrl: Function; |
||||
|
||||
/** |
||||
* JitsiMeetExternalAPI id - unique for a webpage. |
||||
*/ |
||||
export const API_ID |
||||
= getConfigParamsFromUrl().jitsi_meet_external_api_id; |
@ -0,0 +1,2 @@ |
||||
export default from './API'; |
||||
export * from './constants'; |
@ -0,0 +1,3 @@ |
||||
module.exports = { |
||||
'extends': '../../react/.eslintrc.js' |
||||
}; |
@ -0,0 +1,141 @@ |
||||
import Postis from 'postis'; |
||||
|
||||
/** |
||||
* The list of methods of incomming postis messages that we have to support for |
||||
* backward compatability for the users that are directly sending messages to |
||||
* Jitsi Meet (without using external_api.js) |
||||
* |
||||
* @type {string[]} |
||||
*/ |
||||
const legacyIncomingMethods = [ 'display-name', 'toggle-audio', 'toggle-video', |
||||
'toggle-film-strip', 'toggle-chat', 'toggle-contact-list', |
||||
'toggle-share-screen', 'video-hangup', 'email', 'avatar-url' ]; |
||||
|
||||
/** |
||||
* The list of methods of outgoing postis messages that we have to support for |
||||
* backward compatability for the users that are directly listening to the |
||||
* postis messages send by Jitsi Meet(without using external_api.js). |
||||
* |
||||
* @type {string[]} |
||||
*/ |
||||
const legacyOutgoingMethods = [ 'display-name-change', 'incoming-message', |
||||
'outgoing-message', 'participant-joined', 'participant-left', |
||||
'video-ready-to-close', 'video-conference-joined', |
||||
'video-conference-left' ]; |
||||
|
||||
/** |
||||
* The postis method used for all messages. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
const POSTIS_METHOD_NAME = 'data'; |
||||
|
||||
/** |
||||
* The default options for postis. |
||||
* |
||||
* @type {Object} |
||||
*/ |
||||
const defaultPostisOptions = { |
||||
window: window.opener || window.parent |
||||
}; |
||||
|
||||
/** |
||||
* Implements message transport using the postMessage API. |
||||
*/ |
||||
export default class PostMessageTransportBackend { |
||||
/** |
||||
* Creates new PostMessageTransportBackend instance. |
||||
* |
||||
* @param {Object} options - Optional parameters for configuration of the |
||||
* transport. |
||||
*/ |
||||
constructor(options = {}) { |
||||
const postisOptions = Object.assign({}, defaultPostisOptions, options); |
||||
|
||||
this.postis = Postis(postisOptions); |
||||
|
||||
// backward compatability
|
||||
legacyIncomingMethods.forEach(method => |
||||
this.postis.listen(method, |
||||
params => this._onPostisDataReceived(method, params))); |
||||
|
||||
this.postis.listen(POSTIS_METHOD_NAME, data => |
||||
this._dataReceivedCallBack(data)); |
||||
|
||||
this._dataReceivedCallBack = () => { |
||||
// do nothing until real callback is set;
|
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Handles incomming legacy postis data. |
||||
* |
||||
* @param {string} method - The method property from postis data object. |
||||
* @param {Any} params - The params property from postis data object. |
||||
* @returns {void} |
||||
*/ |
||||
_onPostisDataReceived(method, params = {}) { |
||||
const newData = { |
||||
data: { |
||||
name: method, |
||||
data: params |
||||
} |
||||
}; |
||||
|
||||
this._dataReceivedCallBack(newData); |
||||
} |
||||
|
||||
/** |
||||
* Sends the passed data via postis using the old format. |
||||
* |
||||
* @param {Object} data - The data to be sent. |
||||
* @returns {void} |
||||
*/ |
||||
_sendLegacyData(data) { |
||||
const method = data.name; |
||||
|
||||
if (method && legacyOutgoingMethods.indexOf(method) !== -1) { |
||||
this.postis.send({ |
||||
method, |
||||
params: data.data |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Disposes the allocated resources. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
dispose() { |
||||
this.postis.destroy(); |
||||
} |
||||
|
||||
/** |
||||
* Sends the passed data. |
||||
* |
||||
* @param {Object} data - The data to be sent. |
||||
* @returns {void} |
||||
*/ |
||||
send(data) { |
||||
this.postis.send({ |
||||
method: POSTIS_METHOD_NAME, |
||||
params: data |
||||
}); |
||||
|
||||
// For the legacy use case we don't need any new fields defined in
|
||||
// Transport class. That's why we are passing only the original object
|
||||
// passed by the consumer of the Transport class which is data.data.
|
||||
this._sendLegacyData(data.data); |
||||
} |
||||
|
||||
/** |
||||
* Sets the callback for receiving data. |
||||
* |
||||
* @param {Function} callback - The new callback. |
||||
* @returns {void} |
||||
*/ |
||||
setDataReceivedCallback(callback) { |
||||
this._dataReceivedCallBack = callback; |
||||
} |
||||
} |
@ -0,0 +1,243 @@ |
||||
import { |
||||
MESSAGE_TYPE_EVENT, |
||||
MESSAGE_TYPE_RESPONSE, |
||||
MESSAGE_TYPE_REQUEST |
||||
} from './constants'; |
||||
|
||||
/** |
||||
* Stores the currnet transport that have to be used. |
||||
*/ |
||||
export default class Transport { |
||||
/** |
||||
* Creates new instance. |
||||
* |
||||
* @param {Object} options - Optional parameters for configuration of the |
||||
* transport. |
||||
*/ |
||||
constructor(options = {}) { |
||||
const { transport } = options; |
||||
|
||||
this._requestID = 0; |
||||
|
||||
this._responseHandlers = new Map(); |
||||
|
||||
this._listeners = new Map(); |
||||
|
||||
this._unprocessedMessages = new Set(); |
||||
|
||||
this.addListener = this.on; |
||||
|
||||
if (transport) { |
||||
this.setTransport(transport); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Disposes the current transport. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_disposeTransport() { |
||||
if (this._transport) { |
||||
this._transport.dispose(); |
||||
this._transport = null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Handles incomming data from the transport. |
||||
* |
||||
* @param {Object} data - The data. |
||||
* @returns {void} |
||||
*/ |
||||
_onDataReceived(data) { |
||||
if (data.type === MESSAGE_TYPE_RESPONSE) { |
||||
const handler = this._responseHandlers.get(data.id); |
||||
|
||||
if (handler) { |
||||
handler(data); |
||||
this._responseHandlers.delete(data.id); |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
if (data.type === MESSAGE_TYPE_REQUEST) { |
||||
this.emit('request', data.data, (result, error) => { |
||||
this._transport.send({ |
||||
type: MESSAGE_TYPE_RESPONSE, |
||||
result, |
||||
error, |
||||
id: data.id |
||||
}); |
||||
}); |
||||
} else { |
||||
this.emit('event', data.data); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Disposes the allocated resources. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
dispose() { |
||||
this._responseHandlers.clear(); |
||||
this._unprocessedMessages.clear(); |
||||
this.removeAllListeners(); |
||||
this._disposeTransport(); |
||||
} |
||||
|
||||
/** |
||||
* Calls each of the listeners registered for the event named eventName, in |
||||
* the order they were registered, passing the supplied arguments to each. |
||||
* |
||||
* @param {string} eventName - The name of the event. |
||||
* @returns {boolean} True if the event had listeners, false otherwise. |
||||
*/ |
||||
emit(eventName, ...args) { |
||||
const listenersForEvent = this._listeners.get(eventName); |
||||
|
||||
if (!listenersForEvent || listenersForEvent.size === 0) { |
||||
this._unprocessedMessages.add(args); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
let isProcessed = false; |
||||
|
||||
listenersForEvent.forEach(listener => { |
||||
isProcessed = listener(...args) || isProcessed; |
||||
}); |
||||
|
||||
if (!isProcessed) { |
||||
this._unprocessedMessages.add(args); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Adds the listener function to the listeners collection for the event |
||||
* named eventName. |
||||
* |
||||
* @param {string} eventName - The name of the event. |
||||
* @param {Function} listener - The listener that will be added. |
||||
* @returns {Transport} References to the instance of Transport class, so |
||||
* that calls can be chained. |
||||
*/ |
||||
on(eventName, listener) { |
||||
let listenersForEvent = this._listeners.get(eventName); |
||||
|
||||
if (!listenersForEvent) { |
||||
listenersForEvent = new Set(); |
||||
this._listeners.set(eventName, listenersForEvent); |
||||
} |
||||
|
||||
listenersForEvent.add(listener); |
||||
|
||||
this._unprocessedMessages.forEach(args => { |
||||
if (listener(...args)) { |
||||
this._unprocessedMessages.delete(args); |
||||
} |
||||
}); |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes all listeners, or those of the specified eventName. |
||||
* |
||||
* @param {string} [eventName] - The name of the event. |
||||
* @returns {Transport} References to the instance of Transport class, so |
||||
* that calls can be chained. |
||||
*/ |
||||
removeAllListeners(eventName) { |
||||
if (eventName) { |
||||
this._listeners.delete(eventName); |
||||
} else { |
||||
this._listeners.clear(); |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Removes the listener function from the listeners collection for the event |
||||
* named eventName. |
||||
* |
||||
* @param {string} eventName - The name of the event. |
||||
* @param {Function} listener - The listener that will be removed. |
||||
* @returns {Transport} References to the instance of Transport class, so |
||||
* that calls can be chained. |
||||
*/ |
||||
removeListener(eventName, listener) { |
||||
const listenersForEvent = this._listeners.get(eventName); |
||||
|
||||
if (listenersForEvent) { |
||||
listenersForEvent.delete(listener); |
||||
} |
||||
|
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Sends the passed data. |
||||
* |
||||
* @param {Object} data - The data to be sent. |
||||
* @returns {void} |
||||
*/ |
||||
sendEvent(data = {}) { |
||||
if (this._transport) { |
||||
this._transport.send({ |
||||
type: MESSAGE_TYPE_EVENT, |
||||
data |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sending request. |
||||
* |
||||
* @param {Object} data - The data for the request. |
||||
* @returns {Promise} |
||||
*/ |
||||
sendRequest(data) { |
||||
if (!this._transport) { |
||||
return Promise.reject(new Error('No transport defined!')); |
||||
} |
||||
this._requestID++; |
||||
const id = this._requestID; |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
this._responseHandlers.set(this._requestID, response => { |
||||
const { result, error } = response; |
||||
|
||||
if (result) { |
||||
resolve(result); |
||||
} else if (error) { |
||||
reject(error); |
||||
} else { // no response
|
||||
reject(new Error('Unexpected response format!')); |
||||
} |
||||
}); |
||||
|
||||
this._transport.send({ |
||||
id, |
||||
type: MESSAGE_TYPE_REQUEST, |
||||
data |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Changes the current transport. |
||||
* |
||||
* @param {Object} transport - The new transport that will be used. |
||||
* @returns {void} |
||||
*/ |
||||
setTransport(transport) { |
||||
this._disposeTransport(); |
||||
this._transport = transport; |
||||
this._transport.setDataReceivedCallback( |
||||
this._onDataReceived.bind(this)); |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
/** |
||||
* The message type for events. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
export const MESSAGE_TYPE_EVENT = 'event'; |
||||
|
||||
/** |
||||
* The message type for responses. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
export const MESSAGE_TYPE_RESPONSE = 'response'; |
||||
|
||||
/** |
||||
* The message type for requests. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
export const MESSAGE_TYPE_REQUEST = 'request'; |
@ -0,0 +1,31 @@ |
||||
import { API_ID } from '../API'; |
||||
import { getJitsiMeetGlobalNS } from '../util/helpers'; |
||||
|
||||
import Transport from './Transport'; |
||||
import PostMessageTransportBackend from './PostMessageTransportBackend'; |
||||
|
||||
/** |
||||
* Option for the default low level transport. |
||||
* |
||||
* @type {Object} |
||||
*/ |
||||
const postMessageOptions = {}; |
||||
|
||||
if (typeof API_ID === 'number') { |
||||
postMessageOptions.scope |
||||
= `jitsi_meet_external_api_${API_ID}`; |
||||
} |
||||
|
||||
export const transport = new Transport({ |
||||
transport: new PostMessageTransportBackend(postMessageOptions) |
||||
}); |
||||
|
||||
/** |
||||
* Sets the transport to passed transport. |
||||
* |
||||
* @param {Object} newTransport - The new transport. |
||||
* @returns {void} |
||||
*/ |
||||
getJitsiMeetGlobalNS().useNewExternalTransport = function(newTransport) { |
||||
transport.setTransport(newTransport); |
||||
}; |
Loading…
Reference in new issue