Skip to content
This repository was archived by the owner on Mar 22, 2024. It is now read-only.

languageClientWrapper: Reject start with unreachable web socket or web worker url #34

Merged
merged 2 commits into from
Aug 18, 2023
Merged
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
10 changes: 8 additions & 2 deletions packages/examples/src/wrapperAdvanced.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EditorAppConfigClassic, MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper';
import { EditorAppConfigClassic, LanguageClientError, MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper';
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js';
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js';

Expand Down Expand Up @@ -34,6 +34,7 @@ Same again.`
languageClientConfig: {
options: {
$type: 'WebSocket',
name: 'wrapper42 language client',
host: 'localhost',
port: 3000,
path: 'sampleServer',
Expand Down Expand Up @@ -110,6 +111,7 @@ const sleepOne = (milliseconds: number) => {
setTimeout(async () => {
alert(`Updating editors after ${milliseconds}ms`);

wrapper42Config.languageClientConfig = undefined;
const appConfig42 = wrapper42Config.wrapperConfig.editorAppConfig as EditorAppConfigClassic;
appConfig42.languageId = 'javascript';
appConfig42.useDiffEditor = false;
Expand Down Expand Up @@ -157,9 +159,13 @@ const sleepTwo = (milliseconds: number) => {
};

try {
await startWrapper42();
await startWrapper43();
await startWrapper44();
try {
await startWrapper42();
} catch (e) {
console.log(`Catched expected connection error: ${(e as LanguageClientError).message}`);
}

// change the editors config, content or swap normal and diff editors after five seconds
sleepOne(5000);
Expand Down
2 changes: 2 additions & 0 deletions packages/monaco-editor-wrapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
WorkerConfigOptions,
WorkerConfigDirect,
LanguageClientConfig,
LanguageClientError
} from './languageClientWrapper.js';

import {
Expand Down Expand Up @@ -63,6 +64,7 @@ export type {
WorkerConfigOptions,
WorkerConfigDirect,
LanguageClientConfig,
LanguageClientError,
UserConfig,
ModelUpdate
};
Expand Down
78 changes: 60 additions & 18 deletions packages/monaco-editor-wrapper/src/languageClientWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ export type WebSocketCallOptions = {
reportStatus?: boolean;
}

export type LanguageClientConfigBase = {
name?: string;
}

export type LanguageClientConfigType = 'WebSocket' | 'WebSocketUrl' | 'WorkerConfig' | 'Worker';

export type WebSocketUrl = {
export type WebSocketUrl = LanguageClientConfigBase & {
secured: boolean;
host: string;
port?: number;
path?: string;
}

export type WebSocketConfigOptions = {
export type WebSocketConfigOptions = LanguageClientConfigBase & {
$type: 'WebSocket'
secured: boolean;
host: string;
Expand All @@ -30,21 +34,20 @@ export type WebSocketConfigOptions = {
stopOptions?: WebSocketCallOptions;
}

export type WebSocketConfigOptionsUrl = {
export type WebSocketConfigOptionsUrl = LanguageClientConfigBase & {
$type: 'WebSocketUrl'
url: string;
startOptions?: WebSocketCallOptions;
stopOptions?: WebSocketCallOptions;
}

export type WorkerConfigOptions = {
export type WorkerConfigOptions = LanguageClientConfigBase & {
$type: 'WorkerConfig'
url: URL;
type: 'classic' | 'module';
name?: string;
};

export type WorkerConfigDirect = {
export type WorkerConfigDirect = LanguageClientConfigBase & {
$type: 'WorkerDirect';
worker: Worker;
};
Expand All @@ -55,16 +58,23 @@ export type LanguageClientConfig = {
initializationOptions?: any;
}

export type LanguageClientError = {
message: string;
error: Error | string;
};

export class LanguageClientWrapper {

private languageClient: MonacoLanguageClient | undefined;
private languageClientConfig?: LanguageClientConfig;
private worker: Worker | undefined;
private languageId: string | undefined;
private name;

constructor(languageClientConfig?: LanguageClientConfig) {
if (languageClientConfig) {
this.languageClientConfig = languageClientConfig;
this.name = this.languageClientConfig.options.name ?? 'unnamed';
}
}

Expand Down Expand Up @@ -94,10 +104,13 @@ export class LanguageClientWrapper {

async start() {
if (this.languageClientConfig) {
console.log('Starting monaco-languageclient');
await this.startLanguageClientConnection();
return this.startLanguageClientConnection();
} else {
await Promise.reject('Unable to start monaco-languageclient. No configuration was provided.');
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Unable to start monaco-languageclient. No configuration was provided.`,
error: 'No error was provided.'
};
return Promise.reject(languageClientError);
}
}

