fiftyonedegreescore
Version:
51degrees device detection for nodejs
195 lines (194 loc) • 9.46 kB
JavaScript
/* *********************************************************************
* This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
* Copyright 2017 51Degrees Mobile Experts Limited, 5 Charlotte Close,
* Caversham, Reading, Berkshire, United Kingdom RG4 7BY
*
* This Source Code Form is the subject of the following patents and patent
* applications, owned by 51Degrees Mobile Experts Limited of 5 Charlotte
* Close, Caversham, Reading, Berkshire, United Kingdom RG4 7BY:
* European Patent No. 2871816;
* European Patent Application No. 17184134.9;
* United States Patent Nos. 9,332,086 and 9,350,823; and
* United States Patent Application No. 15/686,066.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0.
*
* If a copy of the MPL was not distributed with this file, You can obtain
* one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, v. 2.0.
********************************************************************** */
var fs = require("fs");
var zlib = require("zlib");
var crypto = require("crypto");
var https = require("https");
var dataSetNextUpdateDate;
var querystring = require("querystring");
var updating;
// Default the product name to Lite.
var product = 'Lite';
// Regular expression to check for a valid license key.
var validLicenseRegEx = new RegExp("^[A-Z\\d]+$");
// Update function called when an update is due.
var update = function (provider, errorHandler) {
// Load config and make the querystring parameter and http request options.
var config = provider.config
// Check to see if a preferred product has been requested.
if (config.Product) {
product = config.Product;
}
var parameters = {
LicenseKeys: config.License,
Type: config.Type,
Download: 'True',
Product: product,
};
// Check the license key is not empty
if (config.License.length === 0) {
errorHandler('At least one valid license key is required ' + 'to update device data. See https://51degrees.com/' + 'compare-data-options to acquire valid license keys.');
return false;
}
// Check the license key is a valid format.
if (validLicenseRegEx.exec(config.License) === null) {
errorHandler('The license key(s) provided were invalid. See ' + 'https://51degrees.com/compare-data-options to acquire ' + 'valid license keys.');
return false;
}
// Set the request options to get the update from.
var requestOptions = {
host: "distributor.51degrees.com",
port: 443,
path: "/api/v2/download?" + querystring.stringify(parameters)
};
var request = https.get(requestOptions);
// When recieving response, if gzip download file, if not, return error.
request.on("response", function (response) {
// If the response code is not 200, then throw an error as the
// download will not happen.
if (response.statusCode !== 200) {
switch (response.statusCode) {
case 429:
errorHandler('Too many attempts have been made to ' + 'download a data file from this public IP ' + 'address or with this license key. Try again ' + 'after a period of time.');
return false;
case 403:
errorHandler('Data not downloaded. The license key is not' + 'valid');
return false;
default:
errorHandler('An error occurred fetching the data file. ' + 'Try again incase the error is temporary, ' + 'or validate license key and network ' + 'configuration.');
return false;
}
}
// If the response is not gzip encoded then return an error.
if (response.headers["content-encoding"] && response.headers["content-encoding"].indexOf("gzip") === -1) {
errorHandler("The response encoding was " + response.headers['content-encoding']);
return false;
}
// Set updating flag to true so that another update process does not start.
updating = true;
// Stream contents of the gzip file into temp file on disk.
var zippedOutput = fs.createWriteStream(config.dataFile + ".gz");
response.pipe(zippedOutput);
response.on("end", function () {
// When the stream completes, read zipped file.
fs.readFile(config.dataFile + ".gz", function (err, zippedFile) {
if (err) {
// There was an error reading the donwloaded file.
errorHandler(err);
return false;
}
// Check the hash of the zipped file against the md5 from the request.
var hash = crypto.createHash("md5").update(zippedFile).digest("hex");
if (hash === response.headers["content-md5"]) {
// The hashes match, so unzip the file.
zlib.unzip(zippedFile, function (err, data) {
// Now write it to file.
fs.writeFile(config.dataFile, data, function (err) {
if (err) {
// There was an error writing to file.
errorHandler(err);
return false;
}
// If writing to file succedes delete the gzip file.
fs.unlink(config.dataFile + ".gz", function (err) {
if (err) {
// There was an error deleting the file.
errorHandler(err);
}
else {
// The file was delted, so return
// without error.
errorHandler();
}
});
});
});
}
else {
// The hashes did not match.
errorHandler("Data was downloaded but the MD5 hash failed");
}
});
});
});
};
module.exports = function (provider, FOD) {
var config = provider.config;
var currentProduct = provider.getDataSetName();
// Update the data file immediately if the Product is supplied
// and the current data set is different to what was requested.
if (config.Product && currentProduct.indexOf(config.Product) === -1) {
dataSetNextUpdateDate = new Date(0);
} else {
// Get the next update date of the data file (only called once on init).
dataSetNextUpdateDate = new Date(provider.getDataSetNextUpdateDate());
// Get the product name of the data file which is being used. If a lite
// file is being used then emit an info event stating automatic updates
// are not supported.
if (currentProduct.indexOf('Premium') !== -1) {
product = 'Premium';
}
else if (currentProduct.indexOf('Enterprise') !== -1) {
product = 'Enterprise';
}
else if (currentProduct.indexOf('Lite') !== -1 && provider.getDataSetFormat().indexOf('HashTrieV34') !== -1) {
product = 'Lite';
}
else {
FOD.log.emit('info', '[' + provider.Id + '] ' + 'Lite Pattern data file does not support automatic' + ' updates. See https://51degrees.com/compare-data-' + 'options for more information.');
}
}
// Regularly check if the data file is up to date against the current time.
var checkUpdate = function() {
if (updating) {
return false;
}
if (new Date() > dataSetNextUpdateDate) {
// Run update function.
update(provider, function (err) {
if (err) {
// If failed, output log the error and unset the updating flag.
FOD.log.emit("info", '[' + provider.Id + '] ' + 'Could not update the data file ' + 'reason: ' + err);
updating = false;
return false;
}
else {
// If updated successfully, reload the provider using the new data file,
// set the new update date, and unset the updating flag.
provider.reloadFromFile();
dataSetNextUpdateDate = new Date(provider.getDataSetNextUpdateDate());
FOD.log.emit('info', '[' + provider.Id + '] ' + 'Automatically updated data file ' + config.dataFile + ' with version published ' + 'on ' + provider.getDataSetPublishedDate());
updating = false;
}
});
}
else {
FOD.log.emit('info', '[' + provider.Id + '] ' + 'Could not update the data file reason: ' + 'The data file is current and does not need to be ' + 'updated');
}
}
// Attempt the update process once then every 30 minutes.
checkUpdate();
var timer = setInterval(checkUpdate, 1800000);
FOD.log.emit('info', '[' + provider.Id + '] ' + 'Auto updater started. Next update date ' + dataSetNextUpdateDate);
return timer;
};