Skip to content

Commit 3d0c158

Browse files
authored
chore: add connection storage, simplify connection controller and storage controller interfaces (#627)
1 parent 68a90ab commit 3d0c158

10 files changed

+788
-482
lines changed

src/connectionController.ts

Lines changed: 28 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@ import { CONNECTION_STATUS } from './views/webview-app/extension-app-message-con
1212
import { createLogger } from './logging';
1313
import formatError from './utils/formatError';
1414
import type LegacyConnectionModel from './views/webview-app/legacy/connection-model/legacy-connection-model';
15-
import type { SecretStorageLocationType } from './storage/storageController';
16-
import {
17-
StorageLocation,
18-
SecretStorageLocation,
19-
} from './storage/storageController';
2015
import type { StorageController } from './storage';
21-
import { StorageVariables } from './storage';
2216
import type { StatusView } from './views';
2317
import type TelemetryService from './telemetry/telemetryService';
2418
import LINKS from './utils/links';
@@ -27,11 +21,11 @@ import type {
2721
ConnectionOptions as ConnectionOptionsFromLegacyDS,
2822
} from 'mongodb-data-service-legacy';
2923
import {
30-
getConnectionTitle,
3124
extractSecrets,
32-
mergeSecrets,
3325
convertConnectionModelToInfo,
3426
} from 'mongodb-data-service-legacy';
27+
import type { LoadedConnection } from './storage/connectionStorage';
28+
import { ConnectionStorage } from './storage/connectionStorage';
3529

3630
export function launderConnectionOptionTypeFromLegacyToCurrent(
3731
opts: ConnectionOptionsFromLegacyDS
@@ -58,15 +52,6 @@ export enum ConnectionTypes {
5852
CONNECTION_ID = 'CONNECTION_ID',
5953
}
6054

61-
export interface StoreConnectionInfo {
62-
id: string; // Connection model id or a new uuid.
63-
name: string; // Possibly user given name, not unique.
64-
storageLocation: StorageLocation;
65-
secretStorageLocation?: SecretStorageLocationType;
66-
connectionOptions?: ConnectionOptionsFromLegacyDS;
67-
connectionModel?: LegacyConnectionModel;
68-
}
69-
7055
export enum NewConnectionType {
7156
NEW_CONNECTION = 'NEW_CONNECTION',
7257
SAVED_CONNECTION = 'SAVED_CONNECTION',
@@ -82,15 +67,6 @@ interface ConnectionQuickPicks {
8267
data: { type: NewConnectionType; connectionId?: string };
8368
}
8469

85-
type StoreConnectionInfoWithConnectionOptions = StoreConnectionInfo &
86-
Required<Pick<StoreConnectionInfo, 'connectionOptions'>>;
87-
88-
type StoreConnectionInfoWithSecretStorageLocation = StoreConnectionInfo &
89-
Required<Pick<StoreConnectionInfo, 'secretStorageLocation'>>;
90-
91-
type LoadedConnection = StoreConnectionInfoWithConnectionOptions &
92-
StoreConnectionInfoWithSecretStorageLocation;
93-
9470
export default class ConnectionController {
9571
// This is a map of connection ids to their configurations.
9672
// These connections can be saved on the session (runtime),
@@ -99,7 +75,7 @@ export default class ConnectionController {
9975
[connectionId: string]: LoadedConnection;
10076
} = Object.create(null);
10177
_activeDataService: DataService | null = null;
102-
_storageController: StorageController;
78+
_connectionStorage: ConnectionStorage;
10379
_telemetryService: TelemetryService;
10480

10581
private readonly _serviceName = 'mdb.vscode.savedConnections';
@@ -130,37 +106,19 @@ export default class ConnectionController {
130106
telemetryService: TelemetryService;
131107
}) {
132108
this._statusView = statusView;
133-
this._storageController = storageController;
134109
this._telemetryService = telemetryService;
110+
this._connectionStorage = new ConnectionStorage({
111+
storageController,
112+
});
135113
}
136114

137115
async loadSavedConnections(): Promise<void> {
138-
const globalAndWorkspaceConnections = Object.entries({
139-
...this._storageController.get(
140-
StorageVariables.GLOBAL_SAVED_CONNECTIONS,
141-
StorageLocation.GLOBAL
142-
),
143-
...this._storageController.get(
144-
StorageVariables.WORKSPACE_SAVED_CONNECTIONS,
145-
StorageLocation.WORKSPACE
146-
),
147-
});
148-
149-
await Promise.all(
150-
globalAndWorkspaceConnections.map(
151-
async ([connectionId, connectionInfo]) => {
152-
const connectionInfoWithSecrets =
153-
await this._getConnectionInfoWithSecrets(connectionInfo);
154-
if (!connectionInfoWithSecrets) {
155-
return;
156-
}
116+
const loadedConnections = await this._connectionStorage.loadConnections();
157117

158-
this._connections[connectionId] = connectionInfoWithSecrets;
159-
}
160-
)
161-
);
118+
for (const connection of loadedConnections) {
119+
this._connections[connection.id] = connection;
120+
}
162121

163-
const loadedConnections = Object.values(this._connections);
164122
if (loadedConnections.length) {
165123
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);
166124
}
@@ -179,61 +137,6 @@ export default class ConnectionController {
179137
}); */
180138
}
181139

182-
// TODO: Move this into the connectionStorage.
183-
async _getConnectionInfoWithSecrets(
184-
connectionInfo: StoreConnectionInfo
185-
): Promise<LoadedConnection | undefined> {
186-
try {
187-
// We tried migrating this connection earlier but failed because Keytar was not
188-
// available. So we return simply the connection without secrets.
189-
if (
190-
connectionInfo.connectionModel ||
191-
!connectionInfo.secretStorageLocation ||
192-
connectionInfo.secretStorageLocation === 'vscode.Keytar' ||
193-
connectionInfo.secretStorageLocation ===
194-
SecretStorageLocation.KeytarSecondAttempt
195-
) {
196-
// We had migrations in VSCode for ~5 months. We drop the connections
197-
// that did not migrate.
198-
return undefined;
199-
}
200-
201-
const unparsedSecrets =
202-
(await this._storageController.getSecret(connectionInfo.id)) ?? '';
203-
204-
return this._mergedConnectionInfoWithSecrets(
205-
connectionInfo as LoadedConnection,
206-
unparsedSecrets
207-
);
208-
} catch (error) {
209-
log.error('Error while retrieving connection info', error);
210-
return undefined;
211-
}
212-
}
213-
214-
_mergedConnectionInfoWithSecrets(
215-
connectionInfo: LoadedConnection,
216-
unparsedSecrets: string
217-
): LoadedConnection {
218-
if (!unparsedSecrets) {
219-
return connectionInfo;
220-
}
221-
222-
const secrets = JSON.parse(unparsedSecrets);
223-
const connectionInfoWithSecrets = mergeSecrets(
224-
{
225-
id: connectionInfo.id,
226-
connectionOptions: connectionInfo.connectionOptions,
227-
},
228-
secrets
229-
);
230-
231-
return {
232-
...connectionInfo,
233-
connectionOptions: connectionInfoWithSecrets.connectionOptions,
234-
};
235-
}
236-
237140
async connectWithURI(): Promise<boolean> {
238141
let connectionString: string | undefined;
239142

@@ -293,7 +196,7 @@ export default class ConnectionController {
293196
);
294197

295198
try {
296-
const connectResult = await this.saveNewConnectionFromFormAndConnect(
199+
const connectResult = await this.saveNewConnectionAndConnect(
297200
{
298201
id: uuidv4(),
299202
connectionOptions: {
@@ -333,52 +236,24 @@ export default class ConnectionController {
333236
});
334237
}
335238

336-
private async _saveConnectionWithSecrets(
337-
newStoreConnectionInfoWithSecrets: LoadedConnection
338-
): Promise<LoadedConnection> {
339-
// We don't want to store secrets to disc.
340-
const { connectionInfo: safeConnectionInfo, secrets } = extractSecrets(
341-
newStoreConnectionInfoWithSecrets as ConnectionInfoFromLegacyDS
342-
);
343-
const savedConnectionInfo = await this._storageController.saveConnection({
344-
...newStoreConnectionInfoWithSecrets,
345-
connectionOptions: safeConnectionInfo.connectionOptions, // The connection info without secrets.
346-
});
347-
await this._storageController.setSecret(
348-
savedConnectionInfo.id,
349-
JSON.stringify(secrets)
350-
);
351-
352-
return savedConnectionInfo;
353-
}
354-
355-
async saveNewConnectionFromFormAndConnect(
239+
async saveNewConnectionAndConnect(
356240
originalConnectionInfo: ConnectionInfoFromLegacyDS,
357241
connectionType: ConnectionTypes
358242
): Promise<ConnectionAttemptResult> {
359-
const name = getConnectionTitle(originalConnectionInfo);
360-
const newConnectionInfo = {
361-
id: originalConnectionInfo.id,
362-
name,
363-
// To begin we just store it on the session, the storage controller
364-
// handles changing this based on user preference.
365-
storageLocation: StorageLocation.NONE,
366-
secretStorageLocation: SecretStorageLocation.SecretStorage,
367-
connectionOptions: originalConnectionInfo.connectionOptions,
368-
};
243+
const savedConnectionWithoutSecrets =
244+
await this._connectionStorage.saveNewConnection(originalConnectionInfo);
369245

370-
const savedConnectionInfo = await this._saveConnectionWithSecrets(
371-
newConnectionInfo
372-
);
373-
374-
this._connections[savedConnectionInfo.id] = {
375-
...savedConnectionInfo,
246+
this._connections[savedConnectionWithoutSecrets.id] = {
247+
...savedConnectionWithoutSecrets,
376248
connectionOptions: originalConnectionInfo.connectionOptions, // The connection options with secrets.
377249
};
378250

379-
log.info('Connect called to connect to instance', savedConnectionInfo.name);
251+
log.info(
252+
'Connect called to connect to instance',
253+
savedConnectionWithoutSecrets.name
254+
);
380255

381-
return this._connect(savedConnectionInfo.id, connectionType);
256+
return this._connect(savedConnectionWithoutSecrets.id, connectionType);
382257
}
383258

384259
async _connectWithDataService(
@@ -571,15 +446,10 @@ export default class ConnectionController {
571446
return true;
572447
}
573448

574-
private async _removeSecretsFromKeychain(connectionId: string) {
575-
await this._storageController.deleteSecret(connectionId);
576-
}
577-
578449
async removeSavedConnection(connectionId: string): Promise<void> {
579450
delete this._connections[connectionId];
580451

581-
await this._removeSecretsFromKeychain(connectionId);
582-
this._storageController.removeConnection(connectionId);
452+
await this._connectionStorage.removeConnection(connectionId);
583453

584454
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);
585455
}
@@ -680,7 +550,7 @@ export default class ConnectionController {
680550
},
681551
});
682552
} catch (e) {
683-
throw new Error(`An error occured parsing the connection name: ${e}`);
553+
throw new Error(`An error occurred parsing the connection name: ${e}`);
684554
}
685555

686556
if (!inputtedConnectionName) {
@@ -691,7 +561,7 @@ export default class ConnectionController {
691561
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);
692562
this.eventEmitter.emit(DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED);
693563

694-
await this._storageController.saveConnection(
564+
await this._connectionStorage.saveConnection(
695565
this._connections[connectionId]
696566
);
697567

@@ -725,7 +595,7 @@ export default class ConnectionController {
725595
return this._activeDataService !== null;
726596
}
727597

728-
getSavedConnections(): StoreConnectionInfo[] {
598+
getSavedConnections(): LoadedConnection[] {
729599
return Object.values(this._connections);
730600
}
731601

@@ -917,13 +787,10 @@ export default class ConnectionController {
917787
},
918788
},
919789
...Object.values(this._connections)
920-
.sort(
921-
(
922-
connectionA: StoreConnectionInfo,
923-
connectionB: StoreConnectionInfo
924-
) => (connectionA.name || '').localeCompare(connectionB.name || '')
790+
.sort((connectionA: LoadedConnection, connectionB: LoadedConnection) =>
791+
(connectionA.name || '').localeCompare(connectionB.name || '')
925792
)
926-
.map((item: StoreConnectionInfo) => ({
793+
.map((item: LoadedConnection) => ({
927794
label: item.name,
928795
data: {
929796
type: NewConnectionType.SAVED_CONNECTION,

src/explorer/explorerTreeController.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as vscode from 'vscode';
22

3-
import type { StoreConnectionInfo } from '../connectionController';
43
import type ConnectionController from '../connectionController';
54
import { DataServiceEventTypes } from '../connectionController';
65
import ConnectionTreeItem from './connectionTreeItem';
@@ -139,7 +138,7 @@ export default class ExplorerTreeController
139138
this._connectionTreeItems = {};
140139

141140
// Create new connection tree items, using cached children wherever possible.
142-
connections.forEach((connection: StoreConnectionInfo) => {
141+
connections.forEach((connection) => {
143142
const isActiveConnection =
144143
connection.id === this._connectionController.getActiveConnectionId();
145144
const isBeingConnectedTo =

src/mdbExtensionController.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ import type PlaygroundsTreeItem from './explorer/playgroundsTreeItem';
3838
import PlaygroundResultProvider from './editors/playgroundResultProvider';
3939
import WebviewController from './views/webviewController';
4040
import { createIdFactory, generateId } from './utils/objectIdHelper';
41+
import { ConnectionStorage } from './storage/connectionStorage';
4142

4243
// This class is the top-level controller for our extension.
4344
// Commands which the extensions handles are defined in the function `activate`.
4445
export default class MDBExtensionController implements vscode.Disposable {
4546
_playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider;
4647
_playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider;
4748
_connectionController: ConnectionController;
49+
_connectionStorage: ConnectionStorage;
4850
_context: vscode.ExtensionContext;
4951
_editorsController: EditorsController;
5052
_playgroundController: PlaygroundController;
@@ -68,6 +70,9 @@ export default class MDBExtensionController implements vscode.Disposable {
6870
this._context = context;
6971
this._statusView = new StatusView(context);
7072
this._storageController = new StorageController(context);
73+
this._connectionStorage = new ConnectionStorage({
74+
storageController: this._storageController,
75+
});
7176
this._telemetryService = new TelemetryService(
7277
this._storageController,
7378
context,
@@ -669,7 +674,7 @@ export default class MDBExtensionController implements vscode.Disposable {
669674
// Show the overview page when it hasn't been show to the
670675
// user yet, and they have no saved connections.
671676
if (!hasBeenShownViewAlready) {
672-
if (!this._storageController.hasSavedConnections()) {
677+
if (!this._connectionStorage.hasSavedConnections()) {
673678
void vscode.commands.executeCommand(
674679
EXTENSION_COMMANDS.MDB_OPEN_OVERVIEW_PAGE
675680
);

0 commit comments

Comments
 (0)