@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
342 lines (340 loc) • 12 kB
JavaScript
import { ReplaySubject, mergeMap, take } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { LogLevel } from '../diagnostics/log-level';
import { Logging } from '../diagnostics/logging';
import { EnvironmentModule } from '../manifest/environment-modules';
import { headerConstants } from './http-constants';
import { Net } from './net';
/**
* The state of Websocket connection.
*/
export var WebsocketStreamConnectionState;
(function (WebsocketStreamConnectionState) {
/**
* Initializing.
*/
WebsocketStreamConnectionState[WebsocketStreamConnectionState["Initializing"] = 1] = "Initializing";
/**
* Connected.
*/
WebsocketStreamConnectionState[WebsocketStreamConnectionState["Connected"] = 2] = "Connected";
/**
* Disconnected.
*/
WebsocketStreamConnectionState[WebsocketStreamConnectionState["Disconnected"] = 3] = "Disconnected";
/**
* Failed.
*/
WebsocketStreamConnectionState[WebsocketStreamConnectionState["Failed"] = 4] = "Failed";
/**
* Not configured.
*/
WebsocketStreamConnectionState[WebsocketStreamConnectionState["NotConfigured"] = 5] = "NotConfigured";
})(WebsocketStreamConnectionState || (WebsocketStreamConnectionState = {}));
/**
* The state of Websocket stream packet.
*/
export var WebsocketStreamState;
(function (WebsocketStreamState) {
/**
* Empty packet.
*/
WebsocketStreamState[WebsocketStreamState["Noop"] = 1] = "Noop";
/**
* Data packet.
*/
WebsocketStreamState[WebsocketStreamState["Data"] = 2] = "Data";
/**
* Error packet. (reserved for socket level error communication if any)
*/
WebsocketStreamState[WebsocketStreamState["Error"] = 3] = "Error";
})(WebsocketStreamState || (WebsocketStreamState = {}));
/**
* The request state of data such as CIM and PowerShell stream.
*/
export var WebsocketStreamDataRequestState;
(function (WebsocketStreamDataRequestState) {
/**
* empty packet.
*/
WebsocketStreamDataRequestState[WebsocketStreamDataRequestState["Noop"] = 1] = "Noop";
/**
* Data packet.
*/
WebsocketStreamDataRequestState[WebsocketStreamDataRequestState["Normal"] = 2] = "Normal";
/**
* Cancel
*/
WebsocketStreamDataRequestState[WebsocketStreamDataRequestState["Cancel"] = 3] = "Cancel";
})(WebsocketStreamDataRequestState || (WebsocketStreamDataRequestState = {}));
/**
* The response state of data such as CIM and PowerShell stream.
*/
export var WebsocketStreamDataState;
(function (WebsocketStreamDataState) {
/**
* empty packet.
*/
WebsocketStreamDataState[WebsocketStreamDataState["Noop"] = 1] = "Noop";
/**
* Completed packet.
*/
WebsocketStreamDataState[WebsocketStreamDataState["Completed"] = 2] = "Completed";
/**
* Data packet.
*/
WebsocketStreamDataState[WebsocketStreamDataState["Data"] = 3] = "Data";
/**
* Error
*/
WebsocketStreamDataState[WebsocketStreamDataState["Error"] = 4] = "Error";
/**
* Cancelled
*/
WebsocketStreamDataState[WebsocketStreamDataState["Cancelled"] = 5] = "Cancelled";
})(WebsocketStreamDataState || (WebsocketStreamDataState = {}));
export var WebsocketStreamName;
(function (WebsocketStreamName) {
WebsocketStreamName["CimStreamName"] = "SME-CIM";
WebsocketStreamName["PowerShellStreamName"] = "SME-PowerShell";
WebsocketStreamName["SshStreamName"] = "SME-SSH";
WebsocketStreamName["SystemStreamName"] = "System";
})(WebsocketStreamName || (WebsocketStreamName = {}));
;
/**
* Websocket Stream Processor class.
*/
export class WebsocketStreamProcessor {
observer;
target;
options;
/**
* Holding result if waitCompleted option is specified for multiple instances.
*/
response;
/**
* Track closing state.
*/
closing;
/**
* Track closed state.
*/
closed;
/**
* Track observer end call by unsubscribe or observer completion.
*/
end;
/**
* Sent once.
*/
sendOnce;
/**
* Initializes a new instance of the CimProcessor class.
* @param observer Observer to send back result to caller.
* @param target Stream Target object.
* @param options Options for Cim stream query.
*/
constructor(observer, target, options) {
this.observer = observer;
this.target = target;
this.options = options;
}
/**
* Push the result to the observer.
* @param result the result of TData.
*/
next(result) {
this.observer?.next(result);
this.sendOnce = true;
}
/**
* Complete the observer.
*/
complete() {
this.closing = true;
this.observer?.complete();
this.closed = true;
}
/**
* Error the observer.
*/
error(error) {
this.closing = true;
this.observer?.error(error);
this.closed = true;
}
}
/**
* The Websocket stream class.
*/
export class WebsocketStream {
gateway;
static logSourceName = 'WebsocketStream';
static maxConnectionRetries = 10;
static reconnectWaitTime = 500;
socketStateRaw = WebsocketStreamConnectionState.Disconnected;
socketState = new ReplaySubject();
socket;
connectionRetries = WebsocketStream.maxConnectionRetries;
handlers = new Map();
strings = MsftSme.getStrings().MsftSmeShell.Core.WebsocketStream;
/**
* Initializes a new instance of the WebsocketStream class.
*
* @param gateway the gateway connection object.
*/
constructor(gateway) {
this.gateway = gateway;
// initialize only after gateway data was populated via RPC.
this.gateway.initialize().pipe(mergeMap(() => this.gateway.navigationReadyObservable), take(1)).subscribe(() => {
// enable websocket stream only when the module added the options at initialization.
const global = MsftSme.self();
if (!global.Init.websocket && !global.Init.sshWebsocket) {
this.socketState.next(WebsocketStreamConnectionState.NotConfigured);
this.socketStateRaw = WebsocketStreamConnectionState.NotConfigured;
return;
}
this.initialize(true);
});
}
/**
* Register the processor for the stream name.
* @param name the name of stream.
* @param handler the handler to process packet.
*/
registerProcessor(name, handler) {
this.handlers.set(name, handler);
}
/**
* Send next stream data to websocket.
*
* @param streamName the stream name.
* @param data the data to send.
* @param options the options.
*/
sendNext(streamName, data, options) {
if (!this.socket) {
throw new Error('WebsocketStream: socket is not ready.');
}
const packet = { streamName, state: WebsocketStreamState.Data, data, options };
this.debugLog('Socket sending data.', packet);
this.socket.next(JSON.stringify(packet));
}
/**
* Send error stream data to websocket.
*
* @param streamName the stream name.
* @param error the error to send.
* @param options the options.
*/
sendError(streamName, error, options) {
if (!this.socket) {
throw new Error('WebsocketStream: socket is not ready.');
}
const packet = { streamName, state: WebsocketStreamState.Error, data: error, options };
this.debugLog('Socket sending error.', packet);
this.socket.next(JSON.stringify(packet));
}
/**
* Get target data.
* @param authorizationManager the authorization manager.
* @param nodeName the node Name
* @param endpoint the endpoint data.
* @return WebsocketStreamDataTarget target data.
*/
getTarget(authorizationManager, nodeName, endpoint) {
const headers = authorizationManager.createTokenHeaders(nodeName);
if (endpoint) {
headers[headerConstants.POWERSHELL_ENDPOINT] = endpoint;
}
const target = { nodeName, headers };
return target;
}
initialize(firstTime) {
// get gateway socket url.
const gatewaySocketUrl = this.gateway.gatewayUrl.replace('http', 'ws');
const moduleName = MsftSme.self().Init.moduleName;
let url = Net.streamSocket.format(gatewaySocketUrl, moduleName);
if (EnvironmentModule.isGatewayV200) {
url = Net.streamSocketV200.format(gatewaySocketUrl, moduleName);
}
const isSsh = MsftSme.self().Init.sshWebsocket;
if (isSsh) {
url = Net.sshStreamSocket.format(gatewaySocketUrl);
}
this.debugLog('Socket initializing...: {0}'.format(url));
if (!firstTime) {
this.handlers.forEach((value) => value.reset());
}
// create stream socket.
this.socketState.next(WebsocketStreamConnectionState.Initializing);
this.socket = webSocket({
url: url,
openObserver: {
next: () => {
this.debugLog('Socket opened: {0}'.format(url));
this.socketState.next(WebsocketStreamConnectionState.Connected);
this.socketStateRaw = WebsocketStreamConnectionState.Connected;
this.connectionRetries = WebsocketStream.maxConnectionRetries;
}
},
closeObserver: {
next: () => {
this.debugLog('Socket closed: {0}'.format(url));
this.socketState.next(WebsocketStreamConnectionState.Disconnected);
this.socketStateRaw = WebsocketStreamConnectionState.Disconnected;
this.reconnect(new Error(this.strings.Common.ConnectionRetiesError.message));
}
},
// for compatibility with older version of rxjs websocket
serializer: (value) => value
});
this.socket.subscribe({
next: received => {
const message = received;
this.debugLog('Socket received data.', message);
if (message.state === WebsocketStreamState.Data) {
const handler = this.handlers.get(message.streamName);
if (handler) {
handler.process(message.data);
}
else {
throw new Error(this.strings.Common.HandlerRegistrationError.message.format(message.streamName));
}
}
else if (message.state === WebsocketStreamState.Error) {
let errorMessage = this.strings.Common.CommunicationError.message;
if (message.data && message.data.error && message.data.error.message) {
errorMessage = this.strings.Common.CommunicationErrorDetail.message.format(message.data.error.message);
}
Logging.log({ level: LogLevel.Error, source: WebsocketStream.logSourceName, message: errorMessage });
this.reconnect(new Error(errorMessage));
}
},
error: error => this.reconnect(error)
});
}
dispose() {
if (this.socket) {
this.socket.unsubscribe();
this.socket = null;
}
}
reconnect(error) {
if (this.connectionRetries-- > 0) {
this.dispose();
setTimeout(() => this.initialize(false), WebsocketStream.reconnectWaitTime);
}
else {
this.socketState.next(WebsocketStreamConnectionState.Failed);
throw error;
}
}
debugLog(message, object) {
Logging.log({ level: LogLevel.Debug, source: WebsocketStream.logSourceName, message: message });
if (object) {
Logging.debug(object);
}
}
}
//# sourceMappingURL=websocket-stream.js.map