@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
488 lines (486 loc) • 21.7 kB
JavaScript
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