This commit enhances application security against potential attacks by isolating dependencies that access the host system (like file operations) from the renderer process. It narrows the exposed functionality to script execution only, adding an extra security layer. The changes allow secure and scalable API exposure, preparing for future functionalities such as desktop notifications for script errors (#264), improved script execution handling (#296), and creating restore points (#50) in a secure and repeatable way. Changes include: - Inject `CodeRunner` into Vue components via dependency injection. - Move `CodeRunner` to the application layer as an abstraction for better domain-driven design alignment. - Refactor `SystemOperations` and related interfaces, removing the `I` prefix. - Update architecture documentation for clarity. - Update return types in `NodeSystemOperations` to match the Node APIs. - Improve `WindowVariablesProvider` integration tests for better error context. - Centralize type checks with common functions like `isArray` and `isNumber`. - Change `CodeRunner` to use `os` parameter, ensuring correct window variable injection. - Streamline API exposure to the renderer process: - Automatically bind function contexts to prevent loss of original context. - Implement a way to create facades (wrapper/proxy objects) for increased security.
89 lines
3.5 KiB
TypeScript
89 lines
3.5 KiB
TypeScript
import { inject, type InjectionKey } from 'vue';
|
|
import type { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
|
|
import type { useApplication } from '@/presentation/components/Shared/Hooks/UseApplication';
|
|
import type { useRuntimeEnvironment } from '@/presentation/components/Shared/Hooks/UseRuntimeEnvironment';
|
|
import type { useClipboard } from '@/presentation/components/Shared/Hooks/Clipboard/UseClipboard';
|
|
import type { useCurrentCode } from '@/presentation/components/Shared/Hooks/UseCurrentCode';
|
|
import type { useAutoUnsubscribedEvents } from '@/presentation/components/Shared/Hooks/UseAutoUnsubscribedEvents';
|
|
import type { useUserSelectionState } from '@/presentation/components/Shared/Hooks/UseUserSelectionState';
|
|
import type { useLogger } from '@/presentation/components/Shared/Hooks/UseLogger';
|
|
import type { useCodeRunner } from './components/Shared/Hooks/UseCodeRunner';
|
|
|
|
export const InjectionKeys = {
|
|
useCollectionState: defineTransientKey<ReturnType<typeof useCollectionState>>('useCollectionState'),
|
|
useApplication: defineSingletonKey<ReturnType<typeof useApplication>>('useApplication'),
|
|
useRuntimeEnvironment: defineSingletonKey<ReturnType<typeof useRuntimeEnvironment>>('useRuntimeEnvironment'),
|
|
useAutoUnsubscribedEvents: defineTransientKey<ReturnType<typeof useAutoUnsubscribedEvents>>('useAutoUnsubscribedEvents'),
|
|
useClipboard: defineTransientKey<ReturnType<typeof useClipboard>>('useClipboard'),
|
|
useCurrentCode: defineTransientKey<ReturnType<typeof useCurrentCode>>('useCurrentCode'),
|
|
useUserSelectionState: defineTransientKey<ReturnType<typeof useUserSelectionState>>('useUserSelectionState'),
|
|
useLogger: defineTransientKey<ReturnType<typeof useLogger>>('useLogger'),
|
|
useCodeRunner: defineTransientKey<ReturnType<typeof useCodeRunner>>('useCodeRunner'),
|
|
};
|
|
|
|
export interface InjectionKeyWithLifetime<T> {
|
|
readonly lifetime: InjectionKeyLifetime;
|
|
readonly key: InjectionKey<T> & symbol;
|
|
}
|
|
|
|
export interface SingletonKey<T> extends InjectionKeyWithLifetime<T> {
|
|
readonly lifetime: InjectionKeyLifetime.Singleton;
|
|
readonly key: InjectionKey<T> & symbol;
|
|
}
|
|
|
|
export interface TransientKey<T> extends InjectionKeyWithLifetime<() => T> {
|
|
readonly lifetime: InjectionKeyLifetime.Transient;
|
|
readonly key: InjectionKey<() => T> & symbol;
|
|
}
|
|
|
|
export type AnyLifetimeInjectionKey<T> = InjectionKeyWithLifetime<T> | TransientKey<T>;
|
|
|
|
export type InjectionKeySelector<T> = (keys: typeof InjectionKeys) => AnyLifetimeInjectionKey<T>;
|
|
|
|
export function injectKey<T>(
|
|
keySelector: InjectionKeySelector<T>,
|
|
vueInjector = inject,
|
|
): T {
|
|
const key = keySelector(InjectionKeys);
|
|
const injectedValue = injectRequired(key.key, vueInjector);
|
|
if (key.lifetime === InjectionKeyLifetime.Transient) {
|
|
const factory = injectedValue as () => T;
|
|
const value = factory();
|
|
return value;
|
|
}
|
|
|
|
return injectedValue as T;
|
|
}
|
|
|
|
export enum InjectionKeyLifetime {
|
|
Singleton,
|
|
Transient,
|
|
}
|
|
|
|
function defineSingletonKey<T>(key: string): SingletonKey<T> {
|
|
return {
|
|
lifetime: InjectionKeyLifetime.Singleton,
|
|
key: Symbol(key),
|
|
};
|
|
}
|
|
|
|
function defineTransientKey<T>(key: string): TransientKey<T> {
|
|
return {
|
|
lifetime: InjectionKeyLifetime.Transient,
|
|
key: Symbol(key),
|
|
};
|
|
}
|
|
|
|
function injectRequired<T>(
|
|
key: InjectionKey<T>,
|
|
vueInjector = inject,
|
|
): T {
|
|
const injectedValue = vueInjector(key);
|
|
|
|
if (injectedValue === undefined) {
|
|
throw new Error(`Failed to inject value for key: ${key.description}`);
|
|
}
|
|
|
|
return injectedValue;
|
|
}
|