From 1f8592173c4a3c1a5627f915aaa42bac1ba500ba Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Tue, 7 Jul 2026 17:07:45 -0400 Subject: [PATCH 1/7] FIx #406 --- client/src/extension.ts | 50 ++++++++++++++++++++++++++--------- client/src/makeRESTRequest.ts | 22 +++++++-------- 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index eb7a363..3dd07b0 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -39,11 +39,14 @@ export let client: LanguageClient; /** * Cache for cookies from REST requests to InterSystems servers. */ -const cookiesCache: Map = new Map(); +const cookiesCache: Map, + resolve?: (value: string[]) => void, +}> = new Map(); -export function updateCookies(newCookies: string[], server: ServerSpec): string[] { +export async function updateCookies(newCookies: string[], server: ServerSpec): Promise { const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; - const cookies = cookiesCache.get(key) ?? []; + const cookies = await (cookiesCache.get(key)?.promise ?? emptyCookies()); newCookies.forEach((cookie) => { const [cookieName] = cookie.split("="); const index = cookies.findIndex((el) => el.startsWith(cookieName)); @@ -53,12 +56,33 @@ export function updateCookies(newCookies: string[], server: ServerSpec): string[ cookies.push(cookie); } }); - cookiesCache.set(key, cookies); + const cache = cookiesCache.get(key); + if (cache && cache.resolve) { + cache.resolve(cookies) + delete cache.resolve + } else { + cookiesCache.set(key, { promise: new Promise((resolve) => resolve(cookies)) }); + } return cookies; } -export function getCookies(server: ServerSpec): string[] { - return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`) ?? []; +function emptyCookies(): Promise { + return new Promise((resolve) => resolve([])) +} + +// Each getCookies call MUST be accompanied by some calls to updateCookies +export async function getCookies(server: ServerSpec): Promise { + const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; + if (cookiesCache.has(key)) { + // A typical caller waits for the cookie promise to resolve. + return cookiesCache.get(key).promise + } else { + // The first caller for this key gets the empty cookies immediately. + let resolve: (value: string[]) => void; + const promise: Promise = new Promise((r) => resolve = r); + cookiesCache.set(key, { promise, resolve }) + return emptyCookies(); + } } let objectScriptApi: any; @@ -282,12 +306,12 @@ export async function activate(context: ExtensionContext) { : uri.path.split("/").slice(1).join("."); const docParams = params.server.apiVersion >= 4 && - workspace - .getConfiguration( - "objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), - ) - .get("multilineMethodArgs") + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") ? { format: "udl-multiline" } : undefined; const resp = await makeRESTRequest( @@ -419,7 +443,7 @@ export async function deactivate(): Promise { 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-")); + const sessionCookie = (await getCookies(serverSpec)).find((c) => c.startsWith("CSPSESSIONID-")); if (!sessionCookie || loggedOut.has(sessionCookie)) continue; loggedOut.add(sessionCookie); promises.push( diff --git a/client/src/makeRESTRequest.ts b/client/src/makeRESTRequest.ts index 3a4f7d5..e84a9ff 100644 --- a/client/src/makeRESTRequest.ts +++ b/client/src/makeRESTRequest.ts @@ -52,8 +52,8 @@ export async function makeRESTRequest( // The server doesn't support the Atelier API version required to make this request client.warn( "Cannot make required REST request to server " + - `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + - `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.`, + `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + + `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.`, ); return undefined; } @@ -74,7 +74,7 @@ export async function makeRESTRequest( const httpsAgent = new https.Agent({ rejectUnauthorized: workspace.getConfiguration("http").get("proxyStrictSSL") }); // Get the cookies - let cookies: string[] = getCookies(server); + let cookies: string[] = await getCookies(server); // Make the request try { @@ -96,7 +96,7 @@ export async function makeRESTRequest( return status < 500; }, }); - cookies = updateCookies(respdata.headers["set-cookie"] || [], server); + cookies = await updateCookies(respdata.headers["set-cookie"] || [], server); if (respdata.status === 202) { // The schema is being recalculated so we need to make another call to get it respdata = await axios.request({ @@ -108,7 +108,7 @@ export async function makeRESTRequest( Cookie: cookies.join(" "), }, }); - updateCookies(respdata.headers["set-cookie"] || [], server); + await updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; } else if (respdata.status === 304) { // The schema hasn't changed @@ -129,7 +129,7 @@ export async function makeRESTRequest( withCredentials: true, httpsAgent, }); - cookies = updateCookies(respdata.headers["set-cookie"] || [], server); + cookies = await updateCookies(respdata.headers["set-cookie"] || [], server); if (respdata.status === 202) { // The schema is being recalculated so we need to make another call to get it respdata = await axios.request({ @@ -141,7 +141,7 @@ export async function makeRESTRequest( Cookie: cookies.join(" "), }, }); - updateCookies(respdata.headers["set-cookie"] || [], server); + await updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; } else if (respdata.status === 304) { // The schema hasn't changed @@ -189,7 +189,7 @@ export async function makeRESTRequest( httpsAgent, }); } - updateCookies(respdata.headers["set-cookie"] || [], server); + await updateCookies(respdata.headers["set-cookie"] || [], server); } else { respdata = await axios.request({ method: method, @@ -220,14 +220,14 @@ export async function makeRESTRequest( params: params, }); } - updateCookies(respdata.headers["set-cookie"] || [], server); + await updateCookies(respdata.headers["set-cookie"] || [], server); } return respdata; } } catch (error) { + await updateCookies(cookies, server) client.warn( - `Error making REST request ${method} ${path}: ${ - typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) + `Error making REST request ${method} ${path}: ${typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) }`, ); return undefined; From 8bb688919b0d1a857b074bd0ee023a8619767c0d Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Jul 2026 10:03:25 -0400 Subject: [PATCH 2/7] Create server sessions at activation instead of queueing requests Per review feedback on #408: rather than serializing REST requests via a promise-based cookie cache, establish one session per workspace folder during activation, before it completes. IntelliSense requests aren't sent until after activation finishes, so they all reuse these sessions instead of each creating a new one when many editor tabs are open at startup (#406). Reverts the queueing changes to makeRESTRequest.ts and extracts the spec resolution logic into a reusable helper. Co-Authored-By: Claude Opus 4.8 --- client/src/extension.ts | 182 ++++++++++++++++------------------ client/src/makeRESTRequest.ts | 22 ++-- 2 files changed, 99 insertions(+), 105 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 3dd07b0..d23978c 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -39,14 +39,11 @@ export let client: LanguageClient; /** * Cache for cookies from REST requests to InterSystems servers. */ -const cookiesCache: Map, - resolve?: (value: string[]) => void, -}> = new Map(); +const cookiesCache: Map = new Map(); -export async function updateCookies(newCookies: string[], server: ServerSpec): Promise { +export function updateCookies(newCookies: string[], server: ServerSpec): string[] { const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; - const cookies = await (cookiesCache.get(key)?.promise ?? emptyCookies()); + const cookies = cookiesCache.get(key) ?? []; newCookies.forEach((cookie) => { const [cookieName] = cookie.split("="); const index = cookies.findIndex((el) => el.startsWith(cookieName)); @@ -56,33 +53,12 @@ export async function updateCookies(newCookies: string[], server: ServerSpec): P cookies.push(cookie); } }); - const cache = cookiesCache.get(key); - if (cache && cache.resolve) { - cache.resolve(cookies) - delete cache.resolve - } else { - cookiesCache.set(key, { promise: new Promise((resolve) => resolve(cookies)) }); - } + cookiesCache.set(key, cookies); return cookies; } -function emptyCookies(): Promise { - return new Promise((resolve) => resolve([])) -} - -// Each getCookies call MUST be accompanied by some calls to updateCookies -export async function getCookies(server: ServerSpec): Promise { - const key = `${server.username}@${server.host}:${server.port}${server.pathPrefix}`; - if (cookiesCache.has(key)) { - // A typical caller waits for the cookie promise to resolve. - return cookiesCache.get(key).promise - } else { - // The first caller for this key gets the empty cookies immediately. - let resolve: (value: string[]) => void; - const promise: Promise = new Promise((r) => resolve = r); - cookiesCache.set(key, { promise, resolve }) - return emptyCookies(); - } +export function getCookies(server: ServerSpec): string[] { + return cookiesCache.get(`${server.username}@${server.host}:${server.port}${server.pathPrefix}`) ?? []; } let objectScriptApi: any; @@ -188,67 +164,70 @@ export async function activate(context: ExtensionContext) { }); } - const textDecoder = new TextDecoder(); - context.subscriptions.push( - // Register custom request handlers - client.onRequest("intersystems/server/resolveFromUri", async (uri: string) => { - const uriObj = Uri.parse(uri); - const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); - const serverSpec = objectScriptApi.serverForUri(uriObj); - if ( - // Server was resolved - serverSpec.host !== "" && - // Connection isn't unauthenticated - serverSpec.username != undefined && - serverSpec.username != "" && - serverSpec.username.toLowerCase() != "unknownuser" && - // A password is missing - typeof serverSpec.password === "undefined" && - // A supported version of the Server Manager is installed - serverManagerExt != undefined && - gt(serverManagerExt.packageJSON.version, "3.0.0") - ) { - // The main extension didn't provide a password, so we must - // get it from the server manager's authentication provider. - const scopes = [serverSpec.serverName, serverSpec.username]; - try { - const account = serverManagerApi?.getAccount - ? serverManagerApi.getAccount({ name: serverSpec.serverName, ...serverSpec }) - : undefined; - let session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { - silent: true, + // Resolve the ServerSpec for a document or workspace folder URI, prompting + // for a missing password via the Server Manager's authentication provider. + const resolveServerSpec = async (uriObj: Uri): Promise => { + const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); + const serverSpec = objectScriptApi.serverForUri(uriObj); + if ( + // Server was resolved + serverSpec.host !== "" && + // Connection isn't unauthenticated + serverSpec.username != undefined && + serverSpec.username != "" && + serverSpec.username.toLowerCase() != "unknownuser" && + // A password is missing + typeof serverSpec.password === "undefined" && + // A supported version of the Server Manager is installed + serverManagerExt != undefined && + gt(serverManagerExt.packageJSON.version, "3.0.0") + ) { + // The main extension didn't provide a password, so we must + // get it from the server manager's authentication provider. + const scopes = [serverSpec.serverName, serverSpec.username]; + try { + const account = serverManagerApi?.getAccount + ? serverManagerApi.getAccount({ name: serverSpec.serverName, ...serverSpec }) + : undefined; + let session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { + silent: true, + account, + }); + if (!session) { + session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { + createIfNone: true, account, }); - if (!session) { - session = await authentication.getSession(serverManager.AUTHENTICATION_PROVIDER, scopes, { - createIfNone: true, - account, - }); - } - if (session) { - serverSpec.username = session.scopes[1]; - serverSpec.password = session.accessToken; - } - } catch (error) { - // The user did not consent to sharing authentication information - if (error instanceof Error) { - client.warn(`${serverManager.AUTHENTICATION_PROVIDER}: ${error.message}`); - } + } + if (session) { + serverSpec.username = session.scopes[1]; + serverSpec.password = session.accessToken; + } + } catch (error) { + // The user did not consent to sharing authentication information + if (error instanceof Error) { + client.warn(`${serverManager.AUTHENTICATION_PROVIDER}: ${error.message}`); } } - if ( - typeof serverSpec.username == "string" && - serverSpec.username.toLowerCase() == "unknownuser" && - typeof serverSpec.password == "undefined" - ) { - // UnknownUser without a password means "unauthenticated" - serverSpec.username = undefined; - } - if (wsFolderUriString && !wsFolderServerSpecs.has(wsFolderUriString)) { - wsFolderServerSpecs.set(wsFolderUriString, serverSpec); - } - return serverSpec; - }), + } + if ( + typeof serverSpec.username == "string" && + serverSpec.username.toLowerCase() == "unknownuser" && + typeof serverSpec.password == "undefined" + ) { + // UnknownUser without a password means "unauthenticated" + serverSpec.username = undefined; + } + if (wsFolderUriString && !wsFolderServerSpecs.has(wsFolderUriString)) { + wsFolderServerSpecs.set(wsFolderUriString, serverSpec); + } + return serverSpec; + }; + + const textDecoder = new TextDecoder(); + context.subscriptions.push( + // Register custom request handlers + client.onRequest("intersystems/server/resolveFromUri", (uri: string) => resolveServerSpec(Uri.parse(uri))), client.onRequest("intersystems/uri/localToVirtual", (uri: string): string => { const newuri: Uri = objectScriptApi.serverDocumentUriForUri(Uri.parse(uri)); return newuri.toString(); @@ -306,12 +285,12 @@ export async function activate(context: ExtensionContext) { : uri.path.split("/").slice(1).join("."); const docParams = params.server.apiVersion >= 4 && - workspace - .getConfiguration( - "objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), - ) - .get("multilineMethodArgs") + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") ? { format: "udl-multiline" } : undefined; const resp = await makeRESTRequest( @@ -353,6 +332,21 @@ export async function activate(context: ExtensionContext) { // Start the client. This will also launch the server client.start(); + // Establish one session with each server used by the workspace before + // activation completes. Since IntelliSense requests aren't sent until after + // activation finishes, they will all reuse these sessions rather than each + // creating a new one when many editor tabs are open at startup (#406). + for (const f of workspace.workspaceFolders ?? []) { + try { + const serverSpec = await resolveServerSpec(f.uri); + if (!serverSpec.active || !serverSpec.apiVersion) continue; + // A GET request authenticates and caches the session cookie + await makeRESTRequest("GET", 1, "", serverSpec); + } catch { + // Ignore any failure; the session will be created on demand instead + } + } + const workbenchConfig = workspace.getConfiguration("workbench"); if ( workspace @@ -443,7 +437,7 @@ export async function deactivate(): Promise { for (const f of workspace.workspaceFolders ?? []) { const serverSpec = wsFolderServerSpecs.get(f.uri.toString()); if (!serverSpec?.active) continue; - const sessionCookie = (await getCookies(serverSpec)).find((c) => c.startsWith("CSPSESSIONID-")); + const sessionCookie = getCookies(serverSpec).find((c) => c.startsWith("CSPSESSIONID-")); if (!sessionCookie || loggedOut.has(sessionCookie)) continue; loggedOut.add(sessionCookie); promises.push( diff --git a/client/src/makeRESTRequest.ts b/client/src/makeRESTRequest.ts index e84a9ff..3a4f7d5 100644 --- a/client/src/makeRESTRequest.ts +++ b/client/src/makeRESTRequest.ts @@ -52,8 +52,8 @@ export async function makeRESTRequest( // The server doesn't support the Atelier API version required to make this request client.warn( "Cannot make required REST request to server " + - `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + - `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.`, + `${server.serverName !== "" ? `'${server.serverName}'` : `${server.host}:${server.port}${server.pathPrefix}`} ` + + `because it does not support the '${path}' endpoint, which requires Atelier API version ${api}.`, ); return undefined; } @@ -74,7 +74,7 @@ export async function makeRESTRequest( const httpsAgent = new https.Agent({ rejectUnauthorized: workspace.getConfiguration("http").get("proxyStrictSSL") }); // Get the cookies - let cookies: string[] = await getCookies(server); + let cookies: string[] = getCookies(server); // Make the request try { @@ -96,7 +96,7 @@ export async function makeRESTRequest( return status < 500; }, }); - cookies = await updateCookies(respdata.headers["set-cookie"] || [], server); + cookies = updateCookies(respdata.headers["set-cookie"] || [], server); if (respdata.status === 202) { // The schema is being recalculated so we need to make another call to get it respdata = await axios.request({ @@ -108,7 +108,7 @@ export async function makeRESTRequest( Cookie: cookies.join(" "), }, }); - await updateCookies(respdata.headers["set-cookie"] || [], server); + updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; } else if (respdata.status === 304) { // The schema hasn't changed @@ -129,7 +129,7 @@ export async function makeRESTRequest( withCredentials: true, httpsAgent, }); - cookies = await updateCookies(respdata.headers["set-cookie"] || [], server); + cookies = updateCookies(respdata.headers["set-cookie"] || [], server); if (respdata.status === 202) { // The schema is being recalculated so we need to make another call to get it respdata = await axios.request({ @@ -141,7 +141,7 @@ export async function makeRESTRequest( Cookie: cookies.join(" "), }, }); - await updateCookies(respdata.headers["set-cookie"] || [], server); + updateCookies(respdata.headers["set-cookie"] || [], server); return respdata; } else if (respdata.status === 304) { // The schema hasn't changed @@ -189,7 +189,7 @@ export async function makeRESTRequest( httpsAgent, }); } - await updateCookies(respdata.headers["set-cookie"] || [], server); + updateCookies(respdata.headers["set-cookie"] || [], server); } else { respdata = await axios.request({ method: method, @@ -220,14 +220,14 @@ export async function makeRESTRequest( params: params, }); } - await updateCookies(respdata.headers["set-cookie"] || [], server); + updateCookies(respdata.headers["set-cookie"] || [], server); } return respdata; } } catch (error) { - await updateCookies(cookies, server) client.warn( - `Error making REST request ${method} ${path}: ${typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) + `Error making REST request ${method} ${path}: ${ + typeof error == "string" ? error : error instanceof Error ? error.toString() : JSON.stringify(error) }`, ); return undefined; From 4f852edf2a2bc9588204f1978f433e74a08a5e93 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Jul 2026 10:03:25 -0400 Subject: [PATCH 3/7] Create server sessions at activation instead of queueing requests Per Brett's review of #408: rather than serializing REST requests via a promise-based cookie cache, create a session for each workspace folder during activation, before it completes. IntelliSense requests aren't sent until after activation finishes, so they all reuse these sessions instead of each creating a new one when many editor tabs are open at startup (#406). The resolveFromUri handler body is extracted into a resolveServerSpec helper so the activation loop can reuse it; no other code is modified. Co-Authored-By: Claude Opus 4.8 --- client/src/extension.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index d23978c..5de342d 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -164,8 +164,13 @@ export async function activate(context: ExtensionContext) { }); } +<<<<<<< HEAD // Resolve the ServerSpec for a document or workspace folder URI, prompting // for a missing password via the Server Manager's authentication provider. +======= + // Resolve the ServerSpec for a document or workspace folder URI, prompting for + // a missing password via the Server Manager's authentication provider. +>>>>>>> 9f61b15 (Create server sessions at activation instead of queueing requests) const resolveServerSpec = async (uriObj: Uri): Promise => { const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); const serverSpec = objectScriptApi.serverForUri(uriObj); @@ -285,12 +290,12 @@ export async function activate(context: ExtensionContext) { : uri.path.split("/").slice(1).join("."); const docParams = params.server.apiVersion >= 4 && - workspace - .getConfiguration( - "objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), - ) - .get("multilineMethodArgs") + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") ? { format: "udl-multiline" } : undefined; const resp = await makeRESTRequest( From 411b9dcc436c6bb6096cba4ac3f4477d924e76cc Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Jul 2026 10:56:55 -0400 Subject: [PATCH 4/7] clean --- client/src/extension.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 5de342d..a83441f 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -164,13 +164,8 @@ export async function activate(context: ExtensionContext) { }); } -<<<<<<< HEAD // Resolve the ServerSpec for a document or workspace folder URI, prompting // for a missing password via the Server Manager's authentication provider. -======= - // Resolve the ServerSpec for a document or workspace folder URI, prompting for - // a missing password via the Server Manager's authentication provider. ->>>>>>> 9f61b15 (Create server sessions at activation instead of queueing requests) const resolveServerSpec = async (uriObj: Uri): Promise => { const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); const serverSpec = objectScriptApi.serverForUri(uriObj); From df756d13146ced08dfd0b9b3eb967d4adb3fa268 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Jul 2026 10:59:00 -0400 Subject: [PATCH 5/7] clean up mess introducec by claude --- client/src/extension.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index a83441f..a6a929a 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -330,17 +330,12 @@ export async function activate(context: ExtensionContext) { ); // Start the client. This will also launch the server - client.start(); + await client.start(); - // Establish one session with each server used by the workspace before - // activation completes. Since IntelliSense requests aren't sent until after - // activation finishes, they will all reuse these sessions rather than each - // creating a new one when many editor tabs are open at startup (#406). for (const f of workspace.workspaceFolders ?? []) { try { const serverSpec = await resolveServerSpec(f.uri); if (!serverSpec.active || !serverSpec.apiVersion) continue; - // A GET request authenticates and caches the session cookie await makeRESTRequest("GET", 1, "", serverSpec); } catch { // Ignore any failure; the session will be created on demand instead From 1991d49b07e90a3a2d5a9bead4bbbd8d719c1ff8 Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Fri, 24 Jul 2026 11:05:54 -0400 Subject: [PATCH 6/7] comment --- client/src/extension.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index a6a929a..33d7d8d 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -285,12 +285,12 @@ export async function activate(context: ExtensionContext) { : uri.path.split("/").slice(1).join("."); const docParams = params.server.apiVersion >= 4 && - workspace - .getConfiguration( - "objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), - ) - .get("multilineMethodArgs") + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") ? { format: "udl-multiline" } : undefined; const resp = await makeRESTRequest( @@ -332,6 +332,7 @@ export async function activate(context: ExtensionContext) { // Start the client. This will also launch the server await client.start(); + // Ensure that every server has at most one session. for (const f of workspace.workspaceFolders ?? []) { try { const serverSpec = await resolveServerSpec(f.uri); From d84351625a5709ed0ac21c07027a57f42d801fcc Mon Sep 17 00:00:00 2001 From: "Kuang-Chen (KC) Lu" Date: Wed, 29 Jul 2026 15:25:51 -0400 Subject: [PATCH 7/7] Brett's feedback --- client/src/extension.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/client/src/extension.ts b/client/src/extension.ts index 33d7d8d..a9f586e 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -166,7 +166,7 @@ export async function activate(context: ExtensionContext) { // Resolve the ServerSpec for a document or workspace folder URI, prompting // for a missing password via the Server Manager's authentication provider. - const resolveServerSpec = async (uriObj: Uri): Promise => { + async function resolveServerSpec(uriObj: Uri): Promise { const wsFolderUriString = workspace.getWorkspaceFolder(uriObj)?.uri.toString(); const serverSpec = objectScriptApi.serverForUri(uriObj); if ( @@ -224,6 +224,18 @@ export async function activate(context: ExtensionContext) { return serverSpec; }; + // Ensure that every server has at most one session. + for (const f of workspace.workspaceFolders ?? []) { + try { + const serverSpec = await resolveServerSpec(f.uri); + if (serverSpec.active) { + await makeRESTRequest("HEAD", 1, "", serverSpec); + } + } catch { + // Ignore any failure; the session will be created on demand instead + } + } + const textDecoder = new TextDecoder(); context.subscriptions.push( // Register custom request handlers @@ -285,12 +297,12 @@ export async function activate(context: ExtensionContext) { : uri.path.split("/").slice(1).join("."); const docParams = params.server.apiVersion >= 4 && - workspace - .getConfiguration( - "objectscript", - workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), - ) - .get("multilineMethodArgs") + workspace + .getConfiguration( + "objectscript", + workspace.workspaceFolders?.find((f) => f.name.toLowerCase() == uri.authority.toLowerCase()), + ) + .get("multilineMethodArgs") ? { format: "udl-multiline" } : undefined; const resp = await makeRESTRequest( @@ -332,17 +344,6 @@ export async function activate(context: ExtensionContext) { // Start the client. This will also launch the server await client.start(); - // Ensure that every server has at most one session. - for (const f of workspace.workspaceFolders ?? []) { - try { - const serverSpec = await resolveServerSpec(f.uri); - if (!serverSpec.active || !serverSpec.apiVersion) continue; - await makeRESTRequest("GET", 1, "", serverSpec); - } catch { - // Ignore any failure; the session will be created on demand instead - } - } - const workbenchConfig = workspace.getConfiguration("workbench"); if ( workspace