Bug 972017 Part 4 - Hook up the OT sdk to the direct calling window for Loop. r=nperriault
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
<script type="text/javascript" src="loop/shared/js/actions.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
<script type="text/javascript" src="loop/shared/js/validate.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
<script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
|
||||||
|
<script type="text/javascript" src="loop/shared/js/otSdkDriver.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
|
<script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
|
||||||
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
|
<script type="text/javascript" src="loop/js/conversationViews.js"></script>
|
||||||
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
<script type="text/javascript" src="loop/shared/js/websocket.js"></script>
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ loop.Client = (function($) {
|
|||||||
* @param {Function} cb Callback(err, result)
|
* @param {Function} cb Callback(err, result)
|
||||||
*/
|
*/
|
||||||
setupOutgoingCall: function(calleeIds, callType, cb) {
|
setupOutgoingCall: function(calleeIds, callType, cb) {
|
||||||
|
// For direct calls, we only ever use the logged-in session. Direct
|
||||||
|
// calls by guests aren't valid.
|
||||||
this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.FXA,
|
this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.FXA,
|
||||||
"/calls", "POST", {
|
"/calls", "POST", {
|
||||||
calleeId: calleeIds,
|
calleeId: calleeIds,
|
||||||
|
|||||||
@@ -540,9 +540,15 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
var dispatcher = new loop.Dispatcher();
|
var dispatcher = new loop.Dispatcher();
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
|
var sdkDriver = new loop.OTSdkDriver({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
sdk: OT
|
||||||
|
});
|
||||||
|
|
||||||
var conversationStore = new loop.store.ConversationStore({}, {
|
var conversationStore = new loop.store.ConversationStore({}, {
|
||||||
client: client,
|
client: client,
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
sdkDriver: sdkDriver
|
||||||
});
|
});
|
||||||
|
|
||||||
// XXX For now key this on the pref, but this should really be
|
// XXX For now key this on the pref, but this should really be
|
||||||
|
|||||||
@@ -540,9 +540,15 @@ loop.conversation = (function(mozL10n) {
|
|||||||
|
|
||||||
var dispatcher = new loop.Dispatcher();
|
var dispatcher = new loop.Dispatcher();
|
||||||
var client = new loop.Client();
|
var client = new loop.Client();
|
||||||
|
var sdkDriver = new loop.OTSdkDriver({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
sdk: OT
|
||||||
|
});
|
||||||
|
|
||||||
var conversationStore = new loop.store.ConversationStore({}, {
|
var conversationStore = new loop.store.ConversationStore({}, {
|
||||||
client: client,
|
client: client,
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
sdkDriver: sdkDriver
|
||||||
});
|
});
|
||||||
|
|
||||||
// XXX For now key this on the pref, but this should really be
|
// XXX For now key this on the pref, but this should really be
|
||||||
|
|||||||
@@ -158,6 +158,15 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||||
window.addEventListener('resize', this.updateVideoContainer);
|
window.addEventListener('resize', this.updateVideoContainer);
|
||||||
|
|
||||||
|
// The SDK needs to know about the configuration and the elements to use
|
||||||
|
// for display. So the best way seems to pass the information here - ideally
|
||||||
|
// the sdk wouldn't need to know this, but we can't change that.
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
publisherConfig: this._getPublisherConfig(),
|
||||||
|
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||||
|
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
@@ -165,9 +174,41 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
window.removeEventListener('resize', this.updateVideoContainer);
|
window.removeEventListener('resize', this.updateVideoContainer);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns either the required DOMNode
|
||||||
|
*
|
||||||
|
* @param {String} className The name of the class to get the element for.
|
||||||
|
*/
|
||||||
|
_getElement: function(className) {
|
||||||
|
return this.getDOMNode().querySelector(className);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the required configuration for publishing video on the sdk.
|
||||||
|
*/
|
||||||
|
_getPublisherConfig: function() {
|
||||||
|
// height set to 100%" to fix video layout on Google Chrome
|
||||||
|
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||||
|
return {
|
||||||
|
insertMode: "append",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
publishVideo: this.props.video.enabled,
|
||||||
|
style: {
|
||||||
|
bugDisplayMode: "off",
|
||||||
|
buttonDisplayMode: "off",
|
||||||
|
nameDisplayMode: "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to update the video container whenever the orientation or size of the
|
||||||
|
* display area changes.
|
||||||
|
*/
|
||||||
updateVideoContainer: function() {
|
updateVideoContainer: function() {
|
||||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||||
if (localStreamParent) {
|
if (localStreamParent) {
|
||||||
localStreamParent.style.width = "100%";
|
localStreamParent.style.width = "100%";
|
||||||
}
|
}
|
||||||
@@ -176,13 +217,26 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hangs up the call.
|
||||||
|
*/
|
||||||
hangup: function() {
|
hangup: function() {
|
||||||
this.props.dispatcher.dispatch(
|
this.props.dispatcher.dispatch(
|
||||||
new sharedActions.HangupCall());
|
new sharedActions.HangupCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to control publishing a stream - i.e. to mute a stream
|
||||||
|
*
|
||||||
|
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||||
|
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||||
|
*/
|
||||||
publishStream: function(type, enabled) {
|
publishStream: function(type, enabled) {
|
||||||
// XXX Add this as part of bug 972017.
|
this.props.dispatcher.dispatch(
|
||||||
|
new sharedActions.SetMute({
|
||||||
|
type: type,
|
||||||
|
enabled: enabled
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
@@ -286,7 +340,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
case CALL_STATES.ONGOING: {
|
case CALL_STATES.ONGOING: {
|
||||||
return (OngoingConversationView({
|
return (OngoingConversationView({
|
||||||
dispatcher: this.props.dispatcher,
|
dispatcher: this.props.dispatcher,
|
||||||
video: {enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
video: {enabled: this.state.videoMuted},
|
||||||
|
audio: {enabled: this.state.audioMuted}}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,6 +158,15 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
*/
|
*/
|
||||||
window.addEventListener('orientationchange', this.updateVideoContainer);
|
window.addEventListener('orientationchange', this.updateVideoContainer);
|
||||||
window.addEventListener('resize', this.updateVideoContainer);
|
window.addEventListener('resize', this.updateVideoContainer);
|
||||||
|
|
||||||
|
// The SDK needs to know about the configuration and the elements to use
|
||||||
|
// for display. So the best way seems to pass the information here - ideally
|
||||||
|
// the sdk wouldn't need to know this, but we can't change that.
|
||||||
|
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
publisherConfig: this._getPublisherConfig(),
|
||||||
|
getLocalElementFunc: this._getElement.bind(this, ".local"),
|
||||||
|
getRemoteElementFunc: this._getElement.bind(this, ".remote")
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
@@ -165,9 +174,41 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
window.removeEventListener('resize', this.updateVideoContainer);
|
window.removeEventListener('resize', this.updateVideoContainer);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns either the required DOMNode
|
||||||
|
*
|
||||||
|
* @param {String} className The name of the class to get the element for.
|
||||||
|
*/
|
||||||
|
_getElement: function(className) {
|
||||||
|
return this.getDOMNode().querySelector(className);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the required configuration for publishing video on the sdk.
|
||||||
|
*/
|
||||||
|
_getPublisherConfig: function() {
|
||||||
|
// height set to 100%" to fix video layout on Google Chrome
|
||||||
|
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
|
||||||
|
return {
|
||||||
|
insertMode: "append",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
publishVideo: this.props.video.enabled,
|
||||||
|
style: {
|
||||||
|
bugDisplayMode: "off",
|
||||||
|
buttonDisplayMode: "off",
|
||||||
|
nameDisplayMode: "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to update the video container whenever the orientation or size of the
|
||||||
|
* display area changes.
|
||||||
|
*/
|
||||||
updateVideoContainer: function() {
|
updateVideoContainer: function() {
|
||||||
var localStreamParent = document.querySelector('.local .OT_publisher');
|
var localStreamParent = this._getElement('.local .OT_publisher');
|
||||||
var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
|
var remoteStreamParent = this._getElement('.remote .OT_subscriber');
|
||||||
if (localStreamParent) {
|
if (localStreamParent) {
|
||||||
localStreamParent.style.width = "100%";
|
localStreamParent.style.width = "100%";
|
||||||
}
|
}
|
||||||
@@ -176,13 +217,26 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hangs up the call.
|
||||||
|
*/
|
||||||
hangup: function() {
|
hangup: function() {
|
||||||
this.props.dispatcher.dispatch(
|
this.props.dispatcher.dispatch(
|
||||||
new sharedActions.HangupCall());
|
new sharedActions.HangupCall());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to control publishing a stream - i.e. to mute a stream
|
||||||
|
*
|
||||||
|
* @param {String} type The type of stream, e.g. "audio" or "video".
|
||||||
|
* @param {Boolean} enabled True to enable the stream, false otherwise.
|
||||||
|
*/
|
||||||
publishStream: function(type, enabled) {
|
publishStream: function(type, enabled) {
|
||||||
// XXX Add this as part of bug 972017.
|
this.props.dispatcher.dispatch(
|
||||||
|
new sharedActions.SetMute({
|
||||||
|
type: type,
|
||||||
|
enabled: enabled
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
@@ -286,7 +340,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||||||
case CALL_STATES.ONGOING: {
|
case CALL_STATES.ONGOING: {
|
||||||
return (<OngoingConversationView
|
return (<OngoingConversationView
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
video={{enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
|
video={{enabled: this.state.videoMuted}}
|
||||||
|
audio={{enabled: this.state.audioMuted}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ loop.shared.actions = (function() {
|
|||||||
HangupCall: Action.define("hangupCall", {
|
HangupCall: Action.define("hangupCall", {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to indicate the peer hung up the call.
|
||||||
|
*/
|
||||||
|
PeerHungupCall: Action.define("peerHungupCall", {
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for notifying of connection progress state changes.
|
* Used for notifying of connection progress state changes.
|
||||||
* The connection refers to the overall connection flow as indicated
|
* The connection refers to the overall connection flow as indicated
|
||||||
@@ -85,6 +91,35 @@ loop.shared.actions = (function() {
|
|||||||
ConnectionFailure: Action.define("connectionFailure", {
|
ConnectionFailure: Action.define("connectionFailure", {
|
||||||
// A string relating to the reason the connection failed.
|
// A string relating to the reason the connection failed.
|
||||||
reason: String
|
reason: String
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the ongoing views to notify stores about the elements
|
||||||
|
* required for the sdk.
|
||||||
|
*/
|
||||||
|
SetupStreamElements: Action.define("setupStreamElements", {
|
||||||
|
// The configuration for the publisher/subscribe options
|
||||||
|
publisherConfig: Object,
|
||||||
|
// The local stream element
|
||||||
|
getLocalElementFunc: Function,
|
||||||
|
// The remote stream element
|
||||||
|
getRemoteElementFunc: Function
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for notifying that the media is now up for the call.
|
||||||
|
*/
|
||||||
|
MediaConnected: Action.define("mediaConnected", {
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to mute or unmute a stream
|
||||||
|
*/
|
||||||
|
SetMute: Action.define("setMute", {
|
||||||
|
// The part of the stream to enable, e.g. "audio" or "video"
|
||||||
|
type: String,
|
||||||
|
// Whether or not to enable the stream.
|
||||||
|
enabled: Boolean
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ var loop = loop || {};
|
|||||||
loop.store = (function() {
|
loop.store = (function() {
|
||||||
|
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Websocket states taken from:
|
* Websocket states taken from:
|
||||||
@@ -67,7 +67,7 @@ loop.store = (function() {
|
|||||||
calleeId: undefined,
|
calleeId: undefined,
|
||||||
// The call type for the call.
|
// The call type for the call.
|
||||||
// XXX Don't hard-code, this comes from the data in bug 1072323
|
// XXX Don't hard-code, this comes from the data in bug 1072323
|
||||||
callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
|
callType: CALL_TYPES.AUDIO_VIDEO,
|
||||||
|
|
||||||
// Call Connection information
|
// Call Connection information
|
||||||
// The call id from the loop-server
|
// The call id from the loop-server
|
||||||
@@ -81,7 +81,11 @@ loop.store = (function() {
|
|||||||
// SDK session ID
|
// SDK session ID
|
||||||
sessionId: undefined,
|
sessionId: undefined,
|
||||||
// SDK session token
|
// SDK session token
|
||||||
sessionToken: undefined
|
sessionToken: undefined,
|
||||||
|
// If the audio is muted
|
||||||
|
audioMuted: true,
|
||||||
|
// If the video is muted
|
||||||
|
videoMuted: true
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,9 +108,13 @@ loop.store = (function() {
|
|||||||
if (!options.client) {
|
if (!options.client) {
|
||||||
throw new Error("Missing option client");
|
throw new Error("Missing option client");
|
||||||
}
|
}
|
||||||
|
if (!options.sdkDriver) {
|
||||||
|
throw new Error("Missing option sdkDriver");
|
||||||
|
}
|
||||||
|
|
||||||
this.client = options.client;
|
this.client = options.client;
|
||||||
this.dispatcher = options.dispatcher;
|
this.dispatcher = options.dispatcher;
|
||||||
|
this.sdkDriver = options.sdkDriver;
|
||||||
|
|
||||||
this.dispatcher.register(this, [
|
this.dispatcher.register(this, [
|
||||||
"connectionFailure",
|
"connectionFailure",
|
||||||
@@ -114,8 +122,11 @@ loop.store = (function() {
|
|||||||
"gatherCallData",
|
"gatherCallData",
|
||||||
"connectCall",
|
"connectCall",
|
||||||
"hangupCall",
|
"hangupCall",
|
||||||
|
"peerHungupCall",
|
||||||
"cancelCall",
|
"cancelCall",
|
||||||
"retryCall"
|
"retryCall",
|
||||||
|
"mediaConnected",
|
||||||
|
"setMute"
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -126,6 +137,7 @@ loop.store = (function() {
|
|||||||
* @param {sharedActions.ConnectionFailure} actionData The action data.
|
* @param {sharedActions.ConnectionFailure} actionData The action data.
|
||||||
*/
|
*/
|
||||||
connectionFailure: function(actionData) {
|
connectionFailure: function(actionData) {
|
||||||
|
this._endSession();
|
||||||
this.set({
|
this.set({
|
||||||
callState: CALL_STATES.TERMINATED,
|
callState: CALL_STATES.TERMINATED,
|
||||||
callStateReason: actionData.reason
|
callStateReason: actionData.reason
|
||||||
@@ -152,7 +164,15 @@ loop.store = (function() {
|
|||||||
this.set({callState: CALL_STATES.ALERTING});
|
this.set({callState: CALL_STATES.ALERTING});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WS_STATES.CONNECTING:
|
case WS_STATES.CONNECTING: {
|
||||||
|
this.sdkDriver.connectSession({
|
||||||
|
apiKey: this.get("apiKey"),
|
||||||
|
sessionId: this.get("sessionId"),
|
||||||
|
sessionToken: this.get("sessionToken")
|
||||||
|
});
|
||||||
|
this.set({callState: CALL_STATES.ONGOING});
|
||||||
|
break;
|
||||||
|
}
|
||||||
case WS_STATES.HALF_CONNECTED:
|
case WS_STATES.HALF_CONNECTED:
|
||||||
case WS_STATES.CONNECTED: {
|
case WS_STATES.CONNECTED: {
|
||||||
this.set({callState: CALL_STATES.ONGOING});
|
this.set({callState: CALL_STATES.ONGOING});
|
||||||
@@ -179,6 +199,8 @@ loop.store = (function() {
|
|||||||
callState: CALL_STATES.GATHER
|
callState: CALL_STATES.GATHER
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.videoMuted = this.get("callType") !== CALL_TYPES.AUDIO_VIDEO;
|
||||||
|
|
||||||
if (this.get("outgoing")) {
|
if (this.get("outgoing")) {
|
||||||
this._setupOutgoingCall();
|
this._setupOutgoingCall();
|
||||||
} // XXX Else, other types aren't supported yet.
|
} // XXX Else, other types aren't supported yet.
|
||||||
@@ -200,15 +222,20 @@ loop.store = (function() {
|
|||||||
* Hangs up an ongoing call.
|
* Hangs up an ongoing call.
|
||||||
*/
|
*/
|
||||||
hangupCall: function() {
|
hangupCall: function() {
|
||||||
// XXX Stop the SDK once we add it.
|
|
||||||
|
|
||||||
// Ensure the websocket has been disconnected.
|
|
||||||
if (this._websocket) {
|
if (this._websocket) {
|
||||||
// Let the server know the user has hung up.
|
// Let the server know the user has hung up.
|
||||||
this._websocket.mediaFail();
|
this._websocket.mediaFail();
|
||||||
this._ensureWebSocketDisconnected();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._endSession();
|
||||||
|
this.set({callState: CALL_STATES.FINISHED});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The peer hungup the call.
|
||||||
|
*/
|
||||||
|
peerHungupCall: function() {
|
||||||
|
this._endSession();
|
||||||
this.set({callState: CALL_STATES.FINISHED});
|
this.set({callState: CALL_STATES.FINISHED});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -217,24 +244,15 @@ loop.store = (function() {
|
|||||||
*/
|
*/
|
||||||
cancelCall: function() {
|
cancelCall: function() {
|
||||||
var callState = this.get("callState");
|
var callState = this.get("callState");
|
||||||
if (callState === CALL_STATES.TERMINATED) {
|
if (this._websocket &&
|
||||||
// All we need to do is close the window.
|
(callState === CALL_STATES.CONNECTING ||
|
||||||
this.set({callState: CALL_STATES.CLOSE});
|
callState === CALL_STATES.ALERTING)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callState === CALL_STATES.CONNECTING ||
|
|
||||||
callState === CALL_STATES.ALERTING) {
|
|
||||||
if (this._websocket) {
|
|
||||||
// Let the server know the user has hung up.
|
// Let the server know the user has hung up.
|
||||||
this._websocket.cancel();
|
this._websocket.cancel();
|
||||||
this._ensureWebSocketDisconnected();
|
|
||||||
}
|
|
||||||
this.set({callState: CALL_STATES.CLOSE});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Unsupported cancel in state", callState);
|
this._endSession();
|
||||||
|
this.set({callState: CALL_STATES.CLOSE});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,6 +271,23 @@ loop.store = (function() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that all media is now connected
|
||||||
|
*/
|
||||||
|
mediaConnected: function() {
|
||||||
|
this._websocket.mediaUp();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the mute state for the stream.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.setMute} actionData The mute state for the stream type.
|
||||||
|
*/
|
||||||
|
setMute: function(actionData) {
|
||||||
|
var muteType = actionData.type + "Muted";
|
||||||
|
this.set(muteType, actionData.enabled);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtains the outgoing call data from the server and handles the
|
* Obtains the outgoing call data from the server and handles the
|
||||||
* result.
|
* result.
|
||||||
@@ -308,14 +343,17 @@ loop.store = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures the websocket gets disconnected.
|
* Ensures the session is ended and the websocket is disconnected.
|
||||||
*/
|
*/
|
||||||
_ensureWebSocketDisconnected: function() {
|
_endSession: function(nextState) {
|
||||||
|
this.sdkDriver.disconnectSession();
|
||||||
|
if (this._websocket) {
|
||||||
this.stopListening(this._websocket);
|
this.stopListening(this._websocket);
|
||||||
|
|
||||||
// Now close the websocket.
|
// Now close the websocket.
|
||||||
this._websocket.close();
|
this._websocket.close();
|
||||||
delete this._websocket;
|
delete this._websocket;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
237
browser/components/loop/content/shared/js/otSdkDriver.js
Normal file
237
browser/components/loop/content/shared/js/otSdkDriver.js
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/* global loop:true */
|
||||||
|
|
||||||
|
var loop = loop || {};
|
||||||
|
loop.OTSdkDriver = (function() {
|
||||||
|
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a wrapper for the OT sdk. It is used to translate the SDK events into
|
||||||
|
* actions, and instruct the SDK what to do as a result of actions.
|
||||||
|
*/
|
||||||
|
var OTSdkDriver = function(options) {
|
||||||
|
if (!options.dispatcher) {
|
||||||
|
throw new Error("Missing option dispatcher");
|
||||||
|
}
|
||||||
|
if (!options.sdk) {
|
||||||
|
throw new Error("Missing option sdk");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatcher = options.dispatcher;
|
||||||
|
this.sdk = options.sdk;
|
||||||
|
|
||||||
|
this.dispatcher.register(this, [
|
||||||
|
"setupStreamElements",
|
||||||
|
"setMute"
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
OTSdkDriver.prototype = {
|
||||||
|
/**
|
||||||
|
* Handles the setupStreamElements action. Saves the required data and
|
||||||
|
* kicks off the initialising of the publisher.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.SetupStreamElements} actionData The data associated
|
||||||
|
* with the action. See action.js.
|
||||||
|
*/
|
||||||
|
setupStreamElements: function(actionData) {
|
||||||
|
this.getLocalElement = actionData.getLocalElementFunc;
|
||||||
|
this.getRemoteElement = actionData.getRemoteElementFunc;
|
||||||
|
this.publisherConfig = actionData.publisherConfig;
|
||||||
|
|
||||||
|
// At this state we init the publisher, even though we might be waiting for
|
||||||
|
// the initial connect of the session. This saves time when setting up
|
||||||
|
// the media.
|
||||||
|
this.publisher = this.sdk.initPublisher(this.getLocalElement(),
|
||||||
|
this.publisherConfig,
|
||||||
|
this._onPublishComplete.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the setMute action. Informs the published stream to mute
|
||||||
|
* or unmute audio as appropriate.
|
||||||
|
*
|
||||||
|
* @param {sharedActions.SetMute} actionData The data associated with the
|
||||||
|
* action. See action.js.
|
||||||
|
*/
|
||||||
|
setMute: function(actionData) {
|
||||||
|
if (actionData.type === "audio") {
|
||||||
|
this.publisher.publishAudio(actionData.enabled);
|
||||||
|
} else {
|
||||||
|
this.publisher.publishVideo(actionData.enabled);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects a session for the SDK, listening to the required events.
|
||||||
|
*
|
||||||
|
* sessionData items:
|
||||||
|
* - sessionId: The OT session ID
|
||||||
|
* - apiKey: The OT API key
|
||||||
|
* - sessionToken: The token for the OT session
|
||||||
|
*
|
||||||
|
* @param {Object} sessionData The session data for setting up the OT session.
|
||||||
|
*/
|
||||||
|
connectSession: function(sessionData) {
|
||||||
|
this.session = this.sdk.initSession(sessionData.sessionId);
|
||||||
|
|
||||||
|
this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||||
|
this.session.on("connectionDestroyed",
|
||||||
|
this._onConnectionDestroyed.bind(this));
|
||||||
|
this.session.on("sessionDisconnected",
|
||||||
|
this._onSessionDisconnected.bind(this));
|
||||||
|
|
||||||
|
// This starts the actual session connection.
|
||||||
|
this.session.connect(sessionData.apiKey, sessionData.sessionToken,
|
||||||
|
this._onConnectionComplete.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the sdk session.
|
||||||
|
*/
|
||||||
|
disconnectSession: function() {
|
||||||
|
if (this.session) {
|
||||||
|
this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
|
||||||
|
this.session.off("connectionDestroyed",
|
||||||
|
this._onConnectionDestroyed.bind(this));
|
||||||
|
this.session.off("sessionDisconnected",
|
||||||
|
this._onSessionDisconnected.bind(this));
|
||||||
|
|
||||||
|
this.session.disconnect();
|
||||||
|
delete this.session;
|
||||||
|
}
|
||||||
|
if (this.publisher) {
|
||||||
|
this.publisher.destroy();
|
||||||
|
delete this.publisher;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also, tidy these variables ready for next time.
|
||||||
|
delete this._sessionConnected;
|
||||||
|
delete this._publisherReady;
|
||||||
|
delete this._publishedLocalStream;
|
||||||
|
delete this._subscribedRemoteStream;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called once the session has finished connecting.
|
||||||
|
*
|
||||||
|
* @param {Error} error An OT error object, null if there was no error.
|
||||||
|
*/
|
||||||
|
_onConnectionComplete: function(error) {
|
||||||
|
if (error) {
|
||||||
|
console.error("Failed to complete connection", error);
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
|
reason: "couldNotConnect"
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sessionConnected = true;
|
||||||
|
this._maybePublishLocalStream();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the connection event for a peer's connection being dropped.
|
||||||
|
*
|
||||||
|
* @param {SessionDisconnectEvent} event The event details
|
||||||
|
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
|
||||||
|
*/
|
||||||
|
_onConnectionDestroyed: function(event) {
|
||||||
|
var action;
|
||||||
|
if (event.reason === "clientDisconnected") {
|
||||||
|
action = new sharedActions.PeerHungupCall();
|
||||||
|
} else {
|
||||||
|
// Strictly speaking this isn't a failure on our part, but since our
|
||||||
|
// flow requires a full reconnection, then we just treat this as
|
||||||
|
// if a failure of our end had occurred.
|
||||||
|
action = new sharedActions.ConnectionFailure({
|
||||||
|
reason: "peerNetworkDisconnected"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.dispatcher.dispatch(action);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the session event for the connection for this client being
|
||||||
|
* destroyed.
|
||||||
|
*
|
||||||
|
* @param {SessionDisconnectEvent} event The event details:
|
||||||
|
* https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
|
||||||
|
*/
|
||||||
|
_onSessionDisconnected: function(event) {
|
||||||
|
// We only need to worry about the network disconnected reason here.
|
||||||
|
if (event.reason === "networkDisconnected") {
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
|
reason: "networkDisconnected"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the event when the remote stream is created.
|
||||||
|
*
|
||||||
|
* @param {StreamEvent} event The event details:
|
||||||
|
* https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
|
||||||
|
*/
|
||||||
|
_onRemoteStreamCreated: function(event) {
|
||||||
|
this.session.subscribe(event.stream,
|
||||||
|
this.getRemoteElement(), this.publisherConfig);
|
||||||
|
|
||||||
|
this._subscribedRemoteStream = true;
|
||||||
|
if (this._checkAllStreamsConnected()) {
|
||||||
|
this.dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the publishing being complete.
|
||||||
|
*
|
||||||
|
* @param {Error} error An OT error object, null if there was no error.
|
||||||
|
*/
|
||||||
|
_onPublishComplete: function(error) {
|
||||||
|
if (error) {
|
||||||
|
console.error("Failed to initialize publisher", error);
|
||||||
|
this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
|
||||||
|
reason: "noMedia"
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._publisherReady = true;
|
||||||
|
this._maybePublishLocalStream();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes the local stream if the session is connected
|
||||||
|
* and the publisher is ready.
|
||||||
|
*/
|
||||||
|
_maybePublishLocalStream: function() {
|
||||||
|
if (this._sessionConnected && this._publisherReady) {
|
||||||
|
// We are clear to publish the stream to the session.
|
||||||
|
this.session.publish(this.publisher);
|
||||||
|
|
||||||
|
// Now record the fact, and check if we've got all media yet.
|
||||||
|
this._publishedLocalStream = true;
|
||||||
|
if (this._checkAllStreamsConnected()) {
|
||||||
|
this.dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to check if both local and remote streams are available
|
||||||
|
* and send an action if they are.
|
||||||
|
*/
|
||||||
|
_checkAllStreamsConnected: function() {
|
||||||
|
return this._publishedLocalStream &&
|
||||||
|
this._subscribedRemoteStream;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return OTSdkDriver;
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -59,6 +59,7 @@ browser.jar:
|
|||||||
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
|
||||||
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
content/browser/loop/shared/js/models.js (content/shared/js/models.js)
|
||||||
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
content/browser/loop/shared/js/mixins.js (content/shared/js/mixins.js)
|
||||||
|
content/browser/loop/shared/js/otSdkDriver.js (content/shared/js/otSdkDriver.js)
|
||||||
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
content/browser/loop/shared/js/views.js (content/shared/js/views.js)
|
||||||
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
content/browser/loop/shared/js/utils.js (content/shared/js/utils.js)
|
||||||
content/browser/loop/shared/js/validate.js (content/shared/js/validate.js)
|
content/browser/loop/shared/js/validate.js (content/shared/js/validate.js)
|
||||||
|
|||||||
@@ -161,26 +161,120 @@ describe("loop.conversationViews", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("OngoingConversationView", function() {
|
||||||
|
function mountTestComponent(props) {
|
||||||
|
return TestUtils.renderIntoDocument(
|
||||||
|
loop.conversationViews.OngoingConversationView(props));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should dispatch a setupStreamElements action when the view is created",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "setupStreamElements"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a hangupCall action when the hangup button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
|
||||||
|
var hangupBtn = view.getDOMNode().querySelector('.btn-hangup');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(hangupBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "hangupCall"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a setMute action when the audio mute button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
audio: {enabled: false}
|
||||||
|
});
|
||||||
|
|
||||||
|
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "setMute"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("enabled", true));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("type", "audio"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a setMute action when the video mute button is pressed",
|
||||||
|
function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
video: {enabled: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||||
|
|
||||||
|
React.addons.TestUtils.Simulate.click(muteBtn);
|
||||||
|
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "setMute"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("enabled", false));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("type", "video"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the mute button as mute off", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
video: {enabled: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
|
||||||
|
|
||||||
|
expect(muteBtn.classList.contains("muted")).eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the mute button as mute on", function() {
|
||||||
|
view = mountTestComponent({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
audio: {enabled: false}
|
||||||
|
});
|
||||||
|
|
||||||
|
var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
|
||||||
|
|
||||||
|
expect(muteBtn.classList.contains("muted")).eql(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("OutgoingConversationView", function() {
|
describe("OutgoingConversationView", function() {
|
||||||
var store;
|
var store;
|
||||||
|
|
||||||
function mountTestComponent() {
|
function mountTestComponent() {
|
||||||
return TestUtils.renderIntoDocument(
|
return TestUtils.renderIntoDocument(
|
||||||
loop.conversationViews.OutgoingConversationView({
|
loop.conversationViews.OutgoingConversationView({
|
||||||
|
dispatcher: dispatcher,
|
||||||
store: store
|
store: store
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
store = new loop.store.ConversationStore({}, {
|
|
||||||
dispatcher: dispatcher,
|
|
||||||
client: {}
|
|
||||||
});
|
|
||||||
|
|
||||||
navigator.mozLoop = {
|
navigator.mozLoop = {
|
||||||
getLoopCharPref: function() { return "fake"; },
|
getLoopCharPref: function() { return "fake"; },
|
||||||
appVersionInfo: sinon.spy()
|
appVersionInfo: sinon.spy()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
store = new loop.store.ConversationStore({}, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
client: {},
|
||||||
|
sdkDriver: {}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ describe("loop.conversation", function() {
|
|||||||
dispatcher = new loop.Dispatcher();
|
dispatcher = new loop.Dispatcher();
|
||||||
store = new loop.store.ConversationStore({}, {
|
store = new loop.store.ConversationStore({}, {
|
||||||
client: client,
|
client: client,
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
sdkDriver: {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
<script src="../../content/shared/js/actions.js"></script>
|
<script src="../../content/shared/js/actions.js"></script>
|
||||||
<script src="../../content/shared/js/validate.js"></script>
|
<script src="../../content/shared/js/validate.js"></script>
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||||
|
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||||
<script src="../../content/js/client.js"></script>
|
<script src="../../content/js/client.js"></script>
|
||||||
<script src="../../content/js/conversationViews.js"></script>
|
<script src="../../content/js/conversationViews.js"></script>
|
||||||
<script src="../../content/js/conversation.js"></script>
|
<script src="../../content/js/conversation.js"></script>
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ describe("loop.ConversationStore", function () {
|
|||||||
var WS_STATES = loop.store.WS_STATES;
|
var WS_STATES = loop.store.WS_STATES;
|
||||||
var sharedActions = loop.shared.actions;
|
var sharedActions = loop.shared.actions;
|
||||||
var sharedUtils = loop.shared.utils;
|
var sharedUtils = loop.shared.utils;
|
||||||
var sandbox, dispatcher, client, store, fakeSessionData;
|
var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
|
||||||
var connectPromise, resolveConnectPromise, rejectConnectPromise;
|
var connectPromise, resolveConnectPromise, rejectConnectPromise;
|
||||||
|
var wsCancelSpy, wsCloseSpy, wsMediaUpSpy, fakeWebsocket;
|
||||||
|
|
||||||
function checkFailures(done, f) {
|
function checkFailures(done, f) {
|
||||||
try {
|
try {
|
||||||
@@ -29,9 +30,25 @@ describe("loop.ConversationStore", function () {
|
|||||||
client = {
|
client = {
|
||||||
setupOutgoingCall: sinon.stub()
|
setupOutgoingCall: sinon.stub()
|
||||||
};
|
};
|
||||||
|
sdkDriver = {
|
||||||
|
connectSession: sinon.stub(),
|
||||||
|
disconnectSession: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
wsCancelSpy = sinon.spy();
|
||||||
|
wsCloseSpy = sinon.spy();
|
||||||
|
wsMediaUpSpy = sinon.spy();
|
||||||
|
|
||||||
|
fakeWebsocket = {
|
||||||
|
cancel: wsCancelSpy,
|
||||||
|
close: wsCloseSpy,
|
||||||
|
mediaUp: wsMediaUpSpy
|
||||||
|
};
|
||||||
|
|
||||||
store = new loop.store.ConversationStore({}, {
|
store = new loop.store.ConversationStore({}, {
|
||||||
client: client,
|
client: client,
|
||||||
dispatcher: dispatcher
|
dispatcher: dispatcher,
|
||||||
|
sdkDriver: sdkDriver
|
||||||
});
|
});
|
||||||
fakeSessionData = {
|
fakeSessionData = {
|
||||||
apiKey: "fakeKey",
|
apiKey: "fakeKey",
|
||||||
@@ -63,18 +80,51 @@ describe("loop.ConversationStore", function () {
|
|||||||
describe("#initialize", function() {
|
describe("#initialize", function() {
|
||||||
it("should throw an error if the dispatcher is missing", function() {
|
it("should throw an error if the dispatcher is missing", function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
new loop.store.ConversationStore({}, {client: client});
|
new loop.store.ConversationStore({}, {
|
||||||
|
client: client,
|
||||||
|
sdkDriver: sdkDriver
|
||||||
|
});
|
||||||
}).to.Throw(/dispatcher/);
|
}).to.Throw(/dispatcher/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw an error if the client is missing", function() {
|
it("should throw an error if the client is missing", function() {
|
||||||
expect(function() {
|
expect(function() {
|
||||||
new loop.store.ConversationStore({}, {dispatcher: dispatcher});
|
new loop.store.ConversationStore({}, {
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
sdkDriver: sdkDriver
|
||||||
|
});
|
||||||
}).to.Throw(/client/);
|
}).to.Throw(/client/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the sdkDriver is missing", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.store.ConversationStore({}, {
|
||||||
|
client: client,
|
||||||
|
dispatcher: dispatcher
|
||||||
|
});
|
||||||
|
}).to.Throw(/sdkDriver/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#connectionFailure", function() {
|
describe("#connectionFailure", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
store._websocket = fakeWebsocket;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disconnect the session", function() {
|
||||||
|
dispatcher.dispatch(
|
||||||
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ensure the websocket is closed", function() {
|
||||||
|
dispatcher.dispatch(
|
||||||
|
new sharedActions.ConnectionFailure({reason: "fake"}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsCloseSpy);
|
||||||
|
});
|
||||||
|
|
||||||
it("should set the state to 'terminated'", function() {
|
it("should set the state to 'terminated'", function() {
|
||||||
store.set({callState: CALL_STATES.ALERTING});
|
store.set({callState: CALL_STATES.ALERTING});
|
||||||
|
|
||||||
@@ -119,36 +169,30 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("progress: connecting", function() {
|
describe("progress: connecting", function() {
|
||||||
it("should change the state to 'ongoing'", function() {
|
beforeEach(function() {
|
||||||
store.set({callState: CALL_STATES.ALERTING});
|
store.set({callState: CALL_STATES.ALERTING});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should change the state to 'ongoing'", function() {
|
||||||
dispatcher.dispatch(
|
dispatcher.dispatch(
|
||||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("progress: half-connected", function() {
|
it("should connect the session", function() {
|
||||||
it("should change the state to 'ongoing'", function() {
|
store.set(fakeSessionData);
|
||||||
store.set({callState: CALL_STATES.ALERTING});
|
|
||||||
|
|
||||||
dispatcher.dispatch(
|
dispatcher.dispatch(
|
||||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.HALF_CONNECTED}));
|
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
sinon.assert.calledOnce(sdkDriver.connectSession);
|
||||||
|
sinon.assert.calledWithExactly(sdkDriver.connectSession, {
|
||||||
|
apiKey: "fakeKey",
|
||||||
|
sessionId: "321456",
|
||||||
|
sessionToken: "341256"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("progress: connecting", function() {
|
|
||||||
it("should change the state to 'ongoing'", function() {
|
|
||||||
store.set({callState: CALL_STATES.ALERTING});
|
|
||||||
|
|
||||||
dispatcher.dispatch(
|
|
||||||
new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTED}));
|
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.ONGOING);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -332,6 +376,12 @@ describe("loop.ConversationStore", function () {
|
|||||||
store.set({callState: CALL_STATES.ONGOING});
|
store.set({callState: CALL_STATES.ONGOING});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should disconnect the session", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||||
|
});
|
||||||
|
|
||||||
it("should send a media-fail message to the websocket if it is open", function() {
|
it("should send a media-fail message to the websocket if it is open", function() {
|
||||||
dispatcher.dispatch(new sharedActions.HangupCall());
|
dispatcher.dispatch(new sharedActions.HangupCall());
|
||||||
|
|
||||||
@@ -351,28 +401,51 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#cancelCall", function() {
|
describe("#peerHungupCall", function() {
|
||||||
it("should set the state to close if the call has terminated already", function() {
|
var wsMediaFailSpy, wsCloseSpy;
|
||||||
store.set({callState: CALL_STATES.TERMINATED});
|
|
||||||
|
|
||||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
|
||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("whilst connecting", function() {
|
|
||||||
var wsCancelSpy, wsCloseSpy;
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
wsCancelSpy = sinon.spy();
|
wsMediaFailSpy = sinon.spy();
|
||||||
wsCloseSpy = sinon.spy();
|
wsCloseSpy = sinon.spy();
|
||||||
|
|
||||||
store._websocket = {
|
store._websocket = {
|
||||||
cancel: wsCancelSpy,
|
mediaFail: wsMediaFailSpy,
|
||||||
close: wsCloseSpy
|
close: wsCloseSpy
|
||||||
};
|
};
|
||||||
|
store.set({callState: CALL_STATES.ONGOING});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should disconnect the session", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ensure the websocket is closed", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsCloseSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set the callState to finished", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.PeerHungupCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.FINISHED);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#cancelCall", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
store._websocket = fakeWebsocket;
|
||||||
|
|
||||||
store.set({callState: CALL_STATES.CONNECTING});
|
store.set({callState: CALL_STATES.CONNECTING});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should disconnect the session", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdkDriver.disconnectSession);
|
||||||
|
});
|
||||||
|
|
||||||
it("should send a cancel message to the websocket if it is open", function() {
|
it("should send a cancel message to the websocket if it is open", function() {
|
||||||
dispatcher.dispatch(new sharedActions.CancelCall());
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
@@ -390,7 +463,15 @@ describe("loop.ConversationStore", function () {
|
|||||||
|
|
||||||
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should set the state to close if the call has terminated already", function() {
|
||||||
|
store.set({callState: CALL_STATES.TERMINATED});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.CancelCall());
|
||||||
|
|
||||||
|
expect(store.get("callState")).eql(CALL_STATES.CLOSE);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#retryCall", function() {
|
describe("#retryCall", function() {
|
||||||
@@ -418,6 +499,40 @@ describe("loop.ConversationStore", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#mediaConnected", function() {
|
||||||
|
it("should send mediaUp via the websocket", function() {
|
||||||
|
store._websocket = fakeWebsocket;
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.MediaConnected());
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(wsMediaUpSpy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#setMute", function() {
|
||||||
|
it("should save the mute state for the audio stream", function() {
|
||||||
|
store.set({"audioMuted": false});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetMute({
|
||||||
|
type: "audio",
|
||||||
|
enabled: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(store.get("audioMuted")).eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should save the mute state for the video stream", function() {
|
||||||
|
store.set({"videoMuted": true});
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetMute({
|
||||||
|
type: "video",
|
||||||
|
enabled: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(store.get("videoMuted")).eql(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Events", function() {
|
describe("Events", function() {
|
||||||
describe("Websocket progress", function() {
|
describe("Websocket progress", function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
<script src="../../content/shared/js/validate.js"></script>
|
<script src="../../content/shared/js/validate.js"></script>
|
||||||
<script src="../../content/shared/js/actions.js"></script>
|
<script src="../../content/shared/js/actions.js"></script>
|
||||||
<script src="../../content/shared/js/dispatcher.js"></script>
|
<script src="../../content/shared/js/dispatcher.js"></script>
|
||||||
|
<script src="../../content/shared/js/otSdkDriver.js"></script>
|
||||||
<script src="../../content/shared/js/conversationStore.js"></script>
|
<script src="../../content/shared/js/conversationStore.js"></script>
|
||||||
|
|
||||||
<!-- Test scripts -->
|
<!-- Test scripts -->
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
<script src="validate_test.js"></script>
|
<script src="validate_test.js"></script>
|
||||||
<script src="dispatcher_test.js"></script>
|
<script src="dispatcher_test.js"></script>
|
||||||
<script src="conversationStore_test.js"></script>
|
<script src="conversationStore_test.js"></script>
|
||||||
|
<script src="otSdkDriver_test.js"></script>
|
||||||
<script>
|
<script>
|
||||||
mocha.run(function () {
|
mocha.run(function () {
|
||||||
$("#mocha").append("<p id='complete'>Complete.</p>");
|
$("#mocha").append("<p id='complete'>Complete.</p>");
|
||||||
|
|||||||
300
browser/components/loop/test/shared/otSdkDriver_test.js
Normal file
300
browser/components/loop/test/shared/otSdkDriver_test.js
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
var expect = chai.expect;
|
||||||
|
|
||||||
|
describe("loop.OTSdkDriver", function () {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var sharedActions = loop.shared.actions;
|
||||||
|
|
||||||
|
var sandbox;
|
||||||
|
var dispatcher, driver, publisher, sdk, session, sessionData;
|
||||||
|
var fakeLocalElement, fakeRemoteElement, publisherConfig;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
|
fakeLocalElement = {fake: 1};
|
||||||
|
fakeRemoteElement = {fake: 2};
|
||||||
|
publisherConfig = {
|
||||||
|
fake: "config"
|
||||||
|
};
|
||||||
|
sessionData = {
|
||||||
|
apiKey: "1234567890",
|
||||||
|
sessionId: "3216549870",
|
||||||
|
sessionToken: "1357924680"
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatcher = new loop.Dispatcher();
|
||||||
|
session = _.extend({
|
||||||
|
connect: sinon.stub(),
|
||||||
|
disconnect: sinon.stub(),
|
||||||
|
publish: sinon.stub(),
|
||||||
|
subscribe: sinon.stub()
|
||||||
|
}, Backbone.Events);
|
||||||
|
|
||||||
|
publisher = {
|
||||||
|
destroy: sinon.stub(),
|
||||||
|
publishAudio: sinon.stub(),
|
||||||
|
publishVideo: sinon.stub()
|
||||||
|
};
|
||||||
|
|
||||||
|
sdk = {
|
||||||
|
initPublisher: sinon.stub(),
|
||||||
|
initSession: sinon.stub().returns(session)
|
||||||
|
};
|
||||||
|
|
||||||
|
driver = new loop.OTSdkDriver({
|
||||||
|
dispatcher: dispatcher,
|
||||||
|
sdk: sdk
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Constructor", function() {
|
||||||
|
it("should throw an error if the dispatcher is missing", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.OTSdkDriver({sdk: sdk});
|
||||||
|
}).to.Throw(/dispatcher/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the sdk is missing", function() {
|
||||||
|
expect(function() {
|
||||||
|
new loop.OTSdkDriver({dispatcher: dispatcher});
|
||||||
|
}).to.Throw(/sdk/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#setupStreamElements", function() {
|
||||||
|
it("should call initPublisher", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||||
|
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||||
|
publisherConfig: publisherConfig
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdk.initPublisher);
|
||||||
|
sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("On Publisher Complete", function() {
|
||||||
|
it("should publish the stream if the connection is ready", function() {
|
||||||
|
sdk.initPublisher.callsArgWith(2, null);
|
||||||
|
|
||||||
|
driver.session = session;
|
||||||
|
driver._sessionConnected = true;
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||||
|
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||||
|
publisherConfig: publisherConfig
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.publish);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch connectionFailure if connecting failed", function() {
|
||||||
|
sdk.initPublisher.callsArgWith(2, new Error("Failure"));
|
||||||
|
|
||||||
|
// Special stub, as we want to use the dispatcher, but also know that
|
||||||
|
// we've been called correctly for the second dispatch.
|
||||||
|
var dispatchStub = (function() {
|
||||||
|
var originalDispatch = dispatcher.dispatch.bind(dispatcher);
|
||||||
|
return sandbox.stub(dispatcher, "dispatch", function(action) {
|
||||||
|
originalDispatch(action);
|
||||||
|
});
|
||||||
|
}());
|
||||||
|
|
||||||
|
driver.session = session;
|
||||||
|
driver._sessionConnected = true;
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||||
|
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||||
|
publisherConfig: publisherConfig
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.called(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("reason", "noMedia"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#setMute", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
sdk.initPublisher.returns(publisher);
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||||
|
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||||
|
publisherConfig: publisherConfig
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should publishAudio with the correct enabled value", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.SetMute({
|
||||||
|
type: "audio",
|
||||||
|
enabled: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(publisher.publishAudio);
|
||||||
|
sinon.assert.calledWithExactly(publisher.publishAudio, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should publishVideo with the correct enabled value", function() {
|
||||||
|
dispatcher.dispatch(new sharedActions.SetMute({
|
||||||
|
type: "video",
|
||||||
|
enabled: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(publisher.publishVideo);
|
||||||
|
sinon.assert.calledWithExactly(publisher.publishVideo, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#connectSession", function() {
|
||||||
|
it("should initialise a new session", function() {
|
||||||
|
driver.connectSession(sessionData);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(sdk.initSession);
|
||||||
|
sinon.assert.calledWithExactly(sdk.initSession, "3216549870");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should connect the session", function () {
|
||||||
|
driver.connectSession(sessionData);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.connect);
|
||||||
|
sinon.assert.calledWith(session.connect, "1234567890", "1357924680");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("On connection complete", function() {
|
||||||
|
it("should publish the stream if the publisher is ready", function() {
|
||||||
|
driver._publisherReady = true;
|
||||||
|
session.connect.callsArg(2);
|
||||||
|
|
||||||
|
driver.connectSession(sessionData);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.publish);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch connectionFailure if connecting failed", function() {
|
||||||
|
session.connect.callsArgWith(2, new Error("Failure"));
|
||||||
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
|
|
||||||
|
driver.connectSession(sessionData);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("reason", "couldNotConnect"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#disconnectionSession", function() {
|
||||||
|
it("should disconnect the session", function() {
|
||||||
|
driver.session = session;
|
||||||
|
|
||||||
|
driver.disconnectSession();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.disconnect);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should destroy the publisher", function() {
|
||||||
|
driver.publisher = publisher;
|
||||||
|
|
||||||
|
driver.disconnectSession();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(publisher.destroy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Events", function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
driver.connectSession(sessionData);
|
||||||
|
|
||||||
|
dispatcher.dispatch(new sharedActions.SetupStreamElements({
|
||||||
|
getLocalElementFunc: function() {return fakeLocalElement;},
|
||||||
|
getRemoteElementFunc: function() {return fakeRemoteElement;},
|
||||||
|
publisherConfig: publisherConfig
|
||||||
|
}));
|
||||||
|
|
||||||
|
sandbox.stub(dispatcher, "dispatch");
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("connectionDestroyed", function() {
|
||||||
|
it("should dispatch a peerHungupCall action if the client disconnected", function() {
|
||||||
|
session.trigger("connectionDestroyed", {
|
||||||
|
reason: "clientDisconnected"
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "peerHungupCall"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a connectionFailure action if the connection failed", function() {
|
||||||
|
session.trigger("connectionDestroyed", {
|
||||||
|
reason: "networkDisconnected"
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("reason", "peerNetworkDisconnected"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("sessionDisconnected", function() {
|
||||||
|
it("should dispatch a connectionFailure action if the session was disconnected",
|
||||||
|
function() {
|
||||||
|
session.trigger("sessionDisconnected", {
|
||||||
|
reason: "networkDisconnected"
|
||||||
|
});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "connectionFailure"));
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("reason", "networkDisconnected"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("streamCreated", function() {
|
||||||
|
var fakeStream;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
fakeStream = {
|
||||||
|
fakeStream: 3
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should subscribe to the stream", function() {
|
||||||
|
session.trigger("streamCreated", {stream: fakeStream});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(session.subscribe);
|
||||||
|
sinon.assert.calledWithExactly(session.subscribe,
|
||||||
|
fakeStream, fakeRemoteElement, publisherConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispach a mediaConnected action if both streams are up", function() {
|
||||||
|
driver._publishedLocalStream = true;
|
||||||
|
|
||||||
|
session.trigger("streamCreated", {stream: fakeStream});
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||||
|
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||||
|
sinon.match.hasOwn("name", "mediaConnected"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user