Move the Portal Backend connection string calls out of Helpers

Helpers holds pure string utilities, and every other *Client in the repo sits at its domain root, so name the module for the endpoints it wraps and place it beside HostedUtils. Also drop the branch's ConnectScreen.less tooltip restyle, which was unrelated to connection string login.
This commit is contained in:
Asier Isayas
2026-08-19 15:19:21 -07:00
parent 16126995f7
commit 87c600809f
7 changed files with 24 additions and 49 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
jest.mock("./hooks/useAADAuth");
jest.mock("./hooks/useConfig");
jest.mock("./Platform/Hosted/Helpers/PortalBackendClient");
jest.mock("./Platform/Hosted/PortalBackendConnectionStringClient");
jest.mock("./Platform/Hosted/Components/ConnectExplorer");
jest.mock("./Shared/appInsights");
jest.mock("./Platform/Hosted/Components/AccountSwitcher", () => ({
@@ -29,7 +29,7 @@ import { useAADAuth } from "./hooks/useAADAuth";
import { useConfig } from "./hooks/useConfig";
import { App } from "./HostedExplorer";
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/Helpers/PortalBackendClient";
import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/PortalBackendConnectionStringClient";
const mockFetchEncryptedToken = fetchEncryptedToken as jest.MockedFunction<typeof fetchEncryptedToken>;
+1 -1
View File
@@ -19,12 +19,12 @@ import { MeControl } from "./Platform/Hosted/Components/MeControl";
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
import "./Platform/Hosted/ConnectScreen.less";
import { parseConnectionString } from "./Platform/Hosted/Helpers/ConnectionStringParser";
import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/Helpers/PortalBackendClient";
import { isResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils";
import {
extractMasterKeyFromDirectLoginConnectionString,
isDirectConnectionStringLoginApi,
} from "./Platform/Hosted/HostedUtils";
import { fetchAccessData, fetchEncryptedToken } from "./Platform/Hosted/PortalBackendConnectionStringClient";
import "./Shared/appInsights";
import { allowedHostedExplorerEndpoints } from "./Utils/EndpointUtils";
import { useAADAuth } from "./hooks/useAADAuth";
@@ -7,9 +7,12 @@ import ErrorImage from "../../../../images/error.svg";
import { AuthType } from "../../../AuthType";
import { AccessInputMetadata } from "../../../Contracts/DataModels";
import { parseConnectionString } from "../Helpers/ConnectionStringParser";
import { fetchEncryptedToken, isAccountRestrictedForConnectionStringLogin } from "../Helpers/PortalBackendClient";
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
import { isDirectConnectionStringLoginApi } from "../HostedUtils";
import {
fetchEncryptedToken,
isAccountRestrictedForConnectionStringLogin,
} from "../PortalBackendConnectionStringClient";
interface Props {
connectionString: string;
+9 -17
View File
@@ -65,35 +65,27 @@
visibility: visible;
}
.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails {
top: 50%;
transform: translateY(-50%);
left: 100%;
margin-left: 10px;
width: 320px;
max-height: 240px;
overflow-y: auto;
bottom: 24px;
width: 165px;
visibility: hidden;
background-color: #393939;
color: #ffffff;
position: absolute;
z-index: 1;
left: -10px;
padding: 6px;
text-align: left;
white-space: normal;
overflow-wrap: break-word;
}
.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip:hover:after {
border-width: 8px 8px 8px 0px;
.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorDetails:after {
border-width: 10px 10px 0px 10px;
bottom: -8px;
content: "";
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 100%;
margin-left: 2px;
right: 100%;
border-style: solid;
left: 12px;
width: 0;
height: 0;
border-color: transparent #393939;
border-color: #3b3b3b transparent;
}
.connectExplorerContainer .connectExplorer .connectExplorerContent .errorDetailsInfoTooltip .errorImg {
height: 14px;
+1 -3
View File
@@ -40,9 +40,7 @@ export function getDatabaseAccountKindFromExperience(apiExperience: typeof userC
return AccountKind.GlobalDocumentDB;
}
// Returns the master key carried by SQL, Table, and Gremlin connection strings. Mongo and
// Cassandra do not use an AccountKey token, so they must not be passed here. The key value cannot
// contain a semicolon, so we capture everything up to the next delimiter.
// Returns the master key carried by SQL, Table, and Gremlin connection strings.
export function extractMasterKeyFromDirectLoginConnectionString(connectionString: string): string | undefined {
const matchedParts = connectionString?.match(/AccountKey=([^;]*)/);
return (matchedParts && matchedParts.length > 1 && matchedParts[1]) || undefined;
@@ -1,24 +1,6 @@
import { HttpHeaders } from "../../../Common/Constants";
import { configContext } from "../../../ConfigContext";
import { AccessInputMetadata } from "../../../Contracts/DataModels";
// A failed Portal Backend response. Carries the status so callers can tell a rejected connection string
// from a service failure.
export class PortalBackendError extends Error {
constructor(
message: string,
public readonly statusCode: number,
) {
super(message);
// Set the prototype explicitly so `instanceof` works.
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work
Object.setPrototypeOf(this, PortalBackendError.prototype);
}
static async fromResponse(response: Response): Promise<PortalBackendError> {
return new PortalBackendError(await response.text(), response.status);
}
}
import { HttpHeaders } from "../../Common/Constants";
import { configContext } from "../../ConfigContext";
import { AccessInputMetadata } from "../../Contracts/DataModels";
export async function fetchAccessData(portalToken: string): Promise<AccessInputMetadata> {
const headers = new Headers();
@@ -43,7 +25,7 @@ export async function fetchEncryptedToken(connectionString: string): Promise<str
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw await PortalBackendError.fromResponse(response);
throw response;
}
const encryptedTokenResponse: string = await response.json();
@@ -56,7 +38,7 @@ export async function isAccountRestrictedForConnectionStringLogin(connectionStri
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/guest/accountrestrictions/checkconnectionstringlogin";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw await PortalBackendError.fromResponse(response);
throw response;
}
return (await response.text()).toLowerCase() === "true";
+2 -2
View File
@@ -66,7 +66,7 @@
"./src/Platform/Hosted/Components/MeControl.test.tsx",
"./src/Platform/Hosted/Components/MeControl.tsx",
"./src/Platform/Hosted/Components/SignInButton.tsx",
"./src/Platform/Hosted/Helpers/PortalBackendClient.ts",
"./src/Platform/Hosted/PortalBackendConnectionStringClient.ts",
"./src/Platform/Hosted/HostedUtils.test.ts",
"./src/Platform/Hosted/HostedUtils.ts",
"./src/Platform/Hosted/extractFeatures.test.ts",
@@ -137,4 +137,4 @@
"src/Shared/Telemetry/**/*",
"src/Utils/arm/**/*"
]
}
}