Skip to content
Open
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
33 changes: 22 additions & 11 deletions client/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ function setupWindow(): void {
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
// Before Electron 20, renderers that specified a preload script defaulted to being unsandboxed.
// This meant that by default, preload scripts had access to Node.js.
// Beginning in Electron 20, renderers are sandboxed by default.
// https://www.electronjs.org/docs/latest/breaking-changes#default-changed-renderers-without-nodeintegration-true-are-sandboxed-by-default
// TODO: Move all Node.js-dependent logic to the main process and enable sandboxing.
sandbox: false,
preload: path.join(__dirname, 'preload.js'),
},
});
Expand Down Expand Up @@ -224,19 +230,24 @@ function setupWindow(): void {
// The client is a single page app - loading any other page means the
// user clicked on one of the Privacy, Terms, etc., links. These should
// open in the user's browser.
mainWindow.webContents.on('will-navigate', (event: Event, url: string) => {
try {
const parsed: URL = new URL(url);
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
shell.openExternal(url);
} else {
console.warn(`Refusing to open URL with protocol "${parsed.protocol}"`);
mainWindow.webContents.on(
'will-navigate',
(event: Electron.Event, url: string) => {
try {
const parsed: URL = new URL(url);
if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
shell.openExternal(url);
} else {
console.warn(
`Refusing to open URL with protocol "${parsed.protocol}"`
);
}
} catch (e) {
console.warn(`Could not parse URL ${url}:`, e);
}
} catch (e) {
console.warn(`Could not parse URL ${url}:`, e);
event.preventDefault();
}
event.preventDefault();
});
);
}

function updateTray(status: TunnelStatus) {
Expand Down
1 change: 0 additions & 1 deletion client/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
ipcRenderer,
IpcRendererEvent,
} from 'electron';
import '@sentry/electron/preload';

