diff --git a/src/www/app/app.ts b/src/www/app/app.ts index 9e01c7df66..0f8795ffe1 100644 --- a/src/www/app/app.ts +++ b/src/www/app/app.ts @@ -599,10 +599,10 @@ export class App { connectionState: ServerConnectionState.DISCONNECTED, }; - if (server.error) { + if (server.providerErrorResponse) { serverListItem.message = { type: 'error', - content: server.error, + content: server.providerErrorResponse.message, }; } diff --git a/src/www/app/outline_server_repository/access_key_serialization.ts b/src/www/app/outline_server_repository/access_key_serialization.ts index 17dcc560ce..4f1c53c430 100644 --- a/src/www/app/outline_server_repository/access_key_serialization.ts +++ b/src/www/app/outline_server_repository/access_key_serialization.ts @@ -36,8 +36,14 @@ export function staticKeyToShadowsocksSessionConfig(staticKey: string): Shadowso } } -function parseShadowsocksSessionConfigJson(maybeJsonText: string): ShadowsocksSessionConfig | null { - const {method, password, server, server_port, prefix} = JSON.parse(maybeJsonText); +function parseShadowsocksSessionConfigJson( + maybeJsonText: string +): ShadowsocksSessionConfig | errors.ProviderErrorResponse | null { + const {method, password, server, server_port, prefix, ...rest} = JSON.parse(maybeJsonText); + + if ('code' in rest && 'message' in rest) { + return rest as errors.ProviderErrorResponse; + } // These are the mandatory keys. const missingKeys = []; @@ -63,7 +69,9 @@ function parseShadowsocksSessionConfigJson(maybeJsonText: string): ShadowsocksSe // fetches information from a dynamic access key and attempts to parse it // TODO(daniellacosse): unit tests -export async function fetchShadowsocksSessionConfig(configLocation: URL): Promise { +export async function fetchShadowsocksSessionConfig( + configLocation: URL +): Promise { let response; try { response = await fetch(configLocation, {cache: 'no-store', redirect: 'follow'}); @@ -78,6 +86,10 @@ export async function fetchShadowsocksSessionConfig(configLocation: URL): Promis return staticKeyToShadowsocksSessionConfig(responseBody); } + if (responseBody.toLocaleUpperCase().includes('ERROR')) { + return {code: 0, message: responseBody, details: {}}; + } + return parseShadowsocksSessionConfigJson(responseBody); } catch (cause) { throw new errors.ServerAccessKeyInvalid('Failed to parse VPN information fetched from dynamic access key.', { diff --git a/src/www/app/outline_server_repository/server.ts b/src/www/app/outline_server_repository/server.ts index ed94aa24ee..bcf9133310 100644 --- a/src/www/app/outline_server_repository/server.ts +++ b/src/www/app/outline_server_repository/server.ts @@ -28,6 +28,7 @@ export class OutlineServer implements Server { private static readonly SUPPORTED_CIPHERS = ['chacha20-ietf-poly1305', 'aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm']; errorMessageId?: string; + providerErrorResponse?: errors.ProviderErrorResponse<{}>; private sessionConfig?: ShadowsocksSessionConfig; constructor( @@ -96,7 +97,15 @@ export class OutlineServer implements Server { async connect() { if (this.type === ServerType.DYNAMIC_CONNECTION) { - this.sessionConfig = await fetchShadowsocksSessionConfig(this.sessionConfigLocation); + const sessionConfigOrError = await fetchShadowsocksSessionConfig(this.sessionConfigLocation); + + if ('code' in sessionConfigOrError && 'message' in sessionConfigOrError) { + this.providerErrorResponse = sessionConfigOrError as errors.ProviderErrorResponse; + + throw new errors.SessionConfigFetchFailed('Failed to fetch VPN information from dynamic access key.'); + } + + this.sessionConfig = sessionConfigOrError as ShadowsocksSessionConfig; } try { diff --git a/src/www/model/errors.ts b/src/www/model/errors.ts index a6f35d0089..da13d8f6af 100644 --- a/src/www/model/errors.ts +++ b/src/www/model/errors.ts @@ -15,6 +15,12 @@ import {Server} from './server'; import {CustomError} from '../../infrastructure/custom_error'; +export interface ProviderErrorResponse { + code: number; + message: string; + details: T; +} + export class ServerAlreadyAdded extends CustomError { constructor(public readonly server: Server) { super(); diff --git a/src/www/model/server.ts b/src/www/model/server.ts index 02babaf181..985c42fdf7 100644 --- a/src/www/model/server.ts +++ b/src/www/model/server.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {ProviderErrorResponse} from './errors'; + // TODO: add guidelines for this file export enum ServerType { @@ -34,9 +36,6 @@ export interface Server { // The name of this server, as given by the user. name: string; - // Error returned pertaining to the server's status - error?: string; - // The location to pull the session config from on each connection. sessionConfigLocation?: URL; @@ -51,6 +50,9 @@ export interface Server { // must match one of the localized app message. errorMessageId?: string; + // Provider error returned pertaining to the server's status + providerErrorResponse?: ProviderErrorResponse; + // Connects to the server, redirecting the device's traffic. connect(): Promise;