Merge pull request #7037 from farhan787/issue-#7033 r=@shane-tomlinson
fix(auth_broker): fx-desktop-v1 merged into fx-ios-v1
This commit is contained in:
Коммит
e978dc4ae0
|
@ -1,96 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* V1 of the broker to communicate with Fx Desktop when signing in to Sync.
|
||||
*/
|
||||
|
||||
define(function (require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
const _ = require('underscore');
|
||||
const FxDesktopChannel = require('../../lib/channels/fx-desktop-v1');
|
||||
const FxSyncChannelAuthenticationBroker = require('../auth_brokers/fx-sync-channel');
|
||||
const HaltBehavior = require('../../views/behaviors/halt');
|
||||
const Url = require('../../lib/url');
|
||||
|
||||
var proto = FxSyncChannelAuthenticationBroker.prototype;
|
||||
|
||||
var FxDesktopV1AuthenticationBroker = FxSyncChannelAuthenticationBroker.extend({
|
||||
type: 'fx-desktop-v1',
|
||||
|
||||
commands: {
|
||||
CAN_LINK_ACCOUNT: 'can_link_account',
|
||||
CHANGE_PASSWORD: 'change_password',
|
||||
DELETE_ACCOUNT: 'delete_account',
|
||||
LOADED: 'loaded',
|
||||
LOGIN: 'login'
|
||||
},
|
||||
|
||||
defaultBehaviors: _.extend({}, proto.defaultBehaviors, {
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterForceAuth: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after password reset, no
|
||||
// need to show anything.
|
||||
afterResetPasswordConfirmationPoll: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterSignIn: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterSignInConfirmationPoll: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterSignUpConfirmationPoll: new HaltBehavior()
|
||||
}),
|
||||
|
||||
createChannel () {
|
||||
var channel = new FxDesktopChannel();
|
||||
|
||||
channel.initialize({
|
||||
// Fx Desktop browser will send messages with an origin of the string
|
||||
// `null`. These messages are trusted by the channel by default.
|
||||
//
|
||||
// 1) Fx on iOS and functional tests will send messages from the
|
||||
// content server itself. Accept messages from the content
|
||||
// server to handle these cases.
|
||||
// 2) Fx 18 (& FxOS 1.*) do not support location.origin. Build the origin from location.href
|
||||
origin: this.window.location.origin || Url.getOrigin(this.window.location.href),
|
||||
window: this.window
|
||||
});
|
||||
|
||||
channel.on('error', this.trigger.bind(this, 'error'));
|
||||
|
||||
return channel;
|
||||
},
|
||||
|
||||
afterResetPasswordConfirmationPoll (account) {
|
||||
// We wouldn't expect `customizeSync` to be set when completing
|
||||
// a password reset, but the field must be present for the login
|
||||
// message to be sent. false is the default value set in
|
||||
// lib/fxa-client.js if the value is not present.
|
||||
// See #5528
|
||||
if (! account.has('customizeSync')) {
|
||||
account.set('customizeSync', false);
|
||||
}
|
||||
|
||||
// Only fx-desktop-v1 based integrations send a login message
|
||||
// after reset password complete, assuming the user verifies
|
||||
// in the same browser. fx-desktop-v1 based integrations
|
||||
// do not support WebChannels, and the login message must be
|
||||
// sent within about:accounts for the browser to receive it.
|
||||
// Integrations that support WebChannel messages will send
|
||||
// the login message from the verification tab, and for users
|
||||
// of either integration that verify in a different browser,
|
||||
// they will be asked to signin in this browser using the
|
||||
// new password.
|
||||
return this._notifyRelierOfLogin(account)
|
||||
.then(() => proto.afterResetPasswordConfirmationPoll.call(this, account));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = FxDesktopV1AuthenticationBroker;
|
||||
});
|
||||
|
|
@ -11,19 +11,88 @@ define(function (require, exports, module) {
|
|||
'use strict';
|
||||
|
||||
const _ = require('underscore');
|
||||
const FxDesktopV1AuthenticationBroker = require('../auth_brokers/fx-desktop-v1');
|
||||
const FxDesktopChannel = require('../../lib/channels/fx-desktop-v1');
|
||||
const FxSyncChannelAuthenticationBroker = require('../auth_brokers/fx-sync-channel');
|
||||
const HaltBehavior = require('../../views/behaviors/halt');
|
||||
const NavigateBehavior = require('../../views/behaviors/navigate');
|
||||
const UserAgent = require('../../lib/user-agent');
|
||||
|
||||
const proto = FxDesktopV1AuthenticationBroker.prototype;
|
||||
const proto = FxSyncChannelAuthenticationBroker.prototype;
|
||||
|
||||
const FxiOSV1AuthenticationBroker = FxSyncChannelAuthenticationBroker.extend({
|
||||
type: 'fx-ios-v1',
|
||||
|
||||
commands: {
|
||||
CAN_LINK_ACCOUNT: 'can_link_account',
|
||||
CHANGE_PASSWORD: 'change_password',
|
||||
DELETE_ACCOUNT: 'delete_account',
|
||||
LOADED: 'loaded',
|
||||
LOGIN: 'login'
|
||||
},
|
||||
|
||||
defaultBehaviors: _.extend({}, proto.defaultBehaviors, {
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterForceAuth: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after password reset, no
|
||||
// need to show anything.
|
||||
afterResetPasswordConfirmationPoll: new HaltBehavior(),
|
||||
// about:accounts displays its own screen after sign in, no need
|
||||
// to show anything.
|
||||
afterSignIn: new HaltBehavior(),
|
||||
// about:accounts display the "Signin confirmed" screen after
|
||||
// the user signin is successful
|
||||
afterSignInConfirmationPoll: new NavigateBehavior('signin_confirmed'),
|
||||
// about:accounts display the "Signup complete!" screen after
|
||||
// the users verify their email
|
||||
afterSignUpConfirmationPoll: new NavigateBehavior('signup_confirmed')
|
||||
}),
|
||||
|
||||
const FxiOSV1AuthenticationBroker = FxDesktopV1AuthenticationBroker.extend({
|
||||
defaultCapabilities: _.extend({}, proto.defaultCapabilities, {
|
||||
chooseWhatToSyncCheckbox: false,
|
||||
chooseWhatToSyncWebV1: true,
|
||||
convertExternalLinksToText: true
|
||||
}),
|
||||
|
||||
createChannel () {
|
||||
var channel = new FxDesktopChannel();
|
||||
|
||||
channel.initialize({
|
||||
// Fx on iOS and functional tests will send messages from the
|
||||
// content server itself. Accept messages from the content
|
||||
// server to handle these cases.
|
||||
origin: this.window.location.origin,
|
||||
window: this.window
|
||||
});
|
||||
|
||||
channel.on('error', this.trigger.bind(this, 'error'));
|
||||
|
||||
return channel;
|
||||
},
|
||||
|
||||
afterResetPasswordConfirmationPoll (account) {
|
||||
// We wouldn't expect `customizeSync` to be set when completing
|
||||
// a password reset, but the field must be present for the login
|
||||
// message to be sent. false is the default value set in
|
||||
// lib/fxa-client.js if the value is not present.
|
||||
// See #5528
|
||||
if (! account.has('customizeSync')) {
|
||||
account.set('customizeSync', false);
|
||||
}
|
||||
|
||||
// fx-ios-v1 send a login message after reset password complete,
|
||||
// assuming the user verifies in the same browser. fx-ios-v1
|
||||
// do not support WebChannels, and the login message must be
|
||||
// sent within about:accounts for the browser to receive it.
|
||||
// Integrations that support WebChannel messages will send
|
||||
// the login message from the verification tab, and for users
|
||||
// of either integration that verify in a different browser,
|
||||
// they will be asked to signin in this browser using the
|
||||
// new password.
|
||||
return this._notifyRelierOfLogin(account)
|
||||
.then(() => proto.afterResetPasswordConfirmationPoll.call(this, account));
|
||||
},
|
||||
|
||||
initialize (options = {}) {
|
||||
proto.initialize.call(this, options);
|
||||
|
||||
|
@ -36,15 +105,6 @@ define(function (require, exports, module) {
|
|||
if (! this._supportsChooseWhatToSync(version)) {
|
||||
this.setCapability('chooseWhatToSyncWebV1', false);
|
||||
}
|
||||
|
||||
// Fx for iOS allows the user to see the "confirm your email" screen,
|
||||
// but never takes it away after the user verifies. Allow the poll
|
||||
// so that the user sees the "Signup complete!" screen after they
|
||||
// verify their email.
|
||||
this.setBehavior(
|
||||
'afterSignInConfirmationPoll', new NavigateBehavior('signin_confirmed'));
|
||||
this.setBehavior(
|
||||
'afterSignUpConfirmationPoll', new NavigateBehavior('signup_confirmed'));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
define(function (require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
const chai = require('chai');
|
||||
const FxDesktopV1AuthenticationBroker = require('models/auth_brokers/fx-desktop-v1');
|
||||
const NullChannel = require('lib/channels/null');
|
||||
const sinon = require('sinon');
|
||||
const User = require('models/user');
|
||||
const WindowMock = require('../../../mocks/window');
|
||||
|
||||
var assert = chai.assert;
|
||||
|
||||
describe('models/auth_brokers/fx-desktop-v1', function () {
|
||||
var account;
|
||||
var broker;
|
||||
var channelMock;
|
||||
var user;
|
||||
var windowMock;
|
||||
|
||||
beforeEach(function () {
|
||||
windowMock = new WindowMock();
|
||||
channelMock = new NullChannel();
|
||||
channelMock.send = sinon.spy(function () {
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
user = new User();
|
||||
account = user.initAccount({
|
||||
email: 'testuser@testuser.com',
|
||||
keyFetchToken: 'key-fetch-token',
|
||||
uid: 'uid',
|
||||
unwrapBKey: 'unwrap-b-key'
|
||||
});
|
||||
|
||||
broker = new FxDesktopV1AuthenticationBroker({
|
||||
channel: channelMock,
|
||||
window: windowMock
|
||||
});
|
||||
sinon.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
|
||||
});
|
||||
|
||||
it('has the expected capabilities', () => {
|
||||
assert.isTrue(broker.hasCapability('signup'));
|
||||
assert.isTrue(broker.hasCapability('handleSignedInNotification'));
|
||||
assert.isTrue(broker.hasCapability('emailVerificationMarketingSnippet'));
|
||||
});
|
||||
|
||||
describe('createChannel', function () {
|
||||
it('creates a channel', function () {
|
||||
assert.ok(broker.createChannel());
|
||||
});
|
||||
});
|
||||
|
||||
describe('channel errors', function () {
|
||||
it('are propagated outwards', function () {
|
||||
var channel = broker.createChannel();
|
||||
|
||||
var errorSpy = sinon.spy();
|
||||
broker.on('error', errorSpy);
|
||||
|
||||
var error = new Error('malformed message');
|
||||
channel.trigger('error', error);
|
||||
assert.isTrue(errorSpy.calledWith(error));
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterLoaded', function () {
|
||||
it('sends a `loaded` message', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterLoaded()
|
||||
.then(function () {
|
||||
assert.isTrue(broker.send.calledWith('loaded'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignIn', function () {
|
||||
it('notifies the channel of login, halts by default', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterSignIn(account)
|
||||
.then(function (result) {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignInConfirmationPoll', () => {
|
||||
it('halts by default', () => {
|
||||
return broker.afterSignInConfirmationPoll(account)
|
||||
.then((result) => {
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('beforeSignUpConfirmationPoll', function () {
|
||||
it('notifies the channel of login', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.beforeSignUpConfirmationPoll(account)
|
||||
.then((result) => {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignUpConfirmationPoll', () => {
|
||||
it('halts by default', () => {
|
||||
return broker.afterSignUpConfirmationPoll(account)
|
||||
.then((result) => {
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterResetPasswordConfirmationPoll', function () {
|
||||
it('notifies the channel of login, halts by default', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
// customizeSync is required to send the `login` message, but
|
||||
// it won't be set because the user hasn't visited the signup/in
|
||||
// page.
|
||||
account.unset('customizeSync');
|
||||
|
||||
return broker.afterResetPasswordConfirmationPoll(account)
|
||||
.then(function (result) {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
assert.isTrue(broker.send.calledOnce);
|
||||
const loginData = broker.send.args[0][1];
|
||||
assert.isFalse(loginData.customizeSync);
|
||||
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterChangePassword', function () {
|
||||
it('notifies the channel of change_password with the new login info', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterChangePassword(account)
|
||||
.then(function () {
|
||||
assert.isTrue(broker.send.calledWith('change_password'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterDeleteAccount', function () {
|
||||
it('notifies the channel of delete_account', function () {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
account.set('uid', 'uid');
|
||||
|
||||
return broker.afterDeleteAccount(account)
|
||||
.then(function () {
|
||||
assert.isTrue(broker.send.calledWith('delete_account'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -5,23 +5,24 @@
|
|||
define(function (require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
const Account = require('models/account');
|
||||
const { assert } = require('chai');
|
||||
const FxiOSAuthenticationBroker = require('models/auth_brokers/fx-ios-v1');
|
||||
const FxDesktopV1AuthenticationBroker = require('models/auth_brokers/fx-desktop-v1');
|
||||
const NullChannel = require('lib/channels/null');
|
||||
const Relier = require('models/reliers/relier');
|
||||
const sinon = require('sinon');
|
||||
const User = require('models/user');
|
||||
const WindowMock = require('../../../mocks/window');
|
||||
|
||||
const IMMEDIATE_UNVERIFIED_LOGIN_UA_STRING = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/6.1 Mobile/12F69 Safari/600.1.4'; //eslint-disable-line max-len
|
||||
const CHOOSE_WHAT_TO_SYNC_UA_STRING = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/11.0 Mobile/12F69 Safari/600.1.4'; //eslint-disable-line max-len
|
||||
|
||||
describe('models/auth_brokers/fx-ios-v1', () => {
|
||||
let account;
|
||||
let broker;
|
||||
let channel;
|
||||
const loginMessageDelayMS = 250;
|
||||
let relier;
|
||||
let user;
|
||||
let windowMock;
|
||||
let sandbox;
|
||||
|
||||
|
@ -33,6 +34,14 @@ define(function (require, exports, module) {
|
|||
relier: relier,
|
||||
window: windowMock
|
||||
});
|
||||
|
||||
user = new User();
|
||||
account = user.initAccount({
|
||||
email: 'testuser@testuser.com',
|
||||
keyFetchToken: 'key-fetch-token',
|
||||
uid: 'uid',
|
||||
unwrapBKey: 'unwrap-b-key'
|
||||
});
|
||||
sandbox.stub(broker, '_hasRequiredLoginFields').callsFake(() => true);
|
||||
}
|
||||
|
||||
|
@ -80,15 +89,10 @@ define(function (require, exports, module) {
|
|||
});
|
||||
|
||||
describe('_notifyRelierOfLogin', () => {
|
||||
let account;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.stub(broker, 'send').callsFake(() => Promise.resolve());
|
||||
sandbox.spy(windowMock, 'setTimeout');
|
||||
sandbox.spy(windowMock, 'clearTimeout');
|
||||
account = new Account({
|
||||
uid: 'uid'
|
||||
});
|
||||
});
|
||||
|
||||
function testLoginSent(triggerLoginCB) {
|
||||
|
@ -126,23 +130,137 @@ define(function (require, exports, module) {
|
|||
});
|
||||
|
||||
describe('afterCompleteSignInWithCode', () => {
|
||||
let account;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox.spy(broker, 'afterCompleteSignInWithCode');
|
||||
sandbox.spy(broker, '_notifyRelierOfLogin');
|
||||
sandbox.spy(FxDesktopV1AuthenticationBroker.prototype, 'afterCompleteSignInWithCode');
|
||||
account = new Account({
|
||||
uid: 'uid'
|
||||
});
|
||||
sandbox.spy(FxiOSAuthenticationBroker.prototype, 'afterCompleteSignInWithCode');
|
||||
|
||||
return broker.afterCompleteSignInWithCode(account);
|
||||
});
|
||||
|
||||
it('broker calls correct methods', () => {
|
||||
assert.isTrue(FxDesktopV1AuthenticationBroker.prototype.afterCompleteSignInWithCode.called);
|
||||
assert.isTrue(broker.afterCompleteSignInWithCode.called);
|
||||
assert.isTrue(broker._notifyRelierOfLogin.called);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createChannel', () => {
|
||||
it('creates a channel', () => {
|
||||
assert.ok(broker.createChannel());
|
||||
});
|
||||
});
|
||||
|
||||
describe('channel errors', () => {
|
||||
it('are propagated outwards', () => {
|
||||
var channel = broker.createChannel();
|
||||
|
||||
var errorSpy = sinon.spy();
|
||||
broker.on('error', errorSpy);
|
||||
|
||||
var error = new Error('malformed message');
|
||||
channel.trigger('error', error);
|
||||
assert.isTrue(errorSpy.calledWith(error));
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterLoaded', () => {
|
||||
it('sends a `loaded` message', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterLoaded()
|
||||
.then(() => {
|
||||
assert.isTrue(broker.send.calledWith('loaded'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignIn', () => {
|
||||
it('notifies the channel of login, halts by default', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterSignIn(account)
|
||||
.then((result) => {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignInConfirmationPoll', () => {
|
||||
it('navigates to `signin_confirmed` by default', () => {
|
||||
return broker.afterSignInConfirmationPoll(account)
|
||||
.then(() => {
|
||||
assert.equal(broker.getBehavior('afterSignInConfirmationPoll').type, 'navigate');
|
||||
assert.equal(broker.getBehavior('afterSignInConfirmationPoll').endpoint, 'signin_confirmed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('beforeSignUpConfirmationPoll', () => {
|
||||
it('notifies the channel of login', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.beforeSignUpConfirmationPoll(account)
|
||||
.then(() => {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterSignUpConfirmationPoll', () => {
|
||||
it('navigates to `signup_confirmed` by default', () => {
|
||||
return broker.afterSignUpConfirmationPoll(account)
|
||||
.then(() => {
|
||||
assert.equal(broker.getBehavior('afterSignUpConfirmationPoll').type, 'navigate');
|
||||
assert.equal(broker.getBehavior('afterSignUpConfirmationPoll').endpoint, 'signup_confirmed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterResetPasswordConfirmationPoll', () => {
|
||||
it('notifies the channel of login, halts by default', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
// customizeSync is required to send the `login` message, but
|
||||
// it won't be set because the user hasn't visited the signup/in
|
||||
// page.
|
||||
account.unset('customizeSync');
|
||||
|
||||
return broker.afterResetPasswordConfirmationPoll(account)
|
||||
.then((result) => {
|
||||
assert.isTrue(broker.send.calledWith('login'));
|
||||
assert.isTrue(broker.send.calledOnce);
|
||||
const loginData = broker.send.args[0][1];
|
||||
assert.isFalse(loginData.customizeSync);
|
||||
|
||||
assert.isTrue(result.halt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterChangePassword', () => {
|
||||
it('notifies the channel of change_password with the new login info', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
return broker.afterChangePassword(account)
|
||||
.then(() => {
|
||||
assert.isTrue(broker.send.calledWith('change_password'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('afterDeleteAccount', () => {
|
||||
it('notifies the channel of delete_account', () => {
|
||||
sinon.spy(broker, 'send');
|
||||
|
||||
account.set('uid', 'uid');
|
||||
|
||||
return broker.afterDeleteAccount(account)
|
||||
.then(() => {
|
||||
assert.isTrue(broker.send.calledWith('delete_account'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -84,7 +84,6 @@ require('./spec/lib/xss');
|
|||
require('./spec/models/account');
|
||||
require('./spec/models/attached-clients');
|
||||
require('./spec/models/auth_brokers/base');
|
||||
require('./spec/models/auth_brokers/fx-desktop-v1');
|
||||
require('./spec/models/auth_brokers/fx-desktop-v2');
|
||||
require('./spec/models/auth_brokers/fx-desktop-v3');
|
||||
require('./spec/models/auth_brokers/fx-fennec-v1');
|
||||
|
|
Загрузка…
Ссылка в новой задаче