UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

342 lines (340 loc) 12 kB
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