Skip to content
Merged
Changes from all commits
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
81 changes: 43 additions & 38 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export let client: {
const cookiesCache: Map<string, string[]> = new Map();

export function updateCookies(newCookies: string[], server: ServerSpec): string[] {
const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`;
const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`.toLowerCase();
const cookies = cookiesCache.get(key) ?? [];
newCookies.forEach((cookie) => {
const [cookieName] = cookie.split("=");
Expand All @@ -66,14 +66,14 @@ export function updateCookies(newCookies: string[], server: ServerSpec): string[
}

export function getCookies(server: ServerSpec): string[] {
return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`) ?? [];
return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`.toLowerCase()) ?? [];
}

let objectScriptApi: serverManager.VSCodeObjectScriptAPI;
let serverManagerApi: serverManager.ServerManagerAPI;

/** Resolved connection information for each workspace folder */
const wsFolderServerSpecs: Map<string, ServerSpec> = new Map();
/** Resolved connection information */
const resolvedServerSpecs: Map<string, ServerSpec> = new Map();

export async function activate(context: ExtensionContext) {
// Get the main extension exported API
Expand Down Expand Up @@ -147,16 +147,16 @@ export async function activate(context: ExtensionContext) {

// Send custom notifications when the connection or password changes
objectScriptApi.onDidChangeConnection()(() => {
wsFolderServerSpecs.clear();
resolvedServerSpecs.clear();
client.sendNotification("intersystems/server/connectionChange");
});
const serverManagerExt = extensions.getExtension("intersystems-community.servermanager");
if (serverManagerExt !== undefined) {
// The server manager extension is installed
serverManagerApi = serverManagerExt.isActive ? serverManagerExt.exports : await serverManagerExt.activate();
serverManagerApi.onDidChangePassword()((serverName: string) => {
for (const [k, v] of wsFolderServerSpecs.entries()) {
if (v.serverName == serverName) wsFolderServerSpecs.delete(k);
for (const [k, v] of resolvedServerSpecs.entries()) {
if (v.serverName == serverName) resolvedServerSpecs.delete(k);
}
client.sendNotification("intersystems/server/passwordChange", serverName);
});
Expand All @@ -166,12 +166,25 @@ export async function activate(context: ExtensionContext) {
// for a missing password via the Server Manager's authentication provider.
async function resolveServerSpec(uri: Uri): Promise<ServerSpec | undefined> {
try {
const wsFolderUriString = workspace.getWorkspaceFolder(uri)?.uri.toString();
const serverSpec = objectScriptApi.serverForUri(uri);
if (serverSpec === undefined) {
if (!serverSpec?.active) {
// An inactive connection is indistinguishable from no connection at all
return;
}
const auth = serverSpec.auth ?? new BasicAuthorization(serverSpec.username, serverSpec.password);
if ([undefined, ""].includes(auth?.username)) {
const partialKey = `${serverSpec.host}:${serverSpec.port}${serverSpec.pathPrefix}`.toLowerCase();
for (const key of resolvedServerSpecs.keys()) {
// The username isn't known yet, so see if we have a connection to this server that is already known
if (key.toLowerCase().slice(key.indexOf("@") + 1) == partialKey) {
return resolvedServerSpecs.get(key);
}
}
} else {
// Return resolved spec if we have one that matches exactly
const key = `${auth.username}@${serverSpec.host}:${serverSpec.port}${serverSpec.pathPrefix}`.toLowerCase();
if (resolvedServerSpecs.has(key)) return resolvedServerSpecs.get(key);
}
if (
// Server was resolved
serverSpec.host !== "" &&
Expand Down Expand Up @@ -228,27 +241,29 @@ export async function activate(context: ExtensionContext) {
username: auth.username,
credentials: auth.credentials,
};
if (wsFolderUriString && !wsFolderServerSpecs.has(wsFolderUriString)) {
wsFolderServerSpecs.set(wsFolderUriString, server);
}
const serverKey = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`.toLowerCase();
if (!resolvedServerSpecs.has(serverKey)) resolvedServerSpecs.set(serverKey, server);
return server;
} catch {
// Treat any thrown error as "no server connection"
}
}

// Ensure that every server has at most one session.
// Resolve the server connection for every workspace folder
for (const f of workspace.workspaceFolders ?? []) {
try {
const serverSpec = await resolveServerSpec(f.uri);
if (serverSpec?.active) {
await makeRESTRequest("HEAD", 0, "", serverSpec);
}
await resolveServerSpec(f.uri);
} catch {
// Ignore any failure; the session will be created on demand instead
// Ignore any failure; the server will be resolved on demand instead
}
}

// Create a CSP session for all resolved server connections
// Ignore any failures; the sessions will be created on demand instead
const headPromises: Promise<any>[] = [];
resolvedServerSpecs.forEach((server) => headPromises.push(makeRESTRequest("HEAD", 0, "", server)));
await Promise.allSettled(headPromises);

const textDecoder = new TextDecoder();
context.subscriptions.push(
// Register custom request handlers
Expand Down Expand Up @@ -431,27 +446,17 @@ export async function activate(context: ExtensionContext) {

export async function deactivate(): Promise<void> {
// Stop the server and log out of all CSP sessions
const loggedOut: Set<string> = new Set();
const promises: Promise<any>[] = client ? [client.stop()] : [];
for (const f of workspace.workspaceFolders ?? []) {
const serverSpec = wsFolderServerSpecs.get(f.uri.toString());
if (!serverSpec?.active) continue;
const sessionCookie = getCookies(serverSpec).find((c) => c.startsWith("CSPSESSIONID-"));
if (!sessionCookie || loggedOut.has(sessionCookie)) continue;
loggedOut.add(sessionCookie);
promises.push(
makeRESTRequest(
"HEAD",
0,
"",
serverSpec,
undefined,
undefined,
// Prefer IRISLogout for servers that support it
lt(serverSpec.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" },
),
);
}
resolvedServerSpecs.forEach((server) => promises.push(makeRESTRequest(
"HEAD",
0,
"",
server,
undefined,
undefined,
// Prefer IRISLogout for servers that support it
lt(server.serverVersion, "2018.2.0") ? { CacheLogout: "end" } : { IRISLogout: "end" },
)));
await Promise.allSettled(promises);
}

Expand Down