Skip to content
Merged
Changes from 3 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
101 changes: 72 additions & 29 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,54 @@ let objectScriptApi: serverManager.VSCodeObjectScriptAPI;
let serverManagerApi: serverManager.ServerManagerAPI;

/** Resolved connection information */
const resolvedServerSpecs: Map<string, ServerSpec> = new Map();
const resolvedServerSpecs = new (class {
Comment thread
isc-bsaviano marked this conversation as resolved.
Resolved
private readonly map = new Map<string, Omit<ServerSpec, "namespace">>();

private keyOf(server: Pick<ServerSpec, "host" | "port" | "pathPrefix" | "username">): string {
return `${server.username}@${server.host}:${server.port}${server.pathPrefix}`.toLowerCase();
}

private matches(
value: Omit<ServerSpec, "namespace">,
query: Partial<Pick<ServerSpec, "host" | "port" | "pathPrefix" | "username" | "serverName">>,
): boolean {
const lower = (s?: string) => s?.toLowerCase();
return (
(query.host === undefined || lower(value.host) === lower(query.host)) &&
(query.port === undefined || value.port === query.port) &&
(query.pathPrefix === undefined || lower(value.pathPrefix) === lower(query.pathPrefix)) &&
(query.username === undefined || lower(value.username) === lower(query.username)) &&
(query.serverName === undefined || value.serverName === query.serverName)
);
}

/** Known connections matching whichever of these fields are given. */
list(
query: Partial<Pick<ServerSpec, "host" | "port" | "pathPrefix" | "username" | "serverName">>,
): Omit<ServerSpec, "namespace">[] {
return [...this.map.values()].filter((value) => this.matches(value, query));
}

add(server: Omit<ServerSpec, "namespace">): void {
const key = this.keyOf(server);
if (!this.map.has(key)) this.map.set(key, server);
}

/** Deletes every known connection matching whichever of these fields are given. */
delete(server: Partial<Pick<ServerSpec, "host" | "port" | "pathPrefix" | "username" | "serverName">>): void {
for (const [key, info] of this.map) {
if (this.matches(info, server)) this.map.delete(key);
}
}

clear(): void {
this.map.clear();
}

values(): IterableIterator<Omit<ServerSpec, "namespace">> {
return this.map.values();
}
})();

export async function activate(context: ExtensionContext) {
// Get the main extension exported API
Expand Down Expand Up @@ -155,9 +202,7 @@ export async function activate(context: ExtensionContext) {
// The server manager extension is installed
serverManagerApi = serverManagerExt.isActive ? serverManagerExt.exports : await serverManagerExt.activate();
serverManagerApi.onDidChangePassword()((serverName: string) => {
for (const [k, v] of resolvedServerSpecs.entries()) {
if (v.serverName == serverName) resolvedServerSpecs.delete(k);
}
resolvedServerSpecs.delete({ serverName });
client.sendNotification("intersystems/server/passwordChange", serverName);
});
}
Expand All @@ -172,18 +217,9 @@ export async function activate(context: ExtensionContext) {
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);
for (const cached of resolvedServerSpecs.list({ ...serverSpec, username: auth?.username })) {
Comment thread
isc-klu marked this conversation as resolved.
Resolved
// Auth is namespace-independent, but the caller needs the namespace it actually asked for
return { ...cached, namespace: serverSpec.namespace };
}
if (
// Server was resolved
Expand Down Expand Up @@ -241,8 +277,7 @@ export async function activate(context: ExtensionContext) {
username: auth.username,
credentials: auth.credentials,
};
const serverKey = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`.toLowerCase();
if (!resolvedServerSpecs.has(serverKey)) resolvedServerSpecs.set(serverKey, server);
resolvedServerSpecs.add(server);
return server;
} catch {
// Treat any thrown error as "no server connection"
Expand All @@ -261,7 +296,10 @@ export async function activate(context: ExtensionContext) {
// 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)));
for (const server of resolvedServerSpecs.values()) {
// namespace is irrelevant here because api=0
headPromises.push(makeRESTRequest("HEAD", 0, "", { ...server, namespace: "" }));
}
await Promise.allSettled(headPromises);

const textDecoder = new TextDecoder();
Expand Down Expand Up @@ -447,16 +485,21 @@ export async function activate(context: ExtensionContext) {
export async function deactivate(): Promise<void> {
// Stop the server and log out of all CSP sessions
const promises: Promise<any>[] = client ? [client.stop()] : [];
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" },
)));
for (const server of resolvedServerSpecs.values()) {
promises.push(
makeRESTRequest(
"HEAD",
0,
"",
// namespace is irrelevant because api=0
{ ...server, namespace: "" },
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