Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

index.js 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. 'use strict';
  2. const path = require('path');
  3. const childProcess = require('child_process');
  4. const osName = require('os-name');
  5. const Conf = require('conf');
  6. const chalk = require('chalk');
  7. const debounce = require('lodash.debounce');
  8. const inquirer = require('inquirer');
  9. const uuid = require('uuid');
  10. const providers = require('./providers');
  11. const DEBOUNCE_MS = 100;
  12. class Insight {
  13. constructor(options) {
  14. options = options || {};
  15. options.pkg = options.pkg || {};
  16. // Deprecated options
  17. // TODO: Remove these at some point in the future
  18. if (options.packageName) {
  19. options.pkg.name = options.packageName;
  20. }
  21. if (options.packageVersion) {
  22. options.pkg.version = options.packageVersion;
  23. }
  24. if (!options.trackingCode || !options.pkg.name) {
  25. throw new Error('trackingCode and pkg.name required');
  26. }
  27. this.trackingCode = options.trackingCode;
  28. this.trackingProvider = options.trackingProvider || 'google';
  29. this.packageName = options.pkg.name;
  30. this.packageVersion = options.pkg.version || 'undefined';
  31. this.os = osName();
  32. this.nodeVersion = process.version;
  33. this.appVersion = this.packageVersion;
  34. this.config = options.config || new Conf({
  35. configName: `insight-${this.packageName}`,
  36. defaults: {
  37. clientId: options.clientId || Math.floor(Date.now() * Math.random())
  38. }
  39. });
  40. this._queue = {};
  41. this._permissionTimeout = 30;
  42. this._debouncedSend = debounce(this._send, DEBOUNCE_MS, {leading: true});
  43. }
  44. get optOut() {
  45. return this.config.get('optOut');
  46. }
  47. set optOut(val) {
  48. this.config.set('optOut', val);
  49. }
  50. get clientId() {
  51. return this.config.get('clientId');
  52. }
  53. set clientId(val) {
  54. this.config.set('clientId', val);
  55. }
  56. _save() {
  57. setImmediate(() => {
  58. this._debouncedSend();
  59. });
  60. }
  61. _send() {
  62. const pending = Object.keys(this._queue).length;
  63. if (pending === 0) {
  64. return;
  65. }
  66. this._fork(this._getPayload());
  67. this._queue = {};
  68. }
  69. _fork(payload) {
  70. // Extracted to a method so it can be easily mocked
  71. const cp = childProcess.fork(path.join(__dirname, 'push.js'), {silent: true});
  72. cp.send(payload);
  73. cp.unref();
  74. cp.disconnect();
  75. }
  76. _getPayload() {
  77. return {
  78. queue: Object.assign({}, this._queue),
  79. packageName: this.packageName,
  80. packageVersion: this.packageVersion,
  81. trackingCode: this.trackingCode,
  82. trackingProvider: this.trackingProvider
  83. };
  84. }
  85. _getRequestObj(...args) {
  86. return providers[this.trackingProvider].apply(this, args);
  87. }
  88. track(...args) {
  89. if (this.optOut) {
  90. return;
  91. }
  92. const path = '/' + args.map(el => String(el).trim().replace(/ /, '-')).join('/');
  93. // Timestamp isn't unique enough since it can end up with duplicate entries
  94. this._queue[`${Date.now()} ${uuid.v4()}`] = {
  95. path,
  96. type: 'pageview'
  97. };
  98. this._save();
  99. }
  100. trackEvent(options) {
  101. if (this.optOut) {
  102. return;
  103. }
  104. if (this.trackingProvider !== 'google') {
  105. throw new Error('Event tracking is supported only for Google Analytics');
  106. }
  107. if (!options || !options.category || !options.action) {
  108. throw new Error('`category` and `action` required');
  109. }
  110. // Timestamp isn't unique enough since it can end up with duplicate entries
  111. this._queue[`${Date.now()} ${uuid.v4()}`] = {
  112. category: options.category,
  113. action: options.action,
  114. label: options.label,
  115. value: options.value,
  116. type: 'event'
  117. };
  118. this._save();
  119. }
  120. askPermission(msg, cb) {
  121. const defaultMsg = `May ${chalk.cyan(this.packageName)} anonymously report usage statistics to improve the tool over time?`;
  122. cb = cb || (() => {});
  123. if (!process.stdout.isTTY || process.argv.indexOf('--no-insight') !== -1 || process.env.CI) {
  124. setImmediate(cb, null, false);
  125. return;
  126. }
  127. const prompt = inquirer.prompt({
  128. type: 'confirm',
  129. name: 'optIn',
  130. message: msg || defaultMsg,
  131. default: true
  132. });
  133. // Add a 30 sec timeout before giving up on getting an answer
  134. const permissionTimeout = setTimeout(() => {
  135. // Stop listening for stdin
  136. prompt.ui.close();
  137. // Automatically opt out
  138. this.optOut = true;
  139. cb(null, false);
  140. }, this._permissionTimeout * 1000);
  141. prompt.then(result => {
  142. // Clear the permission timeout upon getting an answer
  143. clearTimeout(permissionTimeout);
  144. this.optOut = !result.optIn;
  145. cb(null, result.optIn);
  146. });
  147. }
  148. }
  149. module.exports = Insight;