You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

440 lines
11 KiB

(function(OCA, OC) {
'use strict';
OCA.SpreedMe = OCA.SpreedMe || {};
function SignalingBase() {
this.sessionId = '';
this.currentCallToken = null;
this.handlers = {};
}
SignalingBase.prototype.on = function(ev, handler) {
if (!this.handlers.hasOwnProperty(ev)) {
this.handlers[ev] = [handler];
} else {
this.handlers[ev].push(handler);
}
};
SignalingBase.prototype.emit = function(/*ev, data*/) {
// Override in subclasses.
};
SignalingBase.prototype._trigger = function(ev, args) {
var handlers = this.handlers[ev];
if (!handlers) {
return;
}
handlers = handlers.slice(0);
for (var i = 0, len = handlers.length; i < len; i++) {
var handler = handlers[i];
handler.apply(handler, args);
}
};
SignalingBase.prototype.getSessionid = function() {
return this.sessionId;
};
SignalingBase.prototype.disconnect = function() {
this.sessionId = '';
this.currentCallToken = null;
};
SignalingBase.prototype.emit = function(ev, data) {
switch (ev) {
case 'join':
var callback = arguments[2];
var token = data;
this.joinCall(token, callback);
break;
case 'leave':
this.leaveCurrentCall();
break;
case 'message':
this.sendCallMessage(data);
break;
}
};
SignalingBase.prototype.leaveCurrentCall = function() {
if (this.currentCallToken) {
this.leaveCall(this.currentCallToken);
this.currentCallToken = null;
}
};
SignalingBase.prototype.leaveAllCalls = function() {
// Override if necessary.
};
SignalingBase.prototype.setRoomCollection = function(rooms) {
this.roomCollection = rooms;
return this.syncRooms();
};
/**
* Sets a single room to be synced.
*
* If there is a RoomCollection set the synchronization will be performed on
* the RoomCollection instead and the given room will be ignored; setting a
* single room is intended to be used only on public pages.
*
* @param OCA.SpreedMe.Models.Room room the room to sync.
*/
SignalingBase.prototype.setRoom = function(room) {
this.room = room;
return this.syncRooms();
};
SignalingBase.prototype.syncRooms = function() {
var defer = $.Deferred();
if (this.roomCollection && oc_current_user) {
this.roomCollection.fetch({
success: function(data) {
defer.resolve(data);
}
});
} else if (this.room) {
this.room.fetch({
success: function(data) {
defer.resolve(data);
}
});
} else {
defer.resolve([]);
}
return defer;
};
// Connection to the internal signaling server provided by the app.
function InternalSignaling() {
SignalingBase.prototype.constructor.apply(this, arguments);
this.spreedArrayConnection = [];
this.pingFails = 0;
this.pingInterval = null;
this.isSendingMessages = false;
this.sendInterval = window.setInterval(function(){
this.sendPendingMessages();
}.bind(this), 500);
}
InternalSignaling.prototype = new SignalingBase();
InternalSignaling.prototype.constructor = InternalSignaling;
InternalSignaling.prototype.disconnect = function() {
this.spreedArrayConnection = [];
if (this.source) {
this.source.close();
this.source = null;
}
if (this.sendInterval) {
window.clearInterval(this.sendInterval);
this.sendInterval = null;
}
if (this.pingInterval) {
window.clearInterval(this.pingInterval);
this.pingInterval = null;
}
if (this.roomPoller) {
window.clearInterval(this.roomPoller);
this.roomPoller = null;
}
SignalingBase.prototype.disconnect.apply(this, arguments);
};
InternalSignaling.prototype.on = function(ev/*, handler*/) {
SignalingBase.prototype.on.apply(this, arguments);
switch (ev) {
case 'connect':
// A connection is established if we can perform a request
// through it.
this._sendMessageWithCallback(ev);
break;
case 'stunservers':
case 'turnservers':
// Values are not pushed by the server but have to be explicitly
// requested.
this._sendMessageWithCallback(ev);
break;
}
};
InternalSignaling.prototype._sendMessageWithCallback = function(ev) {
var message = [{
ev: ev
}];
this._sendMessages(message).done(function(result) {
this._trigger(ev, [result.ocs.data]);
}.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
console.log('Sending signaling message with callback has failed.');
// TODO: Add error handling
});
};
InternalSignaling.prototype._sendMessages = function(messages) {
var defer = $.Deferred();
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'signaling',
type: 'POST',
data: {messages: JSON.stringify(messages)},
beforeSend: function (request) {
request.setRequestHeader('Accept', 'application/json');
},
success: function (result) {
defer.resolve(result);
},
error: function (xhr, textStatus, errorThrown) {
defer.reject(xhr, textStatus, errorThrown);
}
});
return defer;
};
InternalSignaling.prototype.joinCall = function(token, callback, password) {
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
type: 'POST',
beforeSend: function (request) {
request.setRequestHeader('Accept', 'application/json');
},
data: {
password: password
},
success: function (result) {
console.log("Joined", result);
this.sessionId = result.ocs.data.sessionId;
this.currentCallToken = token;
this._startPingCall();
this._startPullingMessages();
// We send an empty call description to simplewebrtc since
// usersChanged (webrtc.js) will create/remove peer connections
// with call participants
var callDescription = {'clients': {}};
callback('', callDescription);
}.bind(this),
error: function (result) {
if (result.status === 404 || result.status === 503) {
// Room not found or maintenance mode
OC.redirect(OC.generateUrl('apps/spreed'));
}
if (result.status === 403) {
// This should not happen anymore since we ask for the password before
// even trying to join the call, but let's keep it for now.
OC.dialogs.prompt(
t('spreed', 'Please enter the password for this call'),
t('spreed','Password required'),
function (result, password) {
if (result && password !== '') {
this.joinCall(token, callback, password);
}
}.bind(this),
true,
t('spreed','Password'),
true
).then(function() {
var $dialog = $('.oc-dialog:visible');
$dialog.find('.ui-icon').remove();
var $buttons = $dialog.find('button');
$buttons.eq(0).text(t('core', 'Cancel'));
$buttons.eq(1).text(t('core', 'Submit'));
});
}
}.bind(this)
});
};
InternalSignaling.prototype.leaveCall = function(token) {
if (token === this.currentCallToken) {
this._stopPingCall();
this._closeEventSource();
}
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
method: 'DELETE',
async: false
});
};
InternalSignaling.prototype.sendCallMessage = function(data) {
if(data.type === 'answer') {
console.log("ANSWER", data);
} else if(data.type === 'offer') {
console.log("OFFER", data);
}
this.spreedArrayConnection.push({
ev: "message",
fn: JSON.stringify(data),
sessionId: this.sessionId
});
};
InternalSignaling.prototype.setRoomCollection = function(/*rooms*/) {
this._pollForRoomChanges();
return SignalingBase.prototype.setRoomCollection.apply(this, arguments);
};
InternalSignaling.prototype.setRoom = function(/*room*/) {
this._pollForRoomChanges();
return SignalingBase.prototype.setRoom.apply(this, arguments);
};
InternalSignaling.prototype._pollForRoomChanges = function() {
if (this.roomPoller) {
window.clearInterval(this.roomPoller);
}
this.roomPoller = window.setInterval(function() {
this.syncRooms();
}.bind(this), 10000);
};
/**
* @private
*/
InternalSignaling.prototype._getCallPeers = function(token) {
var defer = $.Deferred();
$.ajax({
beforeSend: function (request) {
request.setRequestHeader('Accept', 'application/json');
},
url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
success: function (result) {
var peers = result.ocs.data;
defer.resolve(peers);
}
});
return defer;
};
/**
* @private
*/
InternalSignaling.prototype._startPullingMessages = function() {
// Connect to the messages endpoint and pull for new messages
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'signaling',
type: 'GET',
dataType: 'json',
beforeSend: function (request) {
request.setRequestHeader('Accept', 'application/json');
},
success: function (result) {
$.each(result.ocs.data, function(id, message) {
switch(message.type) {
case "usersInRoom":
this._trigger('usersInRoom', [message.data]);
break;
case "message":
if (typeof(message.data) === 'string') {
message.data = JSON.parse(message.data);
}
this._trigger('message', [message.data]);
break;
default:
console.log('Unknown Signaling Message');
break;
}
}.bind(this));
this._startPullingMessages();
}.bind(this),
error: function (/*jqXHR, textStatus, errorThrown*/) {
//Retry to pull messages after 5 seconds
window.setTimeout(function() {
this._startPullingMessages();
}.bind(this), 5000);
}.bind(this)
});
};
/**
* @private
*/
InternalSignaling.prototype._closeEventSource = function() {
if (this.source) {
this.source.close();
this.source = null;
}
};
/**
* @private
*/
InternalSignaling.prototype.sendPendingMessages = function() {
if (!this.spreedArrayConnection.length || this.isSendingMessages) {
return;
}
var pendingMessagesLength = this.spreedArrayConnection.length;
this.isSendingMessages = true;
this._sendMessages(this.spreedArrayConnection).done(function(/*result*/) {
this.spreedArrayConnection.splice(0, pendingMessagesLength);
this.isSendingMessages = false;
}.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
console.log('Sending pending signaling messages has failed.');
this.isSendingMessages = false;
}.bind(this));
};
/**
* @private
*/
InternalSignaling.prototype._startPingCall = function() {
this._pingCall();
// Send a ping to the server all 5 seconds to ensure that the connection
// is still alive.
this.pingInterval = window.setInterval(function() {
this._pingCall();
}.bind(this), 5000);
};
/**
* @private
*/
InternalSignaling.prototype._stopPingCall = function() {
if (this.pingInterval) {
window.clearInterval(this.pingInterval);
this.pingInterval = null;
}
};
/**
* @private
*/
InternalSignaling.prototype._pingCall = function() {
if (!this.currentCallToken) {
return;
}
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + this.currentCallToken + '/ping',
method: 'POST'
}).done(function() {
this.pingFails = 0;
}.bind(this)).fail(function(xhr) {
// If there is an error when pinging, retry for 3 times.
if (xhr.status !== 404 && this.pingFails < 3) {
this.pingFails++;
return;
}
OCA.SpreedMe.Calls.leaveCurrentCall(false);
}.bind(this));
};
OCA.SpreedMe.createSignalingConnection = function() {
// TODO(fancycode): Create different type of signaling connection
// depending on configuration.
return new InternalSignaling();
};
})(OCA, OC);