Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,18 @@
{
"extends": "../../../../../.jshintrc",
"node": true,
"predef": [
"suite",
"test",
"setup",
"teardown",
"suiteSetup",
"suiteTeardown",
"marionette",
"marionetteScriptFinished",
"require",
"define",
"requireApp",
"sinon"
]
}

View file

@ -0,0 +1,55 @@
'use strict';
var FxA = require('./lib/fxa');
marionette('Firefox Accounts Launch Tests', function() {
var app,
selectors,
client = marionette.client({
profile: {
prefs: {
'focusmanager.testmode': true
},
settings: {
'ftu.manifestURL': FxA.FTU_ORIGIN + '/manifest.webapp'
},
apps: {
'test-fxa-client.gaiamobile.org': __dirname + '/test-fxa-client'
}
}
});
setup(function() {
app = new FxA(client);
selectors = FxA.Selectors;
});
//If we can enter email on first screen, that should prove successful launch
suite('Should launch FxA flow from FxA-consuming apps:', function () {
test('Settings app', function () {
app.launch(FxA.SETTINGS_ORIGIN);
app.runSettingsMenu();
app.enterEmailNew();
});
test('UITest app', function () {
app.launch(FxA.UITEST_ORIGIN);
app.runUITestMenu();
app.enterEmailNew();
});
test('FTU menu', function () {
app.launch(FxA.FTU_ORIGIN);
app.runFTUMenu();
app.enterEmailNew();
});
// This is an old version which uses the IAC API.
// TODO: INTERMITTENT failures?
test.skip('test-fxa-client app', function () {
app.launch(FxA.TEST_FXA_CLIENT_ORIGIN);
app.runFxAClientTestMenu();
app.enterEmailNew();
});
});
});

View file

@ -0,0 +1,62 @@
'use strict';
var FxA = require('./lib/fxa'),
FxAUser = require('./lib/fxa_user'),
Server = require('./lib/server'),
config = require('./lib/config.json');
marionette('Firefox Accounts Screen Flow Test (UITest app)', function() {
var app,
selectors,
server,
fxaUser,
client = marionette.client({
profile: {
prefs: {
'focusmanager.testmode': true,
'identity.fxaccounts.auth.uri': 'http://' +
config.SERVER_HOST + ':' +
config.SERVER_PORT + '/' +
config.SERVER_PATH
}
}
});
setup(function(done) {
fxaUser = new FxAUser();
app = new FxA(client);
Server.create(FxA.SERVER_ARGS, function (err, _server) {
if (err) {
console.error(err);
throw err;
}
server = _server;
done();
});
selectors = FxA.Selectors;
app.launch(FxA.UITEST_ORIGIN);
app.runUITestMenu();
});
teardown(function() {
server.stop();
});
test('should walk screen flow for new user', function () {
app.enterEmailNew();
app.clickNext();
app.enterCOPPANew();
app.clickNext();
app.enterPasswordNew();
app.clickNext();
app.clickDone();
});
test('should walk screen flow for existing user', function () {
app.enterEmailExisting();
app.clickNext();
app.enterPasswordExisting();
app.clickNext();
app.clickDone();
});
});

View file

@ -0,0 +1,17 @@
{
"MARIONETTE_TIMEOUT": 20000,
"USER_NEW": 0,
"USER_EXISTING": 1,
"USER_EXISTING_EMAIL": "kilroy_exists",
"USER_EXISTING_PW": "123456789",
"USERNAME_LEN": "24",
"USER_NEW_PW": "123456789",
"PASSWORD_HASH": "059b7beebaf4824e5395fb9eba6e8424b6ee27b43675130608502790e4af2178",
"MAIL_HOST": "restmail.net",
"SERVER_HOST": "localhost",
"SERVER_PORT": 8081,
"SERVER_PATH": "v1",
"AUTH_SERVER_URL": "https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format",
"DEBUG": false
}

View file

