mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-30 03:34:24 +01:00
fix: deep link installs of extensions (#1333)
This commit is contained in:
@@ -13,12 +13,13 @@ import { extractExtensionName } from './components/settings/extensions/utils';
|
||||
|
||||
import WelcomeView from './components/WelcomeView';
|
||||
import ChatView from './components/ChatView';
|
||||
import SettingsView from './components/settings/SettingsView';
|
||||
import SettingsView, { type SettingsViewOptions } from './components/settings/SettingsView';
|
||||
import MoreModelsView from './components/settings/models/MoreModelsView';
|
||||
import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
// Views and their options
|
||||
export type View =
|
||||
| 'welcome'
|
||||
| 'chat'
|
||||
@@ -27,15 +28,27 @@ export type View =
|
||||
| 'configureProviders'
|
||||
| 'configPage';
|
||||
|
||||
export type ViewConfig = {
|
||||
view: View;
|
||||
viewOptions?: SettingsViewOptions | Record<any, any>;
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
const [fatalError, setFatalError] = useState<string | null>(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [pendingLink, setPendingLink] = useState<string | null>(null);
|
||||
const [modalMessage, setModalMessage] = useState<string>('');
|
||||
const [isInstalling, setIsInstalling] = useState(false);
|
||||
const [view, setView] = useState<View>('welcome');
|
||||
const [{ view, viewOptions }, setInternalView] = useState<ViewConfig>({
|
||||
view: 'welcome',
|
||||
viewOptions: {},
|
||||
});
|
||||
|
||||
const { switchModel } = useModel();
|
||||
const { addRecentModel } = useRecentModels();
|
||||
const setView = (view: View, viewOptions: Record<any, any> = {}) => {
|
||||
setInternalView({ view, viewOptions });
|
||||
};
|
||||
|
||||
// Utility function to extract the command from the link
|
||||
function extractCommand(link: string): string {
|
||||
@@ -193,6 +206,7 @@ export default function App() {
|
||||
setView('chat');
|
||||
}}
|
||||
setView={setView}
|
||||
viewOptions={viewOptions as SettingsViewOptions}
|
||||
/>
|
||||
)}
|
||||
{view === 'moreModels' && (
|
||||
|
||||
@@ -46,15 +46,20 @@ const DEFAULT_SETTINGS: SettingsType = {
|
||||
extensions: BUILT_IN_EXTENSIONS,
|
||||
};
|
||||
|
||||
export type SettingsViewOptions = {
|
||||
extensionId: string;
|
||||
showEnvVars: boolean;
|
||||
};
|
||||
|
||||
export default function SettingsView({
|
||||
onClose,
|
||||
setView,
|
||||
viewOptions,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
setView: (view: View) => void;
|
||||
viewOptions: SettingsViewOptions;
|
||||
}) {
|
||||
const [searchParams] = useState(() => new URLSearchParams(window.location.search));
|
||||
|
||||
const [settings, setSettings] = React.useState<SettingsType>(() => {
|
||||
const saved = localStorage.getItem('user_settings');
|
||||
window.electron.logInfo('Settings: ' + saved);
|
||||
@@ -101,10 +106,10 @@ export default function SettingsView({
|
||||
|
||||
// Handle URL parameters for auto-opening extension configuration
|
||||
useEffect(() => {
|
||||
const extensionId = searchParams.get('extensionId');
|
||||
const showEnvVars = searchParams.get('showEnvVars');
|
||||
const extensionId = viewOptions.extensionId;
|
||||
const showEnvVars = viewOptions.showEnvVars;
|
||||
|
||||
if (extensionId && showEnvVars === 'true') {
|
||||
if (extensionId && showEnvVars === true) {
|
||||
// Find the extension in settings
|
||||
const extension = settings.extensions.find((ext) => ext.id === extensionId);
|
||||
if (extension) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getApiUrl, getSecretKey } from './config';
|
||||
import { type View } from './App';
|
||||
import { type SettingsViewOptions } from './components/settings/SettingsView';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
// ExtensionConfig type matching the Rust version
|
||||
@@ -194,7 +195,7 @@ function storeExtensionConfig(config: FullExtensionConfig) {
|
||||
localStorage.setItem('user_settings', JSON.stringify(userSettings));
|
||||
console.log('Extension config stored successfully in user_settings');
|
||||
// Notify settings update through electron IPC
|
||||
window.electron.send('settings-updated');
|
||||
window.electron.emit('settings-updated');
|
||||
} else {
|
||||
console.log('Extension config already exists in user_settings');
|
||||
}
|
||||
@@ -258,7 +259,10 @@ function handleError(message: string, shouldThrow = false): void {
|
||||
}
|
||||
}
|
||||
|
||||
export async function addExtensionFromDeepLink(url: string, setView: (view: View) => void) {
|
||||
export async function addExtensionFromDeepLink(
|
||||
url: string,
|
||||
setView: (view: View, options: SettingsViewOptions) => void
|
||||
) {
|
||||
if (!url.startsWith('goose://extension')) {
|
||||
handleError(
|
||||
'Failed to install extension: Invalid URL: URL must use the goose://extension scheme'
|
||||
@@ -344,9 +348,7 @@ export async function addExtensionFromDeepLink(url: string, setView: (view: View
|
||||
// Check if extension requires env vars and go to settings if so
|
||||
if (envVarsRequired(config)) {
|
||||
console.log('Environment variables required, redirecting to settings');
|
||||
setView('settings');
|
||||
// TODO - add code which can auto-open the modal on the settings view
|
||||
// navigate(`/settings?extensionId=${config.id}&showEnvVars=true`);
|
||||
setView('settings', { extensionId: config.id, showEnvVars: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
} from 'electron';
|
||||
import started from 'electron-squirrel-startup';
|
||||
import path from 'node:path';
|
||||
import { handleSquirrelEvent } from './setup-events';
|
||||
import { startGoosed } from './goosed';
|
||||
import { getBinaryPath } from './utils/binaryPath';
|
||||
import { loadShellEnv } from './utils/loadEnv';
|
||||
@@ -34,82 +33,13 @@ import { promisify } from 'util';
|
||||
|
||||
const exec = promisify(execCallback);
|
||||
|
||||
// Handle Squirrel events for Windows installer
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Windows detected, command line args:', process.argv);
|
||||
|
||||
if (handleSquirrelEvent()) {
|
||||
// squirrel event handled and app will exit in 1000ms, so don't do anything else
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle the protocol on Windows during first launch
|
||||
if (process.argv.length >= 2) {
|
||||
const url = process.argv[1];
|
||||
console.log('Checking URL from command line:', url);
|
||||
if (url.startsWith('goose://')) {
|
||||
console.log('Found goose:// URL in command line args');
|
||||
app.emit('open-url', { preventDefault: () => {} }, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure single instance lock
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, _workingDirectory) => {
|
||||
// Someone tried to run a second instance
|
||||
console.log('Second instance detected with args:', commandLine);
|
||||
|
||||
// Get existing window or create new one
|
||||
const existingWindows = BrowserWindow.getAllWindows();
|
||||
if (existingWindows.length > 0) {
|
||||
const window = existingWindows[0];
|
||||
if (window.isMinimized()) window.restore();
|
||||
window.focus();
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Protocol handling for Windows
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
console.log('Checking last arg for protocol:', url);
|
||||
if (url.startsWith('goose://')) {
|
||||
console.log('Found goose:// URL in second instance');
|
||||
// Send the URL to the window
|
||||
if (!window.webContents.isLoading()) {
|
||||
window.webContents.send('add-extension', url);
|
||||
} else {
|
||||
window.webContents.once('did-finish-load', () => {
|
||||
window.webContents.send('add-extension', url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (started) app.quit();
|
||||
|
||||
// Register protocol handler
|
||||
if (process.platform === 'win32') {
|
||||
const success = app.setAsDefaultProtocolClient('goose', process.execPath, ['--']);
|
||||
console.log('Registering protocol handler for Windows:', success ? 'success' : 'failed');
|
||||
} else {
|
||||
const success = app.setAsDefaultProtocolClient('goose');
|
||||
console.log('Registering protocol handler:', success ? 'success' : 'failed');
|
||||
}
|
||||
|
||||
// Log if we're the default protocol handler
|
||||
console.log('Is default protocol handler:', app.isDefaultProtocolClient('goose'));
|
||||
app.setAsDefaultProtocolClient('goose');
|
||||
|
||||
// Triggered when the user opens "goose://..." links
|
||||
app.on('open-url', async (event, url) => {
|
||||
event.preventDefault();
|
||||
console.log('open-url:', url);
|
||||
|
||||
// Get existing window or create new one
|
||||
let targetWindow: BrowserWindow;
|
||||
|
||||
@@ -26,6 +26,7 @@ type ElectronAPI = {
|
||||
channel: string,
|
||||
callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void
|
||||
) => void;
|
||||
emit: (channel: string, ...args: any[]) => void;
|
||||
};
|
||||
|
||||
type AppConfigAPI = {
|
||||
@@ -55,6 +56,9 @@ const electronAPI: ElectronAPI = {
|
||||
off: (channel: string, callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void) => {
|
||||
ipcRenderer.off(channel, callback);
|
||||
},
|
||||
emit: (channel: string, ...args: any[]) => {
|
||||
ipcRenderer.emit(channel, ...args);
|
||||
},
|
||||
};
|
||||
|
||||
const appConfigAPI: AppConfigAPI = {
|
||||
|
||||
Reference in New Issue
Block a user