mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
cleanup
This commit is contained in:
@@ -61,8 +61,6 @@ pub async fn run() -> Result<()> {
|
||||
|
||||
let tunnel_manager = app_state.tunnel_manager.clone();
|
||||
tokio::spawn(async move {
|
||||
// Give time for GUI to render before doing autostart
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
||||
tunnel_manager.check_auto_start().await;
|
||||
});
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@ pub async fn start_tunnel(State(state): State<Arc<AppState>>) -> Response {
|
||||
}
|
||||
|
||||
/// Stop the tunnel
|
||||
///
|
||||
/// Stops the currently running tunnel and optionally clears the auto-start setting.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/tunnel/stop",
|
||||
@@ -59,8 +57,6 @@ pub async fn stop_tunnel(State(state): State<Arc<AppState>>) -> Response {
|
||||
}
|
||||
|
||||
/// Get tunnel info
|
||||
///
|
||||
/// Returns the current tunnel state and connection information if running.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/tunnel/status",
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
use super::TunnelConfig;
|
||||
use goose::config::Config;
|
||||
use tracing::info;
|
||||
|
||||
const CONFIG_KEY_AUTO_START: &str = "tunnel_auto_start";
|
||||
const SECRET_KEY_SECRET: &str = "tunnel_secret";
|
||||
const SECRET_KEY_AGENT_ID: &str = "tunnel_agent_id";
|
||||
|
||||
/// Load tunnel configuration from Config system
|
||||
/// Non-secrets from config.yaml, secrets from keyring/secrets.yaml
|
||||
pub async fn load_config() -> TunnelConfig {
|
||||
let cfg = Config::global();
|
||||
|
||||
let auto_start = cfg.get_param(CONFIG_KEY_AUTO_START).unwrap_or(false);
|
||||
|
||||
let secret = cfg.get_secret(SECRET_KEY_SECRET).ok();
|
||||
let agent_id = cfg.get_secret(SECRET_KEY_AGENT_ID).ok();
|
||||
|
||||
info!(
|
||||
"Loaded tunnel config from goose Config (auto_start: {})",
|
||||
auto_start
|
||||
);
|
||||
|
||||
TunnelConfig {
|
||||
auto_start,
|
||||
secret,
|
||||
agent_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Save tunnel configuration to Config system
|
||||
/// Non-secrets to config.yaml, secrets to keyring/secrets.yaml
|
||||
pub async fn save_config(config: &TunnelConfig) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cfg = Config::global();
|
||||
|
||||
// Save non-secret config to config.yaml
|
||||
cfg.set_param(CONFIG_KEY_AUTO_START, config.auto_start)?;
|
||||
|
||||
// Save secrets to keyring or secrets.yaml
|
||||
if let Some(secret) = &config.secret {
|
||||
cfg.set_secret(SECRET_KEY_SECRET, secret)?;
|
||||
}
|
||||
if let Some(agent_id) = &config.agent_id {
|
||||
cfg.set_secret(SECRET_KEY_AGENT_ID, agent_id)?;
|
||||
}
|
||||
|
||||
info!("Saved tunnel config to goose Config");
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod config;
|
||||
pub mod ed25519;
|
||||
pub mod lapstone;
|
||||
|
||||
@@ -6,6 +5,7 @@ pub mod lapstone;
|
||||
mod lapstone_test;
|
||||
|
||||
use crate::configuration::Settings;
|
||||
use goose::config::Config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
@@ -75,8 +75,16 @@ impl TunnelManager {
|
||||
}
|
||||
|
||||
async fn load_config(&self) {
|
||||
let loaded = config::load_config().await;
|
||||
*self.config.write().await = loaded;
|
||||
let cfg = Config::global();
|
||||
let auto_start = cfg.get_param("tunnel_auto_start").unwrap_or(false);
|
||||
let secret = cfg.get_secret("tunnel_secret").ok();
|
||||
let agent_id = cfg.get_secret("tunnel_agent_id").ok();
|
||||
|
||||
*self.config.write().await = TunnelConfig {
|
||||
auto_start,
|
||||
secret,
|
||||
agent_id,
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn check_auto_start(&self) {
|
||||
@@ -122,19 +130,26 @@ impl TunnelManager {
|
||||
{
|
||||
let mut cfg = self.config.write().await;
|
||||
f(&mut cfg);
|
||||
if let Err(e) = config::save_config(&cfg).await {
|
||||
|
||||
let global_cfg = Config::global();
|
||||
if let Err(e) = global_cfg.set_param("tunnel_auto_start", cfg.auto_start) {
|
||||
tracing::error!("Failed to save tunnel config: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(secret) = &cfg.secret {
|
||||
if let Err(e) = global_cfg.set_secret("tunnel_secret", secret) {
|
||||
tracing::error!("Failed to save tunnel secret: {}", e);
|
||||
}
|
||||
}
|
||||
if let Some(agent_id) = &cfg.agent_id {
|
||||
if let Err(e) = global_cfg.set_secret("tunnel_agent_id", agent_id) {
|
||||
tracing::error!("Failed to save tunnel agent_id: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> anyhow::Result<TunnelInfo> {
|
||||
let mut state = self.state.write().await;
|
||||
if *state != TunnelState::Idle {
|
||||
anyhow::bail!("Tunnel is already running or starting");
|
||||
}
|
||||
*state = TunnelState::Starting;
|
||||
drop(state);
|
||||
|
||||
async fn start_tunnel_internal(&self) -> anyhow::Result<(TunnelInfo, mpsc::Receiver<()>)> {
|
||||
let config = self.config.read().await.clone();
|
||||
let server_port = get_server_port()?;
|
||||
|
||||
@@ -149,7 +164,7 @@ impl TunnelManager {
|
||||
})
|
||||
.await;
|
||||
|
||||
let (restart_tx, mut restart_rx) = mpsc::channel::<()>(1);
|
||||
let (restart_tx, restart_rx) = mpsc::channel::<()>(1);
|
||||
*self.restart_tx.write().await = Some(restart_tx.clone());
|
||||
|
||||
let result = lapstone::start(
|
||||
@@ -163,7 +178,21 @@ impl TunnelManager {
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(info) => {
|
||||
Ok(info) => Ok((info, restart_rx)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> anyhow::Result<TunnelInfo> {
|
||||
let mut state = self.state.write().await;
|
||||
if *state != TunnelState::Idle {
|
||||
anyhow::bail!("Tunnel is already running or starting");
|
||||
}
|
||||
*state = TunnelState::Starting;
|
||||
drop(state);
|
||||
|
||||
match self.start_tunnel_internal().await {
|
||||
Ok((info, mut restart_rx)) => {
|
||||
*self.state.write().await = TunnelState::Running;
|
||||
*self.info.write().await = Some(info.clone());
|
||||
self.update_config(|c| c.auto_start = true).await;
|
||||
@@ -171,55 +200,35 @@ impl TunnelManager {
|
||||
let state = self.state.clone();
|
||||
let config = self.config.clone();
|
||||
let lapstone_handle = self.lapstone_handle.clone();
|
||||
let restart_tx_clone = self.restart_tx.clone();
|
||||
let watchdog_handle_arc = self.watchdog_handle.clone();
|
||||
let manager = Arc::new(self.clone_for_watchdog());
|
||||
|
||||
let watchdog = tokio::spawn(async move {
|
||||
while restart_rx.recv().await.is_some() {
|
||||
let auto_start = config.read().await.auto_start;
|
||||
if auto_start {
|
||||
tracing::warn!("Tunnel connection lost, initiating restart...");
|
||||
|
||||
lapstone::stop(lapstone_handle.clone()).await;
|
||||
*state.write().await = TunnelState::Idle;
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
*state.write().await = TunnelState::Starting;
|
||||
let cfg = config.read().await.clone();
|
||||
let tunnel_secret = cfg.secret.clone().unwrap_or_else(generate_secret);
|
||||
let server_secret = std::env::var("GOOSE_SERVER__SECRET_KEY")
|
||||
.unwrap_or_else(|_| "test".to_string());
|
||||
let agent_id = cfg.agent_id.clone().unwrap_or_else(generate_agent_id);
|
||||
|
||||
let (new_restart_tx, new_restart_rx) = mpsc::channel::<()>(1);
|
||||
*restart_tx_clone.write().await = Some(new_restart_tx.clone());
|
||||
|
||||
match lapstone::start(
|
||||
server_port,
|
||||
tunnel_secret,
|
||||
server_secret,
|
||||
agent_id,
|
||||
lapstone_handle.clone(),
|
||||
new_restart_tx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
*state.write().await = TunnelState::Running;
|
||||
tracing::info!("Tunnel restarted successfully");
|
||||
restart_rx = new_restart_rx;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to restart tunnel: {}", e);
|
||||
*state.write().await = TunnelState::Error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !auto_start {
|
||||
tracing::info!("Tunnel connection lost but auto_start is disabled");
|
||||
break;
|
||||
}
|
||||
|
||||
tracing::warn!("Tunnel connection lost, initiating restart...");
|
||||
lapstone::stop(lapstone_handle.clone()).await;
|
||||
*state.write().await = TunnelState::Idle;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
*state.write().await = TunnelState::Starting;
|
||||
|
||||
match manager.start_tunnel_internal().await {
|
||||
Ok((_, new_restart_rx)) => {
|
||||
*state.write().await = TunnelState::Running;
|
||||
tracing::info!("Tunnel restarted successfully");
|
||||
restart_rx = new_restart_rx;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to restart tunnel: {}", e);
|
||||
*state.write().await = TunnelState::Error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -234,6 +243,17 @@ impl TunnelManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_for_watchdog(&self) -> Self {
|
||||
TunnelManager {
|
||||
state: self.state.clone(),
|
||||
info: self.info.clone(),
|
||||
config: self.config.clone(),
|
||||
lapstone_handle: self.lapstone_handle.clone(),
|
||||
restart_tx: self.restart_tx.clone(),
|
||||
watchdog_handle: self.watchdog_handle.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn stop(&self, clear_auto_start: bool) {
|
||||
if let Some(handle) = self.watchdog_handle.write().await.take() {
|
||||
handle.abort();
|
||||
|
||||
@@ -386,7 +386,6 @@
|
||||
"super::routes::tunnel"
|
||||
],
|
||||
"summary": "Get tunnel info",
|
||||
"description": "Returns the current tunnel state and connection information if running.",
|
||||
"operationId": "get_tunnel_status",
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -408,7 +407,6 @@
|
||||
"super::routes::tunnel"
|
||||
],
|
||||
"summary": "Stop the tunnel",
|
||||
"description": "Stops the currently running tunnel and optionally clears the auto-start setting.",
|
||||
"operationId": "stop_tunnel",
|
||||
"responses": {
|
||||
"200": {
|
||||
|
||||
@@ -114,8 +114,6 @@ export const startTunnel = <ThrowOnError extends boolean = false>(options?: Opti
|
||||
|
||||
/**
|
||||
* Get tunnel info
|
||||
*
|
||||
* Returns the current tunnel state and connection information if running.
|
||||
*/
|
||||
export const getTunnelStatus = <ThrowOnError extends boolean = false>(options?: Options<GetTunnelStatusData, ThrowOnError>) => {
|
||||
return (options?.client ?? client).get<GetTunnelStatusResponses, unknown, ThrowOnError>({
|
||||
@@ -126,8 +124,6 @@ export const getTunnelStatus = <ThrowOnError extends boolean = false>(options?:
|
||||
|
||||
/**
|
||||
* Stop the tunnel
|
||||
*
|
||||
* Stops the currently running tunnel and optionally clears the auto-start setting.
|
||||
*/
|
||||
export const stopTunnel = <ThrowOnError extends boolean = false>(options?: Options<StopTunnelData, ThrowOnError>) => {
|
||||
return (options?.client ?? client).post<StopTunnelResponses, StopTunnelErrors, ThrowOnError>({
|
||||
|
||||
@@ -5,7 +5,15 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../..
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { Loader2, Copy, Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { errorMessage } from '../../../utils/conversionUtils';
|
||||
import type { TunnelInfo } from '../../../utils/tunnel';
|
||||
import { startTunnel, stopTunnel, getTunnelStatus } from '../../../api/sdk.gen';
|
||||
import type { TunnelInfo } from '../../../api/types.gen';
|
||||
|
||||
const STATUS_MESSAGES = {
|
||||
idle: 'Tunnel is not running',
|
||||
starting: 'Starting tunnel...',
|
||||
running: 'Tunnel is active',
|
||||
error: 'Tunnel encountered an error',
|
||||
} as const;
|
||||
|
||||
export default function TunnelSection() {
|
||||
const [tunnelInfo, setTunnelInfo] = useState<TunnelInfo>({
|
||||
@@ -20,54 +28,51 @@ export default function TunnelSection() {
|
||||
useEffect(() => {
|
||||
const loadTunnelInfo = async () => {
|
||||
try {
|
||||
const baseUrl = await window.electron.getGoosedHostPort();
|
||||
if (!baseUrl) throw new Error('goose server not available');
|
||||
const info = await window.electron.getTunnelStatus(baseUrl);
|
||||
setTunnelInfo(info);
|
||||
const { data } = await getTunnelStatus();
|
||||
if (data) {
|
||||
setTunnelInfo(data);
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMsg = errorMessage(err, 'Failed to load tunnel status');
|
||||
setError(errorMsg);
|
||||
setTunnelInfo({ state: 'error', error: errorMsg });
|
||||
setTunnelInfo({ state: 'error' });
|
||||
}
|
||||
};
|
||||
|
||||
loadTunnelInfo();
|
||||
}, []);
|
||||
|
||||
const handleStartTunnel = async () => {
|
||||
setError(null);
|
||||
setTunnelInfo({ state: 'starting' });
|
||||
|
||||
try {
|
||||
const baseUrl = await window.electron.getGoosedHostPort();
|
||||
if (!baseUrl) throw new Error('goose server not available');
|
||||
const info = await window.electron.startTunnel(baseUrl);
|
||||
setTunnelInfo(info);
|
||||
setShowQRModal(true);
|
||||
} catch (err) {
|
||||
const errorMsg = errorMessage(err, 'Failed to start tunnel');
|
||||
setError(errorMsg);
|
||||
setTunnelInfo({ state: 'error', error: errorMsg });
|
||||
}
|
||||
};
|
||||
|
||||
const handleStopTunnel = async () => {
|
||||
try {
|
||||
const baseUrl = await window.electron.getGoosedHostPort();
|
||||
if (!baseUrl) throw new Error('goose server not available');
|
||||
await window.electron.stopTunnel(baseUrl);
|
||||
setTunnelInfo({ state: 'idle' });
|
||||
setShowQRModal(false);
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, 'Failed to stop tunnel'));
|
||||
const handleToggleTunnel = async () => {
|
||||
if (tunnelInfo.state === 'running') {
|
||||
try {
|
||||
const baseUrl = await window.electron.getGoosedHostPort();
|
||||
if (!baseUrl) throw new Error('goose server not available');
|
||||
const info = await window.electron.getTunnelStatus(baseUrl);
|
||||
setTunnelInfo(info);
|
||||
} catch (statusErr) {
|
||||
console.error('Failed to fetch tunnel status after stop error:', statusErr);
|
||||
setTunnelInfo({ state: 'error', error: 'Failed to stop tunnel and get status' });
|
||||
await stopTunnel();
|
||||
setTunnelInfo({ state: 'idle' });
|
||||
setShowQRModal(false);
|
||||
} catch (err) {
|
||||
setError(errorMessage(err, 'Failed to stop tunnel'));
|
||||
try {
|
||||
const { data } = await getTunnelStatus();
|
||||
if (data) {
|
||||
setTunnelInfo(data);
|
||||
}
|
||||
} catch (statusErr) {
|
||||
console.error('Failed to fetch tunnel status after stop error:', statusErr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setError(null);
|
||||
setTunnelInfo({ state: 'starting' });
|
||||
|
||||
try {
|
||||
const { data } = await startTunnel();
|
||||
if (data) {
|
||||
setTunnelInfo(data);
|
||||
setShowQRModal(true);
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMsg = errorMessage(err, 'Failed to start tunnel');
|
||||
setError(errorMsg);
|
||||
setTunnelInfo({ state: 'error' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -118,37 +123,27 @@ export default function TunnelSection() {
|
||||
<div>
|
||||
<h3 className="text-text-default text-xs">Tunnel Status</h3>
|
||||
<p className="text-xs text-text-muted max-w-md mt-[2px]">
|
||||
{tunnelInfo.state === 'idle' && 'Tunnel is not running'}
|
||||
{tunnelInfo.state === 'starting' && 'Starting tunnel...'}
|
||||
{tunnelInfo.state === 'running' && 'Tunnel is active'}
|
||||
{tunnelInfo.state === 'error' && 'Tunnel encountered an error'}
|
||||
{STATUS_MESSAGES[tunnelInfo.state]}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{tunnelInfo.state === 'idle' && (
|
||||
<Button onClick={handleStartTunnel} variant="default" size="sm">
|
||||
Start Tunnel
|
||||
</Button>
|
||||
)}
|
||||
{tunnelInfo.state === 'starting' && (
|
||||
{tunnelInfo.state === 'starting' ? (
|
||||
<Button disabled variant="secondary" size="sm">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Starting...
|
||||
</Button>
|
||||
)}
|
||||
{tunnelInfo.state === 'running' && (
|
||||
) : tunnelInfo.state === 'running' ? (
|
||||
<>
|
||||
<Button onClick={() => setShowQRModal(true)} variant="default" size="sm">
|
||||
Show QR Code
|
||||
</Button>
|
||||
<Button onClick={handleStopTunnel} variant="destructive" size="sm">
|
||||
<Button onClick={handleToggleTunnel} variant="destructive" size="sm">
|
||||
Stop Tunnel
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{tunnelInfo.state === 'error' && (
|
||||
<Button onClick={handleStartTunnel} variant="default" size="sm">
|
||||
Retry
|
||||
) : (
|
||||
<Button onClick={handleToggleTunnel} variant="default" size="sm">
|
||||
{tunnelInfo.state === 'error' ? 'Retry' : 'Start Tunnel'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -208,7 +203,7 @@ export default function TunnelSection() {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="flex-shrink-0"
|
||||
onClick={() => copyToClipboard(tunnelInfo.url, 'url')}
|
||||
onClick={() => tunnelInfo.url && copyToClipboard(tunnelInfo.url, 'url')}
|
||||
>
|
||||
{copiedUrl ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
@@ -225,7 +220,9 @@ export default function TunnelSection() {
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="flex-shrink-0"
|
||||
onClick={() => copyToClipboard(tunnelInfo.secret, 'secret')}
|
||||
onClick={() =>
|
||||
tunnelInfo.secret && copyToClipboard(tunnelInfo.secret, 'secret')
|
||||
}
|
||||
>
|
||||
{copiedSecret ? (
|
||||
<Check className="h-4 w-4" />
|
||||
@@ -245,7 +242,7 @@ export default function TunnelSection() {
|
||||
<Button variant="outline" onClick={() => setShowQRModal(false)}>
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleStopTunnel}>
|
||||
<Button variant="destructive" onClick={handleToggleTunnel}>
|
||||
Stop Tunnel
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -1310,37 +1310,6 @@ ipcMain.handle('get-wakelock-state', () => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('start-tunnel', async (_event, baseUrl: string) => {
|
||||
try {
|
||||
const { startTunnel } = await import('./utils/tunnel');
|
||||
const tunnelInfo = await startTunnel(baseUrl, SERVER_SECRET);
|
||||
return tunnelInfo;
|
||||
} catch (error) {
|
||||
console.error('Error starting tunnel:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('stop-tunnel', async (_event, baseUrl: string) => {
|
||||
try {
|
||||
const { stopTunnel } = await import('./utils/tunnel');
|
||||
await stopTunnel(baseUrl, SERVER_SECRET);
|
||||
} catch (error) {
|
||||
console.error('Error stopping tunnel:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-tunnel-status', async (_event, baseUrl: string) => {
|
||||
try {
|
||||
const { syncTunnelStatus } = await import('./utils/tunnel');
|
||||
return await syncTunnelStatus(baseUrl, SERVER_SECRET);
|
||||
} catch (error) {
|
||||
console.error('Error getting tunnel status:', error);
|
||||
return { state: 'error', error: String(error) };
|
||||
}
|
||||
});
|
||||
|
||||
// Add file/directory selection handler
|
||||
ipcMain.handle('select-file-or-directory', async (_event, defaultPath?: string) => {
|
||||
const dialogOptions: OpenDialogOptions = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Electron, { contextBridge, ipcRenderer, webUtils } from 'electron';
|
||||
import { Recipe } from './recipe';
|
||||
import type { TunnelInfo } from './utils/tunnel';
|
||||
|
||||
interface NotificationData {
|
||||
title: string;
|
||||
@@ -120,9 +119,6 @@ type ElectronAPI = {
|
||||
hasAcceptedRecipeBefore: (recipe: Recipe) => Promise<boolean>;
|
||||
recordRecipeHash: (recipe: Recipe) => Promise<boolean>;
|
||||
openDirectoryInExplorer: (directoryPath: string) => Promise<boolean>;
|
||||
startTunnel: (baseUrl: string) => Promise<TunnelInfo>;
|
||||
stopTunnel: (baseUrl: string) => Promise<void>;
|
||||
getTunnelStatus: (baseUrl: string) => Promise<TunnelInfo>;
|
||||
};
|
||||
|
||||
type AppConfigAPI = {
|
||||
@@ -258,9 +254,6 @@ const electronAPI: ElectronAPI = {
|
||||
recordRecipeHash: (recipe: Recipe) => ipcRenderer.invoke('record-recipe-hash', recipe),
|
||||
openDirectoryInExplorer: (directoryPath: string) =>
|
||||
ipcRenderer.invoke('open-directory-in-explorer', directoryPath),
|
||||
startTunnel: (baseUrl: string) => ipcRenderer.invoke('start-tunnel', baseUrl),
|
||||
stopTunnel: (baseUrl: string) => ipcRenderer.invoke('stop-tunnel', baseUrl),
|
||||
getTunnelStatus: (baseUrl: string) => ipcRenderer.invoke('get-tunnel-status', baseUrl),
|
||||
};
|
||||
|
||||
const appConfigAPI: AppConfigAPI = {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import log from './logger';
|
||||
|
||||
export type TunnelInfo =
|
||||
| { state: 'idle' }
|
||||
| { state: 'starting' }
|
||||
| { state: 'running'; url: string; hostname: string; secret: string }
|
||||
| { state: 'error'; error: string };
|
||||
|
||||
/**
|
||||
* Start the tunnel via Rust API and remember it
|
||||
*/
|
||||
export async function startTunnel(baseUrl: string, serverSecret: string): Promise<TunnelInfo> {
|
||||
log.info(`Starting tunnel via Rust API at ${baseUrl}`);
|
||||
|
||||
const response = await fetch(`${baseUrl}/api/tunnel/start`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': serverSecret,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to start tunnel: ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
const info: TunnelInfo = await response.json();
|
||||
|
||||
log.info('Tunnel started successfully:', info);
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the tunnel via Rust API, and remember it
|
||||
*/
|
||||
export async function stopTunnel(baseUrl: string, secret: string): Promise<void> {
|
||||
log.info('Stopping tunnel via Rust API');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/api/tunnel/stop`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': secret,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
log.error(`Failed to stop tunnel: ${response.statusText}`);
|
||||
} else {
|
||||
log.info('Tunnel stopped successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Error stopping tunnel:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tunnel info from Rust API
|
||||
* Note: Rust backend is the source of truth
|
||||
*/
|
||||
export async function syncTunnelStatus(baseUrl: string, secret: string): Promise<TunnelInfo> {
|
||||
const response = await fetch(`${baseUrl}/api/tunnel/status`, {
|
||||
headers: {
|
||||
'X-Secret-Key': secret,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to get tunnel status: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
Reference in New Issue
Block a user