Bug 1060610 - Don't update latest callUrl expiration until it is exfiltrated r=Standard8

This commit is contained in:
Adam Roach [:abr] 2014-09-01 17:17:05 -05:00
parent 56093c01cb
commit 4477a63aba
5 changed files with 108 additions and 36 deletions

View File

@ -101,7 +101,7 @@ loop.Client = (function($) {
* Callback parameters:
* - err null on successful registration, non-null otherwise.
* - callUrlData an object of the obtained call url data if successful:
* -- call_url: The url of the call
* -- callUrl: The url of the call
* -- expiresAt: The amount of hours until expiry of the url
*
* @param {String} simplepushUrl a registered Simple Push URL
@ -120,8 +120,6 @@ loop.Client = (function($) {
var urlData = JSON.parse(responseText);
cb(null, this._validate(urlData, expectedCallUrlProperties));
this.mozLoop.noteCallUrlExpiry(urlData.expiresAt);
} catch (err) {
console.log("Error requesting call info", err);
cb(err);
@ -159,8 +157,6 @@ loop.Client = (function($) {
try {
cb(null);
this.mozLoop.noteCallUrlExpiry((new Date()).getTime() / 1000);
} catch (err) {
console.log("Error deleting call info", err);
cb(err);
@ -175,7 +171,7 @@ loop.Client = (function($) {
* Callback parameters:
* - err null on successful registration, non-null otherwise.
* - callUrlData an object of the obtained call url data if successful:
* -- call_url: The url of the call
* -- callUrl: The url of the call
* -- expiresAt: The amount of hours until expiry of the url
*
* @param {String} simplepushUrl a registered Simple Push URL

View File

@ -259,16 +259,18 @@ loop.panel = (function(_, mozL10n) {
var CallUrlResult = React.createClass({displayName: 'CallUrlResult',
propTypes: {
callUrl: React.PropTypes.string,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
callUrl: React.PropTypes.string,
callUrlExpiry: React.PropTypes.number,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
copied: false,
callUrl: this.props.callUrl || ""
callUrl: this.props.callUrl || "",
callUrlExpiry: 0
};
},
@ -307,7 +309,9 @@ loop.panel = (function(_, mozL10n) {
var token = callUrlData.callToken ||
callUrl.pathname.split('/').pop();
this.setState({pending: false, copied: false, callUrl: callUrl.href});
this.setState({pending: false, copied: false,
callUrl: callUrl.href,
callUrlExpiry: callUrlData.expiresAt});
} catch(e) {
console.log(e);
this.props.notifier.errorL10n("unable_retrieve_url");
@ -324,17 +328,26 @@ loop.panel = (function(_, mozL10n) {
},
handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event);
// Note: side effect
document.location = event.target.dataset.mailto;
},
handleCopyButtonClick: function(event) {
this.handleLinkExfiltration(event);
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
handleLinkExfiltration: function(event) {
// TODO Bug 1015988 -- Increase link exfiltration telemetry count
if (this.state.callUrlExpiry) {
navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
}
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
@ -351,6 +364,7 @@ loop.panel = (function(_, mozL10n) {
PanelLayout({summary: __("share_link_header_text")},
React.DOM.div({className: "invite"},
React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true",
onCopy: this.handleLinkExfiltration,
className: inputCSSClass}),
React.DOM.p({className: "btn-group url-actions"},
React.DOM.button({className: "btn btn-email", disabled: !this.state.callUrl,

View File

@ -259,16 +259,18 @@ loop.panel = (function(_, mozL10n) {
var CallUrlResult = React.createClass({
propTypes: {
callUrl: React.PropTypes.string,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
callUrl: React.PropTypes.string,
callUrlExpiry: React.PropTypes.number,
notifier: React.PropTypes.object.isRequired,
client: React.PropTypes.object.isRequired
},
getInitialState: function() {
return {
pending: false,
copied: false,
callUrl: this.props.callUrl || ""
callUrl: this.props.callUrl || "",
callUrlExpiry: 0
};
},
@ -307,7 +309,9 @@ loop.panel = (function(_, mozL10n) {
var token = callUrlData.callToken ||
callUrl.pathname.split('/').pop();
this.setState({pending: false, copied: false, callUrl: callUrl.href});
this.setState({pending: false, copied: false,
callUrl: callUrl.href,
callUrlExpiry: callUrlData.expiresAt});
} catch(e) {
console.log(e);
this.props.notifier.errorL10n("unable_retrieve_url");
@ -324,17 +328,26 @@ loop.panel = (function(_, mozL10n) {
},
handleEmailButtonClick: function(event) {
this.handleLinkExfiltration(event);
// Note: side effect
document.location = event.target.dataset.mailto;
},
handleCopyButtonClick: function(event) {
this.handleLinkExfiltration(event);
// XXX the mozLoop object should be passed as a prop, to ease testing and
// using a fake implementation in UI components showcase.
navigator.mozLoop.copyString(this.state.callUrl);
this.setState({copied: true});
},
handleLinkExfiltration: function(event) {
// TODO Bug 1015988 -- Increase link exfiltration telemetry count
if (this.state.callUrlExpiry) {
navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry);
}
},
render: function() {
// XXX setting elem value from a state (in the callUrl input)
// makes it immutable ie read only but that is fine in our case.
@ -351,6 +364,7 @@ loop.panel = (function(_, mozL10n) {
<PanelLayout summary={__("share_link_header_text")}>
<div className="invite">
<input type="url" value={this.state.callUrl} readOnly="true"
onCopy={this.handleLinkExfiltration}
className={inputCSSClass} />
<p className="btn-group url-actions">
<button className="btn btn-email" disabled={!this.state.callUrl}

View File

@ -84,20 +84,6 @@ describe("loop.Client", function() {
sinon.assert.calledWithExactly(callback, null);
});
it("should reset all url expiry when the request succeeds", function() {
// Sets up the hawkRequest stub to trigger the callback with no error
// and the url.
var dateInMilliseconds = new Date(2014,7,20).getTime();
hawkRequestStub.callsArgWith(3, null);
sandbox.useFakeTimers(dateInMilliseconds);
client.deleteCallUrl(fakeToken, callback);
sinon.assert.calledOnce(mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(mozLoop.noteCallUrlExpiry,
dateInMilliseconds / 1000);
});
it("should send an error when the request fails", function() {
// Sets up the hawkRequest stub to trigger the callback with
// an error
@ -153,7 +139,7 @@ describe("loop.Client", function() {
sinon.assert.calledWithExactly(callback, null, callUrlData);
});
it("should note the call url expiry when the request succeeds",
it("should not update call url expiry when the request succeeds",
function() {
var callUrlData = {
"callUrl": "fakeCallUrl",
@ -167,9 +153,7 @@ describe("loop.Client", function() {
client.requestCallUrl("foo", callback);
sinon.assert.calledOnce(mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(mozLoop.noteCallUrlExpiry,
6000);
sinon.assert.notCalled(mozLoop.noteCallUrlExpiry);
});
it("should send an error when the request fails", function() {

View File

@ -49,7 +49,8 @@ describe("loop.panel", function() {
},
setLoopCharPref: sandbox.stub(),
getLoopCharPref: sandbox.stub().returns("unseen"),
copyString: sandbox.stub()
copyString: sandbox.stub(),
noteCallUrlExpiry: sinon.spy()
};
document.mozL10n.initialize(navigator.mozLoop);
@ -412,7 +413,8 @@ describe("loop.panel", function() {
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com"
callUrl: "http://example.com",
callUrlExpiry: 6000
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-copy"));
@ -422,6 +424,68 @@ describe("loop.panel", function() {
view.state.callUrl);
});
it("should note the call url expiry when the url is copied via button",
function() {
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
notifier: notifier,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-copy"));
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should note the call url expiry when the url is emailed",
function() {
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
notifier: notifier,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
view.getDOMNode().querySelector(".btn-email").dataset.mailto = "#";
TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-email"));
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should note the call url expiry when the url is copied manually",
function() {
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
notifier: notifier,
client: fakeClient
}));
view.setState({
pending: false,
copied: false,
callUrl: "http://example.com",
callUrlExpiry: 6000
});
var urlField = view.getDOMNode().querySelector("input[type='url']");
TestUtils.Simulate.copy(urlField);
sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
6000);
});
it("should notify the user when the operation failed", function() {
fakeClient.requestCallUrl = function(_, cb) {
cb("fake error");