This commit introduces native operating system file dialogs in the desktop application replacing the existing web-based dialogs. It lays the foundation for future enhancements such as: - Providing error messages when saving or executing files, addressing #264. - Creating system restore points, addressing #50. Documentation updates: - Update `desktop-vs-web-features.md` with added functionality. - Update `README.md` with security feature highlights. - Update home page documentation to emphasize security features. Other supporting changes include: - Integrate IPC communication channels for secure Electron dialog API interactions. - Refactor `IpcRegistration` for more type-safety and simplicity. - Introduce a Vue hook to encapsulate dialog functionality. - Improve errors during IPC registration for easier troubleshooting. - Move `ClientLoggerFactory` for consistency in hooks organization and remove `LoggerFactory` interface for simplicity. - Add tests for the save file dialog in the browser context. - Add `Blob` polyfill in tests to compensate for the missing `blob.text()` function in `jsdom` (see jsdom/jsdom#2555). Improve environment detection logic: - Treat test environment as browser environments to correctly activate features based on the environment. This resolves issues where the environment is misidentified as desktop, but Electron preloader APIs are missing. - Rename `isDesktop` environment identification variable to `isRunningAsDesktopApplication` for better clarity and to avoid confusion with desktop environments in web/browser/test environments. - Simplify `BrowserRuntimeEnvironment` to consistently detect non-desktop application environments. - Improve environment detection for Electron main process (electron/electron#2288).
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { FunctionKeys } from '@/TypeHelpers';
|
||||
import { CodeRunner } from '@/application/CodeRunner/CodeRunner';
|
||||
import { Dialog } from '@/presentation/common/Dialog';
|
||||
import { IpcChannel } from './IpcChannel';
|
||||
|
||||
export const IpcChannelDefinitions = {
|
||||
CodeRunner: defineElectronIpcChannel<CodeRunner>('code-run', ['runCode']),
|
||||
Dialog: defineElectronIpcChannel<Dialog>('dialogs', ['saveFile']),
|
||||
} as const;
|
||||
|
||||
export type ChannelDefinitionKey = keyof typeof IpcChannelDefinitions;
|
||||
|
||||
function defineElectronIpcChannel<T>(
|
||||
name: string,
|
||||
functionNames: readonly FunctionKeys<T>[],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ipcMain } from 'electron/main';
|
||||
import { ipcRenderer } from 'electron/renderer';
|
||||
import { isFunction } from '@/TypeHelpers';
|
||||
import { FunctionKeys, isFunction } from '@/TypeHelpers';
|
||||
import { IpcChannel } from './IpcChannel';
|
||||
|
||||
export function createIpcConsumerProxy<T>(
|
||||
@@ -25,9 +25,7 @@ export function registerIpcChannel<T>(
|
||||
) {
|
||||
channel.accessibleMembers.forEach((functionKey) => {
|
||||
const originalFunction = originalObject[functionKey];
|
||||
if (!isFunction(originalFunction)) {
|
||||
throw new Error('Non-function members are not yet supported');
|
||||
}
|
||||
validateIpcFunction(functionKey, originalFunction, channel);
|
||||
const ipcChannel = getIpcChannelIdentifier(channel.namespace, functionKey as string);
|
||||
electronIpcMain.handle(ipcChannel, (_event, ...args: unknown[]) => {
|
||||
return originalFunction.apply(originalObject, args);
|
||||
@@ -35,6 +33,28 @@ export function registerIpcChannel<T>(
|
||||
});
|
||||
}
|
||||
|
||||
function validateIpcFunction<T>(
|
||||
functionKey: FunctionKeys<T>,
|
||||
functionValue: T[FunctionKeys<T>],
|
||||
channel: IpcChannel<T>,
|
||||
): asserts functionValue is T[FunctionKeys<T>] & ((...args: unknown[]) => unknown) {
|
||||
const functionName = functionKey.toString();
|
||||
if (functionValue === undefined) {
|
||||
throwErrorWithContext(`The function "${functionName}" is not found on the target object.`);
|
||||
}
|
||||
if (!isFunction(functionValue)) {
|
||||
throwErrorWithContext('Non-function members are not yet supported.');
|
||||
}
|
||||
function throwErrorWithContext(message: string): never {
|
||||
throw new Error([
|
||||
message,
|
||||
`Channel: ${JSON.stringify(channel)}.`,
|
||||
`Function key: ${functionName}.`,
|
||||
`Value: ${JSON.stringify(functionValue)}`,
|
||||
].join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
function getIpcChannelIdentifier(namespace: string, key: string) {
|
||||
return `proxy:${namespace}:${key}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user