Socket
Socket
Sign inDemoInstall

crx

Package Overview
Dependencies
Maintainers
2
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

crx - npm Package Compare versions

Comparing version 0.4.4 to 1.0.0

93

bin/crx.js
#!/usr/bin/env node
var path = require("path")
, fs = require("fs")
, child = require("child_process")
var path = require("path");
var fs = require("fs");
var rsa = require('node-rsa');
, program = require("commander")
, ChromeExtension = require("..")
var program = require("commander");
var ChromeExtension = require("..");
var pkg = require('../package.json');
, resolve = path.resolve
, join = path.join
, spawn = child.spawn
, exec = child.exec
var resolve = path.resolve;
var join = path.join;
, cwd = process.cwd()
var cwd = process.cwd();
program
.version("0.2.8")
.version(pkg.version)
.option("-f, --file [file]", "input/output <file> instead of stdin/stdout")

@@ -28,3 +27,3 @@ .option("-p, --private-key <file>", "relative path to private key [key.pem]")

.description("generate a private key in [directory]/key.pem")
.action(keygen)
.action(keygen);

@@ -34,3 +33,3 @@ program

.description("pack [directory] into a .crx extension")
.action(pack)
.action(pack);

@@ -40,23 +39,24 @@ // program

// .description("unpack a .crx extension into a directory")
// .action(unpack)
// .action(unpack);
program.parse(process.argv)
program.parse(process.argv);
function keygen(dir, cb) {
dir = resolve(cwd, dir)
dir = resolve(cwd, dir);
var key = join(dir, "key.pem")
var keyPath = join(dir, "key.pem");
fs.exists(key, function(exists) {
if (exists) return cb && typeof(cb) == "function" && cb()
fs.exists(keyPath, function(exists) {
if (exists) {
return cb && typeof(cb) == "function" && cb();
}
var pubPath = key + ".pub"
, command = "ssh-keygen -N '' -b 1024 -t rsa -f key.pem"
var key = new rsa({ b: 1024 });
exec(command, {cwd: dir}, function(err) {
if (err) throw err
fs.writeFile(keyPath, key.getPrivatePEM(), function(err){
if (err){
throw err;
}
// TODO: find a way to prevent .pub output
fs.unlink(pubPath)
cb && typeof(cb) == "function" && cb()
cb && typeof(cb) == "function" && cb();
})

@@ -67,33 +67,26 @@ })

function pack(dir) {
var input = resolve(cwd, dir)
, output =
program.file === true ? input + ".crx" :
program.file ? resolve(cwd, program.file) : false
var input = resolve(cwd, dir);
var output = program.file === true ? input + ".crx" : (program.file ? resolve(cwd, program.file) : false);
, stream = output ? fs.createWriteStream(output) : process.stdout
, key = program.privateKey
? resolve(cwd, program.privateKey)
: join(input, "key.pem")
var stream = output ? fs.createWriteStream(output) : process.stdout;
var key = program.privateKey ? resolve(cwd, program.privateKey) : join(input, "key.pem");
, crx = new ChromeExtension({
rootDirectory: input,
maxBuffer: program.maxBuffer
})
var crx = new ChromeExtension({
rootDirectory: input,
maxBuffer: program.maxBuffer
});
fs.readFile(key, function(err, data) {
if (err) keygen(dir, pack.bind(null, dir))
if (err) {
throw err;
}
crx.privateKey = data
crx.privateKey = data;
crx.load(function(err) {
if (err) throw err
crx.pack().then(function(crxBuffer) {
stream.end(crxBuffer);
this.pack(function(err, data){
if (err) throw err
stream.end(data)
this.destroy()
})
})
})
return crx.destroy();
});
});
}

@@ -1,3 +0,3 @@

Copyright (c) 2011 Jed Schmidt, http://jed.is/
Copyright (c) 2014 Jed Schmidt, Thomas Parisot
Permission is hereby granted, free of charge, to any person obtaining

@@ -10,6 +10,6 @@ a copy of this software and associated documentation files (the

the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

@@ -16,0 +16,0 @@ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF

@@ -5,4 +5,4 @@ {

"description": "Build Google Chrome extensions with node.js",
"version": "0.4.4",
"homepage": "https://github.com/jed/crx",
"version": "1.0.0",
"homepage": "https://github.com/oncletom/crx",
"bin": {

@@ -13,7 +13,8 @@ "crx": "./bin/crx.js"

"type": "git",
"url": "git://github.com/jed/crx.git"
"url": "git://github.com/oncletom/crx.git"
},
"main": "./src/crx.js",
"engines": {
"node": ">=0.7.12"
"node": ">=0.7.12",
"npm": ">=1.4.6"
},

@@ -24,7 +25,12 @@ "scripts": {

"dependencies": {
"commander": "",
"archiver": "0.8.x",
"wrench": "1.5.x"
"archiver": "^0.8.0",
"commander": "^2.5.0",
"es6-promise": "^2.0.0",
"node-rsa": "^0.1.54",
"rimraf": "^2.2.8",
"wrench": "^1.5.0"
},
"devDependencies": {}
"devDependencies": {
"tape": "^3.0.3"
}
}

@@ -1,8 +0,5 @@

crx
===
# crx [![Build Status](https://secure.travis-ci.org/oncletom/crx.svg)](http://travis-ci.org/oncletom/crx)
[![Build Status](https://secure.travis-ci.org/jed/crx.png)](http://travis-ci.org/jed/crx)
crx is a [node.js](http://nodejs.org/) command line app for packing Google Chrome extensions. If you'd like to integrate it into your [grunt](http://gruntjs.com/) workflow, give [grunt-crx](https://github.com/oncletom/grunt-crx) a spin.
crx is a [node.js](http://nodejs.org/) command line app for packing Google Chrome extensions. If you'd like to integrate it into your [grunt](http://gruntjs.com/) workflow, give [oncletom](https://github.com/oncletom)'s [grunt-crx](https://github.com/oncletom/grunt-crx) a spin.
## Requirements

@@ -12,3 +9,2 @@

* openssl
* ssh-keygen

@@ -21,2 +17,4 @@ ## Install

Asynchronous functions returns an [ES6 Promise](https://github.com/jakearchibald/es6-promise).
### ChromeExtension = require("crx")

@@ -27,14 +25,38 @@ ### crx = new ChromeExtension(attrs)

### crx.load(path, callback)
### crx.load(path)
Loads the Chrome Extension from the specified path.
Asynchronously loads the Chrome Extension from the specified path (or uses the `attr.rootDirectory` value).
### crx.pack(callback)
```js
crx.load().then(function(crx){
// ...
});
```
Packs the Chrome Extension, and calls back with a Buffer containing the `.crx` file.
### crx.pack()
Packs the Chrome Extension and resolves the promise with a Buffer containing the `.crx` file.
```js
crx.pack().then(function(crxBuffer){
fs.writeFile('/tmp/foobar.crx', crxBuffer);
});
```
### crx.generateUpdateXML()
Returns a Buffer containing the update.xml file used for autoupdate, as specified for `update_url` in the manifest. In this case, the instance must have a property called `codebase`.
Returns a Buffer containing the update.xml file used for `autoupdate`, as specified for `update_url` in the manifest. In this case, the instance must have a property called `codebase`.
```js
var crx = new ChromeExtension({ ..., codebase: 'https://autoupdateserver.com/myFirstExtension.crx' });
crx.pack().then(function(crxBuffer){
// ...
var xmlBuffer = crx.generateUpdateXML();
fs.writeFile('/foo/bar/update.xml', xmlBuffer);
});
```
### crx.destroy()

@@ -44,28 +66,34 @@

```js
crx.pack().then(function(crxBuffer){
// ...
return crx.destroy();
}).then(function(){
console.log('Extension saved and workspace cleaned up!');
});
```
## Module example
```javascript
var fs = require("fs")
, ChromeExtension = require("crx")
, join = require("path").join
, crx = new ChromeExtension(
codebase: "http://localhost:8000/myFirstExtension.crx",
privateKey: fs.readFileSync(join(__dirname, "key.pem")),
rootDirectory: join(__dirname, "myFirstExtension")
})
var fs = require("fs"),
var ChromeExtension = require("crx"),
var join = require("path").join,
var crx = new ChromeExtension(
codebase: "http://localhost:8000/myFirstExtension.crx",
privateKey: fs.readFileSync(join(__dirname, "key.pem"))
});
crx.load(function(err) {
if (err) throw err
crx.load(join(__dirname, "myFirstExtension"))
.then(function() {
return crx.pack(function(crxBuffer){
var updateXML = crx.generateUpdateXML()
this.pack(function(err, data){
if (err) throw err
fs.writeFile(join(__dirname, "update.xml"), updateXML)
fs.writeFile(join(__dirname, "myFirstExtension.crx"), crxBuffer)
var updateXML = this.generateUpdateXML()
fs.writeFile(join(__dirname, "update.xml"), updateXML)
fs.writeFile(join(__dirname, "myFirstExtension.crx"), data)
this.destroy()
return crx.destroy();
})
})
})
```

@@ -140,4 +168,22 @@

Copyright (c) 2012 Jed Schmidt. See LICENSE.txt for details.
Copyright (c) 2014 Jed Schmidt, Thomas Parisot
Send any questions or comments [here](http://twitter.com/jedschmidt).
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@@ -1,169 +0,287 @@

var fs = require("fs")
, path = require("path")
, join = path.join
, sep = require("path").sep
, crypto = require("crypto")
, child = require("child_process")
, wrench = require("wrench")
, archiver = require("archiver")
, spawn = child.spawn
, exec = child.exec
'use strict';
module.exports = new function() {
function ChromeExtension(attrs) {
if (this instanceof ChromeExtension) {
for (var name in attrs) this[name] = attrs[name]
var fs = require("fs");
var path = require("path");
var join = path.join;
var crypto = require("crypto");
var spawn = require("child_process").spawn;
var wrench = require("wrench");
var archiver = require("archiver");
var rm = require('rimraf');
var Promise = require('es6-promise').Promise;
this.path = join("tmp", "crx-" + (Math.random() * 1e17).toString(36))
}
else return new ChromeExtension(attrs)
function ChromeExtension(attrs) {
if ((this instanceof ChromeExtension) !== true) {
return new ChromeExtension(attrs);
}
ChromeExtension.prototype = this
/*
Defaults
*/
this.appId = null;
this.destroy = function() {
wrench.rmdirSyncRecursive(path.dirname(this.path))
this.manifest = '';
this.loaded = false;
this.rootDirectory = '';
this.publicKey = null;
this.privateKey = null;
this.codebase = null;
/*
Copying attributes
*/
for (var name in attrs) {
this[name] = attrs[name];
}
this.pack = function(cb) {
if (!this.loaded) return this.load(function(err) {
return err ? cb(err) : this.pack(cb)
})
this.path = join("tmp", "crx-" + (Math.random() * 1e17).toString(36))
}
this.generatePublicKey(function(err) {
if (err) return cb(err)
ChromeExtension.prototype = {
var manifest = JSON.stringify(this.manifest)
/**
* Destroys generated files.
*
* @returns {Promise}
*/
destroy: function () {
var path = this.path;
this.writeFile("manifest.json", manifest, function(err) {
if (err) return cb(err)
this.loadContents(function(err) {
if (err) return cb(err)
return new Promise(function(resolve, reject){
rm(path, function(err){
if (err){
return reject(err);
}
this.generateSignature()
this.generatePackage()
resolve();
});
});
},
cb.call(this, null, this.package)
/**
* Packs the content of the extension in a crx file.
*
* @returns {Promise}
* @example
*
* crx.pack().then(function(crxContent){
* // do something with the crxContent binary data
* });
*
*/
pack: function () {
if (!this.loaded) {
return this.load().then(this.pack.bind(this));
}
var selfie = this;
return selfie.writeFile("manifest.json", JSON.stringify(selfie.manifest))
.then(this.generatePublicKey.bind(this))
.then(function(publicKey){
selfie.publicKey = publicKey;
return selfie.loadContents().then(function (contents) {
var signature = selfie.generateSignature(contents);
return selfie.generatePackage(signature, publicKey, contents);
})
})
})
}
});
},
this.load = function(cb) {
/**
* Loads extension manifest and copies its content to a workable path.
*
* @param {string=} path
* @returns {Promise}
*/
load: function (path) {
if (!fs.existsSync("tmp")) {
fs.mkdirSync("tmp")
fs.mkdirSync("tmp");
}
wrench.copyDirRecursive(this.rootDirectory, this.path, function(err) {
if (err) { throw err }
this.manifest = require(join(process.cwd(), this.path, "manifest.json"))
this.loaded = true
cb.call(this)
}.bind(this))
}
var selfie = this;
this.readFile = function(name, cb) {
var path = join(this.path, name)
return new Promise(function(resolve, reject){
wrench.copyDirRecursive(path || selfie.rootDirectory, selfie.path, function (err) {
if (err) {
return reject(err);
}
fs.readFile(path, "binary", function(err, data) {
if (err) return cb.call(this, err)
selfie.manifest = require(join(process.cwd(), selfie.path, "manifest.json"));
selfie.loaded = true;
cb.call(this, null, this[name] = data)
}.bind(this))
}
resolve(selfie);
});
});
},
this.writeFile = function(path, data, cb) {
path = join(this.path, path)
/**
* Writes data into the extension workable directory.
*
* @param {string} path
* @param {*} data
* @returns {Promise}
*/
writeFile: function (path, data) {
var absPath = join(this.path, path);
fs.writeFile(path, data, function(err, data) {
if (err) return cb.call(this, err)
return new Promise(function(resolve, reject){
fs.writeFile(absPath, data, function (err) {
if (err) {
return reject(err);
}
cb.call(this)
}.bind(this))
}
resolve();
});
});
},
this.generatePublicKey = function(cb) {
var rsa = spawn("openssl", ["rsa", "-pubout", "-outform", "DER"])
/**
* Generates a public key.
*
* BC BREAK `this.publicKey` is not stored anymore (since 1.0.0)
*
* @returns {Promise}
* @example
*
* crx.generatePublicKey(function(publicKey){
* // do something with publicKey
* });
*/
generatePublicKey: function () {
var privateKey = this.privateKey;
rsa.stdout.on("data", function(data) {
this.publicKey = data
cb && cb.call(this, null, this)
}.bind(this))
return new Promise(function(resolve, reject){
var rsa = spawn("openssl", ["rsa", "-pubout", "-outform", "DER"]);
rsa.stdin.end(this.privateKey)
}
rsa.stdout.on("data", function (publicKey) {
resolve(publicKey);
});
this.generateSignature = function() {
return this.signature = new Buffer(
rsa.on('error', reject);
rsa.stdin.end(privateKey);
});
},
/**
* Generates a SHA1 package signature.
*
* BC BREAK `this.signature` is not stored anymore (since 1.0.0)
*
* @param {Buffer} contents
* @returns {Buffer}
*/
generateSignature: function (contents) {
return new Buffer(
crypto
.createSign("sha1")
.update(this.contents)
.update(contents)
.sign(this.privateKey),
"binary"
)
}
);
},
this.loadContents = function(cb) {
var archive = archiver("zip")
this.contents = ""
/**
*
* BC BREAK `this.contents` is not stored anymore (since 1.0.0)
*
* @returns {Promise}
*/
loadContents: function () {
var archive = archiver("zip");
var selfie = this;
files = wrench.readdirSyncRecursive(this.path)
for (var i = 0; i < files.length; i++) {
current = files[i]
stat = fs.statSync(join(this.path, current))
if (stat.isFile() && current !== "key.pem") {
archive.append(fs.createReadStream(join(this.path, current)), { name: current })
}
}
return new Promise(function(resolve, reject){
var contents = new Buffer('');
var allFiles = [];
archive.finalize()
// Relates to the issue: "Event 'finished' no longer valid #18"
// https://github.com/jed/crx/issues/18
// TODO: Buffer concat could be a problem when building a big extension.
// So ideally only the 'finish' callback must be used.
archive.on('readable', function() {
this.contents = !this.contents.length ? archive.read() : Buffer.concat([this.contents, archive.read()])
}.bind(this))
// the callback is called many times
// when 'files' is null, it means we accumulated everything
// hence this weird setup
wrench.readdirRecursive(selfie.path, function(err, files){
if (err){
return reject(err);
}
archive.on('finish', function() {
cb.call(this)
}.bind(this))
// stack unless 'files' is null
if (files){
allFiles = allFiles.concat(files);
return;
}
archive.on("error", function(err) {
throw err
})
}
this.generatePackage = function() {
var signature = this.signature
, publicKey = this.publicKey
, contents = this.contents
allFiles.forEach(function (file) {
var filePath = join(selfie.path, file);
var stat = fs.statSync(filePath);
, keyLength = publicKey.length
, sigLength = signature.length
, zipLength = contents.length
, length = 16 + keyLength + sigLength + zipLength
if (stat.isFile() && file !== "key.pem") {
archive.append(fs.createReadStream(filePath), { name: file });
}
});
, crx = new Buffer(length)
archive.finalize();
crx.write("Cr24" + Array(13).join("\x00"), "binary")
// Relates to the issue: "Event 'finished' no longer valid #18"
// https://github.com/jed/crx/issues/18
// TODO: Buffer concat could be a problem when building a big extension.
// So ideally only the 'finish' callback must be used.
archive.on('readable', function () {
contents = Buffer.concat([contents, archive.read()]);
});
crx[4] = 2
crx.writeUInt32LE(keyLength, 8)
crx.writeUInt32LE(sigLength, 12)
archive.on('finish', function () {
resolve(contents);
});
publicKey.copy(crx, 16)
signature.copy(crx, 16 + keyLength)
contents.copy(crx, 16 + keyLength + sigLength)
archive.on("error", reject);
});
});
},
return this.package = crx
}
/**
* Generates and returns a signed package from extension content.
*
* BC BREAK `this.package` is not stored anymore (since 1.0.0)
*
* @param {Buffer} signature
* @param {Buffer} publicKey
* @param {Buffer} contents
* @returns {Buffer}
*/
generatePackage: function (signature, publicKey, contents) {
var keyLength = publicKey.length;
var sigLength = signature.length;
var zipLength = contents.length;
var length = 16 + keyLength + sigLength + zipLength;
this.generateAppId = function() {
return this.appId = crypto
var crx = new Buffer(length);
crx.write("Cr24" + Array(13).join("\x00"), "binary");
crx[4] = 2;
crx.writeUInt32LE(keyLength, 8);
crx.writeUInt32LE(sigLength, 12);
publicKey.copy(crx, 16);
signature.copy(crx, 16 + keyLength);
contents.copy(crx, 16 + keyLength + sigLength);
return crx;
},
/**
* Generates an appId from the publicKey.
*
* BC BREAK `this.appId` is not stored anymore (since 1.0.0)
*
* @returns {string}
*/
generateAppId: function () {
return crypto
.createHash("sha256")

@@ -173,22 +291,30 @@ .update(this.publicKey)

.slice(0, 32)
.replace(/./g, function(x) {
return (parseInt(x, 16) + 10).toString(26)
})
}
.replace(/./g, function (x) {
return (parseInt(x, 16) + 10).toString(26);
});
},
this.generateUpdateXML = function() {
if (!this.codebase) throw new Error("No URL provided for update.xml.")
/**
* Generates an updateXML file from the extension content.
*
* BC BREAK `this.updateXML` is not stored anymore (since 1.0.0)
*
* @returns {Buffer}
*/
generateUpdateXML: function () {
if (!this.codebase) {
throw new Error("No URL provided for update.xml.");
}
return this.updateXML =
Buffer(
"<?xml version='1.0' encoding='UTF-8'?>\n" +
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>\n" +
" <app appid='" + this.generateAppId() + "'>\n" +
" <updatecheck codebase='" + this.codebase + "' version='" + this.manifest.version + "' />\n" +
" </app>\n" +
"</gupdate>"
)
return Buffer(
"<?xml version='1.0' encoding='UTF-8'?>\n" +
"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>\n" +
" <app appid='" + (this.appId || this.generateAppId()) + "'>\n" +
" <updatecheck codebase='" + this.codebase + "' version='" + this.manifest.version + "' />\n" +
" </app>\n" +
"</gupdate>"
);
}
};
return ChromeExtension
}
module.exports = ChromeExtension;

@@ -1,20 +0,29 @@

var fs = require("fs")
, assert = require("assert")
, ChromeExtension = require("../")
, join = require("path").join
, crx = new ChromeExtension({
privateKey: fs.readFileSync(join(__dirname, "key.pem")),
codebase: "http://localhost:8000/myFirstExtension.crx",
rootDirectory: join(__dirname, "myFirstExtension")
})
var fs = require("fs");
var test = require("tape");
var ChromeExtension = require("../");
var join = require("path").join;
var crx = new ChromeExtension({
privateKey: fs.readFileSync(join(__dirname, "key.pem")),
codebase: "http://localhost:8000/myFirstExtension.crx",
rootDirectory: join(__dirname, "myFirstExtension")
});
crx.pack(function(err, data){
if (err) throw err
test('it should pack the test extension', function(t){
t.plan(3);
var updateXML = this.generateUpdateXML()
crx.pack()
.then(function(packageData){
t.ok(packageData instanceof Buffer);
fs.writeFile(join(__dirname, "update.xml"), updateXML)
fs.writeFile(join(__dirname, "myFirstExtension.crx"), data)
var updateXML = crx.generateUpdateXML();
this.destroy()
})
t.ok(updateXML instanceof Buffer);
fs.writeFile(join(__dirname, "update.xml"), updateXML);
fs.writeFile(join(__dirname, "myFirstExtension.crx"), packageData);
return crx.destroy();
})
.then(t.pass.bind(t))
.catch(t.error.bind(t));
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc