Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
461a3de
fix #1750
isc-klu Apr 13, 2026
ff4d5a9
Merge branch 'intersystems-community:master' into master
isc-klu May 13, 2026
bbb6e86
Merge branch 'intersystems-community:master' into master
isc-klu May 27, 2026
4677311
Merge branch 'intersystems-community:master' into master
isc-klu Jun 18, 2026
aeb08c7
Merge branch 'intersystems-community:master' into master
isc-klu Aug 7, 2026
82bee47
Merge branch 'intersystems-community:master' into master
isc-klu Aug 12, 2026
dc6884b
Merge branch 'intersystems-community:master' into master
isc-klu Aug 18, 2026
531cddb
Merge branch 'intersystems-community:master' into master
isc-klu Aug 20, 2026
288befc
Merge branch 'intersystems-community:master' into master
isc-klu Aug 21, 2026
f3f195d
Merge branch 'intersystems-community:master' into master
isc-klu Aug 25, 2026
f3eeb9d
Merge branch 'intersystems-community:master' into master
isc-klu Aug 25, 2026
3a5931f
Merge branch 'intersystems-community:master' into master
isc-klu Aug 25, 2026
a5cd43c
Merge branch 'intersystems-community:master' into master
isc-klu Aug 25, 2026
8dd8058
Merge remote-tracking branch 'upstream/master'
isc-klu Aug 26, 2026
c2f1ee8
Merge branch 'intersystems-community:master' into master
isc-klu Aug 27, 2026
2961ce6
Merge branch 'intersystems-community:master' into master
isc-klu Sep 3, 2026
602c2d1
Merge branch 'intersystems-community:master' into master
isc-klu Sep 8, 2026
7db3d01
draft
isc-klu Sep 8, 2026
0b3408e
Rename checkConnection to ensureConnection, tighten request() types, …
isc-klu Sep 8, 2026
27b4e40
Fix ensureConnection's own probe being denied the 401 self-heal retry
isc-klu Sep 8, 2026
bf55853
Remove redundant connectionsBeingChecked.delete calls, keep one clean…
isc-klu Sep 8, 2026
62c10c3
rn
isc-klu Sep 9, 2026
4d3f5a9
update
isc-klu Sep 9, 2026
1592922
Fix stale checkConnection reference in comment
isc-klu Sep 9, 2026
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
29 changes: 15 additions & 14 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import BasicAuthorization, {
extensionContext,
workspaceState,
panel,
checkConnection,
ensureConnection,
schemas,
checkingConnection,
inactiveServerIds,
} from "../extension";
import { currentWorkspaceFolder, outputChannel, outputConsole } from "../utils";
Expand Down Expand Up @@ -121,10 +120,8 @@ export class AtelierAPI {
return filename;
}