@ -0,0 +1,235 @@
'use strict';
var FxAUser = require('./fxa_user'),
config = require('./config.json');
/**
* Abstraction around FxA app.
* 'Entrance point' app URL is passed in since FxA acts more like a
* component than a standalone app
* (launched by Settings, Marketplace, FTU, FMD, dev_apps, etc.)
*
* @param client
* @constructor
*/
function FxA(client) {
this.config = config;
this.fxaUser = new FxAUser();
this.client = client.scope({ searchTimeout: config.MARIONETTE_TIMEOUT});
}
FxA.SETTINGS_ORIGIN = 'app://settings.gaiamobile.org';
FxA.UITEST_ORIGIN = 'app://uitest.gaiamobile.org';
FxA.TEST_FXA_CLIENT_ORIGIN = 'app://test-fxa-client.gaiamobile.org';
FxA.FTU_ORIGIN = 'app://ftu.gaiamobile.org';
FxA.SERVER_PATH = 'http://' +
config.SERVER_HOST + ':' +
config.SERVER_PORT + '/' +
config.SERVER_PATH;
FxA.SERVER_ARGS = [
config.SERVER_HOST,
config.SERVER_PORT,
config.SERVER_PATH
];
FxA.COPPA_YEAR = new Date().getFullYear() - 13;
FxA.Selectors = {
body: 'body',
bodyReady: 'body .view-body',
// dev_apps/uitest
apiFxaFrame: '#test-iframe',
fxaFrame: '#fxa-iframe',
tabAPI: '#API',
fxaButton: '#mozId-fxa',
requestButton: '#request',
// dev_apps/test-fxa-client
tabOpenFlow: '#openFlow',
// apps/settings
menuItemFxa: '#menuItem-fxa',
menuItemFmd: '#menuItem-findmydevice',
fxaLogin: '#fxa-login',
fmdLogin: '#findmydevice-login',
fxaCancelAccountConfirmation: '#fxa-cancel-confirmation',
// apps/ftu
forward: '#forward',
createAccountOrLogin: '#fxa-create-account',
// apps/findmydevice
emailInput: '#fxa-email-input',
fxaForgotPassword: 'fxa-forgot-password',
pwInput: '#fxa-pw-input',
//COPPA (age-restriction menu no longer appears,
// but password input tag is still different)
pwInputPostCOPPA: '#fxa-pw-set-input',
pwSetInput: '#fxa-pw-set-input',
pwRefresh: '#fxa-pw-input-refresh',
COPPAElementId: '#fxa-coppa',
COPPASelectId: '#fxa-age-select',
COPPAOptionVal: FxA.COPPA_YEAR + ' or earlier',
COPPAOptionValToFail: FxA.COPPA_YEAR.toString(),
moduleNext: '#fxa-module-next',
moduleDone: '#fxa-module-done',
errorOK: '#fxa-error-ok'
};
FxA.prototype = {
/**
* Launches FxA app and focuses on frame.
*/
launch: function (origin) {
// do unless FTU
if (origin.search('ftu') === -1) {
this.client.apps.launch(origin);
}
this.client.apps.switchToApp(origin);
},
enterInput: function (inputId, inputString) {
this.client.helper
.waitForElement(inputId)
.sendKeys(inputString);
},
enterCOPPANew: function () {
var sel = FxA.Selectors.COPPASelectId;
var inputString = FxA.Selectors.COPPAOptionVal;
this.client.helper.tapSelectOption(sel, inputString);
},
enterEmailNew: function () {
var sel = FxA.Selectors.emailInput;
var inputString = this.fxaUser.email(config.USER_NEW);
this.client.helper.fillInputField(sel, inputString);
},
enterPasswordNew: function () {
var sel = FxA.Selectors.pwInputPostCOPPA;
var inputString = this.fxaUser.password(config.USER_NEW);
this.client.helper.fillInputField(sel, inputString);
},
enterEmailExisting: function () {
var sel = FxA.Selectors.emailInput;
var inputString = this.fxaUser.email(config.USER_EXISTING);
this.client.helper.fillInputField(sel, inputString);
},
enterPasswordExisting: function () {
var sel = FxA.Selectors.pwInput;
var inputString = this.fxaUser.password(config.USER_EXISTING);
this.client.helper.fillInputField(sel, inputString);
},
onClick: function(searchSelector) {
var element = this.client.findElement(searchSelector);
this.client.helper
.waitForElement(element)
.tap();
},
clickNext: function() {
var element = this.client.findElement(FxA.Selectors.moduleNext);
var client = this.client;
client.waitFor(function () {
return client.findElement(FxA.Selectors.moduleNext)
.scriptWith(function (el) {
return el.getAttribute('disabled') !== 'disabled';
});
});
client.helper
.waitForElement(element)
.tap();
},
clickDone: function() {
var element = this.client.findElement(FxA.Selectors.moduleDone);
this.client.helper
.waitForElement(element)
.tap();
},
clickTabOpenFlow: function() {
var element = this.client.findElement(FxA.Selectors.tabOpenFlow);
this.client.helper
.waitForElement(element)
.tap();
},
clickFxaLogin: function() {
var element = this.client.findElement(FxA.Selectors.fxaLogin);
this.client.helper
.waitForElement(element)
.tap();
},
clickMenuItemFxa: function() {
var element = this.client.findElement(FxA.Selectors.menuItemFxa);
this.client.helper
.waitForElement(element)
.tap();
},
clickRequestButton: function() {
var element = this.client.findElement(FxA.Selectors.requestButton);
this.client.helper
.waitForElement(element)
.tap();
},
clickTabAPI: function() {
var element = this.client.findElement(FxA.Selectors.tabAPI);
this.client.helper
.waitForElement(element)
.tap();
},
clickFxaButton: function() {
var element = this.client.findElement(FxA.Selectors.fxaButton);
this.client.helper
.waitForElement(element)
.tap();
},
switchFrame: function(frameId) {
this.client.switchToFrame();
var frame = this.client.findElement(frameId);
this.client.switchToFrame(frame);
},
switchFrameDirect: function(frameId) {
var frame = this.client.findElement(frameId);
this.client.switchToFrame(frame);
},
clickThruPanel: function(panelId, buttonId) {
if (panelId === '#wifi') {
// The wifi panel will bring up a screen to show it is scanning for
// networks. Not waiting for this to clear will blow test timing
// and cause things to fail.
this.client.helper.waitForElementToDisappear('#loading-overlay');
}
// waitForElement is used to make sure animations and page changes have
// finished, and that the panel is displayed.
this.client.helper.waitForElement(panelId);
if (buttonId) {
var button = this.client.helper.waitForElement(buttonId);
button.click();
}
},
runSettingsMenu: function() {
this.clickMenuItemFxa();
this.clickFxaLogin();
this.switchFrame(FxA.Selectors.fxaFrame);
},
runUITestMenu: function() {
this.clickTabAPI();
this.clickFxaButton();
this.switchFrameDirect(FxA.Selectors.apiFxaFrame);
this.clickRequestButton();
this.switchFrame(FxA.Selectors.fxaFrame);
},
runFTUMenu: function() {
this.clickThruPanel('#languages', '#forward');
this.clickThruPanel('#wifi', '#forward');
this.clickThruPanel('#date_and_time', '#forward');
this.clickThruPanel('#geolocation', '#forward');
this.clickThruPanel('#import_contacts', '#forward');
this.clickThruPanel('#firefox_accounts',
FxA.Selectors.createAccountOrLogin);
this.switchFrame(FxA.Selectors.fxaFrame);
},
runFxAClientTestMenu: function() {
this.clickTabOpenFlow();
this.switchFrame(FxA.Selectors.fxaFrame);
}
};
module.exports = FxA;

