passbooker
Version:
Generates Apple Passboks
356 lines (254 loc) • 8.02 kB
JavaScript
;
/**
* Module dependencies
*/
var path = require('path');
var fs = require('fs');
var crypto = require('crypto');
var exec = require('child_process').exec;
var os = require('os');
var async = require('async');
var JSZip = require('JSZip');
var Utils = require('./utils');
var Hoek = require('hoek');
/**
* Config
*/
var signatureCommandTpl = 'openssl smime -binary -sign -certfile {{wwdrCertPath}} -signer {{passCertPath}} -inkey {{passKeyPath}} -in {{manifestFilePath}} -out {{signatureFilePath}} -outform DER';
var supportedImagesByPassType = {
'boardingPass': [
'icon.png', 'icon@2x.png',
'logo.png', 'logo@2x.png',
'footer.png', 'footer@2x.png'
],
'coupon': [
'icon.png', 'icon@2x.png',
'logo.png', 'logo@2x.png',
'strip.png', 'strip@2x.png'
],
'eventTicket': [
'icon.png', 'icon@2x.png',
'logo.png', 'logo@2x.png',
'strip.png', 'strip@2x.png',
'background.png', 'background@2x.png',
'thumbnail.png', 'thumbnail@2x.png'
],
'storeCard': [
'icon.png', 'icon@2x.png',
'logo.png', 'logo@2x.png',
'strip.png', 'strip@2x.png'
],
'generic': [
'icon.png', 'icon@2x.png',
'logo.png', 'logo@2x.png',
'thumbnail.png', 'thumbnail@2x.png'
],
};
/**
* Pass
*/
var Pass = function fnPass( template, options ) {
if ( !template ) {
throw new Error('No `template` supplied!');
}
if ( !options ) {
throw new Error('No `options` supplied!');
}
var that = this;
that.options = options;
that.template = template;
that.type = that.template.type;
that.pass = {};
that.barcode = {};
that._setImagePaths();
that._setPass();
};
/**
* Get SHA1 checksum of a string
* @api Private
*/
Pass.prototype._getSHA1Checksum = function( string ) {
return crypto.createHash( 'sha1' ).update( string, 'utf8' ).digest( 'hex' );
};
/**
* Set image paths
* @api Private
*/
Pass.prototype._setImagePaths = function fn_setImagePaths() {
var that = this;
var supportedImages = supportedImagesByPassType[that.type];
that.imagePaths = fs
.readdirSync( that.template.options.imagePath )
.filter(function( file ) {
return supportedImages.indexOf( file ) > -1;
})
.map(function( file ) {
return path.join( that.template.options.imagePath, file );
});
return true;
};
/**
* Set pass object
* @api Private
*/
Pass.prototype._setPass = function fn_setPass() {
var that = this;
that.pass = Hoek.applyToDefaults( that.template.getPass(), that.pass );
that.pass.serialNumber = this.options.serialNumber || Utils.guid();
that.pass[that.type].headerFields = Hoek.merge( that.pass[that.type].headerFields, this.options.headerFields );
that.pass[that.type].primaryFields = Hoek.merge( that.pass[that.type].primaryFields, this.options.primaryFields );
that.pass[that.type].secondaryFields = Hoek.merge( that.pass[that.type].secondaryFields, this.options.secondaryFields );
that.pass[that.type].auxiliaryFields = Hoek.merge( that.pass[that.type].auxiliaryFields, this.options.auxiliaryFields );
that.pass[that.type].backFields = Hoek.merge( that.pass[that.type].backFields, this.options.backFields );
that.pass.barcode = this.options.barcode || that.pass.barcode || {};
return true;
};
/**
* Set manifest object
* @api Private
*/
Pass.prototype._setManifest = function() {
var that = this;
that.manifest = {
'pass.json': that._getSHA1Checksum( JSON.stringify(that.pass) )
};
that.imagePaths.forEach(function( image ) {
that.manifest[path.basename(image)] = that._getSHA1Checksum( fs.readFileSync(image) );
});
return true;
};
/**
* Get signature
* @api Private
*/
Pass.prototype._getSignature = function fn_getSignature( callback ) {
var that = this;
var command = '';
var manifestFilePath = path.join( os.tmpdir(), Utils.guid() );
var signatureFilePath = path.join( os.tmpdir(), Utils.guid() );
var keysPath = that.template.options.keysPath;
var passCertPath = path.join( keysPath, that.template.options.passCertName );
var passKeyPath = path.join( keysPath, that.template.options.passKeytName );
var wwdrCertPath = path.join( keysPath, that.template.options.wwdrCertName );
if ( !fs.existsSync(keysPath) ) {
throw new Error('`keysPath` does not exist!');
}
if ( !fs.existsSync(passCertPath) ) {
throw new Error('Pass certificate does not exist!');
}
if ( !fs.existsSync(passKeyPath) ) {
throw new Error('Pass key does not exist!');
}
if ( !fs.existsSync(wwdrCertPath) ) {
throw new Error('Worldwide Developer Relations Certificate certificate does not exist!');
}
command = signatureCommandTpl
.replace('{{manifestFilePath}}', manifestFilePath )
.replace('{{signatureFilePath}}', signatureFilePath )
.replace('{{passCertPath}}', passCertPath )
.replace('{{passKeyPath}}', passKeyPath )
.replace('{{wwdrCertPath}}', wwdrCertPath );
var tasks = {
writeManifest: function( fn ) {
that._setManifest();
fs.writeFile( manifestFilePath, JSON.stringify(that.manifest), { encoding: 'utf8' }, fn );
},
signManifest: function( fn ) {
return exec( command, fn );
},
signature: function( fn ) {
return fs.readFile( signatureFilePath, fn );
},
deleteManifestJSON: function( fn ) {
return fs.unlink( manifestFilePath, fn );
},
deleteSignature: function( fn ) {
return fs.unlink( signatureFilePath, fn );
},
};
async.series( tasks, function( err, results ) {
return callback( null, results.signature );
});
};
/**
* Getter / Setter
*/
Pass.prototype._setContentField = function fn_setContentField( fieldType, input ) {
var that = this;
if ( !input.value ) {
input.value = input.label;
input.label = null;
}
var fields = {};
that.pass[that.type][fieldType].forEach(function( primaryField ) {
fields[primaryField.key] = primaryField;
});
fields[input.key] = fields[input.key] || {};
fields[input.key].key = input.key;
fields[input.key].value = input.value;
fields[input.key].label = input.label;
if ( !input.label ) {
delete fields[input.key].label;
}
that.pass[that.type][fieldType] = Object.keys( fields ).map(function( aKey ) {
return fields[aKey];
});
return that;
};
Pass.prototype.setPrimaryField = function fnSetPrimaryField( key, label, value ) {
var that = this;
return that._setContentField( 'primaryFields', {
key: key, label: label, value: value
});
};
Pass.prototype.setSecondaryField = function fnSetSecondaryField( key, label, value ) {
var that = this;
return that._setContentField( 'secondaryFields', {
key: key, label: label, value: value
});
};
Pass.prototype.setAuxiliaryField = function fnSetAuxiliaryField( key, label, value ) {
var that = this;
return that._setContentField( 'auxiliaryFields', {
key: key, label: label, value: value
});
};
Pass.prototype.setBackField = function fnSetBackField( key, label, value ) {
var that = this;
return that._setContentField( 'backFields', {
key: key, label: label, value: value
});
};
Pass.prototype.setBarcode = function fn_setBarcode( options ) {
var that = this;
if ( !options ) {
return that;
}
that.pass.barcode = options;
return that;
};
/**
* Save pass
* @api Public
*/
Pass.prototype.getPackage = function fnSavePass( callback ) {
var that = this;
that._getSignature(function( err, signature ) {
if ( !!err ) {
return callback( err );
}
var zip = new JSZip();
zip.file( 'pass.json', JSON.stringify(that.pass) );
zip.file( 'manifest.json', JSON.stringify(that.manifest) );
zip.file( 'signature', signature );
that.imagePaths.forEach(function( imagePath ) {
zip.file( path.basename(imagePath), fs.readFileSync(imagePath) );
});
var zipBuffer = zip.generate({ type: 'nodebuffer' });
return callback( null, zipBuffer );
});
};
/**
* Export
*/
module.exports = Pass;