@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1 lines • 16.5 kB
Source Map (JSON)
{"version":3,"sources":["../../../packages/core/rpc/rpc-forwarder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAkB,MAAM,MAAM,CAAC;AAIxD,OAAO,EAIH,oBAAoB,EACpB,kBAAkB,EAErB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD;;;GAGG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAC,yBAAyB,CAAiC;IACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAmB;IACvC,OAAO,CAAC,MAAM,CAAC,UAAU,CAA4E;WACvF,YAAY,CAAC,GAAG,EAAE,GAAG;WAuCrB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,GAAG,IAAI;IAW/E,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAiBhC;;;;;;OAMG;WACW,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG;CAqBlD;AAED;;;;;;GAMG;AACH,8BAAsB,mBAAmB;IAKzB,OAAO,CAAC,SAAS;IAAU,SAAS,CAAC,GAAG,EAAE,GAAG;IAHzD;;OAEG;gBACiB,SAAS,EAAE,MAAM,EAAY,GAAG,EAAE,GAAG;IAIzD;;;;OAIG;IACI,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,UAAU,CAAC,GAAG,CAAC;IAiB1E;;OAEG;IACI,UAAU,IAAI,UAAU,CAAC,OAAO,CAAC;IAsBxC;;OAEG;IACI,yBAAyB,IAAI,UAAU,CAAC,IAAI,CAAC;IAgBpD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,CAAC,GAAG,CAAC,GAAG,IAAI;IAE7E;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,IAAI,UAAU,CAAC,GAAG,CAAC;IAEnD;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC;IAE1G;;;;;;OAMG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC;IAEzG;;;OAGG;IACH,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC;IAIrD;;;;OAIG;IACH,SAAS,CAAC,UAAU,CAAC,EAAE,EAAE,mBAAmB;IAoB5C;;;;;;OAMG;IACH,SAAS,CAAC,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAuBrG;;;;;;OAMG;IACH,SAAS,CAAC,aAAa,CAAC,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,UAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI;CAsB9H","file":"rpc-forwarder.d.ts","sourcesContent":["import { from, Observable, of, throwError } from 'rxjs';\r\nimport { catchError, filter, map, mergeMap, take } from 'rxjs/operators';\r\nimport { Strings } from '../generated/strings';\r\nimport { RpcForwardAutoClient } from './forward/rpc-forward-auto-client';\r\nimport {\r\n RpcForwardDownKey,\r\n RpcForwardExecuteReportData,\r\n RpcForwardNotifyReportData,\r\n RpcForwardReportData,\r\n RpcForwardResponse,\r\n RpcForwardType\r\n} from './forward/rpc-forward-model';\r\nimport { RpcForwardUpSubjectServer } from './forward/rpc-forward-up-subject-server';\r\nimport { Rpc } from './rpc';\r\nimport { RpcRelationshipType } from './rpc-base';\r\n\r\n/**\r\n * RPC forwarder class.\r\n * @dynamic\r\n */\r\nexport class RpcForwarder {\r\n private static rpcForwardUpSubjectServer: RpcForwardUpSubjectServer<any>;\r\n private static ready: Observable<void>;\r\n private static forwardMap: Map<string, RpcServiceForwarder> = new Map<string, RpcServiceForwarder>();\r\n public static waitForReady(rpc: Rpc) {\r\n // if we are ready to forward, just return an observable.\r\n if (rpc.stateActive && RpcForwarder.ready) {\r\n return of(null);\r\n }\r\n\r\n // if we are already waiting for the rpc, then return that observable\r\n if (RpcForwarder.ready) {\r\n return RpcForwarder.ready;\r\n }\r\n\r\n // if we are not ready to forward, then set up forwarding based on our window type and current rpc state\r\n if (MsftSme.isShell()) {\r\n // If we are shell then setup the the rpc subject server\r\n RpcForwarder.rpcForwardUpSubjectServer = new RpcForwardUpSubjectServer(rpc);\r\n RpcForwarder.rpcForwardUpSubjectServer.subject.subscribe(data => {\r\n RpcForwarder.onForwardReceived(data.data).then(data.deferred.resolve, data.deferred.reject);\r\n });\r\n } else {\r\n if (!rpc.stateActive) {\r\n // return the first rpc state change that sets the state to active.\r\n // then register our rpc command before returning\r\n RpcForwarder.ready = rpc.stateChanged\r\n .pipe(\r\n filter(active => active),\r\n take(1),\r\n map(() => rpc.register(RpcForwardDownKey.command, RpcForwarder.onForwardReceived)));\r\n return RpcForwarder.ready;\r\n } else {\r\n\r\n // If we are not shell, then setup rpc registration for forwarded messages\r\n rpc.register(RpcForwardDownKey.command, RpcForwarder.onForwardReceived);\r\n }\r\n }\r\n\r\n RpcForwarder.ready = of(null);\r\n return RpcForwarder.ready;\r\n }\r\n\r\n public static register(serviceId: string, forwarder: RpcServiceForwarder): void {\r\n // throw an error if the service has already been registered\r\n if (RpcForwarder.forwardMap.has(serviceId)) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ForwarderIdConflict.message;\r\n throw new Error(message.format(serviceId));\r\n }\r\n\r\n // register the forwarder using the serviceId\r\n RpcForwarder.forwardMap.set(serviceId, forwarder);\r\n }\r\n\r\n private static onForwardReceived(data: RpcForwardReportData): Promise<RpcForwardResponse<any>> {\r\n if (!RpcForwarder.forwardMap.has(data.service)) {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ForwarderIdNotFound.message;\r\n return Promise.reject(message.format(data.service));\r\n } else {\r\n const target = RpcForwarder.forwardMap.get(data.service);\r\n return target.handleForwardedMessage(data)\r\n .pipe(\r\n map(result => <RpcForwardResponse<any>>{ result: result }),\r\n catchError(error => {\r\n // on error, make sure the error is serializable and return it as an observable\r\n return of({ error: RpcForwarder.ensureSerializable(error) });\r\n }))\r\n .toPromise();\r\n }\r\n }\r\n\r\n /**\r\n * Transform object into something that can be serializable by dropping\r\n * any non-serializable properties\r\n *\r\n * @param obj the object to make serializable.\r\n * @return a new object that is serializable\r\n */\r\n public static ensureSerializable(obj: any): any {\r\n const type = typeof obj;\r\n if (type === 'string' || type === 'number' || type === 'boolean') {\r\n return obj;\r\n }\r\n if (obj && type === 'object') {\r\n if (Array.isArray(obj)) {\r\n return (<Array<any>>obj).map(v => RpcForwarder.ensureSerializable(v));\r\n }\r\n\r\n const keys = Object.keys(obj);\r\n const result = {};\r\n keys.forEach(key => {\r\n result[key] = RpcForwarder.ensureSerializable(obj[key]);\r\n });\r\n return result;\r\n }\r\n\r\n // We will return null for symbol, function, null, undefined\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Base class to allow 2+ instances of a service to behave as one across the iframe boundary\r\n * three mechanisms are surfaced for communication:\r\n * - Execute: expects a response with data from the receiver\r\n * - Notify: expects a response with no data from the receiver just for confirmation that it was received\r\n * - Init: always called from a child instance, used to synchronize parent and child as the child starts up\r\n */\r\nexport abstract class RpcServiceForwarder {\r\n\r\n /**\r\n * Instantiates a new instance of the RpcServiceForwarder\r\n */\r\n constructor(private serviceId: string, protected rpc: Rpc) {\r\n RpcForwarder.register(this.serviceId, this);\r\n }\r\n\r\n /**\r\n * Called when a forwarded message is received from the rpc.\r\n * @param data The RpcForwardReportData of the request\r\n * @returns an observable for the result of the request call\r\n */\r\n public handleForwardedMessage(data: RpcForwardReportData): Observable<any> {\r\n // in the future Rpc should have no knowledge of shell an module. Only child/parent.\r\n const fromRpc = this.rpc.isShell ? RpcRelationshipType.Child : RpcRelationshipType.Parent;\r\n switch (data.type) {\r\n case RpcForwardType.Init:\r\n return this.onForwardInit();\r\n case RpcForwardType.Execute:\r\n return this.onForwardExecute(fromRpc, data.name, (<RpcForwardExecuteReportData>data).arguments);\r\n case RpcForwardType.Notify:\r\n return this.onForwardNotify(fromRpc, data.name, (<RpcForwardNotifyReportData>data).value);\r\n default: {\r\n const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ForwarderUnknownType.message;\r\n return throwError(() => message.format(data.type));\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Initializes the service. The serviceReady observable is assigned to the output of this function.\r\n */\r\n public initialize(): Observable<boolean> {\r\n return RpcForwarder.waitForReady(this.rpc)\r\n .pipe(\r\n mergeMap(() => {\r\n if (!this.canForward(RpcRelationshipType.Parent)) {\r\n // since we dont have a parent we can stop here\r\n return of(null);\r\n }\r\n\r\n // other wise we have a parent and should forward an init message\r\n const data: RpcForwardReportData = {\r\n service: this.serviceId,\r\n type: RpcForwardType.Init\r\n };\r\n\r\n return from(RpcForwardAutoClient.forward<any>(this.rpc, data));\r\n }),\r\n map(result => result ? this.onForwardInitResponse(result) : null),\r\n map(() => true),\r\n take(1));\r\n }\r\n\r\n /**\r\n * Reinitialize the forwarded service. This should poll any important data from parents.\r\n */\r\n public reinitializeForwardedData(): Observable<void> {\r\n if (!this.canForward(RpcRelationshipType.Parent)) {\r\n // since we dont have a parent we can stop here\r\n return of(null);\r\n }\r\n\r\n // other wise we have a parent and should forward an init message\r\n const data: RpcForwardReportData = {\r\n service: this.serviceId,\r\n type: RpcForwardType.Init\r\n };\r\n\r\n return from(RpcForwardAutoClient.forward<any>(this.rpc, data))\r\n .pipe(map(result => result ? this.onForwardInitResponse(result) : null));\r\n }\r\n\r\n /**\r\n * Called on a child service instance when onForwardInit returns from the parent\r\n * @param data The response from the forwardInit call\r\n */\r\n protected abstract onForwardInitResponse(data: RpcForwardResponse<any>): void;\r\n\r\n /**\r\n * Called when a new instance of the service in another window is initialized and needs to synchronize with its parent\r\n * @param from The RpcRelationshipType that this request is from\r\n * @returns an observable for the all the values needed to initialize the service\r\n */\r\n protected abstract onForwardInit(): Observable<any>;\r\n\r\n /**\r\n * Called when the forwarded services counterpart wants to get data from the parent\r\n * @param from The RpcRelationshipType that this request is from\r\n * @param name The name of the method to forward to\r\n * @param args The arguments of the method\r\n * @returns an observable for the result of the method call\r\n */\r\n protected abstract onForwardExecute(from: RpcRelationshipType, name: string, args: any[]): Observable<any>;\r\n\r\n /**\r\n * Called when the forwarded services counterpart sends a notify message\r\n * @param from The RpcRelationshipType that this request is from\r\n * @param name The name of the property to change\r\n * @param value The new value of the property\r\n * @returns an observable that completes when the property has been changed.\r\n */\r\n protected abstract onForwardNotify(from: RpcRelationshipType, name: string, value: any): Observable<void>;\r\n\r\n /**\r\n * Creates an observable that errors with name not found\r\n * @returns an observable that will error with a name not found message\r\n */\r\n protected nameNotFound(name: string): Observable<any> {\r\n return throwError(() => new Error(`${name} not found in forwarded service: ${this.serviceId}`));\r\n }\r\n\r\n /**\r\n * Determines if a message can be forwarded to the specified RpcRelationshipType\r\n * @param to The RpcRelationshipType that needs to be checked\r\n * @returns true if messages can be forwarded to the specified RpcRelationshipType\r\n */\r\n protected canForward(to: RpcRelationshipType) {\r\n // we can only forward if the rpc is active\r\n if (this.rpc.stateActive) {\r\n if (to === RpcRelationshipType.Parent) {\r\n // we can only forward to parents if we are an extension window\r\n return MsftSme.isExtension();\r\n } else if (to === RpcRelationshipType.Child) {\r\n // when trying to forward to a child, we have to make sure that the rpc outbound is actually ready.\r\n // rpc.StateActive will report true always fro the shell\r\n if (!this.rpc.rpcManager || !this.rpc.rpcManager.rpcOutbound) {\r\n return false;\r\n }\r\n\r\n // we can only forward to parents if we have child windows\r\n return MsftSme.isShell() && window.frames.length > 0;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * Forwards a execution of some named method to the target relationship type\r\n * @param to The RpcRelationshipType that this request is intended for\r\n * @param name The name of the method to execute\r\n * @param value The arguments for the method\r\n * @returns an observable for the result of the method call\r\n */\r\n protected forwardExecute<T>(to: RpcRelationshipType, name: string, args: any[]): Observable<T> | void {\r\n // if we cant forward then just return\r\n if (!this.canForward(to)) {\r\n return;\r\n }\r\n\r\n const data: RpcForwardExecuteReportData = {\r\n arguments: args,\r\n name: name,\r\n service: this.serviceId,\r\n type: RpcForwardType.Execute\r\n };\r\n\r\n return from(RpcForwardAutoClient.forward<any>(this.rpc, data))\r\n .pipe(map(response => {\r\n if (response.error) {\r\n throw response.error;\r\n } else {\r\n return response.result;\r\n }\r\n }));\r\n }\r\n\r\n /**\r\n * Forwards a notification of some state change to the target relationship type\r\n * @param to The RpcRelationshipType that this request is intended for\r\n * @param name The name of the state change\r\n * @param value The new value of some state\r\n * @returns an observable that completes when the state has been changed on the target instance.\r\n */\r\n protected forwardNotify(to: RpcRelationshipType, name: string, value: any, noPrimaryFrame = false): Observable<void> | void {\r\n // if we cant forward then just return\r\n if (!noPrimaryFrame && !this.canForward(to)) {\r\n return;\r\n }\r\n\r\n const data: RpcForwardNotifyReportData = {\r\n value: value,\r\n name: name,\r\n service: this.serviceId,\r\n type: RpcForwardType.Notify\r\n };\r\n\r\n return from(RpcForwardAutoClient.forward<any>(this.rpc, data))\r\n .pipe(map(response => {\r\n if (response.error) {\r\n throw response.error;\r\n } else {\r\n return response.result;\r\n }\r\n }));\r\n }\r\n}\r\n"]}