mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
- Add distinction between `getInstance` and `getInstanceOrCreate` since it is not always possible to create an instance (since you might not know the correct arguments) - Remove all the `export const *Store = *Store.getInstance<*Store>();` calls as it defeats the purpose of `Singleton`. Plus with the typing changes the appropriate `*Store.getInstance()` is "short enough". - Special case the two extension export facades to not need to use `getInstanceOrCreate`. Plus since they are just facades it is always possible to create them. - Move some other types to be also `Singleton`'s: ExtensionLoader, ExtensionDiscovery, ThemeStore, LocalizationStore, ... - Fixed dev-run always using the same port with electron inspect - Update Store documentation with new recommendations about creating instances of singletons - Fix all unit tests to create their dependent singletons Signed-off-by: Sebastian Malton <sebastian@malton.name>
265 lines
8.2 KiB
TypeScript
265 lines
8.2 KiB
TypeScript
// Main process
|
|
|
|
import "../common/system-ca";
|
|
import "../common/prometheus-providers";
|
|
import * as Mobx from "mobx";
|
|
import * as LensExtensions from "../extensions/core-api";
|
|
import { app, autoUpdater, ipcMain, dialog, powerMonitor } from "electron";
|
|
import { appName, isMac } from "../common/vars";
|
|
import path from "path";
|
|
import { LensProxy } from "./lens-proxy";
|
|
import { WindowManager } from "./window-manager";
|
|
import { ClusterManager } from "./cluster-manager";
|
|
import { shellSync } from "./shell-sync";
|
|
import { getFreePort } from "./port";
|
|
import { mangleProxyEnv } from "./proxy-env";
|
|
import { registerFileProtocol } from "../common/register-protocol";
|
|
import logger from "./logger";
|
|
import { ClusterStore } from "../common/cluster-store";
|
|
import { UserStore } from "../common/user-store";
|
|
import { appEventBus } from "../common/event-bus";
|
|
import { ExtensionLoader } from "../extensions/extension-loader";
|
|
import { ExtensionsStore } from "../extensions/extensions-store";
|
|
import { InstalledExtension, ExtensionDiscovery } from "../extensions/extension-discovery";
|
|
import type { LensExtensionId } from "../extensions/lens-extension";
|
|
import { FilesystemProvisionerStore } from "./extension-filesystem";
|
|
import { installDeveloperTools } from "./developer-tools";
|
|
import { LensProtocolRouterMain } from "./protocol-handler";
|
|
import { getAppVersion, getAppVersionFromProxyServer } from "../common/utils";
|
|
import { bindBroadcastHandlers } from "../common/ipc";
|
|
import { startUpdateChecking } from "./app-updater";
|
|
import { IpcRendererNavigationEvents } from "../renderer/navigation/events";
|
|
import { CatalogPusher } from "./catalog-pusher";
|
|
import { catalogEntityRegistry } from "../common/catalog-entity-registry";
|
|
import { HotbarStore } from "../common/hotbar-store";
|
|
|
|
const workingDir = path.join(app.getPath("appData"), appName);
|
|
|
|
app.setName(appName);
|
|
|
|
logger.info("📟 Setting Lens as protocol client for lens://");
|
|
|
|
if (app.setAsDefaultProtocolClient("lens")) {
|
|
logger.info("📟 succeeded ✅");
|
|
} else {
|
|
logger.info("📟 failed ❗");
|
|
}
|
|
|
|
if (!process.env.CICD) {
|
|
app.setPath("userData", workingDir);
|
|
}
|
|
|
|
if (process.env.LENS_DISABLE_GPU) {
|
|
app.disableHardwareAcceleration();
|
|
}
|
|
|
|
mangleProxyEnv();
|
|
|
|
if (app.commandLine.getSwitchValue("proxy-server") !== "") {
|
|
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server");
|
|
}
|
|
|
|
if (!app.requestSingleInstanceLock()) {
|
|
app.exit();
|
|
} else {
|
|
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
|
|
|
for (const arg of process.argv) {
|
|
if (arg.toLowerCase().startsWith("lens://")) {
|
|
lprm.route(arg)
|
|
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
|
|
}
|
|
}
|
|
}
|
|
|
|
app.on("second-instance", (event, argv) => {
|
|
const lprm = LensProtocolRouterMain.getInstanceOrCreate();
|
|
|
|
for (const arg of argv) {
|
|
if (arg.toLowerCase().startsWith("lens://")) {
|
|
lprm.route(arg)
|
|
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl: arg }));
|
|
}
|
|
}
|
|
|
|
WindowManager.getInstance(false)?.ensureMainWindow();
|
|
});
|
|
|
|
app.on("ready", async () => {
|
|
logger.info(`🚀 Starting Lens from "${workingDir}"`);
|
|
logger.info("🐚 Syncing shell environment");
|
|
await shellSync();
|
|
|
|
bindBroadcastHandlers();
|
|
|
|
powerMonitor.on("shutdown", () => {
|
|
app.exit();
|
|
});
|
|
|
|
registerFileProtocol("static", __static);
|
|
|
|
const userStore = UserStore.getInstanceOrCreate();
|
|
const clusterStore = ClusterStore.getInstanceOrCreate();
|
|
const hotbarStore = HotbarStore.getInstanceOrCreate();
|
|
const extensionsStore = ExtensionsStore.getInstanceOrCreate();
|
|
const filesystemStore = FilesystemProvisionerStore.getInstanceOrCreate();
|
|
|
|
logger.info("💾 Loading stores");
|
|
// preload
|
|
await Promise.all([
|
|
userStore.load(),
|
|
clusterStore.load(),
|
|
hotbarStore.load(),
|
|
extensionsStore.load(),
|
|
filesystemStore.load(),
|
|
]);
|
|
|
|
// find free port
|
|
let proxyPort;
|
|
|
|
try {
|
|
logger.info("🔑 Getting free port for LensProxy server");
|
|
proxyPort = await getFreePort();
|
|
} catch (error) {
|
|
logger.error(error);
|
|
dialog.showErrorBox("Lens Error", "Could not find a free port for the cluster proxy");
|
|
app.exit();
|
|
}
|
|
|
|
// create cluster manager
|
|
ClusterManager.getInstanceOrCreate(proxyPort);
|
|
|
|
// run proxy
|
|
try {
|
|
logger.info("🔌 Starting LensProxy");
|
|
// eslint-disable-next-line unused-imports/no-unused-vars-ts
|
|
LensProxy.getInstanceOrCreate(proxyPort).listen();
|
|
} catch (error) {
|
|
logger.error(`Could not start proxy (127.0.0:${proxyPort}): ${error?.message}`);
|
|
dialog.showErrorBox("Lens Error", `Could not start proxy (127.0.0:${proxyPort}): ${error?.message || "unknown error"}`);
|
|
app.exit();
|
|
}
|
|
|
|
// test proxy connection
|
|
try {
|
|
logger.info("🔎 Testing LensProxy connection ...");
|
|
const versionFromProxy = await getAppVersionFromProxyServer(proxyPort);
|
|
|
|
if (getAppVersion() !== versionFromProxy) {
|
|
logger.error(`Proxy server responded with invalid response`);
|
|
}
|
|
logger.info("⚡ LensProxy connection OK");
|
|
} catch (error) {
|
|
logger.error("Checking proxy server connection failed", error);
|
|
}
|
|
|
|
const extensionDiscovery = ExtensionDiscovery.getInstanceOrCreate();
|
|
|
|
ExtensionLoader.getInstanceOrCreate().init();
|
|
extensionDiscovery.init();
|
|
|
|
// Start the app without showing the main window when auto starting on login
|
|
// (On Windows and Linux, we get a flag. On MacOS, we get special API.)
|
|
const startHidden = process.argv.includes("--hidden") || (isMac && app.getLoginItemSettings().wasOpenedAsHidden);
|
|
|
|
logger.info("🖥️ Starting WindowManager");
|
|
const windowManager = WindowManager.getInstanceOrCreate(proxyPort);
|
|
|
|
installDeveloperTools();
|
|
|
|
if (!startHidden) {
|
|
windowManager.initMainWindow();
|
|
}
|
|
|
|
ipcMain.on(IpcRendererNavigationEvents.LOADED, () => {
|
|
CatalogPusher.init(catalogEntityRegistry);
|
|
startUpdateChecking();
|
|
LensProtocolRouterMain
|
|
.getInstance()
|
|
.rendererLoaded = true;
|
|
});
|
|
|
|
ExtensionLoader.getInstance().whenLoaded.then(() => {
|
|
LensProtocolRouterMain
|
|
.getInstance()
|
|
.extensionsLoaded = true;
|
|
});
|
|
|
|
logger.info("🧩 Initializing extensions");
|
|
|
|
// call after windowManager to see splash earlier
|
|
try {
|
|
const extensions = await extensionDiscovery.load();
|
|
|
|
// Start watching after bundled extensions are loaded
|
|
extensionDiscovery.watchExtensions();
|
|
|
|
// Subscribe to extensions that are copied or deleted to/from the extensions folder
|
|
extensionDiscovery.events
|
|
.on("add", (extension: InstalledExtension) => {
|
|
ExtensionLoader.getInstance().addExtension(extension);
|
|
})
|
|
.on("remove", (lensExtensionId: LensExtensionId) => {
|
|
ExtensionLoader.getInstance().removeExtension(lensExtensionId);
|
|
});
|
|
|
|
ExtensionLoader.getInstance().initExtensions(extensions);
|
|
} catch (error) {
|
|
dialog.showErrorBox("Lens Error", `Could not load extensions${error?.message ? `: ${error.message}` : ""}`);
|
|
console.error(error);
|
|
console.trace();
|
|
}
|
|
|
|
setTimeout(() => {
|
|
appEventBus.emit({ name: "service", action: "start" });
|
|
}, 1000);
|
|
});
|
|
|
|
app.on("activate", (event, hasVisibleWindows) => {
|
|
logger.info("APP:ACTIVATE", { hasVisibleWindows });
|
|
|
|
if (!hasVisibleWindows) {
|
|
WindowManager.getInstance(false)?.initMainWindow(false);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* This variable should is used so that `autoUpdater.installAndQuit()` works
|
|
*/
|
|
let blockQuit = true;
|
|
|
|
autoUpdater.on("before-quit-for-update", () => blockQuit = false);
|
|
|
|
app.on("will-quit", (event) => {
|
|
// Quit app on Cmd+Q (MacOS)
|
|
logger.info("APP:QUIT");
|
|
appEventBus.emit({name: "app", action: "close"});
|
|
ClusterManager.getInstance(false)?.stop(); // close cluster connections
|
|
|
|
if (blockQuit) {
|
|
event.preventDefault(); // prevent app's default shutdown (e.g. required for telemetry, etc.)
|
|
|
|
return; // skip exit to make tray work, to quit go to app's global menu or tray's menu
|
|
}
|
|
});
|
|
|
|
app.on("open-url", (event, rawUrl) => {
|
|
// lens:// protocol handler
|
|
event.preventDefault();
|
|
|
|
LensProtocolRouterMain
|
|
.getInstance()
|
|
.route(rawUrl)
|
|
.catch(error => logger.error(`${LensProtocolRouterMain.LoggingPrefix}: an error occured`, { error, rawUrl }));
|
|
});
|
|
|
|
// Extensions-api runtime exports
|
|
export const LensExtensionsApi = {
|
|
...LensExtensions,
|
|
};
|
|
|
|
export {
|
|
Mobx,
|
|
LensExtensionsApi as LensExtensions,
|
|
};
|