зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1153788 - Part 2. Ask the user to re-sign in to Loop if they don't have encryption keys for FxA. r=mikedeboer
This commit is contained in:
Родитель
d8578f5f36
Коммит
a31132716f
|
@ -23,6 +23,41 @@ body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
/* Sign-in request view */
|
||||
|
||||
.sign-in-request {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.sign-in-request > h1 {
|
||||
font-size: 1.7em;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.sign-in-request > h2,
|
||||
.sign-in-request > a {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.sign-in-request > a {
|
||||
cursor: pointer;
|
||||
color: #0295df;
|
||||
}
|
||||
|
||||
.sign-in-request > a:hover:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sign-in-request-button {
|
||||
font-size: 1rem;
|
||||
margin: 1rem;
|
||||
width: 80%;
|
||||
padding: .5rem 1rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Tabs and tab selection buttons */
|
||||
|
||||
.tab-view-container {
|
||||
|
|
|
@ -214,6 +214,45 @@ loop.panel = (function(_, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays a view requesting the user to sign-in again.
|
||||
*/
|
||||
var SignInRequestView = React.createClass({displayName: "SignInRequestView",
|
||||
mixins: [sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
handleSignInClick: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.mozLoop.logInToFxA(true);
|
||||
this.closeWindow();
|
||||
},
|
||||
|
||||
handleGuestClick: function(event) {
|
||||
this.props.mozLoop.logOutFromFxA();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.createElement("div", {className: "sign-in-request"},
|
||||
React.createElement("h1", null, mozL10n.get("sign_in_again_title_line_one")),
|
||||
React.createElement("h2", null, mozL10n.get("sign_in_again_title_line_two")),
|
||||
React.createElement("div", null,
|
||||
React.createElement("button", {className: "btn btn-info sign-in-request-button",
|
||||
onClick: this.handleSignInClick},
|
||||
mozL10n.get("sign_in_again_button")
|
||||
)
|
||||
),
|
||||
React.createElement("a", {onClick: this.handleGuestClick},
|
||||
mozL10n.get("sign_in_again_use_as_guest_button")
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ToSView = React.createClass({displayName: "ToSView",
|
||||
mixins: [sharedMixins.WindowCloseMixin],
|
||||
|
||||
|
@ -758,6 +797,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
|
||||
userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
|
||||
gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
|
||||
};
|
||||
|
@ -796,7 +836,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
var profile = this.props.mozLoop.userProfile;
|
||||
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
|
||||
var newUid = profile ? profile.uid : null;
|
||||
if (currUid != newUid) {
|
||||
if (currUid == newUid) {
|
||||
// Update the state of hasEncryptionKey as this might have changed now.
|
||||
this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey});
|
||||
} else {
|
||||
// On profile change (login, logout), switch back to the default tab.
|
||||
this.selectTab("rooms");
|
||||
this.setState({userProfile: profile});
|
||||
|
@ -827,7 +870,11 @@ loop.panel = (function(_, mozL10n) {
|
|||
},
|
||||
|
||||
selectTab: function(name) {
|
||||
this.refs.tabView.setState({ selectedTab: name });
|
||||
// The tab view might not be created yet (e.g. getting started or fxa
|
||||
// re-sign in.
|
||||
if (this.refs.tabView) {
|
||||
this.refs.tabView.setState({ selectedTab: name });
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -865,6 +912,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
);
|
||||
}
|
||||
|
||||
if (!this.state.hasEncryptionKey) {
|
||||
return React.createElement(SignInRequestView, {mozLoop: this.props.mozLoop});
|
||||
}
|
||||
|
||||
// Determine which buttons to NOT show.
|
||||
var hideButtons = [];
|
||||
if (!this.state.userProfile && !this.props.showTabButtons) {
|
||||
|
@ -937,8 +988,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
notifications: notifications,
|
||||
roomStore: roomStore,
|
||||
mozLoop: navigator.mozLoop,
|
||||
dispatcher: dispatcher}
|
||||
), document.querySelector("#main"));
|
||||
dispatcher: dispatcher}), document.querySelector("#main"));
|
||||
|
||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||
|
@ -959,6 +1009,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
SignInRequestView: SignInRequestView,
|
||||
ToSView: ToSView,
|
||||
UserIdentity: UserIdentity
|
||||
};
|
||||
|
|
|
@ -214,6 +214,45 @@ loop.panel = (function(_, mozL10n) {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays a view requesting the user to sign-in again.
|
||||
*/
|
||||
var SignInRequestView = React.createClass({
|
||||
mixins: [sharedMixins.WindowCloseMixin],
|
||||
|
||||
propTypes: {
|
||||
mozLoop: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
handleSignInClick: function(event) {
|
||||
event.preventDefault();
|
||||
this.props.mozLoop.logInToFxA(true);
|
||||
this.closeWindow();
|
||||
},
|
||||
|
||||
handleGuestClick: function(event) {
|
||||
this.props.mozLoop.logOutFromFxA();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="sign-in-request">
|
||||
<h1>{mozL10n.get("sign_in_again_title_line_one")}</h1>
|
||||
<h2>{mozL10n.get("sign_in_again_title_line_two")}</h2>
|
||||
<div>
|
||||
<button className="btn btn-info sign-in-request-button"
|
||||
onClick={this.handleSignInClick}>
|
||||
{mozL10n.get("sign_in_again_button")}
|
||||
</button>
|
||||
</div>
|
||||
<a onClick={this.handleGuestClick}>
|
||||
{mozL10n.get("sign_in_again_use_as_guest_button")}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var ToSView = React.createClass({
|
||||
mixins: [sharedMixins.WindowCloseMixin],
|
||||
|
||||
|
@ -758,6 +797,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
hasEncryptionKey: this.props.mozLoop.hasEncryptionKey,
|
||||
userProfile: this.props.userProfile || this.props.mozLoop.userProfile,
|
||||
gettingStartedSeen: this.props.mozLoop.getLoopPref("gettingStarted.seen")
|
||||
};
|
||||
|
@ -796,7 +836,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
var profile = this.props.mozLoop.userProfile;
|
||||
var currUid = this.state.userProfile ? this.state.userProfile.uid : null;
|
||||
var newUid = profile ? profile.uid : null;
|
||||
if (currUid != newUid) {
|
||||
if (currUid == newUid) {
|
||||
// Update the state of hasEncryptionKey as this might have changed now.
|
||||
this.setState({hasEncryptionKey: this.props.mozLoop.hasEncryptionKey});
|
||||
} else {
|
||||
// On profile change (login, logout), switch back to the default tab.
|
||||
this.selectTab("rooms");
|
||||
this.setState({userProfile: profile});
|
||||
|
@ -827,7 +870,11 @@ loop.panel = (function(_, mozL10n) {
|
|||
},
|
||||
|
||||
selectTab: function(name) {
|
||||
this.refs.tabView.setState({ selectedTab: name });
|
||||
// The tab view might not be created yet (e.g. getting started or fxa
|
||||
// re-sign in.
|
||||
if (this.refs.tabView) {
|
||||
this.refs.tabView.setState({ selectedTab: name });
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -865,6 +912,10 @@ loop.panel = (function(_, mozL10n) {
|
|||
);
|
||||
}
|
||||
|
||||
if (!this.state.hasEncryptionKey) {
|
||||
return <SignInRequestView mozLoop={this.props.mozLoop} />;
|
||||
}
|
||||
|
||||
// Determine which buttons to NOT show.
|
||||
var hideButtons = [];
|
||||
if (!this.state.userProfile && !this.props.showTabButtons) {
|
||||
|
@ -937,8 +988,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
notifications={notifications}
|
||||
roomStore={roomStore}
|
||||
mozLoop={navigator.mozLoop}
|
||||
dispatcher={dispatcher}
|
||||
/>, document.querySelector("#main"));
|
||||
dispatcher={dispatcher} />, document.querySelector("#main"));
|
||||
|
||||
document.body.setAttribute("dir", mozL10n.getDirection());
|
||||
document.body.setAttribute("platform", loop.shared.utils.getPlatform());
|
||||
|
@ -959,6 +1009,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
RoomEntry: RoomEntry,
|
||||
RoomList: RoomList,
|
||||
SettingsDropdown: SettingsDropdown,
|
||||
SignInRequestView: SignInRequestView,
|
||||
ToSView: ToSView,
|
||||
UserIdentity: UserIdentity
|
||||
};
|
||||
|
|
|
@ -669,11 +669,20 @@ function injectLoopAPI(targetWindow) {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the FxA login flow using the OAuth client and params from the Loop
|
||||
* server.
|
||||
*
|
||||
* @param {Boolean} forceReAuth Set to true to force FxA into a re-auth even
|
||||
* if the user is already logged in.
|
||||
* @return {Promise} Returns a promise that is resolved on successful
|
||||
* completion, or rejected otherwise.
|
||||
*/
|
||||
logInToFxA: {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function() {
|
||||
return MozLoopService.logInToFxA();
|
||||
value: function(forceReAuth) {
|
||||
return MozLoopService.logInToFxA(forceReAuth);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -693,6 +702,18 @@ function injectLoopAPI(targetWindow) {
|
|||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this profile has an encryption key.
|
||||
*
|
||||
* @return {Boolean} True if the profile has an encryption key.
|
||||
*/
|
||||
hasEncryptionKey: {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return MozLoopService.hasEncryptionKey;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the Getting Started tour in the browser.
|
||||
*
|
||||
|
|
|
@ -948,9 +948,10 @@ let MozLoopServiceInternal = {
|
|||
/**
|
||||
* Get the OAuth client constructed with Loop OAauth parameters.
|
||||
*
|
||||
* @param {Boolean} forceReAuth Set to true to force the user to reauthenticate.
|
||||
* @return {Promise}
|
||||
*/
|
||||
promiseFxAOAuthClient: Task.async(function* () {
|
||||
promiseFxAOAuthClient: Task.async(function* (forceReAuth) {
|
||||
// We must make sure to have only a single client otherwise they will have different states and
|
||||
// multiple channels. This would happen if the user clicks the Login button more than once.
|
||||
if (gFxAOAuthClientPromise) {
|
||||
|
@ -961,6 +962,10 @@ let MozLoopServiceInternal = {
|
|||
parameters => {
|
||||
// Add the fact that we want keys to the parameters.
|
||||
parameters.keys = true;
|
||||
if (forceReAuth) {
|
||||
parameters.action = "force_auth";
|
||||
parameters.email = MozLoopService.userProfile.email;
|
||||
}
|
||||
|
||||
try {
|
||||
gFxAOAuthClient = new FxAccountsOAuthClient({
|
||||
|
@ -984,11 +989,12 @@ let MozLoopServiceInternal = {
|
|||
/**
|
||||
* Get the OAuth client and do the authorization web flow to get an OAuth code.
|
||||
*
|
||||
* @param {Boolean} forceReAuth Set to true to force the user to reauthenticate.
|
||||
* @return {Promise}
|
||||
*/
|
||||
promiseFxAOAuthAuthorization: function() {
|
||||
promiseFxAOAuthAuthorization: function(forceReAuth) {
|
||||
let deferred = Promise.defer();
|
||||
this.promiseFxAOAuthClient().then(
|
||||
this.promiseFxAOAuthClient(forceReAuth).then(
|
||||
client => {
|
||||
client.onComplete = this._fxAOAuthComplete.bind(this, deferred);
|
||||
client.onError = this._fxAOAuthError.bind(this, deferred);
|
||||
|
@ -1366,6 +1372,18 @@ this.MozLoopService = {
|
|||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if this profile has an encryption key. For guest profiles
|
||||
* this is always true, since we can generate a new one if needed. For FxA
|
||||
* profiles, we need to check the preference.
|
||||
*
|
||||
* @return {Boolean} True if the profile has an encryption key.
|
||||
*/
|
||||
get hasEncryptionKey() {
|
||||
return !this.userProfile ||
|
||||
Services.prefs.prefHasUserValue("loop.key.fxa");
|
||||
},
|
||||
|
||||
get errors() {
|
||||
return MozLoopServiceInternal.errors;
|
||||
},
|
||||
|
@ -1468,14 +1486,15 @@ this.MozLoopService = {
|
|||
*
|
||||
* The caller should be prepared to handle rejections related to network, server or login errors.
|
||||
*
|
||||
* @param {Boolean} forceReAuth Set to true to force the user to reauthenticate.
|
||||
* @return {Promise} that resolves when the FxA login flow is complete.
|
||||
*/
|
||||
logInToFxA: function() {
|
||||
logInToFxA: function(forceReAuth) {
|
||||
log.debug("logInToFxA with fxAOAuthTokenData:", !!MozLoopServiceInternal.fxAOAuthTokenData);
|
||||
if (MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
if (!forceReAuth && MozLoopServiceInternal.fxAOAuthTokenData) {
|
||||
return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData);
|
||||
}
|
||||
return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
|
||||
return MozLoopServiceInternal.promiseFxAOAuthAuthorization(forceReAuth).then(response => {
|
||||
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
|
||||
}).then(tokenData => {
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
|
||||
|
|
|
@ -70,6 +70,9 @@ describe("loop.panel", function() {
|
|||
on: sandbox.stub()
|
||||
},
|
||||
confirm: sandbox.stub(),
|
||||
hasEncryptionKey: true,
|
||||
logInToFxA: sandbox.stub(),
|
||||
logOutFromFxA: sandbox.stub(),
|
||||
notifyUITour: sandbox.stub(),
|
||||
openURL: sandbox.stub(),
|
||||
getSelectedTabMetadata: sandbox.stub()
|
||||
|
@ -453,6 +456,22 @@ describe("loop.panel", function() {
|
|||
} catch (ex) {}
|
||||
});
|
||||
|
||||
it("should render a SignInRequestView when mozLoop.hasEncryptionKey is false", function() {
|
||||
fakeMozLoop.hasEncryptionKey = false;
|
||||
|
||||
var view = createTestPanelView();
|
||||
|
||||
TestUtils.findRenderedComponentWithType(view, loop.panel.SignInRequestView);
|
||||
});
|
||||
|
||||
it("should render a SignInRequestView when mozLoop.hasEncryptionKey is true", function() {
|
||||
var view = createTestPanelView();
|
||||
|
||||
try {
|
||||
TestUtils.findRenderedComponentWithType(view, loop.panel.SignInRequestView);
|
||||
sinon.assert.fail("Should not find the GettingStartedView if it has been seen");
|
||||
} catch (ex) {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -930,4 +949,32 @@ describe("loop.panel", function() {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
describe("loop.panel.SignInRequestView", function() {
|
||||
var view;
|
||||
|
||||
function mountTestComponent() {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.panel.SignInRequestView, {
|
||||
mozLoop: fakeMozLoop
|
||||
}));
|
||||
}
|
||||
|
||||
it("should call login with forced re-authentication when sign-in is clicked", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode().querySelector("button"));
|
||||
|
||||
sinon.assert.calledOnce(fakeMozLoop.logInToFxA);
|
||||
sinon.assert.calledWithExactly(fakeMozLoop.logInToFxA, true);
|
||||
});
|
||||
|
||||
it("should logout when use as guest is clicked", function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
TestUtils.Simulate.click(view.getDOMNode().querySelector("a"));
|
||||
|
||||
sinon.assert.calledOnce(fakeMozLoop.logOutFromFxA);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ const kFxAKeyPref = "loop.key.fxa";
|
|||
|
||||
do_register_cleanup(function() {
|
||||
Services.prefs.clearUserPref(kGuestKeyPref);
|
||||
Services.prefs.clearUserPref(kFxAKeyPref);
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = null;
|
||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
||||
});
|
||||
|
@ -58,3 +59,26 @@ add_task(function* test_fxaGetKey() {
|
|||
yield Assert.rejects(MozLoopService.promiseProfileEncryptionKey(),
|
||||
/not implemented/, "should reject as unimplemented");
|
||||
});
|
||||
|
||||
add_task(function test_hasEncryptionKey() {
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = null;
|
||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
||||
|
||||
Services.prefs.clearUserPref(kGuestKeyPref);
|
||||
Services.prefs.clearUserPref(kFxAKeyPref);
|
||||
|
||||
Assert.ok(MozLoopService.hasEncryptionKey, "should return true in guest mode without a key");
|
||||
|
||||
Services.prefs.setCharPref(kGuestKeyPref, "123456");
|
||||
|
||||
Assert.ok(MozLoopService.hasEncryptionKey, "should return true in guest mode with a key");
|
||||
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
|
||||
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
|
||||
|
||||
Assert.ok(!MozLoopService.hasEncryptionKey, "should return false in fxa mode without a key");
|
||||
|
||||
Services.prefs.setCharPref(kFxAKeyPref, "12345678");
|
||||
|
||||
Assert.ok(MozLoopService.hasEncryptionKey, "should return true in fxa mode with a key");
|
||||
});
|
||||
|
|
|
@ -132,6 +132,7 @@ navigator.mozLoop = {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
hasEncryptionKey: true,
|
||||
setLoopPref: function(){},
|
||||
releaseCallData: function() {},
|
||||
copyString: function() {},
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
var SignInRequestView = loop.panel.SignInRequestView;
|
||||
// 1.2. Conversation Window
|
||||
var AcceptCallView = loop.conversationViews.AcceptCallView;
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
|
@ -265,6 +266,9 @@
|
|||
React.createElement("p", {className: "note"},
|
||||
React.createElement("strong", null, "Note:"), " 332px wide."
|
||||
),
|
||||
React.createElement(Example, {summary: "Re-sign-in view", dashed: "true", style: {width: "332px"}},
|
||||
React.createElement(SignInRequestView, {mozLoop: mockMozLoopRooms})
|
||||
),
|
||||
React.createElement(Example, {summary: "Room list tab", dashed: "true", style: {width: "332px"}},
|
||||
React.createElement(PanelView, {client: mockClient, notifications: notifications,
|
||||
userProfile: {email: "test@example.com"},
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
// 1. Desktop components
|
||||
// 1.1 Panel
|
||||
var PanelView = loop.panel.PanelView;
|
||||
var SignInRequestView = loop.panel.SignInRequestView;
|
||||
// 1.2. Conversation Window
|
||||
var AcceptCallView = loop.conversationViews.AcceptCallView;
|
||||
var DesktopPendingConversationView = loop.conversationViews.PendingConversationView;
|
||||
|
@ -265,6 +266,9 @@
|
|||
<p className="note">
|
||||
<strong>Note:</strong> 332px wide.
|
||||
</p>
|
||||
<Example summary="Re-sign-in view" dashed="true" style={{width: "332px"}}>
|
||||
<SignInRequestView mozLoop={mockMozLoopRooms} />
|
||||
</Example>
|
||||
<Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
|
||||
<PanelView client={mockClient} notifications={notifications}
|
||||
userProfile={{email: "test@example.com"}}
|
||||
|
|
|
@ -196,6 +196,7 @@ let tests = [
|
|||
};
|
||||
MozLoopServiceInternal.fxAOAuthTokenData = fxASampleToken;
|
||||
MozLoopServiceInternal.fxAOAuthProfile = fxASampleProfile;
|
||||
Services.prefs.setCharPref("loop.key.fxa", "fake");
|
||||
yield MozLoopServiceInternal.notifyStatusChanged("login");
|
||||
|
||||
// Show the Loop menu.
|
||||
|
@ -225,6 +226,7 @@ let tests = [
|
|||
// Logout. The panel tab will switch back to 'rooms'.
|
||||
MozLoopServiceInternal.fxAOAuthTokenData =
|
||||
MozLoopServiceInternal.fxAOAuthProfile = null;
|
||||
Services.prefs.clearUserPref("loop.key.fxa");
|
||||
yield MozLoopServiceInternal.notifyStatusChanged();
|
||||
|
||||
yield tabChangePromise;
|
||||
|
|
|
@ -12,6 +12,15 @@ clientSuperShortname=Hello
|
|||
rooms_tab_button_tooltip=Conversations
|
||||
contacts_tab_button_tooltip=Contacts
|
||||
|
||||
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two):
|
||||
## These are displayed together at the top of the panel when a user is needed to
|
||||
## sign-in again. The first "line_one" is slightly bigger font that "line_two",
|
||||
## hence the separation.
|
||||
sign_in_again_title_line_one=Please sign in again
|
||||
sign_in_again_title_line_two=to continue using Firefox Hello
|
||||
sign_in_again_button=Sign In
|
||||
sign_in_again_use_as_guest_button=Use Hello as a Guest
|
||||
|
||||
## LOCALIZATION_NOTE(first_time_experience.title): clientShortname will be
|
||||
## replaced by the brand name
|
||||
first_time_experience_title={{clientShortname}} — Join the conversation
|
||||
|
|
Загрузка…
Ссылка в новой задаче