<commit_before>'use strict';
const Request = require('https').request;
const CLOUD_REQUEST = { 'user-agent': undefined };
const REQUEST_HEADERS = {
        headers: {
            'content-type':'application/json'
        },
        hostname: 'api.handsetdetection.com',
        method: 'POST',
        path: '/apiv4/device/detect.json'
    };
const CONFIG = {};
const FileSystem = require('fs');
const Unzip = require('unzip2').Extract;
const DATABASE_PATH_FOLDER = __dirname +'/ultimate4';
const DATABASE_PATH_ZIP = DATABASE_PATH_FOLDER + '.zip';
const REVERSE_TREE_TRAVERAL_ORDER = ['platform','browser','1','0'];
let LRU_CACHE = require('lru-cache')( 10e3 );
let LRU_GET = LRU_CACHE.get.bind( LRU_CACHE );
let LRU_SET = LRU_CACHE.set.bind( LRU_CACHE );
const EXTRAS_FILTER = new RegExp( ' |[^(\x20-\x7F)]*', 'g' );
const DEVICE_UA_FILTER = new RegExp( /_| |#|-|,|\.|\/|:|"|'/.source + '|[^(\x20-\x7F)]*', 'g' );
let DEVICE_ARRAY = [];
let EXTRAS_ARRAY = [];
let TREE = {};
let SAVE_TO_CACHE_ENABLED;
let EventEmitter;
///////////////////////////////////////////////////////////
////////// Main Module ///////////////////////////////////
///////////////////////////////////////////////////////
////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
module.exports = {
    init: function( config ) {
        if ( config.username !== undefined && config.secret !== undefined ) {
            CONFIG.username = config.username;
            CONFIG.secret = config.secret;

            if ( config.cloud === true ) {
                setHTTPAuthHeader();
                return request;
            } else if ( config.enableAutoUpdates === true && config.eventEmitter !== undefined ) {
                EventEmitter = config.eventEmitter;
                return FileSystem.readdir( DATABASE_PATH_FOLDER, function( error, files ) {
                    // If the database is not downloaded, then download it
                    if ( error !== null && error.errno === -2 ) {
                        reportLog('HandSetDetection database not found. Downloading...');
                        updateDatabase();
                    }
                    // Every 3 days, update the database
                    setInterval( updateDatabase, 2.592e8 );
                });
            }
        } else if ( config.hosted === true ) {
            CONFIG.dir = DATABASE_PATH_FOLDER + '/';
            loadJSONfilesIntoMemory();
            return matchDevice;
        }
        console.error('ERROR from HandSetDetection client. Required params not met');
    },
    reloadLibrary: loadJSONfilesIntoMemory
};
////////// Cloud client ////////////////////////////////
function request( userAgent, callback ) {
    CLOUD_REQUEST['user-agent'] = userAgent;

    let streamWritable = Request( REQUEST_HEADERS, onResponse ).once('error', callback );
    streamWritable.callback = callback;
    streamWritable.end( JSON.stringify( CLOUD_REQUEST ) );
}
function onResponse( streamReadable ) {
    streamReadable.setEncoding('utf8').callback = this.callback;
    streamReadable.on('data', onChunk ).once('end', onDataEnd ).data = '';
}
function onChunk( chunk ) {
    this.data += chunk;
}
function onDataEnd() {
    this.callback( undefined, JSON.parse( this.data ) );
}
function setHTTPAuthHeader( updateDatabase ) {
    const md5 = ( string )=> require('crypto').createHash('md5').update( string ).digest('hex');
    const HA1 = md5( CONFIG.username + ':APIv4:' + CONFIG.secret );
    const HA2 = md5( updateDatabase === true? 'GET:/apiv4/device/fetcharchive.json' : 'POST:/apiv4/device/detect.json' );
    const cnonce = md5( Math.random().toString() );
    const response = md5( HA1 + ':APIv4:00000001:'+cnonce+':auth:' + HA2 );

    REQUEST_HEADERS.headers.authorization = 'Digest username="'+CONFIG.username+'", realm="APIv4", nonce="APIv4", uri="'+ (updateDatabase === true? '/apiv4/device/fetcharchive.json' : '/apiv4/device/detect.json' ) +'", cnonce="'+cnonce+'", nc=00000001, qop=auth, response="'+response+'", opaque="APIv4"';

    if ( updateDatabase === true ) {
        REQUEST_HEADERS.method = 'GET';
        REQUEST_HEADERS.path = '/apiv4/device/fetcharchive.json';
    } else {
        REQUEST_HEADERS.path = '/apiv4/device/detect.json';
        REQUEST_HEADERS.method = 'POST';
    }
}
////////// Cloud client - end ////////////////////////////////
///////// Hosted client - start ////////////////////////////
function loadJSONfilesIntoMemory() {
    let files_left_to_load = REVERSE_TREE_TRAVERAL_ORDER.length;
    let updated_tree = {};
    let updated_device_array = [];
    let updated_extras_array = [];<commit_msg>An incremental & non-major update<commit_after>'use strict';
const Request = require('https').request;
const CLOUD_REQUEST = { 'user-agent': undefined };
const REQUEST_HEADERS = {
        headers: {
            'content-type':'application/json'
        },
        hostname: 'api.handsetdetection.com',
        method: 'POST',
        path: '/apiv4/device/detect.json'
    };
const CONFIG = {};
const FileSystem = require('fs');
const Unzip = require('unzip2').Extract;
const DATABASE_PATH_FOLDER = __dirname +'/ultimate4';
const DATABASE_PATH_ZIP = DATABASE_PATH_FOLDER + '.zip';
const REVERSE_TREE_TRAVERAL_ORDER = ['platform','browser','1','0'];
let LRU_CACHE = require('lru-cache')( 10e3 );
let LRU_GET = LRU_CACHE.get.bind( LRU_CACHE );
let LRU_SET = LRU_CACHE.set.bind( LRU_CACHE );
const EXTRAS_FILTER = new RegExp( ' |[^(\x20-\x7F)]*', 'g' );
const DEVICE_UA_FILTER = new RegExp( /_| |#|-|,|\.|\/|:|"|'/.source + '|[^(\x20-\x7F)]*', 'g' );
let DEVICE_ARRAY = [];
let EXTRAS_ARRAY = [];
let TREE = {};
let SAVE_TO_CACHE_ENABLED;
let EventEmitter;
///////////////////////////////////////////////////////////
////////// Main Module ///////////////////////////////////
///////////////////////////////////////////////////////
////////////////////////////////////////////////////////
///////////////////////////////////////////////////////
module.exports = {
    init: function( config ) {
        if ( config.username !== undefined && config.secret !== undefined ) {
            CONFIG.username = config.username;
            CONFIG.secret = config.secret;

            if ( config.cloud === true ) {
                setHTTPAuthHeader();
                return request;
            } else if ( config.enableAutoUpdates === true && config.eventEmitter !== undefined ) {
                EventEmitter = config.eventEmitter;
                return FileSystem.readdir( DATABASE_PATH_FOLDER, function( error ) {
                    // If the database is not downloaded, then download it
                    if ( error !== null && error.errno === -2 ) {
                        reportLog('HandSetDetection database not found. Downloading...');
                        updateDatabase();
                    }
                    // Every 3 days, update the database
                    setInterval( updateDatabase, 2.592e8 );
                });
            }
        } else if ( config.hosted === true ) {
            CONFIG.dir = DATABASE_PATH_FOLDER + '/';
            loadJSONfilesIntoMemory();
            return matchDevice;
        }
        console.error('ERROR from HandSetDetection client. Required params not met');
    },
    reloadLibrary: loadJSONfilesIntoMemory
};
////////// Cloud client ////////////////////////////////
function request( userAgent, callback ) {
    CLOUD_REQUEST['user-agent'] = userAgent;

    let streamWritable = Request( REQUEST_HEADERS, onResponse ).once('error', callback );
    streamWritable.callback = callback;
    streamWritable.end( JSON.stringify( CLOUD_REQUEST ) );
}
function onResponse( streamReadable ) {
    streamReadable.setEncoding('utf8').callback = this.callback;
    streamReadable.on('data', onChunk ).once('end', onDataEnd ).data = '';
}
function onChunk( chunk ) {
    this.data += chunk;
}
function onDataEnd() {
    this.callback( null, JSON.parse( this.data ) );
}
function setHTTPAuthHeader( updateDatabase ) {
    const md5 = ( string )=> require('crypto').createHash('md5').update( string ).digest('hex');
    const HA1 = md5( CONFIG.username + ':APIv4:' + CONFIG.secret );
    const HA2 = md5( updateDatabase === true? 'GET:/apiv4/device/fetcharchive.json' : 'POST:/apiv4/device/detect.json' );
    const cnonce = md5( Math.random().toString() );
    const response = md5( HA1 + ':APIv4:00000001:'+cnonce+':auth:' + HA2 );

    REQUEST_HEADERS.headers.authorization = 'Digest username="'+CONFIG.username+'", realm="APIv4", nonce="APIv4", uri="'+ (updateDatabase === true? '/apiv4/device/fetcharchive.json' : '/apiv4/device/detect.json' ) +'", cnonce="'+cnonce+'", nc=00000001, qop=auth, response="'+response+'", opaque="APIv4"';

    if ( updateDatabase === true ) {
        REQUEST_HEADERS.method = 'GET';
        REQUEST_HEADERS.path = '/apiv4/device/fetcharchive.json';
    } else {
        REQUEST_HEADERS.path = '/apiv4/device/detect.json';
        REQUEST_HEADERS.method = 'POST';
    }
}
////////// Cloud client - end ////////////////////////////////
///////// Hosted client - start ////////////////////////////
function loadJSONfilesIntoMemory() {
    let files_left_to_load = REVERSE_TREE_TRAVERAL_ORDER.length;
    let updated_tree = {};
    let updated_device_array = [];
    let updated_extras_array = [];
{'fraction_non_alphanumeric': 0.21893048128342246, 'fraction_numerical': 0.012620320855614974, 'mean_word_length': 2.134763660744217, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7158471', 'n_tokens_mistral': 2752, 'n_tokens_neox': 2634, 'n_words': 548}