diff --git a/src/renderer/before-frame-starts/runnables/configure-immer.injectable.ts b/src/renderer/before-frame-starts/runnables/configure-immer.injectable.ts new file mode 100644 index 0000000000..2159983f2b --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/configure-immer.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { setAutoFreeze, enableMapSet } from "immer"; +import { evenBeforeFrameStartsInjectionToken } from "../tokens"; + +const configureImmerInjectable = getInjectable({ + id: "configure-immer", + instantiate: () => ({ + id: "configure-immer", + run: () => { + // Docs: https://immerjs.github.io/immer/ + // Required in `utils/storage-helper.ts` + setAutoFreeze(false); // allow to merge mobx observables + enableMapSet(); // allow to merge maps and sets + }, + }), + injectionToken: evenBeforeFrameStartsInjectionToken, +}); + +export default configureImmerInjectable; diff --git a/src/renderer/before-frame-starts/runnables/configure-mobx.injectable.ts b/src/renderer/before-frame-starts/runnables/configure-mobx.injectable.ts new file mode 100644 index 0000000000..51a7899801 --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/configure-mobx.injectable.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { configure } from "mobx"; +import { evenBeforeFrameStartsInjectionToken } from "../tokens"; + +const configureMobxInjectable = getInjectable({ + id: "configure-mobx", + instantiate: () => ({ + id: "configure-mobx", + run: () => { + // Docs: https://mobx.js.org/configuration.html + configure({ + enforceActions: "never", + + // TODO: enable later (read more: https://mobx.js.org/migrating-from-4-or-5.html) + // computedRequiresReaction: true, + // reactionRequiresObservable: true, + // observableRequiresReaction: true, + }); + }, + }), + injectionToken: evenBeforeFrameStartsInjectionToken, +}); + +export default configureMobxInjectable; diff --git a/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts new file mode 100644 index 0000000000..09f4813477 --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/load-monaco-themes.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { customMonacoThemeInjectionToken } from "../../components/monaco-editor"; +import addNewMonacoThemeInjectable from "../../monaco/add-new-theme.injectable"; +import { evenBeforeFrameStartsInjectionToken } from "../tokens"; + +const loadMonacoThemesInjectable = getInjectable({ + id: "load-monaco-themes", + instantiate: (di) => { + const customThemes = di.injectMany(customMonacoThemeInjectionToken); + const addNewMonacoTheme = di.inject(addNewMonacoThemeInjectable); + + return { + id: "load-monaco-themes", + run: () => { + customThemes.forEach(addNewMonacoTheme); + }, + }; + }, + injectionToken: evenBeforeFrameStartsInjectionToken, +}); + +export default loadMonacoThemesInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts new file mode 100644 index 0000000000..739251590e --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/setup-auto-registration.injectable.ts @@ -0,0 +1,84 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable"; +import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable"; +import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resource.store"; +import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints"; +import { KubeApi } from "../../../common/k8s-api/kube-api"; +import { KubeObject } from "../../../common/k8s-api/kube-object"; +import type { KubeObjectStoreDependencies } from "../../../common/k8s-api/kube-object.store"; +import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable"; +import { evenBeforeClusterFrameStartsInjectionToken } from "../tokens"; + +const setupAutoRegistrationInjectable = getInjectable({ + id: "setup-auto-registration", + instantiate: (di) => ({ + id: "setup-auto-registration", + run: () => { + const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable); + const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = []; + const beforeApiManagerInitializationApis: KubeApi[] = []; + const deps: KubeObjectStoreDependencies = { + context: di.inject(clusterFrameContextForNamespacedResourcesInjectable), + }; + let initialized = false; + + const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => { + const objectConstructor = class extends KubeObject { + static readonly kind = crd.getResourceKind(); + static readonly namespaced = crd.isNamespaced(); + static readonly apiBase = crd.getResourceApiBase(); + }; + + const api = (() => { + const rawApi = apiManager.getApi(objectConstructor.apiBase); + + if (rawApi) { + return rawApi; + } + + const api = new KubeApi({ objectConstructor }); + + apiManager.registerApi(api); + + return api; + })(); + + if (!apiManager.getStore(api)) { + apiManager.registerStore(new CustomResourceStore(deps, api)); + } + }; + const autoInitKubeApi = (api: KubeApi) => { + apiManager.registerApi(api); + }; + + autoRegistrationEmitter + .on("customResourceDefinition", (crd) => { + if (initialized) { + autoInitCustomResourceStore(crd); + } else { + beforeApiManagerInitializationCrds.push(crd); + } + }) + .on("kubeApi", (api) => { + if (initialized) { + autoInitKubeApi(api); + } else { + beforeApiManagerInitializationApis.push(api); + } + }); + + const apiManager = di.inject(apiManagerInjectable); + + beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore); + beforeApiManagerInitializationApis.forEach(autoInitKubeApi); + initialized = true; + }, + }), + injectionToken: evenBeforeClusterFrameStartsInjectionToken, +}); + +export default setupAutoRegistrationInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts new file mode 100644 index 0000000000..4fe8a7d266 --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/setup-current-cluster-broadcast.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { reaction } from "mobx"; +import { currentClusterMessageChannel } from "../../../common/cluster/current-cluster-channel"; +import { sendMessageToChannelInjectionToken } from "../../../common/utils/channel/message-to-channel-injection-token"; +import matchedClusterIdInjectable from "../../navigation/matched-cluster-id.injectable"; +import { evenBeforeMainFrameStartsInjectionToken } from "../tokens"; + +const setupCurrentClusterBroadcastInjectable = getInjectable({ + id: "setup-current-cluster-broadcast", + instantiate: (di) => { + const matchedClusterId = di.inject(matchedClusterIdInjectable); + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + + return { + id: "setup-current-cluster-broadcast", + run: () => { + reaction( + () => matchedClusterId.get(), + clusterId => sendMessageToChannel(currentClusterMessageChannel, clusterId), + { + fireImmediately: true, + }, + ); + }, + }; + }, + injectionToken: evenBeforeMainFrameStartsInjectionToken, +}); + +export default setupCurrentClusterBroadcastInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts new file mode 100644 index 0000000000..fa2d00ab27 --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/setup-root-mac-class.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import isMacInjectable from "../../../common/vars/is-mac.injectable"; +import { beforeFrameStartsInjectionToken } from "../tokens"; + +const setupRootMacClassnameInjectable = getInjectable({ + id: "setup-root-mac-classname", + instantiate: (di) => { + const isMac = di.inject(isMacInjectable); + + return { + id: "setup-root-mac-classname", + run: () => { + const rootElem = document.getElementById("app"); + + rootElem?.classList.toggle("is-mac", isMac); + }, + }; + }, + injectionToken: beforeFrameStartsInjectionToken, +}); + +export default setupRootMacClassnameInjectable; diff --git a/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts new file mode 100644 index 0000000000..f45c877736 --- /dev/null +++ b/src/renderer/before-frame-starts/runnables/setup-sentry.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import initializeSentryReportingWithInjectable from "../../../common/error-reporting/initialize-sentry-reporting.injectable"; +import { evenBeforeMainFrameStartsInjectionToken } from "../tokens"; +import { init } from "@sentry/electron/renderer"; + +const setupSentryInjectable = getInjectable({ + id: "setup-sentry", + instantiate: (di) => { + const initializeSentryReportingWith = di.inject(initializeSentryReportingWithInjectable); + + return { + id: "setup-sentry", + run: () => { + initializeSentryReportingWith(init); + }, + }; + }, + injectionToken: evenBeforeMainFrameStartsInjectionToken, +}); + +export default setupSentryInjectable; diff --git a/src/renderer/before-frame-starts/tokens.ts b/src/renderer/before-frame-starts/tokens.ts new file mode 100644 index 0000000000..47307a26cb --- /dev/null +++ b/src/renderer/before-frame-starts/tokens.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { Runnable } from "../../common/runnable/run-many-for"; + +export const evenBeforeFrameStartsInjectionToken = getInjectionToken({ + id: "even-before-frame-starts", +}); + +// NOTE: these are only run when process.isMainFrame === true +export const evenBeforeMainFrameStartsInjectionToken = getInjectionToken({ + id: "even-before-main-frame-starts", +}); + +// NOTE: these are only run when process.isMainFrame === false +export const evenBeforeClusterFrameStartsInjectionToken = getInjectionToken({ + id: "even-before-cluster-frame-starts", +}); + +export const beforeFrameStartsInjectionToken = getInjectionToken({ + id: "before-frame-starts", +}); + diff --git a/src/renderer/bootstrap.tsx b/src/renderer/bootstrap.tsx index 57e1297ced..fa1b8f224d 100644 --- a/src/renderer/bootstrap.tsx +++ b/src/renderer/bootstrap.tsx @@ -13,13 +13,8 @@ import * as ReactRouter from "react-router"; import * as ReactRouterDom from "react-router-dom"; import * as LensExtensionsCommonApi from "../extensions/common-api"; import * as LensExtensionsRendererApi from "../extensions/renderer-api"; -import { delay } from "../common/utils"; -import { isMac, isDevelopment } from "../common/vars"; import { DefaultProps } from "./mui-base-theme"; -import configurePackages from "../common/configure-packages"; import * as initializers from "./initializers"; -import logger from "../common/logger"; -import { registerCustomThemes } from "./components/monaco-editor"; import { getDi } from "./getDi"; import { DiContextProvider } from "@ogre-tools/injectable-react"; import type { DiContainer } from "@ogre-tools/injectable"; @@ -36,60 +31,32 @@ import themeStoreInjectable from "./themes/store.injectable"; import navigateToAddClusterInjectable from "../common/front-end-routing/routes/add-cluster/navigate-to-add-cluster.injectable"; import addSyncEntriesInjectable from "./initializers/add-sync-entries.injectable"; import hotbarStoreInjectable from "../common/hotbars/store.injectable"; -import { bindEvents } from "./navigation/events"; import openDeleteClusterDialogInjectable from "./components/delete-cluster-dialog/open.injectable"; import kubernetesClusterCategoryInjectable from "../common/catalog/categories/kubernetes-cluster.injectable"; -import autoRegistrationInjectable from "../common/k8s-api/api-manager/auto-registration.injectable"; import assert from "assert"; import startFrameInjectable from "./start-frame/start-frame.injectable"; - -configurePackages(); // global packages -registerCustomThemes(); // monaco editor themes - -/** - * If this is a development build, wait a second to attach - * Chrome Debugger to renderer process - * https://stackoverflow.com/questions/52844870/debugging-electron-renderer-process-with-vscode - */ -async function attachChromeDebugger() { - if (isDevelopment) { - await delay(1000); - } -} +import loggerInjectable from "../common/logger.injectable"; +import isLinuxInjectable from "../common/vars/is-linux.injectable"; +import isWindowsInjectable from "../common/vars/is-windows.injectable"; export async function bootstrap(di: DiContainer) { const startFrame = di.inject(startFrameInjectable); + const logger = di.inject(loggerInjectable); await startFrame(); - // TODO: Consolidate import time side-effect to setup time - bindEvents(); - const rootElem = document.getElementById("app"); const logPrefix = `[BOOTSTRAP-${process.isMainFrame ? "ROOT" : "CLUSTER"}-FRAME]:`; assert(rootElem, "#app MUST exist"); - - /** - * This is injected here to initialize it for the side effect. - * - * The side effect CANNOT be within `apiManagerInjectable` itself since that causes circular - * dependencies with the current need for legacy di use. - * - * This also MUST be done before anything else so that it can start listening for the events for - * auto initialization. - */ - di.inject(autoRegistrationInjectable); - - await attachChromeDebugger(); - rootElem.classList.toggle("is-mac", isMac); - logger.info(`${logPrefix} initializing CatalogCategoryRegistryEntries`); initializers.initCatalogCategoryRegistryEntries({ navigateToAddCluster: di.inject(navigateToAddClusterInjectable), addSyncEntries: di.inject(addSyncEntriesInjectable), kubernetesClusterCategory: di.inject(kubernetesClusterCategoryInjectable), + isLinux: di.inject(isLinuxInjectable), + isWindows: di.inject(isWindowsInjectable), }); logger.info(`${logPrefix} initializing Catalog`); diff --git a/src/renderer/initializers/catalog-category-registry.tsx b/src/renderer/initializers/catalog-category-registry.tsx index 5b93b32644..07f44e3d15 100644 --- a/src/renderer/initializers/catalog-category-registry.tsx +++ b/src/renderer/initializers/catalog-category-registry.tsx @@ -4,19 +4,22 @@ */ import type { KubernetesClusterCategory } from "../../common/catalog-entities"; -import { isLinux, isWindows } from "../../common/vars"; import { PathPicker } from "../components/path-picker"; interface Dependencies { navigateToAddCluster: () => void; addSyncEntries: (filePaths: string[]) => void; kubernetesClusterCategory: KubernetesClusterCategory; + isWindows: boolean; + isLinux: boolean; } export function initCatalogCategoryRegistryEntries({ navigateToAddCluster, addSyncEntries, kubernetesClusterCategory, + isWindows, + isLinux, } : Dependencies) { kubernetesClusterCategory.on("catalogAddMenu", ctx => { ctx.menuItems.push(