UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

488 lines (486 loc) 21.7 kB
import { Observable, throwError } from 'rxjs'; import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators'; import { LogLevel } from '../diagnostics/log-level'; import { Logging } from '../diagnostics/logging'; import { NodeCimOutput } from './node-connection'; import { PowerShellAlternate } from './powershell-alternate'; import { WebsocketStreamConnectionState, WebsocketStreamDataRequestState, WebsocketStreamDataState, WebsocketStreamName, WebsocketStreamProcessor } from './websocket-stream'; /** * Cim result format. */ export var CimResultFormat; (function (CimResultFormat) { /** * Single instance result. */ CimResultFormat[CimResultFormat["Single"] = 0] = "Single"; /** * Multiple instances result. */ CimResultFormat[CimResultFormat["Multiple"] = 1] = "Multiple"; /** * Mixed data result. */ CimResultFormat[CimResultFormat["Result"] = 2] = "Result"; })(CimResultFormat || (CimResultFormat = {})); /** * Cim Processor interface. Each Cim query creates new observable. */ class CimProcessor extends WebsocketStreamProcessor { format; /** * Initializes a new instance of the CimProcessor class. * @param observer Observer to send back result to caller. * @param target Stream Target object. * @param format CIM result format. * @param options Options for Cim stream query. */ constructor(observer, target, format, options) { super(observer, target, options); this.format = format; } } /** * The CIM stream class. */ export class CimStream { websocketStream; powerShellStream; authorizationManager; static logSourceName = 'CimStream'; /** * The collection of set of monitors. */ static monitorSets = []; processors = new Map(); strings = MsftSme.getStrings().MsftSmeShell.Core.WebsocketStream.CimStream; /** * Register the set of monitors. * * @param monitorSet The set of monitors. */ static registerMonitorSet(monitorSet) { const found = CimStream.monitorSets.find(monitors => monitors.name === monitorSet.name); if (found) { return; } CimStream.monitorSets.push(monitorSet); } /** * Unregister the set of monitors. * * @param name The name of set of monitors. * @returns boolean true if unregistered the named set. */ static unregisterMonitors(name) { const found = CimStream.monitorSets.find(monitors => monitors.name === name); if (found) { CimStream.monitorSets.remove(found); return true; } return false; } /** * Initializes a new instance of the CimStream class. * * @param websocketStream the websocket stream object. * @param powerShellStream the PowerShell steam object. * @param authorizationManager the authorization manager object. */ constructor(websocketStream, powerShellStream, authorizationManager) { this.websocketStream = websocketStream; this.powerShellStream = powerShellStream; this.authorizationManager = authorizationManager; websocketStream.registerProcessor(WebsocketStreamName.CimStreamName, this); } /** * CIM Get MultipleInstances * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param options the options for this request. * @return Observable<CimResult> the query observable. */ getInstanceMultiple(nodeName, namespace, className, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Multiple); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimGetInstanceMultiple'; const request = { name, namespace, className }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Multiple, options); } /** * CIM Get SingleInstance * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param keyProperties the key properties object. * @param options the options for this request. * @return Observable<any> the query observable. */ getInstanceSingle(nodeName, namespace, className, keyProperties, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Multiple, keyProperties); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimGetInstanceSingle'; const request = { name, namespace, className, keyProperties }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Single, options); } /** * CIM Invoke InstanceMethod * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param methodName the method name. * @param keyProperties the key properties object. * @param data the method input data. * @param options the options for this request. * @return Observable<any> the query observable. */ invokeMethodInstance(nodeName, namespace, className, methodName, keyProperties, data, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Multiple, keyProperties, data && data.parameters); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimInvokeMethodInstance'; const request = { name, namespace, className, methodName, keyProperties, data }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Result, options); } /** * CIM Invoke StaticMethod * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param methodName the method name. * @param data the method input data. * @param options the options for this request. * @return Observable<any> the query observable. */ invokeMethodStatic(nodeName, namespace, className, methodName, data, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Result, data && data.parameters); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimInvokeMethodStatic'; const request = { name, namespace, className, methodName, data }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Result, options); } /** * CIM Set SingleInstance * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param keyProperties the key properties object. * @param data the method input data. * @param options the options for this request. * @return Observable<any> the query observable. */ setInstance(nodeName, namespace, className, keyProperties, data, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Single, keyProperties, data && data.properties); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimSetInstance'; const request = { name, namespace, className, keyProperties, data }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Single, options); } /** * CIM Modify SingleInstance * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param keyProperties the key properties object. * @param data the method input data. * @param options the options for this request. * @return Observable<any> the query observable. */ modifyInstance(nodeName, namespace, className, keyProperties, data, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Single, keyProperties, data && data.properties); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimModifyInstance'; const request = { name, namespace, className, keyProperties, data }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Single, options); } /** * CIM Delete SingleInstance * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param className the class name. * @param keyProperties the key properties object. * @param options the options for this request. * @return Observable<any> the query observable. */ deleteInstance(nodeName, namespace, className, keyProperties, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Single, keyProperties); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimDeleteInstance'; const request = { name, namespace, className, keyProperties }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Single, options); } /** * CIM Submit WqlQuery * * @param nodeName the name of the node to use for this request * @param namespace the cim namespace. * @param query the WQL string. * @param options the options for this request. * @return Observable<any> the query observable. */ getInstanceQuery(nodeName, namespace, query, options) { this.setPowerShellParameters(nodeName, options, NodeCimOutput.Query, options && options.powerShellParameters); const observable = PowerShellAlternate.createStream(this.powerShellStream, nodeName, options); if (observable) { return observable; } const name = 'CimGetInstanceQuery'; const request = { name, namespace, query }; const requestState = WebsocketStreamDataRequestState.Normal; return this.monitorCreateRequest(nodeName, requestState, request, CimResultFormat.Multiple, options); } /** * Cancel active CIM query. * Result response comes back to the original query to end. * * @param nodeName the node name. * @param id the id of original request specified as options.queryId. */ cancel(nodeName, id) { const target = this.websocketStream.getTarget(this.authorizationManager, nodeName); const requestState = WebsocketStreamDataRequestState.Cancel; const request = { id, target, requestState, request: null }; this.websocketStream.sendNext(WebsocketStreamName.CimStreamName, request); } /** * Reset data for connection cleanup. */ reset() { Logging.log({ level: LogLevel.Warning, message: this.strings.ResetError.message, source: CimStream.logSourceName }); const processors = []; this.processors.forEach((value) => processors.push(value)); this.processors.clear(); processors.forEach((processor) => { processor.error(new Error(this.strings.ResetError.message)); }); } /** * Process the socket message. * * @param message the socket message. */ process(message) { if (!message) { throw new Error(this.strings.NoContentError.message); } const processor = this.processors.get(message.id); if (!processor) { Logging.log({ level: LogLevel.Warning, message: this.strings.UnexpectedReceivedError.message, source: CimStream.logSourceName }); return; } switch (message.state) { case WebsocketStreamDataState.Data: this.operationNext(processor, message.response); break; case WebsocketStreamDataState.Completed: this.operationComplete(processor, message.response); this.operationEnd(message.id); break; case WebsocketStreamDataState.Error: this.operationError(processor, { xhr: message }); this.operationEnd(message.id); break; case WebsocketStreamDataState.Noop: break; } } operationNext(processor, response) { const partial = processor.options && processor.options.partial; if (!response) { return !partial; } // buffering result. if (!partial) { if (processor.format === CimResultFormat.Single) { // expecting - { response: any } processor.response = response; } else if (processor.format === CimResultFormat.Multiple) { // expecting - { response: { value: any[]; } } format. if (!response || !response.value) { Logging.log({ level: LogLevel.Error, message: this.strings.UnexpectedMultipleError.message, source: CimStream.logSourceName }); } else { if (!processor.response) { processor.response = response; } else { response.value.forEach(value => processor.response.value.push(value)); } } } else if (processor.format === CimResultFormat.Result) { // expecting - { response: { results: any[] }, <name1>: <value1>, <name2>: <value2> } format. if (!processor.response) { processor.response = response; } else { if (response.results) { if (processor.response.results) { response.results.forEach(value => processor.response.results.push(value)); response.results = undefined; } } // merge other properties. processor.response = { ...processor.response, ...response }; } } } else { processor.next(response); } return !partial; } operationComplete(processor, response) { if (this.operationNext(processor, response)) { // complete mode to send all result once. processor.next(processor.response); } if (!processor.sendOnce) { // send null if no result was produced but success. processor.next(null); } processor.complete(); } operationError(processor, error) { processor.error(error); } operationEnd(id) { this.processors.delete(id); } createRequest(nodeName, requestState, request, format, options) { // publish object is created two ways. // 1) socket is connected so submit the request immediately with simple observable. // (if-block and this is the most of cases.) // 2) socket is not connected so wait for the socket to ready and submit request with // complex observable. Initial connect and re-connection takes this observable. // (else-block and this is a few cases.) let publish; if (this.websocketStream.socketStateRaw === WebsocketStreamConnectionState.Connected) { publish = this.createRequestSimple(nodeName, requestState, request, format, options); } else { publish = this.websocketStream.socketState .pipe(filter(state => state === WebsocketStreamConnectionState.Connected || state === WebsocketStreamConnectionState.Failed || state === WebsocketStreamConnectionState.NotConfigured), take(1), mergeMap(state => { if (state === WebsocketStreamConnectionState.Connected) { return this.createRequestSimple(nodeName, requestState, request, format, options); } return throwError(() => new Error(this.strings.ConnectionError.message)); })); } return publish .pipe(catchError((error) => { // retry if reset connection of socket was observed. if (error && error.message === this.strings.ResetError.message) { return this.monitorCreateRequest(nodeName, requestState, request, format, options); } if ((!options || options.noAuth !== true) && !this.authorizationManager.signOnManager.isSignOnTokenEnabled && this.authorizationManager.canHandleStreamFailure(error.xhr)) { return this.authorizationManager.handleStreamFailure(nodeName, options, error.xhr) .pipe(switchMap(updatedOptions => this.monitorCreateRequest(nodeName, requestState, request, format, updatedOptions))); } if (this.authorizationManager.signOnManager.isSignOnTokenEnabled && this.authorizationManager.signOnManager.canHandleStreamUnauthorizedLogin(error.xhr)) { return this.authorizationManager.signOnManager.handleStreamUnauthorizedLogin(options, error.xhr) .pipe(switchMap(updatedOptions => this.monitorCreateRequest(nodeName, requestState, request, format, updatedOptions))); } return throwError(() => error); })); } createRequestSimple(nodeName, requestState, request, format, options) { return new Observable(observer => { const target = this.websocketStream.getTarget(this.authorizationManager, nodeName); const id = this.sendRequest(observer, target, requestState, request, format, options); return () => { const processor = this.processors.get(id); if (processor) { processor.end = true; if (!processor.closed && !processor.closing) { this.cancel(processor.target.nodeName, id); } } }; }); } sendRequest(observer, target, requestState, request, format, options) { const id = (options && options.queryId) || MsftSme.getUniqueId(); const processor = new CimProcessor(observer, target, format, options); this.processors.set(id, processor); this.websocketStream.sendNext(WebsocketStreamName.CimStreamName, { id, target, requestState, request, options }); return id; } /** * Set PowerShell parameters to the options object. * * @param options The node request options. * @param outputType The output data type. * @param keys The key data. * @param data The arguments data. */ setPowerShellParameters(nodeName, options, cimOutput, keys, data) { if (options == null || options.powerShell == null) { return; } options.powerShellContext = { cimOutput, parameters: { ...(keys || {}), ...(data || {}) } }; options.powerShellEndpoint = this.authorizationManager.getJeaEndpoint(nodeName); } monitorCreateRequest(nodeName, requestState, request, format, options) { let monitored = (nodeName1, requestState1, request1, format1, options1) => this.createRequest(nodeName1, requestState1, request1, format1, options1); for (const monitorSet of CimStream.monitorSets) { monitored = this.monitor(monitored, monitorSet); } return monitored(nodeName, requestState, request, format, options); } monitor(target, monitorSet) { return function (nodeName, requestState, request, format, options) { let context; return monitorSet.preMonitor(nodeName, requestState, request, format, options) .pipe(switchMap(packet => { context = packet; return target(packet.nodeName, packet.requestState, packet.request, packet.format, packet.options); }), catchError((error) => monitorSet.errorMonitor(error, context)), switchMap(response => monitorSet.successMonitor(response, context))); }; } } //# sourceMappingURL=cim-stream.js.map