/**
* The method channel for sending messages through electron's IPC.
Expand Down
9 changes: 5 additions & 4 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-toast": "^3.0.1",
"@polymer/polymer": "^3.5.1",
"@sentry/browser": "^7.31.1",
"@sentry/electron": "^4.2.0",
"@sentry/browser": "10.47.0",
"@sentry/core": "10.47.0",
"@sentry/electron": "7.10.0",
"@webcomponents/webcomponentsjs": "^2.4.4",
"auto-launch": "^5.0.5",
"cordova-plugin-splashscreen": "^6.0.0",
Expand Down Expand Up @@ -85,8 +86,8 @@
"css-loader": "^5.0.1",
"deepmerge": "^4.3.1",
"dotenv": "^17.2.2",
"electron": "19.1.9",
"electron-builder": "^23.6.0",
"electron": "30.5.1",
"electron-builder": "26.8.1",
"eslint": "^8.15.0",
"eslint-import-resolver-typescript": "^3.4.0",
"eslint-plugin-compat": "^4.0.2",
Expand Down
1 change: 1 addition & 0 deletions client/src/cordova/build.action.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ async function appleRelease(platform) {
'archive',
'-configuration',
'Release',
'-allowProvisioningUpdates',
...teamIdArgs
);
}
Expand Down
6 changes: 3 additions & 3 deletions client/web/app/main.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ class CordovaErrorReporter extends SentryErrorReporter {
pluginExec<void>('initializeErrorReporting', dsn).catch(console.error);
}

async report(
userFeedback: string,
async sendFeedback(
message: string,
feedbackCategory: string,
userEmail?: string
): Promise<void> {
await super.report(userFeedback, feedbackCategory, userEmail);
await super.sendFeedback(message, feedbackCategory, userEmail);
// Sends previously captured logs and events to the error reporting framework.
// Associates the report to the provided unique identifier.
await pluginExec<void>('reportEvents', Sentry.lastEventId() || '');
Expand Down
28 changes: 17 additions & 11 deletions client/web/app/main.electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ const isLinux = window.electron.os.platform === 'linux';
const isOsSupported = isWindows || isLinux;

const interceptor = new UrlInterceptor();
window.electron.methodChannel.on('add-server', (_: Event, url: string) => {
interceptor.executeListeners(url);
});
window.electron.methodChannel.on(
'add-server',
(_: Electron.IpcRendererEvent, url: string) => {
interceptor.executeListeners(url);
}
);

window.electron.methodChannel.on(
'localization-request',
(_: Event, localizationKeys: string[]) => {
(_: Electron.IpcRendererEvent, localizationKeys: string[]) => {
const localize = getLocalizationFunction();
if (!localize) {
console.error('Localization function not available.');
Expand Down Expand Up @@ -109,19 +112,22 @@ class ElectronErrorReporter implements OutlineErrorReporter {
constructor() {
// parameters are initialized in main process
Sentry.init({
integrations: getSentryBrowserIntegrations,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
integrations: (integrations: any) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getSentryBrowserIntegrations(integrations) as any,
});
}

report(
userFeedback: string,
sendFeedback(
message: string,
feedbackCategory: string,
userEmail?: string,
tags?: Tags
): Promise<void> {
Sentry.captureEvent({
message: userFeedback,
user: {email: userEmail},
Sentry.captureFeedback({
message: message,
email: userEmail,
tags: {...tags, category: feedbackCategory},
});
return Promise.resolve();
Expand Down Expand Up @@ -156,4 +162,4 @@ main({
getUpdater: () => new ElectronUpdater(),
getVpnServiceInstaller: () => new ElectronVpnInstaller(),
quitApplication: () => window.electron.methodChannel.send('quit-app'),
});
}).catch(console.error);
34 changes: 11 additions & 23 deletions client/web/shared/error_reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
// limitations under the License.

import * as Sentry from '@sentry/browser';
import {Integration as SentryIntegration} from '@sentry/types';
import type {Integration} from '@sentry/core';

export type Tags = {[id: string]: string | boolean | number};

export interface OutlineErrorReporter {
report(
userFeedback: string,
sendFeedback(
message: string,
feedbackCategory: string,
userEmail?: string,
tags?: Tags
Expand All @@ -42,33 +42,21 @@ export class SentryErrorReporter implements OutlineErrorReporter {
this.setUpUnhandledRejectionListener();
}

async report(
userFeedback: string,
async sendFeedback(
message: string,
feedbackCategory: string,
userEmail?: string,
tags?: Tags
): Promise<void> {
const combinedTags = {...this.tags, ...tags};
Sentry.captureEvent({
message: userFeedback,
user: {email: userEmail},
Sentry.captureFeedback({
message: message,
email: userEmail,
tags: {
category: feedbackCategory,
isFeedback: Boolean(userFeedback),
...combinedTags,
},
});
Sentry.configureScope(scope => {
scope.setUser({email: userEmail || ''});
if (combinedTags) {
scope.setTags(combinedTags);
}
scope.setTag('category', feedbackCategory);
});
Sentry.captureMessage(userFeedback);
Sentry.configureScope(scope => {
scope.clear(); // Reset the user context, don't cache the email
});
}

private setUpUnhandledRejectionListener() {
Expand All @@ -90,12 +78,12 @@ export class SentryErrorReporter implements OutlineErrorReporter {
// but replaces the Breadcrumbs integration with a custom one that only collects console statements.
// See https://docs.sentry.io/platforms/javascript/configuration/integrations/default/
export function getSentryBrowserIntegrations(
defaultIntegrations: SentryIntegration[]
): SentryIntegration[] {
defaultIntegrations: Integration[]
): Integration[] {
const integrations = defaultIntegrations.filter(integration => {
return integration.name !== 'Breadcrumbs';
});
const breadcrumbsIntegration = new Sentry.Integrations.Breadcrumbs({
const breadcrumbsIntegration = Sentry.breadcrumbsIntegration({
console: true,
dom: false,
fetch: false,
Expand Down
4 changes: 2 additions & 2 deletions client/web/views/contact_view/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('ContactView', () => {
supportForm.dispatchEvent(new CustomEvent('submit'));
await nextFrame();

expect(mockErrorReporter.report).toHaveBeenCalledWith(
expect(mockErrorReporter.sendFeedback).toHaveBeenCalledWith(
'Test Description',
'general',
'foo@bar.com',
Expand All @@ -217,7 +217,7 @@ describe('ContactView', () => {

it('emits failure event when feedback reporting fails', async () => {
const listener = oneEvent(el, 'error');
mockErrorReporter.report.and.throwError('fail');
mockErrorReporter.sendFeedback.and.throwError('fail');

const supportForm: SupportForm =
el.shadowRoot!.querySelector('support-form')!;
Expand Down
2 changes: 1 addition & 1 deletion client/web/views/contact_view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export class ContactView extends LitElement {

const {description, email, ...tags} = this.formValues as FormValues;
try {
await this.errorReporter.report(
await this.errorReporter.sendFeedback(
description,
this.selectedIssueType?.toString() ?? 'unknown',
email,
Expand Down
Loading
Loading