Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Migrate to functional integration APIs #808

Merged
merged 2 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions src/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ import { EventToMain, ScopeToMain } from './renderer/integrations';
/** Convenience interface used to expose Integrations */
export interface Integrations {
// For main process
SentryMinidump: SentryMinidump;
ElectronMinidump: ElectronMinidump;
ElectronBreadcrumbs: ElectronBreadcrumbs;
MainContext: MainContext;
OnUncaughtExcept: OnUncaughtException;
PreloadInjection: PreloadInjection;
MainProcessSession: MainProcessSession;
AdditionalContext: AdditionalContext;
ChildProcess: ChildProcess;
Screenshots: Screenshots;
SentryMinidump: typeof SentryMinidump;
ElectronMinidump: typeof ElectronMinidump;
ElectronBreadcrumbs: typeof ElectronBreadcrumbs;
MainContext: typeof MainContext;
OnUncaughtExcept: typeof OnUncaughtException;
PreloadInjection: typeof PreloadInjection;
MainProcessSession: typeof MainProcessSession;
AdditionalContext: typeof AdditionalContext;
ChildProcess: typeof ChildProcess;
Screenshots: typeof Screenshots;
// For renderer process
ScopeToMain: ScopeToMain;
ScopeToMain: typeof ScopeToMain;
// eslint-disable-next-line deprecation/deprecation
EventToMain: EventToMain;
}
Expand Down
142 changes: 67 additions & 75 deletions src/main/integrations/additional-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DeviceContext, Event, EventProcessor, Integration } from '@sentry/types';
import { convertIntegrationFnToClass } from '@sentry/core';
import { DeviceContext, IntegrationFn } from '@sentry/types';
import { app, screen as electronScreen } from 'electron';
import { CpuInfo, cpus } from 'os';

Expand All @@ -19,89 +20,80 @@ const DEFAULT_OPTIONS: AdditionalContextOptions = {
language: true,
};

