Improve script error dialogs #304
- Include the script's directory path #304. - Exclude Windows-specific instructions on non-Windows OS. - Standardize language across dialogs for consistency. Other supporting changes: - Add script diagnostics data collection from main process. - Document script file storage and execution tamper protection in SECURITY.md. - Remove redundant comment in `NodeReadbackFileWriter`. - Centralize error display for uniformity and simplicity. - Simpify `WindowVariablesValidator` to omit checks when not on the renderer process. - Improve and centralize Electron environment detection. - Use more emphatic language (don't worry) in error messages.
This commit is contained in:
@@ -15,6 +15,7 @@ import { useLogger } from '@/presentation/components/Shared/Hooks/Log/UseLogger'
|
||||
import { useCodeRunner } from '@/presentation/components/Shared/Hooks/UseCodeRunner';
|
||||
import { CurrentEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironmentFactory';
|
||||
import { useDialog } from '@/presentation/components/Shared/Hooks/Dialog/UseDialog';
|
||||
import { useScriptDiagnosticsCollector } from '@/presentation/components/Shared/Hooks/UseScriptDiagnosticsCollector';
|
||||
|
||||
export function provideDependencies(
|
||||
context: IApplicationContext,
|
||||
@@ -72,6 +73,10 @@ export function provideDependencies(
|
||||
InjectionKeys.useDialog,
|
||||
useDialog,
|
||||
),
|
||||
useScriptDiagnosticsCollector: (di) => di.provide(
|
||||
InjectionKeys.useScriptDiagnosticsCollector,
|
||||
useScriptDiagnosticsCollector,
|
||||
),
|
||||
};
|
||||
registerAll(Object.values(resolvers), api);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { injectKey } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Dialog } from '@/presentation/common/Dialog';
|
||||
import { CodeRunError } from '@/application/CodeRunner/CodeRunner';
|
||||
import IconButton from './IconButton.vue';
|
||||
import { createScriptErrorDialog } from './ScriptErrorDialog';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -24,6 +23,7 @@ export default defineComponent({
|
||||
const { os, isRunningAsDesktopApplication } = injectKey((keys) => keys.useRuntimeEnvironment);
|
||||
const { codeRunner } = injectKey((keys) => keys.useCodeRunner);
|
||||
const { dialog } = injectKey((keys) => keys.useDialog);
|
||||
const { scriptDiagnosticsCollector } = injectKey((keys) => keys.useScriptDiagnosticsCollector);
|
||||
|
||||
const canRun = computed<boolean>(() => getCanRunState(
|
||||
currentState.value.os,
|
||||
@@ -38,7 +38,12 @@ export default defineComponent({
|
||||
currentContext.state.collection.scripting.fileExtension,
|
||||
);
|
||||
if (!success) {
|
||||
showScriptRunError(dialog, error);
|
||||
dialog.showError(...(await createScriptErrorDialog({
|
||||
errorContext: 'run',
|
||||
errorType: error.type,
|
||||
errorMessage: error.message,
|
||||
isFileReadbackError: error.type === 'FileReadbackVerificationError',
|
||||
}, scriptDiagnosticsCollector)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,65 +62,4 @@ function getCanRunState(
|
||||
const isRunningOnSelectedOs = selectedOs === hostOs;
|
||||
return isRunningAsDesktopApplication && isRunningOnSelectedOs;
|
||||
}
|
||||
|
||||
function showScriptRunError(dialog: Dialog, error: CodeRunError) {
|
||||
const technicalDetails = `[${error.type}] ${error.message}`;
|
||||
dialog.showError(
|
||||
...(
|
||||
error.type === 'FileReadbackVerificationError'
|
||||
? createAntivirusErrorDialog(technicalDetails)
|
||||
: createGenericErrorDialog(technicalDetails)),
|
||||
);
|
||||
}
|
||||
|
||||
function createGenericErrorDialog(technicalDetails: string): Parameters<Dialog['showError']> {
|
||||
return [
|
||||
'Error Running Script',
|
||||
[
|
||||
'We encountered an issue while running the script.',
|
||||
'This could be due to a variety of factors such as system permissions, resource constraints, or security software interventions.',
|
||||
'\n',
|
||||
'Here are some steps you can take:',
|
||||
'- Confirm that you have the necessary permissions to execute scripts on your system.',
|
||||
'- Check if there is sufficient disk space and system resources available.',
|
||||
[
|
||||
'- Antivirus or security software can sometimes mistakenly block script execution.',
|
||||
'Verify your security settings, or temporarily disable the security software to see if that resolves the issue.',
|
||||
'privacy.sexy is secure, transparent, and open-source, but the scripts might still be mistakenly flagged by antivirus software.',
|
||||
].join(' '),
|
||||
'- If possible, try running a different script to determine if the issue is specific to a particular script.',
|
||||
'- Should the problem persist, reach out to the community for further assistance.',
|
||||
'\n',
|
||||
'Technical Details:',
|
||||
technicalDetails,
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
|
||||
function createAntivirusErrorDialog(technicalDetails: string): Parameters<Dialog['showError']> {
|
||||
return [
|
||||
'Potential Antivirus Intervention',
|
||||
[
|
||||
[
|
||||
'We\'ve encountered a problem which may be due to your antivirus software intervening.',
|
||||
'privacy.sexy is secure, transparent, and open-source, but the scripts might still be mistakenly flagged by antivirus software such as Defender.',
|
||||
].join(' '),
|
||||
'\n',
|
||||
'To address this, you can:',
|
||||
'1. Temporarily disable your antivirus (real-time protection) or add an exclusion for privacy.sexy scripts.',
|
||||
'2. Re-try running or downloading the script.',
|
||||
'3. If the issue persists, check your antivirus logs for more details and consider reporting this as a false positive to your antivirus provider.',
|
||||
'\n',
|
||||
'To handle false warnings in Defender: Open "Virus & threat protection" from the "Start" menu.',
|
||||
'\n',
|
||||
[
|
||||
'Remember to re-enable your antivirus protection as soon as possible for your security.',
|
||||
'For more guidance, refer to your antivirus documentation.',
|
||||
].join(' '),
|
||||
'\n',
|
||||
'Technical Details:',
|
||||
technicalDetails,
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -20,8 +20,9 @@ import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue'
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { ScriptFilename } from '@/application/CodeRunner/ScriptFilename';
|
||||
import { Dialog, FileType, SaveFileError } from '@/presentation/common/Dialog';
|
||||
import { FileType } from '@/presentation/common/Dialog';
|
||||
import IconButton from '../IconButton.vue';
|
||||
import { createScriptErrorDialog } from '../ScriptErrorDialog';
|
||||
import RunInstructions from './RunInstructions/RunInstructions.vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -34,6 +35,7 @@ export default defineComponent({
|
||||
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||
const { isRunningAsDesktopApplication } = injectKey((keys) => keys.useRuntimeEnvironment);
|
||||
const { dialog } = injectKey((keys) => keys.useDialog);
|
||||
const { scriptDiagnosticsCollector } = injectKey((keys) => keys.useScriptDiagnosticsCollector);
|
||||
|
||||
const areInstructionsVisible = ref(false);
|
||||
const filename = computed<string>(() => buildFilename(currentState.value.collection.scripting));
|
||||
@@ -45,7 +47,12 @@ export default defineComponent({
|
||||
getType(currentState.value.collection.scripting.language),
|
||||
);
|
||||
if (!success) {
|
||||
showScriptSaveError(dialog, error);
|
||||
dialog.showError(...(await createScriptErrorDialog({
|
||||
errorContext: 'save',
|
||||
errorType: error.type,
|
||||
errorMessage: error.message,
|
||||
isFileReadbackError: error.type === 'FileReadbackVerificationError',
|
||||
}, scriptDiagnosticsCollector)));
|
||||
return;
|
||||
}
|
||||
areInstructionsVisible.value = true;
|
||||
@@ -77,60 +84,4 @@ function buildFilename(scripting: IScriptingDefinition) {
|
||||
}
|
||||
return ScriptFilename;
|
||||
}
|
||||
|
||||
function showScriptSaveError(dialog: Dialog, error: SaveFileError) {
|
||||
const technicalDetails = `[${error.type}] ${error.message}`;
|
||||
dialog.showError(
|
||||
...(
|
||||
error.type === 'FileReadbackVerificationError'
|
||||
? createAntivirusErrorDialog(technicalDetails)
|
||||
: createGenericErrorDialog(technicalDetails)),
|
||||
);
|
||||
}
|
||||
|
||||
function createGenericErrorDialog(technicalDetails: string): Parameters<Dialog['showError']> {
|
||||
return [
|
||||
'Error Saving Script',
|
||||
[
|
||||
'An error occurred while saving the script.',
|
||||
'This issue may arise from insufficient permissions, limited disk space, or interference from security software.',
|
||||
'\n',
|
||||
'To address this:',
|
||||
'- Verify your permissions for the selected save directory.',
|
||||
'- Check available disk space.',
|
||||
'- Review your antivirus or security settings; adding an exclusion for privacy.sexy might be necessary.',
|
||||
'- Try saving the script to a different location or modifying your selection.',
|
||||
'- If the problem persists, reach out to the community for further assistance.',
|
||||
'\n',
|
||||
'Technical Details:',
|
||||
technicalDetails,
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
|
||||
function createAntivirusErrorDialog(technicalDetails: string): Parameters<Dialog['showError']> {
|
||||
return [
|
||||
'Potential Antivirus Intervention',
|
||||
[
|
||||
[
|
||||
'It seems your antivirus software might have blocked the saving of the script.',
|
||||
'privacy.sexy is secure, transparent, and open-source, but the scripts might still be mistakenly flagged by antivirus software such as Defender.',
|
||||
].join(' '),
|
||||
'\n',
|
||||
'To resolve this, consider:',
|
||||
'1. Checking your antivirus for any blocking notifications and allowing the script.',
|
||||
'2. Temporarily disabling real-time protection or adding an exclusion for privacy.sexy scripts.',
|
||||
'3. Re-attempting to save the script.',
|
||||
'4. If the problem continues, review your antivirus logs for more details.',
|
||||
'\n',
|
||||
'To handle false warnings in Defender: Open "Virus & threat protection" from the "Start" menu.',
|
||||
'\n',
|
||||
'Always ensure to re-enable your antivirus protection promptly.',
|
||||
'For more guidance, refer to your antivirus documentation.',
|
||||
'\n',
|
||||
'Technical Details:',
|
||||
technicalDetails,
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</p>
|
||||
<p>
|
||||
These false positives are common for scripts that modify system settings.
|
||||
privacy.sexy is secure, transparent, and open-source.
|
||||
Don't worry; privacy.sexy is secure, transparent, and open-source.
|
||||
</p>
|
||||
<p>
|
||||
To handle false warnings in Microsoft Defender:
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
import { ScriptDiagnosticData, ScriptDiagnosticsCollector } from '@/application/ScriptDiagnostics/ScriptDiagnosticsCollector';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { Dialog } from '@/presentation/common/Dialog';
|
||||
|
||||
export async function createScriptErrorDialog(
|
||||
information: ScriptErrorDetails,
|
||||
scriptDiagnosticsCollector: ScriptDiagnosticsCollector | undefined,
|
||||
): Promise<Parameters<Dialog['showError']>> {
|
||||
const diagnostics = await scriptDiagnosticsCollector?.collectDiagnosticInformation();
|
||||
if (information.isFileReadbackError) {
|
||||
return createAntivirusErrorDialog(information, diagnostics);
|
||||
}
|
||||
return createGenericErrorDialog(information, diagnostics);
|
||||
}
|
||||
|
||||
export interface ScriptErrorDetails {
|
||||
readonly errorContext: 'run' | 'save';
|
||||
readonly errorType: string;
|
||||
readonly errorMessage: string;
|
||||
readonly isFileReadbackError: boolean;
|
||||
}
|
||||
|
||||
function createGenericErrorDialog(
|
||||
information: ScriptErrorDetails,
|
||||
diagnostics: ScriptDiagnosticData | undefined,
|
||||
): Parameters<Dialog['showError']> {
|
||||
return [
|
||||
selectBasedOnErrorContext({
|
||||
runningScript: 'Error Running Script',
|
||||
savingScript: 'Error Saving Script',
|
||||
}, information),
|
||||
[
|
||||
selectBasedOnErrorContext({
|
||||
runningScript: 'An error occurred while running the script.',
|
||||
savingScript: 'An error occurred while saving the script.',
|
||||
}, information),
|
||||
'This error could be caused by insufficient permissions, limited disk space, or security software interference.',
|
||||
'\n',
|
||||
generateUnorderedSolutionList({
|
||||
title: 'To address this, you can:',
|
||||
solutions: [
|
||||
'Check if there is enough disk space and system resources are available.',
|
||||
selectBasedOnDirectoryPath({
|
||||
withoutDirectoryPath: 'Verify your access rights to the script\'s folder.',
|
||||
withDirectoryPath: (directory) => `Verify your access rights to the script's folder: "${directory}".`,
|
||||
}, diagnostics),
|
||||
[
|
||||
'Check if antivirus or security software has mistakenly blocked the script.',
|
||||
'Don\'t worry; privacy.sexy is secure, transparent, and open-source, but the scripts might still be mistakenly flagged by antivirus software.',
|
||||
'Temporarily disabling the security software may resolve this.',
|
||||
].join(' '),
|
||||
selectBasedOnErrorContext({
|
||||
runningScript: 'Confirm that you have the necessary permissions to execute scripts on your system.',
|
||||
savingScript: 'Try saving the script to a different location.',
|
||||
}, information),
|
||||
generateTryDifferentSelectionAdvice(information),
|
||||
'If the problem persists, reach out to the community for further assistance.',
|
||||
],
|
||||
}),
|
||||
'\n',
|
||||
generateTechnicalDetails(information),
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
|
||||
function createAntivirusErrorDialog(
|
||||
information: ScriptErrorDetails,
|
||||
diagnostics: ScriptDiagnosticData | undefined,
|
||||
): Parameters<Dialog['showError']> {
|
||||
const defenderSteps = generateDefenderSteps(information, diagnostics);
|
||||
return [
|
||||
'Possible Antivirus Script Block',
|
||||
[
|
||||
[
|
||||
'It seems your antivirus software might have removed the script.',
|
||||
'Don\'t worry; privacy.sexy is secure, transparent, and open-source, but the scripts might still be mistakenly flagged by antivirus software.',
|
||||
].join(' '),
|
||||
'\n',
|
||||
selectBasedOnErrorContext({
|
||||
savingScript: generateOrderedSolutionList({
|
||||
title: 'To address this, you can:',
|
||||
solutions: [
|
||||
'Check your antivirus for any blocking notifications and allow the script.',
|
||||
'Disable antivirus or security software temporarily or add an exclusion.',
|
||||
'Save the script again.',
|
||||
],
|
||||
}),
|
||||
runningScript: generateOrderedSolutionList({
|
||||
title: 'To address this, you can:',
|
||||
solutions: [
|
||||
selectBasedOnDirectoryPath({
|
||||
withoutDirectoryPath: 'Disable antivirus or security software temporarily or add an exclusion.',
|
||||
withDirectoryPath: (directory) => `Disable antivirus or security software temporarily or add a directory exclusion for scripts executed from: "${directory}".`,
|
||||
}, diagnostics),
|
||||
'Run the script again.',
|
||||
],
|
||||
}),
|
||||
}, information),
|
||||
defenderSteps ? `\n${defenderSteps}\n` : '\n',
|
||||
[
|
||||
'It\'s important to re-enable your antivirus protection after resolving the issue for your security.',
|
||||
'For more guidance, refer to your antivirus documentation.',
|
||||
].join(' '),
|
||||
'\n',
|
||||
generateUnorderedSolutionList({
|
||||
title: 'If the problem persists:',
|
||||
solutions: [
|
||||
generateTryDifferentSelectionAdvice(information),
|
||||
'Consider reporting this as a false positive to your antivirus provider.',
|
||||
'Review your antivirus logs for more details.',
|
||||
'Reach out to the community for further assistance.',
|
||||
],
|
||||
}),
|
||||
'\n',
|
||||
generateTechnicalDetails(information),
|
||||
].join('\n'),
|
||||
];
|
||||
}
|
||||
|
||||
interface SolutionListOptions {
|
||||
readonly solutions: readonly string[];
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
function generateUnorderedSolutionList(options: SolutionListOptions) {
|
||||
return [
|
||||
options.title,
|
||||
...options.solutions.map((step) => `- ${step}`),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function generateTechnicalDetails(information: ScriptErrorDetails) {
|
||||
const maxErrorMessageCharacters = 100;
|
||||
const trimmedErrorMessage = information.errorMessage.length > maxErrorMessageCharacters
|
||||
? `${information.errorMessage.substring(0, maxErrorMessageCharacters - 3)}...`
|
||||
: information.errorMessage;
|
||||
return `Technical Details: [${information.errorType}] ${trimmedErrorMessage}`;
|
||||
}
|
||||
|
||||
function generateTryDifferentSelectionAdvice(information: ScriptErrorDetails) {
|
||||
return selectBasedOnErrorContext({
|
||||
runningScript: 'Run a different script selection to check if the problem is script-specific.',
|
||||
savingScript: 'Save a different script selection to check if the problem is script-specific.',
|
||||
}, information);
|
||||
}
|
||||
|
||||
function selectBasedOnDirectoryPath<T>(
|
||||
options: {
|
||||
readonly withoutDirectoryPath: T,
|
||||
withDirectoryPath: (directoryPath: string) => T,
|
||||
},
|
||||
diagnostics: ScriptDiagnosticData | undefined,
|
||||
): T {
|
||||
if (!diagnostics?.scriptsDirectoryAbsolutePath) {
|
||||
return options.withoutDirectoryPath;
|
||||
}
|
||||
return options.withDirectoryPath(diagnostics.scriptsDirectoryAbsolutePath);
|
||||
}
|
||||
|
||||
function generateOrderedSolutionList(options: SolutionListOptions): string {
|
||||
return [
|
||||
options.title,
|
||||
...options.solutions.map((step, index) => `${index + 1}. ${step}`),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function generateDefenderSteps(
|
||||
information: ScriptErrorDetails,
|
||||
diagnostics: ScriptDiagnosticData | undefined,
|
||||
): string | undefined {
|
||||
if (diagnostics?.currentOperatingSystem !== OperatingSystem.Windows) {
|
||||
return undefined;
|
||||
}
|
||||
return generateOrderedSolutionList({
|
||||
title: 'To handle false warnings in Defender:',
|
||||
solutions: [
|
||||
'Open "Virus & threat protection" via the "Start" menu.',
|
||||
'Open "Manage settings" under "Virus & threat protection settings" heading.',
|
||||
...selectBasedOnErrorContext({
|
||||
savingScript: [
|
||||
'Disable "Real-time protection" or add an exclusion by selecting "Add or remove exclusions".',
|
||||
],
|
||||
runningScript: selectBasedOnDirectoryPath({
|
||||
withoutDirectoryPath: [
|
||||
'Disable real-time protection or add exclusion for scripts.',
|
||||
],
|
||||
withDirectoryPath: (directory) => [
|
||||
'Open "Add or remove exclusions" under "Add or remove exclusions".',
|
||||
`Add directory exclusion for "${directory}".`,
|
||||
],
|
||||
}, diagnostics),
|
||||
}, information),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function selectBasedOnErrorContext<T>(options: {
|
||||
readonly savingScript: T;
|
||||
readonly runningScript: T;
|
||||
}, information: ScriptErrorDetails): T {
|
||||
if (information.errorContext === 'run') {
|
||||
return options.runningScript;
|
||||
}
|
||||
return options.savingScript;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
|
||||
|
||||
export function useScriptDiagnosticsCollector(
|
||||
window: Partial<WindowVariables> = globalThis.window,
|
||||
) {
|
||||
return {
|
||||
scriptDiagnosticsCollector: window?.scriptDiagnosticsCollector,
|
||||
};
|
||||
}
|
||||
@@ -3,17 +3,22 @@ import { CodeRunner } from '@/application/CodeRunner/CodeRunner';
|
||||
import { Dialog } from '@/presentation/common/Dialog';
|
||||
import { ElectronDialog } from '@/infrastructure/Dialog/Electron/ElectronDialog';
|
||||
import { IpcChannel } from '@/presentation/electron/shared/IpcBridging/IpcChannel';
|
||||
import { ScriptEnvironmentDiagnosticsCollector } from '@/infrastructure/ScriptDiagnostics/ScriptEnvironmentDiagnosticsCollector';
|
||||
import { ScriptDiagnosticsCollector } from '@/application/ScriptDiagnostics/ScriptDiagnosticsCollector';
|
||||
import { registerIpcChannel } from '../shared/IpcBridging/IpcProxy';
|
||||
import { ChannelDefinitionKey, IpcChannelDefinitions } from '../shared/IpcBridging/IpcChannelDefinitions';
|
||||
|
||||
export function registerAllIpcChannels(
|
||||
registrar: IpcChannelRegistrar = registerIpcChannel,
|
||||
createCodeRunner: CodeRunnerFactory = () => new ScriptFileCodeRunner(),
|
||||
createDialog: DialogFactory = () => new ElectronDialog(),
|
||||
registrar: IpcChannelRegistrar = registerIpcChannel,
|
||||
createScriptDiagnosticsCollector
|
||||
: ScriptDiagnosticsCollectorFactory = () => new ScriptEnvironmentDiagnosticsCollector(),
|
||||
) {
|
||||
const ipcInstanceCreators: IpcChannelRegistrars = {
|
||||
CodeRunner: () => createCodeRunner(),
|
||||
Dialog: () => createDialog(),
|
||||
ScriptDiagnosticsCollector: () => createScriptDiagnosticsCollector(),
|
||||
};
|
||||
Object.entries(ipcInstanceCreators).forEach(([name, instanceFactory]) => {
|
||||
try {
|
||||
@@ -26,9 +31,11 @@ export function registerAllIpcChannels(
|
||||
});
|
||||
}
|
||||
|
||||
export type IpcChannelRegistrar = typeof registerIpcChannel;
|
||||
|
||||
export type CodeRunnerFactory = () => CodeRunner;
|
||||
export type DialogFactory = () => Dialog;
|
||||
export type IpcChannelRegistrar = typeof registerIpcChannel;
|
||||
export type ScriptDiagnosticsCollectorFactory = () => ScriptDiagnosticsCollector;
|
||||
|
||||
type RegistrationChannel<T extends ChannelDefinitionKey> = (typeof IpcChannelDefinitions)[T];
|
||||
type ExtractChannelServiceType<T> = T extends IpcChannel<infer U> ? U : never;
|
||||
|
||||
@@ -7,10 +7,10 @@ import { IpcChannelDefinitions } from '../../shared/IpcBridging/IpcChannelDefini
|
||||
import { createSecureFacade } from './SecureFacadeCreator';
|
||||
|
||||
export function provideWindowVariables(
|
||||
createLogger: LoggerFactory = () => createElectronLogger(),
|
||||
convertToOs = convertPlatformToOs,
|
||||
createApiFacade: ApiFacadeFactory = createSecureFacade,
|
||||
ipcConsumerCreator: IpcConsumerProxyCreator = createIpcConsumerProxy,
|
||||
convertToOs = convertPlatformToOs,
|
||||
createLogger: LoggerFactory = () => createElectronLogger(),
|
||||
): WindowVariables {
|
||||
// Enforces mandatory variable availability at compile time
|
||||
const variables: RequiredWindowVariables = {
|
||||
@@ -19,6 +19,9 @@ export function provideWindowVariables(
|
||||
os: convertToOs(process.platform),
|
||||
codeRunner: ipcConsumerCreator(IpcChannelDefinitions.CodeRunner),
|
||||
dialog: ipcConsumerCreator(IpcChannelDefinitions.Dialog),
|
||||
scriptDiagnosticsCollector: ipcConsumerCreator(
|
||||
IpcChannelDefinitions.ScriptDiagnosticsCollector,
|
||||
),
|
||||
};
|
||||
return variables;
|
||||
}
|
||||
@@ -26,8 +29,8 @@ export function provideWindowVariables(
|
||||
type RequiredWindowVariables = PartiallyRequired<WindowVariables, 'os' /* | 'anotherOptionalKey'.. */>;
|
||||
type PartiallyRequired<T, K extends keyof T> = Required<Omit<T, K>> & Pick<T, K>;
|
||||
|
||||
export type LoggerFactory = () => Logger;
|
||||
|
||||
export type ApiFacadeFactory = typeof createSecureFacade;
|
||||
|
||||
export type IpcConsumerProxyCreator = typeof createIpcConsumerProxy;
|
||||
|
||||
export type LoggerFactory = () => Logger;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { FunctionKeys } from '@/TypeHelpers';
|
||||
import { CodeRunner } from '@/application/CodeRunner/CodeRunner';
|
||||
import { Dialog } from '@/presentation/common/Dialog';
|
||||
import { ScriptDiagnosticsCollector } from '@/application/ScriptDiagnostics/ScriptDiagnosticsCollector';
|
||||
import { IpcChannel } from './IpcChannel';
|
||||
|
||||
export const IpcChannelDefinitions = {
|
||||
CodeRunner: defineElectronIpcChannel<CodeRunner>('code-run', ['runCode']),
|
||||
Dialog: defineElectronIpcChannel<Dialog>('dialogs', ['showError', 'saveFile']),
|
||||
ScriptDiagnosticsCollector: defineElectronIpcChannel<ScriptDiagnosticsCollector>('script-diagnostics-collector', ['collectDiagnosticInformation']),
|
||||
} as const;
|
||||
|
||||
export type ChannelDefinitionKey = keyof typeof IpcChannelDefinitions;
|
||||
|
||||
@@ -51,8 +51,11 @@
|
||||
</style>
|
||||
<div id="javascriptDisabled">
|
||||
<h1>Problem loading page</h1>
|
||||
<p>The page does not work without JavaScript enabled. Please enable it to use privacy.sexy. There's no shady stuff
|
||||
as 100% of the website is open source.</p>
|
||||
<p>
|
||||
The page does not work without JavaScript enabled.
|
||||
Please enable it to use privacy.sexy.
|
||||
Don't worry; privacy.sexy is secure, transparent, and open-source.
|
||||
</p>
|
||||
</div>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { useUserSelectionState } from '@/presentation/components/Shared/Hoo
|
||||
import type { useLogger } from '@/presentation/components/Shared/Hooks/Log/UseLogger';
|
||||
import type { useCodeRunner } from './components/Shared/Hooks/UseCodeRunner';
|
||||
import type { useDialog } from './components/Shared/Hooks/Dialog/UseDialog';
|
||||
import type { useScriptDiagnosticsCollector } from './components/Shared/Hooks/UseScriptDiagnosticsCollector';
|
||||
|
||||
export const InjectionKeys = {
|
||||
useCollectionState: defineTransientKey<ReturnType<typeof useCollectionState>>('useCollectionState'),
|
||||
@@ -21,6 +22,7 @@ export const InjectionKeys = {
|
||||
useLogger: defineTransientKey<ReturnType<typeof useLogger>>('useLogger'),
|
||||
useCodeRunner: defineTransientKey<ReturnType<typeof useCodeRunner>>('useCodeRunner'),
|
||||
useDialog: defineTransientKey<ReturnType<typeof useDialog>>('useDialog'),
|
||||
useScriptDiagnosticsCollector: defineTransientKey<ReturnType<typeof useScriptDiagnosticsCollector>>('useScriptDiagnostics'),
|
||||
};
|
||||
|
||||
export interface InjectionKeyWithLifetime<T> {
|
||||
|
||||
Reference in New Issue
Block a user