Skip to content

Commit

Permalink
chore(client): clean up errors (#2346)
Browse files Browse the repository at this point in the history
  • Loading branch information
fortuna authored Jan 24, 2025
1 parent a6c43ca commit a7e6a91
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 213 deletions.
9 changes: 3 additions & 6 deletions client/electron/routing_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import * as sudo from 'sudo-prompt';
import {pathToEmbeddedOutlineService} from './app_paths';
import {TunnelStatus} from '../src/www/app/outline_server_repository/vpn';
import {ErrorCode} from '../src/www/model/errors';
import {
PlatformError,
ROUTING_SERVICE_NOT_RUNNING,
} from '../src/www/model/platform_error';
import {PlatformError, GoErrorCode} from '../src/www/model/platform_error';

const isLinux = platform() === 'linux';
const isWindows = platform() === 'win32';
Expand Down Expand Up @@ -135,7 +132,7 @@ export class RoutingDaemon {
cleanup();
newSocket.destroy();
const perr = new PlatformError(
ROUTING_SERVICE_NOT_RUNNING,
GoErrorCode.ROUTING_SERVICE_NOT_RUNNING,
'routing daemon service stopped before started'
);
reject(new Error(perr.toJSON()));
Expand All @@ -159,7 +156,7 @@ export class RoutingDaemon {
console.error('Routing daemon socket setup failed', err);
this.socket = null;
const perr = new PlatformError(
ROUTING_SERVICE_NOT_RUNNING,
GoErrorCode.ROUTING_SERVICE_NOT_RUNNING,
'routing daemon is not running',
{cause: err}
);
Expand Down
51 changes: 18 additions & 33 deletions client/src/www/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,14 @@ import {OperationTimedOut} from '@outline/infrastructure/timeout_promise';

import {Clipboard} from './clipboard';
import {EnvironmentVariables} from './environment';
import {localizeErrorCode} from './error_localizer';
import * as config from './outline_server_repository/config';
import {Settings, SettingsKey} from './settings';
import {Updater} from './updater';
import {UrlInterceptor} from './url_interceptor';
import {VpnInstaller} from './vpn_installer';
import * as errors from '../model/errors';
import * as events from '../model/events';
import {
PlatformError,
ROUTING_SERVICE_NOT_RUNNING,
} from '../model/platform_error';
import {PlatformError, GoErrorCode} from '../model/platform_error';
import {Server, ServerRepository} from '../model/server';
import {OutlineErrorReporter} from '../shared/error_reporter';
import {ServerConnectionState, ServerListItem} from '../views/servers_view';
Expand Down Expand Up @@ -302,7 +298,7 @@ export class App {
'cipher',
error.cipher
);
} else if (error instanceof errors.ServerAccessKeyInvalid) {
} else if (error instanceof errors.InvalidServiceConfiguration) {
toastMessage = this.localize('error-connection-configuration');
buttonMessage = this.localize('error-details');
buttonHandler = () => {
Expand All @@ -320,20 +316,15 @@ export class App {
buttonHandler = () => {
this.showErrorCauseDialog(error);
};
} else if (error instanceof errors.SessionConfigError) {
toastMessage = error.message;
} else if (error instanceof errors.SessionProviderError) {
toastMessage = error.message;
buttonMessage = this.localize('error-details');

console.log(error, error.message, error.details);
buttonHandler = () => {
this.showErrorDetailsDialog(error.details);
};
} else if (error instanceof PlatformError) {
toastMessage = localizeErrorCode(error.code, this.localize);
buttonMessage = this.localize('error-details');
buttonHandler = () => this.showErrorDetailsDialog(error.toString());
if (error.details) {
buttonMessage = this.localize('error-details');
buttonHandler = () => {
alert(error.details);
};
}
} else {
const hasErrorDetails = Boolean(error.message || error.cause);
toastMessage = this.localize('error-unexpected');
Expand Down Expand Up @@ -559,7 +550,7 @@ export class App {
console.error(`could not connect to server ${serverId}: ${e}`);
if (
e instanceof PlatformError &&
e.code === ROUTING_SERVICE_NOT_RUNNING
e.code === GoErrorCode.ROUTING_SERVICE_NOT_RUNNING
) {
const confirmation =
this.localize('outline-services-installation-confirmation') +
Expand Down Expand Up @@ -754,22 +745,16 @@ export class App {
}

private showErrorCauseDialog(error: Error) {
let message = error.toString();

if (error.cause) {
message += '\nCause: ';
message += error.cause.toString();
}

return alert(message);
}

private showErrorDetailsDialog(details: string) {
if (!details) return;

return alert(details);
const makeString = (error: unknown, indent: string): string => {
let message = indent + String(error);
if (error instanceof Object && 'cause' in error && error.cause) {
message += `\n${indent}Cause: `;
message += makeString(error.cause, indent + ' ');
}
return message;
};
return alert(makeString(error, ''));
}

//#endregion UI dialogs

// Helpers:
Expand Down
34 changes: 0 additions & 34 deletions client/src/www/app/error_localizer.ts

This file was deleted.

11 changes: 3 additions & 8 deletions client/src/www/app/main.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,10 @@ import {installDefaultMethodChannel, MethodChannel} from './method_channel';
import {VpnApi} from './outline_server_repository/vpn';
import {CordovaVpnApi} from './outline_server_repository/vpn.cordova';
import {OutlinePlatform} from './platform';
import {
OUTLINE_PLUGIN_NAME,
pluginExec,
pluginExecWithErrorCode,
} from './plugin.cordova';
import {OUTLINE_PLUGIN_NAME, pluginExec} from './plugin.cordova';
import {AbstractUpdater} from './updater';
import * as interceptors from './url_interceptor';
import {NoOpVpnInstaller, VpnInstaller} from './vpn_installer';
import {PlatformError} from '../model/platform_error';
import {SentryErrorReporter, Tags} from '../shared/error_reporter';

const hasDeviceSupport = cordova.platformId !== 'browser';
Expand Down Expand Up @@ -78,10 +73,10 @@ class CordovaErrorReporter extends SentryErrorReporter {
class CordovaMethodChannel implements MethodChannel {
invokeMethod(methodName: string, params: string): Promise<string> {
try {
return pluginExecWithErrorCode('invokeMethod', methodName, params);
return pluginExec('invokeMethod', methodName, params);
} catch (e) {
console.debug('invokeMethod failed', methodName, e);
throw PlatformError.parseFrom(e);
throw e;
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions client/src/www/app/main.electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {AbstractUpdater} from './updater';
import {UrlInterceptor} from './url_interceptor';
import {VpnInstaller} from './vpn_installer';
import {ErrorCode, OutlinePluginError} from '../model/errors';
import {PlatformError} from '../model/platform_error';
import {deserializeError} from '../model/platform_error';
import {
getSentryBrowserIntegrations,
OutlineErrorReporter,
Expand Down Expand Up @@ -137,7 +137,7 @@ class ElectronMethodChannel implements MethodChannel {
params
);
} catch (e) {
throw PlatformError.parseFrom(e);
throw deserializeError(e);
}
}
}
Expand Down
16 changes: 11 additions & 5 deletions client/src/www/app/outline_server_repository/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,18 @@ export async function parseTunnelConfig(
tunnelConfigText: string
): Promise<TunnelConfigJson | null> {
tunnelConfigText = tunnelConfigText.trim();
if (tunnelConfigText.startsWith('ss://')) {
return staticKeyToTunnelConfig(tunnelConfigText);
let responseJson;
try {
if (tunnelConfigText.startsWith('ss://')) {
return staticKeyToTunnelConfig(tunnelConfigText);
}
responseJson = JSON.parse(tunnelConfigText);
} catch (err) {
throw new errors.InvalidServiceConfiguration('Invalid config format', {
cause: err,
});
}

const responseJson = JSON.parse(tunnelConfigText);

if ('error' in responseJson) {
throw new errors.SessionProviderError(
responseJson.error.message,
Expand Down Expand Up @@ -165,7 +171,7 @@ export async function parseAccessKey(

throw new TypeError('Access Key is not a ss:// or ssconf:// URL');
} catch (e) {
throw new errors.ServerAccessKeyInvalid('Invalid static access key.', {
throw new errors.InvalidServiceConfiguration('Invalid static access key.', {
cause: e,
});
}
Expand Down
4 changes: 2 additions & 2 deletions client/src/www/app/outline_server_repository/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ async function fetchTunnelConfig(
)
).trim();
if (!responseBody) {
throw new errors.ServerAccessKeyInvalid(
throw new errors.InvalidServiceConfiguration(
'Got empty config from dynamic key.'
);
}
Expand All @@ -159,7 +159,7 @@ async function fetchTunnelConfig(
throw cause;
}

throw new errors.ServerAccessKeyInvalid(
throw new errors.InvalidServiceConfiguration(
'Failed to parse VPN information fetched from dynamic access key.',
{cause}
);
Expand Down
8 changes: 4 additions & 4 deletions client/src/www/app/outline_server_repository/vpn.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import {StartRequestJson, VpnApi, TunnelStatus} from './vpn';
import * as errors from '../../model/errors';
import {OUTLINE_PLUGIN_NAME, pluginExecWithErrorCode} from '../plugin.cordova';
import {OUTLINE_PLUGIN_NAME, pluginExec} from '../plugin.cordova';

export class CordovaVpnApi implements VpnApi {
constructor() {}
Expand All @@ -23,7 +23,7 @@ export class CordovaVpnApi implements VpnApi {
if (!request.config) {
throw new errors.IllegalServerConfiguration();
}
return pluginExecWithErrorCode<void>(
return pluginExec<void>(
'start',
// TODO(fortuna): Make the Cordova plugin take a StartRequestJson.
request.id,
Expand All @@ -33,11 +33,11 @@ export class CordovaVpnApi implements VpnApi {
}

stop(id: string) {
return pluginExecWithErrorCode<void>('stop', id);
return pluginExec<void>('stop', id);
}

isRunning(id: string) {
return pluginExecWithErrorCode<boolean>('isRunning', id);
return pluginExec<boolean>('isRunning', id);
}

onStatusChange(listener: (id: string, status: TunnelStatus) => void): void {
Expand Down
22 changes: 9 additions & 13 deletions client/src/www/app/plugin.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {PlatformError} from '../model/platform_error';
import {deserializeError} from '../model/platform_error';

export const OUTLINE_PLUGIN_NAME = 'OutlinePlugin';

// Helper function to call the Outline Cordova plugin.
export function pluginExec<T>(cmd: string, ...args: unknown[]): Promise<T> {
return new Promise<T>((resolve, reject) => {
cordova.exec(resolve, reject, OUTLINE_PLUGIN_NAME, cmd, args);
});
}

export async function pluginExecWithErrorCode<T>(
export async function pluginExec<T>(
cmd: string,
...args: unknown[]
): Promise<T> {
try {
return await pluginExec<T>(cmd, ...args);
} catch (e) {
throw PlatformError.parseFrom(e);
}
return new Promise<T>((resolve, reject) => {
try {
cordova.exec(resolve, reject, OUTLINE_PLUGIN_NAME, cmd, args);
} catch (e) {
throw deserializeError(e);
}
});
}
10 changes: 2 additions & 8 deletions client/src/www/model/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ export class SessionConfigFetchFailed extends CustomError {
super(message, options);
}
}

export class SessionConfigError extends CustomError {
constructor(message: string, options?: {cause?: Error}) {
super(message, options);
}
}

// SessionProviderError is the error that a provider can specify in a dynamic key.
export class SessionProviderError extends CustomError {
readonly details: string | undefined;

Expand All @@ -62,7 +56,7 @@ export class SessionProviderError extends CustomError {
}
}

export class ServerAccessKeyInvalid extends CustomError {
export class InvalidServiceConfiguration extends CustomError {
constructor(message: string, options?: {cause?: Error}) {
super(message, options);
}
Expand Down
Loading

0 comments on commit a7e6a91

Please sign in to comment.