public constructor(wsOrFile?: string | vscode.Uri, retryAfter401 = true) {
if (retryAfter401) {
this.wsOrFile = wsOrFile;
}
public constructor(wsOrFile?: string | vscode.Uri) {
this.wsOrFile = wsOrFile;
let workspaceFolderName = "";
let namespace = "";
if (wsOrFile) {
Expand Down Expand Up @@ -328,12 +325,19 @@ export class AtelierAPI {

private async request(
minVersion: number,
method: string,
method: "GET" | "HEAD" | "PUT" | "POST" | "DELETE",
path?: string,
body?: any,
params?: any,
headers?: any,
options?: any
options?: {
/** Abort the request if it hasn't completed within this many milliseconds. */
timeout?: number;
/** Suppress writing this request/response to the ObjectScript output channel, even when `objectscript.outputRESTTraffic` is on. */
noOutput?: boolean;
/** On a 401 response, suppress the automatic single retry with fresh credentials. */
_retriedAfter401?: boolean;
}
): Promise<any> {
const { active, apiVersion, host, port, https } = this.config;
if (!active || !port || !host) {
Expand Down Expand Up @@ -365,7 +369,6 @@ export class AtelierAPI {
});
return result.length ? "?" + result.join("&") : "";
};
method = method.toUpperCase();
if (body && !headers["Content-Type"]) {
headers["Content-Type"] = "application/json";
}
Expand Down Expand Up @@ -465,15 +468,15 @@ export class AtelierAPI {
if (response.status === 401) {
authRequestMap.delete(mapKey);
cookiesMap.delete(mapKey);
if (this.wsOrFile && !checkingConnection) {
if (this.wsOrFile) {
if (!options?._retriedAfter401) {
return this.request(minVersion, method, originalPath, body, params, headers, {
...options,
_retriedAfter401: true,
});
}
setTimeout(() => {
checkConnection(
ensureConnection(
this.config.auth.resolved(),
typeof this.wsOrFile === "object" ? this.wsOrFile : undefined,
true
Expand Down Expand Up @@ -596,9 +599,7 @@ export class AtelierAPI {
panel.tooltip = "Disconnected";
workspaceState.update(this.configName.toLowerCase() + ":host", undefined);
workspaceState.update(this.configName.toLowerCase() + ":port", undefined);
if (!checkingConnection) {
setTimeout(() => checkConnection(false, undefined, true), 30000);
}
setTimeout(() => ensureConnection(false, undefined, true), 30000);
}
throw error;
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/serverActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from "vscode";
import {
config,
workspaceState,
checkConnection,
ensureConnection,
explorerProvider,
filesystemSchemas,
FILESYSTEM_SCHEMA,
Expand Down Expand Up @@ -72,7 +72,7 @@ export async function serverActions(): Promise<void> {
return connConfig.update("conn", { ...targetConfig, active: !active }, target);
}
case "refreshConnection": {
await checkConnection(true, undefined, true);
await ensureConnection(true, undefined, true);
break;
}
case "switchNamespace": {
Expand Down
51 changes: 27 additions & 24 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,6 @@ export function config(setting?: string, workspaceFolderName?: string): any {

let reporter: TelemetryReporter | undefined;

export let checkingConnection = false;

export let serverManagerApi: serverManager.ServerManagerAPI;

type ConnSpec = serverManager.IServerSpec & {
Expand Down Expand Up @@ -375,18 +373,29 @@ export function getResolvedConnectionSpec(
/** The `api.serverId`s of all servers that are known to be inactive */
export const inactiveServerIds: Set<string> = new Set();

export async function checkConnection(
/** `configName`s for which an `ensureConnection` call is currently in progress */
const ensuringConnection: Set<string> = new Set();

/**
* Verify `uri`'s connection works, repairing it if not (may show a modal credential prompt).
* Reflects the outcome in the status bar, `objectscript.conn.active`, and `workspaceState`.
* No-ops if a call is already in progress.
* @param clearState Discard cached connection details first, forcing fresh resolution.
* @param triggerRefreshes Refresh the explorer/projects views once settled.
* @param withTimeout Time out the check so it can't hang extension activation.
*/
export async function ensureConnection(
clearState = false,
uri?: vscode.Uri,
triggerRefreshes?: boolean,
inActivate = false
withTimeout = false
): Promise<void> {
// Do nothing if already checking the connection
if (checkingConnection) {
const { apiTarget, configName } = connectionTarget(uri);
// Do nothing if already checking this connection
if (ensuringConnection.has(configName)) {
return;
}

const { apiTarget, configName } = connectionTarget(uri);
const wsKey = configName.toLowerCase();
if (clearState) {
// clean-up cached values
Expand All @@ -399,7 +408,7 @@ export async function checkConnection(
await workspaceState.update(wsKey + ":docker", undefined);
_onDidChangeConnection.fire();
}
let api = new AtelierAPI(apiTarget, false);
let api = new AtelierAPI(apiTarget);
const { active, host = "", port = 0, superserverPort = 0, ns = "", auth } = api.config;
vscode.commands.executeCommand("setContext", "vscode-objectscript.connectActive", active);
if (!panel.text) {
Expand Down Expand Up @@ -463,7 +472,7 @@ export async function checkConnection(
if (api.externalServer) {
inactiveServerIds.delete(api.serverId);
}
api = new AtelierAPI(apiTarget, false);
api = new AtelierAPI(apiTarget);

if (!api.config.host || !api.config.port || !api.config.ns) {
const message = "'host', 'port' and 'ns' must be specified.";
Expand All @@ -474,7 +483,7 @@ export async function checkConnection(
if (!api.externalServer) await setConnectionState(configName, false);
return;
}
checkingConnection = true;
ensuringConnection.add(configName);

const username = auth.username || "UnknownUser";
const identity = username.startsWith("*") ? `using ${username.slice(1, -1)}` : `as user \`${username}\``;
Expand All @@ -496,7 +505,7 @@ export async function checkConnection(
// Do the check
// Only time out requests when called from activate()
// Timeout is needed in that case to prevent extension activation from hanging
const serverInfoTimeout = inActivate ? 5000 : undefined;
const serverInfoTimeout = withTimeout ? 5000 : undefined;
return api
.serverInfo(true, serverInfoTimeout)
.then(gotServerInfo)
Expand Down Expand Up @@ -524,7 +533,7 @@ export async function checkConnection(
if (newSpec) {
// We were able to resolve credentials, so try again
await workspaceState.update(wsKey + ":password", newSpec.auth?.accessToken);
api = new AtelierAPI(apiTarget, false);
api = new AtelierAPI(apiTarget);
await api
.serverInfo(true, serverInfoTimeout)
.then(async (info) => {
Expand All @@ -537,9 +546,6 @@ export async function checkConnection(
if (error?.statusCode != 401) errorMessage = undefined;
await workspaceState.update(wsKey + ":password", undefined);
success = false;
})
.finally(() => {
checkingConnection = false;
});
}
} else {
Expand Down Expand Up @@ -572,9 +578,6 @@ export async function checkConnection(
await workspaceState.update(wsKey + ":password", undefined);
return false;
})
.finally(() => {
checkingConnection = false;
})
);
} else {
inactiveServerIds.add(api.serverId);
Expand All @@ -600,7 +603,7 @@ export async function checkConnection(
if (!api.externalServer) await setConnectionState(configName, false);
})
.finally(() => {
checkingConnection = false;
ensuringConnection.delete(configName);
if (triggerRefreshes) {
setTimeout(() => {
explorerProvider.refresh();
Expand Down Expand Up @@ -948,7 +951,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<server
// Necessary because we are in our activate method, so its call to the Server Manager API cannot call back to our API to do that.
await resolveConnectionSpec(serverName, uri);
} finally {
await checkConnection(true, uri, true, true);
await ensureConnection(true, uri, true, true);
}
} catch (_) {
// Ignore any failure
Expand Down Expand Up @@ -1148,13 +1151,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<server
await workspaceState.update("workspaceFolder", workspaceFolder);
// Only need to check when editor is undefined because
// we will always check when editor is defined below
if (!editor) await checkConnection(false);
if (!editor) await ensureConnection(false);
}
}
if (editor) {
const conf = vscode.workspace.getConfiguration("objectscript");
const uriString = editor.document.uri.toString();
await checkConnection(false, editor.document.uri);
await ensureConnection(false, editor.document.uri);
if (
conf.get("openClassContracted") &&
editor.document.languageId == clsLangId &&
Expand Down Expand Up @@ -1596,9 +1599,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<server
// This should create new CSP sessions if needed
for (const wsFolder of affectedWsFolders) {
try {
await checkConnection(true, wsFolder.uri, true);
await ensureConnection(true, wsFolder.uri, true);
} catch {
// Errors are handled by checkConnection()
// Errors are handled by ensureConnection()
}
}
explorerProvider.refresh();
Expand Down