UNPKG

hau

Version:

Easily track hourly daily, weekly, and monthly active users with Redis.

223 lines (185 loc) 5.84 kB
//----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- var util = require('./util') , moment = require('moment') , async = require('async') , _ = require('underscore'); //----------------------------------------------------------------------------- // Public //----------------------------------------------------------------------------- /** General Note: When using the active-user object, you do not need to pass in the client parameter. It is already filled in via underscore.js's #partial method. Example: var activity = activeUser.createClient(); activity.daily(function (err, num) { console.log(num); // Today's daily active users. }); **/ /** * Reports hourly active users for a given date. * * @param {Object} client The Redis client. * @param {String} [action] The tracked action. * @param {String} [date] The date of the action. Defaults to the current day. * @param {Function (err Error, num Number)} callback */ function hourly (client, action, date, callback) { if (arguments.length == 4) { date = moment.utc(date); } // Assume we are missing a date. if (arguments.length == 3) { callback = date; date = moment.utc(); } // We have only a client and a callback. if (arguments.length == 2) { callback = action; action = null; date = moment.utc(); } var key = util.keyFor(action, date); activeUsers(client, key, callback); } /** * Reports daily active users for a given date. * * @param {Object} client The Redis client. * @param {String} [action] The tracked action. * @param {String} [date] The date of the action. Defaults to the current day. * @param {Function (err Error, num Number)} callback */ function daily (client, action, date, callback) { if (arguments.length == 4) { date = moment.utc(date); } // Assume we are missing a date. if (arguments.length == 3) { callback = date; date = moment.utc(); } // We have only a client and a callback. if (arguments.length == 2) { callback = action; action = null; date = moment.utc(); } var keys = _.times(24, function (n) { var day = date.startOf('day').add('hours', n); return util.keyFor(action, day); }); activeUsers(client, keys, callback); } /** * Reports weekly active users for a given date. * * @param {Object} client The Redis client. * @param {String} [action] The tracked action. * @param {String} [date] The date of the action. Defaults to the current week. * @param {Function (err Error, num Number)} callback */ function weekly (client, action, date, callback) { if (arguments.length == 4) { date = moment.utc(date); } // Assume we are missing a date. if (arguments.length == 3) { callback = date; date = moment.utc(); } // We have only a client and a callback. if (arguments.length == 2) { callback = action; action = null; date = moment.utc(); } var keys = _.times(7*24, function (n) { var day = date.startOf('week').add('hours', n); return util.keyFor(action, day); }); activeUsers(client, keys, callback); } /** * Reports monthly active users for a given date. * * @param {Object} client The Redis client. * @param {String} [action] The tracked action. * @param {String} [date] The date of the action. Defaults to the current month. * @param {Function (err Error, num Number)} callback */ function monthly (client, action, date, callback) { if (arguments.length == 4) { date = moment.utc(date); } // Assume we are missing a date. if (arguments.length == 3) { callback = date; date = moment.utc(); } // We have only a client and a callback. if (arguments.length == 2) { callback = action; action = null; date = moment.utc(); } var keys = _.times(24*date.daysInMonth(), function (n) { var day = date.startOf('month').add('hours', n); return util.keyFor(action, day); }); activeUsers(client, keys, callback); } //----------------------------------------------------------------------------- // Private //----------------------------------------------------------------------------- /** * Determines the number of active users for a given set of active-user keys. * * @param {Object} client The Redis client. * @param {Array} keyOrKeys The Redis keys for the days to be fetched. * @param {Function (err Error, num Number)} callback */ function activeUsers (client, keyOrKeys, callback) { var keys = 'string' === typeof keyOrKeys ? [keyOrKeys] : keyOrKeys; async.map(keys, client.get.bind(client), function (err, results) { if (err) return callback(err); var binaryBigint = _.reduce(results, function (memo, buffer) { var dailyBigint; if (buffer) { const buffer64 = util.to64Bits(buffer); dailyBigint = buffer64.readBigInt64BE(); } else { dailyBigint = BigInt(0); } return dailyBigint | memo; }, BigInt(0)); callback(null, cardinality(binaryBigint)); }); } /** * Caculates the cardinality of a BitSet. * * @param {Object} binaryBigint A Bigint BitSet. * @returns {Number} The cardinality of the BitSet. */ function cardinality (binaryBigint) { var binaryString = binaryBigint.toString(2) , len = binaryString.length , cardinality = 0 for (var i = 0; i < len; i++) { if (binaryString.charAt(i) == '1') { cardinality++; } } return cardinality; } //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- exports.hourly = hourly; exports.daily = daily; exports.weekly = weekly; exports.monthly = monthly;