{"version":3,"sources":["../../../packages/core/security/connection-manager.ts"],"names":[],"mappings":"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","file":"connection-manager.d.ts","sourcesContent":["import { concat, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';\r\nimport { catchError, map, mergeMap, take, tap } from 'rxjs/operators';\r\nimport { PlainVersionedObject, VersionedObject, VersionedObjectConstructor } from '../base/versioned-object';\r\nimport { GatewayConnection } from '../data/gateway-connection';\r\nimport { Net } from '../data/net';\r\nimport { LogLevel } from '../diagnostics/log-level';\r\nimport { Logging } from '../diagnostics/logging';\r\nimport { Strings } from '../generated/strings';\r\nimport { RpcForwardResponse } from '../rpc/forward/rpc-forward-model';\r\nimport { Rpc } from '../rpc/rpc';\r\nimport { RpcRelationshipType } from '../rpc/rpc-base';\r\nimport { RpcServiceForwarder } from '../rpc/rpc-forwarder';\r\nimport { Connection, connectionTypeConstants, ConnectionUtility } from './connection';\r\nimport { CommonConnectionSettings } from './connection-manager-settings/common-connection-settings';\r\nimport { ConnectionSettings } from './connection-manager-settings/connection-settings';\r\n\r\nexport interface ConnectionManagerInitProperties {\r\n    connections: Connection[];\r\n    activeConnection: Connection;\r\n}\r\n\r\nexport enum ConnectionChangeType {\r\n    Initialized = 0,\r\n    Activated = 1,\r\n    Added = 2,\r\n    Removed = 3\r\n}\r\n\r\nexport interface ConnectionsChangedEvent {\r\n    type: ConnectionChangeType;\r\n}\r\nexport interface ConnectionChangedEvent extends ConnectionsChangedEvent {\r\n    connection: Connection;\r\n}\r\n\r\nexport interface ConnectionsInitializedEvent extends ConnectionsChangedEvent {\r\n    connections: Connection[];\r\n}\r\n\r\n/**\r\n * Node aliases visit list.\r\n *  This is created when retry connection to aliases while connection.name not reacheable;\r\n *  and is deleted when find the reacheable alias or end of the list\r\n */\r\nexport interface AliasesVisitList {\r\n    /**\r\n     * current visit index\r\n     */\r\n    currentIndex: number;\r\n    /**\r\n     * aliases list\r\n     */\r\n    aliases: string[];\r\n}\r\n\r\nexport class ConnectionManager extends RpcServiceForwarder implements ConnectionManagerInitProperties {\r\n    private static activeConnectionPropertyName = 'activeConnection';\r\n    private static connectionsPropertyName = 'connections';\r\n    private static saveConnectionMethodName = 'saveConnection';\r\n    private static saveConnectionsMethodName = 'saveConnections';\r\n    private static removeConnectionMethodName = 'removeConnection';\r\n\r\n    private static gatewayConnectionApi = 'connections';\r\n\r\n    private allConnections: Connection[] = [];\r\n    private activeConnectionIndex = -1;\r\n\r\n    /**\r\n     * The map of active nodes aliases list, this will be build on demand and clear up whenever any connection aliases changed\r\n     * key: nodeName, this is unique, that means same nodeName with different connection type treat as one entry\r\n     * value: A aliases list for this nodeName (say cluster name),\r\n     *  this may contains child aliases (say IP address) of any alias (say server node).\r\n     */\r\n    private connectionAliasesMap: MsftSme.StringMap<string[]> = {};\r\n\r\n    /**\r\n     * nodeAliasesVisit map, the key is connection nodeName\r\n     */\r\n    private nodeAliasesVisitMap: MsftSme.StringMap<AliasesVisitList> = {};\r\n\r\n    /**\r\n     * Subject that Fires once and remembers when connections have been initialized\r\n     */\r\n    public connectionsInitialized = new ReplaySubject<Connection[]>(1);\r\n\r\n    /**\r\n     * Event subject that signals that the connection(s) have changed.\r\n     * Filter on changeType to determine what type of change has occurred\r\n     */\r\n    public connectionsChanged = new Subject<ConnectionsChangedEvent>();\r\n\r\n    /**\r\n     * Indicates that restoring connections has started and shouldnt call the gateway again\r\n     */\r\n    private restoreInProgress = false;\r\n\r\n    /**\r\n     * Indicates that restoring connections has started and shouldnt call the gateway again\r\n     */\r\n    private restoreCompleted = false;\r\n\r\n    /**\r\n     * The connection settings subject for caching the connection's settings while active.\r\n     */\r\n    private connectionSettings = new ReplaySubject<ConnectionSettings>(1);\r\n\r\n    constructor(rpc: Rpc, private gatewayConnection: GatewayConnection) {\r\n        super('connection-manager', rpc);\r\n    }\r\n\r\n    public restoreConnections(refresh = false): Observable<Connection[]> {\r\n        if (!this.canForward(RpcRelationshipType.Parent) && (refresh || (!this.restoreCompleted && !this.restoreInProgress))) {\r\n            this.restoreInProgress = true;\r\n            this.gatewayConnection.get(ConnectionManager.gatewayConnectionApi)\r\n                .pipe(\r\n                    map(data => {\r\n                        if (data.value) {\r\n                            (Array.isArray(data.value) ? data.value : [data.value])\r\n                                .forEach(connection => {\r\n                                    // for some reason the node api returns properties in a nested format. Unpack it into the correct format\r\n                                    if (connection.properties) {\r\n                                        connection = connection.properties;\r\n                                    }\r\n\r\n                                    // ensure bare minimum properties exist, ignore otherwise\r\n                                    if (connection && connection.name && connection.type && connection.id) {\r\n                                        this.addOrUpdateConnection(connection, false);\r\n                                        return;\r\n                                    }\r\n                                });\r\n                        }\r\n\r\n                        return this.allConnections;\r\n                    }),\r\n                    catchError(error => {\r\n                        this.restoreInProgress = false;\r\n                        const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ServerListRetrieve.message;\r\n                        Logging.log({\r\n                            source: 'ConnectionManager',\r\n                            level: LogLevel.Error,\r\n                            message: message.format(Net.getErrorMessage(error))\r\n                        });\r\n                        this.connectionsInitialized.error(error);\r\n                        return of(this.allConnections);\r\n                    }))\r\n                .subscribe(() => {\r\n                    this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n\r\n                    this.connectionsChanged.next(\r\n                        <ConnectionsInitializedEvent>{\r\n                            type: ConnectionChangeType.Initialized,\r\n                            connections: this.allConnections\r\n                        });\r\n\r\n                    this.connectionsInitialized.next(this.allConnections);\r\n                    this.restoreInProgress = false;\r\n                    this.restoreCompleted = true;\r\n                });\r\n        }\r\n\r\n        return this.connectionsInitialized;\r\n    }\r\n\r\n    /**\r\n     * Gets all connections\r\n     */\r\n    public get connections(): Connection[] {\r\n        return this.allConnections;\r\n    }\r\n\r\n    /**\r\n     * Gets active connection.\r\n     */\r\n    public get activeConnection(): Connection {\r\n        return this.allConnections[this.activeConnectionIndex];\r\n    }\r\n\r\n    /**\r\n     * Sets active connection.\r\n     */\r\n    public set activeConnection(connection: Connection) {\r\n        // only change active connection if it has really changed\r\n        if (!ConnectionUtility.areEqual(this.activeConnection, connection)) {\r\n            if (!connection) {\r\n                this.activeConnectionIndex = -1;\r\n            } else {\r\n                this.activeConnectionIndex = this.addOrUpdateConnection(connection, false);\r\n            }\r\n\r\n            this.connectionsChanged.next(<ConnectionChangedEvent>{ type: ConnectionChangeType.Activated, connection: connection });\r\n            this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.activeConnectionPropertyName, this.activeConnection);\r\n            this.forwardNotify(RpcRelationshipType.Parent, ConnectionManager.activeConnectionPropertyName, this.activeConnection);\r\n            // collect connection settings for new active connection\r\n            this.getConnectionSettings().pipe(take(1)).subscribe(settings => this.connectionSettings.next(settings));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Add or update connection.\r\n     */\r\n    public addOrUpdateConnection(connection: Connection, save: boolean = true, merge = false): number {\r\n        connection.id = ConnectionUtility.createConnectionId(connection.type, connection.name, connection.groupId);\r\n        ConnectionUtility.forceLowercase(connection);\r\n        let index = this.allConnections.findIndex(c => ConnectionUtility.areEqual(c, connection));\r\n        if (index >= 0) {\r\n            if (merge) {\r\n                const oldConnection = this.allConnections[index];\r\n                if (oldConnection.tags) {\r\n                    connection.tags = oldConnection.tags.concat(connection.tags || []).unique();\r\n                }\r\n                if (oldConnection.properties) {\r\n                    connection.properties = { ...oldConnection.properties, ...connection.properties || {} };\r\n                }\r\n            }\r\n\r\n            this.allConnections[index] = connection;\r\n            this.connectionsChanged.next(<ConnectionChangedEvent>{ type: ConnectionChangeType.Added, connection: connection });\r\n        } else {\r\n            this.allConnections.push(connection);\r\n            this.connectionsChanged.next(<ConnectionChangedEvent>{ type: ConnectionChangeType.Added, connection: connection });\r\n            if (!this.restoreInProgress) {\r\n                this.connectionsInitialized.next(this.allConnections);\r\n            }\r\n            index = this.allConnections.length - 1;\r\n            // notify parent and child of collection changed.\r\n            this.forwardNotify(RpcRelationshipType.Parent, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n            this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n        }\r\n\r\n        if (save) {\r\n            this.saveConnection(connection).subscribe();\r\n        }\r\n\r\n        return index;\r\n    }\r\n\r\n    /**\r\n     * Remove connection.\r\n     */\r\n    public removeConnection(connection: Connection): Observable<any> {\r\n        const forward = this.forwardExecute(RpcRelationshipType.Parent, ConnectionManager.removeConnectionMethodName, [connection]);\r\n        if (forward) {\r\n            return <Observable<any>>forward;\r\n        }\r\n\r\n        const urlEncodedID = encodeURIComponent(connection.id);\r\n        return this.gatewayConnection.delete(`${ConnectionManager.gatewayConnectionApi}/${urlEncodedID}`)\r\n            .pipe(\r\n                map(response => {\r\n                    const index = this.allConnections.findIndex(c => ConnectionUtility.areEqual(c, connection));\r\n                    if (index >= 0) {\r\n                        // if this connection is active, set active connection to null\r\n                        if (this.activeConnectionIndex === index) {\r\n                            this.activeConnection = null;\r\n                        }\r\n\r\n                        // remove the connection from all connections\r\n                        this.allConnections.splice(index, 1);\r\n                    }\r\n                    this.connectionsChanged.next(<ConnectionChangedEvent>{ type: ConnectionChangeType.Removed, connection: connection });\r\n                    if (!this.restoreInProgress) {\r\n                        this.connectionsInitialized.next(this.allConnections);\r\n                    }\r\n\r\n                    // notify our children that connections have changed\r\n                    this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n\r\n                    return response;\r\n                }));\r\n    }\r\n\r\n    public updateConnectionsLastCheckedTime(connections: Connection[]): Observable<any> {\r\n        const now = Date.now();\r\n        const observables = [];\r\n        if (connections && connections.length > 0) {\r\n            connections.forEach(connection => {\r\n                if (connection.properties == null\r\n                    || connection.properties.lastUpdatedTime == null\r\n                    || MsftSme.round(connection.properties.lastUpdatedTime / 1000) + 2 < MsftSme.round(now / 1000)) {\r\n                    // update if there is more than 2 second difference.\r\n                    connection.properties = connection.properties || {};\r\n                    connection.properties.lastUpdatedTime = now;\r\n                    observables.push(this.saveConnection(connection));\r\n                }\r\n\r\n                observables.push(of(null));\r\n            });\r\n        } else {\r\n            observables.push(of(null));\r\n        }\r\n\r\n        return concat(...observables);\r\n    }\r\n\r\n    public saveConnection(connection: Connection): Observable<any> {\r\n        ConnectionUtility.forceLowercase(connection);\r\n        const forward = this.forwardExecute(RpcRelationshipType.Parent, ConnectionManager.saveConnectionMethodName, [connection]);\r\n        if (forward) {\r\n            return <Observable<any>>forward;\r\n        }\r\n\r\n        if (!connection.type || !connection.name) {\r\n            const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ServerListFailedSave.message;\r\n            return throwError(() => new Error(message));\r\n        }\r\n\r\n        connection.id = ConnectionUtility.createConnectionId(connection.type, connection.name, connection.groupId);\r\n\r\n        // define properties if it doesn't exist on the connection\r\n        if (MsftSme.isNullOrUndefined(connection.properties)) {\r\n            connection.properties = {};\r\n        }\r\n        this.addOrUpdateConnection(connection, false);\r\n\r\n        const urlEncodedID = encodeURIComponent(connection.id);\r\n        return this.gatewayConnection.put(`${ConnectionManager.gatewayConnectionApi}/${urlEncodedID}`, JSON.stringify(connection))\r\n            .pipe(\r\n                tap((data: any) => {\r\n                    if (data.properties && data.properties.displayName) {\r\n                        // merge the display name from the gateway into the connection properties\r\n                        connection.properties.displayName = data.properties.displayName;\r\n                    }\r\n\r\n                    this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Bulk operation for saving multiple connections\r\n     * @param connection the connection object.\r\n     */\r\n    public saveConnections(connections: Connection[]): Observable<any> {\r\n        connections.forEach(connection => {\r\n            this.addOrUpdateConnection(connection, false);\r\n            ConnectionUtility.forceLowercase(connection);\r\n\r\n            if (!connection.type || !connection.name) {\r\n                const message = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Error.ServerListFailedSave.message;\r\n                return throwError(() => new Error(message));\r\n            }\r\n\r\n            connection.id = ConnectionUtility.createConnectionId(connection.type, connection.name, connection.groupId);\r\n        });\r\n\r\n        const forward = this.forwardExecute(RpcRelationshipType.Parent, ConnectionManager.saveConnectionsMethodName, [connections]);\r\n        if (forward) {\r\n            return <Observable<any>>forward;\r\n        }\r\n\r\n        return this.gatewayConnection.put(`${ConnectionManager.gatewayConnectionApi}`, JSON.stringify(connections))\r\n            .pipe(\r\n                tap(_ => {\r\n                    this.forwardNotify(RpcRelationshipType.Child, ConnectionManager.connectionsPropertyName, this.allConnections);\r\n                }));\r\n    }\r\n\r\n    /**\r\n     * Finds a connection given a name and type\r\n     * @param name the name of the connection to find\r\n     * @param type the type of the connection to find, defaults to server type\r\n     */\r\n    public findConnection(name: string, type?: string): Observable<Connection> {\r\n        if (!name) {\r\n            return of(null);\r\n        }\r\n\r\n        type = type || connectionTypeConstants.server;\r\n\r\n        if (this.activeConnection && this.activeConnection.name === name && this.activeConnection.type === type) {\r\n            return of(this.activeConnection);\r\n        }\r\n\r\n        return this.connectionsInitialized\r\n            .pipe(\r\n                map(_ => {\r\n                    return this.connections.find(c => c.name === name && c.type === type);\r\n                }));\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 onForwardInitResponse(data: RpcForwardResponse<ConnectionManagerInitProperties>): void {\r\n        if (data.error) {\r\n            // if there is an error, we cannot continue, so throw its\r\n            throw data.error;\r\n        }\r\n\r\n        this.allConnections = data.result.connections;\r\n        this.activeConnection = data.result.activeConnection;\r\n        this.connectionsChanged.next(\r\n            <ConnectionsInitializedEvent>{\r\n                type: ConnectionChangeType.Initialized,\r\n                connections: this.allConnections\r\n            });\r\n        this.connectionsInitialized.next(this.allConnections);\r\n    }\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 onForwardInit(): Observable<ConnectionManagerInitProperties> {\r\n        return of({\r\n            connections: this.connections,\r\n            activeConnection: this.activeConnection\r\n        });\r\n    }\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 onForwardExecute(from: RpcRelationshipType, name: string, args: any[]): Observable<any> {\r\n        if (from === RpcRelationshipType.Child && args && args.length >= 1) {\r\n            if (name === ConnectionManager.saveConnectionMethodName) {\r\n                // we dont actually have anything to return here.\r\n                return this.saveConnection(args[0]).pipe(map(() => null));\r\n            }\r\n            if (name === ConnectionManager.saveConnectionsMethodName) {\r\n                return this.saveConnections(args[0]);\r\n            }\r\n            if (name === ConnectionManager.removeConnectionMethodName) {\r\n                // we dont actually have anything to return here.\r\n                return this.removeConnection(args[0]).pipe(map(() => null));\r\n            }\r\n        }\r\n        // ConnectionManager does not allow any method calls at this time\r\n        return this.nameNotFound(name);\r\n    }\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 onForwardNotify(from: RpcRelationshipType, name: string, value: any): Observable<void> {\r\n        if (name === ConnectionManager.connectionsPropertyName) {\r\n            this.allConnections = value;\r\n            this.connectionsChanged.next(\r\n                <ConnectionsInitializedEvent>{\r\n                    type: ConnectionChangeType.Initialized,\r\n                    connections: this.allConnections\r\n                });\r\n            this.connectionsInitialized.next(this.allConnections);\r\n            return of(null);\r\n        }\r\n        if (name === ConnectionManager.activeConnectionPropertyName) {\r\n            this.activeConnection = value;\r\n            return of(null);\r\n        }\r\n        return this.nameNotFound(name);\r\n    }\r\n\r\n    /**\r\n     * Get aliases data and save the change with connection\r\n     * @param aliases the alias list.\r\n     * @param connection the connection object.\r\n     * @param nodeName the node name.\r\n     */\r\n    public saveAliasesData(aliases: string[], connection: Connection, nodeName: string) {\r\n        let save = false;\r\n        // save the connection if aliase info changed\r\n        if (!this.isArraySame(aliases, connection.aliases)) {\r\n            connection.aliases = aliases;\r\n            // remove the activeAlias if it's not in aliases any more\r\n            if (connection.aliases.indexOf(connection.activeAlias) === -1) {\r\n                connection.activeAlias = null;\r\n            }\r\n            save = true;\r\n        }\r\n        // if current nodeName is not connection name save it as activeAlias\r\n        if (connection.name !== nodeName && connection.activeAlias !== nodeName) {\r\n            connection.activeAlias = nodeName;\r\n            save = true;\r\n        } else {\r\n            // current nodeName is connection name, remove connection activeAlias\r\n            if (connection.name === nodeName && !!connection.activeAlias) {\r\n                connection.activeAlias = null;\r\n                save = true;\r\n            }\r\n        }\r\n\r\n        if (save) {\r\n            this.saveConnection(connection);\r\n        }\r\n\r\n        // delete visit map entry when succeed\r\n        this.deleteAliasesVisitList(connection.name);\r\n    }\r\n\r\n    /**\r\n     * Get active alias from nodeAliasesVisit map with given nodeName\r\n     *  return the node in aliases list in order,\r\n     *  return null if no alias or end of list\r\n     * @param nodeName key in nodeAliasesVisitMap\r\n     */\r\n    public getActiveAlias(nodeName: string): string {\r\n        // aliases visit list already exists\r\n        if (nodeName in this.nodeAliasesVisitMap) {\r\n            const vlist = this.nodeAliasesVisitMap[nodeName];\r\n            // move currentIndex to next\r\n            if (++vlist.currentIndex < vlist.aliases.length) {\r\n                return vlist.aliases[vlist.currentIndex];\r\n            } else {\r\n                // end of the list\r\n                this.deleteAliasesVisitList(nodeName);\r\n                return null;\r\n            }\r\n        } else { // no aliases list yet (first time or none)\r\n            const aliases = this.getNodeAliasesList(nodeName);\r\n            if (aliases) { // create aliases visit list for the first time\r\n                const vlist: AliasesVisitList = {\r\n                    currentIndex: 0,\r\n                    aliases: aliases\r\n                };\r\n                this.nodeAliasesVisitMap[nodeName] = vlist;\r\n                // return first item, aliases[vlist.currentIndex]\r\n                return aliases[0];\r\n            } else {\r\n                // no aliases, return null\r\n                return null;\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Delete aliasesVistList entry with given nodeName from nodeAliasesVisitMap\r\n     * @param nodeName the node name.\r\n     */\r\n    private deleteAliasesVisitList(nodeName: string): void {\r\n        if (nodeName in this.nodeAliasesVisitMap) {\r\n            delete this.nodeAliasesVisitMap[nodeName];\r\n        }\r\n    }\r\n\r\n    private isArraySame(array1: string[], array2: string[]): boolean {\r\n        // both are not null, compare every element\r\n        array1 = [].concat(array1);\r\n        array2 = [].concat(array2);\r\n        if (array1 && array2) {\r\n            return (array1.length === array2.length) && array1.every(function (element, index) {\r\n                return element === array2[index];\r\n            });\r\n        } else {\r\n            // both are null\r\n            if (!array1 && !array2) {\r\n                return true;\r\n            } else { // one of it is null\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Get nodeAliasesList from map; if not exists create it\r\n     * @param nodeName name of the node, this is unique regardless connection type\r\n     */\r\n    private getNodeAliasesList(nodeName: string): string[] {\r\n        if (nodeName in this.connectionAliasesMap) {\r\n            return this.connectionAliasesMap[nodeName];\r\n        } else {\r\n            return this.buildNodeAliasesList(nodeName);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Build nodeAliasesList entry with given nodeName, then add it into the map,\r\n     *  return the list of aliases\r\n     * @param nodeName name of the node\r\n     */\r\n    private buildNodeAliasesList(nodeName: string): string[] {\r\n        const connection = this.findConnectionWithAliases(nodeName);\r\n        if (!connection) {\r\n            return null;\r\n        }\r\n        // assume activeAlias is in aliases[]\r\n        let aliases: string[] = [];\r\n        if (connection.activeAlias) {\r\n            // put activeAlias at the first of the list\r\n            aliases.push(connection.activeAlias);\r\n            aliases = aliases.concat(connection.aliases.filter(item => item !== connection.activeAlias));\r\n        } else {\r\n            aliases = connection.aliases;\r\n        }\r\n        for (let i = 0; i < aliases.length; i++) {\r\n            const childList = this.buildNodeAliasesList(aliases[i]);\r\n            // insert the childList to list, the list.length will increase\r\n            if (childList) {\r\n                // remove remaining items after current item\r\n                const remainList = aliases.splice(i + 1, aliases.length - i);\r\n                aliases = aliases.concat(childList, remainList);\r\n                // next iteration move to node in childList\r\n            }\r\n        }\r\n        this.connectionAliasesMap[nodeName] = aliases;\r\n        return aliases;\r\n    }\r\n\r\n    /**\r\n     * Finds the first connection with aliases info given a name, assume the connections already initialized\r\n     * @param name the name of the connection to find\r\n     */\r\n    private findConnectionWithAliases(name: string): Connection {\r\n        if (!name) {\r\n            return null;\r\n        }\r\n\r\n        if (this.activeConnection && this.activeConnection.name === name && !!this.activeConnection.aliases) {\r\n            return this.activeConnection;\r\n        }\r\n\r\n        return this.connections.find(c => c.name === name && !!(c.aliases));\r\n    }\r\n\r\n    /**\r\n     * Gets the common connection settings.\r\n     * By default, will use the active connection, but allows input for different connection objects.\r\n     * @return Observable of the common connection settings object\r\n     */\r\n    public getCommonConnectionSettings(connection?: Connection): Observable<CommonConnectionSettings> {\r\n        if (connection) {\r\n            return this.getConnectionSettings(connection)\r\n                .pipe(\r\n                    take(1),\r\n                    map(settings => settings.common),\r\n                    take(1));\r\n\r\n        } else {\r\n            return this.connectionSettings.pipe(map(settings => settings.common), take(1));\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Get extension connection settings for the active connection.\r\n     * @return Observable of specified type\r\n     */\r\n    public getExtensionConnectionSettings<T extends VersionedObject>(type: VersionedObjectConstructor<T>): Observable<T> {\r\n        const name = MsftSme.self().Environment.name;\r\n        return this.connectionSettings\r\n            .pipe(\r\n                map(settings => VersionedObject.ensureIsVersionedObject(settings.extensions[name])),\r\n                map(settings => {\r\n                    return new type(\r\n                        settings,\r\n                        { save: (object) => this.setExtensionSettings(name, object) });\r\n                }),\r\n                take(1));\r\n    }\r\n\r\n    /**\r\n     * Sets extension settings for the active connection.\r\n     * @param extensionName the extension name.\r\n     * @param extensionSettings the extension settings.\r\n     */\r\n    public setExtensionSettings(extensionName: string, extensionSettings: PlainVersionedObject): Observable<void> {\r\n        return this.connectionSettings\r\n            .pipe(mergeMap(settings => settings.trySave(() => {\r\n                settings.extensions[extensionName] = extensionSettings;\r\n            })));\r\n\r\n    }\r\n\r\n    /**\r\n     * Gets the connection settings object\r\n     * By default, will use the active connection, but allows input for different connection objects.\r\n     * @return Observable of ConnectionSettings\r\n     */\r\n    private getConnectionSettings(connection = this.activeConnection): Observable<ConnectionSettings> {\r\n        return of(connection).pipe(\r\n            map(connectionObject => {\r\n                return connectionObject.settings;\r\n            }),\r\n            take(1),\r\n            catchError((error) => {\r\n                const messageFormat = MsftSme.getStrings<Strings>().MsftSmeShell.Core.Errors.UserProfile.Get.formatMessage;\r\n                Logging.logError('ConnectionManager.getConnectionSettings', messageFormat.format(Net.getErrorMessage(error)));\r\n                return throwError(() => error);\r\n            }),\r\n            map((settings: PlainVersionedObject) => {\r\n                // If the setttings are not versioned (or not defined), then start with empty settings object\r\n                settings = VersionedObject.ensureIsVersionedObject(settings);\r\n\r\n                // return new connection settings object\r\n                return new ConnectionSettings(settings, {\r\n                    save: (object) => this.setConnectionSettings(object, connection)\r\n                });\r\n            }));\r\n    }\r\n\r\n    /**\r\n     * Sets the connection settings from the active connection\r\n     * @param settings a PlainVersionedObject\r\n     * @return An observable with the result from the set operation\r\n     */\r\n    private setConnectionSettings(settings: PlainVersionedObject, connection: Connection): Observable<any> {\r\n        // return an observable that saves the connection\r\n        connection.settings = settings;\r\n        return this.saveConnection(connection);\r\n    }\r\n\r\n}\r\n"]}