Skip to content

Commit 8192b14

Browse files
committed
Improve management of web sessions when connection changes
1 parent 05f2cd0 commit 8192b14

2 files changed

Lines changed: 68 additions & 73 deletions

File tree

src/api/index.ts

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,31 @@ const DEFAULT_SERVER_VERSION = "2016.2.0";
2020
import * as Atelier from "./atelier";
2121
import { isfsConfig } from "../utils/FileProviderUtil";
2222

23-
// Map of the authRequest promises for each username@host:port/pathPrefix target to avoid concurrency issues
23+
// Map of the authRequest promises for each `username@http(s)://host:port/pathPrefix` target to avoid concurrency issues
2424
const authRequestMap = new Map<string, Promise<any>>();
2525

26-
/** Map of `username@host:port/pathPrefix` to cookies */
27-
const cookiesMap = new Map<string, string[]>();
26+
/** Map of `username@http(s)://host:port/pathPrefix` to cookies */
27+
export const cookiesMap = new Map<string, string[]>();
28+
29+
/** Log out of CSP sessions specified in the `sessions` array, or all known sessions if no argument was passed. */
30+
export async function logoutOfSessions(sessions?: string[]): Promise<void> {
31+
const httpsAgent = new httpsModule.Agent({
32+
rejectUnauthorized: vscode.workspace.getConfiguration("http").get("proxyStrictSSL"),
33+
});
34+
return Promise.allSettled(
35+
(sessions ?? Array.from(cookiesMap.keys())).map((session) => {
36+
const cookie = cookiesMap.get(session);
37+
if (!cookie) return;
38+
const url = session.slice(session.indexOf("@") + 1);
39+
return axios.head(`${url}/api/atelier/?CacheLogout=end`, {
40+
headers: {
41+
Cookie: cookie,
42+
},
43+
httpsAgent,
44+
});
45+
})
46+
).then(() => {}); // Returned object isn't needed
47+
}
2848

