- Add setOpenClawDefaultModel() to write the correct model to ~/.openclaw/openclaw.json based on the selected provider - Maps: openrouter -> openrouter/anthropic/claude-sonnet-4, anthropic -> anthropic/claude-sonnet-4, etc. - Call setOpenClawDefaultModel on provider:save IPC handler - Fixes "No API key found for provider anthropic" when user configured OpenRouter (wrong default model was being used)
708 lines
21 KiB
TypeScript
708 lines
21 KiB
TypeScript
/**
|
|
* IPC Handlers
|
|
* Registers all IPC handlers for main-renderer communication
|
|
*/
|
|
import { ipcMain, BrowserWindow, shell, dialog, app } from 'electron';
|
|
import { GatewayManager } from '../gateway/manager';
|
|
import {
|
|
storeApiKey,
|
|
getApiKey,
|
|
deleteApiKey,
|
|
hasApiKey,
|
|
saveProvider,
|
|
getProvider,
|
|
getAllProviders,
|
|
deleteProvider,
|
|
setDefaultProvider,
|
|
getDefaultProvider,
|
|
getAllProvidersWithKeyInfo,
|
|
isEncryptionAvailable,
|
|
type ProviderConfig,
|
|
} from '../utils/secure-storage';
|
|
import { getOpenClawStatus } from '../utils/paths';
|
|
import { getSetting } from '../utils/store';
|
|
import { saveProviderKeyToOpenClaw, setOpenClawDefaultModel } from '../utils/openclaw-auth';
|
|
|
|
/**
|
|
* Register all IPC handlers
|
|
*/
|
|
export function registerIpcHandlers(
|
|
gatewayManager: GatewayManager,
|
|
mainWindow: BrowserWindow
|
|
): void {
|
|
// Gateway handlers
|
|
registerGatewayHandlers(gatewayManager, mainWindow);
|
|
|
|
// OpenClaw handlers
|
|
registerOpenClawHandlers();
|
|
|
|
// Provider handlers
|
|
registerProviderHandlers();
|
|
|
|
// Shell handlers
|
|
registerShellHandlers();
|
|
|
|
// Dialog handlers
|
|
registerDialogHandlers();
|
|
|
|
// App handlers
|
|
registerAppHandlers();
|
|
}
|
|
|
|
/**
|
|
* Gateway-related IPC handlers
|
|
*/
|
|
function registerGatewayHandlers(
|
|
gatewayManager: GatewayManager,
|
|
mainWindow: BrowserWindow
|
|
): void {
|
|
// Get Gateway status
|
|
ipcMain.handle('gateway:status', () => {
|
|
return gatewayManager.getStatus();
|
|
});
|
|
|
|
// Check if Gateway is connected
|
|
ipcMain.handle('gateway:isConnected', () => {
|
|
return gatewayManager.isConnected();
|
|
});
|
|
|
|
// Start Gateway
|
|
ipcMain.handle('gateway:start', async () => {
|
|
try {
|
|
await gatewayManager.start();
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Stop Gateway
|
|
ipcMain.handle('gateway:stop', async () => {
|
|
try {
|
|
await gatewayManager.stop();
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Restart Gateway
|
|
ipcMain.handle('gateway:restart', async () => {
|
|
try {
|
|
await gatewayManager.restart();
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Gateway RPC call
|
|
ipcMain.handle('gateway:rpc', async (_, method: string, params?: unknown, timeoutMs?: number) => {
|
|
try {
|
|
const result = await gatewayManager.rpc(method, params, timeoutMs);
|
|
return { success: true, result };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Get the Control UI URL with token for embedding
|
|
ipcMain.handle('gateway:getControlUiUrl', async () => {
|
|
try {
|
|
const status = gatewayManager.getStatus();
|
|
const token = await getSetting('gatewayToken');
|
|
const port = status.port || 18789;
|
|
// Pass token as query param - Control UI will store it in localStorage
|
|
const url = `http://127.0.0.1:${port}/?token=${encodeURIComponent(token)}`;
|
|
return { success: true, url, port, token };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Health check
|
|
ipcMain.handle('gateway:health', async () => {
|
|
try {
|
|
const health = await gatewayManager.checkHealth();
|
|
return { success: true, ...health };
|
|
} catch (error) {
|
|
return { success: false, ok: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Forward Gateway events to renderer
|
|
gatewayManager.on('status', (status) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:status-changed', status);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('message', (message) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:message', message);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('notification', (notification) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:notification', notification);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('channel:status', (data) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:channel-status', data);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('chat:message', (data) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:chat-message', data);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('exit', (code) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:exit', code);
|
|
}
|
|
});
|
|
|
|
gatewayManager.on('error', (error) => {
|
|
if (!mainWindow.isDestroyed()) {
|
|
mainWindow.webContents.send('gateway:error', error.message);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* OpenClaw-related IPC handlers
|
|
* For checking submodule status
|
|
*/
|
|
function registerOpenClawHandlers(): void {
|
|
// Get OpenClaw submodule status
|
|
ipcMain.handle('openclaw:status', () => {
|
|
return getOpenClawStatus();
|
|
});
|
|
|
|
// Check if OpenClaw is ready (submodule present and dependencies installed)
|
|
ipcMain.handle('openclaw:isReady', () => {
|
|
const status = getOpenClawStatus();
|
|
return status.submoduleExists && status.isInstalled;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Provider-related IPC handlers
|
|
*/
|
|
function registerProviderHandlers(): void {
|
|
// Check if encryption is available
|
|
ipcMain.handle('provider:encryptionAvailable', () => {
|
|
return isEncryptionAvailable();
|
|
});
|
|
|
|
// Get all providers with key info
|
|
ipcMain.handle('provider:list', async () => {
|
|
return await getAllProvidersWithKeyInfo();
|
|
});
|
|
|
|
// Get a specific provider
|
|
ipcMain.handle('provider:get', async (_, providerId: string) => {
|
|
return await getProvider(providerId);
|
|
});
|
|
|
|
// Save a provider configuration
|
|
ipcMain.handle('provider:save', async (_, config: ProviderConfig, apiKey?: string) => {
|
|
try {
|
|
// Save the provider config
|
|
await saveProvider(config);
|
|
|
|
// Store the API key if provided
|
|
if (apiKey) {
|
|
await storeApiKey(config.id, apiKey);
|
|
|
|
// Also write to OpenClaw auth-profiles.json so the gateway can use it
|
|
try {
|
|
saveProviderKeyToOpenClaw(config.type, apiKey);
|
|
} catch (err) {
|
|
console.warn('Failed to save key to OpenClaw auth-profiles:', err);
|
|
}
|
|
}
|
|
|
|
// Set the default model in OpenClaw config based on provider type
|
|
try {
|
|
setOpenClawDefaultModel(config.type);
|
|
} catch (err) {
|
|
console.warn('Failed to set OpenClaw default model:', err);
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Delete a provider
|
|
ipcMain.handle('provider:delete', async (_, providerId: string) => {
|
|
try {
|
|
await deleteProvider(providerId);
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Update API key for a provider
|
|
ipcMain.handle('provider:setApiKey', async (_, providerId: string, apiKey: string) => {
|
|
try {
|
|
await storeApiKey(providerId, apiKey);
|
|
|
|
// Also write to OpenClaw auth-profiles.json
|
|
// Resolve provider type from stored config, or use providerId as type
|
|
const provider = await getProvider(providerId);
|
|
const providerType = provider?.type || providerId;
|
|
try {
|
|
saveProviderKeyToOpenClaw(providerType, apiKey);
|
|
} catch (err) {
|
|
console.warn('Failed to save key to OpenClaw auth-profiles:', err);
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Delete API key for a provider
|
|
ipcMain.handle('provider:deleteApiKey', async (_, providerId: string) => {
|
|
try {
|
|
await deleteApiKey(providerId);
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Check if a provider has an API key
|
|
ipcMain.handle('provider:hasApiKey', async (_, providerId: string) => {
|
|
return await hasApiKey(providerId);
|
|
});
|
|
|
|
// Get the actual API key (for internal use only - be careful!)
|
|
ipcMain.handle('provider:getApiKey', async (_, providerId: string) => {
|
|
return await getApiKey(providerId);
|
|
});
|
|
|
|
// Set default provider
|
|
ipcMain.handle('provider:setDefault', async (_, providerId: string) => {
|
|
try {
|
|
await setDefaultProvider(providerId);
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: String(error) };
|
|
}
|
|
});
|
|
|
|
// Get default provider
|
|
ipcMain.handle('provider:getDefault', async () => {
|
|
return await getDefaultProvider();
|
|
});
|
|
|
|
// Validate API key by making a real test request to the provider
|
|
// providerId can be either a stored provider ID or a provider type (e.g., 'openrouter', 'anthropic')
|
|
ipcMain.handle('provider:validateKey', async (_, providerId: string, apiKey: string) => {
|
|
try {
|
|
// First try to get existing provider
|
|
const provider = await getProvider(providerId);
|
|
|
|
// Use provider.type if provider exists, otherwise use providerId as the type
|
|
// This allows validation during setup when provider hasn't been saved yet
|
|
const providerType = provider?.type || providerId;
|
|
|
|
console.log(`Validating API key for provider type: ${providerType}`);
|
|
return await validateApiKeyWithProvider(providerType, apiKey);
|
|
} catch (error) {
|
|
console.error('Validation error:', error);
|
|
return { valid: false, error: String(error) };
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate API key by making a real chat completion API call to the provider
|
|
* This sends a minimal "hi" message to verify the key works
|
|
*/
|
|
async function validateApiKeyWithProvider(
|
|
providerType: string,
|
|
apiKey: string
|
|
): Promise<{ valid: boolean; error?: string }> {
|
|
const trimmedKey = apiKey.trim();
|
|
if (!trimmedKey) {
|
|
return { valid: false, error: 'API key is required' };
|
|
}
|
|
|
|
try {
|
|
switch (providerType) {
|
|
case 'anthropic':
|
|
return await validateAnthropicKey(trimmedKey);
|
|
case 'openai':
|
|
return await validateOpenAIKey(trimmedKey);
|
|
case 'google':
|
|
return await validateGoogleKey(trimmedKey);
|
|
case 'openrouter':
|
|
return await validateOpenRouterKey(trimmedKey);
|
|
case 'ollama':
|
|
// Ollama doesn't require API key validation
|
|
return { valid: true };
|
|
default:
|
|
// For custom providers, just check the key is not empty
|
|
return { valid: true };
|
|
}
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
return { valid: false, error: errorMessage };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse error message from API response
|
|
*/
|
|
function parseApiError(data: unknown): string {
|
|
if (!data || typeof data !== 'object') return 'Unknown error';
|
|
|
|
// Anthropic format: { error: { message: "..." } }
|
|
// OpenAI format: { error: { message: "..." } }
|
|
// Google format: { error: { message: "..." } }
|
|
const obj = data as { error?: { message?: string; type?: string }; message?: string };
|
|
|
|
if (obj.error?.message) return obj.error.message;
|
|
if (obj.message) return obj.message;
|
|
|
|
return 'Unknown error';
|
|
}
|
|
|
|
/**
|
|
* Validate Anthropic API key by making a minimal chat completion request
|
|
*/
|
|
async function validateAnthropicKey(apiKey: string): Promise<{ valid: boolean; error?: string }> {
|
|
try {
|
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-api-key': apiKey,
|
|
'anthropic-version': '2023-06-01',
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'claude-3-haiku-20240307',
|
|
max_tokens: 1,
|
|
messages: [{ role: 'user', content: 'hi' }],
|
|
}),
|
|
});
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
|
|
if (response.ok) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Authentication error
|
|
if (response.status === 401) {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
|
|
// Permission error (invalid key format, etc.)
|
|
if (response.status === 403) {
|
|
return { valid: false, error: parseApiError(data) };
|
|
}
|
|
|
|
// Rate limit or overloaded - key is valid but service is busy
|
|
if (response.status === 429 || response.status === 529) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Model not found or bad request but auth passed - key is valid
|
|
if (response.status === 400 || response.status === 404) {
|
|
const errorType = (data as { error?: { type?: string } })?.error?.type;
|
|
if (errorType === 'authentication_error' || errorType === 'invalid_api_key') {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
// Other errors like invalid_request_error mean the key is valid
|
|
return { valid: true };
|
|
}
|
|
|
|
return { valid: false, error: parseApiError(data) || `API error: ${response.status}` };
|
|
} catch (error) {
|
|
return { valid: false, error: `Connection error: ${error instanceof Error ? error.message : String(error)}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate OpenAI API key by making a minimal chat completion request
|
|
*/
|
|
async function validateOpenAIKey(apiKey: string): Promise<{ valid: boolean; error?: string }> {
|
|
try {
|
|
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'gpt-4o-mini',
|
|
max_tokens: 1,
|
|
messages: [{ role: 'user', content: 'hi' }],
|
|
}),
|
|
});
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
|
|
if (response.ok) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Authentication error
|
|
if (response.status === 401) {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
|
|
// Rate limit - key is valid
|
|
if (response.status === 429) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Model not found or bad request but auth passed - key is valid
|
|
if (response.status === 400 || response.status === 404) {
|
|
const errorCode = (data as { error?: { code?: string } })?.error?.code;
|
|
if (errorCode === 'invalid_api_key') {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
return { valid: true };
|
|
}
|
|
|
|
return { valid: false, error: parseApiError(data) || `API error: ${response.status}` };
|
|
} catch (error) {
|
|
return { valid: false, error: `Connection error: ${error instanceof Error ? error.message : String(error)}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate Google (Gemini) API key by making a minimal generate content request
|
|
*/
|
|
async function validateGoogleKey(apiKey: string): Promise<{ valid: boolean; error?: string }> {
|
|
try {
|
|
const response = await fetch(
|
|
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: 'hi' }] }],
|
|
generationConfig: { maxOutputTokens: 1 },
|
|
}),
|
|
}
|
|
);
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
|
|
if (response.ok) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Authentication error
|
|
if (response.status === 400 || response.status === 401 || response.status === 403) {
|
|
const errorStatus = (data as { error?: { status?: string } })?.error?.status;
|
|
if (errorStatus === 'UNAUTHENTICATED' || errorStatus === 'PERMISSION_DENIED') {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
// Check if it's actually an auth error
|
|
const errorMessage = parseApiError(data).toLowerCase();
|
|
if (errorMessage.includes('api key') || errorMessage.includes('invalid') || errorMessage.includes('unauthorized')) {
|
|
return { valid: false, error: parseApiError(data) };
|
|
}
|
|
// Other errors mean key is valid
|
|
return { valid: true };
|
|
}
|
|
|
|
// Rate limit - key is valid
|
|
if (response.status === 429) {
|
|
return { valid: true };
|
|
}
|
|
|
|
return { valid: false, error: parseApiError(data) || `API error: ${response.status}` };
|
|
} catch (error) {
|
|
return { valid: false, error: `Connection error: ${error instanceof Error ? error.message : String(error)}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate OpenRouter API key by making a minimal chat completion request
|
|
*/
|
|
async function validateOpenRouterKey(apiKey: string): Promise<{ valid: boolean; error?: string }> {
|
|
try {
|
|
// Use a popular free model for validation
|
|
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'HTTP-Referer': 'https://clawx.app',
|
|
'X-Title': 'ClawX',
|
|
},
|
|
body: JSON.stringify({
|
|
model: 'meta-llama/llama-3.2-3b-instruct:free',
|
|
max_tokens: 1,
|
|
messages: [{ role: 'user', content: 'hi' }],
|
|
}),
|
|
});
|
|
|
|
const data = await response.json().catch(() => ({}));
|
|
console.log('OpenRouter validation response:', response.status, JSON.stringify(data));
|
|
|
|
// Helper to check if error message indicates auth failure
|
|
const isAuthError = (d: unknown): boolean => {
|
|
const errorObj = (d as { error?: { message?: string; code?: number | string; type?: string } })?.error;
|
|
if (!errorObj) return false;
|
|
|
|
const message = (errorObj.message || '').toLowerCase();
|
|
const code = errorObj.code;
|
|
const type = (errorObj.type || '').toLowerCase();
|
|
|
|
// Check for explicit auth-related errors
|
|
if (code === 401 || code === '401' || code === 403 || code === '403') return true;
|
|
if (type.includes('auth') || type.includes('invalid')) return true;
|
|
if (message.includes('invalid api key') || message.includes('invalid key') ||
|
|
message.includes('unauthorized') || message.includes('authentication') ||
|
|
message.includes('invalid credentials') || message.includes('api key is not valid')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (response.ok) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Always check for auth errors in the response body first
|
|
if (isAuthError(data)) {
|
|
// Return user-friendly message instead of raw API errors like "User not found."
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
|
|
// Authentication error status codes - always return user-friendly message
|
|
if (response.status === 401 || response.status === 403) {
|
|
return { valid: false, error: 'Invalid API key' };
|
|
}
|
|
|
|
// Rate limit - key is valid
|
|
if (response.status === 429) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Payment required or insufficient credits - key format is valid
|
|
if (response.status === 402) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// For 400/404, we must be very careful - only consider valid if clearly not an auth issue
|
|
if (response.status === 400 || response.status === 404) {
|
|
// If we got here without detecting auth error, it might be a model issue
|
|
// But be conservative - require explicit success indication
|
|
const errorObj = (data as { error?: { message?: string; code?: number } })?.error;
|
|
const message = (errorObj?.message || '').toLowerCase();
|
|
|
|
// Only consider valid if the error is clearly about the model, not the key
|
|
if (message.includes('model') && !message.includes('key') && !message.includes('auth')) {
|
|
return { valid: true };
|
|
}
|
|
|
|
// Default to invalid for ambiguous 400/404 errors
|
|
return { valid: false, error: parseApiError(data) || 'Invalid API key or request' };
|
|
}
|
|
|
|
return { valid: false, error: parseApiError(data) || `API error: ${response.status}` };
|
|
} catch (error) {
|
|
return { valid: false, error: `Connection error: ${error instanceof Error ? error.message : String(error)}` };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shell-related IPC handlers
|
|
*/
|
|
function registerShellHandlers(): void {
|
|
// Open external URL
|
|
ipcMain.handle('shell:openExternal', async (_, url: string) => {
|
|
await shell.openExternal(url);
|
|
});
|
|
|
|
// Open path in file explorer
|
|
ipcMain.handle('shell:showItemInFolder', async (_, path: string) => {
|
|
shell.showItemInFolder(path);
|
|
});
|
|
|
|
// Open path
|
|
ipcMain.handle('shell:openPath', async (_, path: string) => {
|
|
return await shell.openPath(path);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Dialog-related IPC handlers
|
|
*/
|
|
function registerDialogHandlers(): void {
|
|
// Show open dialog
|
|
ipcMain.handle('dialog:open', async (_, options: Electron.OpenDialogOptions) => {
|
|
const result = await dialog.showOpenDialog(options);
|
|
return result;
|
|
});
|
|
|
|
// Show save dialog
|
|
ipcMain.handle('dialog:save', async (_, options: Electron.SaveDialogOptions) => {
|
|
const result = await dialog.showSaveDialog(options);
|
|
return result;
|
|
});
|
|
|
|
// Show message box
|
|
ipcMain.handle('dialog:message', async (_, options: Electron.MessageBoxOptions) => {
|
|
const result = await dialog.showMessageBox(options);
|
|
return result;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* App-related IPC handlers
|
|
*/
|
|
function registerAppHandlers(): void {
|
|
// Get app version
|
|
ipcMain.handle('app:version', () => {
|
|
return app.getVersion();
|
|
});
|
|
|
|
// Get app name
|
|
ipcMain.handle('app:name', () => {
|
|
return app.getName();
|
|
});
|
|
|
|
// Get app path
|
|
ipcMain.handle('app:getPath', (_, name: Parameters<typeof app.getPath>[0]) => {
|
|
return app.getPath(name);
|
|
});
|
|
|
|
// Get platform
|
|
ipcMain.handle('app:platform', () => {
|
|
return process.platform;
|
|
});
|
|
|
|
// Quit app
|
|
ipcMain.handle('app:quit', () => {
|
|
app.quit();
|
|
});
|
|
|
|
// Relaunch app
|
|
ipcMain.handle('app:relaunch', () => {
|
|
app.relaunch();
|
|
app.quit();
|
|
});
|
|
}
|