Bug 1209078 - Part 2. If a user attempts to open their own room within Firefox when the room is already open, provide a message to inform the user. r=mikedeboer

This commit is contained in:
Mark Banner 2015-09-29 14:50:15 +01:00
Родитель 218567ce2a
Коммит 85a8eff79d
10 изменённых файлов: 169 добавлений и 26 удалений

Просмотреть файл

@ -623,12 +623,18 @@ loop.store.ActiveRoomStore = (function() {
// previously. We should add better user feedback here. // previously. We should add better user feedback here.
console.error("Firefox didn't handle room it said it could."); console.error("Firefox didn't handle room it said it could.");
} else { } else {
this.dispatcher.dispatch(new sharedActions.JoinedRoom({ if (e.detail.message.alreadyOpen) {
apiKey: "", this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
sessionToken: "", reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
sessionId: "", }));
expires: 0 } else {
})); this.dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
}
} }
} }
@ -1064,6 +1070,15 @@ loop.store.ActiveRoomStore = (function() {
* will skip the leave message. * will skip the leave message.
*/ */
_leaveRoom: function(nextState, failedJoinRequest) { _leaveRoom: function(nextState, failedJoinRequest) {
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
// If the user agent is handling the room, all we need to do is advance
// to the next state.
this.setStoreState({
roomState: nextState
});
return;
}
if (loop.standaloneMedia) { if (loop.standaloneMedia) {
loop.standaloneMedia.multiplexGum.reset(); loop.standaloneMedia.multiplexGum.reset();
} }

Просмотреть файл

@ -75,6 +75,7 @@ var inChrome = typeof Components != "undefined" && "utils" in Components;
var FAILURE_DETAILS = { var FAILURE_DETAILS = {
MEDIA_DENIED: "reason-media-denied", MEDIA_DENIED: "reason-media-denied",
NO_MEDIA: "reason-no-media", NO_MEDIA: "reason-no-media",
ROOM_ALREADY_OPEN: "reason-room-already-open",
UNABLE_TO_PUBLISH_MEDIA: "unable-to-publish-media", UNABLE_TO_PUBLISH_MEDIA: "unable-to-publish-media",
USER_UNAVAILABLE: "reason-user-unavailable", USER_UNAVAILABLE: "reason-user-unavailable",
COULD_NOT_CONNECT: "reason-could-not-connect", COULD_NOT_CONNECT: "reason-could-not-connect",

Просмотреть файл

@ -184,6 +184,14 @@ html[dir="rtl"] .rooms-footer .footer-logo {
margin: 2rem auto; margin: 2rem auto;
} }
.handle-user-agent-view > .info-panel > .failure {
color: red;
font-weight: bold;
/* Add padding to match the height of the button. */
padding: 1.15rem 0;
margin: 0;
}
.handle-user-agent-view > .info-panel > button { .handle-user-agent-view > .info-panel > button {
width: 80%; width: 80%;
height: 4rem; height: 4rem;

Просмотреть файл

@ -125,12 +125,19 @@ loop.store.StandaloneMetricsStore = (function() {
* @param {sharedActions.ConnectionFailure} actionData * @param {sharedActions.ConnectionFailure} actionData
*/ */
connectionFailure: function(actionData) { connectionFailure: function(actionData) {
if (actionData.reason === FAILURE_DETAILS.MEDIA_DENIED) { switch(actionData.reason) {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed, case FAILURE_DETAILS.MEDIA_DENIED:
"Media denied"); this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
} else if (actionData.reason === FAILURE_DETAILS.NO_MEDIA) { "Media denied");
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed, break;
"No media"); case FAILURE_DETAILS.NO_MEDIA:
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"No media");
break;
case FAILURE_DETAILS.ROOM_ALREADY_OPEN:
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Room already open");
break;
} }
}, },

Просмотреть файл

@ -75,7 +75,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom()); this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
}, },
render: function() { _renderJoinButton: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ? var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") : mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label"); mozL10n.get("rooms_room_join_label");
@ -86,6 +86,22 @@ loop.standaloneRoomViews = (function(mozL10n) {
disabled: this.state.roomState === ROOM_STATES.JOINED disabled: this.state.roomState === ROOM_STATES.JOINED
}); });
return (
React.createElement("button", {
className: buttonClasses,
onClick: this.handleJoinButton},
buttonMessage
)
);
},
_renderFailureText: function() {
return (
React.createElement("p", {className: "failure"}, mozL10n.get("rooms_already_joined") )
);
},
render: function() {
// The extra scroller div here is for providing a scroll view for shorter // The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which // screens, as the common.css specifies overflow:hidden for the body which
// we need in some places. // we need in some places.
@ -96,11 +112,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }), React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }),
React.createElement("p", {className: "roomName"}, this.state.roomName), React.createElement("p", {className: "roomName"}, this.state.roomName),
React.createElement("p", {className: "loop-logo"}), React.createElement("p", {className: "loop-logo"}),
React.createElement("button", {
className: buttonClasses, this.state.failureReason ?
onClick: this.handleJoinButton}, this._renderFailureText() :
buttonMessage this._renderJoinButton()
)
), ),
React.createElement(ToSView, { React.createElement(ToSView, {
dispatcher: this.props.dispatcher}), dispatcher: this.props.dispatcher}),

