{"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"]}