mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Remove Singleton from BaseStore to remove global shared state
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
2fa09ba10d
commit
257082e699
@ -82,8 +82,6 @@ describe("BaseStore", () => {
|
|||||||
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
mainDi.override(directoryForUserDataInjectable, () => "some-user-data-directory");
|
||||||
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
mainDi.permitSideEffects(getConfigurationFileModelInjectable);
|
||||||
|
|
||||||
TestStore.resetInstance();
|
|
||||||
|
|
||||||
const mockOpts = {
|
const mockOpts = {
|
||||||
"some-user-data-directory": {
|
"some-user-data-directory": {
|
||||||
"test-store.json": JSON.stringify({}),
|
"test-store.json": JSON.stringify({}),
|
||||||
@ -92,13 +90,12 @@ describe("BaseStore", () => {
|
|||||||
|
|
||||||
mockFs(mockOpts);
|
mockFs(mockOpts);
|
||||||
|
|
||||||
store = TestStore.createInstance();
|
store = new TestStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockFs.restore();
|
mockFs.restore();
|
||||||
store.disableSync();
|
store.disableSync();
|
||||||
TestStore.resetInstance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("persistence", () => {
|
describe("persistence", () => {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { ipcMain, ipcRenderer } from "electron";
|
|||||||
import type { IEqualsComparer } from "mobx";
|
import type { IEqualsComparer } from "mobx";
|
||||||
import { makeObservable, reaction, runInAction } from "mobx";
|
import { makeObservable, reaction, runInAction } from "mobx";
|
||||||
import type { Disposer } from "./utils";
|
import type { Disposer } from "./utils";
|
||||||
import { Singleton, toJS } from "./utils";
|
import { toJS } from "./utils";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
import { broadcastMessage, ipcMainOn, ipcRendererOn } from "./ipc";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
@ -31,14 +31,13 @@ export interface BaseStoreParams<T> extends ConfOptions<T> {
|
|||||||
/**
|
/**
|
||||||
* Note: T should only contain base JSON serializable types.
|
* Note: T should only contain base JSON serializable types.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseStore<T extends object> extends Singleton {
|
export abstract class BaseStore<T extends object> {
|
||||||
protected storeConfig?: Config<T>;
|
protected storeConfig?: Config<T>;
|
||||||
protected syncDisposers: Disposer[] = [];
|
protected syncDisposers: Disposer[] = [];
|
||||||
|
|
||||||
readonly displayName: string = this.constructor.name;
|
readonly displayName: string = this.constructor.name;
|
||||||
|
|
||||||
protected constructor(protected params: BaseStoreParams<T>) {
|
protected constructor(protected params: BaseStoreParams<T>) {
|
||||||
super();
|
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
if (ipcRenderer) {
|
if (ipcRenderer) {
|
||||||
|
|||||||
@ -11,15 +11,11 @@ import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
|||||||
const clusterStoreInjectable = getInjectable({
|
const clusterStoreInjectable = getInjectable({
|
||||||
id: "cluster-store",
|
id: "cluster-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new ClusterStore({
|
||||||
ClusterStore.resetInstance();
|
createCluster: di.inject(createClusterInjectionToken),
|
||||||
|
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
||||||
return ClusterStore.createInstance({
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
createCluster: di.inject(createClusterInjectionToken),
|
}),
|
||||||
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
|
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,14 +10,10 @@ import loggerInjectable from "../logger.injectable";
|
|||||||
const hotbarStoreInjectable = getInjectable({
|
const hotbarStoreInjectable = getInjectable({
|
||||||
id: "hotbar-store",
|
id: "hotbar-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new HotbarStore({
|
||||||
HotbarStore.resetInstance();
|
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
return HotbarStore.createInstance({
|
}),
|
||||||
catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,14 +10,10 @@ import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
|
|||||||
const userStoreInjectable = getInjectable({
|
const userStoreInjectable = getInjectable({
|
||||||
id: "user-store",
|
id: "user-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new UserStore({
|
||||||
UserStore.resetInstance();
|
selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable),
|
||||||
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
return UserStore.createInstance({
|
}),
|
||||||
selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable),
|
|
||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -52,7 +52,10 @@ export function getOrInsertSet<K, SK>(map: Map<K, Set<SK>>, key: K): Set<SK> {
|
|||||||
* Like `getOrInsert` but with delayed creation of the item. Which is useful
|
* Like `getOrInsert` but with delayed creation of the item. Which is useful
|
||||||
* if it is very expensive to create the initial value.
|
* if it is very expensive to create the initial value.
|
||||||
*/
|
*/
|
||||||
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V {
|
export function getOrInsertWith<K, V>(map: Map<K, V>, key: K, builder: () => V): V;
|
||||||
|
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V;
|
||||||
|
|
||||||
|
export function getOrInsertWith<K extends object, V>(map: Map<K, V> | WeakMap<K, V>, key: K, builder: () => V): V {
|
||||||
if (!map.has(key)) {
|
if (!map.has(key)) {
|
||||||
map.set(key, builder());
|
map.set(key, builder());
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/common/utils/random-bytes.injectable.ts
Normal file
16
src/common/utils/random-bytes.injectable.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 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 { randomBytes } from "crypto";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
export type RandomBytes = (size: number) => Promise<Buffer>;
|
||||||
|
|
||||||
|
const randomBytesInjectable = getInjectable({
|
||||||
|
id: "random-bytes",
|
||||||
|
instantiate: (): RandomBytes => promisify(randomBytes),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default randomBytesInjectable;
|
||||||
@ -3,10 +3,13 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface StaticThis<T, R extends any[]> { new(...args: R): T }
|
export interface StaticThis<T, R extends any[]> { new(...args: R): T }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state
|
||||||
|
*/
|
||||||
export class Singleton {
|
export class Singleton {
|
||||||
private static instances = new WeakMap<object, Singleton>();
|
private static readonly instances = new WeakMap<object, Singleton>();
|
||||||
private static creating = "";
|
private static creating = "";
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@ -7,12 +7,7 @@ import { WeblinkStore } from "./weblink-store";
|
|||||||
|
|
||||||
const weblinkStoreInjectable = getInjectable({
|
const weblinkStoreInjectable = getInjectable({
|
||||||
id: "weblink-store",
|
id: "weblink-store",
|
||||||
|
instantiate: () => new WeblinkStore(),
|
||||||
instantiate: () => {
|
|
||||||
WeblinkStore.resetInstance();
|
|
||||||
|
|
||||||
return WeblinkStore.createInstance();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default weblinkStoreInjectable;
|
export default weblinkStoreInjectable;
|
||||||
|
|||||||
@ -5,19 +5,19 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
import { FileSystemProvisionerStore } from "./file-system-provisioner-store";
|
||||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||||
|
import ensureDirectoryInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||||
|
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||||
|
import randomBytesInjectable from "../../../common/utils/random-bytes.injectable";
|
||||||
|
|
||||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||||
id: "file-system-provisioner-store",
|
id: "file-system-provisioner-store",
|
||||||
|
|
||||||
instantiate: (di) => {
|
instantiate: (di) => new FileSystemProvisionerStore({
|
||||||
FileSystemProvisionerStore.resetInstance();
|
directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
|
||||||
|
ensureDirectory: di.inject(ensureDirectoryInjectable),
|
||||||
return FileSystemProvisionerStore.createInstance({
|
joinPaths: di.inject(joinPathsInjectable),
|
||||||
directoryForExtensionData: di.inject(directoryForExtensionDataInjectable),
|
randomBytes: di.inject(randomBytesInjectable),
|
||||||
});
|
}),
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default fileSystemProvisionerStoreInjectable;
|
export default fileSystemProvisionerStoreInjectable;
|
||||||
|
|||||||
@ -3,28 +3,31 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
import { SHA256 } from "crypto-js";
|
import { SHA256 } from "crypto-js";
|
||||||
import fse from "fs-extra";
|
|
||||||
import { action, makeObservable, observable } from "mobx";
|
import { action, makeObservable, observable } from "mobx";
|
||||||
import path from "path";
|
|
||||||
import { BaseStore } from "../../../common/base-store";
|
import { BaseStore } from "../../../common/base-store";
|
||||||
import type { LensExtensionId } from "../../lens-extension";
|
import type { LensExtensionId } from "../../lens-extension";
|
||||||
import { getOrInsertWith, toJS } from "../../../common/utils";
|
import { getOrInsertWithAsync, toJS } from "../../../common/utils";
|
||||||
|
import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable";
|
||||||
|
import type { JoinPaths } from "../../../common/path/join-paths.injectable";
|
||||||
|
import type { RandomBytes } from "../../../common/utils/random-bytes.injectable";
|
||||||
|
|
||||||
interface FSProvisionModel {
|
interface FSProvisionModel {
|
||||||
extensions: Record<string, string>; // extension names to paths
|
extensions: Record<string, string>; // extension names to paths
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
directoryForExtensionData: string;
|
readonly directoryForExtensionData: string;
|
||||||
|
ensureDirectory: EnsureDirectory;
|
||||||
|
joinPaths: JoinPaths;
|
||||||
|
randomBytes: RandomBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||||
readonly displayName = "FilesystemProvisionerStore";
|
readonly displayName = "FilesystemProvisionerStore";
|
||||||
registeredExtensions = observable.map<LensExtensionId, string>();
|
readonly registeredExtensions = observable.map<LensExtensionId, string>();
|
||||||
|
|
||||||
constructor(private dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
super({
|
super({
|
||||||
configName: "lens-filesystem-provisioner-store",
|
configName: "lens-filesystem-provisioner-store",
|
||||||
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
|
||||||
@ -41,14 +44,14 @@ export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
|||||||
* @returns path to the folder that the extension can safely write files to.
|
* @returns path to the folder that the extension can safely write files to.
|
||||||
*/
|
*/
|
||||||
async requestDirectory(extensionName: string): Promise<string> {
|
async requestDirectory(extensionName: string): Promise<string> {
|
||||||
const dirPath = getOrInsertWith(this.registeredExtensions, extensionName, () => {
|
const dirPath = await getOrInsertWithAsync(this.registeredExtensions, extensionName, async () => {
|
||||||
const salt = randomBytes(32).toString("hex");
|
const salt = (await this.dependencies.randomBytes(32)).toString("hex");
|
||||||
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
const hashedName = SHA256(`${extensionName}/${salt}`).toString();
|
||||||
|
|
||||||
return path.resolve(this.dependencies.directoryForExtensionData, hashedName);
|
return this.dependencies.joinPaths(this.dependencies.directoryForExtensionData, hashedName);
|
||||||
});
|
});
|
||||||
|
|
||||||
await fse.ensureDir(dirPath);
|
await this.dependencies.ensureDirectory(dirPath);
|
||||||
|
|
||||||
return dirPath;
|
return dirPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,39 @@ import { BaseStore } from "../common/base-store";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import type { LensExtension } from "./lens-extension";
|
import type { LensExtension } from "./lens-extension";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
|
import type { StaticThis } from "../common/utils";
|
||||||
|
import { getOrInsertWith } from "../common/utils";
|
||||||
|
|
||||||
export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
export abstract class ExtensionStore<T extends object> extends BaseStore<T> {
|
||||||
|
private static readonly instances = new WeakMap<object, ExtensionStore<object>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static createInstance<T extends ExtensionStore<object>, R extends any[]>(this: StaticThis<T, R>, ...args: R): T {
|
||||||
|
return getOrInsertWith(ExtensionStore.instances, this, () => new this(...args)) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict?: true): T;
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict: false): T | undefined;
|
||||||
|
static getInstance<T, R extends any[]>(this: StaticThis<T, R>, strict = true): T | undefined {
|
||||||
|
if (!ExtensionStore.instances.has(this) && strict) {
|
||||||
|
throw new TypeError(`instance of ${this.name} is not created`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExtensionStore.instances.get(this) as (T | undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This is a form of global shared state. Just call `new Store(...)`
|
||||||
|
*/
|
||||||
|
static resetInstance() {
|
||||||
|
ExtensionStore.instances.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
readonly displayName = "ExtensionStore<T>";
|
readonly displayName = "ExtensionStore<T>";
|
||||||
protected extension?: LensExtension;
|
protected extension?: LensExtension;
|
||||||
|
|
||||||
|
|||||||
@ -7,14 +7,7 @@ import { ExtensionsStore } from "./extensions-store";
|
|||||||
|
|
||||||
const extensionsStoreInjectable = getInjectable({
|
const extensionsStoreInjectable = getInjectable({
|
||||||
id: "extensions-store",
|
id: "extensions-store",
|
||||||
|
instantiate: () => new ExtensionsStore(),
|
||||||
instantiate: () => {
|
|
||||||
ExtensionsStore.resetInstance();
|
|
||||||
|
|
||||||
return ExtensionsStore.createInstance();
|
|
||||||
},
|
|
||||||
|
|
||||||
causesSideEffects: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default extensionsStoreInjectable;
|
export default extensionsStoreInjectable;
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { DefaultProps } from "./mui-base-theme";
|
|||||||
import configurePackages from "../common/configure-packages";
|
import configurePackages from "../common/configure-packages";
|
||||||
import * as initializers from "./initializers";
|
import * as initializers from "./initializers";
|
||||||
import logger from "../common/logger";
|
import logger from "../common/logger";
|
||||||
import { WeblinkStore } from "../common/weblink-store";
|
|
||||||
import { registerCustomThemes } from "./components/monaco-editor";
|
import { registerCustomThemes } from "./components/monaco-editor";
|
||||||
import { getDi } from "./getDi";
|
import { getDi } from "./getDi";
|
||||||
import { DiContextProvider } from "@ogre-tools/injectable-react";
|
import { DiContextProvider } from "@ogre-tools/injectable-react";
|
||||||
@ -130,8 +129,6 @@ export async function bootstrap(di: DiContainer) {
|
|||||||
// TODO: Remove temporal dependencies
|
// TODO: Remove temporal dependencies
|
||||||
di.inject(themeStoreInjectable);
|
di.inject(themeStoreInjectable);
|
||||||
|
|
||||||
WeblinkStore.createInstance();
|
|
||||||
|
|
||||||
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
const extensionInstallationStateStore = di.inject(extensionInstallationStateStoreInjectable);
|
||||||
|
|
||||||
extensionInstallationStateStore.bindIpcListeners();
|
extensionInstallationStateStore.bindIpcListeners();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user