Skip to content

Commit

Permalink
Merge branch 'master' into fortuna-go-config
Browse files Browse the repository at this point in the history
  • Loading branch information
fortuna committed Jan 24, 2025
2 parents 5beaddd + a7e6a91 commit 0fa5ab0
Show file tree
Hide file tree
Showing 24 changed files with 942 additions and 3,501 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
name: Lint

concurrency:
group: ${{ github.head_ref || github.ref }}
group: ${{ github.head_ref || github.ref }}-lint
cancel-in-progress: true

on:
Expand Down
10 changes: 9 additions & 1 deletion client/electron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ You can download the binary tarball, or [use a package manager](https://github.c
brew install zig
```

## Requirements for building from Windows

- Visual Studio 2022 or later

If you see an error about `node-gyp` not finding Visual Studio, try one of the following:

- (Recommended) Install the VSSetup PowerShell package using `Install-Module VSSetup -Scope CurrentUser`
- Change PowerShell LanguageMode using `$ExecutionContext.SessionState.LanguageMode="FullLanguage"`

## Release

To build the _release_ version of Windows installer, you'll also need:

- [Java 8+ Runtime](https://www.java.com/en/download/). This is required for the cross-platform Windows executable signing tool [Jsign](https://ebourg.github.io/jsign/). If you don't need to sign the executables, feel free to skip this.

2 changes: 1 addition & 1 deletion client/electron/go_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let invokeMethodFunc: Function | undefined;
* Ensure that the function signature and data structures are consistent with the C definitions
* in `./client/go/outline/electron/go_plugin.go`.
*/
export async function invokeMethod(
export async function invokeGoMethod(
method: string,
input: string
): Promise<string> {
Expand Down
90 changes: 48 additions & 42 deletions client/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
import {autoUpdater} from 'electron-updater';

import {lookupIp} from './connectivity';
import {invokeMethod} from './go_plugin';
import {invokeGoMethod} from './go_plugin';
import {GoVpnTunnel} from './go_vpn_tunnel';
import {installRoutingServices, RoutingDaemon} from './routing_service';
import {TunnelStore} from './tunnel_store';
Expand Down Expand Up @@ -529,53 +529,59 @@ function main() {
// If the function encounters an error, it throws an Error that can be parsed by the `PlatformError`.
ipcMain.handle(
'outline-ipc-invoke-method',
(_, method: string, params: string): Promise<string> =>
invokeMethod(method, params)
);

// Connects to a proxy server specified by a config.
//
// If any issues occur, an Error will be thrown, which you can try-catch around
// `ipcRenderer.invoke`. But you should avoid depending on the specific error type.
// Instead, you should use its message property (which would probably be a JSON representation
// of a PlatformError). See https://github.com/electron/electron/issues/24427.
//
// TODO: refactor channel name and namespace to a constant
ipcMain.handle(
'outline-ipc-start-proxying',
async (_, request: StartRequestJson): Promise<void> => {
// TODO: Rather than first disconnecting, implement a more efficient switchover (as well as
// being faster, this would help prevent traffic leaks - the Cordova clients already do
// this).
if (currentTunnel) {
console.log('disconnecting from current server...');
currentTunnel.disconnect();
await currentTunnel.onceDisconnected;
}
async (_, method: string, params: string): Promise<string> => {
// TODO(fortuna): move all other IPCs here.
switch (method) {
case 'StartProxying': {
// Connects to a proxy server specified by a config.
//
// If any issues occur, an Error will be thrown, which you can try-catch around
// `ipcRenderer.invoke`. But you should avoid depending on the specific error type.
// Instead, you should use its message property (which would probably be a JSON representation
// of a PlatformError). See https://github.com/electron/electron/issues/24427.
//
// TODO: refactor channel name and namespace to a constant
const request = JSON.parse(params) as StartRequestJson;
// TODO: Rather than first disconnecting, implement a more efficient switchover (as well as
// being faster, this would help prevent traffic leaks - the Cordova clients already do
// this).
if (currentTunnel) {
console.log('disconnecting from current server...');
currentTunnel.disconnect();
await currentTunnel.onceDisconnected;
}

console.log(`connecting to ${request.name} (${request.id})...`);

try {
await startVpn(request, false);
console.log(`connected to ${request.name} (${request.id})`);
await setupAutoLaunch(request);
// Auto-connect requires IPs; the hostname in here has already been resolved (see above).
tunnelStore.save(request).catch(() => {
console.error('Failed to store tunnel.');
});
} catch (e) {
console.error('could not connect:', e);
// clean up the state, no need to await because stopVpn might throw another error which can be ignored
stopVpn();
throw e;
}
break;
}

console.log(`connecting to ${request.name} (${request.id})...`);
case 'StopProxying':
// Disconnects from the current server, if any.
// TODO: refactor channel name and namespace to a constant
await stopVpn();
return '';

try {
await startVpn(request, false);
console.log(`connected to ${request.name} (${request.id})`);
await setupAutoLaunch(request);
// Auto-connect requires IPs; the hostname in here has already been resolved (see above).
tunnelStore.save(request).catch(() => {
console.error('Failed to store tunnel.');
});
} catch (e) {
console.error('could not connect:', e);
// clean up the state, no need to await because stopVpn might throw another error which can be ignored
stopVpn();
throw e;
default:
return await invokeGoMethod(method, params);
}
}
);

// Disconnects from the current server, if any.
// TODO: refactor channel name and namespace to a constant
ipcMain.handle('outline-ipc-stop-proxying', stopVpn);

// Install backend services and return the error code
// TODO: refactor channel name and namespace to a constant
ipcMain.handle('outline-ipc-install-outline-services', async () => {
Expand Down
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
6 changes: 3 additions & 3 deletions client/electron/vpn_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {invokeMethod} from './go_plugin';
import {invokeGoMethod} from './go_plugin';
import {
StartRequestJson,
TunnelStatus,
Expand Down Expand Up @@ -71,13 +71,13 @@ export async function establishVpn(request: StartRequestJson) {
transport: request.config.transport,
};

await invokeMethod('EstablishVPN', JSON.stringify(config));
await invokeGoMethod('EstablishVPN', JSON.stringify(config));
statusCb?.(currentRequestId, TunnelStatus.CONNECTED);
}

export async function closeVpn(): Promise<void> {
statusCb?.(currentRequestId!, TunnelStatus.DISCONNECTING);
await invokeMethod('CloseVPN', '');
await invokeGoMethod('CloseVPN', '');
statusCb?.(currentRequestId!, TunnelStatus.DISCONNECTED);
}

Expand Down
20 changes: 10 additions & 10 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
"codrova-osx": "Version-controlled platform config files at src/cordova/apple/xcode/osx/Outline/config.xml, src/cordova/apple/xcode/osx/osx.json, and src/cordova/apple/xcode/osx/www/cordova_plugins.js as a workaround for https://github.com/apache/cordova-osx/issues/106. Delete these files when the issue is fixed."
},
"dependencies": {
"@material/mwc-button": "^0.25.3",
"@material/mwc-button": "^0.27.0",
"@material/mwc-circular-progress": "^0.27.0",
"@material/mwc-formfield": "^0.25.3",
"@material/mwc-icon-button": "^0.25.3",
"@material/mwc-menu": "^0.25.3",
"@material/mwc-radio": "^0.25.3",
"@material/mwc-select": "^0.25.3",
"@material/mwc-textarea": "^0.25.3",
"@material/mwc-textfield": "^0.25.3",
"@material/mwc-formfield": "^0.27.0",
"@material/mwc-icon-button": "^0.27.0",
"@material/mwc-menu": "^0.27.0",
"@material/mwc-radio": "^0.27.0",
"@material/mwc-select": "^0.27.0",
"@material/mwc-textarea": "^0.27.0",
"@material/mwc-textfield": "^0.27.0",
"@material/web": "^2.0.0",
"@outline/infrastructure": "file:../infrastructure",
"@polymer/app-layout": "^3.1.0",
Expand Down Expand Up @@ -50,7 +50,7 @@
"element-internals-polyfill": "^1.3.12",
"fs-extra": "^11.2.0",
"koffi": "^2.9.1",
"lit": "^2.2.2",
"lit": "^3.2.1",
"ShadowsocksConfig": "github:Jigsaw-Code/outline-shadowsocksconfig#v0.2.1",
"socks": "^1.1.10",
"sudo-prompt": "^9.2.1",
Expand Down Expand Up @@ -107,8 +107,8 @@
"karma-webpack": "^5.0.0",
"minimist": "^1.2.6",
"node-fetch": "^3.3.0",
"node-loader": "^2.0.0",
"node-gyp": "^10.0.1",
"node-loader": "^2.0.0",
"postcss": "^7.0.39",
"postcss-rtl": "^1.7.3",
"prettier": "^2.8.0",
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.

8 changes: 2 additions & 6 deletions client/src/www/app/main.cordova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ 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';
Expand Down Expand Up @@ -77,7 +73,7 @@ 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 e;
Expand Down
Loading

0 comments on commit 0fa5ab0

Please sign in to comment.