Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
merge main
  • Loading branch information
Anemy committed Dec 22, 2023
commit fa57c29b2b7fc6aa6fc25a82b25dde0a56c09892
164 changes: 63 additions & 101 deletions src/connectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EventEmitter } from 'events';
import type { MongoClientOptions } from 'mongodb';
import { v4 as uuidv4 } from 'uuid';
import { cloneDeep, merge } from 'lodash';
import { mongoLogId } from 'mongodb-log-writer';
import type {
ConnectionInfo as ConnectionInfoFromLegacyDS,
ConnectionOptions as ConnectionOptionsFromLegacyDS,
Expand All @@ -27,19 +28,11 @@ import type LegacyConnectionModel from './views/webview-app/legacy/connection-mo
import type { StorageController } from './storage';
import type { StatusView } from './views';
import type TelemetryService from './telemetry/telemetryService';
import LINKS from './utils/links';
import { openLink } from './utils/linkHelper';
import type { LoadedConnection } from './storage/connectionStorage';
import { ConnectionStorage } from './storage/connectionStorage';
import LINKS from './utils/links';

export function launderConnectionOptionTypeFromLegacyToCurrent(
opts: ConnectionOptionsFromLegacyDS
): ConnectionOptionsFromCurrentDS {
// Ensure that, at most, the types for OIDC mismatch here.
return opts as Omit<typeof opts, 'oidc'>;
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJSON = require('../package.json');

Expand Down Expand Up @@ -81,14 +74,12 @@ type RecursivePartial<T> = {
: T[P];
};

// function isOIDCAuth(connectionString: string): boolean {
// const authMechanismString = (
// new ConnectionString(connectionString).searchParams.get('authMechanism') ||
// ''
// ).toUpperCase();

// return authMechanismString === 'MONGODB-OIDC';
// }
export function launderConnectionOptionTypeFromLegacyToCurrent(
opts: ConnectionOptionsFromLegacyDS
): ConnectionOptionsFromCurrentDS {
// Ensure that, at most, the types for OIDC mismatch here.
return opts as Omit<typeof opts, 'oidc'>;
}

export default class ConnectionController {
// This is a map of connection ids to their configurations.
Expand Down Expand Up @@ -285,11 +276,19 @@ export default class ConnectionController {
connectionId: string,
connectionType: ConnectionTypes
): Promise<ConnectionAttemptResult> {
// Store a version of this connection, so we can see when the connection
// is successful if it is still the most recent connection attempt.
this._connectingVersion = connectionId;
const connectingAttemptVersion = this._connectingVersion;
this._connecting = true;
// Cancel the current connection attempt if we're connecting.
this._connectionAttempt?.cancelConnectionAttempt();

const connectionAttempt = createConnectionAttempt({
connectFn: (connectionConfig) =>
connect({
...connectionConfig,
productName: packageJSON.name,
productDocsLink: LINKS.extensionDocs(),
}),
logger: Object.assign(log, { mongoLogId }),
});
this._connectionAttempt = connectionAttempt;
this._connectingConnectionId = connectionId;
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);

Expand All @@ -307,13 +306,6 @@ export default class ConnectionController {
};
}

this._statusView.showMessage('Connecting to MongoDB...');
log.info('Connecting to MongoDB...', {
connectionInfo: JSON.stringify(
extractSecrets(this._connections[connectionId]).connectionInfo
),
});

const connectionInfo: LoadedConnection = merge(
cloneDeep(this._connections[connectionId]),
this.connectionMergeInfos[connectionId] ?? {}
Expand All @@ -323,14 +315,14 @@ export default class ConnectionController {
throw new Error('Connect failed: connectionOptions are missing.');
}

// const isOIDCConnectionAttempt = isOIDCAuth(
// connectionInfo.connectionOptions.connectionString
// );
// TODO: Should we show a different alert or warning when
// OIDC to instruct the user to go to the page?
this._statusView.showMessage('Connecting to MongoDB...');
log.info('Connecting to MongoDB...', {
connectionInfo: JSON.stringify(
extractSecrets(this._connections[connectionId]).connectionInfo
),
});

let dataService;
let connectError;
try {
const connectionOptions = adjustConnectionOptionsBeforeConnect({
connectionOptions: launderConnectionOptionTypeFromLegacyToCurrent(
Expand All @@ -343,52 +335,44 @@ export default class ConnectionController {
browserCommandForOIDCAuth: undefined, // We overwrite this below.
},
});
dataService = await connect({
connectionOptions: {
...connectionOptions,
oidc: {
...cloneDeep(connectionOptions.oidc),
openBrowser: async ({ signal, url }) => {
try {
await openLink(url);
} catch (err) {
if (signal.aborted) return;
// If opening the link fails we default to regular link opening.
await vscode.commands.executeCommand(
'vscode.open',
vscode.Uri.parse(url)
);
}
},
dataService = await connectionAttempt.connect({
...connectionOptions,
oidc: {
...cloneDeep(connectionOptions.oidc),
openBrowser: async ({ signal, url }) => {
try {
await openLink(url);
} catch (err) {
if (signal.aborted) return;
// If opening the link fails we default to regular link opening.
await vscode.commands.executeCommand(
'vscode.open',
vscode.Uri.parse(url)
);
}
},
},
productName: packageJSON.name,
productDocsLink: LINKS.extensionDocs(),
});
} catch (error) {
connectError = error;
}

const shouldEndPrevConnectAttempt = this._endPrevConnectAttempt({
connectionId,
connectingAttemptVersion,
dataService,
});

if (shouldEndPrevConnectAttempt) {
return {
successfullyConnected: false,
connectionErrorMessage: 'connection attempt overridden',
};
}

this._statusView.hideMessage();

if (connectError) {
this._connecting = false;
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);

throw connectError;
if (!dataService || connectionAttempt.isClosed()) {
return {
successfullyConnected: false,
connectionErrorMessage: 'connection attempt cancelled',
};
}
} catch (error) {
throw error;
} finally {
if (
this._connectionAttempt === connectionAttempt &&
this._connectingConnectionId === connectionId
) {
// When this is still the most recent connection attempt cleanup the connecting messages.
this._statusView.hideMessage();
this._connectionAttempt = null;
this._connectingConnectionId = null;
this.eventEmitter.emit(DataServiceEventTypes.CONNECTIONS_DID_CHANGE);
}
}

log.info('Successfully connected', { connectionId });
Expand Down Expand Up @@ -449,7 +433,7 @@ export default class ConnectionController {
return;
}

let mergeConnectionInfo = {};
let mergeConnectionInfo: LoadedConnection | {} = {};
if (vscode.workspace.getConfiguration('mdb').get('persistOIDCTokens')) {
mergeConnectionInfo = {
connectionOptions: await dataService.getUpdatedSecrets(),
Expand Down Expand Up @@ -502,30 +486,8 @@ export default class ConnectionController {
});
}

private _endPrevConnectAttempt({
connectionId,
connectingAttemptVersion,
dataService,
}: {
connectionId: string;
connectingAttemptVersion: null | string;
dataService: DataService | null;
}): boolean {
if (
connectingAttemptVersion !== this._connectingVersion ||
!this._connections[connectionId]
) {
// If the current attempt is no longer the most recent attempt
// or the connection no longer exists we silently end the connection
// and return.
void dataService?.disconnect().catch(() => {
/* ignore */
});

return true;
}

return false;
cancelConnectionAttempt() {
this._connectionAttempt?.cancelConnectionAttempt();
}

async connectWithConnectionId(connectionId: string): Promise<boolean> {
Expand Down
12 changes: 6 additions & 6 deletions src/test/suite/views/webviewController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,12 @@ suite('Webview Test Suite', () => {
const fakeWebview = {
html: '',
postMessage: (message): void => {
assert(!message.connectionSuccess);
const expectedMessage = 'connection attempt overridden';
assert(
message.connectionMessage === expectedMessage,
`Expected connection message "${message.connectionMessage}" to equal ${expectedMessage}`
);
try {
assert.strictEqual(message.connectionSuccess, false);
assert.strictEqual(
message.connectionMessage,
'connection attempt cancelled'
);

void testConnectionController.disconnect();
done();
Expand Down
2 changes: 1 addition & 1 deletion src/views/webview-app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useDetectVsCodeDarkMode } from './use-detect-vscode-dark-mode';
const App: React.FC = () => {
const darkMode = useDetectVsCodeDarkMode();

return true || getFeatureFlag('useNewConnectionForm') ? (
return getFeatureFlag('useNewConnectionForm') ? (
<LeafyGreenProvider darkMode={darkMode}>
<OverviewPage />
</LeafyGreenProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/views/webview-app/connection-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const initialConnectionInfo = createNewConnectionInfo();
const ConnectionForm: React.FunctionComponent<{
isConnecting: boolean;
onCancelConnectClicked: () => void;
onConnectClicked: (onConnectClicked: ConnectionInfo) => void;
onConnectClicked: (connectionInfo: ConnectionInfo) => void;
onClose: () => void;
open: boolean;
connectionErrorMessage: string;
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.