UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

331 lines (330 loc) • 14.2 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalyticsService = void 0; const path = require("path"); const _ = require("lodash"); const decorators_1 = require("../../common/decorators"); const helpers_1 = require("../../common/helpers"); const constants_1 = require("../../common/constants"); const yok_1 = require("../../common/yok"); class AnalyticsService { constructor($logger, $options, $staticConfig, $prompter, $userSettingsService, $analyticsSettingsService, $childProcess, $projectDataService, $mobileHelper, $projectHelper) { this.$logger = $logger; this.$options = $options; this.$staticConfig = $staticConfig; this.$prompter = $prompter; this.$userSettingsService = $userSettingsService; this.$analyticsSettingsService = $analyticsSettingsService; this.$childProcess = $childProcess; this.$projectDataService = $projectDataService; this.$mobileHelper = $mobileHelper; this.$projectHelper = $projectHelper; this.shouldDisposeInstance = true; this.analyticsStatuses = {}; } setShouldDispose(shouldDispose) { this.shouldDisposeInstance = shouldDispose; } async checkConsent() { if (await this.$analyticsSettingsService.canDoRequest()) { const initialTrackFeatureUsageStatus = await this.getStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME); let trackFeatureUsage = initialTrackFeatureUsageStatus === "enabled"; if ((await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME)) && (0, helpers_1.isInteractive)()) { const message = `Do you want to help us improve ${this.$analyticsSettingsService.getClientName()} by automatically sending anonymous usage statistics? We will not use this information to identify or contact you.`; trackFeatureUsage = await this.$prompter.confirm(message, () => true); await this.setStatus(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, trackFeatureUsage); await this.trackAcceptFeatureUsage({ acceptTrackFeatureUsage: trackFeatureUsage, }); } const isErrorReportingUnset = await this.isNotConfirmed(this.$staticConfig.ERROR_REPORT_SETTING_NAME); const isUsageReportingConfirmed = !(await this.isNotConfirmed(this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME)); if (isErrorReportingUnset && isUsageReportingConfirmed) { await this.setStatus(this.$staticConfig.ERROR_REPORT_SETTING_NAME, trackFeatureUsage); } } } async setStatus(settingName, enabled) { this.analyticsStatuses[settingName] = enabled ? "enabled" : "disabled"; await this.$userSettingsService.saveSetting(settingName, enabled.toString()); } async isEnabled(settingName) { const analyticsStatus = await this.getStatus(settingName); return analyticsStatus === "enabled"; } getStatusMessage(settingName, jsonFormat, readableSettingName) { if (jsonFormat) { return this.getJsonStatusMessage(settingName); } return this.getHumanReadableStatusMessage(settingName, readableSettingName); } async trackAcceptFeatureUsage(settings) { const acceptTracking = !!(settings && settings.acceptTrackFeatureUsage); const googleAnalyticsEventData = { googleAnalyticsDataType: "event", action: "Accept Tracking", label: acceptTracking.toString(), }; await this.forcefullyTrackInGoogleAnalytics(googleAnalyticsEventData); } async trackInGoogleAnalytics(gaSettings) { await this.initAnalyticsStatuses(); if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME] === "enabled") { return this.forcefullyTrackInGoogleAnalytics(gaSettings); } } async trackEventActionInGoogleAnalytics(data) { const device = data.device; const platform = device ? device.deviceInfo.platform : data.platform; const normalizedPlatform = platform ? this.$mobileHelper.normalizePlatformName(platform) : platform; const isForDevice = device ? !device.isEmulator : data.isForDevice; let label = ""; label = this.addDataToLabel(label, normalizedPlatform); if (isForDevice !== null && isForDevice !== undefined) { const deviceType = isForDevice ? constants_1.DeviceTypes.Device : this.$mobileHelper.isAndroidPlatform(platform) ? constants_1.DeviceTypes.Emulator : constants_1.DeviceTypes.Simulator; label = this.addDataToLabel(label, deviceType); } if (device) { label = this.addDataToLabel(label, device.deviceInfo.version); } if (data.additionalData) { label = this.addDataToLabel(label, data.additionalData); } const customDimensions = {}; this.setProjectRelatedCustomDimensions(customDimensions, data.projectDir); const googleAnalyticsEventData = { googleAnalyticsDataType: "event", action: data.action, label, customDimensions, value: data.value, }; await this.trackInGoogleAnalytics(googleAnalyticsEventData); } async finishTracking() { return new Promise((resolve, reject) => { if (this.brokerProcess && this.brokerProcess.connected) { let timer; const handler = (data) => { if (data === "ProcessFinishedTasks") { this.brokerProcess.removeListener("message", handler); clearTimeout(timer); resolve(); } }; timer = setTimeout(() => { this.brokerProcess.removeListener("message", handler); resolve(); }, 3000); this.brokerProcess.on("message", handler); const msg = { type: "FinishTracking" }; this.brokerProcess.send(msg, (err) => this.$logger.trace(`Error while sending ${JSON.stringify(msg)}`)); } else { resolve(); } }); } forcefullyTrackInGoogleAnalytics(gaSettings) { gaSettings.customDimensions = gaSettings.customDimensions || {}; gaSettings.customDimensions["cd5"] = this.$options.analyticsClient || ((0, helpers_1.isInteractive)() ? "CLI" : "Unknown"); this.setProjectRelatedCustomDimensions(gaSettings.customDimensions); const googleAnalyticsData = _.merge({ type: "googleAnalyticsData", category: "CLI", }, gaSettings); this.$logger.trace("Will send the following information to Google Analytics:", googleAnalyticsData); return this.sendMessageToBroker(googleAnalyticsData); } setProjectRelatedCustomDimensions(customDimensions, projectDir) { if (!projectDir) { try { projectDir = this.$projectHelper.projectDir; } catch (err) { this.$logger.trace("Unable to get the projectDir from projectHelper", err); } } if (projectDir) { const projectData = this.$projectDataService.getProjectData(projectDir); customDimensions["cd2"] = projectData.projectType; customDimensions["cd9"] = projectData.isShared.toString(); } return customDimensions; } dispose() { if (this.brokerProcess && this.shouldDisposeInstance) { this.brokerProcess.disconnect(); } } addDataToLabel(label, newData) { if (newData && label) { return `${label}_${newData}`; } return label || newData || ""; } getAnalyticsBroker() { return new Promise((resolve, reject) => { const brokerProcessArgs = this.getBrokerProcessArgs(); const broker = this.$childProcess.spawn(process.execPath, brokerProcessArgs, { stdio: ["ignore", "ignore", "ignore", "ipc"], detached: true, }); broker.unref(); let isSettled = false; const timeoutId = setTimeout(() => { if (!isSettled) { reject(new Error("Unable to start Analytics Broker process.")); } }, AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT); broker.on("error", (err) => { clearTimeout(timeoutId); if (!isSettled) { isSettled = true; reject(err); } }); broker.on("message", (data) => { if (data === "ProcessReadyToReceive") { clearTimeout(timeoutId); if (!isSettled) { isSettled = true; this.brokerProcess = broker; resolve(broker); } } }); }); } getBrokerProcessArgs() { const brokerProcessArgs = [ path.join(__dirname, "analytics-broker-process.js"), this.$staticConfig.PATH_TO_BOOTSTRAP, ]; if (this.$options.analyticsLogFile) { brokerProcessArgs.push(this.$options.analyticsLogFile); } return brokerProcessArgs; } async sendInfoForTracking(trackingInfo, settingName) { await this.initAnalyticsStatuses(); if (!this.$staticConfig.disableAnalytics && this.analyticsStatuses[settingName] === "enabled") { return this.sendMessageToBroker(trackingInfo); } } async sendMessageToBroker(message) { let broker; try { broker = await this.getAnalyticsBroker(); } catch (err) { this.$logger.trace("Unable to get broker instance due to error: ", err); return; } return new Promise((resolve, reject) => { if (broker && broker.connected) { try { broker.send(message, (error) => resolve()); } catch (err) { this.$logger.trace("Error while trying to send message to broker:", err); resolve(); } } else { this.$logger.trace("Broker not found or not connected."); resolve(); } }); } async initAnalyticsStatuses() { if (await this.$analyticsSettingsService.canDoRequest()) { this.$logger.trace("Initializing analytics statuses."); const settingsNames = [ this.$staticConfig.TRACK_FEATURE_USAGE_SETTING_NAME, this.$staticConfig.ERROR_REPORT_SETTING_NAME, ]; for (const settingName of settingsNames) { await this.getStatus(settingName); } this.$logger.trace("Analytics statuses: ", this.analyticsStatuses); } } async getStatus(settingName) { if (!_.has(this.analyticsStatuses, settingName)) { const settingValue = await this.$userSettingsService.getSettingValue(settingName); if (settingValue) { const isEnabled = (0, helpers_1.toBoolean)(settingValue); if (isEnabled) { this.analyticsStatuses[settingName] = "enabled"; } else { this.analyticsStatuses[settingName] = "disabled"; } } else { this.analyticsStatuses[settingName] = "not confirmed"; } } return this.analyticsStatuses[settingName]; } async isNotConfirmed(settingName) { const analyticsStatus = await this.getStatus(settingName); return analyticsStatus === "not confirmed"; } async getHumanReadableStatusMessage(settingName, readableSettingName) { let status = null; if (await this.isNotConfirmed(settingName)) { status = "disabled until confirmed"; } else { status = await this.getStatus(settingName); } return `${readableSettingName} is ${status}.`; } async getJsonStatusMessage(settingName) { const status = await this.getStatus(settingName); const enabled = status === "not confirmed" ? null : status === "enabled"; return JSON.stringify({ enabled }); } trackException(exception, message) { const data = { type: "exception", exception, message, }; return this.sendInfoForTracking(data, this.$staticConfig.ERROR_REPORT_SETTING_NAME); } } exports.AnalyticsService = AnalyticsService; AnalyticsService.ANALYTICS_BROKER_START_TIMEOUT = 10 * 1000; __decorate([ (0, decorators_1.cache)() ], AnalyticsService.prototype, "getAnalyticsBroker", null); __decorate([ (0, decorators_1.cache)() ], AnalyticsService.prototype, "initAnalyticsStatuses", null); yok_1.injector.register("analyticsService", AnalyticsService);