import {WebviewApi} from 'vscode-webview';
import {EventListener, Event} from './event/Event';
import {RequestResponseType, Response, Request, RequestHandler} from './request/Request';
import {MessageType} from '../utils/Types';
import {getIdeType} from '../utils/Utils';
type AnyPromiseResolve = (value: (any | PromiseLike<any>)) => void;
type AnyPromiseReject = (reason?: any) => void;
export abstract class MessageManager {
private static INSTANCE: MessageManager | undefined = undefined;
private readonly eventListeners: Map<string, EventListener<any>[]> = new Map();
private curReqId: number = 0;
private readonly pendingRequests: Map<number, [AnyPromiseResolve, AnyPromiseReject]> = new Map();
private readonly requestHandlers: Map<string, RequestHandler<any, any>> = new Map();
public static instance(): MessageManager {
if (MessageManager.INSTANCE !== undefined) {
return MessageManager.INSTANCE;
}
switch (getIdeType()) {
case 'VSCode':
MessageManager.INSTANCE = new VSCodeMessageManager();
break;
case 'Standalone':
MessageManager.INSTANCE = new StandaloneMessageManager();
break;
case 'Deveco':
MessageManager.INSTANCE = DevecoMessageManager.DEVECO_INSTANCE;
break;
case 'vNext':
MessageManager.INSTANCE = new VNextMessageManager();
break;
default:
throw Error('wrong type');
}
MessageManager.INSTANCE.init();
return MessageManager.INSTANCE;
}
public sendEvent<T>(event: Event<T>): string | undefined {
return this.sendRawMessage(JSON.stringify(event));
}
public registerEventListener<T>(listener: EventListener<T>): void {
if (!this.eventListeners.has(listener.eventType.command)) {
this.eventListeners.set(listener.eventType.command, []);
}
let commandListeners = this.eventListeners.get(listener.eventType.command);
if (commandListeners === undefined || commandListeners === null) {
return;
}
commandListeners.push(listener);
}
public async sendRequest<REQ_BODY, RESP_BODY>(requestType: RequestResponseType<REQ_BODY, RESP_BODY>,
reqBody: REQ_BODY, hasTimeout?: boolean ): Promise<RESP_BODY> {
let request = requestType.createRequest(reqBody, this.curReqId++);
let id = request.id;
let combined;
let promise = new Promise<RESP_BODY>((resolve, reject) => this.pendingRequests.set(id, [resolve, reject]));
let timeout = new Promise<RESP_BODY>((resolve, reject) =>
setTimeout((): void => reject(new Error(`${requestType.command} timeout after 10000ms`)), 10000));
if (hasTimeout) {
combined = Promise.race([promise, timeout]);
} else {
combined = promise;
}
this.sendRawMessage(JSON.stringify(request));
return combined;
}
public handleRequest<REQ_BODY, RESP_BODY>(handler: RequestHandler<REQ_BODY, RESP_BODY>): void {
this.requestHandlers.set(handler.type.command, handler);
}
private init(): void {
this.listenRawMessage(msg => {
let msgBody = JSON.parse(msg);
let msgType = msgBody.type as MessageType;
switch (msgType) {
case 'event': {
let eventMsg = msgBody as Event<any>;
let listeners = this.eventListeners.get(eventMsg.command);
if (listeners !== undefined) {
listeners.forEach(l => l.onEventCallback(eventMsg.body));
}
break;
}
case 'request': {
let requestMsg = msgBody as Request<any>;
const handler = this.requestHandlers.get(requestMsg.command);
if (handler === undefined || handler === null) {
break;
}
let promise = handler.handler(requestMsg.body);
promise.then(value => {
let successResp = handler.type.createSuccessResponse(value, requestMsg.id);
this.sendRawMessage(JSON.stringify(successResp));
}).catch(reason => {
let errorResp = handler.type.createErrorResponse(`${reason}`, requestMsg.id);
this.sendRawMessage(JSON.stringify(errorResp));
});
break;
}
case 'response': {
let responseMsg = msgBody as Response<any>;
let resolveReject = this.pendingRequests.get(responseMsg.id);
if (resolveReject === undefined || resolveReject === null) {
break;
}
if (responseMsg.success) {
resolveReject[0](responseMsg.body);
} else {
resolveReject[1](responseMsg.errMessage);
}
break;
}
default:
throw Error('wrong type');
}
});
}
protected abstract sendRawMessage(msg: string): string | undefined;
protected abstract listenRawMessage(listener: (msg: string) => void): void;
}
class VSCodeMessageManager extends MessageManager {
private readonly webviewApi: WebviewApi<unknown> = acquireVsCodeApi();
protected sendRawMessage(msg: string): string | undefined {
try {
this.webviewApi.postMessage(msg);
return undefined;
} catch (e) {
return `post msg error ${e}`;
}
}
protected listenRawMessage(listener: (msg: string) => void): void {
window.addEventListener('message', event => listener(event.data));
}
}
class StandaloneMessageManager extends MessageManager {
protected listenRawMessage(listener: (msg: string) => void): void {
}
protected sendRawMessage(msg: string): string | undefined {
return undefined;
}
}
export class DevecoMessageManager extends MessageManager {
public static readonly DEVECO_INSTANCE: DevecoMessageManager = new DevecoMessageManager();
public readonly listeners: ((msg: string) => void)[] = [];
protected listenRawMessage(listener: (msg: string) => void): void {
this.listeners.push(listener);
}
protected sendRawMessage(msg: string): string | undefined {
(window as any).cefQuery({
request: msg,
onSuccess: (response: any) => {
},
onFailure: (responseCode: any, failMessage: any) => {
},
});
return undefined;
}
}
export class VNextMessageManager extends MessageManager {
protected listenRawMessage(listener: (msg: string) => void): void {
window.addEventListener('message', event => listener(event.data));
}
protected sendRawMessage(msg: string): string | undefined {
try {
(window as any).ipc.postMessage(msg);
return undefined;
} catch (e) {
return `post msg error ${e}`;
}
}
}