View file

@ -0,0 +1,54 @@
'use strict';
var config = require('./config.json');
/**
* FxA user handler for email, password & string generations
* @constructor
*/
function FxAUser() {
}
FxAUser.prototype = {
_randString: function(len, charSet){
charSet = charSet || 'abcdefghijklmnopqrstuvwxyz0123456789';
var rand = '';
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
rand += charSet.substring(randomPoz, randomPoz + 1);
}
return rand;
},
get _uniqueUserName() {
return this._randString(config.USERNAME_LEN);
},
/**
* @TODO bug 1052267
* add ability to generate variety of "invalid" email strings
*/
email: function(userType) {
switch(userType) {
case config.USER_NEW:
return this._uniqueUserName + '@' + config.MAIL_HOST;
default:
return config.USER_EXISTING_EMAIL + '@' + config.MAIL_HOST;
}
},
/**
* @TODO bug 1052267
* add ability to generate variety of "invalid" password strings
*/
password: function(userType) {
switch(userType) {
case config.USER_NEW:
return config.USER_NEW_PW;
default:
return config.USER_EXISTING_PW;
}
}
};
module.exports = FxAUser;

View file

@ -0,0 +1,39 @@
'use strict';
function Server(child) {
this.child = child;
}
Server.prototype = {
/**
* Sends signal to stop child process and stop server.
*
* TODO: Bug 1056186:
* [FxA] Create unit tests for Firefox Accounts mock server process
*/
stop: function() {
this.child.kill('SIGTERM');
}
};
/**
* Spawn the child process where the FxA mock server lives.
*
* @param args - [ host, port, path ]
* @param callback
*/
function create(args, callback) {
var child = require('child_process')
.fork(__dirname + '/server_mock_fxa.js', args)
.on('message', function(data) {
if (Array.isArray(data) && data[0] === 'start') {
callback(null, new Server(child));
}
})
.on('error', function (err) {
callback(err);
});
}
Server.create = create;
module.exports = Server;

