Skip to content

Commit

Permalink
Fixed #1 (provide Node.js binding)
Browse files Browse the repository at this point in the history
  • Loading branch information
shyiko committed Jan 13, 2016
1 parent 8af0aef commit 05c0d58
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 119 deletions.
2 changes: 1 addition & 1 deletion bin/electron-har
Expand Up @@ -5,7 +5,7 @@ process.env.ELECTRON_HAR_AS_NPM_MODULE = 1;
require('child_process').
spawn(
require('electron-prebuilt'),
[__dirname + '/../src'].concat(process.argv.slice(2)),
[__dirname + '/../src/electron-har.js'].concat(process.argv.slice(2)),
{stdio: 'inherit'}
).on('close', function (code) {
process.exit(code);
Expand Down
7 changes: 5 additions & 2 deletions package.json
@@ -1,18 +1,21 @@
{
"name": "electron-har",
"description": "A command-line tool for generating HTTP Archive (HAR) (based on Electron)",
"version": "0.1.5",
"version": "0.1.6",
"author": "Stanley Shyiko <stanley.shyiko@gmail.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/shyiko/electron-har"
},
"bin": "./bin/electron-har",
"lib": "./src/index.js",
"dependencies": {
"yargs": "^3.31.0",
"json-stable-stringify": "^1.0.0",
"electron-prebuilt": "^0.35.4"
"electron-prebuilt": "^0.35.4",
"object-assign": "^4.0.1",
"tmp": "^0.0.28"
},
"devDependencies": {
"electron-packager": "^5.1.1",
Expand Down
47 changes: 47 additions & 0 deletions readme.md
Expand Up @@ -29,6 +29,53 @@ DISPLAY=:1 xvfb-run electron-har http://google.com -o google_com.har
electron-har --help
```

... or **pragmatically**

```js
var electronHAR = require('electron-har');

electronHAR('http://enterprise.com/self-destruct', {
user: {
name: 'jean_luc_picard',
password: 'picard_4_7_alpha_tango'
}
}, function (err, json) {
if (err) {
throw err;
}
console.log(json.log.entries);
});
```

In a headless environment you might want to use [kesla/headless](https://github.com/kesla/node-headless) (which will start Xvfn for you).

```js
var headless = require('headless');
var electronHAR = require('electron-har');

(function (cb) {
if (!process.env.DISPLAY) {
headless(function (err, proc, display) {
if (err) {
return cb(err);
}
process.env.DISPLAY = ':' + display;
cb(null, proc);
})
} else {
process.nextTick(cb);
}
})(function (err, xvfb) {
if (err) {
throw err;
}
electronHAR(..., function (err, json) {
...
xvfb && xvfb.kill();
})
});
```

## License

[MIT License](https://github.com/shyiko/electron-har/blob/master/mit.license)
122 changes: 122 additions & 0 deletions src/electron-har.js
@@ -0,0 +1,122 @@
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')
.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 argvValidationError;
if (!url) {
argvValidationError = 'URL must be specified';
} else if (!/^(http|https|file):\/\//.test(url)) {
argvValidationError = 'URL must contain the protocol prefix, e.g. the http:// or file://.';
}
if (argvValidationError) {
yargs.showHelp();
console.error(argvValidationError);
// http://tldp.org/LDP/abs/html/exitcodes.html
process.exit(3);
}

var electron = require('electron');
var app = require('app');
var BrowserWindow = require('browser-window');
var stringify = require('json-stable-stringify');
var fs = require('fs');

if (timeout > 0) {
setTimeout(function () {
console.error('Timed out waiting for the HAR');
debug || app.exit(4);
}, timeout * 1000);
}

app.commandLine.appendSwitch('disable-http-cache');
app.dock && app.dock.hide();
app.on('window-all-closed', function () { app.quit(); });

electron.ipcMain
.on('har-generation-succeeded', function (sender, event) {
var har = stringify(event, {space: 2});
if (outputFile) {
fs.writeFile(outputFile, har, function (err) {
if (err) {
console.error(err.message);
debug || app.exit(5);
return;
}
debug || app.quit();
});
} else {
console.log(har);
debug || app.quit();
}
})
.on('har-generation-failed', function (sender, event) {
console.error('An attempt to generate HAR resulted in error code ' + event.errorCode +
(event.errorDescription ? ' (' + event.errorDescription + ')' : '') +
'. \n(error codes defined in http://src.chromium.org/svn/trunk/src/net/base/net_error_list.h)');
debug || app.exit(event.errorCode);
});

app.on('ready', function () {

BrowserWindow.removeDevToolsExtension('devtools-extension');
BrowserWindow.addDevToolsExtension(__dirname + '/devtools-extension');

var bw = new BrowserWindow({show: debug});

if (username) {
bw.webContents.on('login', function (event, request, authInfo, cb) {
event.preventDefault(); // default behavior is to cancel auth
cb(username, password);
});
}

function notifyDevToolsExtensionOfLoad(e) {
if (e.sender.getURL() != 'chrome://ensure-electron-resolution/') {
bw.webContents.executeJavaScript('new Image().src = "https://did-finish-load/"');
}
}

// fired regardless of the outcome (success or not)
bw.webContents.on('did-finish-load', notifyDevToolsExtensionOfLoad);

bw.webContents.on('did-fail-load', function (e, errorCode, errorDescription, url) {
if (url !== 'chrome://ensure-electron-resolution/' && url !== 'https://did-finish-load/') {
bw.webContents.removeListener('did-finish-load', notifyDevToolsExtensionOfLoad);
bw.webContents.executeJavaScript('require("electron").ipcRenderer.send("har-generation-failed", ' +
JSON.stringify({errorCode: errorCode, errorDescription: errorDescription}) + ')');
}
});

electron.ipcMain.on('devtools-loaded', function (event) {
setTimeout(function () {
// bw.loadURL proved to be unreliable on Debian 8 (half of the time it has no effect)
bw.webContents.executeJavaScript('location = ' + JSON.stringify(url));
}, 0);
});

bw.openDevTools();

// 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/');

});
170 changes: 54 additions & 116 deletions src/index.js
@@ -1,121 +1,59 @@
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')
.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 argvValidationError;
if (!url) {
argvValidationError = 'URL must be specified';
} else if (!/^(http|https|file):\/\//.test(url)) {
argvValidationError = 'URL must contain the protocol prefix, e.g. the http:// or file://.';
}
if (argvValidationError) {
yargs.showHelp();
console.error(argvValidationError);
process.exit(1);
}

var electron = require('electron');
var app = require('app');
var BrowserWindow = require('browser-window');
var stringify = require('json-stable-stringify');
var tmp = require('tmp');
var assign = require('object-assign');
var fs = require('fs');

if (timeout > 0) {
setTimeout(function () {
console.error('Timed out waiting for the HAR');
debug || app.exit(2);
}, timeout * 1000);
}

app.commandLine.appendSwitch('disable-http-cache');
app.dock && app.dock.hide();
app.on('window-all-closed', function () { app.quit(); });

electron.ipcMain
.on('har-generation-succeeded', function (sender, event) {
var har = stringify(event, {space: 2});
if (outputFile) {
fs.writeFile(outputFile, har, function (err) {
var execFile = require('child_process').execFile;

/**
* @param {string} url url (e.g. http://google.com)
* @param {object} o CLI options (NOTE: only properties not present in (or different from) CLI are described below)
* @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,
* 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).
tmp.file(function (err, path, fd, cleanup) {
if (err) {
return cb(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, {
output: path,
user: o.user === Object(o.user) ?
o.user.name + ':' + o.user.password : o.user
});
execFile(
__dirname + '/../bin/electron-har',
[url].concat(Object.keys(oo).reduce(function (r, k) {
r.push(k.length === 1 ? '-' + k : '--' + k);
oo[k] && r.push(oo[k]);
return r;
}, [])),
function (err, stdout, stderr) {
if (err) {
console.error(err.message);
debug || app.exit(1);
return;
if (stderr) {
err.message = stderr.trim();
}
return cb(err);
}
debug || app.quit();
fs.readFile(path, 'utf8', function (err, data) {
if (err) {
return cb(err);
}
try {
cb(null, JSON.parse(data));
} catch (e) {
return cb(e);
}
});
});
} else {
console.log(har);
debug || app.quit();
}
})
.on('har-generation-failed', function (sender, event) {
console.error('An attempt to generate HAR resulted in error code ' + event.errorCode +
(event.errorDescription ? ' (' + event.errorDescription + ')' : '') +
'. \n(error codes defined in http://src.chromium.org/svn/trunk/src/net/base/net_error_list.h)');
debug || app.exit(event.errorCode);
});

app.on('ready', function () {

BrowserWindow.removeDevToolsExtension('devtools-extension');
BrowserWindow.addDevToolsExtension(__dirname + '/devtools-extension');

var bw = new BrowserWindow({show: debug});

if (username) {
bw.webContents.on('login', function (event, request, authInfo, cb) {
event.preventDefault(); // default behavior is to cancel auth
cb(username, password);
});
}

function notifyDevToolsExtensionOfLoad(e) {
if (e.sender.getURL() != 'chrome://ensure-electron-resolution/') {
bw.webContents.executeJavaScript('new Image().src = "https://did-finish-load/"');
}
}

// fired regardless of the outcome (success or not)
bw.webContents.on('did-finish-load', notifyDevToolsExtensionOfLoad);

bw.webContents.on('did-fail-load', function (e, errorCode, errorDescription, url) {
if (url !== 'chrome://ensure-electron-resolution/' && url !== 'https://did-finish-load/') {
bw.webContents.removeListener('did-finish-load', notifyDevToolsExtensionOfLoad);
bw.webContents.executeJavaScript('require("electron").ipcRenderer.send("har-generation-failed", ' +
JSON.stringify({errorCode: errorCode, errorDescription: errorDescription}) + ')');
}
});

electron.ipcMain.on('devtools-loaded', function (event) {
setTimeout(function () {
// bw.loadURL proved to be unreliable on Debian 8 (half of the time it has no effect)
bw.webContents.executeJavaScript('location = ' + JSON.stringify(url));
}, 0);
});

bw.openDevTools();

// 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/');

});
};
File renamed without changes.

0 comments on commit 05c0d58

Please sign in to comment.