| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- var request = require("request");
- var uuid = require("uuid");
- var querystring = require("querystring");
- var utils = require("./utils");
- var config = require("./config");
- var url = require("url");
- var debug = require("debug")("universal-analytics");
- module.exports = init;
- function init (tid, cid, options) {
- return new Visitor(tid, cid, options);
- }
- var Visitor = module.exports.Visitor = function (tid, cid, options, context, persistentParams) {
- if (typeof tid === 'object') {
- options = tid;
- tid = cid = null;
- } else if (typeof cid === 'object') {
- options = cid;
- cid = null;
- }
- this._queue = [];
- this.options = options || {};
- if(this.options.hostname) {
- config.hostname = this.options.hostname;
- }
- if(this.options.path) {
- config.path = this.options.path;
- }
- if (this.options.http) {
- var parsedHostname = url.parse(config.hostname);
- config.hostname = 'http://' + parsedHostname.host;
- }
- if(this.options.enableBatching !== undefined) {
- config.batching = options.enableBatching;
- }
- if(this.options.batchSize) {
- config.batchSize = this.options.batchSize;
- }
- this._context = context || {};
- this._persistentParams = persistentParams || {};
- this.tid = tid || this.options.tid;
- this.cid = this._determineCid(cid, this.options.cid, (this.options.strictCidFormat !== false));
- if(this.options.uid) {
- this.uid = this.options.uid;
- }
- }
- module.exports.middleware = function (tid, options) {
- this.tid = tid;
- this.options = options;
- var cookieName = (this.options || {}).cookieName || "_ga";
- return function (req, res, next) {
- req.visitor = module.exports.createFromSession(req.session);
- if (req.visitor) return next();
- var cid;
- if (req.cookies && req.cookies[cookieName]) {
- var gaSplit = req.cookies[cookieName].split('.');
- cid = gaSplit[2] + "." + gaSplit[3];
- }
- req.visitor = init(tid, cid, options);
- if (req.session) {
- req.session.cid = req.visitor.cid;
- }
- next();
- }
- }
- module.exports.createFromSession = function (session) {
- if (session && session.cid) {
- return init(this.tid, session.cid, this.options);
- }
- }
- Visitor.prototype = {
- debug: function (d) {
- debug.enabled = arguments.length === 0 ? true : d;
- debug("visitor.debug() is deprecated: set DEBUG=universal-analytics to enable logging")
- return this;
- },
- reset: function () {
- this._context = null;
- return this;
- },
- set: function (key, value) {
- this._persistentParams = this._persistentParams || {};
- this._persistentParams[key] = value;
- },
- pageview: function (path, hostname, title, params, fn) {
- if (typeof path === 'object' && path != null) {
- params = path;
- if (typeof hostname === 'function') {
- fn = hostname
- }
- path = hostname = title = null;
- } else if (typeof hostname === 'function') {
- fn = hostname
- hostname = title = null;
- } else if (typeof title === 'function') {
- fn = title;
- title = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.dp = path || params.dp || this._context.dp;
- params.dh = hostname || params.dh || this._context.dh;
- params.dt = title || params.dt || this._context.dt;
- this._tidyParameters(params);
- if (!params.dp && !params.dl) {
- return this._handleError("Please provide either a page path (dp) or a document location (dl)", fn);
- }
- return this._withContext(params)._enqueue("pageview", params, fn);
- },
- screenview: function (screenName, appName, appVersion, appId, appInstallerId, params, fn) {
- if (typeof screenName === 'object' && screenName != null) {
- params = screenName;
- if (typeof appName === 'function') {
- fn = appName
- }
- screenName = appName = appVersion = appId = appInstallerId = null;
- } else if (typeof appName === 'function') {
- fn = appName
- appName = appVersion = appId = appInstallerId = null;
- } else if (typeof appVersion === 'function') {
- fn = appVersion;
- appVersion = appId = appInstallerId = null;
- } else if (typeof appId === 'function') {
- fn = appId;
- appId = appInstallerId = null;
- } else if (typeof appInstallerId === 'function') {
- fn = appInstallerId;
- appInstallerId = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.cd = screenName || params.cd || this._context.cd;
- params.an = appName || params.an || this._context.an;
- params.av = appVersion || params.av || this._context.av;
- params.aid = appId || params.aid || this._context.aid;
- params.aiid = appInstallerId || params.aiid || this._context.aiid;
- this._tidyParameters(params);
- if (!params.cd || !params.an) {
- return this._handleError("Please provide at least a screen name (cd) and an app name (an)", fn);
- }
- return this._withContext(params)._enqueue("screenview", params, fn);
- },
- event: function (category, action, label, value, params, fn) {
- if (typeof category === 'object' && category != null) {
- params = category;
- if (typeof action === 'function') {
- fn = action
- }
- category = action = label = value = null;
- } else if (typeof label === 'function') {
- fn = label;
- label = value = null;
- } else if (typeof value === 'function') {
- fn = value;
- value = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.ec = category || params.ec || this._context.ec;
- params.ea = action || params.ea || this._context.ea;
- params.el = label || params.el || this._context.el;
- params.ev = value || params.ev || this._context.ev;
- params.p = params.p || params.dp || this._context.p || this._context.dp;
- delete params.dp;
- this._tidyParameters(params);
- if (!params.ec || !params.ea) {
- return this._handleError("Please provide at least an event category (ec) and an event action (ea)", fn);
- }
- return this._withContext(params)._enqueue("event", params, fn);
- },
- transaction: function (transaction, revenue, shipping, tax, affiliation, params, fn) {
- if (typeof transaction === 'object') {
- params = transaction;
- if (typeof revenue === 'function') {
- fn = revenue
- }
- transaction = revenue = shipping = tax = affiliation = null;
- } else if (typeof revenue === 'function') {
- fn = revenue;
- revenue = shipping = tax = affiliation = null;
- } else if (typeof shipping === 'function') {
- fn = shipping;
- shipping = tax = affiliation = null;
- } else if (typeof tax === 'function') {
- fn = tax;
- tax = affiliation = null;
- } else if (typeof affiliation === 'function') {
- fn = affiliation;
- affiliation = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.ti = transaction || params.ti || this._context.ti;
- params.tr = revenue || params.tr || this._context.tr;
- params.ts = shipping || params.ts || this._context.ts;
- params.tt = tax || params.tt || this._context.tt;
- params.ta = affiliation || params.ta || this._context.ta;
- params.p = params.p || this._context.p || this._context.dp;
- this._tidyParameters(params);
- if (!params.ti) {
- return this._handleError("Please provide at least a transaction ID (ti)", fn);
- }
- return this._withContext(params)._enqueue("transaction", params, fn);
- },
- item: function (price, quantity, sku, name, variation, params, fn) {
- if (typeof price === 'object') {
- params = price;
- if (typeof quantity === 'function') {
- fn = quantity
- }
- price = quantity = sku = name = variation = null;
- } else if (typeof quantity === 'function') {
- fn = quantity;
- quantity = sku = name = variation = null;
- } else if (typeof sku === 'function') {
- fn = sku;
- sku = name = variation = null;
- } else if (typeof name === 'function') {
- fn = name;
- name = variation = null;
- } else if (typeof variation === 'function') {
- fn = variation;
- variation = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.ip = price || params.ip || this._context.ip;
- params.iq = quantity || params.iq || this._context.iq;
- params.ic = sku || params.ic || this._context.ic;
- params.in = name || params.in || this._context.in;
- params.iv = variation || params.iv || this._context.iv;
- params.p = params.p || this._context.p || this._context.dp;
- params.ti = params.ti || this._context.ti;
- this._tidyParameters(params);
- if (!params.ti) {
- return this._handleError("Please provide at least an item transaction ID (ti)", fn);
- }
- return this._withContext(params)._enqueue("item", params, fn);
- },
- exception: function (description, fatal, params, fn) {
- if (typeof description === 'object') {
- params = description;
- if (typeof fatal === 'function') {
- fn = fatal;
- }
- description = fatal = null;
- } else if (typeof fatal === 'function') {
- fn = fatal;
- fatal = 0;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.exd = description || params.exd || this._context.exd;
- params.exf = +!!(fatal || params.exf || this._context.exf);
- if (params.exf === 0) {
- delete params.exf;
- }
- this._tidyParameters(params);
- return this._withContext(params)._enqueue("exception", params, fn);
- },
- timing: function (category, variable, time, label, params, fn) {
- if (typeof category === 'object') {
- params = category;
- if (typeof variable === 'function') {
- fn = variable;
- }
- category = variable = time = label = null;
- } else if (typeof variable === 'function') {
- fn = variable;
- variable = time = label = null;
- } else if (typeof time === 'function') {
- fn = time;
- time = label = null;
- } else if (typeof label === 'function') {
- fn = label;
- label = null;
- } else if (typeof params === 'function') {
- fn = params;
- params = null;
- }
- params = this._translateParams(params);
- params = Object.assign({}, this._persistentParams || {}, params);
- params.utc = category || params.utc || this._context.utc;
- params.utv = variable || params.utv || this._context.utv;
- params.utt = time || params.utt || this._context.utt;
- params.utl = label || params.utl || this._context.utl;
- this._tidyParameters(params);
- return this._withContext(params)._enqueue("timing", params, fn);
- },
- send: function (fn) {
- var self = this;
- var count = 1;
- var fn = fn || function () {};
- debug("Sending %d tracking call(s)", self._queue.length);
- var getBody = function(params) {
- return params.map(function(x) { return querystring.stringify(x); }).join("\n");
- }
- var onFinish = function (err) {
- debug("Finished sending tracking calls")
- fn.call(self, err || null, count - 1);
- }
- var iterator = function () {
- if (!self._queue.length) {
- return onFinish(null);
- }
- var params = [];
- if(config.batching) {
- params = self._queue.splice(0, Math.min(self._queue.length, config.batchSize));
- } else {
- params.push(self._queue.shift());
- }
- var useBatchPath = params.length > 1;
- var path = config.hostname + (useBatchPath ? config.batchPath :config.path);
- debug("%d: %o", count++, params);
- var options = Object.assign({}, self.options.requestOptions, {
- body: getBody(params),
- headers: self.options.headers || {}
- });
- request.post(path, options, nextIteration);
- }
- function nextIteration(err) {
- if (err) return onFinish(err);
- iterator();
- }
- iterator();
- },
- _enqueue: function (type, params, fn) {
- if (typeof params === 'function') {
- fn = params;
- params = {};
- }
- params = this._translateParams(params) || {};
- Object.assign(params, {
- v: config.protocolVersion,
- tid: this.tid,
- cid: this.cid,
- t: type
- });
- if(this.uid) {
- params.uid = this.uid;
- }
- this._queue.push(params);
- if (debug.enabled) {
- this._checkParameters(params);
- }
- debug("Enqueued %s (%o)", type, params);
- if (fn) {
- this.send(fn);
- }
- return this;
- },
- _handleError: function (message, fn) {
- debug("Error: %s", message)
- fn && fn.call(this, new Error(message))
- return this;
- },
- _determineCid: function () {
- var args = Array.prototype.splice.call(arguments, 0);
- var id;
- var lastItem = args.length-1;
- var strict = args[lastItem];
- if (strict) {
- for (var i = 0; i < lastItem; i++) {
- id = utils.ensureValidCid(args[i]);
- if (id !== false) return id;
- if (id != null) debug("Warning! Invalid UUID format '%s'", args[i]);
- }
- } else {
- for (var i = 0; i < lastItem; i++) {
- if (args[i]) return args[i];
- }
- }
- return uuid.v4();
- },
- _checkParameters: function (params) {
- for (var param in params) {
- if (config.acceptedParameters.indexOf(param) !== -1 || config.acceptedParametersRegex.filter(function (r) {
- return r.test(param);
- }).length) {
- continue;
- }
- debug("Warning! Unsupported tracking parameter %s (%s)", param, params[param]);
- }
- },
- _translateParams: function (params) {
- var translated = {};
- for (var key in params) {
- if (config.parametersMap.hasOwnProperty(key)) {
- translated[config.parametersMap[key]] = params[key];
- } else {
- translated[key] = params[key];
- }
- }
- return translated;
- },
- _tidyParameters: function (params) {
- for (var param in params) {
- if (params[param] === null || params[param] === undefined) {
- delete params[param];
- }
- }
- return params;
- },
- _withContext: function (context) {
- var visitor = new Visitor(this.tid, this.cid, this.options, context, this._persistentParams);
- visitor._queue = this._queue;
- return visitor;
- }
- }
- Visitor.prototype.pv = Visitor.prototype.pageview
- Visitor.prototype.e = Visitor.prototype.event
- Visitor.prototype.t = Visitor.prototype.transaction
- Visitor.prototype.i = Visitor.prototype.item
|