Просмотреть файл

@ -75,7 +75,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom()); this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
}, },
render: function() { _renderJoinButton: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ? var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") : mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label"); mozL10n.get("rooms_room_join_label");
@ -86,6 +86,22 @@ loop.standaloneRoomViews = (function(mozL10n) {
disabled: this.state.roomState === ROOM_STATES.JOINED disabled: this.state.roomState === ROOM_STATES.JOINED
}); });
return (
<button
className={buttonClasses}
onClick={this.handleJoinButton}>
{buttonMessage}
</button>
);
},
_renderFailureText: function() {
return (
<p className="failure">{ mozL10n.get("rooms_already_joined") }</p>
);
},
render: function() {
// The extra scroller div here is for providing a scroll view for shorter // The extra scroller div here is for providing a scroll view for shorter
// screens, as the common.css specifies overflow:hidden for the body which // screens, as the common.css specifies overflow:hidden for the body which
// we need in some places. // we need in some places.
@ -96,11 +112,11 @@ loop.standaloneRoomViews = (function(mozL10n) {
<p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p> <p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p>
<p className="roomName">{ this.state.roomName }</p> <p className="roomName">{ this.state.roomName }</p>
<p className="loop-logo" /> <p className="loop-logo" />
<button {
className={buttonClasses} this.state.failureReason ?
onClick={this.handleJoinButton}> this._renderFailureText() :
{buttonMessage} this._renderJoinButton()
</button> }
</div> </div>
<ToSView <ToSView
dispatcher={this.props.dispatcher} /> dispatcher={this.props.dispatcher} />

Просмотреть файл

@ -69,6 +69,7 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
rooms_room_joined_label=Someone has joined the conversation! rooms_room_joined_label=Someone has joined the conversation!
rooms_room_join_label=Join the conversation rooms_room_join_label=Join the conversation
rooms_room_joined_own_conversation_label=Enjoy your conversation rooms_room_joined_own_conversation_label=Enjoy your conversation
rooms_already_joined=You're already in this conversation.
rooms_display_name_guest=Guest rooms_display_name_guest=Guest
rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid. rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again. rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.

Просмотреть файл

@ -873,7 +873,7 @@ describe("loop.store.ActiveRoomStore", function () {
}); });
}); });
describe("Firefox Handles Room", function() { describe("User Agent Handles Room", function() {
var channelListener; var channelListener;
beforeEach(function() { beforeEach(function() {
@ -951,7 +951,8 @@ describe("loop.store.ActiveRoomStore", function () {
detail: { detail: {
id: "loop-link-clicker", id: "loop-link-clicker",
message: { message: {
response: true response: true,
alreadyOpen: false
} }
} }
}); });
@ -965,6 +966,28 @@ describe("loop.store.ActiveRoomStore", function () {
expires: 0 expires: 0
})); }));
}); });
it("should dispatch a ConnectionFailure action if the room was already opened", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true,
alreadyOpen: true
}
}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
}));
});
}); });
}); });
@ -1257,6 +1280,29 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED); expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
}); });
it("should set the state to `FAILED` if the user agent is handling the room", function() {
store.setStoreState({
standalone: true,
userAgentHandlesRoom: true
});
store.connectionFailure(connectionFailureAction);
expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
});
it("should not do any other cleanup if the user agent is handling the room", function() {
store.setStoreState({
standalone: true,
userAgentHandlesRoom: true
});
store.connectionFailure(connectionFailureAction);
sinon.assert.notCalled(fakeMultiplexGum.reset);
sinon.assert.notCalled(fakeSdkDriver.disconnectSession);
});
}); });
describe("#setMute", function() { describe("#setMute", function() {

Просмотреть файл

@ -77,6 +77,17 @@ describe("loop.store.StandaloneMetricsStore", function() {
"No media"); "No media");
}); });
it("should log an event on connection failure if the room was already open", function() {
store.connectionFailure(new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
}));
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.failed,
"Room already open");
});
it("should log an event on GotMediaPermission", function() { it("should log an event on GotMediaPermission", function() {
store.gotMediaPermission(); store.gotMediaPermission();

Просмотреть файл

@ -183,6 +183,28 @@ describe("loop.standaloneRoomViews", function() {
expect(button.classList.contains("disabled")).eql(true); expect(button.classList.contains("disabled")).eql(true);
}); });
it("should not display a join button if there is a failure reason", function() {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button).eql(null);
});
it("should display a room already joined message if opening failed", function() {
activeRoomStore.setStoreState({
failureReason: FAILURE_DETAILS.ROOM_ALREADY_OPEN
});
view = mountTestComponent();
var text = view.getDOMNode().querySelector(".failure");
expect(text.textContent).eql("rooms_already_joined");
});
}); });
describe("StandaloneRoomHeader", function() { describe("StandaloneRoomHeader", function() {