Skip to content

Commit 47a4c0e

Browse files
Open Protocol Handlers for Opening Connection Dialog (#18047)
* Set up open protocol handler and open conn dialog * Provide args to command handler * Feed connection info into connection dialog * Clean up parse URI logic * Check for empty URI parameters * Remove console log statement * Support all options in parameters * Always register the URI handler * Stops processing args when connection string is provided * Add jsDoc comments to protocol handler * Fix formatting * Fix formatting * Fix formatting * Address linting issues * Fix linting * Fix lint errors * Fix linting errors * Last lint fix
1 parent 097b89d commit 47a4c0e

File tree

3 files changed

+296
-3
lines changed

3 files changed

+296
-3
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"multi-root ready"
3737
],
3838
"activationEvents": [
39+
"onUri",
3940
"onCommand:mssql.loadCompletionExtension"
4041
],
4142
"main": "./out/src/extension",

src/controllers/mainController.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*---------------------------------------------------------------------------------------------
1+
/*---------------------------------------------------------------------------------------------
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
@@ -55,6 +55,7 @@ import { ObjectExplorerFilter } from "../objectExplorer/objectExplorerFilter";
5555
import { ExecutionPlanService } from "../services/executionPlanService";
5656
import { ExecutionPlanWebviewController } from "./executionPlanWebviewController";
5757
import { QueryResultWebviewController } from "../queryResult/queryResultWebViewController";
58+
import { MssqlProtocolHandler } from "../mssqlProtocolHandler";
5859

5960
/**
6061
* The main controller class that initializes the extension
@@ -336,6 +337,24 @@ export default class MainController implements vscode.Disposable {
336337
providerInstance,
337338
);
338339

340+
const self = this;
341+
const uriHandler: vscode.UriHandler = {
342+
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
343+
if (self.isExperimentalEnabled) {
344+
const mssqlProtocolHandler = new MssqlProtocolHandler();
345+
346+
const connectionInfo =
347+
mssqlProtocolHandler.handleUri(uri);
348+
349+
vscode.commands.executeCommand(
350+
Constants.cmdAddObjectExplorer,
351+
connectionInfo,
352+
);
353+
}
354+
},
355+
};
356+
vscode.window.registerUriHandler(uriHandler);
357+
339358
// Add handlers for VS Code generated commands
340359
this._vscodeWrapper.onDidCloseTextDocument(
341360
async (params) => await this.onDidCloseTextDocument(params),
@@ -676,18 +695,24 @@ export default class MainController implements vscode.Disposable {
676695
// Old style Add connection when experimental features are not enabled
677696

678697
// Add Object Explorer Node
679-
this.registerCommand(Constants.cmdAddObjectExplorer);
680-
this._event.on(Constants.cmdAddObjectExplorer, async () => {
698+
this.registerCommandWithArgs(Constants.cmdAddObjectExplorer);
699+
this._event.on(Constants.cmdAddObjectExplorer, async (args: any) => {
681700
if (!this.isExperimentalEnabled) {
682701
if (!self._objectExplorerProvider.objectExplorerExists) {
683702
self._objectExplorerProvider.objectExplorerExists = true;
684703
}
685704
await self.createObjectExplorerSession();
686705
} else {
706+
let connectionInfo: IConnectionInfo | undefined = undefined;
707+
if (args) {
708+
connectionInfo = args as IConnectionInfo;
709+
}
710+
687711
const connDialog = new ConnectionDialogWebviewController(
688712
this._context,
689713
this,
690714
this._objectExplorerProvider,
715+
connectionInfo,
691716
);
692717
connDialog.revealToForeground();
693718
}

src/mssqlProtocolHandler.ts

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from "vscode";
7+
import * as Utils from "./models/utils";
8+
import { IConnectionInfo } from "vscode-mssql";
9+
10+
enum Command {
11+
connect = "/connect",
12+
openConnectionDialog = "/openConnectionDialog",
13+
}
14+
15+
/**
16+
* Handles MSSQL protocol URIs.
17+
*/
18+
export class MssqlProtocolHandler {
19+
constructor() {}
20+
21+
/**
22+
* Handles the given URI and returns connection information if applicable. Examples of URIs handled:
23+
* - vscode://ms-mssql.mssql/connect?server=myServer&database=dbName&user=sa&authenticationType=SqlLogin
24+
* - vscode://ms-mssql.mssql/connect?connectionString=Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;
25+
*
26+
* @param uri - The URI to handle.
27+
* @returns The connection information or undefined if not applicable.
28+
*/
29+
public handleUri(uri: vscode.Uri): IConnectionInfo | undefined {
30+
Utils.logDebug(
31+
`[MssqlProtocolHandler][handleUri] URI: ${uri.toString()}`,
32+
);
33+
34+
switch (uri.path) {
35+
case Command.connect:
36+
Utils.logDebug(
37+
`[MssqlProtocolHandler][handleUri] connect: ${uri.path}`,
38+
);
39+
40+
return this.connect(uri);
41+
42+
case Command.openConnectionDialog:
43+
return undefined;
44+
45+
default:
46+
Utils.logDebug(
47+
`[MssqlProtocolHandler][handleUri] Unknown URI path, defaulting to connect: ${uri.path}`,
48+
);
49+
50+
return this.connect(uri);
51+
}
52+
}
53+
54+
/**
55+
* Connects using the given URI.
56+
*
57+
* @param uri - The URI containing connection information.
58+
* @returns The connection information or undefined if not applicable.
59+
*/
60+
private connect(uri: vscode.Uri): IConnectionInfo | undefined {
61+
return this.readProfileFromArgs(uri.query);
62+
}
63+
64+
/**
65+
* Reads the profile information from the query string and returns an IConnectionInfo object.
66+
*
67+
* @param query - The query string containing connection information.
68+
* @returns The connection information object or undefined if the query is empty.
69+
*/
70+
private readProfileFromArgs(query: string): IConnectionInfo | undefined {
71+
if (!query) {
72+
return undefined;
73+
}
74+
75+
const args = new URLSearchParams(query);
76+
77+
const connectionString = args.get("connectionString") ?? undefined;
78+
if (connectionString !== undefined) {
79+
return {
80+
connectionString,
81+
} as IConnectionInfo;
82+
}
83+
84+
const server = args.get("server") ?? "";
85+
const database = args.get("database") ?? "";
86+
const user = args.get("user") ?? "";
87+
const email = args.get("email") ?? "";
88+
const accountId = args.get("accountId") ?? "";
89+
const tenantId = args.get("tenantId") ?? "";
90+
91+
const portValue = parseInt(args.get("port"));
92+
const port = isNaN(portValue) ? 0 : portValue;
93+
94+
/*
95+
Authentication Type:
96+
1. Take --authenticationType, if not
97+
2. Take --integrated, if not
98+
3. take --aad, if not
99+
4. If user exists, and user has @, then it's azureMFA
100+
5. If user exists but doesn't have @, then its SqlLogin
101+
6. If user doesn't exist, then integrated
102+
*/
103+
const authenticationType = args.get("authenticationType")
104+
? args.get("authenticationType")
105+
: args.get("integrated")
106+
? "Integrated"
107+
: args.get("aad")
108+
? "AzureMFA"
109+
: user && user.length > 0
110+
? user.includes("@")
111+
? "AzureMFA"
112+
: "SqlLogin"
113+
: "Integrated";
114+
115+
const azureAccountToken = args.get("azureAccountToken") ?? undefined;
116+
117+
const expiresOnValue = parseInt(args.get("expiresOn"));
118+
const expiresOn = isNaN(expiresOnValue) ? undefined : expiresOnValue;
119+
120+
const encryptValueFlag = parseInt(args.get("encrypt"));
121+
const encryptValueStr = args.get("encrypt") ?? "Mandatory"; // default to Mandatory
122+
const encrypt = isNaN(encryptValueFlag)
123+
? encryptValueStr
124+
: encryptValueFlag === 1;
125+
126+
const trustServerCertificateValue = parseInt(
127+
args.get("trustServerCertificate"),
128+
);
129+
const trustServerCertificate = isNaN(trustServerCertificateValue)
130+
? undefined
131+
: trustServerCertificateValue === 1;
132+
133+
const hostNameInCertificate =
134+
args.get("hostNameInCertificate") ?? undefined;
135+
136+
const persistSecurityInfoValue = parseInt(
137+
args.get("persistSecurityInfo"),
138+
);
139+
const persistSecurityInfo = isNaN(persistSecurityInfoValue)
140+
? undefined
141+
: persistSecurityInfoValue === 1;
142+
143+
const columnEncryptionSetting =
144+
args.get("columnEncryptionSetting") ?? undefined;
145+
const attestationProtocol =
146+
args.get("attestationProtocol") ?? undefined;
147+
const enclaveAttestationUrl =
148+
args.get("enclaveAttestationUrl") ?? undefined;
149+
150+
const connectTimeoutValue = parseInt(args.get("connectTimeout"));
151+
const connectTimeout = isNaN(connectTimeoutValue)
152+
? undefined
153+
: connectTimeoutValue;
154+
155+
const commandTimeoutValue = parseInt(args.get("commandTimeout"));
156+
const commandTimeout = isNaN(commandTimeoutValue)
157+
? undefined
158+
: commandTimeoutValue;
159+
160+
const connectRetryCountValue = parseInt(args.get("connectRetryCount"));
161+
const connectRetryCount = isNaN(connectRetryCountValue)
162+
? undefined
163+
: connectRetryCountValue;
164+
165+
const connectRetryIntervalValue = parseInt(
166+
args.get("connectRetryInterval"),
167+
);
168+
const connectRetryInterval = isNaN(connectRetryIntervalValue)
169+
? undefined
170+
: connectRetryIntervalValue;
171+
172+
const applicationName = args.get("applicationName")
173+
? `${args.get("applicationName")}-azdata`
174+
: "azdata";
175+
176+
const workstationId = args.get("workstationId") ?? undefined;
177+
const applicationIntent = args.get("applicationIntent") ?? undefined;
178+
const currentLanguage = args.get("currentLanguage") ?? undefined;
179+
180+
const poolingValue = parseInt(args.get("pooling"));
181+
const pooling = isNaN(poolingValue) ? undefined : poolingValue === 1;
182+
183+
const maxPoolSizeValue = parseInt(args.get("maxPoolSize"));
184+
const maxPoolSize = isNaN(maxPoolSizeValue)
185+
? undefined
186+
: maxPoolSizeValue;
187+
188+
const minPoolSizeValue = parseInt(args.get("minPoolSize"));
189+
const minPoolSize = isNaN(minPoolSizeValue)
190+
? undefined
191+
: minPoolSizeValue;
192+
193+
const loadBalanceTimeoutValue = parseInt(
194+
args.get("loadBalanceTimeout"),
195+
);
196+
const loadBalanceTimeout = isNaN(loadBalanceTimeoutValue)
197+
? undefined
198+
: loadBalanceTimeoutValue;
199+
200+
const replicationValue = parseInt(args.get("replication"));
201+
const replication = isNaN(replicationValue)
202+
? undefined
203+
: replicationValue === 1;
204+
205+
const attachDbFilename = args.get("attachDbFilename") ?? undefined;
206+
const failoverPartner = args.get("failoverPartner") ?? undefined;
207+
208+
const multiSubnetFailoverValue = parseInt(
209+
args.get("multiSubnetFailover"),
210+
);
211+
const multiSubnetFailover = isNaN(multiSubnetFailoverValue)
212+
? undefined
213+
: multiSubnetFailoverValue === 1;
214+
215+
const multipleActiveResultSetsValue = parseInt(
216+
args.get("multipleActiveResultSets"),
217+
);
218+
const multipleActiveResultSets = isNaN(multipleActiveResultSetsValue)
219+
? undefined
220+
: multipleActiveResultSetsValue === 1;
221+
222+
const packetSizeValue = parseInt(args.get("packetSize"));
223+
const packetSize = isNaN(packetSizeValue) ? undefined : packetSizeValue;
224+
225+
const typeSystemVersion = args.get("typeSystemVersion") ?? undefined;
226+
227+
return {
228+
server,
229+
database,
230+
user,
231+
email,
232+
accountId,
233+
tenantId,
234+
port,
235+
authenticationType,
236+
azureAccountToken,
237+
expiresOn,
238+
encrypt,
239+
trustServerCertificate,
240+
hostNameInCertificate,
241+
persistSecurityInfo,
242+
columnEncryptionSetting,
243+
attestationProtocol,
244+
enclaveAttestationUrl,
245+
connectTimeout,
246+
commandTimeout,
247+
connectRetryCount,
248+
connectRetryInterval,
249+
applicationName,
250+
workstationId,
251+
applicationIntent,
252+
currentLanguage,
253+
pooling,
254+
maxPoolSize,
255+
minPoolSize,
256+
loadBalanceTimeout,
257+
replication,
258+
attachDbFilename,
259+
failoverPartner,
260+
multiSubnetFailover,
261+
multipleActiveResultSets,
262+
packetSize,
263+
typeSystemVersion,
264+
connectionString,
265+
} as IConnectionInfo;
266+
}
267+
}

0 commit comments

Comments
 (0)