Ind.ie is now Small Technology Foundation.
Commit 8eea9b2d authored by bmcminn's avatar bmcminn

added network throttling and user agent device parameters

parent fa53222d
{
"name": "electron-har",
"description": "A command-line tool for generating HTTP Archive (HAR) (based on Electron)",
"version": "0.1.8",
"version": "0.2.0",
"author": "Stanley Shyiko <stanley.shyiko@gmail.com>",
"license": "MIT",
"repository": {
......@@ -12,7 +12,7 @@
"main": "./src/index.js",
"dependencies": {
"cross-exec-file": "^1.0.0",
"electron-prebuilt": "^0.35.4",
"electron-prebuilt": "^0.35.6",
"json-stable-stringify": "^1.0.0",
"object-assign": "^4.0.1",
"tmp": "^0.0.28",
......
var devices = require('./profiles/devices');
var networks = require('./profiles/networks');
var yargs = require('yargs')
.usage('Usage: electron-har [options...] <url>')
// NOTE: when adding an option - keep it compatible with `curl` (if possible)
.describe('u', 'Username and password (divided by colon)').alias('u', 'user').nargs('u', 1)
.describe('o', 'Write to file instead of stdout').alias('o', 'output').nargs('o', 1)
.describe('m', 'Maximum time allowed for HAR generation (in seconds)').alias('m', 'max-time').nargs('m', 1)
.describe('debug', 'Show GUI (useful for debugging)').boolean('debug')
// - https://curl.haxx.se/docs/manual.html
.describe('u', 'Username and password (divided by colon)')
.alias('u', 'user')
.nargs('u', 1)
.describe('o', 'Write to file instead of stdout')
.alias('o', 'output')
.nargs('o', 1)
.describe('m', 'Maximum time allowed for HAR generation (in seconds)')
.alias('m', 'max-time')
.nargs('m', 1)
.describe('A', 'User Agent profile to use:\n - ' + Object.keys(devices).join('\n - '))
.alias('A', 'user-agent')
.nargs('A', 1)
.describe('Y', 'Network throttling profile to use:\n - ' + Object.keys(networks).join('\n - '))
.alias('Y', 'limit-rate')
.nargs('Y', 1)
.describe('debug', 'Show GUI (useful for debugging)')
.boolean('debug')
.help('h').alias('h', 'help')
.version(function () { return require('../package').version; })
.strict();
var argv = process.env.ELECTRON_HAR_AS_NPM_MODULE ?
yargs.argv : yargs.parse(process.argv.slice(1));
var url = argv._[0];
if (argv.u) {
var usplit = argv.u.split(':');
var username = usplit[0];
var password = usplit[1] || '';
}
var outputFile = argv.output;
var timeout = parseInt(argv.m, 10);
var debug = !!argv.debug;
var userAgent = argv.A;
var limitRate = argv.Y;
var argvValidationError;
if (!url) {
argvValidationError = 'URL must be specified';
} else if (!/^(http|https):\/\//.test(url)) {
argvValidationError = 'URL must contain the protocol prefix, e.g. http://';
}
if (argvValidationError) {
yargs.showHelp();
console.error(argvValidationError);
......@@ -76,11 +109,25 @@ electron.ipcMain
});
app.on('ready', function () {
BrowserWindow.removeDevToolsExtension('devtools-extension');
BrowserWindow.addDevToolsExtension(__dirname + '/devtools-extension');
var bw = new BrowserWindow({show: debug});
// BrowserWindow config object to store configurable properties
var winConfig = {
show: debug
};
// if a user agent profile has been selected, apply it
if (userAgent && devices[userAgent]) {
winConfig.device = devices[userAgent];
// override browser window dimensions with device specs
winConfig.width = winConfig.device.width;
winConfig.height = winConfig.device.height;
}
// initialize the browser window instance
var bw = new BrowserWindow(winConfig);
if (username) {
bw.webContents.on('login', function (event, request, authInfo, cb) {
......@@ -89,6 +136,22 @@ app.on('ready', function () {
});
}
// assign the user agent string
if (winConfig.device) {
// TODO: figure out how to apply custom user-agent header to session
// bw.webContents.session.defaultSession.onBeforeSendHeaders(function(details, callback) {
// details.requestHeaders['User-Agent'] = winConfig.device.userAgentString;
// callback({cancel: false, requestHeaders: details.requestHeaders});
// });
}
// set the network throttling profile
if (limitRate) {
bw.webContents.session.enableNetworkEmulation(networks[limitRate]);
// console.log(networks[limitRate]);
}
function notifyDevToolsExtensionOfLoad(e) {
if (e.sender.getURL() != 'chrome://ensure-electron-resolution/') {
bw.webContents.executeJavaScript('new Image().src = "https://did-finish-load/"');
......@@ -118,5 +181,4 @@ app.on('ready', function () {
// any url will do, but make sure to call loadURL before 'devtools-opened'.
// otherwise require('electron') within child BrowserWindow will (most likely) fail
bw.loadURL('chrome://ensure-electron-resolution/');
});
var tmp = require('tmp');
var assign = require('object-assign');
var fs = require('fs');
var execFile = require('child_process').execFile;
var crossExecFile = require('cross-exec-file');
/**
......@@ -10,49 +9,74 @@ var crossExecFile = require('cross-exec-file');
* @param {object|string} o.user either object with 'name' and 'password' properties or a string (e.g. 'username:password')
* @param {string} o.user.name username
* @param {string} o.user.password password
* @param {function(err, json)} cb callback (NOTE: if err != null err.code will be the exit code (e.g. 3 - wrong usage,
* @param {function(err, json)} callback callback (NOTE: if err != null err.code will be the exit code (e.g. 3 - wrong usage,
* 4 - timeout, below zero - http://src.chromium.org/svn/trunk/src/net/base/net_error_list.h))
*/
module.exports = function electronHAR(url, o, cb) {
typeof o === 'function' && (cb = o, o = {});
// using temporary file to prevent messages like "Xlib: extension ...", "libGL error ..."
// from cluttering stdout in a headless env (as in Xvfb).
module.exports = function electronHAR(url, options, callback) {
typeof options === 'function' && (callback = options, options = {});
// using temporary file to prevent messages like "Xlib: extension ...",
// "libGL error ..." from cluttering stdout in a headless env (as in Xvfb).
tmp.file(function (err, path, fd, cleanup) {
if (err) {
return cb(err);
return callback(err);
}
cb = (function (cb) { return function () {
process.nextTick(Function.prototype.bind.apply(cb,
[null].concat(Array.prototype.slice.call(arguments))));
cleanup();
}; })(cb);
var oo = assign({}, o, {
callback = (function (callback) {
return function () {
process.nextTick(Function.prototype.bind.apply(callback,
[null].concat(Array.prototype.slice.call(arguments))
));
cleanup();
};
})(callback);
console.log(options);
// map options into config object
var config = assign({}, options, {
output: path,
user: o.user === Object(o.user) ?
o.user.name + ':' + o.user.password : o.user
user: options.user === Object(options.user) ?
options.user.name + ':' + options.user.password :
options.user,
'user-agent': options['user-agent'] ? options['user-agent'] : null,
'limit-rate': options['limit-rate'] ? options['limit-rate'] : null
});
// initialize electron-har process
crossExecFile(
__dirname + '/../bin/electron-har',
[url].concat(Object.keys(oo).reduce(function (r, k) {
var v = oo[k];
v != null && r.push(k.length === 1 ? '-' + k : '--' + k, v);
return r;
}, [])),
[url].concat(
Object
.keys(config)
.reduce(function (n, flag) {
var argvs = config[flag];
argvs !== null && argvs.push(flag.length === 1 ? '-' + flag : '--' + flag, argvs);
return argvs;
}, [])
),
function (err, stdout, stderr) {
if (err) {
if (stderr) {
err.message = stderr.trim();
}
return cb(err);
return callback(err);
}
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
return cb(err);
return callback(err);
}
try {
cb(null, JSON.parse(data));
callback(null, JSON.parse(data));
} catch (e) {
return cb(e);
return callback(e);
}
});
});
......
module.exports = {
GALAXY_S5: {
diviceName: 'Galaxy S5',
width: 360,
height: 640,
pixelRatio: 2,
userAgentString: 'Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Mobile Safari/537.36',
useragentType: 'mobile'
},
NEXUS_5X: {
diviceName: 'Nexus 5X',
width: 411,
height: 731,
pixelRatio: 2.625,
userAgentString: 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Mobile Safari/537.36',
useragentType: 'mobile'
},
NEXUS_6P: {
diviceName: 'Nexus 6P',
width: 435,
height: 773,
pixelRatio: 3.3,
userAgentString: 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.23 Mobile Safari/537.36',
useragentType: 'mobile'
},
IPHONE_5: {
diviceName: 'iPhone 5',
width: 320,
height: 568,
pixelRatio: 2,
userAgentString: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
useragentType: 'mobile'
},
IPHONE_6: {
diviceName: 'iPhone 6',
width: 375,
height: 667,
pixelRatio: 2,
userAgentString: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
useragentType: 'mobile'
},
IPHONE_6PLUS: {
diviceName: 'iPhone 6+',
width: 414,
height: 736,
pixelRatio: 3,
userAgentString: 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
useragentType: 'mobile'
},
IPAD: {
diviceName: 'iPad',
width: 768,
height: 1024,
pixelRatio: 2,
userAgentString: 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
useragentType: 'mobile'
},
DESKTOP_1080P: {
diviceName: 'Desktop 1080p',
width: 1920,
height: 1080,
pixelRatio: 1,
userAgentString: '',
useragentType: 'desktop'
},
LAPTOP_1080P: {
diviceName: 'Laptop Touch screen',
width: 1920,
height: 1080,
pixelRatio: 1,
userAgentString: '',
useragentType: 'desktop with touch'
}
// IPAD_PRO: {
// diviceName: 2,
// width: 30000,
// height: 15000,
// pixelRatio: '',
// userAgentString: 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
// useragentType: 'mobile'
// }
};
var Mb = 100000; // 1 million Bytes in a Megabyte
var Kb = 1000; // 1 thousand Bytes in a Kilobyte
/**
* List of enumerated latency profiles
* @sauce: https://github.com/atom/electron/blob/master/docs/api/session.md#sesenablenetworkemulationoptions
* @type {Object}
*/
module.exports = {
WIFI: {
latency: 2,
downloadThroughput: 30*Mb,
uploadThroughput: 15*Mb
},
DSL: {
latency: 5,
downloadThroughput: 2*Mb,
uploadThroughput: 1*Mb
},
REGULAR_4G: {
latency: 20,
downloadThroughput: 4*Mb,
uploadThroughput: 3*Mb
},
GOOD_3G: {
latency: 40,
downloadThroughput: 1*Mb,
uploadThroughput: 750*Kb
},
REGULAR_3G: {
latency: 100,
downloadThroughput: 750*Kb,
uploadThroughput: 250*Kb
},
GOOD_2G: {
latency: 150,
downloadThroughput: 450*Kb,
uploadThroughput: 150*Kb
},
REGULAR_2G: {
latency: 300,
downloadThroughput: 250*Kb,
uploadThroughput: 50*Kb
},
GPRS: {
latency: 500,
downloadThroughput: 50*Kb,
uploadThroughput: 20*Kb,
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment