fiftyonedegreescore
Version:
51degrees device detection for nodejs
191 lines (190 loc) • 7.72 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 zlib = require("zlib");
var https = require("https");
// Share usage object to return.
var shareUsage = {};
// Is a share usage worker already running?
var running = 0;
// Queue to store new device information.
var queue = [],
// Maximum length of the queue.
newDeviceQueueLength = 50;
// Array of local addresses to check client address against.
var local = ['127.0.0.1', '0:0:0:0:0:0:0:1'];
// 51Degrees log to send messages to.
var log;
// Product and version used when sharing usage.
var version,
product;
// Boolean value used to stop sharing usage if there is a connection
// problem.
var stop = false;
// Url to send device information to.
var requestOptions = {
host: 'devices.51degrees.com',
path: '/new.ashx',
method: 'POST',
port: '443',
headers: {
'Content-Type': 'text/xml; charset=utf-8',
'Content-Encoding': 'gzip'
}
};
// Indicates if the device is local.
var isLocal = function (address) {
var isLocal = false;
local.forEach(function (localAddress) {
if (address.indexOf(localAddress) !== -1) {
isLocal = true;
}
});
return isLocal;
};
// Sends all the data in the queue.
var sendData = function (outputStream) {
log.emit('debug', 'Sending usage data to ' + requestOptions.host);
var request = https.request(requestOptions, function (response) {
switch (response.statusCode) {
case 200: // OK
// Ok response, do nothing
break;
case 408: // Request Timeout
// Could be temporary, do nothing.
log.emit('debug', "Response code is 408 : " + response.statusMessage);
break;
default:
// Turn off functionality.
log.emit('error', 'Stopping usage sharing as remote ' + 'name ' + requestOptions.host + ' returned status ' +
'description ' + response.statusMessage);
stop = true;
break;
}
}).on('error', function (err) {
if (err.code === 'ENOTFOUND') {
// The address was not found, stop sharing.
stop = true;
log.emit('error', 'Stopping usage sharing as remote name ' + requestOptions.host + ' could not be resolved ' +
'and generated ENOTFOUND exception.');
}
else if (err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
// The certificate was not trusted, stop sharing.
stop = true;
log.emit('error', 'Stopping usage sharing as secure connection to remote ' + requestOptions.host + ' could not ' +
'be established and threw error ' + err.code);
}
else {
// Some other error occured, stop sharing.
stop = true;
log.emit('error', 'Stopping usage shareing after ' + err.code + ' exception.');
}
})
// Set the timeout to 1 second.
.setTimeout(1000);
// Begin the devices XML string
var xmlString = '<?xml version="1.0" encoding="UTF-8"?>';
xmlString += '<Devices>';
// Add each device to the XML and remove from the queue.
while (queue.length > 0) {
// Using pop instead of shift lists devices in reverse order
// but is quicker.
xmlString += queue.pop();
}
// End the devices XML string.
xmlString += '</Devices>';
// Compress the XML string and send it.
zlib.gzip(xmlString, function (err, result) {
request.write(result);
request.end();
log.emit('debug', 'Usage data sent successfully');
});
};
// Adds the request to the queue to be processed.
shareUsage.recordNewDevice = function (request) {
if (stop === false) {
// Usage sharing has not been stopped, so get the device information.
var device = getContent(request);
// Add the device information to the queue.
queue.push(device);
if (queue.length === newDeviceQueueLength) {
// The queue has reached is maximum length, so send the data.
sendData(queue);
}
}
};
// Replaces symbols that may be interpreted as part of the xml.
var charEscape = function (str) {
return str.replace(/\</g, "<").replace(/\>/g, ">").replace(/\"/g, """);
};
// Records the information as XML data and converts to a string for storage.
var getContent = function (request) {
// Begin the XML.
var device = '<Device>';
// Add the sender information.
device += '<DateSent>' + new Date().toISOString() + '</DateSent>';
device += '<Version>' + version + '</Version>';
device += '<Product>' + product + '</Product>';
// Get the remote address.
var remoteAddress = request.connection.remoteAddress;
// Are you local?
if (isLocal(remoteAddress.toString()) === true) {
device += '<ClientIP>' + remoteAddress.toString().replace(/^.*:/, '') + '</ClientIP>';
}
// Add the local address, removing the leading part.
var localAddress = request.connection.localAddress.replace(/^.*:/, '');
device += '<ServerIP>' + localAddress + '</ServerIP>';
// Add the headers that are useful.
Object.keys(request.headers).forEach(function (header) {
if (header === 'user-agent' || header === 'host' || header.indexOf('profile') !== -1) {
device += '<Header Name="' + header + '">' + charEscape(request.headers[header]) + '</Header>';
}
});
// End the XML.
device += '</Device>';
return device;
};
// Module constructor. Sets the error log, product version and product name.
module.exports = function (provider, FOD) {
log = FOD.log;
if (provider.config.UsageSharingDebug === true) {
// Provider has been created as part of a test, so send the usage
// data to localhost after one request to be tested.
requestOptions.host = 'localhost';
requestOptions.port = 1234;
newDeviceQueueLength = 1;
}
// Get the version of the data set e.g. "3.2".
version = provider.getDataSetFormat();
// Get the product name e.g. "Node js : Trie"
product = 'Node js : ' + provider.config.Type;
if (running !== 1) {
log.emit('info', '[' + provider.Id + '] ' + 'Usage sharer started');
// The usage sharer is started.
running = 1;
}
else {
log.emit('info', '[' + provider.Id + '] ' + 'Updated pre-existing usage sharer.');
}
// Return the share usage object.
return shareUsage;
};