Source: countly.js

/************
* Countly NodeJS SDK
* https://github.com/Countly/countly-sdk-nodejs
************/

/**
 * Countly object to manage the internal queue and send requests to Countly server
 * @name Countly
 * @global
 * @namespace Countly
 */
 
 var fs = require('fs'),
    os = require('os'),
    path = require('path'),
    http = require('http'),
    https = require('https'),
    cluster = require('cluster');

 var Countly = {};
 
(function (Countly) {
	'use strict';
    
    var SDK_VERSION = "16.12";
    var SDK_NAME = "javascript_native_nodejs";

	var inited = false,
		sessionStarted = false,
        platform,
        filePath = "../data/",
		apiPath = "/i",
		beatInterval = 500,
        queueSize = 1000,
		requestQueue = [],
		eventQueue = [],
        crashLogs = [],
        timedEvents = {},
        crashSegments = null,
		autoExtend = true,
		lastBeat,
        storedDuration = 0,
        lastView = null,
        lastViewTime = 0,
        lastMsTs = 0,
        lastViewStoredDuration = 0,
        failTimeout = 0,
        failTimeoutAmount = 60,
        sessionUpdate = 60,
        maxEventBatch = 10,
        readyToProcess = true,
        trackTime = true,
        metrics = {},
        startTime;

/**
* Countly metrics object
* @typedef {Object} Metrics
* @property {string} _os - name of platform/operating system
* @property {string} _os_version - version of platform/operating system
* @property {string} _device - device name
* @property {string} _resolution - screen resolution of the device
* @property {string} _carrier - carrier or operator used for connection
* @property {string} _density - screen density of the device
* @property {string} _locale - locale or language of the device in ISO format
* @property {string} _store - source from where the user/device/installation came from
*/


/**
* Countly initialization object
* @typedef {Object} Init
* @property {string} app_key - app key for your app created in Countly
* @property {string} device_id - to identify a visitor, will be auto generated if not provided
* @property {string} url - your Countly server url, you can use your own server URL or IP here
* @property {string} [app_version=0.0] - the version of your app or website
* @property {string=} country_code - country code for your visitor
* @property {string=} city - name of the city of your visitor
* @property {string=} ip_address - ip address of your visitor
* @property {boolean} [debug=false] - output debug info into console
* @property {number} [interval=500] - set an interval how often to check if there is any data to report and report it in miliseconds
* @property {number} [queue_size=1000] - maximum amount of queued requests to store
* @property {number} [fail_timeout=60] - set time in seconds to wait after failed connection to server in seconds
* @property {number} [session_update=60] - how often in seconds should session be extended
* @property {number} [max_events=10] - maximum amount of events to send in one batch
* @property {boolean} [force_post=false] - force using post method for all requests
* @property {Metrics} metrics - provide {@link Metrics} for this user/device, or else will try to collect what's possible
*/

/**
 * Initialize Countly object
 * @param {Init} conf - Countly initialization {@link Init} object with configuration options
 */
	Countly.init = function(ob){
		if(!inited){
            startTime = getTimestamp();
			inited = true;
			ob = ob || {};
            timedEvents = {};
            beatInterval = ob.interval || Countly.interval || beatInterval;
            queueSize = ob.queue_size || Countly.queue_size || queueSize;
            failTimeoutAmount = ob.fail_timeout || Countly.fail_timeout || failTimeoutAmount;
            sessionUpdate = ob.session_update || Countly.session_update || sessionUpdate;
            maxEventBatch = ob.max_events || Countly.max_events || maxEventBatch;
            metrics = ob.metrics || Countly.metrics || {};
			Countly.debug = ob.debug || Countly.debug || false;
			Countly.app_key = ob.app_key || Countly.app_key || null;
			Countly.url = stripTrailingSlash(ob.url || Countly.url || "");
			Countly.app_version = ob.app_version || Countly.app_version || "0.0";
			Countly.country_code = ob.country_code || Countly.country_code || null;
			Countly.city = ob.city || Countly.city || null;
			Countly.ip_address = ob.ip_address || Countly.ip_address || null;
            Countly.force_post = ob.force_post || Countly.force_post || false;
            
            if(Countly.url === ""){
                log("Please provide server URL");
            }
            else{
                log("Countly initialized");
                if (cluster.isMaster) {
                    Countly.device_id = ob.device_id || Countly.device_id || getId();
                    storeSet("cly_id", Countly.device_id);
                    requestQueue = storeGet("cly_queue", []);
                    eventQueue = storeGet("cly_event", []);
                    heartBeat();
                    //listen to current workers
                    if(cluster.workers){
                        for (var id in cluster.workers) {
                            cluster.workers[id].on('message', handleWorkerMessage);
                        }
                    }
                    //handle future workers
                    cluster.on('fork', function(worker) {
                        worker.on('message', handleWorkerMessage);
                    });
                }
            }
		}
	};
    
    /**
    * Start session
    * @param {boolean} noHeartBeat - true if you don't want to use internal heartbeat to manage session
    */
	
	Countly.begin_session = function(noHeartBeat){
		if(!sessionStarted){
			log("Session started");
			lastBeat = getTimestamp();
			sessionStarted = true;
			autoExtend = (noHeartBeat) ? false : true;
			var req = {};
			req.begin_session = 1;
			req.metrics = JSON.stringify(getMetrics());
			toRequestQueue(req);
		}
	};
	
    /**
    * Report session duration
    * @param {int} sec - amount of seconds to report for current session
    */
	Countly.session_duration = function(sec){
		if(sessionStarted){
			log("Session extended", sec);
			toRequestQueue({session_duration:sec});
		}
	};
	
    /**
    * End current session
    * @param {int} sec - amount of seconds to report for current session, before ending it
    */
	Countly.end_session = function(sec){
		if(sessionStarted){
            sec = sec || getTimestamp()-lastBeat;
			log("Ending session");
            reportViewDuration();
			sessionStarted = false;
			toRequestQueue({end_session:1, session_duration:sec});
		}
	};
	
    /**
    * Change current user/device id
    * @param {string} newId - new user/device ID to use
    * @param {boolean=} merge - move data from old ID to new ID on server
    **/
	Countly.change_id = function(newId, merge){
        if(cluster.isMaster){
            if(Countly.device_id != newId){
                if(!merge){
                    //end current session
                    Countly.end_session();
                    //clear timed events
                    timedEvents = {};
                }
                var oldId = Countly.device_id;
                Countly.device_id = newId;
                storeSet("cly_id", Countly.device_id);
                log("Changing id");
                if(merge)
                    toRequestQueue({old_device_id:oldId});
                else
                    //start new session for new id
                    Countly.begin_session(!autoExtend);
            }
        }
        else{
            process.send({ cly: {change_id: newId, merge:merge} });
        }
	};
    
    /**
    * Countly custom event object
    * @typedef {Object} Event
    * @property {string} key - name or id of the event
    * @property {number} [count=1] - how many times did event occur
    * @property {number=} sum - sum to report with event (if any)
    * @property {number=} dur - duration to report with event (if any)
    * @property {Object=} segmentation - object with segments key /values
    */
	
    /**
    * Report custom event
    * @param {Event} event - Countly {@link Event} object
    **/
	Countly.add_event = function(event){
		if(!event.key){
			log("Event must have key property");
			return;
		}
		if(cluster.isMaster){
            if(!event.count)
                event.count = 1;
            
            var props = ["key", "count", "sum", "dur", "segmentation"];
            var e = getProperties(event, props);
            e.timestamp = getMsTimestamp();
            var date = new Date();
            e.hour = date.getHours();
            e.dow = date.getDay();
            log("Adding event: ", event);
            eventQueue.push(e);
            storeSet("cly_event", eventQueue);
        }
        else{
            process.send({ cly: {event: event} });
        }
	};
    
    /**
    * Start timed event, which will fill in duration property upon ending automatically
    * @param {string} key - event name that will be used as key property
    **/
    Countly.start_event = function(key){
        if(timedEvents[key]){
            log("Timed event with key " + key + " already started");
            return;
        }
        timedEvents[key] = getTimestamp();
    };
    
    /**
    * End timed event
    * @param {string|Event} event - event key if string or Countly {@link Event} object
    **/
    Countly.end_event = function(event){
       if(typeof event == "string"){
           event = {key:event};
       }
       if(!event.key){
           log("Event must have key property");
           return;
       }
       if(!timedEvents[event.key]){
           log("Timed event with key " + key + " was not started");
           return;
       }
       event.dur = getTimestamp() - timedEvents[event.key];
       Countly.add_event(event);
       delete timedEvents[event.key];
    };
	
    /**
    * Countly user information object
    * @typedef {Object} UserDetails
    * @property {string=} name - user's full name
    * @property {string=} username - user's username or nickname
    * @property {string=} email - user's email address
    * @property {string=} organization - user's organization or company
    * @property {string=} phone - user's phone number
    * @property {string=} picture - url to user's picture
    * @property {string=} gender - M value for male and F value for femail
    * @property {number=} byear - user's birth year used to calculate current age
    * @property {Object=} custom - object with custom key value properties you want to save with user
    */
	
    /**
    * Report custom event
    * @param {UserDetails} user - Countly {@link UserDetails} object
    **/
	Countly.user_details = function(user){
		log("Adding userdetails: ", user);
		var props = ["name", "username", "email", "organization", "phone", "picture", "gender", "byear", "custom"];
		toRequestQueue({user_details: JSON.stringify(getProperties(user, props))});
	};
    
    /**
    * Report user conversion to the server (when user signup or made a purchase, or whatever your conversion is)
    * @param {string} campaign_id - id of campaign, the last part of the countly campaign link
    * @param {string=} campaign_user_id - id of user's clicked on campaign link, if you have one
    **/
    Countly.report_conversion = function(campaign_id, campaign_user_id){
        if(campaign_id && campaign_user_id)
            toRequestQueue({campaign_id: campaign_id, campaign_user: campaign_user_id});
        else if(campaign_id)
            toRequestQueue({campaign_id: campaign_id});
        else
            log("No campaign data found");
    };
    
    /**************************
    * Modifying custom property values of user details
    * Possible modification commands
    *  - inc, to increment existing value by provided value
    *  - mul, to multiply existing value by provided value
    *  - max, to select maximum value between existing and provided value
    *  - min, to select minimum value between existing and provided value
    *  - setOnce, to set value only if it was not set before
    *  - push, creates an array property, if property does not exist, and adds value to array
    *  - pull, to remove value from array property
    *  - addToSet, creates an array property, if property does not exist, and adds unique value to array, only if it does not yet exist in array
    **************************/
    var customData = {};
    var change_custom_property = function(key, value, mod){
        if(!customData[key])
            customData[key] = {};
        if(mod == "$push" || mod == "$pull" || mod == "$addToSet"){
            if(!customData[key][mod])
                customData[key][mod] = [];
            customData[key][mod].push(value);
        }
        else
            customData[key][mod] = value;
    };
    
    /**
    * @namespace Countly.userData
    */
    Countly.userData = {
        /**
        * Sets user's custom property value
        * @param {string} key - name of the property to attach to user
        * @param {string|number} value - value to store under provided property
        **/
        set: function(key, value){
            customData[key] = value;
        },
        /**
        * Sets user's custom property value only if it was not set before
        * @param {string} key - name of the property to attach to user
        * @param {string|number} value - value to store under provided property
        **/
        set_once: function(key, value){
            change_custom_property(key, 1, "$setOnce");
        },
        /**
        * Increment value under the key of this user's custom properties by one
        * @param {string} key - name of the property to attach to user
        **/
        increment: function(key){
            change_custom_property(key, 1, "$inc");
        },
        /**
        * Increment value under the key of this user's custom properties by provided value
        * @param {string} key - name of the property to attach to user
        * @param {number} value - value by which to increment server value
        **/
        increment_by: function(key, value){
            change_custom_property(key, value, "$inc");
        },
        /**
        * Multiply value under the key of this user's custom properties by provided value
        * @param {string} key - name of the property to attach to user
        * @param {number} value - value by which to multiply server value
        **/
        multiply: function(key, value){
            change_custom_property(key, value, "$mul");
        },
        /**
        * Save maximal value under the key of this user's custom properties
        * @param {string} key - name of the property to attach to user
        * @param {number} value - value which to compare to server's value and store maximal value of both provided
        **/
        max: function(key, value){
            change_custom_property(key, value, "$max");
        },
        /**
        * Save minimal value under the key of this user's custom properties
        * @param {string} key - name of the property to attach to user
        * @param {number} value - value which to compare to server's value and store minimal value of both provided
        **/
        min: function(key, value){
            change_custom_property(key, value, "$min");
        },
        /**
        * Add value to array under the key of this user's custom properties. If property is not an array, it will be converted to array
        * @param {string} key - name of the property to attach to user
        * @param {string|number} value - value which to add to array
        **/
        push: function(key, value){
            change_custom_property(key, value, "$push");
        },
        /**
        * Add value to array under the key of this user's custom properties, storing only unique values. If property is not an array, it will be converted to array
        * @param {string} key - name of the property to attach to user
        * @param {string|number} value - value which to add to array
        **/
        push_unique: function(key, value){
            change_custom_property(key, value, "$addToSet");
        },
        /**
        * Remove value from array under the key of this user's custom properties
        * @param {string} key - name of the property
        * @param {string|number} value - value which to remove from array
        **/
        pull: function(key, value){
            change_custom_property(key, value, "$pull");
        },
        /**
        * Save changes made to user's custom properties object and send them to server
        **/
        save: function(){
            toRequestQueue({user_details: JSON.stringify({custom:customData})});
            customData = {};
        }
    };
    
    /**
    * Automatically track javascript errors that happen on the nodejs process
    * @param {string=} segments - additional key value pairs you want to provide with error report, like versions of libraries used, etc.
    **/
    Countly.track_errors = function(segments){
        crashSegments = segments;
        process.on('uncaughtException', function (err) {
            recordError(err, false);
            if(cluster.isMaster){
                forceStore();
            }
            console.error((new Date()).toUTCString() + ' uncaughtException:', err.message);
            console.error(err.stack);
            process.exit(1);
        });
    };
    
    /**
    * Log an exception that you catched through try and catch block and handled yourself and just want to report it to server
    * @param {Object} err - error exception object provided in catch block
    * @param {string=} segments - additional key value pairs you want to provide with error report, like versions of libraries used, etc.
    **/
    Countly.log_error = function(err, segments){
        recordError(err, true, segments);
    };
    
    /**
    * Add new line in the log of breadcrumbs of what was done did, will be included together with error report
    * @param {string} record - any text describing an action
    **/
    Countly.add_log = function(record){
        crashLogs.push(record);
    };
    
    /**
    * Stop tracking duration time for this user/device
    **/
    Countly.stop_time = function(){
        trackTime = false;
        storedDuration = getTimestamp() - lastBeat;
        lastViewStoredDuration = getTimestamp() - lastViewTime;
    };
    
    /**
    * Start tracking duration time for this user/device, by default it is automatically if you scalled (@link begin_session)
    **/
    Countly.start_time = function(){
        trackTime = true;
        lastBeat = getTimestamp() - storedDuration;
        lastViewTime = getTimestamp() - lastViewStoredDuration;
        lastViewStoredDuration = 0;
    };
    
    /**
    * Track which parts of application user visits
    * @param {string=} name - optional name of the view
    **/
    Countly.track_view = function(name){
        reportViewDuration();
        if(name){
            lastView = name;
            lastViewTime = getTimestamp();
            if(!platform)
                getMetrics();
            var segments = {
                "name": name,
                "visit":1,
                "segment":platform
            };
            
            //track pageview
            Countly.add_event({
                "key": "[CLY]_view",
                "segmentation": segments
            });
        }
    };
    
    /**
    * Track which parts of application user visits. Alias of {@link track_view} method for compatability with Web SDK
    * @param {string=} name - optional name of the view
    **/
    Countly.track_pageview = function(name){
        Countly.track_view(name);
    };
    
    /**
    * Make raw request with provided parameters
    * @example Countly.request({app_key:"somekey", devide_id:"someid", events:"[{'key':'val','count':1}]", begin_session:1});
    * @param {Object} request - object with key/values which will be used as request parameters
    **/
    Countly.request = function(request){
        if(!request.app_key || !request.device_id){
            log("app_key or device_id is missing");
            return;
        }
        if(cluster.isMaster){
            requestQueue.push(request);
            storeSet("cly_queue", requestQueue);
        }
        else{
            process.send({ cly: {request: request} });
        }
    };
    
	/**
	*  PRIVATE METHODS
	**/
    
    function reportViewDuration(){
        if(lastView){
            if(!platform)
                getMetrics();
            var segments = {
                "name": lastView,
                "segment":platform
            };

            //track pageview
            Countly.add_event({
                "key": "[CLY]_view",
                "dur": getTimestamp() - lastViewTime,
                "segmentation": segments
            });
            lastView = null;
        }
    }
	
	//insert request to queue
	function toRequestQueue(request){
        if(cluster.isMaster){
            if(!Countly.app_key || !Countly.device_id){
                log("app_key or device_id is missing");
                return;
            }
            request.app_key = Countly.app_key;
            request.device_id = Countly.device_id;
            request.sdk_name = SDK_NAME;
            request.sdk_version = SDK_VERSION;
            
            if(Countly.country_code)
                request.country_code = Countly.country_code;
            
            if(Countly.city)
                request.city = Countly.city;
            
            if(Countly.ip_address !== null)
                request.ip_address = Countly.ip_address;
                
            request.timestamp = getMsTimestamp();
            var date = new Date();
            request.hour = date.getHours();
            request.dow = date.getDay();
		  
            if(requestQueue.length > queueSize)
                requestQueue.shift();

            requestQueue.push(request);
            storeSet("cly_queue", requestQueue);
        }
        else{
            process.send({ cly: {cly_queue: request} });
        }
	}
	
	//heart beat
	function heartBeat(){
		
		//extend session if needed
		if(sessionStarted && autoExtend && trackTime){
			var last = getTimestamp();
			if(last - lastBeat > sessionUpdate){
				Countly.session_duration(last - lastBeat);
				lastBeat = last;
			}
		}
		
		//process event queue
		if(eventQueue.length > 0){
			if(eventQueue.length <= maxEventBatch){
				toRequestQueue({events: JSON.stringify(eventQueue)});
				eventQueue = [];
			}
			else{
				var events = eventQueue.splice(0, maxEventBatch);
				toRequestQueue({events: JSON.stringify(events)});
			}
            storeSet("cly_event", eventQueue);
		}
		
		//process request queue with event queue
		if(requestQueue.length > 0 && readyToProcess && getTimestamp() > failTimeout){
            readyToProcess = false;
            var params = requestQueue.shift();
            log("Processing request", params);
            makeRequest(params, function(err, params){
                log("Request Finished", params, err);
                if(err){
                    requestQueue.unshift(params);
                    failTimeout = getTimestamp() + failTimeoutAmount;
                }
                storeSet("cly_queue", requestQueue);
                readyToProcess = true;
            });
		}
		
		setTimeout(heartBeat, beatInterval);
	}
	
	//get ID
	function getId(){
		return storeGet("cly_id", null) || generateUUID();
	}
	
	//generate UUID
	function generateUUID() {
		var d = new Date().getTime();
		var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
			var r = (d + Math.random()*16)%16 | 0;
			d = Math.floor(d/16);
			return (c=='x' ? r : (r&0x3|0x8)).toString(16);
		});
		return uuid;
	}
	
	//get metrics of the browser
	function getMetrics(){
		var m = JSON.parse(JSON.stringify(metrics));
		
		//getting app version
		m._app_version = Countly.app_version;
        
        m._os = os.type();
        m._os_version = os.release();
        platform = os.type();
		
		log("Got metrics", m);
		return m;
	}
	
	//log stuff
	function log(){
		if(Countly.debug && typeof console !== "undefined"){
            if(arguments[1] && typeof arguments[1] == "object")
                arguments[1] = JSON.stringify(arguments[1]);
			console.log( Array.prototype.slice.call(arguments).join("\n") );
        }
	}
	
	//get current timestamp
	function getTimestamp(){
		return Math.floor(new Date().getTime() / 1000);
	}
    
    //get unique timestamp in miliseconds
    function getMsTimestamp(){
        var ts = new Date().getTime();
        if(lastMsTs >= ts)
            lastMsTs++;
        else
            lastMsTs = ts;
        return lastMsTs;
    }
    
    function recordError(err, nonfatal, segments){
        if(err){
            segments = segments || crashSegments;
            var error = "";
            if(typeof err === "object"){
                if(typeof err.stack !== "undefined")
                    error = err.stack;
                else{
                    if(typeof err.name !== "undefined")
                        error += err.name+":";
                    if(typeof err.message !== "undefined")
                        error += err.message+"\n";
                    if(typeof err.fileName !== "undefined")
                        error += "in "+err.fileName+"\n";
                    if(typeof err.lineNumber !== "undefined")
                        error += "on "+err.lineNumber;
                    if(typeof err.columnNumber !== "undefined")
                        error += ":"+err.columnNumber;
                }
            }
            else{
                error = err+"";
            }
            nonfatal = (nonfatal) ? true : false;
            var metrics = getMetrics();
            var ob = {_os:metrics._os, _os_version:metrics._os_version, _error:error, _app_version:metrics._app_version, _run:getTimestamp()-startTime};
            
            ob._not_os_specific = true;
            
            if(crashLogs.length > 0)
                ob._logs = crashLogs.join("\n");
            crashLogs = [];
            ob._nonfatal = nonfatal;
            
            if(typeof segments !== "undefined")
                ob._custom = segments;
            
            toRequestQueue({crash: JSON.stringify(ob)});
        }
    }
	
	//sending HTTP request
	function makeRequest(params, callback) {
        try {
            log("Sending HTTP request");
            var serverOptions = parseUrl(Countly.url);
            var data = prepareParams(params);
            var method = "GET";
            var options = {
                host: serverOptions.host,
                port: serverOptions.port,
                path: apiPath+"?"+data,
                method: 'GET'
            };
            
            if(data.length >= 2000)
                method = "POST";
            else if(Countly.force_post)
                method = "POST";
            
            if(method == "POST"){
                options.method = "POST";
                options.path = apiPath;
                options.headers = {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'Content-Length': Buffer.byteLength(data)
                };
            }
            var protocol = http;
            if(Countly.url.indexOf("https") === 0)
                protocol = https;
            var req = protocol.request(options, function(res) {
                var str = '';
                res.on('data', function (chunk) {
                    str += chunk;
                });
            
                res.on('end', function () {
                    try{
                        str = JSON.parse(str);
                    }
                    catch(ex){
                        str = {};
                    }
                    if(res.statusCode >= 200 && res.statusCode < 300 && str.result == "Success"){
                        callback(false, params);
                    }
                    else{
                        callback(true, params);
                    }
                });
            });
            if(method == "POST"){
                // write data to request body
                req.write(data);
            }
            req.end();
        } catch (e) {
            // fallback
			log("Failed HTTP request", e);
            if (typeof callback === 'function') { callback(true, params); }
        }
    }
	
	//convert JSON object to query params
	function prepareParams(params){
		var str = [];
		for(var i in params){
			str.push(i+"="+encodeURIComponent(params[i]));
		}
		return str.join("&");
	}
	
	//removing trailing slashes
	function stripTrailingSlash(str) {
		if(str.substr(str.length - 1) == '/') {
			return str.substr(0, str.length - 1);
		}
		return str;
	}
    
    //parsing host and port information from url
	function parseUrl(url) {
		var serverOptions = {
			host: 'localhost',
			port: 80
		};
        if(Countly.url.indexOf("https") === 0)
            serverOptions.port = 443;
		var host = url.split("://").pop();
        serverOptions.host = host;
		var lastPos = host.indexOf(":");
		if (lastPos > -1) {
            serverOptions.host = host.slice(0,lastPos);
			serverOptions.port = Number(host.slice(lastPos+1,host.length));
		}
		return serverOptions;
	}
	
	//retrieve only specific properties from object
	function getProperties(orig, props){
		var ob = {};
		var prop;
		for(var i = 0; i < props.length; i++){
			prop = props[i];
			if(typeof orig[prop] !== "undefined")
				ob[prop] = orig[prop];
		}
		return ob;
	}
    
    function handleWorkerMessage(msg){
        if(msg.cly){
            if(msg.cly.cly_queue){
                toRequestQueue(msg.cly.cly_queue);
            }
            else if(msg.cly.change_id){
                Countly.change_id(msg.cly.change_id, msg.cly.merge);
            }
            else if(msg.cly.event){
                Countly.add_event(msg.cly.event);
            }
            else if(msg.cly.request){
                Countly.request(msg.cly.request);
            }
        }
    }
    
    var __data = {};
    
    var readFile = function(key){
        var dir = path.resolve(__dirname, filePath+'__'+key+'.json');
        
        //try reading data file
        var data;
        try{
            data = fs.readFileSync(dir);
        } catch (ex) {
            //ther was no file, probably new init
            data = null;
        }
    
        try{
            //trying to parse json string
            data = JSON.parse(data);
        } catch (ex) {
            //problem parsing, corrupted file?
            console.log(ex.stack);
            //backup corrupted file data
            fs.writeFile(path.resolve(__dirname, filePath+"__"+key+"."+getTimestamp()+Math.random()+".json"), data, function(){});
            //start with new clean object
            data = null;
        }
        return data;
    };
    
    var forceStore = function(){
        for(var i in __data){
            var dir = path.resolve(__dirname, filePath+'__'+i+'.json');
            var ob = {};
            ob[i] = __data[i];
            fs.writeFileSync(dir, JSON.stringify(ob));
        }
    };
	
	var storeSet = function(key, value) {
		__data[key] = value;
        var dir = path.resolve(__dirname, filePath+'__'+key+'.json');
        var ob = {};
        ob[key] = value;
        fs.writeFile(dir, JSON.stringify(ob), function (err) {
            if(err) {
                return console.log(err);
            }
        });
	};
    
    
    var storeGet = function(key, def) {
        if(typeof __data[key] == "undefined"){
            var ob = readFile(key);
            if(!ob)
                __data[key] = def;
            else
                __data[key] = ob[key];
        }
        return __data[key];
	};
})(Countly);

module.exports = Countly;