From 9284741df51d7e68ca46c21d34277d4677f5f368 Mon Sep 17 00:00:00 2001 From: Juho Heikka Date: Thu, 23 Dec 2021 11:00:18 +0200 Subject: [PATCH] Show splash window until bundled extensions have been loaded (#4570) * Show splash window until bundled extensions have been loaded Signed-off-by: Juho Heikka * Remove unnecessary variable. Signed-off-by: Juho Heikka * Refactor autoInitExtensions Signed-off-by: Juho Heikka * Add timeout to waiting for bundled extensions to load Signed-off-by: Juho Heikka --- src/common/ipc/extension-loader.ipc.ts | 21 +++++++++++++++++++ src/common/ipc/index.ts | 1 + .../extension-loader/extension-loader.ts | 18 +++++++++++++--- src/main/window-manager.ts | 4 ++-- src/renderer/root-frame.tsx | 18 ++++++++++++++-- 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/common/ipc/extension-loader.ipc.ts diff --git a/src/common/ipc/extension-loader.ipc.ts b/src/common/ipc/extension-loader.ipc.ts new file mode 100644 index 0000000000..dae1c72316 --- /dev/null +++ b/src/common/ipc/extension-loader.ipc.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +export const BundledExtensionsLoaded = "extension-loader:bundled-extensions-loaded"; diff --git a/src/common/ipc/index.ts b/src/common/ipc/index.ts index ad1cf2a76c..774ce589e9 100644 --- a/src/common/ipc/index.ts +++ b/src/common/ipc/index.ts @@ -27,3 +27,4 @@ export * from "./update-available.ipc"; export * from "./cluster.ipc"; export * from "./type-enforced-ipc"; export * from "./hotbar"; +export * from "./extension-loader.ipc"; diff --git a/src/extensions/extension-loader/extension-loader.ts b/src/extensions/extension-loader/extension-loader.ts index b7d53402ed..af5f05c100 100644 --- a/src/extensions/extension-loader/extension-loader.ts +++ b/src/extensions/extension-loader/extension-loader.ts @@ -255,7 +255,8 @@ export class ExtensionLoader { loadOnClusterManagerRenderer() { logger.debug(`${logModule}: load on main renderer (cluster manager)`); - this.autoInitExtensions(async (extension: LensRendererExtension) => { + + return this.autoInitExtensions(async (extension: LensRendererExtension) => { const removeItems = [ registries.GlobalPageRegistry.getInstance().add(extension.globalPages, extension), registries.AppPreferenceRegistry.getInstance().add(extension.appPreferences), @@ -311,7 +312,9 @@ export class ExtensionLoader { } protected autoInitExtensions(register: (ext: LensExtension) => Promise) { - return reaction(() => this.toJSON(), installedExtensions => { + const loadingExtensions: { isBundled: boolean, loaded: Promise }[] = []; + + reaction(() => this.toJSON(), installedExtensions => { for (const [extId, extension] of installedExtensions) { const alreadyInit = this.instances.has(extId) || this.nonInstancesByName.has(extension.manifest.name); @@ -326,7 +329,14 @@ export class ExtensionLoader { const instance = new LensExtensionClass(extension); - instance.enable(register); + const loaded = instance.enable(register).catch((err) => { + logger.error(`${logModule}: failed to enable`, { ext: extension, err }); + }); + + loadingExtensions.push({ + isBundled: extension.isBundled, + loaded, + }); this.instances.set(extId, instance); } catch (err) { logger.error(`${logModule}: activation extension error`, { ext: extension, err }); @@ -338,6 +348,8 @@ export class ExtensionLoader { }, { fireImmediately: true, }); + + return loadingExtensions; } protected requireExtension(extension: InstalledExtension): LensExtensionConstructor | null { diff --git a/src/main/window-manager.ts b/src/main/window-manager.ts index 48704ba074..3987e29824 100644 --- a/src/main/window-manager.ts +++ b/src/main/window-manager.ts @@ -24,7 +24,7 @@ import { makeObservable, observable } from "mobx"; import { app, BrowserWindow, dialog, ipcMain, shell, webContents } from "electron"; import windowStateKeeper from "electron-window-state"; import { appEventBus } from "../common/event-bus"; -import { ipcMainOn } from "../common/ipc"; +import { BundledExtensionsLoaded, ipcMainOn } from "../common/ipc"; import { delay, iter, Singleton } from "../common/utils"; import { ClusterFrameInfo, clusterFrameMap } from "../common/cluster-frames"; import { IpcRendererNavigationEvents } from "../renderer/navigation/events"; @@ -181,7 +181,7 @@ export class WindowManager extends Singleton { if (!this.mainWindow) { viewHasLoaded = new Promise(resolve => { - ipcMain.once(IpcRendererNavigationEvents.LOADED, () => resolve()); + ipcMain.once(BundledExtensionsLoaded, () => resolve()); }); await this.initMainWindow(showSplash); } diff --git a/src/renderer/root-frame.tsx b/src/renderer/root-frame.tsx index c403af4bed..33132570c0 100644 --- a/src/renderer/root-frame.tsx +++ b/src/renderer/root-frame.tsx @@ -29,7 +29,7 @@ import { ErrorBoundary } from "./components/error-boundary"; import { Notifications } from "./components/notifications"; import { ConfirmDialog } from "./components/confirm-dialog"; import type { ExtensionLoader } from "../extensions/extension-loader"; -import { broadcastMessage } from "../common/ipc"; +import { broadcastMessage, BundledExtensionsLoaded } from "../common/ipc"; import { CommandContainer } from "./components/command-palette/command-container"; import { registerIpcListeners } from "./ipc"; import { ipcRenderer } from "electron"; @@ -39,6 +39,7 @@ import logger from "../common/logger"; import { unmountComponentAtNode } from "react-dom"; import { ClusterFrameHandler } from "./components/cluster-manager/lens-views"; import type { LensProtocolRouterRenderer } from "./protocol-handler"; +import { delay } from "./utils"; injectSystemCAs(); @@ -54,8 +55,21 @@ export class RootFrame extends React.Component { lensProtocolRouterRendererInjectable: LensProtocolRouterRenderer, ) { catalogEntityRegistry.init(); - extensionLoader.loadOnClusterManagerRenderer(); + + try { + // maximum time to let bundled extensions finish loading + const timeout = delay(10000); + + const loadingExtensions = extensionLoader.loadOnClusterManagerRenderer(); + const loadingBundledExtensions = loadingExtensions.filter(e => e.isBundled).map(e => e.loaded); + const bundledExtensionsFinished = Promise.all(loadingBundledExtensions); + + await Promise.race([bundledExtensionsFinished, timeout]); + } finally { + ipcRenderer.send(BundledExtensionsLoaded); + } lensProtocolRouterRendererInjectable.init(); + bindProtocolAddRouteHandlers(); window.addEventListener("offline", () => broadcastMessage("network:offline"));