mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 18:01:39 +00:00
Checkpoint
This commit is contained in:
@@ -241,8 +241,6 @@ src/Platform/Hosted/Authorization.ts
|
||||
src/Platform/Hosted/DataAccessUtility.ts
|
||||
src/Platform/Hosted/ExplorerFactory.ts
|
||||
src/Platform/Hosted/Helpers/ConnectionStringParser.test.ts
|
||||
src/Platform/Hosted/HostedUtils.test.ts
|
||||
src/Platform/Hosted/HostedUtils.ts
|
||||
src/Platform/Hosted/Main.ts
|
||||
src/Platform/Hosted/Maint.test.ts
|
||||
src/Platform/Hosted/NotificationsClient.ts
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu"
|
||||
import { Dropdown, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { useSubscriptions } from "../../../hooks/useSubscriptions";
|
||||
import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts";
|
||||
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||
|
||||
const buttonStyles: IButtonStyles = {
|
||||
root: {
|
||||
@@ -37,12 +38,27 @@ const buttonStyles: IButtonStyles = {
|
||||
}
|
||||
};
|
||||
|
||||
export const AccountSwitchComponent: React.FunctionComponent<{ armToken: string }> = ({ armToken }) => {
|
||||
const cachedSubscriptionId = localStorage.getItem("cachedSubscriptionId");
|
||||
const cachedDatabaseAccountName = localStorage.getItem("cachedDatabaseAccountName");
|
||||
|
||||
export const AccountSwitchComponent: React.FunctionComponent<{
|
||||
armToken: string;
|
||||
setDatabaseAccount: (account: DatabaseAccount) => void;
|
||||
}> = ({ armToken, setDatabaseAccount }) => {
|
||||
const subscriptions = useSubscriptions(armToken);
|
||||
const cachedSubscriptionId = localStorage.getItem("cachedSubscriptionId");
|
||||
const [selectedSubscriptionId, setSelectedSubscriptionId] = React.useState<string>(cachedSubscriptionId);
|
||||
const accounts = useDatabaseAccounts(selectedSubscriptionId, armToken);
|
||||
const [selectedAccountName, setSelectedAccoutName] = React.useState<string>();
|
||||
const [selectedAccountName, setSelectedAccoutName] = React.useState<string>(cachedDatabaseAccountName);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (accounts && selectedAccountName) {
|
||||
const account = accounts.find(account => account.name == selectedAccountName);
|
||||
// Only set a new account if one is found
|
||||
if (account) {
|
||||
setDatabaseAccount(account);
|
||||
}
|
||||
}
|
||||
}, [accounts, selectedAccountName]);
|
||||
|
||||
const menuProps: IContextualMenuProps = {
|
||||
directionalHintFixed: true,
|
||||
@@ -85,30 +101,28 @@ export const AccountSwitchComponent: React.FunctionComponent<{ armToken: string
|
||||
{
|
||||
key: "switchAccount",
|
||||
onRender: (item, dismissMenu) => {
|
||||
const isLoadingAccounts = false;
|
||||
|
||||
const options = accounts.map(account => ({
|
||||
key: account.name,
|
||||
text: account.name,
|
||||
data: account
|
||||
}));
|
||||
|
||||
const placeHolderText = isLoadingAccounts
|
||||
? "Loading Cosmos DB accounts"
|
||||
: !options || !options.length
|
||||
? "No Cosmos DB accounts found"
|
||||
: "Select Cosmos DB account from list";
|
||||
// const placeHolderText = isLoadingAccounts
|
||||
// ? "Loading Cosmos DB accounts"
|
||||
// : !options || !options.length
|
||||
// ? "No Cosmos DB accounts found"
|
||||
// : "Select Cosmos DB account from list";
|
||||
|
||||
const dropdownProps: IDropdownProps = {
|
||||
label: "Cosmos DB Account Name",
|
||||
className: "accountSwitchAccountDropdown",
|
||||
options,
|
||||
options: accounts.map(account => ({
|
||||
key: account.name,
|
||||
text: account.name,
|
||||
data: account
|
||||
})),
|
||||
onChange: (event, option) => {
|
||||
const accountName = String(option.key);
|
||||
setSelectedAccoutName(String(option.key));
|
||||
localStorage.setItem("cachedDatabaseAccountName", accountName);
|
||||
dismissMenu();
|
||||
},
|
||||
defaultSelectedKey: selectedAccountName,
|
||||
placeholder: placeHolderText,
|
||||
placeholder: "No Cosmos DB accounts found",
|
||||
styles: {
|
||||
callout: "accountSwitchAccountDropdownMenu"
|
||||
}
|
||||
|
||||
@@ -1843,7 +1843,7 @@ export default class Explorer {
|
||||
if (inputs != null) {
|
||||
// In development mode, save the iframe message from the portal in session storage.
|
||||
// This allows webpack hot reload to funciton properly
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
if (process.env.NODE_ENV === "development" && configContext.platform === Platform.Portal) {
|
||||
sessionStorage.setItem("portalDataExplorerInitMessage", JSON.stringify(inputs));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import { useDirectories } from "./hooks/useDirectories";
|
||||
import * as Msal from "msal";
|
||||
import { configContext } from "./ConfigContext";
|
||||
import { HttpHeaders } from "./Common/Constants";
|
||||
import { GenerateTokenResponse } from "./Contracts/DataModels";
|
||||
import { GenerateTokenResponse, DatabaseAccount } from "./Contracts/DataModels";
|
||||
import { AuthType } from "./AuthType";
|
||||
|
||||
initializeIcons();
|
||||
@@ -43,6 +43,12 @@ const msal = new Msal.UserAgentApplication({
|
||||
}
|
||||
});
|
||||
|
||||
interface HostedExplorerChildFrame extends Window {
|
||||
authType: AuthType;
|
||||
databaseAccount: DatabaseAccount;
|
||||
authorizationToken: string;
|
||||
}
|
||||
|
||||
const cachedAccount = msal.getAllAccounts()?.[0];
|
||||
const cachedTenantId = localStorage.getItem("cachedTenantId");
|
||||
|
||||
@@ -65,6 +71,9 @@ const App: React.FunctionComponent = () => {
|
||||
const [graphToken, setGraphToken] = React.useState<string>();
|
||||
const [armToken, setArmToken] = React.useState<string>();
|
||||
const [connectionString, setConnectionString] = React.useState<string>("");
|
||||
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
|
||||
|
||||
const ref = React.useRef<HTMLIFrameElement>();
|
||||
|
||||
const login = React.useCallback(async () => {
|
||||
const response = await msal.loginPopup();
|
||||
@@ -96,6 +105,21 @@ const App: React.FunctionComponent = () => {
|
||||
}
|
||||
}, [account, tenantId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// If ref.current is undefined no iframe has been rendered
|
||||
if (ref.current) {
|
||||
const frameWindow = ref.current.contentWindow as HostedExplorerChildFrame;
|
||||
frameWindow.authType = AuthType.AAD;
|
||||
frameWindow.databaseAccount = databaseAccount;
|
||||
frameWindow.authorizationToken = armToken;
|
||||
// const frameWindow = ref.current.contentWindow;
|
||||
// frameWindow.authType = AuthType.EncryptedToken;
|
||||
// frameWindow.encryptedToken = encryptedToken;
|
||||
// frameWindow.encryptedTokenMetadata = encryptedTokenMetadata;
|
||||
// frameWindow.parsedConnectionString = "foo";
|
||||
}
|
||||
}, [ref, encryptedToken, encryptedTokenMetadata, isLoggedIn, databaseAccount]);
|
||||
|
||||
const photo = useGraphPhoto(graphToken);
|
||||
const directories = useDirectories(armToken);
|
||||
|
||||
@@ -118,7 +142,7 @@ const App: React.FunctionComponent = () => {
|
||||
)}
|
||||
{isLoggedIn && (
|
||||
<span className="accountSwitchComponentContainer">
|
||||
<AccountSwitchComponent armToken={armToken} />
|
||||
<AccountSwitchComponent armToken={armToken} setDatabaseAccount={setDatabaseAccount} />
|
||||
</span>
|
||||
)}
|
||||
{!isLoggedIn && encryptedTokenMetadata?.accountName && (
|
||||
@@ -202,26 +226,22 @@ const App: React.FunctionComponent = () => {
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{encryptedTokenMetadata && !isLoggedIn && (
|
||||
{databaseAccount && (
|
||||
// Ideally we would import and render data explorer like any other React component, however
|
||||
// because it still has a significant amount of Knockout code, this would lead to memory leaks.
|
||||
// Knockout does not have a way to tear down all of its binding and listeners with a single method.
|
||||
// It's possible this can be changed once all knockout code has been removed.
|
||||
<iframe
|
||||
// Setting key is needed so React will re-render this element on any account change
|
||||
key={databaseAccount.id}
|
||||
ref={ref}
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
className="iframe"
|
||||
title="explorer"
|
||||
src={`explorer.html?v=1.0.1&platform=Hosted&authType=${AuthType.EncryptedToken}&key=${encodeURIComponent(
|
||||
encryptedToken
|
||||
)}&metadata=${JSON.stringify(encryptedTokenMetadata)}`}
|
||||
src="explorer.html?v=1.0.1&platform=Hosted"
|
||||
></iframe>
|
||||
)}
|
||||
{/* {!encryptedTokenMetadata && isLoggedIn && (
|
||||
<iframe
|
||||
id="explorerMenu"
|
||||
name="explorer"
|
||||
className="iframe"
|
||||
title="explorer"
|
||||
src={`explorer.html?v=1.0.1&platform=Hosted&authType=${AuthType.AAD}`}
|
||||
></iframe>
|
||||
)} */}
|
||||
{!isLoggedIn && !encryptedTokenMetadata && (
|
||||
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
|
||||
<div className="connectExplorerFormContainer">
|
||||
|
||||
44
src/Main.tsx
44
src/Main.tsx
@@ -60,7 +60,7 @@ import { AuthType } from "./AuthType";
|
||||
|
||||
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
|
||||
import { applyExplorerBindings } from "./applyExplorerBindings";
|
||||
import { initializeConfiguration, Platform } from "./ConfigContext";
|
||||
import { configContext, initializeConfiguration, Platform } from "./ConfigContext";
|
||||
import Explorer from "./Explorer/Explorer";
|
||||
import React, { useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
@@ -71,6 +71,10 @@ import refreshImg from "../images/refresh-cosmos.svg";
|
||||
import arrowLeftImg from "../images/imgarrowlefticon.svg";
|
||||
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
|
||||
import { AccountKind, DefaultAccountExperience, TagNames } from "./Common/Constants";
|
||||
import { updateUserContext } from "./UserContext";
|
||||
import AuthHeadersUtil from "./Platform/Hosted/Authorization";
|
||||
import { CollectionCreation } from "./Shared/Constants";
|
||||
import { extractFeatures } from "./Platform/Hosted/extractFeatures";
|
||||
|
||||
// TODO: Encapsulate and reuse all global variables as environment variables
|
||||
window.authType = AuthType.AAD;
|
||||
@@ -97,7 +101,39 @@ const App: React.FunctionComponent = () => {
|
||||
initializeConfiguration().then(config => {
|
||||
let explorer: Explorer;
|
||||
if (config.platform === Platform.Hosted) {
|
||||
explorer = Hosted.initializeExplorer();
|
||||
const authType: AuthType = window.authType;
|
||||
explorer = new Explorer();
|
||||
if (window.authType === AuthType.EncryptedToken) {
|
||||
updateUserContext({
|
||||
accessToken: window.encryptedToken
|
||||
});
|
||||
Hosted.initDataExplorerFrameInputs(explorer);
|
||||
} else if (window.authType === AuthType.AAD) {
|
||||
const account = window.databaseAccount;
|
||||
const serverId = AuthHeadersUtil.serverId;
|
||||
const accountResourceId = account.id;
|
||||
const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
|
||||
const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
|
||||
const inputs: DataExplorerInputsFrame = {
|
||||
databaseAccount: account,
|
||||
subscriptionId,
|
||||
resourceGroup,
|
||||
masterKey: "",
|
||||
hasWriteAccess: true, //TODO: 425017 - support read access
|
||||
authorizationToken: `Bearer ${window.authorizationToken}`,
|
||||
features: extractFeatures(),
|
||||
csmEndpoint: undefined,
|
||||
dnsSuffix: undefined,
|
||||
serverId: serverId,
|
||||
extensionEndpoint: configContext.BACKEND_ENDPOINT,
|
||||
subscriptionType: CollectionCreation.DefaultSubscriptionType,
|
||||
quotaId: undefined,
|
||||
addCollectionDefaultFlight: explorer.flight(),
|
||||
isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription()
|
||||
};
|
||||
explorer.initDataExplorerWithFrameInputs(inputs);
|
||||
explorer.isAccountReady(true);
|
||||
}
|
||||
} else if (config.platform === Platform.Emulator) {
|
||||
window.authType = AuthType.MasterKey;
|
||||
const explorer = new Explorer();
|
||||
@@ -118,7 +154,7 @@ const App: React.FunctionComponent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
||||
window.addEventListener("message", message => explorer.handleMessage(message), false);
|
||||
}
|
||||
applyExplorerBindings(explorer);
|
||||
});
|
||||
@@ -177,7 +213,7 @@ const App: React.FunctionComponent = () => {
|
||||
aria-label="Share url link"
|
||||
className="shareLink"
|
||||
type="text"
|
||||
read-only
|
||||
read-only={true}
|
||||
data-bind="value: shareAccessUrl"
|
||||
/>
|
||||
<span
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AccessInputMetadata } from "../../Contracts/DataModels";
|
||||
import { HostedUtils } from "./HostedUtils";
|
||||
import { getDatabaseAccountPropertiesFromMetadata } from "./HostedUtils";
|
||||
|
||||
describe("getDatabaseAccountPropertiesFromMetadata", () => {
|
||||
it("should only return an object with the mongoEndpoint key if the apiKind is mongoCompute (5)", () => {
|
||||
let mongoComputeAccount: AccessInputMetadata = {
|
||||
const mongoComputeAccount: AccessInputMetadata = {
|
||||
accountName: "compute-batch2",
|
||||
apiEndpoint: "compute-batch2.mongo.cosmos.azure.com:10255",
|
||||
apiKind: 5,
|
||||
@@ -11,21 +11,21 @@ describe("getDatabaseAccountPropertiesFromMetadata", () => {
|
||||
expiryTimestamp: "1234",
|
||||
mongoEndpoint: "https://compute-batch2.mongo.cosmos.azure.com:443/"
|
||||
};
|
||||
expect(HostedUtils.getDatabaseAccountPropertiesFromMetadata(mongoComputeAccount)).toEqual({
|
||||
expect(getDatabaseAccountPropertiesFromMetadata(mongoComputeAccount)).toEqual({
|
||||
mongoEndpoint: mongoComputeAccount.mongoEndpoint,
|
||||
documentEndpoint: mongoComputeAccount.documentEndpoint
|
||||
});
|
||||
});
|
||||
|
||||
it("should not return an object with the mongoEndpoint key if the apiKind is mongo (1)", () => {
|
||||
let mongoAccount: AccessInputMetadata = {
|
||||
const mongoAccount: AccessInputMetadata = {
|
||||
accountName: "compute-batch2",
|
||||
apiEndpoint: "compute-batch2.mongo.cosmos.azure.com:10255",
|
||||
apiKind: 1,
|
||||
documentEndpoint: "https://compute-batch2.documents.azure.com:443/",
|
||||
expiryTimestamp: "1234"
|
||||
};
|
||||
expect(HostedUtils.getDatabaseAccountPropertiesFromMetadata(mongoAccount)).toEqual({
|
||||
expect(getDatabaseAccountPropertiesFromMetadata(mongoAccount)).toEqual({
|
||||
documentEndpoint: mongoAccount.documentEndpoint
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,33 +3,31 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { AccessInputMetadata } from "../../Contracts/DataModels";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
|
||||
export class HostedUtils {
|
||||
static getDatabaseAccountPropertiesFromMetadata(metadata: AccessInputMetadata): any {
|
||||
let properties = { documentEndpoint: metadata.documentEndpoint };
|
||||
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(metadata.apiKind);
|
||||
export function getDatabaseAccountPropertiesFromMetadata(metadata: AccessInputMetadata): unknown {
|
||||
let properties = { documentEndpoint: metadata.documentEndpoint };
|
||||
const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(metadata.apiKind);
|
||||
|
||||
if (apiExperience === Constants.DefaultAccountExperience.Cassandra) {
|
||||
if (apiExperience === Constants.DefaultAccountExperience.Cassandra) {
|
||||
properties = Object.assign(properties, {
|
||||
cassandraEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableCassandra }]
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.Table) {
|
||||
properties = Object.assign(properties, {
|
||||
tableEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableTable }]
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.Graph) {
|
||||
properties = Object.assign(properties, {
|
||||
gremlinEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableGremlin }]
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.MongoDB) {
|
||||
if (metadata.apiKind === DataModels.ApiKind.MongoDBCompute) {
|
||||
properties = Object.assign(properties, {
|
||||
cassandraEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableCassandra }]
|
||||
mongoEndpoint: metadata.mongoEndpoint
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.Table) {
|
||||
properties = Object.assign(properties, {
|
||||
tableEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableTable }]
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.Graph) {
|
||||
properties = Object.assign(properties, {
|
||||
gremlinEndpoint: metadata.apiEndpoint,
|
||||
capabilities: [{ name: Constants.CapabilityNames.EnableGremlin }]
|
||||
});
|
||||
} else if (apiExperience === Constants.DefaultAccountExperience.MongoDB) {
|
||||
if (metadata.apiKind === DataModels.ApiKind.MongoDBCompute) {
|
||||
properties = Object.assign(properties, {
|
||||
mongoEndpoint: metadata.mongoEndpoint
|
||||
});
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -1,60 +1,17 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import AuthHeadersUtil from "./Authorization";
|
||||
import Q from "q";
|
||||
import {
|
||||
AccessInputMetadata,
|
||||
ApiKind,
|
||||
DatabaseAccount,
|
||||
GenerateTokenResponse,
|
||||
resourceTokenConnectionStringProperties
|
||||
} from "../../Contracts/DataModels";
|
||||
import { AuthType } from "../../AuthType";
|
||||
import { CollectionCreation } from "../../Shared/Constants";
|
||||
import { DataExplorerInputsFrame } from "../../Contracts/ViewModels";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import { HostedUtils } from "./HostedUtils";
|
||||
import { sendMessage } from "../../Common/MessageHandler";
|
||||
import { SessionStorageUtility, StorageKey } from "../../Shared/StorageUtility";
|
||||
import { SubscriptionUtilMappings } from "../../Shared/Constants";
|
||||
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||
import Explorer from "../../Explorer/Explorer";
|
||||
import { updateUserContext } from "../../UserContext";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { configContext } from "../../ConfigContext";
|
||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||
import { ApiKind, DatabaseAccount, resourceTokenConnectionStringProperties } from "../../Contracts/DataModels";
|
||||
import { DataExplorerInputsFrame } from "../../Contracts/ViewModels";
|
||||
import Explorer from "../../Explorer/Explorer";
|
||||
import "../../Explorer/Tables/DataTable/DataTableBindingManager";
|
||||
import { CollectionCreation, SubscriptionUtilMappings } from "../../Shared/Constants";
|
||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||
import AuthHeadersUtil from "./Authorization";
|
||||
import { extractFeatures } from "./extractFeatures";
|
||||
import { getDatabaseAccountPropertiesFromMetadata } from "./HostedUtils";
|
||||
|
||||
export default class Main {
|
||||
private static _databaseAccountId: string;
|
||||
private static _encryptedToken: string;
|
||||
private static _accessInputMetadata: AccessInputMetadata;
|
||||
|
||||
public static initializeExplorer(): Explorer {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let authType: string = params && params.get("authType");
|
||||
|
||||
// Encrypted token flow
|
||||
if (params && params.has("key")) {
|
||||
Main._encryptedToken = encodeURIComponent(params.get("key"));
|
||||
Main._accessInputMetadata = JSON.parse(params.get("metadata"));
|
||||
authType = AuthType.EncryptedToken;
|
||||
}
|
||||
|
||||
const explorer = new Explorer();
|
||||
// workaround to resolve cyclic refs with view // TODO. Is this even needed anymore?
|
||||
explorer.renewExplorerShareAccess = Main.renewExplorerAccess;
|
||||
window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
|
||||
if (authType === AuthType.EncryptedToken) {
|
||||
updateUserContext({
|
||||
accessToken: Main._encryptedToken
|
||||
});
|
||||
Main._initDataExplorerFrameInputs(explorer);
|
||||
} else if (authType === AuthType.AAD) {
|
||||
} else {
|
||||
Main._initDataExplorerFrameInputs(explorer);
|
||||
}
|
||||
return explorer;
|
||||
}
|
||||
|
||||
public static parseResourceTokenConnectionString(connectionString: string): resourceTokenConnectionStringProperties {
|
||||
let accountEndpoint: string;
|
||||
let collectionId: string;
|
||||
@@ -87,75 +44,7 @@ export default class Main {
|
||||
};
|
||||
}
|
||||
|
||||
public static renewExplorerAccess = (explorer: Explorer, connectionString: string): Q.Promise<void> => {
|
||||
if (!connectionString) {
|
||||
console.error("Missing or invalid connection string input");
|
||||
Q.reject("Missing or invalid connection string input");
|
||||
}
|
||||
|
||||
if (Main._isResourceToken(connectionString)) {
|
||||
return Main._renewExplorerAccessWithResourceToken(explorer, connectionString);
|
||||
}
|
||||
|
||||
const deferred: Q.Deferred<void> = Q.defer<void>();
|
||||
AuthHeadersUtil.generateUnauthenticatedEncryptedTokenForConnectionString(connectionString).then(
|
||||
(encryptedToken: GenerateTokenResponse) => {
|
||||
if (!encryptedToken || !encryptedToken.readWrite) {
|
||||
deferred.reject("Encrypted token is empty or undefined");
|
||||
}
|
||||
|
||||
Main._encryptedToken = encryptedToken.readWrite;
|
||||
window.authType = AuthType.EncryptedToken;
|
||||
|
||||
updateUserContext({
|
||||
accessToken: Main._encryptedToken
|
||||
});
|
||||
Main._getAccessInputMetadata(Main._encryptedToken).then(
|
||||
() => {
|
||||
if (explorer.isConnectExplorerVisible()) {
|
||||
explorer.notificationConsoleData([]);
|
||||
explorer.hideConnectExplorerForm();
|
||||
}
|
||||
|
||||
if (Main._accessInputMetadata.apiKind != ApiKind.Graph) {
|
||||
// do not save encrypted token for graphs because we cannot extract master key in the client
|
||||
SessionStorageUtility.setEntryString(StorageKey.EncryptedKeyToken, Main._encryptedToken);
|
||||
window.parent &&
|
||||
window.parent.history.replaceState(
|
||||
{ encryptedToken: encryptedToken },
|
||||
"",
|
||||
`?key=${Main._encryptedToken}${(window.parent && window.parent.location.hash) || ""}`
|
||||
); // replace query params if any
|
||||
} else {
|
||||
SessionStorageUtility.removeEntry(StorageKey.EncryptedKeyToken);
|
||||
window.parent &&
|
||||
window.parent.history.replaceState(
|
||||
{ encryptedToken: encryptedToken },
|
||||
"",
|
||||
`?${(window.parent && window.parent.location.hash) || ""}`
|
||||
); // replace query params if any
|
||||
}
|
||||
|
||||
const masterKey: string = Main._getMasterKeyFromConnectionString(connectionString);
|
||||
Main._setExplorerReady(explorer, masterKey);
|
||||
|
||||
deferred.resolve();
|
||||
},
|
||||
(error: any) => {
|
||||
console.error(error);
|
||||
deferred.reject(error);
|
||||
}
|
||||
);
|
||||
},
|
||||
(error: any) => {
|
||||
deferred.reject(`Failed to generate encrypted token: ${getErrorMessage(error)}`);
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise.timeout(Constants.ClientDefaults.requestTimeoutMs);
|
||||
};
|
||||
|
||||
private static _initDataExplorerFrameInputs(
|
||||
public static initDataExplorerFrameInputs(
|
||||
explorer: Explorer,
|
||||
masterKey?: string /* master key extracted from connection string if available */,
|
||||
account?: DatabaseAccount,
|
||||
@@ -187,7 +76,7 @@ export default class Main {
|
||||
id: Main._databaseAccountId,
|
||||
name: Main._accessInputMetadata.accountName,
|
||||
kind: this._getDatabaseAccountKindFromExperience(apiExperience),
|
||||
properties: HostedUtils.getDatabaseAccountPropertiesFromMetadata(Main._accessInputMetadata),
|
||||
properties: getDatabaseAccountPropertiesFromMetadata(Main._accessInputMetadata),
|
||||
tags: { defaultExperience: apiExperience }
|
||||
},
|
||||
subscriptionId,
|
||||
@@ -237,7 +126,7 @@ export default class Main {
|
||||
id: Main._databaseAccountId,
|
||||
name: Main._accessInputMetadata.accountName,
|
||||
kind: this._getDatabaseAccountKindFromExperience(apiExperience),
|
||||
properties: HostedUtils.getDatabaseAccountPropertiesFromMetadata(Main._accessInputMetadata),
|
||||
properties: getDatabaseAccountPropertiesFromMetadata(Main._accessInputMetadata),
|
||||
tags: { defaultExperience: apiExperience }
|
||||
},
|
||||
subscriptionId,
|
||||
@@ -273,11 +162,6 @@ export default class Main {
|
||||
return Constants.AccountKind.GlobalDocumentDB;
|
||||
}
|
||||
|
||||
private static async _getAccessInputMetadata(accessInput: string): Promise<void> {
|
||||
const metadata = await AuthHeadersUtil.getAccessInputMetadata(accessInput);
|
||||
Main._accessInputMetadata = metadata;
|
||||
}
|
||||
|
||||
private static _getMasterKeyFromConnectionString(connectionString: string): string {
|
||||
if (!connectionString || Main._accessInputMetadata == null || Main._accessInputMetadata.apiKind !== ApiKind.Graph) {
|
||||
// client only needs master key for Graph API
|
||||
@@ -291,69 +175,4 @@ export default class Main {
|
||||
private static _isResourceToken(connectionString: string): boolean {
|
||||
return connectionString && connectionString.includes("type=resource");
|
||||
}
|
||||
|
||||
private static _renewExplorerAccessWithResourceToken = (
|
||||
explorer: Explorer,
|
||||
connectionString: string
|
||||
): Q.Promise<void> => {
|
||||
window.authType = AuthType.ResourceToken;
|
||||
|
||||
const properties: resourceTokenConnectionStringProperties = Main.parseResourceTokenConnectionString(
|
||||
connectionString
|
||||
);
|
||||
if (
|
||||
!properties.accountEndpoint ||
|
||||
!properties.resourceToken ||
|
||||
!properties.databaseId ||
|
||||
!properties.collectionId
|
||||
) {
|
||||
console.error("Invalid connection string input");
|
||||
Q.reject("Invalid connection string input");
|
||||
}
|
||||
updateUserContext({
|
||||
resourceToken: properties.resourceToken,
|
||||
endpoint: properties.accountEndpoint
|
||||
});
|
||||
explorer.resourceTokenDatabaseId(properties.databaseId);
|
||||
explorer.resourceTokenCollectionId(properties.collectionId);
|
||||
if (properties.partitionKey) {
|
||||
explorer.resourceTokenPartitionKey(properties.partitionKey);
|
||||
}
|
||||
Main._accessInputMetadata = Main._getAccessInputMetadataFromAccountEndpoint(properties.accountEndpoint);
|
||||
|
||||
if (explorer.isConnectExplorerVisible()) {
|
||||
explorer.notificationConsoleData([]);
|
||||
explorer.hideConnectExplorerForm();
|
||||
}
|
||||
|
||||
Main._setExplorerReady(explorer);
|
||||
return Q.resolve();
|
||||
};
|
||||
|
||||
private static _getAccessInputMetadataFromAccountEndpoint = (accountEndpoint: string): AccessInputMetadata => {
|
||||
const documentEndpoint: string = accountEndpoint;
|
||||
const result: RegExpMatchArray = accountEndpoint.match("https://([^\\.]+)\\..+");
|
||||
const accountName: string = result && result[1];
|
||||
const apiEndpoint: string = accountEndpoint.substring(8);
|
||||
const apiKind: number = ApiKind.SQL;
|
||||
|
||||
return {
|
||||
accountName,
|
||||
apiEndpoint,
|
||||
apiKind,
|
||||
documentEndpoint,
|
||||
expiryTimestamp: ""
|
||||
};
|
||||
};
|
||||
|
||||
private static _setExplorerReady(
|
||||
explorer: Explorer,
|
||||
masterKey?: string,
|
||||
account?: DatabaseAccount,
|
||||
authorizationToken?: string
|
||||
) {
|
||||
Main._initDataExplorerFrameInputs(explorer, masterKey, account, authorizationToken);
|
||||
explorer.isAccountReady.valueHasMutated();
|
||||
sendMessage("ready");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user