UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

261 lines (259 loc) 10.9 kB
import { from, of, throwError } from 'rxjs'; import { catchError, filter, map, mergeMap, take } from 'rxjs/operators'; import { RpcForwardAutoClient } from './forward/rpc-forward-auto-client'; import { RpcForwardDownKey } from './forward/rpc-forward-model'; import { RpcForwardUpSubjectServer } from './forward/rpc-forward-up-subject-server'; /** * RPC forwarder class. * @dynamic */ export class RpcForwarder { static rpcForwardUpSubjectServer; static ready; static forwardMap = new Map(); static waitForReady(rpc) { // if we are ready to forward, just return an observable. if (rpc.stateActive && RpcForwarder.ready) { return of(null); } // if we are already waiting for the rpc, then return that observable if (RpcForwarder.ready) { return RpcForwarder.ready; } // if we are not ready to forward, then set up forwarding based on our window type and current rpc state if (MsftSme.isShell()) { // If we are shell then setup the the rpc subject server RpcForwarder.rpcForwardUpSubjectServer = new RpcForwardUpSubjectServer(rpc); RpcForwarder.rpcForwardUpSubjectServer.subject.subscribe(data => { RpcForwarder.onForwardReceived(data.data).then(data.deferred.resolve, data.deferred.reject); }); } else { if (!rpc.stateActive) { // return the first rpc state change that sets the state to active. // then register our rpc command before returning RpcForwarder.ready = rpc.stateChanged .pipe(filter(active => active), take(1), map(() => rpc.register(RpcForwardDownKey.command, RpcForwarder.onForwardReceived))); return RpcForwarder.ready; } else { // If we are not shell, then setup rpc registration for forwarded messages rpc.register(RpcForwardDownKey.command, RpcForwarder.onForwardReceived); } } RpcForwarder.ready = of(null); return RpcForwarder.ready; } static register(serviceId, forwarder) { // throw an error if the service has already been registered if (RpcForwarder.forwardMap.has(serviceId)) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.ForwarderIdConflict.message; throw new Error(message.format(serviceId)); } // register the forwarder using the serviceId RpcForwarder.forwardMap.set(serviceId, forwarder); } static onForwardReceived(data) { if (!RpcForwarder.forwardMap.has(data.service)) { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.ForwarderIdNotFound.message; return Promise.reject(message.format(data.service)); } else { const target = RpcForwarder.forwardMap.get(data.service); return target.handleForwardedMessage(data) .pipe(map(result => ({ result: result })), catchError(error => { // on error, make sure the error is serializable and return it as an observable return of({ error: RpcForwarder.ensureSerializable(error) }); })) .toPromise(); } } /** * Transform object into something that can be serializable by dropping * any non-serializable properties * * @param obj the object to make serializable. * @return a new object that is serializable */ static ensureSerializable(obj) { const type = typeof obj; if (type === 'string' || type === 'number' || type === 'boolean') { return obj; } if (obj && type === 'object') { if (Array.isArray(obj)) { return obj.map(v => RpcForwarder.ensureSerializable(v)); } const keys = Object.keys(obj); const result = {}; keys.forEach(key => { result[key] = RpcForwarder.ensureSerializable(obj[key]); }); return result; } // We will return null for symbol, function, null, undefined return null; } } /** * Base class to allow 2+ instances of a service to behave as one across the iframe boundary * three mechanisms are surfaced for communication: * - Execute: expects a response with data from the receiver * - Notify: expects a response with no data from the receiver just for confirmation that it was received * - Init: always called from a child instance, used to synchronize parent and child as the child starts up */ export class RpcServiceForwarder { serviceId; rpc; /** * Instantiates a new instance of the RpcServiceForwarder */ constructor(serviceId, rpc) { this.serviceId = serviceId; this.rpc = rpc; RpcForwarder.register(this.serviceId, this); } /** * Called when a forwarded message is received from the rpc. * @param data The RpcForwardReportData of the request * @returns an observable for the result of the request call */ handleForwardedMessage(data) { // in the future Rpc should have no knowledge of shell an module. Only child/parent. const fromRpc = this.rpc.isShell ? 1 /* RpcRelationshipType.Child */ : 0 /* RpcRelationshipType.Parent */; switch (data.type) { case 2 /* RpcForwardType.Init */: return this.onForwardInit(); case 0 /* RpcForwardType.Execute */: return this.onForwardExecute(fromRpc, data.name, data.arguments); case 1 /* RpcForwardType.Notify */: return this.onForwardNotify(fromRpc, data.name, data.value); default: { const message = MsftSme.getStrings().MsftSmeShell.Core.Error.ForwarderUnknownType.message; return throwError(() => message.format(data.type)); } } } /** * Initializes the service. The serviceReady observable is assigned to the output of this function. */ initialize() { return RpcForwarder.waitForReady(this.rpc) .pipe(mergeMap(() => { if (!this.canForward(0 /* RpcRelationshipType.Parent */)) { // since we dont have a parent we can stop here return of(null); } // other wise we have a parent and should forward an init message const data = { service: this.serviceId, type: 2 /* RpcForwardType.Init */ }; return from(RpcForwardAutoClient.forward(this.rpc, data)); }), map(result => result ? this.onForwardInitResponse(result) : null), map(() => true), take(1)); } /** * Reinitialize the forwarded service. This should poll any important data from parents. */ reinitializeForwardedData() { if (!this.canForward(0 /* RpcRelationshipType.Parent */)) { // since we dont have a parent we can stop here return of(null); } // other wise we have a parent and should forward an init message const data = { service: this.serviceId, type: 2 /* RpcForwardType.Init */ }; return from(RpcForwardAutoClient.forward(this.rpc, data)) .pipe(map(result => result ? this.onForwardInitResponse(result) : null)); } /** * Creates an observable that errors with name not found * @returns an observable that will error with a name not found message */ nameNotFound(name) { return throwError(() => new Error(`${name} not found in forwarded service: ${this.serviceId}`)); } /** * Determines if a message can be forwarded to the specified RpcRelationshipType * @param to The RpcRelationshipType that needs to be checked * @returns true if messages can be forwarded to the specified RpcRelationshipType */ canForward(to) { // we can only forward if the rpc is active if (this.rpc.stateActive) { if (to === 0 /* RpcRelationshipType.Parent */) { // we can only forward to parents if we are an extension window return MsftSme.isExtension(); } else if (to === 1 /* RpcRelationshipType.Child */) { // when trying to forward to a child, we have to make sure that the rpc outbound is actually ready. // rpc.StateActive will report true always fro the shell if (!this.rpc.rpcManager || !this.rpc.rpcManager.rpcOutbound) { return false; } // we can only forward to parents if we have child windows return MsftSme.isShell() && window.frames.length > 0; } } return false; } /** * Forwards a execution of some named method to the target relationship type * @param to The RpcRelationshipType that this request is intended for * @param name The name of the method to execute * @param value The arguments for the method * @returns an observable for the result of the method call */ forwardExecute(to, name, args) { // if we cant forward then just return if (!this.canForward(to)) { return; } const data = { arguments: args, name: name, service: this.serviceId, type: 0 /* RpcForwardType.Execute */ }; return from(RpcForwardAutoClient.forward(this.rpc, data)) .pipe(map(response => { if (response.error) { throw response.error; } else { return response.result; } })); } /** * Forwards a notification of some state change to the target relationship type * @param to The RpcRelationshipType that this request is intended for * @param name The name of the state change * @param value The new value of some state * @returns an observable that completes when the state has been changed on the target instance. */ forwardNotify(to, name, value, noPrimaryFrame = false) { // if we cant forward then just return if (!noPrimaryFrame && !this.canForward(to)) { return; } const data = { value: value, name: name, service: this.serviceId, type: 1 /* RpcForwardType.Notify */ }; return from(RpcForwardAutoClient.forward(this.rpc, data)) .pipe(map(response => { if (response.error) { throw response.error; } else { return response.result; } })); } } //# sourceMappingURL=rpc-forwarder.js.map