mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-29 18:36:23 +02:00
Merge branch 'dev' of github.com:rowboatlabs/rowboat into dev
This commit is contained in:
commit
c0efa3329a
10 changed files with 476 additions and 41 deletions
|
|
@ -18,6 +18,7 @@ import z from 'zod';
|
|||
import { RunEvent } from 'packages/shared/dist/runs.js';
|
||||
import container from '@x/core/dist/di/container.js';
|
||||
import { IGranolaConfigRepo } from '@x/core/dist/knowledge/granola/repo.js';
|
||||
import { triggerSync as triggerGranolaSync } from '@x/core/dist/knowledge/granola/sync.js';
|
||||
|
||||
type InvokeChannels = ipc.InvokeChannels;
|
||||
type IPCChannels = ipc.IPCChannels;
|
||||
|
|
@ -316,6 +317,12 @@ export function setupIpcHandlers() {
|
|||
'granola:setConfig': async (_event, args) => {
|
||||
const repo = container.resolve<IGranolaConfigRepo>('granolaConfigRepo');
|
||||
await repo.setConfig({ enabled: args.enabled });
|
||||
|
||||
// Trigger sync immediately when enabled
|
||||
if (args.enabled) {
|
||||
triggerGranolaSync();
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { app, BrowserWindow, protocol, net } from "electron";
|
||||
import { app, BrowserWindow, protocol, net, shell } from "electron";
|
||||
import path from "node:path";
|
||||
import { setupIpcHandlers, startRunsWatcher, startWorkspaceWatcher, stopWorkspaceWatcher } from "./ipc.js";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
|
@ -80,6 +80,24 @@ function createWindow() {
|
|||
},
|
||||
});
|
||||
|
||||
// Open external links in system browser (not sandboxed Electron window)
|
||||
// This handles window.open() and target="_blank" links
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
// Open all URLs in system browser
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' }; // Prevent Electron from opening a new window
|
||||
});
|
||||
|
||||
// Handle navigation to external URLs (e.g., clicking a link without target="_blank")
|
||||
win.webContents.on('will-navigate', (event, url) => {
|
||||
// Allow internal navigation (app protocol or dev server)
|
||||
const isInternal = url.startsWith('app://') || url.startsWith('http://localhost:5173');
|
||||
if (!isInternal) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
}
|
||||
});
|
||||
|
||||
// #region agent log
|
||||
const loadURL = app.isPackaged ? 'app://./' : 'http://localhost:5173';
|
||||
fetch('http://127.0.0.1:7242/ingest/dd33b297-24f6-4846-82f9-02599308a13a',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'main.ts:65',message:'createWindow called',data:{isPackaged:app.isPackaged,loadURL,preloadPath},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'E'})}).catch(()=>{});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BrowserWindow } from 'electron';
|
||||
import { shell } from 'electron';
|
||||
import { createAuthServer } from './auth-server.js';
|
||||
import * as oauthClient from '@x/core/dist/auth/oauth-client.js';
|
||||
import type { Configuration } from '@x/core/dist/auth/oauth-client.js';
|
||||
|
|
@ -6,6 +6,9 @@ import { getProviderConfig, getAvailableProviders } from '@x/core/dist/auth/prov
|
|||
import container from '@x/core/dist/di/container.js';
|
||||
import { IOAuthRepo } from '@x/core/dist/auth/repo.js';
|
||||
import { IClientRegistrationRepo } from '@x/core/dist/auth/client-repo.js';
|
||||
import { triggerSync as triggerGmailSync } from '@x/core/dist/knowledge/sync_gmail.js';
|
||||
import { triggerSync as triggerCalendarSync } from '@x/core/dist/knowledge/sync_calendar.js';
|
||||
import { triggerSync as triggerFirefliesSync } from '@x/core/dist/knowledge/sync_fireflies.js';
|
||||
|
||||
const REDIRECT_URI = 'http://localhost:8080/oauth/callback';
|
||||
|
||||
|
|
@ -110,6 +113,17 @@ export async function connectProvider(provider: string): Promise<{ success: bool
|
|||
// Store flow state
|
||||
activeFlows.set(state, { codeVerifier, provider, config });
|
||||
|
||||
// Build authorization URL
|
||||
const authUrl = oauthClient.buildAuthorizationUrl(config, {
|
||||
redirectUri: REDIRECT_URI,
|
||||
scope: scopes.join(' '),
|
||||
codeChallenge,
|
||||
state,
|
||||
});
|
||||
|
||||
// Declare timeout variable (will be set after server is created)
|
||||
let cleanupTimeout: NodeJS.Timeout;
|
||||
|
||||
// Create callback server
|
||||
const { server } = await createAuthServer(8080, async (code, receivedState) => {
|
||||
// Validate state
|
||||
|
|
@ -138,6 +152,14 @@ export async function connectProvider(provider: string): Promise<{ success: bool
|
|||
// Save tokens
|
||||
console.log(`[OAuth] Token exchange successful for ${provider}`);
|
||||
await oauthRepo.saveTokens(provider, tokens);
|
||||
|
||||
// Trigger immediate sync for relevant providers
|
||||
if (provider === 'google') {
|
||||
triggerGmailSync();
|
||||
triggerCalendarSync();
|
||||
} else if (provider === 'fireflies-ai') {
|
||||
triggerFirefliesSync();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('OAuth token exchange failed:', error);
|
||||
throw error;
|
||||
|
|
@ -145,35 +167,22 @@ export async function connectProvider(provider: string): Promise<{ success: bool
|
|||
// Clean up
|
||||
activeFlows.delete(state);
|
||||
server.close();
|
||||
clearTimeout(cleanupTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
// Build authorization URL
|
||||
const authUrl = oauthClient.buildAuthorizationUrl(config, {
|
||||
redirectUri: REDIRECT_URI,
|
||||
scope: scopes.join(' '),
|
||||
codeChallenge,
|
||||
state,
|
||||
});
|
||||
// Set timeout to clean up abandoned flows (5 minutes)
|
||||
// This prevents memory leaks if user never completes the OAuth flow
|
||||
cleanupTimeout = setTimeout(() => {
|
||||
if (activeFlows.has(state)) {
|
||||
console.log(`[OAuth] Cleaning up abandoned OAuth flow for ${provider} (timeout)`);
|
||||
activeFlows.delete(state);
|
||||
server.close();
|
||||
}
|
||||
}, 5 * 60 * 1000); // 5 minutes
|
||||
|
||||
// Open browser window
|
||||
const authWindow = new BrowserWindow({
|
||||
width: 600,
|
||||
height: 700,
|
||||
show: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
});
|
||||
|
||||
authWindow.loadURL(authUrl.toString());
|
||||
|
||||
// Clean up on window close
|
||||
authWindow.on('closed', () => {
|
||||
activeFlows.delete(state);
|
||||
server.close();
|
||||
});
|
||||
// Open in system browser (shares cookies/sessions with user's regular browser)
|
||||
shell.openExternal(authUrl.toString());
|
||||
|
||||
// Wait for callback (server will handle it)
|
||||
return { success: true };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue