diff --git a/browser/components/loop/content/js/conversationViews.js b/browser/components/loop/content/js/conversationViews.js index d86c6691fea2..8db57f09256c 100644 --- a/browser/components/loop/content/js/conversationViews.js +++ b/browser/components/loop/content/js/conversationViews.js @@ -27,6 +27,13 @@ loop.conversationViews = (function(mozL10n) { return contact.email.find(e => e.pref) || contact.email[0]; } + function _getContactDisplayName(contact) { + if (contact.name && contact.name[0]) { + return contact.name[0]; + } + return _getPreferredEmail(contact).value; + } + /** * Displays information about the call * Caller avatar, name & conversation creation date @@ -107,14 +114,7 @@ loop.conversationViews = (function(mozL10n) { }, render: function() { - var contactName; - - if (this.props.contact.name && - this.props.contact.name[0]) { - contactName = this.props.contact.name[0]; - } else { - contactName = _getPreferredEmail(this.props.contact).value; - } + var contactName = _getContactDisplayName(this.props.contact); document.title = contactName; @@ -262,7 +262,10 @@ loop.conversationViews = (function(mozL10n) { emailLinkButtonDisabled: true }); - this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink()); + this.props.dispatcher.dispatch(new sharedActions.FetchRoomEmailLink({ + roomOwner: navigator.mozLoop.userProfile.email, + roomName: _getContactDisplayName(this.props.contact) + })); }, render: function() { diff --git a/browser/components/loop/content/js/conversationViews.jsx b/browser/components/loop/content/js/conversationViews.jsx index cf3368c94cd0..4b8ab9bb9205 100644 --- a/browser/components/loop/content/js/conversationViews.jsx +++ b/browser/components/loop/content/js/conversationViews.jsx @@ -27,6 +27,13 @@ loop.conversationViews = (function(mozL10n) { return contact.email.find(e => e.pref) || contact.email[0]; } + function _getContactDisplayName(contact) { + if (contact.name && contact.name[0]) { + return contact.name[0]; + } + return _getPreferredEmail(contact).value; + } + /** * Displays information about the call * Caller avatar, name & conversation creation date @@ -107,14 +114,7 @@ loop.conversationViews = (function(mozL10n) { }, render: function() { - var contactName; - - if (this.props.contact.name && - this.props.contact.name[0]) { - contactName = this.props.contact.name[0]; - } else { - contactName = _getPreferredEmail(this.props.contact).value; - } + var contactName = _getContactDisplayName(this.props.contact); document.title = contactName; @@ -262,7 +262,10 @@ loop.conversationViews = (function(mozL10n) { emailLinkButtonDisabled: true }); - this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink()); + this.props.dispatcher.dispatch(new sharedActions.FetchRoomEmailLink({ + roomOwner: navigator.mozLoop.userProfile.email, + roomName: _getContactDisplayName(this.props.contact) + })); }, render: function() { diff --git a/browser/components/loop/content/shared/js/actions.js b/browser/components/loop/content/shared/js/actions.js index 5355d4fb11b6..3dffc285df65 100644 --- a/browser/components/loop/content/shared/js/actions.js +++ b/browser/components/loop/content/shared/js/actions.js @@ -76,10 +76,12 @@ loop.shared.actions = (function() { }), /** - * Fetch a new call url from the server, intended to be sent over email when + * Fetch a new room url from the server, intended to be sent over email when * a contact can't be reached. */ - FetchEmailLink: Action.define("fetchEmailLink", { + FetchRoomEmailLink: Action.define("fetchRoomEmailLink", { + roomOwner: String, + roomName: String }), /** diff --git a/browser/components/loop/content/shared/js/conversationStore.js b/browser/components/loop/content/shared/js/conversationStore.js index c5eb044e97d4..ffc271c9732b 100644 --- a/browser/components/loop/content/shared/js/conversationStore.js +++ b/browser/components/loop/content/shared/js/conversationStore.js @@ -210,7 +210,7 @@ loop.store = loop.store || {}; "retryCall", "mediaConnected", "setMute", - "fetchEmailLink" + "fetchRoomEmailLink" ]); this.setStoreState({ @@ -323,18 +323,21 @@ loop.store = loop.store || {}; }, /** - * Fetches a new call URL intended to be sent over email when a contact + * Fetches a new room URL intended to be sent over email when a contact * can't be reached. */ - fetchEmailLink: function() { - // XXX This is an empty string as a conversation identifier. Bug 1015938 implements - // a user-set string. - this.client.requestCallUrl("", function(err, callUrlData) { + fetchRoomEmailLink: function(actionData) { + this.mozLoop.rooms.create({ + roomName: actionData.roomName, + roomOwner: actionData.roomOwner, + maxSize: loop.store.MAX_ROOM_CREATION_SIZE, + expiresIn: loop.store.DEFAULT_EXPIRES_IN + }, function(err, createdRoomData) { if (err) { this.trigger("error:emailLink"); return; } - this.setStoreState({"emailLink": callUrlData.callUrl}); + this.setStoreState({"emailLink": createdRoomData.roomUrl}); }.bind(this)); }, diff --git a/browser/components/loop/content/shared/js/roomStore.js b/browser/components/loop/content/shared/js/roomStore.js index 4b1da8a36c96..4118f87e1cda 100644 --- a/browser/components/loop/content/shared/js/roomStore.js +++ b/browser/components/loop/content/shared/js/roomStore.js @@ -16,6 +16,20 @@ loop.store = loop.store || {}; */ var sharedActions = loop.shared.actions; + /** + * Maximum size given to createRoom; only 2 is supported (and is + * always passed) because that's what the user-experience is currently + * designed and tested to handle. + * @type {Number} + */ + var MAX_ROOM_CREATION_SIZE = loop.store.MAX_ROOM_CREATION_SIZE = 2; + + /** + * The number of hours for which the room will exist - default 8 weeks + * @type {Number} + */ + var DEFAULT_EXPIRES_IN = loop.store.DEFAULT_EXPIRES_IN = 24 * 7 * 8; + /** * Room validation schema. See validate.js. * @type {Object} @@ -61,13 +75,13 @@ loop.store = loop.store || {}; * designed and tested to handle. * @type {Number} */ - maxRoomCreationSize: 2, + maxRoomCreationSize: MAX_ROOM_CREATION_SIZE, /** * The number of hours for which the room will exist - default 8 weeks * @type {Number} */ - defaultExpiresIn: 24 * 7 * 8, + defaultExpiresIn: DEFAULT_EXPIRES_IN, /** * Registered actions. diff --git a/browser/components/loop/test/desktop-local/conversationViews_test.js b/browser/components/loop/test/desktop-local/conversationViews_test.js index 2d4e47f7b263..be0ed37bf3d5 100644 --- a/browser/components/loop/test/desktop-local/conversationViews_test.js +++ b/browser/components/loop/test/desktop-local/conversationViews_test.js @@ -56,7 +56,10 @@ describe("loop.conversationViews", function () { }, getAudioBlob: sinon.spy(function(name, callback) { callback(null, new Blob([new ArrayBuffer(10)], {type: "audio/ogg"})); - }) + }), + userProfile: { + email: "bob@invalid.tld" + } }; fakeWindow = { @@ -241,12 +244,15 @@ describe("loop.conversationViews", function () { describe("CallFailedView", function() { var store, fakeAudio; - function mountTestComponent(props) { + var contact = {email: [{value: "test@test.tld"}]}; + + function mountTestComponent(options) { + options = options || {}; return TestUtils.renderIntoDocument( loop.conversationViews.CallFailedView({ dispatcher: dispatcher, store: store, - contact: {email: [{value: "test@test.tld"}]} + contact: options.contact })); } @@ -266,7 +272,7 @@ describe("loop.conversationViews", function () { it("should dispatch a retryCall action when the retry button is pressed", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); var retryBtn = view.getDOMNode().querySelector('.btn-retry'); @@ -279,7 +285,7 @@ describe("loop.conversationViews", function () { it("should dispatch a cancelCall action when the cancel button is pressed", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); var cancelBtn = view.getDOMNode().querySelector('.btn-cancel'); @@ -290,9 +296,9 @@ describe("loop.conversationViews", function () { sinon.match.hasOwn("name", "cancelCall")); }); - it("should dispatch a fetchEmailLink action when the cancel button is pressed", + it("should dispatch a fetchRoomEmailLink action when the email button is pressed", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); var emailLinkBtn = view.getDOMNode().querySelector('.btn-email'); @@ -300,12 +306,32 @@ describe("loop.conversationViews", function () { sinon.assert.calledOnce(dispatcher.dispatch); sinon.assert.calledWithMatch(dispatcher.dispatch, - sinon.match.hasOwn("name", "fetchEmailLink")); + sinon.match.hasOwn("name", "fetchRoomEmailLink")); + sinon.assert.calledWithMatch(dispatcher.dispatch, + sinon.match.hasOwn("roomOwner", fakeMozLoop.userProfile.email)); + sinon.assert.calledWithMatch(dispatcher.dispatch, + sinon.match.hasOwn("roomName", "test@test.tld")); + }); + + it("should name the created room using the contact name when available", + function() { + view = mountTestComponent({contact: { + email: [{value: "test@test.tld"}], + name: ["Mr Fake ContactName"] + }}); + + var emailLinkBtn = view.getDOMNode().querySelector('.btn-email'); + + React.addons.TestUtils.Simulate.click(emailLinkBtn); + + sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledWithMatch(dispatcher.dispatch, + sinon.match.hasOwn("roomName", "Mr Fake ContactName")); }); it("should disable the email link button once the action is dispatched", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); var emailLinkBtn = view.getDOMNode().querySelector('.btn-email'); React.addons.TestUtils.Simulate.click(emailLinkBtn); @@ -314,7 +340,7 @@ describe("loop.conversationViews", function () { it("should compose an email once the email link is received", function() { var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail"); - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); store.setStoreState({emailLink: "http://fake.invalid/"}); sinon.assert.calledOnce(composeCallUrlEmail); @@ -324,7 +350,7 @@ describe("loop.conversationViews", function () { it("should close the conversation window once the email link is received", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); store.setStoreState({emailLink: "http://fake.invalid/"}); @@ -333,7 +359,7 @@ describe("loop.conversationViews", function () { it("should display an error message in case email link retrieval failed", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); store.trigger("error:emailLink"); @@ -342,7 +368,7 @@ describe("loop.conversationViews", function () { it("should allow retrying to get a call url if it failed previously", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); store.trigger("error:emailLink"); @@ -350,7 +376,7 @@ describe("loop.conversationViews", function () { }); it("should play a failure sound, once", function() { - view = mountTestComponent(); + view = mountTestComponent({contact: contact}); sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob); sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob, diff --git a/browser/components/loop/test/shared/conversationStore_test.js b/browser/components/loop/test/shared/conversationStore_test.js index d75d3fc7e8bc..784ff5936b53 100644 --- a/browser/components/loop/test/shared/conversationStore_test.js +++ b/browser/components/loop/test/shared/conversationStore_test.js @@ -42,6 +42,9 @@ describe("loop.store.ConversationStore", function () { calls: { setCallInProgress: sandbox.stub(), clearCallInProgress: sandbox.stub() + }, + rooms: { + create: sandbox.stub() } }; @@ -701,20 +704,29 @@ describe("loop.store.ConversationStore", function () { }); }); - describe("#fetchEmailLink", function() { + describe("#fetchRoomEmailLink", function() { it("should request a new call url to the server", function() { - store.fetchEmailLink(new sharedActions.FetchEmailLink()); + store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({ + roomOwner: "bob@invalid.tld", + roomName: "FakeRoomName" + })); - sinon.assert.calledOnce(client.requestCallUrl); - sinon.assert.calledWith(client.requestCallUrl, ""); + sinon.assert.calledOnce(fakeMozLoop.rooms.create); + sinon.assert.calledWithMatch(fakeMozLoop.rooms.create, { + roomOwner: "bob@invalid.tld", + roomName: "FakeRoomName" + }); }); - it("should update the emailLink attribute when the new call url is received", + it("should update the emailLink attribute when the new room url is received", function() { - client.requestCallUrl = function(callId, cb) { - cb(null, {callUrl: "http://fake.invalid/"}); + fakeMozLoop.rooms.create = function(roomData, cb) { + cb(null, {roomUrl: "http://fake.invalid/"}); }; - store.fetchEmailLink(new sharedActions.FetchEmailLink()); + store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({ + roomOwner: "bob@invalid.tld", + roomName: "FakeRoomName" + })); expect(store.getStoreState("emailLink")).eql("http://fake.invalid/"); }); @@ -722,10 +734,13 @@ describe("loop.store.ConversationStore", function () { it("should trigger an error:emailLink event in case of failure", function() { var trigger = sandbox.stub(store, "trigger"); - client.requestCallUrl = function(callId, cb) { - cb("error"); + fakeMozLoop.rooms.create = function(roomData, cb) { + cb(new Error("error")); }; - store.fetchEmailLink(new sharedActions.FetchEmailLink()); + store.fetchRoomEmailLink(new sharedActions.FetchRoomEmailLink({ + roomOwner: "bob@invalid.tld", + roomName: "FakeRoomName" + })); sinon.assert.calledOnce(trigger); sinon.assert.calledWithExactly(trigger, "error:emailLink");