securityos/node_modules/next/dist/telemetry/storage.js

288 lines
12 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "Telemetry", {
enumerable: true,
get: function() {
return Telemetry;
}
});
const _picocolors = require("../lib/picocolors");
const _conf = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/conf"));
const _crypto = require("crypto");
const _isdocker = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/is-docker"));
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _anonymousmeta = require("./anonymous-meta");
const _ciinfo = /*#__PURE__*/ _interop_require_wildcard(require("./ci-info"));
const _postpayload = require("./post-payload");
const _projectid = require("./project-id");
const _ponyfill = require("next/dist/compiled/@edge-runtime/ponyfill");
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function(nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for(var key in obj){
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
// This is the key that stores whether or not telemetry is enabled or disabled.
const TELEMETRY_KEY_ENABLED = "telemetry.enabled";
// This is the key that specifies when the user was informed about anonymous
// telemetry collection.
const TELEMETRY_KEY_NOTIFY_DATE = "telemetry.notifiedAt";
// This is a quasi-persistent identifier used to dedupe recurring events. It's
// generated from random data and completely anonymous.
const TELEMETRY_KEY_ID = `telemetry.anonymousId`;
// This is the cryptographic salt that is included within every hashed value.
// This salt value is never sent to us, ensuring privacy and the one-way nature
// of the hash (prevents dictionary lookups of pre-computed hashes).
// See the `oneWayHash` function.
const TELEMETRY_KEY_SALT = `telemetry.salt`;
function getStorageDirectory(distDir) {
const isLikelyEphemeral = _ciinfo.isCI || (0, _isdocker.default)();
if (isLikelyEphemeral) {
return _path.default.join(distDir, "cache");
}
return undefined;
}
class Telemetry {
constructor({ distDir }){
this.notify = ()=>{
if (this.isDisabled || !this.conf) {
return;
}
// The end-user has already been notified about our telemetry integration. We
// don't need to constantly annoy them about it.
// We will re-inform users about the telemetry if significant changes are
// ever made.
if (this.conf.get(TELEMETRY_KEY_NOTIFY_DATE, "")) {
return;
}
this.conf.set(TELEMETRY_KEY_NOTIFY_DATE, Date.now().toString());
console.log(`${(0, _picocolors.magenta)((0, _picocolors.bold)("Attention"))}: Next.js now collects completely anonymous telemetry regarding usage.`);
console.log(`This information is used to shape Next.js' roadmap and prioritize features.`);
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:`);
console.log((0, _picocolors.cyan)("https://nextjs.org/telemetry"));
console.log();
};
this.setEnabled = (_enabled)=>{
const enabled = !!_enabled;
this.conf && this.conf.set(TELEMETRY_KEY_ENABLED, enabled);
return this.conf && this.conf.path;
};
this.oneWayHash = (payload)=>{
const hash = (0, _crypto.createHash)("sha256");
// Always prepend the payload value with salt. This ensures the hash is truly
// one-way.
hash.update(this.salt);
// Update is an append operation, not a replacement. The salt from the prior
// update is still present!
hash.update(payload);
return hash.digest("hex");
};
this.record = (_events, deferred)=>{
const prom = (deferred ? // flushDetached we can skip starting the initial
// submitRecord which will then be cancelled
new Promise((resolve)=>resolve({
isFulfilled: true,
isRejected: false,
value: _events
})) : this.submitRecord(_events)).then((value)=>({
isFulfilled: true,
isRejected: false,
value
})).catch((reason)=>({
isFulfilled: false,
isRejected: true,
reason
}))// Acts as `Promise#finally` because `catch` transforms the error
.then((res)=>{
// Clean up the event to prevent unbounded `Set` growth
if (!deferred) {
this.queue.delete(prom);
}
return res;
});
prom._events = Array.isArray(_events) ? _events : [
_events
];
prom._controller = prom._controller;
// Track this `Promise` so we can flush pending events
this.queue.add(prom);
return prom;
};
this.flush = async ()=>Promise.all(this.queue).catch(()=>null);
// writes current events to disk and spawns separate
// detached process to submit the records without blocking
// the main process from exiting
this.flushDetached = (mode, dir)=>{
const allEvents = [];
this.queue.forEach((item)=>{
try {
var _item__controller;
(_item__controller = item._controller) == null ? void 0 : _item__controller.abort();
allEvents.push(...item._events);
} catch (_) {
// if we fail to abort ignore this event
}
});
_fs.default.mkdirSync(this.distDir, {
recursive: true
});
_fs.default.writeFileSync(_path.default.join(this.distDir, "_events.json"), JSON.stringify(allEvents));
// Note: cross-spawn is not used here as it causes
// a new command window to appear when we don't want it to
const child_process = require("child_process");
// we use spawnSync when debugging to ensure logs are piped
// correctly to stdout/stderr
const spawn = this.NEXT_TELEMETRY_DEBUG ? child_process.spawnSync : child_process.spawn;
spawn(process.execPath, [
require.resolve("./detached-flush"),
mode,
dir
], {
detached: !this.NEXT_TELEMETRY_DEBUG,
windowsHide: true,
shell: false,
...this.NEXT_TELEMETRY_DEBUG ? {
stdio: "inherit"
} : {}
});
};
this.submitRecord = async (_events)=>{
let events;
if (Array.isArray(_events)) {
events = _events;
} else {
events = [
_events
];
}
if (events.length < 1) {
return Promise.resolve();
}
if (this.NEXT_TELEMETRY_DEBUG) {
// Print to standard error to simplify selecting the output
events.forEach(({ eventName, payload })=>console.error(`[telemetry] ` + JSON.stringify({
eventName,
payload
}, null, 2)));
// Do not send the telemetry data if debugging. Users may use this feature
// to preview what data would be sent.
return Promise.resolve();
}
// Skip recording telemetry if the feature is disabled
if (this.isDisabled) {
return Promise.resolve();
}
const context = {
anonymousId: this.anonymousId,
projectId: await this.getProjectId(),
sessionId: this.sessionId
};
const meta = (0, _anonymousmeta.getAnonymousMeta)();
const postController = new _ponyfill.AbortController();
const res = (0, _postpayload._postPayload)(`https://telemetry.nextjs.org/api/v1/record`, {
context,
meta,
events: events.map(({ eventName, payload })=>({
eventName,
fields: payload
}))
}, postController.signal);
res._controller = postController;
return res;
};
// Read in the constructor so that .env can be loaded before reading
const { NEXT_TELEMETRY_DISABLED, NEXT_TELEMETRY_DEBUG } = process.env;
this.NEXT_TELEMETRY_DISABLED = NEXT_TELEMETRY_DISABLED;
this.NEXT_TELEMETRY_DEBUG = NEXT_TELEMETRY_DEBUG;
this.distDir = distDir;
const storageDirectory = getStorageDirectory(distDir);
try {
// `conf` incorrectly throws a permission error during initialization
// instead of waiting for first use. We need to handle it, otherwise the
// process may crash.
this.conf = new _conf.default({
projectName: "nextjs",
cwd: storageDirectory
});
} catch (_) {
this.conf = null;
}
this.sessionId = (0, _crypto.randomBytes)(32).toString("hex");
this.queue = new Set();
this.notify();
}
get anonymousId() {
const val = this.conf && this.conf.get(TELEMETRY_KEY_ID);
if (val) {
return val;
}
const generated = (0, _crypto.randomBytes)(32).toString("hex");
this.conf && this.conf.set(TELEMETRY_KEY_ID, generated);
return generated;
}
get salt() {
const val = this.conf && this.conf.get(TELEMETRY_KEY_SALT);
if (val) {
return val;
}
const generated = (0, _crypto.randomBytes)(16).toString("hex");
this.conf && this.conf.set(TELEMETRY_KEY_SALT, generated);
return generated;
}
get isDisabled() {
if (!!this.NEXT_TELEMETRY_DISABLED || !this.conf) {
return true;
}
return this.conf.get(TELEMETRY_KEY_ENABLED, true) === false;
}
get isEnabled() {
return !this.NEXT_TELEMETRY_DISABLED && !!this.conf && this.conf.get(TELEMETRY_KEY_ENABLED, true) !== false;
}
async getProjectId() {
this.loadProjectId = this.loadProjectId || (0, _projectid.getRawProjectId)();
return this.oneWayHash(await this.loadProjectId);
}
}
//# sourceMappingURL=storage.js.map