mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-14 14:56:32 +00:00
Removes corvus in favor of sentry and analytics client
Change-type: patch Signed-off-by: Otavio Jacobi
This commit is contained in:
parent
8cd6da1260
commit
3513a83b90
2
.github/actions/publish/action.yml
vendored
2
.github/actions/publish/action.yml
vendored
@ -172,7 +172,7 @@ runs:
|
|||||||
for target in ${TARGETS}; do
|
for target in ${TARGETS}; do
|
||||||
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
electron-builder ${ELECTRON_BUILDER_OS} ${target} ${ARCHITECTURE_FLAGS} \
|
||||||
--c.extraMetadata.analytics.sentry.token='${{ steps.sentry.outputs.dsn }}' \
|
--c.extraMetadata.analytics.sentry.token='${{ steps.sentry.outputs.dsn }}' \
|
||||||
--c.extraMetadata.analytics.mixpanel.token='balena-etcher' \
|
--c.extraMetadata.analytics.amplitude.token='balena-etcher' \
|
||||||
--c.extraMetadata.packageType="${target}"
|
--c.extraMetadata.packageType="${target}"
|
||||||
|
|
||||||
find dist -type f -maxdepth 1
|
find dist -type f -maxdepth 1
|
||||||
|
@ -31,7 +31,7 @@ Releasing
|
|||||||
- [Post release note to forums](https://forums.balena.io/c/etcher)
|
- [Post release note to forums](https://forums.balena.io/c/etcher)
|
||||||
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
||||||
- [Update the website](https://github.com/balena-io/etcher-homepage)
|
- [Update the website](https://github.com/balena-io/etcher-homepage)
|
||||||
- Wait 2-3 hours for analytics (Sentry, Mixpanel) to trickle in and check for elevated error rates, or regressions
|
- Wait 2-3 hours for analytics (Sentry, Amplitude) to trickle in and check for elevated error rates, or regressions
|
||||||
- If regressions arise; pull the release, and release a patched version, else:
|
- If regressions arise; pull the release, and release a patched version, else:
|
||||||
- [Upload deb & rpm packages to Bintray](#uploading-packages-to-bintray)
|
- [Upload deb & rpm packages to Bintray](#uploading-packages-to-bintray)
|
||||||
- [Upload build artifacts to Amazon S3](#uploading-binaries-to-amazon-s3)
|
- [Upload build artifacts to Amazon S3](#uploading-binaries-to-amazon-s3)
|
||||||
@ -48,7 +48,7 @@ Make sure to set the analytics tokens when generating production release binarie
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
|
export ANALYTICS_SENTRY_TOKEN="xxxxxx"
|
||||||
export ANALYTICS_MIXPANEL_TOKEN="xxxxxx"
|
export ANALYTICS_AMPLITUDE_TOKEN="xxxxxx"
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
@ -112,4 +112,4 @@ Analytics
|
|||||||
- [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
|
- [ ] Disable analytics, open DevTools Network pane or a packet sniffer, and
|
||||||
check that no request is sent
|
check that no request is sent
|
||||||
- [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
|
- [ ] **Disable analytics, refresh application from DevTools (using Cmd-R or
|
||||||
F5), and check that initial events are not sent to Mixpanel**
|
F5), and check that initial events are not sent to Amplitude**
|
||||||
|
@ -296,6 +296,8 @@ driveScanner.start();
|
|||||||
|
|
||||||
let popupExists = false;
|
let popupExists = false;
|
||||||
|
|
||||||
|
analytics.initAnalytics();
|
||||||
|
|
||||||
window.addEventListener('beforeunload', async (event) => {
|
window.addEventListener('beforeunload', async (event) => {
|
||||||
if (!flashState.isFlashing() || popupExists) {
|
if (!flashState.isFlashing() || popupExists) {
|
||||||
analytics.logEvent('Close application', {
|
analytics.logEvent('Close application', {
|
||||||
|
@ -15,84 +15,47 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as resinCorvus from 'resin-corvus/browser';
|
import { Client, createClient, createNoopClient } from 'analytics-client';
|
||||||
|
import * as SentryRenderer from '@sentry/electron/renderer';
|
||||||
import * as packageJSON from '../../../../package.json';
|
|
||||||
import { getConfig } from '../../../shared/utils';
|
|
||||||
import * as settings from '../models/settings';
|
import * as settings from '../models/settings';
|
||||||
import { store } from '../models/store';
|
import { store } from '../models/store';
|
||||||
|
import * as packageJSON from '../../../../package.json';
|
||||||
|
|
||||||
const DEFAULT_PROBABILITY = 0.1;
|
let analyticsClient: Client;
|
||||||
|
|
||||||
async function installCorvus(): Promise<void> {
|
|
||||||
const sentryToken =
|
|
||||||
(await settings.get('analyticsSentryToken')) ||
|
|
||||||
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
|
||||||
const mixpanelToken =
|
|
||||||
(await settings.get('analyticsMixpanelToken')) ||
|
|
||||||
_.get(packageJSON, ['analytics', 'mixpanel', 'token']);
|
|
||||||
resinCorvus.install({
|
|
||||||
services: {
|
|
||||||
sentry: sentryToken,
|
|
||||||
mixpanel: mixpanelToken,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
release: packageJSON.version,
|
|
||||||
shouldReport: () => {
|
|
||||||
return settings.getSync('errorReporting');
|
|
||||||
},
|
|
||||||
mixpanelDeferred: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mixpanelSample = DEFAULT_PROBABILITY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Init analytics configurations
|
* @summary Init analytics configurations
|
||||||
*/
|
*/
|
||||||
async function initConfig() {
|
export const initAnalytics = _.once(() => {
|
||||||
await installCorvus();
|
const dsn =
|
||||||
let validatedConfig = null;
|
settings.getSync('analyticsSentryToken') ||
|
||||||
try {
|
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
||||||
const configUrl = await settings.get('configUrl');
|
SentryRenderer.init({ dsn });
|
||||||
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();
|
const projectName =
|
||||||
|
settings.getSync('analyticsAmplitudeToken') ||
|
||||||
|
_.get(packageJSON, ['analytics', 'amplitude', 'token']);
|
||||||
|
|
||||||
/**
|
const clientConfig = {
|
||||||
* @summary Check that the client is eligible for analytics
|
projectName,
|
||||||
*/
|
endpoint: 'data.balena-staging.com',
|
||||||
function isClientEligible(probability: number) {
|
componentName: 'etcher',
|
||||||
return Math.random() < probability;
|
componentVersion: packageJSON.version,
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 (config.HTTP_PROTOCOL !== undefined && config.api_host !== undefined) {
|
analyticsClient = projectName
|
||||||
mixpanelConfig.api_host = `${config.HTTP_PROTOCOL}://${config.api_host}`;
|
? createClient(clientConfig)
|
||||||
}
|
: createNoopClient();
|
||||||
return mixpanelConfig;
|
});
|
||||||
|
|
||||||
|
function reportAnalytics(message: string, data: _.Dictionary<any> = {}) {
|
||||||
|
const { applicationSessionUuid, flashingWorkflowUuid } = store
|
||||||
|
.getState()
|
||||||
|
.toJS();
|
||||||
|
|
||||||
|
analyticsClient.track(message, {
|
||||||
|
...data,
|
||||||
|
applicationSessionUuid,
|
||||||
|
flashingWorkflowUuid,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,16 +64,12 @@ function validateMixpanelConfig(config: {
|
|||||||
* @description
|
* @description
|
||||||
* This function sends the debug message to product analytics services.
|
* This function sends the debug message to product analytics services.
|
||||||
*/
|
*/
|
||||||
export function logEvent(message: string, data: _.Dictionary<any> = {}) {
|
export async function logEvent(message: string, data: _.Dictionary<any> = {}) {
|
||||||
const { applicationSessionUuid, flashingWorkflowUuid } = store
|
const shouldReportAnalytics = await settings.get('errorReporting');
|
||||||
.getState()
|
if (shouldReportAnalytics) {
|
||||||
.toJS();
|
initAnalytics();
|
||||||
resinCorvus.logEvent(message, {
|
reportAnalytics(message, data);
|
||||||
...data,
|
}
|
||||||
sample: mixpanelSample,
|
|
||||||
applicationSessionUuid,
|
|
||||||
flashingWorkflowUuid,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,4 +78,11 @@ export function logEvent(message: string, data: _.Dictionary<any> = {}) {
|
|||||||
* @description
|
* @description
|
||||||
* This function logs an exception to error reporting services.
|
* This function logs an exception to error reporting services.
|
||||||
*/
|
*/
|
||||||
export const logException = resinCorvus.logException;
|
export function logException(error: any) {
|
||||||
|
const shouldReportErrors = settings.getSync('errorReporting');
|
||||||
|
if (shouldReportErrors) {
|
||||||
|
initAnalytics();
|
||||||
|
console.error(error);
|
||||||
|
SentryRenderer.captureException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { promises as fs } from 'fs';
|
|||||||
import { platform } from 'os';
|
import { platform } from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
import './app/i18n';
|
import './app/i18n';
|
||||||
|
|
||||||
@ -27,9 +28,10 @@ import { packageType, version } from '../../package.json';
|
|||||||
import * as EXIT_CODES from '../shared/exit-codes';
|
import * as EXIT_CODES from '../shared/exit-codes';
|
||||||
import { delay, getConfig } from '../shared/utils';
|
import { delay, getConfig } from '../shared/utils';
|
||||||
import * as settings from './app/models/settings';
|
import * as settings from './app/models/settings';
|
||||||
import { logException } from './app/modules/analytics';
|
|
||||||
import { buildWindowMenu } from './menu';
|
import { buildWindowMenu } from './menu';
|
||||||
import * as i18n from 'i18next';
|
import * as i18n from 'i18next';
|
||||||
|
import * as SentryMain from '@sentry/electron/main';
|
||||||
|
import * as packageJSON from '../../package.json';
|
||||||
|
|
||||||
const customProtocol = 'etcher';
|
const customProtocol = 'etcher';
|
||||||
const scheme = `${customProtocol}://`;
|
const scheme = `${customProtocol}://`;
|
||||||
@ -53,13 +55,21 @@ async function checkForUpdates(interval: number) {
|
|||||||
packageUpdated = true;
|
packageUpdated = true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logException(err);
|
logMainProcessException(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await delay(interval);
|
await delay(interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logMainProcessException(error: any) {
|
||||||
|
const shouldReportErrors = settings.getSync('errorReporting');
|
||||||
|
if (shouldReportErrors) {
|
||||||
|
console.error(error);
|
||||||
|
SentryMain.captureException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function isFile(filePath: string): Promise<boolean> {
|
async function isFile(filePath: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const stat = await fs.stat(filePath);
|
const stat = await fs.stat(filePath);
|
||||||
@ -94,6 +104,14 @@ async function getCommandLineURL(argv: string[]): Promise<string | undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initSentryMain = _.once(() => {
|
||||||
|
const dsn =
|
||||||
|
settings.getSync('analyticsSentryToken') ||
|
||||||
|
_.get(packageJSON, ['analytics', 'sentry', 'token']);
|
||||||
|
|
||||||
|
SentryMain.init({ dsn });
|
||||||
|
});
|
||||||
|
|
||||||
const sourceSelectorReady = new Promise((resolve) => {
|
const sourceSelectorReady = new Promise((resolve) => {
|
||||||
electron.ipcMain.on('source-selector-ready', resolve);
|
electron.ipcMain.on('source-selector-ready', resolve);
|
||||||
});
|
});
|
||||||
@ -190,8 +208,9 @@ async function createMainWindow() {
|
|||||||
const page = mainWindow.webContents;
|
const page = mainWindow.webContents;
|
||||||
|
|
||||||
page.once('did-frame-finish-load', async () => {
|
page.once('did-frame-finish-load', async () => {
|
||||||
|
console.log('packageUpdatable', packageUpdatable);
|
||||||
autoUpdater.on('error', (err) => {
|
autoUpdater.on('error', (err) => {
|
||||||
logException(err);
|
logMainProcessException(err);
|
||||||
});
|
});
|
||||||
if (packageUpdatable) {
|
if (packageUpdatable) {
|
||||||
try {
|
try {
|
||||||
@ -208,7 +227,7 @@ async function createMainWindow() {
|
|||||||
onlineConfig?.autoUpdates?.checkForUpdatesTimer ?? 300000;
|
onlineConfig?.autoUpdates?.checkForUpdatesTimer ?? 300000;
|
||||||
checkForUpdates(checkForUpdatesTimer);
|
checkForUpdates(checkForUpdatesTimer);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logException(err);
|
logMainProcessException(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -233,6 +252,7 @@ async function main(): Promise<void> {
|
|||||||
if (!electron.app.requestSingleInstanceLock()) {
|
if (!electron.app.requestSingleInstanceLock()) {
|
||||||
electron.app.quit();
|
electron.app.quit();
|
||||||
} else {
|
} else {
|
||||||
|
initSentryMain();
|
||||||
await electron.app.whenReady();
|
await electron.app.whenReady();
|
||||||
const window = await createMainWindow();
|
const window = await createMainWindow();
|
||||||
electron.app.on('second-instance', async (_event, argv) => {
|
electron.app.on('second-instance', async (_event, argv) => {
|
||||||
@ -256,7 +276,6 @@ async function main(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
||||||
console.time('ready-to-show');
|
console.time('ready-to-show');
|
||||||
|
898
package-lock.json
generated
898
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -53,6 +53,7 @@
|
|||||||
"@balena/lint": "5.4.2",
|
"@balena/lint": "5.4.2",
|
||||||
"@balena/sudo-prompt": "9.2.1-workaround-windows-amperstand-in-username-0849e215b947987a643fe5763902aea201255534",
|
"@balena/sudo-prompt": "9.2.1-workaround-windows-amperstand-in-username-0849e215b947987a643fe5763902aea201255534",
|
||||||
"@fortawesome/fontawesome-free": "5.15.4",
|
"@fortawesome/fontawesome-free": "5.15.4",
|
||||||
|
"@sentry/electron": "^4.1.2",
|
||||||
"@svgr/webpack": "5.5.0",
|
"@svgr/webpack": "5.5.0",
|
||||||
"@types/chai": "4.3.4",
|
"@types/chai": "4.3.4",
|
||||||
"@types/copy-webpack-plugin": "6.4.3",
|
"@types/copy-webpack-plugin": "6.4.3",
|
||||||
@ -68,6 +69,7 @@
|
|||||||
"@types/terser-webpack-plugin": "5.0.4",
|
"@types/terser-webpack-plugin": "5.0.4",
|
||||||
"@types/tmp": "0.2.3",
|
"@types/tmp": "0.2.3",
|
||||||
"@types/webpack-node-externals": "2.5.3",
|
"@types/webpack-node-externals": "2.5.3",
|
||||||
|
"analytics-client": "^2.0.1",
|
||||||
"aws4-axios": "2.4.9",
|
"aws4-axios": "2.4.9",
|
||||||
"chai": "4.3.7",
|
"chai": "4.3.7",
|
||||||
"copy-webpack-plugin": "7.0.0",
|
"copy-webpack-plugin": "7.0.0",
|
||||||
@ -102,7 +104,6 @@
|
|||||||
"react-i18next": "11.18.6",
|
"react-i18next": "11.18.6",
|
||||||
"redux": "4.2.0",
|
"redux": "4.2.0",
|
||||||
"rendition": "19.3.2",
|
"rendition": "19.3.2",
|
||||||
"resin-corvus": "2.0.5",
|
|
||||||
"semver": "7.3.8",
|
"semver": "7.3.8",
|
||||||
"simple-progress-webpack-plugin": "1.1.2",
|
"simple-progress-webpack-plugin": "1.1.2",
|
||||||
"sinon": "9.2.4",
|
"sinon": "9.2.4",
|
||||||
|
@ -29,7 +29,7 @@ import * as PnpWebpackPlugin from 'pnp-webpack-plugin';
|
|||||||
import * as tsconfigRaw from './tsconfig.webpack.json';
|
import * as tsconfigRaw from './tsconfig.webpack.json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't webpack package.json as mixpanel & sentry tokens
|
* Don't webpack package.json as sentry tokens
|
||||||
* will be inserted in it after webpacking
|
* will be inserted in it after webpacking
|
||||||
*/
|
*/
|
||||||
function externalPackageJson(packageJsonPath: string) {
|
function externalPackageJson(packageJsonPath: string) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user