Files
OpenQode/bin/goose-ultra-final/electron/preload.js
Gemini AI b6f2c68243 Feature: Secret Key User System & Inline Qwen OAuth
Phase 1: User Authentication System
- Added user-data.js: Secret code generation, user creation, session management
- Added UserAuth.tsx: LoginGate, onboarding wizard, secret code reveal dialog
- Users get isolated environments (projects, chats, API keys)

Phase 2: Inline Qwen OAuth (No External CLI)
- Added qwen-oauth.js: Device Authorization Grant with PKCE
- Added QwenAuthDialog.tsx: Full inline auth flow with user code display
- Tokens saved per-user with legacy fallback

Phase 3: Integration
- Updated main.js with IPC handlers for user auth and Qwen OAuth
- Updated preload.js with electron.user and electron.qwenAuth bridges
- Wrapped App.tsx with LoginGate for authentication enforcement

Based on analysis of qwen-code repository OAuth implementation.
2025-12-20 18:31:50 +04:00

161 lines
7.9 KiB
JavaScript

const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
getAppPath: () => ipcRenderer.invoke('get-app-path'),
getPlatform: () => ipcRenderer.invoke('get-platform'),
getServerPort: () => ipcRenderer.invoke('get-server-port'),
exportProjectZip: (projectId) => ipcRenderer.invoke('export-project-zip', { projectId }),
// Chat Bridge
startChat: (messages, model) => ipcRenderer.send('chat-stream-start', { messages, model }),
onChatChunk: (callback) => ipcRenderer.on('chat-chunk', (_, chunk) => callback(chunk)),
onChatStatus: (callback) => ipcRenderer.on('chat-status', (_, status) => callback(status)),
onChatComplete: (callback) => ipcRenderer.on('chat-complete', (_, response) => callback(response)),
onChatError: (callback) => ipcRenderer.on('chat-error', (_, error) => callback(error)),
removeChatListeners: () => {
ipcRenderer.removeAllListeners('chat-chunk');
ipcRenderer.removeAllListeners('chat-status');
ipcRenderer.removeAllListeners('chat-complete');
ipcRenderer.removeAllListeners('chat-error');
},
// Filesystem
fs: {
list: (path) => ipcRenderer.invoke('fs-list', path),
read: (path) => ipcRenderer.invoke('fs-read', path),
write: (path, content) => ipcRenderer.invoke('fs-write', { path, content }),
delete: (path) => ipcRenderer.invoke('fs-delete', path)
},
// Image Generation (ChatGPT-like)
image: {
generate: (prompt, options) => ipcRenderer.invoke('image-generate', { prompt, options }),
detect: (message) => ipcRenderer.invoke('image-detect', { message })
},
// IT Expert Execution Bridge
runPowerShell: (execSessionId, script, enabled) => ipcRenderer.send('exec-run-powershell', { execSessionId, script, enabled }),
cancelExecution: (execSessionId) => ipcRenderer.send('exec-cancel', { execSessionId }),
onExecStart: (callback) => ipcRenderer.on('exec-start', (_, data) => callback(data)),
onExecChunk: (callback) => ipcRenderer.on('exec-chunk', (_, data) => callback(data)),
onExecComplete: (callback) => ipcRenderer.on('exec-complete', (_, data) => callback(data)),
onExecError: (callback) => ipcRenderer.on('exec-error', (_, data) => callback(data)),
onExecCancelled: (callback) => ipcRenderer.on('exec-cancelled', (_, data) => callback(data)),
removeExecListeners: () => {
ipcRenderer.removeAllListeners('exec-start');
ipcRenderer.removeAllListeners('exec-chunk');
ipcRenderer.removeAllListeners('exec-complete');
ipcRenderer.removeAllListeners('exec-error');
ipcRenderer.removeAllListeners('exec-cancelled');
},
// VI CONTROL (Contract v6 - Complete Automation)
vi: {
// Hosts
getHosts: () => ipcRenderer.invoke('vi-hosts-list'),
addHost: (host) => ipcRenderer.invoke('vi-hosts-add', host),
updateHost: (host) => ipcRenderer.invoke('vi-hosts-update', host),
deleteHost: (hostId) => ipcRenderer.invoke('vi-hosts-delete', hostId),
// Credentials
getCredentials: () => ipcRenderer.invoke('vi-credentials-list'),
saveCredential: (label, type, value) => ipcRenderer.invoke('vi-credentials-save', { label, type, value }),
deleteCredential: (credId) => ipcRenderer.invoke('vi-credentials-delete', { credId }),
// Execution
runSSH: (execSessionId, hostId, command, credId) => ipcRenderer.send('vi-ssh-run', { execSessionId, hostId, command, credId }),
runSSHWithPassword: (execSessionId, hostId, command, password) => ipcRenderer.send('vi-ssh-run-with-password', { execSessionId, hostId, command, password }),
cancelSSH: (execSessionId) => ipcRenderer.send('vi-ssh-cancel', { execSessionId }),
// Host update
updateHost: (host) => ipcRenderer.invoke('vi-hosts-update', host),
// RDP
launchRDP: (hostId) => ipcRenderer.invoke('vi-rdp-launch', { hostId }),
// === NEW: Computer Use / Automation ===
// Screen Capture
captureScreen: (mode) => ipcRenderer.invoke('vi-capture-screen', { mode }), // mode: 'desktop' | 'window'
getWindows: () => ipcRenderer.invoke('vi-get-windows'),
// Vision Analysis
analyzeScreenshot: (imageDataUrl) => ipcRenderer.invoke('vi-analyze-screenshot', { imageDataUrl }),
// Task Translation & Execution
translateTask: (task) => ipcRenderer.invoke('vi-translate-task', { task }),
executeCommand: (command) => ipcRenderer.invoke('vi-execute-command', { command }),
// Task Chain with progress
executeChain: (tasks) => ipcRenderer.send('vi-execute-chain', { tasks }),
onChainProgress: (callback) => ipcRenderer.on('vi-chain-progress', (_, data) => callback(data)),
onChainComplete: (callback) => ipcRenderer.on('vi-chain-complete', (_, data) => callback(data)),
removeChainListeners: () => {
ipcRenderer.removeAllListeners('vi-chain-progress');
ipcRenderer.removeAllListeners('vi-chain-complete');
},
// Browser
openBrowser: (url) => ipcRenderer.invoke('vi-open-browser', { url })
},
// Ollama Cloud
ollama: {
getKeyStatus: () => ipcRenderer.invoke('ollama-get-key-status'),
saveKey: (key) => ipcRenderer.invoke('ollama-save-key', { key }),
getModels: () => ipcRenderer.invoke('ollama-get-models')
},
// ==========================================
// USER AUTHENTICATION SYSTEM
// ==========================================
user: {
// Get list of secret questions
getSecretQuestions: () => ipcRenderer.invoke('user-get-secret-questions'),
// Create a new account (returns { success, user, secretCode, session } or { success: false, error })
create: (displayName, questionId, answer) =>
ipcRenderer.invoke('user-create', { displayName, questionId, answer }),
// Login with secret code (returns { success, user, session } or { success: false, error })
login: (secretCode) => ipcRenderer.invoke('user-login', { secretCode }),
// Get current session (returns session object or null)
getSession: () => ipcRenderer.invoke('user-get-session'),
// Logout (optionally clean data)
logout: (cleanData = false) => ipcRenderer.invoke('user-logout', { cleanData }),
// Get user statistics
getStats: (userId) => ipcRenderer.invoke('user-get-stats', { userId }),
// Clean all user data (projects, chats, keys)
cleanData: (userId) => ipcRenderer.invoke('user-clean-data', { userId }),
// Get user's projects directory path
getProjectsDir: (userId) => ipcRenderer.invoke('user-get-projects-dir', { userId })
},
// ==========================================
// QWEN OAUTH (INLINE DEVICE FLOW)
// ==========================================
qwenAuth: {
// Start the device authorization flow (triggers browser open)
start: () => ipcRenderer.send('qwen-auth-start'),
// Cancel ongoing authorization
cancel: () => ipcRenderer.send('qwen-auth-cancel'),
// Get current auth status
getStatus: () => ipcRenderer.invoke('qwen-get-auth-status'),
// Clear saved tokens
clearTokens: () => ipcRenderer.invoke('qwen-clear-tokens'),
// Event listeners for auth flow
onProgress: (callback) => ipcRenderer.on('qwen-auth-progress', (_, data) => callback(data)),
onSuccess: (callback) => ipcRenderer.on('qwen-auth-success', (_, creds) => callback(creds)),
onError: (callback) => ipcRenderer.on('qwen-auth-error', (_, error) => callback(error)),
// Cleanup listeners
removeListeners: () => {
ipcRenderer.removeAllListeners('qwen-auth-progress');
ipcRenderer.removeAllListeners('qwen-auth-success');
ipcRenderer.removeAllListeners('qwen-auth-error');
}
}
});