diff --git a/.eslintignore b/.eslintignore index 8d665326b..fee7d9f6b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx b/src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx index 32215146e..48530a85e 100644 --- a/src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx +++ b/src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.tsx @@ -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(cachedSubscriptionId); const accounts = useDatabaseAccounts(selectedSubscriptionId, armToken); - const [selectedAccountName, setSelectedAccoutName] = React.useState(); + const [selectedAccountName, setSelectedAccoutName] = React.useState(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" } diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts index 58490c8d1..631638e65 100644 --- a/src/Explorer/Explorer.ts +++ b/src/Explorer/Explorer.ts @@ -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)); } diff --git a/src/HostedExplorer.tsx b/src/HostedExplorer.tsx index 7aa713f3e..69b335f1d 100644 --- a/src/HostedExplorer.tsx +++ b/src/HostedExplorer.tsx @@ -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(); const [armToken, setArmToken] = React.useState(); const [connectionString, setConnectionString] = React.useState(""); + const [databaseAccount, setDatabaseAccount] = React.useState(); + + const ref = React.useRef(); 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 && ( - + )} {!isLoggedIn && encryptedTokenMetadata?.accountName && ( @@ -202,26 +226,22 @@ const App: React.FunctionComponent = () => { - {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. )} - {/* {!encryptedTokenMetadata && isLoggedIn && ( - - )} */} {!isLoggedIn && !encryptedTokenMetadata && (
diff --git a/src/Main.tsx b/src/Main.tsx index a63f4ed38..a06fb8478 100644 --- a/src/Main.tsx +++ b/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" /> { 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 }); }); diff --git a/src/Platform/Hosted/HostedUtils.ts b/src/Platform/Hosted/HostedUtils.ts index 601ab5895..828ce7bf1 100644 --- a/src/Platform/Hosted/HostedUtils.ts +++ b/src/Platform/Hosted/HostedUtils.ts @@ -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; } diff --git a/src/Platform/Hosted/Main.ts b/src/Platform/Hosted/Main.ts index a23568f6b..5d722c650 100644 --- a/src/Platform/Hosted/Main.ts +++ b/src/Platform/Hosted/Main.ts @@ -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 => { - 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 = Q.defer(); - 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 { - 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 => { - 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"); - } }