diff --git a/src/ConfigContext.ts b/src/ConfigContext.ts
index 2ca4b9ada..18a1f1fa0 100644
--- a/src/ConfigContext.ts
+++ b/src/ConfigContext.ts
@@ -4,7 +4,7 @@ export enum Platform {
Emulator = "Emulator",
}
-interface ConfigContext {
+export interface ConfigContext {
platform: Platform;
allowedParentFrameOrigins: string[];
gitSha?: string;
diff --git a/src/Contracts/ExplorerContracts.ts b/src/Contracts/ExplorerContracts.ts
index e9ed8a322..1689a96b5 100644
--- a/src/Contracts/ExplorerContracts.ts
+++ b/src/Contracts/ExplorerContracts.ts
@@ -33,7 +33,6 @@ export enum MessageTypes {
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
- InitTestExplorer,
}
export { Versions, ActionContracts, Diagnostics };
diff --git a/src/Explorer/Explorer.ts b/src/Explorer/Explorer.ts
index 8c7ebd2e3..33c557106 100644
--- a/src/Explorer/Explorer.ts
+++ b/src/Explorer/Explorer.ts
@@ -1718,58 +1718,7 @@ export default class Explorer {
this._addSynapseLinkDialogProps.valueHasMutated();
};
- private _shouldProcessMessage(event: MessageEvent): boolean {
- if (typeof event.data !== "object") {
- return false;
- }
- if (event.data["signature"] !== "pcIframe") {
- return false;
- }
- if (!("data" in event.data)) {
- return false;
- }
- if (typeof event.data["data"] !== "object") {
- return false;
- }
-
- // before initialization completed give exception
- const message = event.data.data;
- if (!this._importExplorerConfigComplete && message && message.type) {
- const messageType = message.type;
- switch (messageType) {
- case MessageTypes.SendNotification:
- case MessageTypes.ClearNotification:
- case MessageTypes.LoadingStatus:
- case MessageTypes.InitTestExplorer:
- return true;
- }
- }
- if (!("inputs" in event.data["data"]) && !this._importExplorerConfigComplete) {
- return false;
- }
- return true;
- }
-
- public handleMessage(event: MessageEvent) {
- if (isInvalidParentFrameOrigin(event)) {
- return;
- }
-
- if (!this._shouldProcessMessage(event)) {
- return;
- }
-
- const message: any = event.data.data;
- const inputs: ViewModels.DataExplorerInputsFrame = message.inputs;
-
- const isRunningInPortal = configContext.platform === Platform.Portal;
- const isRunningInDevMode = process.env.NODE_ENV === "development";
- if (inputs && configContext.BACKEND_ENDPOINT && isRunningInPortal && isRunningInDevMode) {
- inputs.extensionEndpoint = configContext.PROXY_PATH;
- }
-
- this.initDataExplorerWithFrameInputs(inputs);
-
+ public handleMessage(message: any) {
const openAction: ActionContracts.DataExplorerAction = message.openAction;
if (!!openAction) {
if (this.isRefreshingExplorer()) {
@@ -1874,7 +1823,7 @@ export default class Explorer {
}
}
- public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): void {
+ public configure(inputs: ViewModels.DataExplorerInputsFrame): void {
if (inputs != null) {
// In development mode, save the iframe message from the portal in session storage.
// This allows webpack hot reload to funciton properly
diff --git a/src/HostedExplorerChildFrame.ts b/src/HostedExplorerChildFrame.ts
index 70497fb2c..2cff6c862 100644
--- a/src/HostedExplorerChildFrame.ts
+++ b/src/HostedExplorerChildFrame.ts
@@ -1,17 +1,18 @@
import { AuthType } from "./AuthType";
import { AccessInputMetadata, DatabaseAccount } from "./Contracts/DataModels";
+type HostedConfig = AAD | ConnectionString | EncryptedToken | ResourceToken;
export interface HostedExplorerChildFrame extends Window {
- hostedConfig: AAD | ConnectionString | EncryptedToken | ResourceToken;
+ hostedConfig: HostedConfig;
}
-interface AAD {
+export interface AAD {
authType: AuthType.AAD;
databaseAccount: DatabaseAccount;
authorizationToken: string;
}
-interface ConnectionString {
+export interface ConnectionString {
authType: AuthType.ConnectionString;
// Connection string uses still use encrypted token for Cassandra/Mongo APIs as they us the portal backend proxy
encryptedToken: string;
@@ -20,13 +21,13 @@ interface ConnectionString {
masterKey?: string;
}
-interface EncryptedToken {
+export interface EncryptedToken {
authType: AuthType.EncryptedToken;
encryptedToken: string;
encryptedTokenMetadata: AccessInputMetadata;
}
-interface ResourceToken {
+export interface ResourceToken {
authType: AuthType.ResourceToken;
resourceToken: string;
}
diff --git a/src/Main.tsx b/src/Main.tsx
index 955287fd9..ad4a9caaa 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -53,215 +53,22 @@ import "object.entries/auto";
import "./Libs/is-integer-polyfill";
import "url-polyfill/url-polyfill.min";
-initializeIcons();
-
-import { AuthType } from "./AuthType";
-
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
-import { applyExplorerBindings } from "./applyExplorerBindings";
-import { configContext, initializeConfiguration, Platform } from "./ConfigContext";
-import Explorer from "./Explorer/Explorer";
-import React, { useEffect } from "react";
+import React from "react";
import ReactDOM from "react-dom";
import copyImage from "../images/Copy.svg";
import hdeConnectImage from "../images/HdeConnectCosmosDB.svg";
import refreshImg from "../images/refresh-cosmos.svg";
import arrowLeftImg from "../images/imgarrowlefticon.svg";
import { KOCommentEnd, KOCommentIfStart } from "./koComment";
-import { updateUserContext } from "./UserContext";
-import { CollectionCreation } from "./Shared/Constants";
-import { extractFeatures } from "./Platform/Hosted/extractFeatures";
-import { emulatorAccount } from "./Platform/Emulator/emulatorAccount";
-import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
-import {
- getDatabaseAccountKindFromExperience,
- getDatabaseAccountPropertiesFromMetadata,
-} from "./Platform/Hosted/HostedUtils";
-import { DefaultExperienceUtility } from "./Shared/DefaultExperienceUtility";
-import { parseResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils";
-import { AccountKind, DefaultAccountExperience, ServerIds } from "./Common/Constants";
-import { listKeys } from "./Utils/arm/generatedClients/2020-04-01/databaseAccounts";
-import { SelfServeType } from "./SelfServe/SelfServeUtils";
+import { useConfig } from "./hooks/useConfig";
+import { useKnockoutExplorer } from "./hooks/useKnockoutExplorer";
+
+initializeIcons();
const App: React.FunctionComponent = () => {
- useEffect(() => {
- initializeConfiguration().then(async (config) => {
- let explorer: Explorer;
- if (config.platform === Platform.Hosted) {
- const win = (window as unknown) as HostedExplorerChildFrame;
- explorer = new Explorer();
- explorer.selfServeType(SelfServeType.none);
- if (win.hostedConfig.authType === AuthType.EncryptedToken) {
- // TODO: Remove window.authType
- window.authType = AuthType.EncryptedToken;
- // Impossible to tell if this is a try cosmos sub using an encrypted token
- explorer.isTryCosmosDBSubscription(false);
- updateUserContext({
- accessToken: encodeURIComponent(win.hostedConfig.encryptedToken),
- });
-
- const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
- win.hostedConfig.encryptedTokenMetadata.apiKind
- );
- explorer.initDataExplorerWithFrameInputs({
- databaseAccount: {
- id: "",
- // id: Main._databaseAccountId,
- name: win.hostedConfig.encryptedTokenMetadata.accountName,
- kind: getDatabaseAccountKindFromExperience(apiExperience),
- properties: getDatabaseAccountPropertiesFromMetadata(win.hostedConfig.encryptedTokenMetadata),
- tags: [],
- },
- subscriptionId: undefined,
- resourceGroup: undefined,
- masterKey: undefined,
- hasWriteAccess: true, // TODO: we should embed this information in the token ideally
- authorizationToken: undefined,
- features: extractFeatures(),
- csmEndpoint: undefined,
- dnsSuffix: undefined,
- serverId: ServerIds.productionPortal,
- extensionEndpoint: configContext.BACKEND_ENDPOINT,
- subscriptionType: CollectionCreation.DefaultSubscriptionType,
- quotaId: undefined,
- addCollectionDefaultFlight: explorer.flight(),
- isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
- });
- explorer.isAccountReady(true);
- } else if (win.hostedConfig.authType === AuthType.ResourceToken) {
- window.authType = AuthType.ResourceToken;
- // Resource tokens can only be used with SQL API
- const apiExperience: string = DefaultAccountExperience.DocumentDB;
- const parsedResourceToken = parseResourceTokenConnectionString(win.hostedConfig.resourceToken);
- updateUserContext({
- resourceToken: parsedResourceToken.resourceToken,
- endpoint: parsedResourceToken.accountEndpoint,
- });
- explorer.resourceTokenDatabaseId(parsedResourceToken.databaseId);
- explorer.resourceTokenCollectionId(parsedResourceToken.collectionId);
- if (parsedResourceToken.partitionKey) {
- explorer.resourceTokenPartitionKey(parsedResourceToken.partitionKey);
- }
- explorer.initDataExplorerWithFrameInputs({
- databaseAccount: {
- id: "",
- name: parsedResourceToken.accountEndpoint,
- kind: AccountKind.GlobalDocumentDB,
- properties: { documentEndpoint: parsedResourceToken.accountEndpoint },
- tags: { defaultExperience: apiExperience },
- },
- subscriptionId: undefined,
- resourceGroup: undefined,
- masterKey: undefined,
- hasWriteAccess: true, // TODO: we should embed this information in the token ideally
- authorizationToken: undefined,
- features: extractFeatures(),
- csmEndpoint: undefined,
- dnsSuffix: undefined,
- serverId: ServerIds.productionPortal,
- extensionEndpoint: configContext.BACKEND_ENDPOINT,
- subscriptionType: CollectionCreation.DefaultSubscriptionType,
- quotaId: undefined,
- addCollectionDefaultFlight: explorer.flight(),
- isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
- isAuthWithresourceToken: true,
- });
- explorer.isAccountReady(true);
- explorer.isRefreshingExplorer(false);
- } else if (win.hostedConfig.authType === AuthType.ConnectionString) {
- // For legacy reasons lots of code expects a connection string login to look and act like an encrypted token login
- window.authType = AuthType.EncryptedToken;
- // Impossible to tell if this is a try cosmos sub using an encrypted token
- explorer.isTryCosmosDBSubscription(false);
- updateUserContext({
- accessToken: encodeURIComponent(win.hostedConfig.encryptedToken),
- });
-
- const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
- win.hostedConfig.encryptedTokenMetadata.apiKind
- );
- explorer.initDataExplorerWithFrameInputs({
- databaseAccount: {
- id: "",
- // id: Main._databaseAccountId,
- name: win.hostedConfig.encryptedTokenMetadata.accountName,
- kind: getDatabaseAccountKindFromExperience(apiExperience),
- properties: getDatabaseAccountPropertiesFromMetadata(win.hostedConfig.encryptedTokenMetadata),
- tags: [],
- },
- subscriptionId: undefined,
- resourceGroup: undefined,
- masterKey: win.hostedConfig.masterKey,
- hasWriteAccess: true, // TODO: we should embed this information in the token ideally
- authorizationToken: undefined,
- features: extractFeatures(),
- csmEndpoint: undefined,
- dnsSuffix: undefined,
- serverId: ServerIds.productionPortal,
- extensionEndpoint: configContext.BACKEND_ENDPOINT,
- subscriptionType: CollectionCreation.DefaultSubscriptionType,
- quotaId: undefined,
- addCollectionDefaultFlight: explorer.flight(),
- isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
- });
- explorer.isAccountReady(true);
- } else if (win.hostedConfig.authType === AuthType.AAD) {
- window.authType = AuthType.AAD;
- const account = win.hostedConfig.databaseAccount;
- const accountResourceId = account.id;
- const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
- const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
- updateUserContext({
- authorizationToken: `Bearer ${win.hostedConfig.authorizationToken}`,
- databaseAccount: win.hostedConfig.databaseAccount,
- });
- const keys = await listKeys(subscriptionId, resourceGroup, account.name);
- explorer.initDataExplorerWithFrameInputs({
- databaseAccount: account,
- subscriptionId,
- resourceGroup,
- masterKey: keys.primaryMasterKey,
- hasWriteAccess: true, //TODO: 425017 - support read access
- authorizationToken: `Bearer ${win.hostedConfig.authorizationToken}`,
- features: extractFeatures(),
- csmEndpoint: undefined,
- dnsSuffix: undefined,
- serverId: ServerIds.productionPortal,
- extensionEndpoint: configContext.BACKEND_ENDPOINT,
- subscriptionType: CollectionCreation.DefaultSubscriptionType,
- quotaId: undefined,
- addCollectionDefaultFlight: explorer.flight(),
- isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
- });
- explorer.isAccountReady(true);
- }
- } else if (config.platform === Platform.Emulator) {
- window.authType = AuthType.MasterKey;
- explorer = new Explorer();
- explorer.selfServeType(SelfServeType.none);
- explorer.databaseAccount(emulatorAccount);
- explorer.isAccountReady(true);
- } else if (config.platform === Platform.Portal) {
- window.authType = AuthType.AAD;
- explorer = new Explorer();
-
- // In development mode, try to load the iframe message from session storage.
- // This allows webpack hot reload to funciton properly
- if (process.env.NODE_ENV === "development") {
- const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
- if (initMessage) {
- const message = JSON.parse(initMessage);
- console.warn("Loaded cached portal iframe message from session storage");
- console.dir(message);
- explorer.initDataExplorerWithFrameInputs(message);
- }
- }
-
- window.addEventListener("message", explorer.handleMessage.bind(explorer), false);
- }
- applyExplorerBindings(explorer);
- });
- }, []);
+ const config = useConfig();
+ useKnockoutExplorer(config);
return (
diff --git a/src/applyExplorerBindings.ts b/src/applyExplorerBindings.ts
index 56114f1dc..ca28aea5c 100644
--- a/src/applyExplorerBindings.ts
+++ b/src/applyExplorerBindings.ts
@@ -1,5 +1,4 @@
import { BindingHandlersRegisterer } from "./Bindings/BindingHandlersRegisterer";
-import { sendMessage } from "./Common/MessageHandler";
import * as ko from "knockout";
import Explorer from "./Explorer/Explorer";
@@ -10,7 +9,6 @@ export const applyExplorerBindings = (explorer: Explorer) => {
ko.applyBindings(explorer);
// This message should ideally be sent immediately after explorer has been initialized for optimal data explorer load times.
// TODO: Send another message to describe that the bindings have been applied, and handle message transfers accordingly in the portal
- sendMessage("ready");
$("#divExplorer").show();
}
};
diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts
new file mode 100644
index 000000000..27cce3c2d
--- /dev/null
+++ b/src/hooks/useConfig.ts
@@ -0,0 +1,14 @@
+import { useEffect, useState } from "react";
+import { ConfigContext, initializeConfiguration } from "../ConfigContext";
+
+// This hook initializes global configuration from a config.json file that is injected at deploy time
+// This allows the same main Data Explorer build to be exactly the same in all clouds/platforms,
+// but override some of the configuration as nesssary
+export function useConfig(): Readonly {
+ const [state, setState] = useState();
+
+ useEffect(() => {
+ initializeConfiguration().then((response) => setState(response));
+ }, []);
+ return state;
+}
diff --git a/src/hooks/useKnockoutExplorer.ts b/src/hooks/useKnockoutExplorer.ts
new file mode 100644
index 000000000..3dce2a113
--- /dev/null
+++ b/src/hooks/useKnockoutExplorer.ts
@@ -0,0 +1,298 @@
+import { useEffect } from "react";
+import { applyExplorerBindings } from "../applyExplorerBindings";
+import { AuthType } from "../AuthType";
+import { AccountKind, DefaultAccountExperience, ServerIds } from "../Common/Constants";
+import { sendMessage } from "../Common/MessageHandler";
+import { configContext, ConfigContext, Platform } from "../ConfigContext";
+import { ActionType, DataExplorerAction } from "../Contracts/ActionContracts";
+import { MessageTypes } from "../Contracts/ExplorerContracts";
+import { DataExplorerInputsFrame } from "../Contracts/ViewModels";
+import Explorer from "../Explorer/Explorer";
+import {
+ AAD,
+ ConnectionString,
+ EncryptedToken,
+ HostedExplorerChildFrame,
+ ResourceToken,
+} from "../HostedExplorerChildFrame";
+import { emulatorAccount } from "../Platform/Emulator/emulatorAccount";
+import { extractFeatures } from "../Platform/Hosted/extractFeatures";
+import { parseResourceTokenConnectionString } from "../Platform/Hosted/Helpers/ResourceTokenUtils";
+import {
+ getDatabaseAccountKindFromExperience,
+ getDatabaseAccountPropertiesFromMetadata,
+} from "../Platform/Hosted/HostedUtils";
+import { SelfServeType } from "../SelfServe/SelfServeUtils";
+import { CollectionCreation } from "../Shared/Constants";
+import { DefaultExperienceUtility } from "../Shared/DefaultExperienceUtility";
+import { updateUserContext } from "../UserContext";
+import { listKeys } from "../Utils/arm/generatedClients/2020-04-01/databaseAccounts";
+import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
+
+// This hook will create a new instance of Explorer.ts and bind it to the DOM
+// This hook has a LOT of magic, but ideally we can delete it once we have removed KO and switched entirely to React
+// Pleas tread carefully :)
+let explorer: Explorer;
+
+export function useKnockoutExplorer(config: ConfigContext): Explorer {
+ explorer = explorer || new Explorer();
+ useEffect(() => {
+ const effect = async () => {
+ if (config) {
+ if (config.platform === Platform.Hosted) {
+ await configureHosted(config);
+ } else if (config.platform === Platform.Emulator) {
+ configureEmulator();
+ } else if (config.platform === Platform.Portal) {
+ configurePortal();
+ }
+ applyExplorerBindings(explorer);
+ }
+ };
+ effect();
+ }, [config]);
+ return explorer;
+}
+
+async function configureHosted(config: ConfigContext) {
+ const win = (window as unknown) as HostedExplorerChildFrame;
+ explorer.selfServeType(SelfServeType.none);
+ if (win.hostedConfig.authType === AuthType.EncryptedToken) {
+ configureHostedWithEncryptedToken(win.hostedConfig, config);
+ } else if (win.hostedConfig.authType === AuthType.ResourceToken) {
+ configureHostedWithResourceToken(win.hostedConfig);
+ } else if (win.hostedConfig.authType === AuthType.ConnectionString) {
+ configureHostedWithConnectionString(win.hostedConfig);
+ } else if (win.hostedConfig.authType === AuthType.AAD) {
+ await configureHostedWithAAD(win.hostedConfig);
+ }
+}
+
+async function configureHostedWithAAD(config: AAD) {
+ window.authType = AuthType.AAD;
+ const account = config.databaseAccount;
+ const accountResourceId = account.id;
+ const subscriptionId = accountResourceId && accountResourceId.split("subscriptions/")[1].split("/")[0];
+ const resourceGroup = accountResourceId && accountResourceId.split("resourceGroups/")[1].split("/")[0];
+ updateUserContext({
+ authorizationToken: `Bearer ${config.authorizationToken}`,
+ databaseAccount: config.databaseAccount,
+ });
+ const keys = await listKeys(subscriptionId, resourceGroup, account.name);
+ explorer.configure({
+ databaseAccount: account,
+ subscriptionId,
+ resourceGroup,
+ masterKey: keys.primaryMasterKey,
+ hasWriteAccess: true,
+ authorizationToken: `Bearer ${config.authorizationToken}`,
+ features: extractFeatures(),
+ csmEndpoint: undefined,
+ dnsSuffix: undefined,
+ serverId: ServerIds.productionPortal,
+ extensionEndpoint: configContext.BACKEND_ENDPOINT,
+ subscriptionType: CollectionCreation.DefaultSubscriptionType,
+ quotaId: undefined,
+ addCollectionDefaultFlight: explorer.flight(),
+ isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
+ });
+ explorer.isAccountReady(true);
+}
+
+function configureHostedWithConnectionString(config: ConnectionString) {
+ // For legacy reasons lots of code expects a connection string login to look and act like an encrypted token login
+ window.authType = AuthType.EncryptedToken;
+ // Impossible to tell if this is a try cosmos sub using an encrypted token
+ explorer.isTryCosmosDBSubscription(false);
+ updateUserContext({
+ accessToken: encodeURIComponent(config.encryptedToken),
+ });
+
+ const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
+ config.encryptedTokenMetadata.apiKind
+ );
+ explorer.configure({
+ databaseAccount: {
+ id: "",
+ // id: Main._databaseAccountId,
+ name: config.encryptedTokenMetadata.accountName,
+ kind: getDatabaseAccountKindFromExperience(apiExperience),
+ properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
+ tags: [],
+ },
+ subscriptionId: undefined,
+ resourceGroup: undefined,
+ masterKey: config.masterKey,
+ hasWriteAccess: true,
+ authorizationToken: undefined,
+ features: extractFeatures(),
+ csmEndpoint: undefined,
+ dnsSuffix: undefined,
+ serverId: ServerIds.productionPortal,
+ extensionEndpoint: configContext.BACKEND_ENDPOINT,
+ subscriptionType: CollectionCreation.DefaultSubscriptionType,
+ quotaId: undefined,
+ addCollectionDefaultFlight: explorer.flight(),
+ isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
+ });
+ explorer.isAccountReady(true);
+}
+
+function configureHostedWithResourceToken(config: ResourceToken) {
+ window.authType = AuthType.ResourceToken;
+ // Resource tokens can only be used with SQL API
+ const apiExperience: string = DefaultAccountExperience.DocumentDB;
+ const parsedResourceToken = parseResourceTokenConnectionString(config.resourceToken);
+ updateUserContext({
+ resourceToken: parsedResourceToken.resourceToken,
+ endpoint: parsedResourceToken.accountEndpoint,
+ });
+ explorer.resourceTokenDatabaseId(parsedResourceToken.databaseId);
+ explorer.resourceTokenCollectionId(parsedResourceToken.collectionId);
+ if (parsedResourceToken.partitionKey) {
+ explorer.resourceTokenPartitionKey(parsedResourceToken.partitionKey);
+ }
+ explorer.configure({
+ databaseAccount: {
+ id: "",
+ name: parsedResourceToken.accountEndpoint,
+ kind: AccountKind.GlobalDocumentDB,
+ properties: { documentEndpoint: parsedResourceToken.accountEndpoint },
+ tags: { defaultExperience: apiExperience },
+ },
+ subscriptionId: undefined,
+ resourceGroup: undefined,
+ masterKey: undefined,
+ hasWriteAccess: true,
+ authorizationToken: undefined,
+ features: extractFeatures(),
+ csmEndpoint: undefined,
+ dnsSuffix: undefined,
+ serverId: ServerIds.productionPortal,
+ extensionEndpoint: configContext.BACKEND_ENDPOINT,
+ subscriptionType: CollectionCreation.DefaultSubscriptionType,
+ quotaId: undefined,
+ addCollectionDefaultFlight: explorer.flight(),
+ isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
+ isAuthWithresourceToken: true,
+ });
+ explorer.isAccountReady(true);
+ explorer.isRefreshingExplorer(false);
+}
+
+function configureHostedWithEncryptedToken(config: EncryptedToken, configContext: ConfigContext) {
+ window.authType = AuthType.EncryptedToken;
+ // Impossible to tell if this is a try cosmos sub using an encrypted token
+ explorer.isTryCosmosDBSubscription(false);
+ updateUserContext({
+ accessToken: encodeURIComponent(config.encryptedToken),
+ });
+
+ const apiExperience: string = DefaultExperienceUtility.getDefaultExperienceFromApiKind(
+ config.encryptedTokenMetadata.apiKind
+ );
+ explorer.configure({
+ databaseAccount: {
+ id: "",
+ name: config.encryptedTokenMetadata.accountName,
+ kind: getDatabaseAccountKindFromExperience(apiExperience),
+ properties: getDatabaseAccountPropertiesFromMetadata(config.encryptedTokenMetadata),
+ tags: [],
+ },
+ subscriptionId: undefined,
+ resourceGroup: undefined,
+ masterKey: undefined,
+ hasWriteAccess: true,
+ authorizationToken: undefined,
+ features: extractFeatures(),
+ csmEndpoint: undefined,
+ dnsSuffix: undefined,
+ serverId: ServerIds.productionPortal,
+ extensionEndpoint: configContext.BACKEND_ENDPOINT,
+ subscriptionType: CollectionCreation.DefaultSubscriptionType,
+ quotaId: undefined,
+ addCollectionDefaultFlight: explorer.flight(),
+ isTryCosmosDBSubscription: explorer.isTryCosmosDBSubscription(),
+ });
+ explorer.isAccountReady(true);
+}
+
+function configureEmulator() {
+ window.authType = AuthType.MasterKey;
+ explorer.selfServeType(SelfServeType.none);
+ explorer.databaseAccount(emulatorAccount);
+ explorer.isAccountReady(true);
+}
+
+function configurePortal() {
+ window.authType = AuthType.AAD;
+ // In development mode, try to load the iframe message from session storage.
+ // This allows webpack hot reload to function properly in the portal
+ if (process.env.NODE_ENV === "development" && !window.location.search.includes("disablePortalInitCache")) {
+ const initMessage = sessionStorage.getItem("portalDataExplorerInitMessage");
+ if (initMessage) {
+ const message = JSON.parse(initMessage);
+ console.warn(
+ "Loaded cached portal iframe message from session storage. Do a full page refresh to get a new message"
+ );
+ console.dir(message);
+ explorer.configure(message);
+ }
+ }
+ // In the Portal, configuration of Explorer happens via iframe message
+ window.addEventListener(
+ "message",
+ (event) => {
+ console.dir(event);
+ if (isInvalidParentFrameOrigin(event)) {
+ return;
+ }
+
+ if (!shouldProcessMessage(event)) {
+ return;
+ }
+
+ // Check for init message
+ const message: PortalMessage = event.data?.data;
+ const inputs = message?.inputs;
+ if (inputs) {
+ if (
+ configContext.BACKEND_ENDPOINT &&
+ configContext.platform === Platform.Portal &&
+ process.env.NODE_ENV === "development"
+ ) {
+ inputs.extensionEndpoint = configContext.PROXY_PATH;
+ }
+
+ explorer.configure(inputs);
+ }
+ },
+ false
+ );
+
+ sendMessage("ready");
+}
+
+function shouldProcessMessage(event: MessageEvent): boolean {
+ if (typeof event.data !== "object") {
+ return false;
+ }
+ if (event.data["signature"] !== "pcIframe") {
+ return false;
+ }
+ if (!("data" in event.data)) {
+ return false;
+ }
+ if (typeof event.data["data"] !== "object") {
+ return false;
+ }
+
+ return true;
+}
+
+interface PortalMessage {
+ openAction?: DataExplorerAction;
+ actionType?: ActionType;
+ type?: MessageTypes;
+ inputs?: DataExplorerInputsFrame;
+}
diff --git a/test/testExplorer/TestExplorer.ts b/test/testExplorer/TestExplorer.ts
index fec9c10b2..612ae2f64 100644
--- a/test/testExplorer/TestExplorer.ts
+++ b/test/testExplorer/TestExplorer.ts
@@ -1,7 +1,5 @@
-import { MessageTypes } from "../../src/Contracts/ExplorerContracts";
import "../../less/hostedexplorer.less";
import { TestExplorerParams } from "./TestExplorerParams";
-import { ClientSecretCredential } from "@azure/identity";
import { DatabaseAccountsGetResponse } from "@azure/arm-cosmosdb/esm/models";
import { CosmosDBManagementClient } from "@azure/arm-cosmosdb";
import * as msRest from "@azure/ms-rest-js";
@@ -19,26 +17,6 @@ class CustomSigner implements msRest.ServiceClientCredentials {
}
}
-const handleMessage = (event: MessageEvent): void => {
- if (event.data.type === MessageTypes.InitTestExplorer) {
- sendMessageToExplorerFrame(event.data);
- }
-};
-
-const AADLogin = async (
- notebooksTestRunnerApplicationId: string,
- notebooksTestRunnerClientId: string,
- notebooksTestRunnerClientSecret: string
-): Promise => {
- const credentials = new ClientSecretCredential(
- notebooksTestRunnerApplicationId,
- notebooksTestRunnerClientId,
- notebooksTestRunnerClientSecret
- );
- const token = await credentials.getToken("https://management.core.windows.net/.default");
- return token.token;
-};
-
const getDatabaseAccount = async (
token: string,
notebooksAccountSubscriptonId: string,
@@ -49,34 +27,8 @@ const getDatabaseAccount = async (
return client.databaseAccounts.get(notebooksAccountResourceGroup, notebooksAccountName);
};
-const sendMessageToExplorerFrame = (data: unknown): void => {
- const explorerFrame = document.getElementById("explorerMenu") as HTMLIFrameElement;
-
- explorerFrame &&
- explorerFrame.contentDocument &&
- explorerFrame.contentDocument.referrer &&
- explorerFrame.contentWindow.postMessage(
- {
- signature: "pcIframe",
- data: data,
- },
- explorerFrame.contentDocument.referrer || window.location.href
- );
-};
-
const initTestExplorer = async (): Promise => {
- window.addEventListener("message", handleMessage, false);
-
const urlSearchParams = new URLSearchParams(window.location.search);
- const notebooksTestRunnerTenantId = decodeURIComponent(
- urlSearchParams.get(TestExplorerParams.notebooksTestRunnerTenantId)
- );
- const notebooksTestRunnerClientId = decodeURIComponent(
- urlSearchParams.get(TestExplorerParams.notebooksTestRunnerClientId)
- );
- const notebooksTestRunnerClientSecret = decodeURIComponent(
- urlSearchParams.get(TestExplorerParams.notebooksTestRunnerClientSecret)
- );
const portalRunnerDatabaseAccount = decodeURIComponent(
urlSearchParams.get(TestExplorerParams.portalRunnerDatabaseAccount)
);
@@ -89,11 +41,7 @@ const initTestExplorer = async (): Promise => {
);
const selfServeType = urlSearchParams.get(TestExplorerParams.selfServeType);
- const token = await AADLogin(
- notebooksTestRunnerTenantId,
- notebooksTestRunnerClientId,
- notebooksTestRunnerClientSecret
- );
+ const token = decodeURIComponent(urlSearchParams.get(TestExplorerParams.token));
const databaseAccount = await getDatabaseAccount(
token,
portalRunnerSubscripton,
@@ -102,7 +50,6 @@ const initTestExplorer = async (): Promise => {
);
const initTestExplorerContent = {
- type: MessageTypes.InitTestExplorer,
inputs: {
databaseAccount: databaseAccount,
subscriptionId: portalRunnerSubscripton,
@@ -130,11 +77,35 @@ const initTestExplorer = async (): Promise => {
},
// add UI test only when feature is not dependent on flights anymore
flights: [],
- selfServeType: selfServeType,
+ selfServeType,
} as ViewModels.DataExplorerInputsFrame,
};
- window.postMessage(initTestExplorerContent, window.location.href);
+ const iframe = document.createElement("iframe");
+ window.addEventListener(
+ "message",
+ (event) => {
+ // After we have received the "ready" message from the child iframe we can post configuration
+ // This simulates the same action that happens in the portal
+ console.dir(event.data);
+ if (event.data?.data === "ready") {
+ iframe.contentWindow.postMessage(
+ {
+ signature: "pcIframe",
+ data: initTestExplorerContent,
+ },
+ iframe.contentDocument.referrer || window.location.href
+ );
+ }
+ },
+ false
+ );
+ iframe.id = "explorerMenu";
+ iframe.name = "explorer";
+ iframe.classList.add("iframe");
+ iframe.title = "explorer";
+ iframe.src = "explorer.html?platform=Portal&disablePortalInitCache";
+ document.body.appendChild(iframe);
};
-window.addEventListener("load", initTestExplorer);
+initTestExplorer();
diff --git a/test/testExplorer/TestExplorerParams.ts b/test/testExplorer/TestExplorerParams.ts
index bcf65b458..43e7c28f1 100644
--- a/test/testExplorer/TestExplorerParams.ts
+++ b/test/testExplorer/TestExplorerParams.ts
@@ -1,10 +1,8 @@
export enum TestExplorerParams {
- notebooksTestRunnerTenantId = "notebooksTestRunnerTenantId",
- notebooksTestRunnerClientId = "notebooksTestRunnerClientId",
- notebooksTestRunnerClientSecret = "notebooksTestRunnerClientSecret",
portalRunnerDatabaseAccount = "portalRunnerDatabaseAccount",
portalRunnerDatabaseAccountKey = "portalRunnerDatabaseAccountKey",
portalRunnerSubscripton = "portalRunnerSubscripton",
portalRunnerResourceGroup = "portalRunnerResourceGroup",
selfServeType = "selfServeType",
+ token = "token",
}
diff --git a/test/testExplorer/TestExplorerUtils.ts b/test/testExplorer/TestExplorerUtils.ts
index be3728e6e..f26bc2778 100644
--- a/test/testExplorer/TestExplorerUtils.ts
+++ b/test/testExplorer/TestExplorerUtils.ts
@@ -1,5 +1,6 @@
import { Frame } from "puppeteer";
import { TestExplorerParams } from "./TestExplorerParams";
+import { ClientSecretCredential } from "@azure/identity";
let testExplorerFrame: Frame;
export const getTestExplorerFrame = async (params?: Map): Promise => {
@@ -15,19 +16,15 @@ export const getTestExplorerFrame = async (params?: Map): Promis
const portalRunnerSubscripton = process.env.PORTAL_RUNNER_SUBSCRIPTION;
const portalRunnerResourceGroup = process.env.PORTAL_RUNNER_RESOURCE_GROUP;
+ const credentials = new ClientSecretCredential(
+ notebooksTestRunnerTenantId,
+ notebooksTestRunnerClientId,
+ notebooksTestRunnerClientSecret
+ );
+
+ const { token } = await credentials.getToken("https://management.core.windows.net/.default");
+
const testExplorerUrl = new URL("testExplorer.html", "https://localhost:1234");
- testExplorerUrl.searchParams.append(
- TestExplorerParams.notebooksTestRunnerTenantId,
- encodeURI(notebooksTestRunnerTenantId)
- );
- testExplorerUrl.searchParams.append(
- TestExplorerParams.notebooksTestRunnerClientId,
- encodeURI(notebooksTestRunnerClientId)
- );
- testExplorerUrl.searchParams.append(
- TestExplorerParams.notebooksTestRunnerClientSecret,
- encodeURI(notebooksTestRunnerClientSecret)
- );
testExplorerUrl.searchParams.append(
TestExplorerParams.portalRunnerDatabaseAccount,
encodeURI(portalRunnerDatabaseAccount)
@@ -41,6 +38,7 @@ export const getTestExplorerFrame = async (params?: Map): Promis
TestExplorerParams.portalRunnerResourceGroup,
encodeURI(portalRunnerResourceGroup)
);
+ testExplorerUrl.searchParams.append(TestExplorerParams.token, encodeURI(token));
if (params) {
for (const key of params.keys()) {
diff --git a/test/testExplorer/testExplorer.html b/test/testExplorer/testExplorer.html
index d9a0c8184..92632d41b 100644
--- a/test/testExplorer/testExplorer.html
+++ b/test/testExplorer/testExplorer.html
@@ -6,13 +6,5 @@
-
-
-
+