2949
interface ConnectionSettings {
3050
serverName: string;
@@ -175,10 +195,6 @@ export class AtelierAPI {
175195
return cookiesMap.get(this.mapKey()) ?? [];
176196
}
177197

178-
public clearCookies(): void {
179-
cookiesMap.delete(this.mapKey());
180-
}
181-
182198
public xdebugUrl(): string {
183199
const { host, https, port, apiVersion, pathPrefix } = this.config;
184200
const proto = https ? "wss" : "ws";
@@ -208,13 +224,13 @@ export class AtelierAPI {
208224
}
209225

210226
/** Return the key for getting values from connection-specific Maps for this connection */
211-
private mapKey(): string {
212-
const { host, port, username } = this.config;
227+
public mapKey(): string {
228+
const { host, https, port, username } = this.config;
213229
let pathPrefix = this._config.pathPrefix || "";
214230
if (pathPrefix.length && !pathPrefix.startsWith("/")) {
215231
pathPrefix = "/" + pathPrefix;
216232
}
217-
return `${username}@${host}:${port}${pathPrefix}`;
233+
return `${username}@http${https ? "s" : ""}://${host}:${port}${pathPrefix}`;
218234
}
219235

220236
private setConnection(workspaceFolderName: string, namespace?: string): void {
@@ -307,7 +323,7 @@ export class AtelierAPI {
307323
return serverName && serverName !== "" ? serverName : `${host}:${port}${pathPrefix}`;
308324
}
309325

310-
public async request(
326+
private async request(
311327
minVersion: number,
312328
method: string,
313329
path?: string,
@@ -457,7 +473,7 @@ export class AtelierAPI {
457473
}
458474
throw { statusCode: response.status, message: response.statusText };
459475
}
460-
await this.updateCookies(response.headers["set-cookie"] || []);
476+
this.updateCookies(response.headers["set-cookie"] || []);
461477
if (method === "HEAD") {
462478
if (!originalPath) {
463479
authRequestMap.delete(mapKey);

src/extension.ts

Lines changed: 40 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import { ObjectScriptRoutineSymbolProvider } from "./providers/ObjectScriptRouti
7171
import { ObjectScriptCodeLensProvider } from "./providers/ObjectScriptCodeLensProvider";
7272
import { XmlContentProvider } from "./providers/XmlContentProvider";
7373

74-
import { AtelierAPI } from "./api";
74+
import { AtelierAPI, cookiesMap, logoutOfSessions } from "./api";
7575
import { ObjectScriptDebugAdapterDescriptorFactory } from "./debug/debugAdapterFactory";
7676
import { ObjectScriptConfigurationProvider } from "./debug/debugConfProvider";
7777
import { ProjectsExplorerProvider } from "./explorer/projectsExplorer";
@@ -340,20 +340,16 @@ export function getResolvedConnectionSpec(key: string, dflt: any): any {
340340
/** The `api.serverId`s of all servers that are known to be inactive */
341341
export const inactiveServerIds: Set<string> = new Set();
342342

343-
export async function checkConnection(
344-
clearCookies = false,
345-
uri?: vscode.Uri,
346-
triggerRefreshes?: boolean
347-
): Promise<void> {
343+
export async function checkConnection(clearState = false, uri?: vscode.Uri, triggerRefreshes?: boolean): Promise<void> {
348344
// Do nothing if already checking the connection
349345
if (checkingConnection) {
350346
return;
351347
}
352348

353349
const { apiTarget, configName } = connectionTarget(uri);
354350
const wsKey = configName.toLowerCase();
355-
if (clearCookies) {
356-
/// clean-up cached values
351+
if (clearState) {
352+
// clean-up cached values
357353
await workspaceState.update(wsKey + ":host", undefined);
358354
await workspaceState.update(wsKey + ":port", undefined);
359355
await workspaceState.update(wsKey + ":superserverPort", undefined);
@@ -422,10 +418,6 @@ export async function checkConnection(
422418
}
423419
}
424420

425-
if (clearCookies) {
426-
api.clearCookies();
427-
}
428-
429421
// Before recreating the api object (in case something has updated connection details since we last fetched them?)
430422
// if this is an external server, remove it from the inactive list because its presence there would block the reconnect that we want to be attempting
431423
if (api.externalServer) {
@@ -1521,38 +1513,44 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
15211513
supportsMultipleEditorsPerDocument: false,
15221514
}),
15231515
vscode.workspace.onDidChangeConfiguration(async ({ affectsConfiguration }) => {
1524-
if (affectsConfiguration("objectscript.conn") || affectsConfiguration("intersystems.servers")) {
1525-
if (affectsConfiguration("intersystems.servers")) {
1526-
// Gather the server names previously resolved
1527-
const resolvedServers: string[] = [];
1528-
resolvedConnSpecs.forEach((v, k) => resolvedServers.push(k));
1529-
// Clear the cache
1530-
resolvedConnSpecs.clear();
1531-
// Resolve them again, sequentially in case user needs to be prompted for credentials
1532-
for await (const serverName of resolvedServers) {
1533-
await resolveConnectionSpec(serverName);
1534-
}
1535-
}
1536-
// Check connections sequentially for each workspace folder
1537-
let refreshFilesExplorer = false;
1538-
for await (const folder of vscode.workspace.workspaceFolders ?? []) {
1539-
if (schemas.includes(folder.uri.scheme)) {
1540-
refreshFilesExplorer = true;
1541-
}
1542-
try {
1543-
await checkConnection(true, folder.uri, true);
1544-
} catch (_) {
1545-
continue;
1546-
}
1516+
// Loop through all ws folder connections and see if any changed
1517+
// Find all "sessions" that are new or orphaned.
1518+
// For new ones, establish a connection?
1519+
// For orphans, logout and clear the cookies.
1520+
let refreshFilesExplorer = false;
1521+
const newConnections: Set<string> = new Set();
1522+
const affectedWsFolders: vscode.WorkspaceFolder[] = [];
1523+
for (const wsFolder of vscode.workspace.workspaceFolders ?? []) {
1524+
const api = new AtelierAPI(wsFolder.uri);
1525+
newConnections.add(api.mapKey());
1526+
const { serverName } = api.config;
1527+
if (
1528+
(notIsfs(wsFolder.uri) && affectsConfiguration("objectscript.conn", wsFolder)) ||
1529+
(serverName && affectsConfiguration(`intersystems.servers.${serverName}`, wsFolder))
1530+
) {
1531+
// Connection info changed
1532+
affectedWsFolders.push(wsFolder);
1533+
if (filesystemSchemas.includes(wsFolder.uri.scheme)) refreshFilesExplorer = true;
15471534
}
1548-
explorerProvider.refresh();
1549-
projectsExplorerProvider.refresh();
1550-
if (refreshFilesExplorer) {
1551-
// This unavoidably switches to the File Explorer view, so only do it if isfs folders were found
1552-
vscode.commands.executeCommand("workbench.files.action.refreshFilesExplorer");
1535+
}
1536+
// Log out of any sessions that are orphaned
1537+
logoutOfSessions(Array.from(cookiesMap.keys()).filter((k) => !newConnections.has(k)));
1538+
// Update the connection info for affected workspace folders
1539+
// This should create new CSP sessions if needed
1540+
for (const wsFolder of affectedWsFolders) {
1541+
try {
1542+
await checkConnection(true, wsFolder.uri, true);
1543+
} catch {
1544+
// Errors are handled by checkConnection()
15531545
}
1554-
updateWebAndAbstractDocsCaches(vscode.workspace.workspaceFolders);
15551546
}
1547+
explorerProvider.refresh();
1548+
projectsExplorerProvider.refresh();
1549+
if (refreshFilesExplorer) {
1550+
// This unavoidably switches to the File Explorer view, so only do it if isfs folders were found
1551+
vscode.commands.executeCommand("workbench.files.action.refreshFilesExplorer");
1552+
}
1553+
updateWebAndAbstractDocsCaches(affectedWsFolders);
15561554
if (affectsConfiguration("objectscript.commentToken")) {
15571555
// Update the language configuration for "objectscript" and "objectscript-macros"
15581556
macLangConf?.dispose();
@@ -2037,24 +2035,5 @@ export async function deactivate(): Promise<void> {
20372035
intLangConf?.dispose();
20382036
disposeDocumentIndex();
20392037
// Log out of all CSP sessions
2040-
const loggedOut: Set<string> = new Set();
2041-
const promises: Promise<any>[] = [];
2042-
for (const f of vscode.workspace.workspaceFolders ?? []) {
2043-
const api = new AtelierAPI(f.uri);
2044-
if (!api.active || !api.cookies.length) continue;
2045-
const sessionCookie = api.cookies.find((c) => c.startsWith("CSPSESSIONID-"));
2046-
if (!sessionCookie || loggedOut.has(sessionCookie)) continue;
2047-
loggedOut.add(sessionCookie);
2048-
promises.push(
2049-
api.request(
2050-
0,
2051-
"HEAD",
2052-
undefined,
2053-
undefined,
2054-
// Prefer IRISLogout for servers that support it
2055-
semver.lt(api.config.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" }
2056-
)
2057-
);
2058-
}
2059-
await Promise.allSettled(promises);
2038+
await logoutOfSessions();
20602039
}

0 commit comments

Comments
 (0)