Expand All @@ -118,7 +131,11 @@ export class LanguageClientWrapper {
console.log('Re-Starting monaco-languageclient');
await this.startLanguageClientConnection();
} else {
await Promise.reject('Unable to restart languageclient. No configuration was provided.');
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Unable to restart languageclient. No configuration was provided.`,
error: 'No error was provided.'
};
await Promise.reject(languageClientError);
}
}

Expand All @@ -141,6 +158,13 @@ export class LanguageClientWrapper {
};
this.handleLanguageClientStart(messageTransports, resolve, reject);
};
webSocket.onerror = (ev: Event) => {
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Websocket connection failed.`,
error: (ev as ErrorEvent).error ?? 'No error was provided.'
};
reject(languageClientError);
};
} else {
if (!this.worker) {
if (lcConfig?.$type === 'WorkerConfig') {
Expand All @@ -149,6 +173,14 @@ export class LanguageClientWrapper {
type: workerConfig.type,
name: workerConfig.name
});

this.worker.onerror = (ev) => {
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Illegal worker configuration detected. Potentially the url is wrong.`,
error: ev.error ?? 'No error was provided.'
};
reject(languageClientError);
};
} else {
const workerDirectConfig = lcConfig as WorkerConfigDirect;
this.worker = workerDirectConfig.worker;
Expand Down Expand Up @@ -190,11 +222,13 @@ export class LanguageClientWrapper {
}
}
} catch (e) {
const errorMsg = `monaco-languageclient start was unsuccessful: ${e}`;
reject(errorMsg);
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Start was unsuccessful.`,
error: (e as Error) ?? 'No error was provided.'
};
reject(languageClientError);
}
const msg = 'monaco-languageclient was successfully started.';
resolve(msg);
resolve(`languageClientWrapper (${this.name}): Start was successfully.`);
}

private createLanguageClient(transports: MessageTransports): MonacoLanguageClient {
Expand Down Expand Up @@ -231,19 +265,27 @@ export class LanguageClientWrapper {
this.languageClient = undefined;
await Promise.resolve('monaco-languageclient and monaco-editor were successfully disposed.');
} catch (e) {
await Promise.reject(`Disposing the monaco-languageclient resulted in error: ${e}`);
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Disposing the monaco-languageclient resulted in error.`,
error: (e as Error) ?? 'No error was provided.'
};
await Promise.reject(languageClientError);
}
}
else {
await Promise.reject('Unable to dispose monaco-languageclient: It is not yet started.');
const languageClientError: LanguageClientError = {
message: `languageClientWrapper (${this.name}): Unable to dispose monaco-languageclient: It is not yet started.`,
error: 'No error was provided.'
};
await Promise.reject(languageClientError);
}
}

reportStatus() {
const status: string[] = [];
status.push('LanguageClientWrapper status:');
status.push(`LanguageClient: ${this.getLanguageClient()}`);
status.push(`Worker: ${this.getWorker()}`);
status.push(`LanguageClient: ${this.getLanguageClient()} `);
status.push(`Worker: ${this.getWorker()} `);
return status;
}
}
54 changes: 50 additions & 4 deletions packages/monaco-editor-wrapper/test/languageClientWrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,68 @@ describe('Test LanguageClientWrapper', () => {
expect(languageClientWrapper.isStarted()).toBeFalsy();
});

test('Start: no config', async () => {
test('Constructor: no config', async () => {
const languageClientWrapper = new LanguageClientWrapper();
expect(async () => {
await languageClientWrapper.start();
}).rejects.toEqual('Unable to start monaco-languageclient. No configuration was provided.');
}).rejects.toEqual({
message: 'languageClientWrapper (undefined): Unable to start monaco-languageclient. No configuration was provided.',
error: 'No error was provided.'
});
});

test('Start: config', async () => {
test('Constructor: config', async () => {
const languageClientConfig: LanguageClientConfig = {
options: {
$type: 'WebSocketUrl',
url: 'ws://localhost:3000/sampleServer'
url: 'ws://localhost:12345/Tester'
}
};
const languageClientWrapper = new LanguageClientWrapper(languageClientConfig);
expect(languageClientWrapper.haveLanguageClientConfig()).toBeTruthy();
});

test('Start: unreachable url', async () => {
const languageClientConfig: LanguageClientConfig = {
options: {
$type: 'WebSocketUrl',
url: 'ws://localhost:12345/Tester',
name: 'test-unreachable'
}
};
const languageClientWrapper = new LanguageClientWrapper(languageClientConfig);
expect(languageClientWrapper.haveLanguageClientConfig()).toBeTruthy();
await expect(languageClientWrapper.start()).rejects.toEqual({
message: 'languageClientWrapper (test-unreachable): Websocket connection failed.',
error: 'No error was provided.'
});
});

test('Only unreachable worker url', async () => {
const prom = new Promise((_resolve, reject) => {
const worker = new Worker('aBogusUrl');

worker.onerror = () => {
reject('error');
};
});
await expect(prom).rejects.toEqual('error');
});

test('Start: unreachable worker url', async () => {
const languageClientConfig: LanguageClientConfig = {
options: {
$type: 'WorkerConfig',
url: new URL('http://localhost:63315'),
type: 'classic'
}
};
const languageClientWrapper = new LanguageClientWrapper(languageClientConfig);
expect(languageClientWrapper.haveLanguageClientConfig()).toBeTruthy();
await expect(languageClientWrapper.start()).rejects.toEqual({
message: 'languageClientWrapper (unnamed): Illegal worker configuration detected. Potentially the url is wrong.',
error: 'No error was provided.'
});
});

});