View file

@ -0,0 +1,434 @@
'use strict';
var restify = require('restify'),
ecstatic = require('ecstatic'),
config = require('./config.json');
var server = restify.createServer({
name: 'Mock Firefox Accounts Server'
});
server.hostname = process.argv[2];
server.port = process.argv[3];
server.version = process.argv[4];
process.on('uncaughtException', gracefulShutdown);
server.on('uncaughtException', gracefulShutdown);
function shutdown(cb) {
try {
server.close(cb);
} catch (e) {
console.error(e.stack);
process.exit(1);
}
}
function gracefulShutdown(err) {
if (err) {
console.error(err.stack);
}
shutdown();
}
/**
* authPW = 123456789
* client accepts any hex Token the server kindly grants it
* Note: other user types could be added: i.e. verified user
*/
var account = {
'new': {
'authPW': config.PASSWORD_HASH,
'uid': randomString(32),
'sessionToken': randomString(64),
'keyFetchToken': randomString(64),
'passwordChangeToken': randomString(64),
'verified': false,
'authAt': Date.now()
},
'exists': {
'email': config.USER_EXISTING_EMAIL + '@' + config.MAIL_HOST,
'authPW': config.PASSWORD_HASH,
'uid': randomString(32),
'sessionToken': randomString(64),
'keyFetchToken': randomString(64),
'passwordChangeToken': randomString(64),
'verified': false,
'authAt': Date.now()
}
};
/**
* Mock Firefox Accounts Server
*
* Purpose:
* Mimic behavior of FxA server v1
* (SHA: 29dc9abbacb3fe1b6de763f3c7bd85e63215297c)
* with mostly 'canned' responses
* Some APIs (i.e. for authentication) provide limited logical checking
* for testing B2G
*
* Reference
* https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
*/
var Server = {
//set to true for verbose logging
debug: config.DEBUG || false,
// allows for terminating process directly when calling server standalone
// when calling as child process, use child.kill() from parent
stop: function() {
gracefulShutdown();
console.log('SERVER STOPPED');
},
start: function() {
server.use(restify.acceptParser(server.acceptable));
server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(ecstatic({ root: __dirname + '/' }));
server.get('/', ecstatic({ root: __dirname }));
server.listen(server.port, function() {
var debug = Server.debug || false;
if (debug) {
console.log(' %s running on port: %s', server.name, server.port);
}
});
}
};
(function(err, port) {
Server.start();
process.send(['start']);
})();
// handle process messages
process.on('message', function(data) {
switch (data) {
case 'stop':
console.log('STOPPING MOCK SERVER');
Server.stop();
break;
}
});
server.post('/v1/account/create', postAccountCreate);
server.get('/v1/account/status', getAccountStatus);
server.get('/v1/account/devices', getAccountDevices);
server.get('/v1/account/keys', getAccountKeys);
server.post('/v1/account/reset', respondEmpty);
server.post('/v1/account/destroy', respondEmpty);
server.post('/v1/account/login', postAccountLogin);
server.get('/v1/session/status', respondEmpty);
server.post('/v1/session/destroy', respondEmpty);
server.get('/v1/recovery_email/status', getRecoveryEmailStatus);
server.post('/v1/recovery_email/resend_code', respondEmpty);
server.post('/v1/recovery_email/verify_code', respondEmpty);
server.post('/v1/certificate/sign', postCertificateSign);
server.post('/v1/password/change/start', postPasswordChangeStart);
server.post('/v1/password/change/finish', respondEmpty);
server.post('/v1/password/forgot/send_code', postPasswordForgotSendCode);
server.post('/v1/password/forgot/resend_code', postPasswordForgotResendCode);
server.post('/v1/password/forgot/verify_code', postPasswordForgotVerifyCode);
server.get('/v1/forgot/status', getPasswordForgotStatus);
server.post('/v1/get_random_bytes', postGetRandomBytes);
//catch any undefined requests
server.get(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, anyUndefined);
server.post(/^\/([a-zA-Z0-9_\.~-]+)\/(.*)/, anyUndefined);
/************************************
* API: Account
************************************/
function postAccountCreate(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse = getRespAccountCreate(accountSelected);
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountStatus(req,res,next) {
var jsonResponse = { 'exists': false };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountDevices(req,res,next) {
var jsonResponse = {
'devices': [
{
'id': '4c352927-cd4f-4a4a-a03d-7d1893d950b8',
'type': 'computer',
'name': 'Foxys Macbook'
}
]
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getAccountKeys(req,res,next) {
var jsonResponse = {
'bundle': randomString(256)
};
printLog('GET /account/keys', jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Authentication
************************************/
function postAccountLogin(req,res,next) {
var jsonResponse;
var accountSelected = getAccountType(req.body.email);
if (req.body.email === account.exists.email) {
if (req.body.authPW === account.exists.authPW) {
jsonResponse = getRespAccountLogin(accountSelected);
res.send(200, jsonResponse);
} else {
//status code 400, errno 103: incorrect password
jsonResponse = getError(103,accountSelected);
res.send(jsonResponse);
}
} else {
//status code 400, errno 102: attempt to access a non-existent account
jsonResponse = getError(102, accountSelected);
res.send(jsonResponse);
}
printLog(req.method + ' ' + req.url, jsonResponse);
}
/************************************
* API: Recovery Email
************************************/
function getRecoveryEmailStatus(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse = getRespRecoveryEmail(accountSelected);
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Certificate Signing
************************************/
function postCertificateSign(req,res,next) {
var jsonResponse = {
'cert': randomString(520,
'ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz0123456789')
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Password
************************************/
/**
* should verify request of format:
* {
* "email": "me@example.com",
* "oldAuthPW":
* "d486e79c9f3214b0010fe31bfb50fa6c12e1d093f7770c81c6b1c19c7ee375a6"
* }
*/
function postPasswordChangeStart(req,res,next) {
var accountSelected = getAccountType(req.body.email);
var jsonResponse;
if (req.body.email === account.exists.email) {
jsonResponse = getRespPasswordChangeStart(accountSelected);
res.send(200, jsonResponse);
} else {
//status code 400, errno 102: attempt to access non-existent account
jsonResponse = getError(102, accountSelected);
res.send(jsonResponse);
}
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotSendCode(req,res,next) {
var jsonResponse = getRespPasswordForgotSendCode();
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotResendCode(req,res,next) {
var jsonResponse = getRespPasswordForgotSendCode();
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function postPasswordForgotVerifyCode(req,res,next) {
var jsonResponse = {
'accountResetToken': randomString(128)
};
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
function getPasswordForgotStatus(req,res,next) {
var jsonResponse = { 'tries': 3, 'ttl': 420 };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Miscellaneous
************************************/
function postGetRandomBytes(req,res,next) {
var randomData = getRandomBytes();
var jsonResponse = { 'data': randomData };
printLog(req.method + ' ' + req.url, jsonResponse);
res.send(200,jsonResponse);
}
/************************************
* API: Undefined
* any other requests land here
************************************/
function anyUndefined(req,res,next) {
printLog(req.method + ' ' + req.url + ' (UNDEFINED)',
'value of req is: ' + req);
}
function randomString(len, charSet) {
charSet = charSet || 'abcdefghijklmnopqrstuvwxyz0123456789';
var rand = '';
for (var i = 0; i < len; i++) {
var randomPoz = Math.floor(Math.random() * charSet.length);
rand += charSet.substring(randomPoz,randomPoz+1);
}
return rand;
}
/**
* mock API will random 64 char length hex strings
* @returns {string}
*/
function getRandomBytes() {
return randomString(64);
}
function getAccountType(returnType) {
switch(returnType) {
case account.exists.email:
return account.exists;
default:
return account.new;
}
}
function respondEmpty(req,res,next) {
printLog(req.method + ' ' + req.url, {});
res.send(200, {});
}
function getRespAccountLogin(accountSelected) {
return {
'uid': accountSelected.uid,
'sessionToken': accountSelected.sessionToken,
'verified': accountSelected.verified,
'authAt': Date.now()
};
}
function getRespRecoveryEmail(accountSelected) {
return {
'email': accountSelected.email,
'verified': accountSelected.verified
};
}
function getRespAccountCreate(accountSelected) {
return {
'uid': accountSelected.uid,
'sessionToken': accountSelected.sessionToken,
'keyFetchToken': accountSelected.keyFetchToken,
'authAt': Date.now()
};
}
function getRespPasswordChangeStart(accountSelected) {
return {
'keyFetchToken': accountSelected.keyFetchToken,
'passwordChangeToken': accountSelected.passwordChangeToken,
'verified': accountSelected.verified
};
}
function getRespPasswordForgotSendCode() {
return {
'passwordForgotToken': randomString(128),
'ttl': 900,
'codeLength': 8,
'tries': 3
};
}
/**
* Use:
* var errNum = req.socket._httpMessage.statusCode;
* @param errNum
* @param accountSelected
* @returns {*}
*/
function getError(errNum,accountSelected) {
var info = config.AUTH_SERVER_URL;
switch(errNum) {
case 102:
return {
'code': 400,
'errno': errNum,
'error': 'Bad Request',
'message': 'Attempt to access an account that does not exist',
'info': info,
'email': accountSelected.email,
'log': []
};
case 103:
return {
'code': 400,
'errno': errNum,
'error': 'Bad Request',
'message': 'Incorrect password',
'info': info,
'email': accountSelected.email,
'log': []
};
case 999:
return {
'code': errNum,
'errno': 999,
'error': 'Not Found',
'message': 'Unspecified error',
'info': info,
'log': []
};
}
}
/**
* Log req/res to stdout
*/
function printLog(title,msg) {
if (config.DEBUG) {
console.log('\n========================');
console.log(title);
console.log('========================');
console.log(msg);
console.log('\n\n');
}
}

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FxAccounts testing tools</title>
<!-- We don't need localization for this app. -->
<script defer type="text/javascript" src="/js/fxa_iac_client.js"></script>
<script defer type="text/javascript" src="/js/app.js"></script>
<link rel="stylesheet" type="text/css" href="style/style.css">
</head>
<body>
<section role="dialog">
<menu>
<button id="getAccount">Get Fx Account</button>
<button id="openFlow">Launch FxAccounts flow</button>
<button id="logout">Logout</button>
<div class="refresh-auth">
<button id="refreshAuthentication">Refresh Authentication UI</button>
<input id="accountId" type="text" value="dummy@account" x-inputmode="verbatim">
</div>
</menu>
<section id="result">
<p id="result-text"></p>
</section>
<section id="events">
<p id="events-text"></p>
</section>
</section>
<!-- Progress overlay -->
<section id="overlay" role="dialog">
<progress></progress>
<p id="overlay-msg">Connecting to Firefox</p>
</section>
</body>
</html>

View file

@ -0,0 +1,124 @@
'use strict';
/* global FxAccountsIACHelper */
var hideTimer;
var Overlay = {
show: function fxam_overlay_show() {
if (hideTimer) {
return;
}
var overlay = document.querySelector('#overlay');
overlay.classList.add('show');
hideTimer = setTimeout(Overlay.hide, 2000);
},
hide: function fxam_overlay_hide() {
hideTimer = clearTimeout(hideTimer);
var overlay = document.querySelector('#overlay');
if (!overlay) {
return;
}
overlay.classList.remove('show');
}
};
var TestFxAClient = function TestFxAClient() {
var getFxAccountsButton, launchFxAFlowButton, logoutButton,
refreshAuthButton, resultTextEl, resultEl, timer, eventsEl,
eventsTextEl, eventsTimer, accountIdInput;
var init = function init() {
resultEl = document.getElementById('result');
resultTextEl = document.getElementById('result-text');
getFxAccountsButton = document.getElementById('getAccount');
launchFxAFlowButton = document.getElementById('openFlow');
logoutButton = document.getElementById('logout');
refreshAuthButton = document.getElementById('refreshAuthentication');
eventsEl = document.getElementById('events');
eventsTextEl = document.getElementById('events-text');
accountIdInput = document.getElementById('accountId');
getFxAccountsButton.addEventListener('click', handler);
launchFxAFlowButton.addEventListener('click', handler);
logoutButton.addEventListener('click', handler);
refreshAuthButton.addEventListener('click', handler);
FxAccountsIACHelper.addEventListener('onlogin', function() {
showEvent('onlogin');
});
FxAccountsIACHelper.addEventListener('onlogout', function() {
showEvent('onlogout');
});
FxAccountsIACHelper.addEventListener('onverifiedlogin', function() {
showEvent('onverifiedlogin');
});
};
function _setResponse(response, errorDebug) {
resultTextEl.innerHTML = '';
var text = document.createTextNode((errorDebug)? 'Error ' : 'Success :');
resultTextEl.appendChild(text);
var responseBeautyfied =
document.createTextNode(JSON.stringify(response || 'No params', null, 4));
resultTextEl.appendChild(responseBeautyfied);
clearTimeout(timer);
resultEl.classList.add('show');
timer = setTimeout(function() {
resultEl.classList.remove('show');
}, 3000);
}
function showEvent(eventName) {
eventsTextEl.innerHTML = '';
var text = document.createTextNode('Event received: ' + eventName);
eventsTextEl.appendChild(text);
clearTimeout(timer);
eventsEl.classList.add('show');
eventsTimer = setTimeout(function() {
eventsEl.classList.remove('show');
}, 5000);
}
var showResponse = function showResponse(response) {
Overlay.hide();
_setResponse(response);
};
var showError = function showResponse(response) {
Overlay.hide();
_setResponse(response, true);
};
var handler = function handler(evt) {
var method = evt.target.id;
switch (method) {
case 'openFlow':
FxAccountsIACHelper[method](showResponse, showError);
break;
case 'getAccount':
case 'logout':
Overlay.show();
FxAccountsIACHelper[method](showResponse, showError);
break;
case 'refreshAuthentication':
FxAccountsIACHelper[method](accountIdInput.value,
showResponse, showError);
break;
}
};
return {
'init': init
};
}();
TestFxAClient.init();

View file

@ -0,0 +1,13 @@
{
"name": "Test FxA Client",
"launch_path": "/index.html",
"type": "certified",
"developer": {
"name": "The Gaia Team",
"url": "https://github.com/mozilla-b2g/gaia"
},
"icons": {
"120": "/style/icons/icon.png"
},
"orientation": "default"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,97 @@
html, body {
height: 100%;
padding: 0;
margin: 0;
font-size: 10px;
}
body {
margin: 0;
width: 100%;
height: 100%;
background-color: #000;
/* XXX: The below overflow property is being ignored by B2G Phone
https://bugzilla.mozilla.org/show_bug.cgi?id=777672
*/
overflow: hidden;
}
button {
display: block;
width: 100%;
height: 6rem;
line-height: 6rem;
font-size: 1.6rem;
}
menu {
margin: 0;
padding: 0;
}
#result {
display: none;
}
#result.show {
display: block;
}
#events {
display: none;
}
#events.show {
display: block;
}
h1 {
padding: 1rem 1.5rem;
-moz-box-sizing: border-box;
font-size: 2rem;
color: white;
}
p {
padding: 1rem 1.5rem;
-moz-box-sizing: border-box;
font-size: 1.6rem;
color: white;
}
/* * * * * * * * * * * * * * * * * * * * * * * *
* Overlay:
* IMPORTANT: Avoid to use 'visibility' or 'opacity' due
* to performance issues. For hiding, use 'display:none;''
* * * * * * * * * * * * * * * * * * * * * * * */
#overlay {
display: none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
font-size: 2rem;
background-color: rgba(200,200,200,0.9);
flex-direction: column;
justify-content: center;
align-items: center;
}
#overlay.show {
display: flex;
}
input {
width: 100%;
height: 4rem;
line-height: 6rem;
font-size: 1.6rem;
}
.refresh-auth {
border-top: 0.1rem solid;
margin-top: 1rem;
}