"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
var _chalk = _interopRequireDefault(require("next/dist/compiled/chalk"));
var _conf = _interopRequireDefault(require("next/dist/compiled/conf"));
var _crypto = require("crypto");
var _isDocker = _interopRequireDefault(require("next/dist/compiled/is-docker"));
var _path = _interopRequireDefault(require("path"));
var _anonymousMeta = require("./anonymous-meta");
var ciEnvironment = _interopRequireWildcard(require("./ci-info"));
var _postPayload = require("./post-payload");
var _projectId = require("./project-id");
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache() {
    if (typeof WeakMap !== "function") return null;
    var cache = new WeakMap();
    _getRequireWildcardCache = function() {
        return cache;
    };
    return cache;
}
function _interopRequireWildcard(obj) {
    if (obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache();
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {};
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (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`;
class Telemetry {
    constructor({ distDir  }){
        // 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;
        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.rawProjectId = (0, _projectId).getRawProjectId();
        this.queue = new Set();
        this.notify();
    }
    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(`${_chalk.default.magenta.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(_chalk.default.cyan("https://nextjs.org/telemetry"));
        console.log();
    };
    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;
    }
    setEnabled = (_enabled)=>{
        const enabled = !!_enabled;
        this.conf && this.conf.set(TELEMETRY_KEY_ENABLED, enabled);
        return this.conf && this.conf.path;
    };
    get isEnabled() {
        return !!this.conf && this.conf.get(TELEMETRY_KEY_ENABLED, true) !== false;
    }
    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");
    };
    get projectId() {
        return this.oneWayHash(this.rawProjectId);
    }
    record = (_events)=>{
        const _this = this;
        // pseudo try-catch
        async function wrapper() {
            return await _this.submitRecord(_events);
        }
        const prom = wrapper().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
            this.queue.delete(prom);
            return res;
        });
        // Track this `Promise` so we can flush pending events
        this.queue.add(prom);
        return prom;
    };
    flush = async ()=>Promise.all(this.queue).catch(()=>null);
    submitRecord = (_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: this.projectId,
            sessionId: this.sessionId
        };
        const meta = (0, _anonymousMeta).getAnonymousMeta();
        return (0, _postPayload)._postPayload(`https://telemetry.nextjs.org/api/v1/record`, {
            context,
            meta,
            events: events.map(({ eventName , payload  })=>({
                    eventName,
                    fields: payload
                }))
        });
    };
}
exports.Telemetry = Telemetry;
function getStorageDirectory(distDir) {
    const isLikelyEphemeral = ciEnvironment.isCI || (0, _isDocker).default();
    if (isLikelyEphemeral) {
        return _path.default.join(distDir, "cache");
    }
    return undefined;
}

//# sourceMappingURL=storage.js.map