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

fix(client/linux): use new installation error and service path #2218

Merged
merged 4 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
18 changes: 14 additions & 4 deletions client/electron/app_paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ export function getAppPath() {

export function pathToEmbeddedTun2socksBinary() {
return path.join(
unpackedAppPath(), 'client', 'output', 'build',
(isWindows ? 'windows' : 'linux'),
'tun2socks' + (isWindows ? '.exe' : ''));
unpackedAppPath(),
'client',
'output',
'build',
isWindows ? 'windows' : 'linux',
'tun2socks' + (isWindows ? '.exe' : '')
);
}

/**
Expand All @@ -60,5 +64,11 @@ export function pathToEmbeddedOutlineService() {
if (isWindows) {
return getAppPath();
}
return path.join(unpackedAppPath(), 'client', 'tools', 'outline_proxy_controller', 'dist');
return path.join(
unpackedAppPath(),
'client',
'electron',
'linux_proxy_controller',
'dist'
);
}
34 changes: 27 additions & 7 deletions client/electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@

import * as os from 'node:os';

import {clipboard, contextBridge, ipcRenderer, IpcRendererEvent} from 'electron';
import {
clipboard,
contextBridge,
ipcRenderer,
IpcRendererEvent,
} from 'electron';
import '@sentry/electron/preload';

/**
Expand All @@ -46,18 +51,27 @@ export class ElectronRendererMethodChannel {

// TODO: replace the `any` with a better type once we unify the IPC call framework
/* eslint-disable @typescript-eslint/no-explicit-any */
readonly invoke = async (channel: string, ...args: unknown[]): Promise<any> => {
readonly invoke = async (
channel: string,
...args: unknown[]
): Promise<any> => {
const ipcName = `${this.namespace}-${channel}`;
try {
await ipcRenderer.invoke(ipcName, ...args);
return await ipcRenderer.invoke(ipcName, ...args);
} catch (e) {
// Normalize the error message to what's being thrown in the IPC itself
// e.message == "Error invoking remote method 'xxx': <error name>: <actual message>"
// https://github.com/electron/electron/blob/v31.0.0/lib/renderer/api/ipc-renderer.ts#L22
if (typeof e?.message === 'string') {
const errPattern = new RegExp(`'${ipcName}':\\s*(?<name>[^:]+):\\s*(?<message>.*)`, 's');
const errPattern = new RegExp(
`'${ipcName}':\\s*(?<name>[^:]+):\\s*(?<message>.*)`,
's'
);
const groups = e.message.match(errPattern)?.groups;
if (typeof groups?.['name'] === 'string' && typeof groups?.['message'] === 'string') {
if (
typeof groups?.['name'] === 'string' &&
typeof groups?.['message'] === 'string'
) {
e.name = groups['name'];
e.message = groups['message'];
}
Expand All @@ -66,11 +80,17 @@ export class ElectronRendererMethodChannel {
}
};

readonly on = (channel: string, listener: (e: IpcRendererEvent, ...args: unknown[]) => void): void => {
readonly on = (
channel: string,
listener: (e: IpcRendererEvent, ...args: unknown[]) => void
): void => {
ipcRenderer.on(`${this.namespace}-${channel}`, listener);
};

readonly once = (channel: string, listener: (e: IpcRendererEvent, ...args: unknown[]) => void): void => {
readonly once = (
channel: string,
listener: (e: IpcRendererEvent, ...args: unknown[]) => void
): void => {
ipcRenderer.once(`${this.namespace}-${channel}`, listener);
};
}
Expand Down
35 changes: 25 additions & 10 deletions client/electron/routing_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ import * as sudo from 'sudo-prompt';

import {pathToEmbeddedOutlineService} from './app_paths';
import {TunnelStatus} from '../src/www/app/outline_server_repository/vpn';
import {ErrorCode, SystemConfigurationException} from '../src/www/model/errors';
import {ErrorCode} from '../src/www/model/errors';
import {
PlatformError,
ROUTING_SERVICE_NOT_RUNNING,
} from '../src/www/model/platform_error';

const isLinux = platform() === 'linux';
const isWindows = platform() === 'win32';
Expand Down Expand Up @@ -130,11 +134,11 @@ export class RoutingDaemon {
if (this.stopping) {
cleanup();
newSocket.destroy();
reject(
new SystemConfigurationException(
'routing daemon service stopped before started'
)
const perr = new PlatformError(
ROUTING_SERVICE_NOT_RUNNING,
'routing daemon service stopped before started'
);
reject(new Error(perr.toJSON()));
} else {
fulfill();
}
Expand All @@ -154,9 +158,12 @@ export class RoutingDaemon {
const initialErrorHandler = (err: Error) => {
console.error('Routing daemon socket setup failed', err);
this.socket = null;
reject(
new SystemConfigurationException('routing daemon is not running')
const perr = new PlatformError(
ROUTING_SERVICE_NOT_RUNNING,
'routing daemon is not running',
{cause: err}
);
reject(new Error(perr.toJSON()));
};
newSocket.once('error', initialErrorHandler);
});
Expand Down Expand Up @@ -293,7 +300,10 @@ function installWindowsRoutingServices(): Promise<void> {
// build/windows
//
// Surrounding quotes important, consider "c:\program files"!
const script = `"${path.join(pathToEmbeddedOutlineService(), WINDOWS_INSTALLER_FILENAME)}"`;
const script = `"${path.join(
pathToEmbeddedOutlineService(),
WINDOWS_INSTALLER_FILENAME
)}"`;
return executeCommandAsRoot(script);
}

Expand Down Expand Up @@ -362,10 +372,15 @@ async function installLinuxRoutingServices(): Promise<void> {
installationFileDescriptors
.map(
({filename, sha256}) =>
`/usr/bin/echo "${sha256} ${path.join(tmp, filename)}" | /usr/bin/shasum -a 256 -c`
`/usr/bin/echo "${sha256} ${path.join(
tmp,
filename
)}" | /usr/bin/shasum -a 256 -c`
)
.join(' && ');
command += ` && "${path.join(tmp, LINUX_INSTALLER_FILENAME)}" "${userInfo().username}"`;
command += ` && "${path.join(tmp, LINUX_INSTALLER_FILENAME)}" "${
userInfo().username
}"`;

console.log('trying to run command as root: ', command);
await executeCommandAsRoot(command);
Expand Down
20 changes: 13 additions & 7 deletions client/src/www/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import {UrlInterceptor} from './url_interceptor';
import {VpnInstaller} from './vpn_installer';
import * as errors from '../model/errors';
import * as events from '../model/events';
import {PlatformError} from '../model/platform_error';
import {
PlatformError,
ROUTING_SERVICE_NOT_RUNNING,
} from '../model/platform_error';
import {Server} from '../model/server';
import {OutlineErrorReporter} from '../shared/error_reporter';
import {ServerConnectionState, ServerListItem} from '../views/servers_view';
Expand Down Expand Up @@ -543,12 +546,15 @@ export class App {
connectionState: ServerConnectionState.DISCONNECTED,
});
console.error(`could not connect to server ${serverId}: ${e}`);
if (e instanceof errors.SystemConfigurationException) {
if (
await this.showConfirmationDialog(
this.localize('outline-services-installation-confirmation')
)
) {
if (
e instanceof PlatformError &&
e.code === ROUTING_SERVICE_NOT_RUNNING
) {
const confirmation =
this.localize('outline-services-installation-confirmation') +
'\n\n--------------------\n' +
e.toString();
if (await this.showConfirmationDialog(confirmation)) {
await this.installVpnService();
return;
}
Expand Down
1 change: 1 addition & 0 deletions client/src/www/app/main.electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class ElectronVpnInstaller implements VpnInstaller {
const err = await window.electron.methodChannel.invoke(
'install-outline-services'
);
console.warn('Install Service Error ???', err);
jyyi1 marked this conversation as resolved.
Show resolved Hide resolved

// catch custom errors (even simple as numbers) does not work for ipcRenderer:
// https://github.com/electron/electron/issues/24427
Expand Down
41 changes: 41 additions & 0 deletions client/src/www/model/platform_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,35 @@ function convertRawErrorObjectToPlatformError(rawObj: object): PlatformError {
return new PlatformError(code, rawObj.message, options);
}

/**
* Recursively converts a {@link PlatformError} into a raw JavaScript object that
* could be converted into a JSON string.
* @param {PlatformError} platErr Any non-null PlatformError.
* @returns {object} A plain JavaScript object that can be converted to JSON.
*/
function convertPlatformErrorToRawErrorObject(platErr: PlatformError): object {
const rawObj: {
code: string;
message: string;
details?: ErrorDetails;
cause?: object;
} = {
code: platErr.code,
message: platErr.message,
details: platErr.details,
};
if (platErr.cause) {
let cause: PlatformError;
if (platErr.cause instanceof PlatformError) {
cause = platErr.cause;
} else {
cause = new PlatformError(INTERNAL_ERROR, String(platErr.cause));
}
rawObj.cause = convertPlatformErrorToRawErrorObject(cause);
}
return rawObj;
}

/**
* ErrorDetails represents the details map of a {@link PlatformError}.
* The keys in this map are strings, and the values can be of any data type.
Expand Down Expand Up @@ -187,6 +216,15 @@ export class PlatformError extends CustomError {
}
return result;
}

/**
* Returns a JSON string of this error with all details and causes.
* @returns {string} The JSON string representing this error.
*/
toJSON(): string {
const errRawObj = convertPlatformErrorToRawErrorObject(this);
return JSON.stringify(errRawObj);
}
}

//////
Expand All @@ -209,3 +247,6 @@ export const VPN_PERMISSION_NOT_GRANTED = 'ERR_VPN_PERMISSION_NOT_GRANTED';

export const PROXY_SERVER_UNREACHABLE: ErrorCode =
'ERR_PROXY_SERVER_UNREACHABLE';

/** Indicates that the OS routing service is not running (electron only). */
export const ROUTING_SERVICE_NOT_RUNNING = 'ERR_ROUTING_SERVICE_NOT_RUNNING';
Loading