@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1 lines • 35.3 kB
Source Map (JSON)
{"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"]}