nativescript
Version:
Command-line interface for building NativeScript projects
331 lines (330 loc) • 14.2 kB
JavaScript
"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);