Bug 1208466 - Part 2. If an owner of a Loop link clicks their own link and join, make it open the conversation window. r=mikedeboer

This commit is contained in:
Mark Banner 2015-09-28 15:14:53 +01:00
Родитель b082faea89
Коммит aaca92e04c
18 изменённых файлов: 1048 добавлений и 142 удалений

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 2.9 KiB

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

@ -468,6 +468,13 @@ loop.shared.actions = (function() {
// socialShareProviders: Array - Optional.
}),
/**
* Notifies if the user agent will handle the room or not.
*/
UserAgentHandlesRoom: Action.define("userAgentHandlesRoom", {
handlesRoom: Boolean
}),
/**
* Updates the Social API information when it is received.
* XXX: should move to some roomActions module - refs bug 1079284
@ -483,6 +490,16 @@ loop.shared.actions = (function() {
JoinRoom: Action.define("joinRoom", {
}),
/**
* A special action for metrics logging to define what type of join
* occurred when JoinRoom was activated.
* XXX: should move to some roomActions module - refs bug 1079284
*/
MetricsLogJoinRoom: Action.define("metricsLogJoinRoom", {
userAgentHandledRoom: Boolean
// ownRoom: Boolean - Optional. Expected if firefoxHandledRoom is true.
}),
/**
* Starts the process for the user to join the room.
* XXX: should move to some roomActions module - refs bug 1079284

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

@ -132,6 +132,9 @@ loop.store.ActiveRoomStore = (function() {
videoMuted: false,
remoteVideoEnabled: false,
failureReason: undefined,
// Whether or not Firefox can handle this room in the conversation
// window, rather than us handling it in the standalone.
userAgentHandlesRoom: undefined,
// Tracks if the room has been used during this
// session. 'Used' means at least one call has been placed
// with it. Entering and leaving the room without seeing
@ -237,6 +240,7 @@ loop.store.ActiveRoomStore = (function() {
"roomFailure",
"retryAfterRoomFailure",
"updateRoomInfo",
"userAgentHandlesRoom",
"gotMediaPermission",
"joinRoom",
"joinedRoom",
@ -327,6 +331,9 @@ loop.store.ActiveRoomStore = (function() {
* This action is only used for the standalone UI.
*
* @param {sharedActions.FetchServerData} actionData
* @return {Promise} For testing purposes, returns a promise that is resolved
* once data is received from the server, and it is determined
* if Firefox handles the room or not.
*/
fetchServerData: function(actionData) {
if (actionData.windowType !== "room") {
@ -342,68 +349,144 @@ loop.store.ActiveRoomStore = (function() {
this._registerPostSetupActions();
this._getRoomDataForStandalone(actionData.cryptoKey);
var dataPromise = this._getRoomDataForStandalone(actionData.cryptoKey);
var userAgentHandlesPromise = this._promiseDetectUserAgentHandles();
return Promise.all([dataPromise, userAgentHandlesPromise]).then(function(results) {
results.forEach(function(result) {
this.dispatcher.dispatch(result);
}.bind(this));
}.bind(this));
},
/**
* Gets the room data for the standalone, decrypting it as necessary.
*
* @param {String} roomCryptoKey The crypto key associated to the room.
* @return {Promise} A promise that is resolved once the get
* and decryption is complete.
*/
_getRoomDataForStandalone: function(roomCryptoKey) {
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
if (err) {
this.dispatchAction(new sharedActions.RoomFailure({
error: err,
failedJoinRequest: false
return new Promise(function(resolve, reject) {
this._mozLoop.rooms.get(this._storeState.roomToken, function(err, result) {
if (err) {
resolve(new sharedActions.RoomFailure({
error: err,
failedJoinRequest: false
}));
return;
}
var roomInfoData = new sharedActions.UpdateRoomInfo({
// If we've got this far, then we want to go to the ready state
// regardless of success of failure. This is because failures of
// crypto don't stop the user using the room, they just stop
// us putting up the information.
roomState: ROOM_STATES.READY,
roomUrl: result.roomUrl
});
if (!result.context && !result.roomName) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
resolve(roomInfoData);
return;
}
// This handles 'legacy', non-encrypted room names.
if (result.roomName && !result.context) {
roomInfoData.roomName = result.roomName;
resolve(roomInfoData);
return;
}
if (!crypto.isSupported()) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
resolve(roomInfoData);
return;
}
if (!roomCryptoKey) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
resolve(roomInfoData);
return;
}
crypto.decryptBytes(roomCryptoKey, result.context.value)
.then(function(decryptedResult) {
var realResult = JSON.parse(decryptedResult);
roomInfoData.roomDescription = realResult.description;
roomInfoData.roomContextUrls = realResult.urls;
roomInfoData.roomName = realResult.roomName;
resolve(roomInfoData);
}, function(error) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
resolve(roomInfoData);
});
}.bind(this));
}.bind(this));
},
/**
* If the user agent is Firefox, it sends a message to Firefox to see if
* the room can be handled within Firefox rather than the standalone UI.
*
* @return {Promise} A promise that is resolved once it has been determined
* if Firefox can handle the room.
*/
_promiseDetectUserAgentHandles: function() {
return new Promise(function(resolve, reject) {
function resolveWithNotHandlingResponse() {
resolve(new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
}
// If we're not Firefox, don't even try to see if it can be handled
// in the browser.
if (!loop.shared.utils.isFirefox(navigator.userAgent)) {
resolveWithNotHandlingResponse();
return;
}
var roomInfoData = new sharedActions.UpdateRoomInfo({
// If we've got this far, then we want to go to the ready state
// regardless of success of failure. This is because failures of
// crypto don't stop the user using the room, they just stop
// us putting up the information.
roomState: ROOM_STATES.READY,
roomUrl: result.roomUrl
});
// Set up a timer in case older versions of Firefox don't give us a response.
var timer = setTimeout(resolveWithNotHandlingResponse, 250);
var webChannelListenerFunc;
if (!result.context && !result.roomName) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_DATA;
this.dispatcher.dispatch(roomInfoData);
return;
// Listen for the result.
function webChannelListener(e) {
if (e.detail.id !== "loop-link-clicker") {
return;
}
// Stop the default response.
clearTimeout(timer);
// Remove the listener.
window.removeEventListener("WebChannelMessageToContent", webChannelListenerFunc);
// Resolve with the details of if we're able to handle or not.
resolve(new sharedActions.UserAgentHandlesRoom({
handlesRoom: !!e.detail.message && e.detail.message.response
}));
}
// This handles 'legacy', non-encrypted room names.
if (result.roomName && !result.context) {
roomInfoData.roomName = result.roomName;
this.dispatcher.dispatch(roomInfoData);
return;
}
webChannelListenerFunc = webChannelListener.bind(this);
if (!crypto.isSupported()) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED;
this.dispatcher.dispatch(roomInfoData);
return;
}
window.addEventListener("WebChannelMessageToContent", webChannelListenerFunc);
if (!roomCryptoKey) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.NO_CRYPTO_KEY;
this.dispatcher.dispatch(roomInfoData);
return;
}
var dispatcher = this.dispatcher;
crypto.decryptBytes(roomCryptoKey, result.context.value)
.then(function(decryptedResult) {
var realResult = JSON.parse(decryptedResult);
roomInfoData.roomDescription = realResult.description;
roomInfoData.roomContextUrls = realResult.urls;
roomInfoData.roomName = realResult.roomName;
dispatcher.dispatch(roomInfoData);
}, function(error) {
roomInfoData.roomInfoFailure = ROOM_INFO_FAILURES.DECRYPT_FAILED;
dispatcher.dispatch(roomInfoData);
});
// Now send a message to the chrome to see if it can handle this room.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "checkWillOpenRoom",
roomToken: this._storeState.roomToken
}
}
}));
}.bind(this));
},
@ -426,6 +509,18 @@ loop.store.ActiveRoomStore = (function() {
this.setStoreState(newState);
},
/**
* Handles the userAgentHandlesRoom action. Updates the store's data with
* the new state.
*
* @param {sharedActions.userAgentHandlesRoom} actionData
*/
userAgentHandlesRoom: function(actionData) {
this.setStoreState({
userAgentHandlesRoom: actionData.handlesRoom
});
},
/**
* Handles the updateSocialShareInfo action. Updates the room data with new
* Social API info.
@ -477,14 +572,11 @@ loop.store.ActiveRoomStore = (function() {
},
/**
* Handles the action to join to a room.
* Checks that there are audio and video devices available, and joins the
* room if there are. If there aren't then it will dispatch a ConnectionFailure
* action with NO_MEDIA.
*/
joinRoom: function() {
// Reset the failure reason if necessary.
if (this.getStoreState().failureReason) {
this.setStoreState({failureReason: undefined});
}
_checkDevicesAndJoinRoom: function() {
// XXX Ideally we'd do this check before joining a room, but we're waiting
// for the UX for that. See bug 1166824. In the meantime this gives us
// additional information for analysis.
@ -501,6 +593,77 @@ loop.store.ActiveRoomStore = (function() {
}.bind(this));
},
/**
* Hands off the room join to Firefox.
*/
_handoffRoomJoin: function() {
var channelListener;
function handleRoomJoinResponse(e) {
if (e.detail.id !== "loop-link-clicker") {
return;
}
window.removeEventListener("WebChannelMessageToContent", channelListener);
if (!e.detail.message || !e.detail.message.response) {
// XXX Firefox didn't handle this, even though it said it could
// previously. We should add better user feedback here.
console.error("Firefox didn't handle room it said it could.");
} else {
this.dispatcher.dispatch(new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
}
}
channelListener = handleRoomJoinResponse.bind(this);
window.addEventListener("WebChannelMessageToContent", channelListener);
// Now we're set up, dispatch an event.
window.dispatchEvent(new window.CustomEvent("WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "openRoom",
roomToken: this._storeState.roomToken
}
}
}));
},
/**
* Handles the action to join to a room.
*/
joinRoom: function() {
// Reset the failure reason if necessary.
if (this.getStoreState().failureReason) {
this.setStoreState({ failureReason: undefined });
}
// If we're standalone and we know Firefox can handle the room, then hand
// it off.
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
}));
this._handoffRoomJoin();
return;
}
this.dispatcher.dispatch(new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: false
}));
// Otherwise, we handle the room ourselves.
this._checkDevicesAndJoinRoom();
},
/**
* Handles the action that signifies when media permission has been
* granted and starts joining the room.
@ -540,6 +703,15 @@ loop.store.ActiveRoomStore = (function() {
* @param {sharedActions.JoinedRoom} actionData
*/
joinedRoom: function(actionData) {
// If we're standalone and firefox is handling, then just store the new
// state. No need to do anything else.
if (this._storeState.standalone && this._storeState.userAgentHandlesRoom) {
this.setStoreState({
roomState: ROOM_STATES.JOINED
});
return;
}
this.setStoreState({
apiKey: actionData.apiKey,
sessionToken: actionData.sessionToken,

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

@ -25,6 +25,27 @@ body,
background: #000;
}
/* Logos */
.loop-logo-text {
background: url("../img/hello-logo-text.svg") no-repeat;
width: 200px;
height: 36px;
}
.loop-logo {
background: url("../shared/img/helloicon.svg") no-repeat;
width: 100px;
height: 100px;
}
.mozilla-logo {
background: url("../img/mozilla-logo.svg#logo") no-repeat;
background-size: contain;
width: 100px;
height: 30px;
}
.room-conversation-wrapper > .beta-logo {
position: fixed;
top: 0;
@ -43,7 +64,7 @@ body,
margin: 0 auto;
height: 30px;
background-size: contain;
background-image: url("../shared/img/mozilla-logo.png");
background-image: url("../img/mozilla-logo.svg#logo-white");
background-repeat: no-repeat;
}
@ -138,6 +159,44 @@ html[dir="rtl"] .rooms-footer .footer-logo {
line-height: 24px;
}
/**
* Handle in Firefox views
*/
.handle-user-agent-view-scroller {
height: 100%;
overflow: scroll;
}
.handle-user-agent-view {
margin: 2rem auto;
width: 500px;
}
.handle-user-agent-view > .info-panel {
padding-bottom: 40px;
font-size: 1.6rem;
}
.handle-user-agent-view > p,
.handle-user-agent-view > .info-panel > p {
margin-top: 0;
margin: 2rem auto;
}
.handle-user-agent-view > .info-panel > button {
width: 80%;
height: 4rem;
font-size: 1.6rem;
font-weight: bold;
}
.handle-user-agent-view > .info-panel > button.disabled {
background-color: #EBEBEB;
border-color: #EBEBEB;
color: #B2B0B3;
}
/* Room wrapper layout */
.room-conversation-wrapper {

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 467.5 76"><path fill="#5D5F64" d="M263.4 28.2c-.2.4-.5.8-.8 1.2-.3.3-.7.6-1.2.8-.5.2-.9.3-1.4.3-.5 0-1-.1-1.4-.3-.5-.2-.8-.4-1.2-.8-.3-.3-.6-.7-.8-1.2-.2-.4-.3-.9-.3-1.4 0-.5.1-1 .3-1.4.2-.4.4-.8.8-1.2.3-.3.7-.6 1.2-.8.4-.2.9-.3 1.4-.3.5 0 1 .1 1.4.3.4.2.8.4 1.2.8.3.3.6.7.8 1.2.2.5.3.9.3 1.4 0 .5-.1 1-.3 1.4zm-.7-2.6c-.2-.4-.4-.7-.6-1-.2-.3-.6-.5-.9-.7-.4-.2-.7-.2-1.1-.2-.4 0-.8.1-1.1.2-.4.2-.7.4-.9.7-.3.3-.5.6-.6 1-.2.4-.2.8-.2 1.2 0 .4.1.8.2 1.2.2.4.4.7.6 1 .3.3.6.5.9.7.3.2.7.2 1.1.2.4 0 .8-.1 1.1-.2.4-.2.7-.4.9-.7.3-.3.5-.6.6-1 .1-.4.2-.8.2-1.2.1-.4 0-.8-.2-1.2zm-2.2 2.6c-.1-.3-.3-.5-.4-.6-.1-.1-.2-.3-.3-.4l-.1-.1h-.2v1.8h-.7v-4.1h1.3c.2 0 .4 0 .6.1.2.1.3.1.4.2.1.1.2.2.2.4 0 .1.1.3.1.5 0 .3-.1.6-.3.8-.2.2-.4.3-.8.3l.1.1.2.2c.1.1.1.2.2.2.1.1.1.2.2.3l.6 1h-.8l-.3-.7zm0-2.8c-.1-.1-.3-.2-.6-.2h-.4v1.3h.8c.1 0 .2-.1.2-.1.1-.1.2-.3.2-.5s0-.3-.2-.5zM35.6 13.9h-24v19h19.5v9.4H11.6v32.1H0V4.3h37.1l-1.5 9.6zM41.7 8.2c0-4.1 3.2-7.5 7.4-7.5 3.9 0 7.3 3.2 7.3 7.5 0 4.1-3.3 7.4-7.5 7.4-4.1 0-7.2-3.4-7.2-7.4zm1.6 66.2V24l11.2-2v52.4H43.3zM89.2 32.9c-1.1-.4-1.9-.7-3.1-.7-4.7 0-8.6 3.4-9.6 7.6v34.6H65.3V38.3c0-6.5-.7-10.6-1.8-13.8l10.2-2.6c1.2 2.3 1.9 5.3 1.9 8.1 4-5.6 8.1-8.2 13.1-8.2 1.6 0 2.6.2 3.9.8l-3.4 10.3zM103.3 51.8v.8c0 7.1 2.6 14.6 12.7 14.6 4.8 0 8.9-1.7 12.7-5.1l4.4 6.8c-5.4 4.6-11.5 6.8-18.4 6.8-14.6 0-23.7-10.4-23.7-26.8 0-9 1.9-15 6.4-20 4.2-4.8 9.2-6.9 15.7-6.9 5.1 0 9.7 1.3 14.1 5.3 4.5 4 6.7 10.3 6.7 22.3v2.3h-30.6zm9.8-21.4c-6.3 0-9.7 5-9.7 13.3h18.9c0-8.4-3.6-13.3-9.2-13.3zM165.5 10c-2.5-1.2-4-1.8-6.2-1.8-3.8 0-6.3 2.6-6.3 7.2v7.8h13.4l-2.8 7.7h-10.4v43.5h-11V30.9h-4.8v-7.7h5s-.3-2.8-.3-7.6C142.1 5 148.5 0 157.6 0c4.4 0 8 .9 11.5 2.9l-3.6 7.1zM210.1 49c0 16.5-8.8 26.7-22.7 26.7-13.9 0-22.6-10.4-22.6-26.8S173.6 22 187.2 22c14.6 0 22.9 10.8 22.9 27zm-32.9-.8c0 14.9 3.7 19.2 10.4 19.2 6.6 0 10.2-5.4 10.2-18.2 0-14.5-4-18.8-10.5-18.8-7 0-10.1 5.3-10.1 17.8z"/><path fill="#5D5F64" d="M243.6 74.4c-1.8-2.9-10.1-17.3-11.1-19.1-1.9 3.8-9.2 16.3-11.1 19.1h-14.1l19-27.8-14.8-22 12-2.4c2.3 3.8 6.9 11.8 9.3 16.6 1.4-3.3 6.6-13.6 8.1-15.7h13l-15.1 23.3 18.7 27.9h-13.9z"/><g fill="#5D5F64"><path d="M280.6 4.5h8.2v29.3h29.5V4.5h8.4v70.1h-8.4V40.7h-29.5v33.9h-8.2V4.5zM371.5 64.3l3.1 5.1c-4.5 4.1-10.6 6.3-17.2 6.3-14.1 0-22.6-10.2-22.6-27.1 0-8.6 1.8-14.1 6.1-19.2 4.1-4.8 9.1-7.1 15.2-7.1 5.5 0 10.3 1.9 13.8 5.5 4.4 4.5 5.6 9.3 5.8 21.5v1.1H344v1.2c0 4.8.6 8.5 2.3 11.1 2.9 4.4 7.6 6.2 12.7 6.2 4.9.2 8.9-1.3 12.5-4.6zM344 44.5h23.3c-.1-5.5-.8-8.9-2.3-11.3-1.7-2.8-5.3-4.5-9.2-4.5-7.3-.1-11.4 5.2-11.8 15.8zM393.5 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM413.9 64.4c0 4 .6 5.1 2.9 5.1.3 0 1-.2 1-.2l1.6 5.2c-2 .9-3 1.1-5.1 1.1-2.5 0-4.5-.7-6-2.1-1.6-1.4-2.5-3.6-2.5-7.3v-54c0-6.6-1.2-10.4-1.2-10.4l8-1.5s1.3 4.3 1.3 12.1v52zM445.3 22.2c8.5 0 14 3.9 17.5 8.9 3.2 4.6 4.7 10.6 4.7 18.9 0 17-9.1 26-21.9 26-14 0-22-10.3-22-27.1 0-16.6 8.3-26.7 21.7-26.7zm-.1 6.5c-4.5 0-8.7 2.1-10.4 5.5-1.6 3.2-2.5 7.3-2.5 13.3 0 7.2 1.2 13.5 3.2 16.7 1.8 3.1 5.9 5.1 10.3 5.1 5.3 0 9.3-2.8 11-7.7 1.1-3.2 1.5-6 1.5-11 0-7.2-.7-12-2.4-15.3-2-4.5-6.5-6.6-10.7-6.6z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 3.2 KiB

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

@ -0,0 +1 @@
<svg width="568" height="148" viewBox="0 0 568 148" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>Fill 1 Copy</title><style>use:not(:target) { display: none; } use { fill: #383838; } use[id$=&quot;-white&quot;] { fill: #fff; }</style><defs><path id="mozilla-logo" d="M23.39 42.294c1.72 2.656 2.478 5.026 3.44 10.024 6.728-6.568 15.025-10.024 24.08-10.024 8.18 0 14.89 2.656 20.085 8.077 1.4 1.36 2.75 3.113 3.913 4.833 9.038-9.257 17.132-12.91 27.976-12.91 7.722 0 15.042 2.31 19.51 6.156 5.583 4.823 7.353 10.622 7.353 24.124v70.244h-25.092V77.606c0-11.82-1.4-14.107-8.13-14.107-4.822 0-11.6 3.29-17.166 8.305v71.013H54.872V78.533c0-12.326-1.787-15.22-8.97-15.22-4.773 0-11.384 2.472-16.95 7.514v71.99H3.694V73.913c0-14.266-.978-20.43-3.693-25.277l23.39-6.34zm152.244 28.92c-1.772 5.228-2.715 12.168-2.715 22.016 0 11.357 1.162 19.89 3.27 24.89 2.327 5.406 8.146 8.104 13.12 8.104 11.197 0 15.986-10.025 15.986-33.372 0-13.324-1.737-22.025-5.193-26.476-2.48-3.255-6.51-5.194-11.164-5.194-6.19 0-11.216 3.844-13.306 10.032zm46.49-14.265c7.893 9.257 11.418 20.057 11.418 36.07 0 16.982-3.895 28.582-12.412 38.212-7.486 8.483-17.352 13.71-32.563 13.71-26.863 0-44.384-20.076-44.384-51.13 0-31.08 17.706-51.738 44.384-51.738 14.08 0 25.077 4.84 33.558 14.875zm94.588-12.935V61.77l-43.64 63.096h45.344l-6.19 17.952h-72.713v-16.012l46.46-64.645H243.39V44.016h73.322zm40.694-2.328v101.13h-25.853V45.75l25.853-4.063zm3.035-24.874c0 8.89-7.066 15.987-16.002 15.987-8.652 0-15.767-7.098-15.767-15.987 0-8.87 7.353-16.03 16.204-16.03 8.67 0 15.567 7.16 15.567 16.03zm44.048 8.89v76.98c0 17.006.203 19.292 1.755 21.99.977 1.753 3.068 2.698 5.227 2.698.927 0 1.483 0 2.883-.354l4.42 15.42c-4.42 1.73-9.832 2.693-15.43 2.693-11.03 0-19.9-5.197-22.97-13.477-1.94-5.024-2.36-8.127-2.36-22.208V35.7c0-12.918-.337-20.81-1.3-29.722L403.157 0c.926 5.397 1.33 11.77 1.33 25.702zm53.625 0v76.98c0 17.006.22 19.292 1.806 21.99.91 1.753 3 2.698 5.16 2.698.977 0 1.567 0 2.933-.354l4.402 15.42c-4.402 1.73-9.816 2.693-15.432 2.693-11.01 0-19.897-5.197-22.983-13.477-1.973-5.024-2.294-8.127-2.294-22.208V35.7c0-12.918-.387-20.81-1.383-29.722L456.73 0c1.064 5.397 1.383 11.77 1.383 25.702zm74.082 73.894c-17.875 0-24.148 3.254-24.148 15.076 0 7.688 4.89 12.9 11.45 12.9 4.806 0 9.664-2.513 13.492-6.746l.42-21.23h-1.215zM498.687 47.69c9.613-4.064 17.878-5.785 26.983-5.785 16.628 0 27.993 6.157 31.888 17.167 1.282 4.05 1.872 7.135 1.755 17.757L558.687 110v1.752c0 10.607 1.755 14.672 9.313 20.255l-13.73 15.85c-6.038-2.53-11.416-6.98-13.93-11.982-1.905 1.948-4.047 3.837-6.003 5.202-4.79 3.476-11.787 5.414-19.883 5.414-22.005 0-33.96-11.215-33.96-30.86 0-23.196 16.053-34.015 47.485-34.015 1.888 0 3.676 0 5.818.22v-4.03c0-11.02-2.142-14.69-11.653-14.69-8.196 0-17.926 4.03-28.517 11.19l-11.012-18.533c5.245-3.29 9.108-5.203 16.07-8.087z"/></defs><use id="logo" xlink:href="#mozilla-logo"/><use id="logo-white" xlink:href="#mozilla-logo"/></svg>

После

Ширина:  |  Высота:  |  Размер: 2.9 KiB

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

@ -44,7 +44,7 @@ loop.store.StandaloneMetricsStore = (function() {
"connectedToSdkServers",
"connectionFailure",
"gotMediaPermission",
"joinRoom",
"metricsLogJoinRoom",
"joinedRoom",
"leaveRoom",
"mediaConnected",
@ -144,10 +144,20 @@ loop.store.StandaloneMetricsStore = (function() {
/**
* Handles the user clicking the join room button.
*
* @param {sharedActions.MetricsLogJoinRoom} actionData
*/
joinRoom: function() {
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
metricsLogJoinRoom: function(actionData) {
var label;
if (actionData.userAgentHandledRoom) {
label = actionData.ownRoom ? "Joined own room in Firefox" :
"Joined in Firefox";
} else {
label = "Join the conversation";
}
this._storeEvent(METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button, label);
},
/**

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

@ -58,6 +58,59 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneHandleUserAgentView = React.createClass({displayName: "StandaloneHandleUserAgentView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
getInitialState: function() {
return this.getStoreState();
},
handleJoinButton: function() {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
var buttonClasses = React.addons.classSet({
btn: true,
"btn-info": true,
disabled: this.state.roomState === ROOM_STATES.JOINED
});
// 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
// we need in some places.
return (
React.createElement("div", {className: "handle-user-agent-view-scroller"},
React.createElement("div", {className: "handle-user-agent-view"},
React.createElement("div", {className: "info-panel"},
React.createElement("p", {className: "loop-logo-text", title: mozL10n.get("clientShortname2") }),
React.createElement("p", {className: "roomName"}, this.state.roomName),
React.createElement("p", {className: "loop-logo"}),
React.createElement("button", {
className: buttonClasses,
onClick: this.handleJoinButton},
buttonMessage
)
),
React.createElement(ToSView, {
dispatcher: this.props.dispatcher}),
React.createElement("p", {className: "mozilla-logo"})
)
)
);
}
});
/**
* Handles display of failures, determining the correct messages and
* displaying the retry button at appropriate times.
@ -611,7 +664,45 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomControllerView = React.createClass({displayName: "StandaloneRoomControllerView",
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
isFirefox: React.PropTypes.bool.isRequired
},
getInitialState: function() {
return this.getStoreState();
},
render: function() {
// If we don't know yet, don't display anything.
if (this.state.firefoxHandlesRoom === undefined) {
return null;
}
if (this.state.firefoxHandlesRoom) {
return (
React.createElement(StandaloneHandleUserAgentView, {
dispatcher: this.props.dispatcher})
);
}
return (
React.createElement(StandaloneRoomView, {
activeRoomStore: this.getStore(),
dispatcher: this.props.dispatcher,
isFirefox: this.props.isFirefox})
);
}
});
return {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,

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

@ -58,6 +58,59 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneHandleUserAgentView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
getInitialState: function() {
return this.getStoreState();
},
handleJoinButton: function() {
this.props.dispatcher.dispatch(new sharedActions.JoinRoom());
},
render: function() {
var buttonMessage = this.state.roomState === ROOM_STATES.JOINED ?
mozL10n.get("rooms_room_joined_own_conversation_label") :
mozL10n.get("rooms_room_join_label");
var buttonClasses = React.addons.classSet({
btn: true,
"btn-info": true,
disabled: this.state.roomState === ROOM_STATES.JOINED
});
// 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
// we need in some places.
return (
<div className="handle-user-agent-view-scroller">
<div className="handle-user-agent-view">
<div className="info-panel">
<p className="loop-logo-text" title={ mozL10n.get("clientShortname2") }></p>
<p className="roomName">{ this.state.roomName }</p>
<p className="loop-logo" />
<button
className={buttonClasses}
onClick={this.handleJoinButton}>
{buttonMessage}
</button>
</div>
<ToSView
dispatcher={this.props.dispatcher} />
<p className="mozilla-logo" />
</div>
</div>
);
}
});
/**
* Handles display of failures, determining the correct messages and
* displaying the retry button at appropriate times.
@ -611,7 +664,45 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomControllerView = React.createClass({
mixins: [
loop.store.StoreMixin("activeRoomStore")
],
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
isFirefox: React.PropTypes.bool.isRequired
},
getInitialState: function() {
return this.getStoreState();
},
render: function() {
// If we don't know yet, don't display anything.
if (this.state.firefoxHandlesRoom === undefined) {
return null;
}
if (this.state.firefoxHandlesRoom) {
return (
<StandaloneHandleUserAgentView
dispatcher={this.props.dispatcher} />
);
}
return (
<StandaloneRoomView
activeRoomStore={this.getStore()}
dispatcher={this.props.dispatcher}
isFirefox={this.props.isFirefox} />
);
}
});
return {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomFooter: StandaloneRoomFooter,
StandaloneRoomHeader: StandaloneRoomHeader,

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

@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
}
case "room": {
return (
React.createElement(loop.standaloneRoomViews.StandaloneRoomView, {
React.createElement(loop.standaloneRoomViews.StandaloneRoomControllerView, {
activeRoomStore: this.props.activeRoomStore,
dispatcher: this.props.dispatcher,
isFirefox: this.state.isFirefox})

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

@ -153,7 +153,7 @@ loop.webapp = (function(_, OT, mozL10n) {
}
case "room": {
return (
<loop.standaloneRoomViews.StandaloneRoomView
<loop.standaloneRoomViews.StandaloneRoomControllerView
activeRoomStore={this.props.activeRoomStore}
dispatcher={this.props.dispatcher}
isFirefox={this.state.isFirefox} />

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

@ -68,6 +68,7 @@ rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start
rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
rooms_room_joined_label=Someone has joined the conversation!
rooms_room_join_label=Join the conversation
rooms_room_joined_own_conversation_label=Enjoy your conversation
rooms_display_name_guest=Guest
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.

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

@ -6,6 +6,7 @@ describe("loop.store.ActiveRoomStore", function () {
var expect = chai.expect;
var sharedActions = loop.shared.actions;
var sharedUtils = loop.shared.utils;
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
var ROOM_STATES = loop.store.ROOM_STATES;
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
@ -434,20 +435,20 @@ describe("loop.store.ActiveRoomStore", function () {
sinon.assert.calledOnce(fakeMozLoop.rooms.get);
});
it("should dispatch an UpdateRoomInfo message with 'no data' failure if neither roomName nor context are supplied", function() {
it("should dispatch an UpdateRoomInfo message with failure if neither roomName nor context are supplied", function() {
fakeMozLoop.rooms.get.callsArgWith(1, null, {
roomUrl: "http://invalid"
});
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
roomState: ROOM_STATES.READY,
roomUrl: "http://invalid"
}));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo({
roomInfoFailure: ROOM_INFO_FAILURES.NO_DATA,
roomState: ROOM_STATES.READY,
roomUrl: "http://invalid"
}));
});
});
describe("mozLoop.rooms.get returns roomName as a separate field (no context)", function() {
@ -459,13 +460,13 @@ describe("loop.store.ActiveRoomStore", function () {
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
});
});
});
@ -491,25 +492,25 @@ describe("loop.store.ActiveRoomStore", function () {
it("should dispatch UpdateRoomInfo message with 'unsupported' failure if WebCrypto is unsupported", function() {
loop.crypto.isSupported.returns(false);
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.WEB_CRYPTO_UNSUPPORTED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with 'no crypto key' failure if there is no crypto key", function() {
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.NO_CRYPTO_KEY,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with 'decrypt failed' failure if decryption failed", function() {
@ -525,14 +526,14 @@ describe("loop.store.ActiveRoomStore", function () {
};
});
store.fetchServerData(fetchServerAction);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomInfoFailure: ROOM_INFO_FAILURES.DECRYPT_FAILED,
roomState: ROOM_STATES.READY
}, expectedDetails)));
});
});
it("should dispatch UpdateRoomInfo message with the context if decryption was successful", function() {
@ -558,18 +559,175 @@ describe("loop.store.ActiveRoomStore", function () {
};
});
store.fetchServerData(fetchServerAction);
return store.fetchServerData(fetchServerAction).then(function() {
var expectedData = _.extend({
roomContextUrls: roomContext.urls,
roomDescription: roomContext.description,
roomName: roomContext.roomName,
roomState: ROOM_STATES.READY
}, expectedDetails);
var expectedData = _.extend({
roomContextUrls: roomContext.urls,
roomDescription: roomContext.description,
roomName: roomContext.roomName,
roomState: ROOM_STATES.READY
}, expectedDetails);
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(expectedData));
});
});
});
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(expectedData));
describe("User Agent Room Handling", function() {
var channelListener, roomDetails;
beforeEach(function() {
sandbox.stub(sharedUtils, "isFirefox").returns(true);
roomDetails = {
roomName: "fakeName",
roomUrl: "http://invalid"
};
fakeMozLoop.rooms.get.callsArgWith(1, null, roomDetails);
sandbox.stub(window, "addEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent") {
channelListener = listener;
}
});
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent" &&
listener === channelListener) {
channelListener = null;
}
});
});
it("should dispatch UserAgentHandlesRoom with false if the user agent is not Firefox", function() {
sharedUtils.isFirefox.returns(false);
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with false after a timeout if there is no response from the channel", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
sandbox.clock.tick(250);
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should not dispatch if a message is returned not for the link-clicker", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
// We call the listener twice, but the first time with an invalid id.
// Hence we should only get the dispatch once.
channelListener({
detail: {
id: "invalid-id",
message: null
}
});
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
// Although this is only called once for the UserAgentHandlesRoom,
// it gets called twice due to the UpdateRoomInfo. Therefore,
// we test both results here.
sinon.assert.calledTwice(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomInfo(_.extend({
roomState: ROOM_STATES.READY
}, roomDetails)));
});
});
it("should dispatch with false if the user agent does not understand the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with false if the user agent cannot handle the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: false
}
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: false
}));
});
});
it("should dispatch with true if the user agent can handle the message", function() {
// When the dispatchEvent is called, we know the setup code has run, so
// advance the timer.
sandbox.stub(window, "dispatchEvent", function() {
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true
}
}
});
});
return store.fetchServerData(fetchServerAction).then(function() {
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UserAgentHandlesRoom({
handlesRoom: true
}));
});
});
});
});
@ -624,6 +782,20 @@ describe("loop.store.ActiveRoomStore", function () {
});
});
describe("#userAgentHandlesRoom", function() {
it("should update the store state", function() {
store.setStoreState({
UserAgentHandlesRoom: false
});
store.userAgentHandlesRoom(new sharedActions.UserAgentHandlesRoom({
handlesRoom: true
}));
expect(store.getStoreState().userAgentHandlesRoom).eql(true);
});
});
describe("#updateSocialShareInfo", function() {
var fakeSocialShareInfo;
@ -659,32 +831,138 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().failureReason).eql(undefined);
});
it("should set the state to MEDIA_WAIT if media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
describe("Standalone Handles Room", function() {
it("should dispatch a MetricsLogJoinRoom action", function() {
store.joinRoom();
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: false
}));
});
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
it("should set the state to MEDIA_WAIT if media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, true);
store.joinRoom();
expect(store.getStoreState().roomState).eql(ROOM_STATES.MEDIA_WAIT);
});
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
store.joinRoom();
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
});
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
store.joinRoom();
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.NO_MEDIA
}));
});
});
it("should not set the state to MEDIA_WAIT if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
describe("Firefox Handles Room", function() {
var channelListener;
store.joinRoom();
beforeEach(function() {
store.setStoreState({
userAgentHandlesRoom: true,
roomToken: "fakeToken",
standalone: true
});
expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
});
sandbox.stub(window, "addEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent") {
channelListener = listener;
}
});
sandbox.stub(window, "removeEventListener", function(eventName, listener) {
if (eventName === "WebChannelMessageToContent" &&
listener === channelListener) {
channelListener = null;
}
});
it("should dispatch `ConnectionFailure` if no media devices are present", function() {
sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices").callsArgWith(0, false);
sandbox.stub(console, "error");
});
store.joinRoom();
it("should dispatch a MetricsLogJoinRoom action", function() {
store.joinRoom();
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.ConnectionFailure({
reason: FAILURE_DETAILS.NO_MEDIA
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.MetricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
}));
});
it("should dispatch an event to Firefox", function() {
sandbox.stub(window, "dispatchEvent");
store.joinRoom();
sinon.assert.calledOnce(window.dispatchEvent);
sinon.assert.calledWithExactly(window.dispatchEvent, new window.CustomEvent(
"WebChannelMessageToChrome", {
detail: {
id: "loop-link-clicker",
message: {
command: "openRoom",
roomToken: "fakeToken"
}
}
}));
});
it("should log an error if Firefox doesn't handle the room", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: null
}
});
sinon.assert.calledOnce(console.error);
});
it("should dispatch a JoinedRoom action if the room was successfully opened", function() {
// Start the join.
store.joinRoom();
// Pretend Firefox calls back.
channelListener({
detail: {
id: "loop-link-clicker",
message: {
response: true
}
}
});
sinon.assert.called(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.JoinedRoom({
apiKey: "",
sessionToken: "",
sessionId: "",
expires: 0
}));
});
});
});
@ -762,6 +1040,17 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
});
it("should set the state to `JOINED` when Firefox handles the room", function() {
store.setStoreState({
userAgentHandlesRoom: true,
standalone: true
});
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
expect(store._storeState.roomState).eql(ROOM_STATES.JOINED);
});
it("should store the session and api values", function() {
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
@ -771,6 +1060,20 @@ describe("loop.store.ActiveRoomStore", function () {
expect(state.sessionId).eql(fakeJoinedData.sessionId);
});
it("should not store the session and api values when Firefox handles the room", function() {
store.setStoreState({
userAgentHandlesRoom: true,
standalone: true
});
store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
var state = store.getStoreState();
expect(state.apiKey).eql(undefined);
expect(state.sessionToken).eql(undefined);
expect(state.sessionId).eql(undefined);
});
it("should start the session connection with the sdk", function() {
var actionData = new sharedActions.JoinedRoom(fakeJoinedData);

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

@ -86,15 +86,6 @@ describe("loop.store.StandaloneMetricsStore", function() {
"Media granted");
});
it("should log an event on JoinRoom", function() {
store.joinRoom();
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
});
it("should log an event on JoinedRoom", function() {
store.joinedRoom();
@ -150,6 +141,43 @@ describe("loop.store.StandaloneMetricsStore", function() {
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Retry failed room");
});
describe("MetricsLogJoinRoom", function() {
it("should log a 'Join the conversation' event if not joined by Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: false
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Join the conversation");
});
it("should log a 'Joined own room in Firefox' event if joining the own room in Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: true
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Joined own room in Firefox");
});
it("should log a 'Joined in Firefox' event if joining a non-own room in Firefox", function() {
store.metricsLogJoinRoom({
userAgentHandledRoom: true,
ownRoom: false
});
sinon.assert.calledOnce(window.ga);
sinon.assert.calledWithExactly(window.ga,
"send", "event", METRICS_GA_CATEGORY.general, METRICS_GA_ACTIONS.button,
"Joined in Firefox");
});
});
});
describe("Store Change Handlers", function() {

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

@ -128,6 +128,63 @@ describe("loop.standaloneRoomViews", function() {
});
});
describe("StandaloneHandleUserAgentView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneHandleUserAgentView, {
dispatcher: dispatcher
}));
}
it("should display a join room button if the state is not ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.READY
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.textContent).eql("rooms_room_join_label");
});
it("should dispatch a JoinRoom action when the join room button is clicked", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.READY
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
TestUtils.Simulate.click(button);
sinon.assert.calledOnce(dispatcher.dispatch);
sinon.assert.calledWithExactly(dispatcher.dispatch, new sharedActions.JoinRoom());
});
it("should display a enjoy your conversation button if the state is ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.textContent).eql("rooms_room_joined_own_conversation_label");
});
it("should disable the enjoy your conversation button if the state is ROOM_JOINED", function() {
activeRoomStore.setStoreState({
roomState: ROOM_STATES.JOINED
});
view = mountTestComponent();
var button = view.getDOMNode().querySelector(".info-panel > button");
expect(button.classList.contains("disabled")).eql(true);
});
});
describe("StandaloneRoomHeader", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
@ -866,4 +923,47 @@ describe("loop.standaloneRoomViews", function() {
});
});
});
describe("StandaloneRoomControllerView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomControllerView, {
dispatcher: dispatcher,
isFirefox: true
}));
}
it("should not display anything if it is not known if Firefox can handle the room", function() {
activeRoomStore.setStoreState({
firefoxHandlesRoom: undefined
});
view = mountTestComponent();
expect(view.getDOMNode()).eql(null);
});
it("should render StandaloneHandleUserAgentView if Firefox can handle the room", function() {
activeRoomStore.setStoreState({
firefoxHandlesRoom: true
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.standaloneRoomViews.StandaloneHandleUserAgentView);
});
it("should render StandaloneRoomView if Firefox cannot handle the room", function() {
activeRoomStore.setStoreState({
firefoxHandlesRoom: false
});
view = mountTestComponent();
TestUtils.findRenderedComponentWithType(view,
loop.standaloneRoomViews.StandaloneRoomView);
});
});
});

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

@ -119,14 +119,14 @@ describe("loop.webapp", function() {
loop.webapp.UnsupportedBrowserView);
});
it("should display the StandaloneRoomView for `room` window type",
it("should display the StandaloneRoomControllerView for `room` window type",
function() {
standaloneAppStore.setStoreState({windowType: "room", isFirefox: true});
var webappRootView = mountTestComponent();
TestUtils.findRenderedComponentWithType(webappRootView,
loop.standaloneRoomViews.StandaloneRoomView);
loop.standaloneRoomViews.StandaloneRoomControllerView);
});
it("should display the HomeView for `home` window type", function() {

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

@ -36,6 +36,7 @@
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -1515,6 +1516,21 @@
)
),
React.createElement(Section, {name: "StandaloneHandleUserAgentView"},
React.createElement(FramedExample, {
cssClass: "standalone",
dashed: true,
height: 483,
summary: "Standalone Room Handle Join in Firefox",
width: 644},
React.createElement("div", {className: "standalone"},
React.createElement(StandaloneHandleUserAgentView, {
activeRoomStore: readyRoomStore,
dispatcher: dispatcher})
)
)
),
React.createElement(Section, {name: "StandaloneRoomView"},
React.createElement(FramedExample, {cssClass: "standalone",
dashed: true,

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

@ -36,6 +36,7 @@
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var StandaloneRoomView = loop.standaloneRoomViews.StandaloneRoomView;
var StandaloneHandleUserAgentView = loop.standaloneRoomViews.StandaloneHandleUserAgentView;
// 3. Shared components
var ConversationToolbar = loop.shared.views.ConversationToolbar;
@ -1515,6 +1516,21 @@
</FramedExample>
</Section>
<Section name="StandaloneHandleUserAgentView">
<FramedExample
cssClass="standalone"
dashed={true}
height={483}
summary="Standalone Room Handle Join in Firefox"
width={644} >
<div className="standalone">
<StandaloneHandleUserAgentView
activeRoomStore={readyRoomStore}
dispatcher={dispatcher} />
</div>
</FramedExample>
</Section>
<Section name="StandaloneRoomView">
<FramedExample cssClass="standalone"
dashed={true}