mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-19 17:01:13 +00:00
Prepare for Fabric native (#2050)
* Implement fabric native path * Fix default values to work with current fabric clients * Fix Fabric native mode * Fix unit test * export Fabric context * Dynamically close Home tab for Mirrored databases in Fabric rather than conditional init (which doesn't work for Native) * For Fabric native, don't show "Delete Database" in context menu and reading databases should return the database from the context. * Update to V3 messaging * For data plane operations, skip ARM for Fabric native. Refine the tests for fabric to make the distinction between mirrored key, mirrored AAD and native. Fix FabricUtil to strict compile. * Add support for refreshing access tokens * Buf fix: don't wait for refresh is async * Fix format * Fix strict compile issue --------- Co-authored-by: Laurent Nguyen <languye@microsoft.com>
This commit is contained in:
@@ -1,56 +1,112 @@
|
||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||
import { configContext, Platform } from "ConfigContext";
|
||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
||||
import { updateUserContext, userContext } from "UserContext";
|
||||
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
||||
import { FabricArtifactInfo, updateUserContext, userContext } from "UserContext";
|
||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||
|
||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let timeoutId: NodeJS.Timeout | undefined;
|
||||
|
||||
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
||||
let lastRequestTimestamp: number = undefined;
|
||||
let lastRequestTimestamp: number | undefined = undefined;
|
||||
|
||||
const requestDatabaseResourceTokens = async (): Promise<void> => {
|
||||
/**
|
||||
* Request fabric token:
|
||||
* - Mirrored key and AAD: Database Resource Tokens
|
||||
* - Native: AAD token
|
||||
* @returns
|
||||
*/
|
||||
const requestFabricToken = async (): Promise<void> => {
|
||||
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastRequestTimestamp = Date.now();
|
||||
try {
|
||||
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricDatabaseConnectionInfo>(
|
||||
FabricMessageTypes.GetAllResourceTokens,
|
||||
[],
|
||||
userContext.fabricContext.connectionId,
|
||||
);
|
||||
|
||||
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
||||
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint;
|
||||
if (isFabricMirrored()) {
|
||||
await requestAndStoreDatabaseResourceTokens();
|
||||
} else if (isFabricNative()) {
|
||||
await requestAndStoreAccessToken();
|
||||
}
|
||||
|
||||
updateUserContext({
|
||||
fabricContext: {
|
||||
...userContext.fabricContext,
|
||||
databaseConnectionInfo: fabricDatabaseConnectionInfo,
|
||||
isReadOnly: true,
|
||||
},
|
||||
databaseAccount: { ...userContext.databaseAccount },
|
||||
});
|
||||
scheduleRefreshDatabaseResourceToken();
|
||||
scheduleRefreshFabricToken();
|
||||
} catch (error) {
|
||||
logConsoleError(error);
|
||||
logConsoleError(error as string);
|
||||
throw error;
|
||||
} finally {
|
||||
lastRequestTimestamp = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const requestAndStoreDatabaseResourceTokens = async (): Promise<void> => {
|
||||
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
||||
// This should not happen
|
||||
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceTokenInfo = await sendCachedDataMessage<ResourceTokenInfo>(
|
||||
FabricMessageTypes.GetAllResourceTokens,
|
||||
[],
|
||||
userContext.fabricContext.artifactInfo?.connectionId,
|
||||
);
|
||||
|
||||
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
||||
userContext.databaseAccount.properties.documentEndpoint = resourceTokenInfo.endpoint;
|
||||
}
|
||||
|
||||
if (resourceTokenInfo.credentialType === "OAuth2") {
|
||||
// Mirrored AAD
|
||||
updateUserContext({
|
||||
fabricContext: {
|
||||
...userContext.fabricContext,
|
||||
databaseName: resourceTokenInfo.databaseId,
|
||||
artifactInfo: undefined,
|
||||
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
||||
},
|
||||
databaseAccount: { ...userContext.databaseAccount },
|
||||
aadToken: resourceTokenInfo.accessToken,
|
||||
});
|
||||
} else {
|
||||
// TODO: In Fabric contract V2, credentialType is undefined. For V3, it is "Key". Check for "Key" when V3 is supported for Fabric Mirroring Key
|
||||
// Mirrored key
|
||||
updateUserContext({
|
||||
fabricContext: {
|
||||
...userContext.fabricContext,
|
||||
databaseName: resourceTokenInfo.databaseId,
|
||||
artifactInfo: {
|
||||
...(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]),
|
||||
resourceTokenInfo,
|
||||
},
|
||||
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
||||
},
|
||||
databaseAccount: { ...userContext.databaseAccount },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const requestAndStoreAccessToken = async (): Promise<void> => {
|
||||
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
||||
// This should not happen
|
||||
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
||||
return;
|
||||
}
|
||||
|
||||
const accessTokenInfo = await sendCachedDataMessage<{ accessToken: string }>(FabricMessageTypes.GetAccessToken, []);
|
||||
|
||||
updateUserContext({
|
||||
aadToken: accessTokenInfo.accessToken,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check token validity and schedule a refresh if necessary
|
||||
* @param tokenTimestamp
|
||||
* @returns
|
||||
*/
|
||||
export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Promise<void> => {
|
||||
export const scheduleRefreshFabricToken = (refreshNow?: boolean): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
@@ -59,7 +115,7 @@ export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Prom
|
||||
|
||||
timeoutId = setTimeout(
|
||||
() => {
|
||||
requestDatabaseResourceTokens().then(resolve);
|
||||
requestFabricToken().then(resolve);
|
||||
},
|
||||
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
||||
);
|
||||
@@ -68,6 +124,15 @@ export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Prom
|
||||
|
||||
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
||||
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
||||
scheduleRefreshDatabaseResourceToken(true);
|
||||
scheduleRefreshFabricToken(true);
|
||||
}
|
||||
};
|
||||
|
||||
export const isFabric = (): boolean => configContext.platform === Platform.Fabric;
|
||||
export const isFabricMirroredKey = (): boolean =>
|
||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY;
|
||||
export const isFabricMirroredAAD = (): boolean =>
|
||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_AAD;
|
||||
export const isFabricMirrored = (): boolean => isFabricMirroredKey() || isFabricMirroredAAD();
|
||||
export const isFabricNative = (): boolean =>
|
||||
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.NATIVE;
|
||||
|
||||
Reference in New Issue
Block a user