/** Adds Electron context to events and normalises paths. */
export class AdditionalContext implements Integration {
/** @inheritDoc */
public static id: string = 'AdditionalContext';

/** @inheritDoc */
public readonly name: string;

private readonly _options: AdditionalContextOptions;
private readonly _lazyDeviceContext: DeviceContext;

public constructor(options: Partial<AdditionalContextOptions> = {}) {
this._lazyDeviceContext = {};
this.name = AdditionalContext.id;
this._options = {
...DEFAULT_OPTIONS,
...options,
};
}

/** @inheritDoc */
public setupOnce(addGlobalEventProcessor: (callback: EventProcessor) => void): void {
addGlobalEventProcessor(async (event: Event) => this._addAdditionalContext(event));
const INTEGRATION_NAME = 'AdditionalContext';

// Some metrics are only available after app ready so we lazily load them
whenAppReady.then(
() => {
const { language, screen } = this._options;

if (language) {
this._lazyDeviceContext.language = app.getLocale();
}
const additionalContext: IntegrationFn = (userOptions: Partial<AdditionalContextOptions> = {}) => {
const _lazyDeviceContext: DeviceContext = {};

if (screen) {
this._setPrimaryDisplayInfo();
const options = {
...DEFAULT_OPTIONS,
...userOptions,
};

electronScreen.on('display-metrics-changed', () => {
this._setPrimaryDisplayInfo();
});
}
},
() => {
// ignore
},
);
function _setPrimaryDisplayInfo(): void {
const display = electronScreen.getPrimaryDisplay();
const width = Math.floor(display.size.width * display.scaleFactor);
const height = Math.floor(display.size.height * display.scaleFactor);
_lazyDeviceContext.screen_density = display.scaleFactor;
_lazyDeviceContext.screen_resolution = `${width}x${height}`;
}

/** Adds additional context to event */
private _addAdditionalContext(event: Event): Event {
const device: DeviceContext = this._lazyDeviceContext;

const { memory, cpu } = this._options;

if (memory) {
const { total, free } = process.getSystemMemoryInfo();
device.memory_size = total * 1024;
device.free_memory = free * 1024;
}
return {
name: INTEGRATION_NAME,
setup() {
// Some metrics are only available after app ready so we lazily load them
whenAppReady.then(
() => {
const { language, screen } = options;

if (language) {
_lazyDeviceContext.language = app.getLocale();
}

if (screen) {
_setPrimaryDisplayInfo();

electronScreen.on('display-metrics-changed', () => {
_setPrimaryDisplayInfo();
});
}
},
() => {
// ignore
},
);
},
processEvent(event) {
const device: DeviceContext = _lazyDeviceContext;

const { memory, cpu } = options;

if (memory) {
const { total, free } = process.getSystemMemoryInfo();
device.memory_size = total * 1024;
device.free_memory = free * 1024;
}

if (cpu) {
const cpuInfo: CpuInfo[] | undefined = cpus();
if (cpuInfo && cpuInfo.length) {
const firstCpu = cpuInfo[0];
if (cpu) {
const cpuInfo: CpuInfo[] | undefined = cpus();
if (cpuInfo && cpuInfo.length) {
const firstCpu = cpuInfo[0];

device.processor_count = cpuInfo.length;
device.cpu_description = firstCpu.model;
device.processor_frequency = firstCpu.speed;
device.processor_count = cpuInfo.length;
device.cpu_description = firstCpu.model;
device.processor_frequency = firstCpu.speed;

if (app.runningUnderARM64Translation) {
device.machine_arch = 'arm64';
if (app.runningUnderARM64Translation) {
device.machine_arch = 'arm64';
}
}
}
}

return mergeEvents(event, { contexts: { device } });
}
return mergeEvents(event, { contexts: { device } });
},
};
};

/** Sets the display info */
private _setPrimaryDisplayInfo(): void {
const display = electronScreen.getPrimaryDisplay();
const width = Math.floor(display.size.width * display.scaleFactor);
const height = Math.floor(display.size.height * display.scaleFactor);
this._lazyDeviceContext.screen_density = display.scaleFactor;
this._lazyDeviceContext.screen_resolution = `${width}x${height}`;
}
}
/** Adds Electron context to events and normalises paths. */
// eslint-disable-next-line deprecation/deprecation
export const AdditionalContext = convertIntegrationFnToClass(INTEGRATION_NAME, additionalContext);
101 changes: 49 additions & 52 deletions src/main/integrations/browser-window-session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Integration } from '@sentry/types';
import { convertIntegrationFnToClass } from '@sentry/core';
import { IntegrationFn } from '@sentry/types';
import { app, BrowserWindow } from 'electron';

import { ELECTRON_MAJOR_VERSION } from '../electron-normalize';
Expand Down Expand Up @@ -29,73 +30,38 @@ interface Options {
// The state can be, active, inactive, or waiting for a timeout
type SessionState = { name: 'active' } | { name: 'inactive' } | { name: 'timeout'; timer: NodeJS.Timeout };

/**
* Tracks sessions as BrowserWindows focused.
*
* Supports Electron >= v12
*/
export class BrowserWindowSession implements Integration {
/** @inheritDoc */
public static id: string = 'BrowserWindowSession';

/** @inheritDoc */
public readonly name: string;
const INTEGRATION_NAME = 'BrowserWindowSession';

private _state: SessionState;

public constructor(private readonly _options: Options = {}) {
if (ELECTRON_MAJOR_VERSION < 12) {
throw new Error('BrowserWindowSession requires Electron >= v12');
}

this.name = BrowserWindowSession.id;
this._state = { name: 'inactive' };
const browserWindowSession: IntegrationFn = (options: Options = {}) => {
if (ELECTRON_MAJOR_VERSION < 12) {
throw new Error('BrowserWindowSession requires Electron >= v12');
}

/** @inheritDoc */
public setupOnce(): void {
app.on('browser-window-created', (_event, window) => {
window.on('focus', this._windowStateChanged);
window.on('blur', this._windowStateChanged);
window.on('show', this._windowStateChanged);
window.on('hide', this._windowStateChanged);

// when the window is closed we need to remove the listeners
window.once('closed', () => {
window.removeListener('focus', this._windowStateChanged);
window.removeListener('blur', this._windowStateChanged);
window.removeListener('show', this._windowStateChanged);
window.removeListener('hide', this._windowStateChanged);
});
});

// if the app exits while the session is active, end the session
endSessionOnExit();
}
let _state: SessionState = { name: 'inactive' };

private _windowStateChanged = (): void => {
function windowStateChanged(): void {
const hasFocusedWindow = focusedWindow();

if (hasFocusedWindow) {
// We are now active
if (this._state.name === 'inactive') {
if (_state.name === 'inactive') {
// If we were inactive, start a new session
startSession(true);
} else if (this._state.name === 'timeout') {
} else if (_state.name === 'timeout') {
// Clear the timeout since the app has become active again
clearTimeout(this._state.timer);
clearTimeout(_state.timer);
}

this._state = { name: 'active' };
_state = { name: 'active' };
} else {
if (this._state.name === 'active') {
if (_state.name === 'active') {
// We have become inactive, start the timeout
const timeout = (this._options.backgroundTimeoutSeconds ?? 30) * 1_000;
const timeout = (options.backgroundTimeoutSeconds ?? 30) * 1_000;

const timer = setTimeout(() => {
// if the state says we're still waiting for the timeout, end the session
if (this._state.name === 'timeout') {
this._state = { name: 'inactive' };
if (_state.name === 'timeout') {
_state = { name: 'inactive' };
endSession().catch(() => {
// ignore
});
Expand All @@ -104,8 +70,39 @@ export class BrowserWindowSession implements Integration {
// unref so this timer doesn't block app exit
.unref();

this._state = { name: 'timeout', timer };
_state = { name: 'timeout', timer };
}
}
}

return {
name: INTEGRATION_NAME,
setup() {
app.on('browser-window-created', (_event, window) => {
window.on('focus', windowStateChanged);
window.on('blur', windowStateChanged);
window.on('show', windowStateChanged);
window.on('hide', windowStateChanged);

// when the window is closed we need to remove the listeners
window.once('closed', () => {
window.removeListener('focus', windowStateChanged);
window.removeListener('blur', windowStateChanged);
window.removeListener('show', windowStateChanged);
window.removeListener('hide', windowStateChanged);
});
});

// if the app exits while the session is active, end the session
endSessionOnExit();
},
};
}
};

/**
* Tracks sessions as BrowserWindows focused.
*
* Supports Electron >= v12
*/
// eslint-disable-next-line deprecation/deprecation
export const BrowserWindowSession = convertIntegrationFnToClass(INTEGRATION_NAME, browserWindowSession);
Loading
Loading