diff --git a/lib/gui/app/app.js b/lib/gui/app/app.js index fa37bf61..512ddb7d 100644 --- a/lib/gui/app/app.js +++ b/lib/gui/app/app.js @@ -33,6 +33,7 @@ const flashState = require('./models/flash-state') const settings = require('./models/settings') // eslint-disable-next-line node/no-missing-require const windowProgress = require('./os/window-progress') +// eslint-disable-next-line node/no-missing-require const analytics = require('./modules/analytics') const availableDrives = require('./models/available-drives') const driveScanner = require('./modules/drive-scanner') diff --git a/lib/gui/app/modules/analytics.js b/lib/gui/app/modules/analytics.js deleted file mode 100644 index bc6e5737..00000000 --- a/lib/gui/app/modules/analytics.js +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2016 balena.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -'use strict' - -const _ = require('lodash') -const resinCorvus = require('resin-corvus/browser') -const packageJSON = require('../../../../package.json') -const settings = require('../models/settings') -const { getConfig, hasProps } = require('../../../shared/utils') - -const sentryToken = settings.get('analyticsSentryToken') || - _.get(packageJSON, [ 'analytics', 'sentry', 'token' ]) -const mixpanelToken = settings.get('analyticsMixpanelToken') || - _.get(packageJSON, [ 'analytics', 'mixpanel', 'token' ]) - -const configUrl = settings.get('configUrl') || 'https://balena.io/etcher/static/config.json' - -const DEFAULT_PROBABILITY = 0.1 - -const services = { - sentry: sentryToken, - mixpanel: mixpanelToken -} -resinCorvus.install({ - services, - options: { - release: packageJSON.version, - shouldReport: () => { - return settings.get('errorReporting') - }, - mixpanelDeferred: true - } -}) - -let mixpanelSample = DEFAULT_PROBABILITY - -/** - * @summary Init analytics configurations - * @example initConfig() - */ -const initConfig = async () => { - let validatedConfig = null - try { - const config = await getConfig(configUrl) - const mixpanel = _.get(config, [ 'analytics', 'mixpanel' ], {}) - mixpanelSample = mixpanel.probability || DEFAULT_PROBABILITY - if (isClientEligible(mixpanelSample)) { - validatedConfig = validateMixpanelConfig(mixpanel) - } - } catch (err) { - resinCorvus.logException(err) - } - resinCorvus.setConfigs({ - mixpanel: validatedConfig - }) -} - -initConfig() - -/** - * @summary Check that the client is eligible for analytics - * @param {Object} - config - */ -// eslint-disable-next-line -function isClientEligible(probability) { - return Math.random() < probability -} - -/** - * @summary Check that config has at least HTTP_PROTOCOL and api_host - * @param {Object} - config - */ -// eslint-disable-next-line -function validateMixpanelConfig (config) { - /* eslint-disable camelcase */ - const mixpanelConfig = { - api_host: 'https://api.mixpanel.com' - } - if (hasProps(config, [ 'HTTP_PROTOCOL', 'api_host' ])) { - mixpanelConfig.api_host = `${config.HTTP_PROTOCOL}://${config.api_host}` - } - return mixpanelConfig - /* eslint-enable camelcase */ -} - -/** - * @summary Log a debug message - * @function - * @public - * - * @description - * This function sends the debug message to error reporting services. - * - * @param {String} message - message - * - * @example - * analytics.log('Hello World'); - */ -exports.logDebug = resinCorvus.logDebug - -/** - * @summary Log an event - * @function - * @public - * - * @description - * This function sends the debug message to product analytics services. - * - * @param {String} message - message - * @param {Object} [data] - event data - * - * @example - * analytics.logEvent('Select image', { - * image: '/dev/disk2' - * }); - */ -exports.logEvent = (message, data) => { - resinCorvus.logEvent(message, { ...data, sample: mixpanelSample }) -} - -/** - * @summary Log an exception - * @function - * @public - * - * @description - * This function logs an exception to error reporting services. - * - * @param {Error} exception - exception - * - * @example - * analytics.logException(new Error('Something happened')); - */ -exports.logException = resinCorvus.logException diff --git a/lib/gui/app/modules/analytics.ts b/lib/gui/app/modules/analytics.ts new file mode 100644 index 00000000..9fac2b2d --- /dev/null +++ b/lib/gui/app/modules/analytics.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2016 balena.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _ from 'lodash'; +import * as resinCorvus from 'resin-corvus/browser'; + +import * as packageJSON from '../../../../package.json'; +import { getConfig, hasProps } from '../../../shared/utils'; +import * as settings from '../models/settings'; + +const sentryToken = + settings.get('analyticsSentryToken') || + _.get(packageJSON, ['analytics', 'sentry', 'token']); +const mixpanelToken = + settings.get('analyticsMixpanelToken') || + _.get(packageJSON, ['analytics', 'mixpanel', 'token']); + +const configUrl = + settings.get('configUrl') || 'https://balena.io/etcher/static/config.json'; + +const DEFAULT_PROBABILITY = 0.1; + +const services = { + sentry: sentryToken, + mixpanel: mixpanelToken, +}; + +resinCorvus.install({ + services, + options: { + release: packageJSON.version, + shouldReport: () => { + return settings.get('errorReporting'); + }, + mixpanelDeferred: true, + }, +}); + +let mixpanelSample = DEFAULT_PROBABILITY; + +/** + * @summary Init analytics configurations + */ +async function initConfig() { + let validatedConfig = null; + try { + const config = await getConfig(configUrl); + const mixpanel = _.get(config, ['analytics', 'mixpanel'], {}); + mixpanelSample = mixpanel.probability || DEFAULT_PROBABILITY; + if (isClientEligible(mixpanelSample)) { + validatedConfig = validateMixpanelConfig(mixpanel); + } + } catch (err) { + resinCorvus.logException(err); + } + resinCorvus.setConfigs({ + mixpanel: validatedConfig, + }); +} + +initConfig(); + +/** + * @summary Check that the client is eligible for analytics + */ +function isClientEligible(probability: number) { + return Math.random() < probability; +} + +/** + * @summary Check that config has at least HTTP_PROTOCOL and api_host + */ +function validateMixpanelConfig(config: { + api_host?: string; + HTTP_PROTOCOL?: string; +}) { + const mixpanelConfig = { + api_host: 'https://api.mixpanel.com', + }; + if (hasProps(config, ['HTTP_PROTOCOL', 'api_host'])) { + mixpanelConfig.api_host = `${config.HTTP_PROTOCOL}://${config.api_host}`; + } + return mixpanelConfig; +} + +/** + * @summary Log a debug message + * + * @description + * This function sends the debug message to error reporting services. + */ +export const logDebug = resinCorvus.logDebug; + +/** + * @summary Log an event + * + * @description + * This function sends the debug message to product analytics services. + */ +export function logEvent(message: string, data: any) { + resinCorvus.logEvent(message, { ...data, sample: mixpanelSample }); +} + +/** + * @summary Log an exception + * + * @description + * This function logs an exception to error reporting services. + */ +export const logException = resinCorvus.logException; diff --git a/lib/gui/app/modules/image-writer.js b/lib/gui/app/modules/image-writer.js index 6d8dfb1c..c9ad0a42 100644 --- a/lib/gui/app/modules/image-writer.js +++ b/lib/gui/app/modules/image-writer.js @@ -29,6 +29,7 @@ const errors = require('../../../shared/errors') const permissions = require('../../../shared/permissions') // eslint-disable-next-line node/no-missing-require const windowProgress = require('../os/window-progress') +// eslint-disable-next-line node/no-missing-require const analytics = require('../modules/analytics') // eslint-disable-next-line node/no-missing-require const { updateLock } = require('./update-lock') diff --git a/lib/gui/etcher.js b/lib/gui/etcher.js index 2e0b7e8e..7907bfd8 100644 --- a/lib/gui/etcher.js +++ b/lib/gui/etcher.js @@ -26,6 +26,7 @@ const EXIT_CODES = require('../shared/exit-codes') // eslint-disable-next-line node/no-missing-require const { buildWindowMenu } = require('./menu') const settings = require('./app/models/settings') +// eslint-disable-next-line node/no-missing-require const analytics = require('./app/modules/analytics') const { getConfig } = require('../shared/utils') const { version, packageType } = require('../../package.json') diff --git a/tsconfig.json b/tsconfig.json index 5a23b550..b0f14d3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,11 +10,11 @@ "module": "commonjs", "target": "es2017", "jsx": "react", + "typeRoots": ["./node_modules/@types", "./typings"], "allowSyntheticDefaultImports": true }, "include": [ "lib/**/*.ts", - "node_modules/electron/**/*.d.ts", - "typings/**/*.d.ts" + "node_modules/electron/**/*.d.ts" ] } diff --git a/typings/resin-corvus/index.d.ts b/typings/resin-corvus/index.d.ts new file mode 100644 index 00000000..9b0b9114 --- /dev/null +++ b/typings/resin-corvus/index.d.ts @@ -0,0 +1 @@ +declare module 'resin-corvus/browser';