storage.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. var _chalk = _interopRequireDefault(require("next/dist/compiled/chalk"));
  6. var _conf = _interopRequireDefault(require("next/dist/compiled/conf"));
  7. var _crypto = require("crypto");
  8. var _isDocker = _interopRequireDefault(require("next/dist/compiled/is-docker"));
  9. var _path = _interopRequireDefault(require("path"));
  10. var _anonymousMeta = require("./anonymous-meta");
  11. var ciEnvironment = _interopRequireWildcard(require("./ci-info"));
  12. var _postPayload = require("./post-payload");
  13. var _projectId = require("./project-id");
  14. function _interopRequireDefault(obj) {
  15. return obj && obj.__esModule ? obj : {
  16. default: obj
  17. };
  18. }
  19. function _getRequireWildcardCache() {
  20. if (typeof WeakMap !== "function") return null;
  21. var cache = new WeakMap();
  22. _getRequireWildcardCache = function() {
  23. return cache;
  24. };
  25. return cache;
  26. }
  27. function _interopRequireWildcard(obj) {
  28. if (obj && obj.__esModule) {
  29. return obj;
  30. }
  31. if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
  32. return {
  33. default: obj
  34. };
  35. }
  36. var cache = _getRequireWildcardCache();
  37. if (cache && cache.has(obj)) {
  38. return cache.get(obj);
  39. }
  40. var newObj = {};
  41. var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
  42. for(var key in obj){
  43. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  44. var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
  45. if (desc && (desc.get || desc.set)) {
  46. Object.defineProperty(newObj, key, desc);
  47. } else {
  48. newObj[key] = obj[key];
  49. }
  50. }
  51. }
  52. newObj.default = obj;
  53. if (cache) {
  54. cache.set(obj, newObj);
  55. }
  56. return newObj;
  57. }
  58. // This is the key that stores whether or not telemetry is enabled or disabled.
  59. const TELEMETRY_KEY_ENABLED = "telemetry.enabled";
  60. // This is the key that specifies when the user was informed about anonymous
  61. // telemetry collection.
  62. const TELEMETRY_KEY_NOTIFY_DATE = "telemetry.notifiedAt";
  63. // This is a quasi-persistent identifier used to dedupe recurring events. It's
  64. // generated from random data and completely anonymous.
  65. const TELEMETRY_KEY_ID = `telemetry.anonymousId`;
  66. // This is the cryptographic salt that is included within every hashed value.
  67. // This salt value is never sent to us, ensuring privacy and the one-way nature
  68. // of the hash (prevents dictionary lookups of pre-computed hashes).
  69. // See the `oneWayHash` function.
  70. const TELEMETRY_KEY_SALT = `telemetry.salt`;
  71. function getStorageDirectory(distDir) {
  72. const isLikelyEphemeral = ciEnvironment.isCI || (0, _isDocker).default();
  73. if (isLikelyEphemeral) {
  74. return _path.default.join(distDir, "cache");
  75. }
  76. return undefined;
  77. }
  78. class Telemetry {
  79. constructor({ distDir }){
  80. // Read in the constructor so that .env can be loaded before reading
  81. const { NEXT_TELEMETRY_DISABLED , NEXT_TELEMETRY_DEBUG } = process.env;
  82. this.NEXT_TELEMETRY_DISABLED = NEXT_TELEMETRY_DISABLED;
  83. this.NEXT_TELEMETRY_DEBUG = NEXT_TELEMETRY_DEBUG;
  84. const storageDirectory = getStorageDirectory(distDir);
  85. try {
  86. // `conf` incorrectly throws a permission error during initialization
  87. // instead of waiting for first use. We need to handle it, otherwise the
  88. // process may crash.
  89. this.conf = new _conf.default({
  90. projectName: "nextjs",
  91. cwd: storageDirectory
  92. });
  93. } catch (_) {
  94. this.conf = null;
  95. }
  96. this.sessionId = (0, _crypto).randomBytes(32).toString("hex");
  97. this.rawProjectId = (0, _projectId).getRawProjectId();
  98. this.queue = new Set();
  99. this.notify();
  100. }
  101. notify = ()=>{
  102. if (this.isDisabled || !this.conf) {
  103. return;
  104. }
  105. // The end-user has already been notified about our telemetry integration. We
  106. // don't need to constantly annoy them about it.
  107. // We will re-inform users about the telemetry if significant changes are
  108. // ever made.
  109. if (this.conf.get(TELEMETRY_KEY_NOTIFY_DATE, "")) {
  110. return;
  111. }
  112. this.conf.set(TELEMETRY_KEY_NOTIFY_DATE, Date.now().toString());
  113. console.log(`${_chalk.default.magenta.bold("Attention")}: Next.js now collects completely anonymous telemetry regarding usage.`);
  114. console.log(`This information is used to shape Next.js' roadmap and prioritize features.`);
  115. console.log(`You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:`);
  116. console.log(_chalk.default.cyan("https://nextjs.org/telemetry"));
  117. console.log();
  118. };
  119. get anonymousId() {
  120. const val = this.conf && this.conf.get(TELEMETRY_KEY_ID);
  121. if (val) {
  122. return val;
  123. }
  124. const generated = (0, _crypto).randomBytes(32).toString("hex");
  125. this.conf && this.conf.set(TELEMETRY_KEY_ID, generated);
  126. return generated;
  127. }
  128. get salt() {
  129. const val = this.conf && this.conf.get(TELEMETRY_KEY_SALT);
  130. if (val) {
  131. return val;
  132. }
  133. const generated = (0, _crypto).randomBytes(16).toString("hex");
  134. this.conf && this.conf.set(TELEMETRY_KEY_SALT, generated);
  135. return generated;
  136. }
  137. get isDisabled() {
  138. if (!!this.NEXT_TELEMETRY_DISABLED || !this.conf) {
  139. return true;
  140. }
  141. return this.conf.get(TELEMETRY_KEY_ENABLED, true) === false;
  142. }
  143. setEnabled = (_enabled)=>{
  144. const enabled = !!_enabled;
  145. this.conf && this.conf.set(TELEMETRY_KEY_ENABLED, enabled);
  146. return this.conf && this.conf.path;
  147. };
  148. get isEnabled() {
  149. return !!this.conf && this.conf.get(TELEMETRY_KEY_ENABLED, true) !== false;
  150. }
  151. oneWayHash = (payload)=>{
  152. const hash = (0, _crypto).createHash("sha256");
  153. // Always prepend the payload value with salt. This ensures the hash is truly
  154. // one-way.
  155. hash.update(this.salt);
  156. // Update is an append operation, not a replacement. The salt from the prior
  157. // update is still present!
  158. hash.update(payload);
  159. return hash.digest("hex");
  160. };
  161. get projectId() {
  162. return this.oneWayHash(this.rawProjectId);
  163. }
  164. record = (_events)=>{
  165. const _this = this;
  166. // pseudo try-catch
  167. async function wrapper() {
  168. return await _this.submitRecord(_events);
  169. }
  170. const prom = wrapper().then((value)=>({
  171. isFulfilled: true,
  172. isRejected: false,
  173. value
  174. })).catch((reason)=>({
  175. isFulfilled: false,
  176. isRejected: true,
  177. reason
  178. }))// Acts as `Promise#finally` because `catch` transforms the error
  179. .then((res)=>{
  180. // Clean up the event to prevent unbounded `Set` growth
  181. this.queue.delete(prom);
  182. return res;
  183. });
  184. // Track this `Promise` so we can flush pending events
  185. this.queue.add(prom);
  186. return prom;
  187. };
  188. flush = async ()=>Promise.all(this.queue).catch(()=>null);
  189. submitRecord = (_events)=>{
  190. let events;
  191. if (Array.isArray(_events)) {
  192. events = _events;
  193. } else {
  194. events = [
  195. _events
  196. ];
  197. }
  198. if (events.length < 1) {
  199. return Promise.resolve();
  200. }
  201. if (this.NEXT_TELEMETRY_DEBUG) {
  202. // Print to standard error to simplify selecting the output
  203. events.forEach(({ eventName , payload })=>console.error(`[telemetry] ` + JSON.stringify({
  204. eventName,
  205. payload
  206. }, null, 2)));
  207. // Do not send the telemetry data if debugging. Users may use this feature
  208. // to preview what data would be sent.
  209. return Promise.resolve();
  210. }
  211. // Skip recording telemetry if the feature is disabled
  212. if (this.isDisabled) {
  213. return Promise.resolve();
  214. }
  215. const context = {
  216. anonymousId: this.anonymousId,
  217. projectId: this.projectId,
  218. sessionId: this.sessionId
  219. };
  220. const meta = (0, _anonymousMeta).getAnonymousMeta();
  221. return (0, _postPayload)._postPayload(`https://telemetry.nextjs.org/api/v1/record`, {
  222. context,
  223. meta,
  224. events: events.map(({ eventName , payload })=>({
  225. eventName,
  226. fields: payload
  227. }))
  228. });
  229. };
  230. }
  231. exports.Telemetry = Telemetry;
  232. //# sourceMappingURL=storage.js.map