Files
openhuman/app/src/hooks/useWebhooks.ts
T
Steven EnamakelandGitHub ef708bdd20 refactor(core): move app state ownership into the Rust core (#320)
* refactor(core): integrate CoreStateProvider and streamline state management

- Replaced UserProvider with CoreStateProvider in App component to centralize state management.
- Updated various components to utilize the new CoreStateProvider for accessing session tokens and user data.
- Refactored hooks and components to eliminate direct Redux store dependencies, enhancing modularity and maintainability.
- Introduced a new core state management structure to improve the handling of user authentication and onboarding states.

This refactor aims to simplify state access across the application and improve overall code clarity.

* fix: restore polyfills import and clean up component imports

- Reintroduced the import of polyfills in main.tsx to ensure compatibility.
- Adjusted import statements in PrivacyPanel and SocketProvider for consistency.
- Simplified conditional expressions in TeamInvitesPanel and TeamMembersPanel for better readability.
- Refactored CoreStateProvider and store.ts to streamline state initialization.
- Enhanced formatting in various files for improved code clarity and maintainability.

* refactor(tests): streamline onboarding and protected route tests

- Updated OnboardingOverlay tests to utilize mock state management and simplify assertions.
- Refactored ProtectedRoute tests to improve readability and ensure consistent use of mock state.
- Enhanced teamApi tests to replace direct API calls with core RPC method calls, improving test isolation and clarity.
- Adjusted socketSelectors tests to utilize a centralized core state snapshot for better state management during tests.

* refactor(onboarding): simplify onboarding state management and improve loading logic

- Removed unnecessary local state for onboarding completion in OnboardingOverlay, directly utilizing snapshot data.
- Enhanced loading logic to prevent unnecessary renders and improve user experience during onboarding.
- Updated PrivacyPanel to handle analytics consent persistence with error handling.
- Refactored TeamManagementPanel and TeamPanel to improve team data fetching logic and loading states.
- Streamlined CoreStateProvider to manage session token synchronization and state updates more effectively.

* chore(deps): update tempfile dependency and refactor app state management

- Added `tempfile` dependency to Cargo.toml for improved temporary file handling.
- Refactored app state loading and saving functions to enhance error handling and ensure data integrity.
- Introduced quarantine mechanism for corrupted app state files to prevent application crashes.
- Updated URL parsing logic to ensure proper formatting of API URLs.
- Adjusted type definitions in schemas for better clarity and consistency.

* refactor(tests): simplify mock state usage in onboarding and protected route tests

- Consolidated mock state management in OnboardingOverlay and ProtectedRoute tests for improved readability.
- Streamlined assertions by reducing unnecessary lines in test setups.
- Enhanced consistency in mock return values across tests to ensure clarity and maintainability.

* refactor(tests): enhance PublicRoute tests with mock state and routing

- Updated PublicRoute tests to utilize MemoryRouter for improved routing simulation.
- Simplified mock state management by integrating `useCoreState` for user authentication scenarios.
- Streamlined test assertions and removed redundant preloaded state configurations for clarity and maintainability.

* refactor(tests): streamline mock state usage in PublicRoute and Mnemonic tests

- Simplified mock state management in PublicRoute and Mnemonic tests for improved readability.
- Consolidated mock return values to reduce redundancy and enhance clarity in test setups.
- Improved consistency in the usage of `useCoreState` across test files.

* Refactor billing components for improved readability

- Cleaned up import statements in BillingPanel.tsx for better organization.
- Enhanced formatting of billing plan descriptions in billingHelpers.ts for consistency.
- Improved readability of conditional checks in BillingPanel component.

* Refactor API endpoint handling for clarity and consistency

- Updated references from `/settings` to `/auth/me` in various modules to standardize user authentication flows.
- Renamed `parse_settings_response_json` to `parse_api_response_json` for improved clarity in response handling.
- Adjusted user ID extraction functions to reflect the new endpoint structure, enhancing maintainability across the codebase.
- Updated test cases to align with the new endpoint naming conventions, ensuring consistency in API interactions.
2026-04-04 03:20:18 -07:00

235 lines
7.4 KiB
TypeScript

import debug from 'debug';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCoreState } from '../providers/CoreStateProvider';
import { tunnelsApi } from '../services/api/tunnelsApi';
import { getCoreHttpBaseUrl } from '../services/coreRpcClient';
import { useAppDispatch, useAppSelector } from '../store/hooks';
import {
addActivity,
addTunnel,
removeTunnel,
setError,
setLoading,
setRegistrations,
setTunnels,
type WebhookActivityEntry,
} from '../store/webhooksSlice';
import {
openhumanWebhooksListLogs,
openhumanWebhooksListRegistrations,
openhumanWebhooksRegisterEcho,
openhumanWebhooksUnregisterEcho,
type WebhookDebugLogEntry,
} from '../utils/tauriCommands';
const log = debug('webhooks');
/** Convert a debug log entry to an activity entry for the ring buffer. */
function logToActivity(entry: WebhookDebugLogEntry): WebhookActivityEntry {
return {
correlation_id: entry.correlation_id,
tunnel_name: entry.tunnel_name,
method: entry.method,
path: entry.path,
status_code: entry.status_code,
skill_id: entry.skill_id,
timestamp: entry.updated_at || entry.timestamp,
};
}
/**
* Hook for managing webhook tunnels, registrations, and live activity.
*
* - Fetches tunnels from the backend API (CRUD)
* - Fetches registrations + debug logs from the Rust core (via JSON-RPC)
* - Subscribes to SSE /events/webhooks for real-time activity updates
*/
export function useWebhooks() {
const { snapshot } = useCoreState();
const dispatch = useAppDispatch();
const { tunnels, registrations, activity, loading, error } = useAppSelector(
state => state.webhooks
);
const token = snapshot.sessionToken;
const [coreConnected, setCoreConnected] = useState(false);
const eventSourceRef = useRef<EventSource | null>(null);
// ── Load registrations + logs from core RPC ──────────────────────────────
const loadCoreData = useCallback(async () => {
try {
const [regsResponse, logsResponse] = await Promise.all([
openhumanWebhooksListRegistrations(),
openhumanWebhooksListLogs(100),
]);
dispatch(setRegistrations(regsResponse.result.result.registrations));
// Seed activity from debug logs
const logs = logsResponse.result.result.logs;
for (const entry of logs.reverse()) {
dispatch(addActivity(logToActivity(entry)));
}
log(
'Loaded %d registrations, %d logs from core',
regsResponse.result.result.registrations.length,
logs.length
);
} catch (err) {
log(
'Core RPC not available (registrations/logs): %s',
err instanceof Error ? err.message : err
);
}
}, [dispatch]);
// ── Fetch tunnels from backend API ───────────────────────────────────────
const fetchTunnels = useCallback(async () => {
dispatch(setLoading(true));
try {
const data = await tunnelsApi.getTunnels();
dispatch(setTunnels(data));
log('Fetched %d tunnels', data.length);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to fetch tunnels';
dispatch(setError(msg));
log('Error fetching tunnels: %s', msg);
}
}, [dispatch]);
// ── Subscribe to SSE for real-time webhook events ────────────────────────
useEffect(() => {
let cancelled = false;
const connect = async () => {
try {
const baseUrl = await getCoreHttpBaseUrl();
if (cancelled) return;
const es = new EventSource(`${baseUrl}/events/webhooks`);
eventSourceRef.current = es;
es.addEventListener('webhooks_debug', () => {
setCoreConnected(true);
// Reload registrations + logs on any debug event (registration change, new log, etc.)
void loadCoreData();
});
es.onopen = () => {
setCoreConnected(true);
log('SSE connected to /events/webhooks');
};
es.onerror = () => {
setCoreConnected(false);
};
} catch {
setCoreConnected(false);
}
};
void connect();
return () => {
cancelled = true;
if (eventSourceRef.current) {
eventSourceRef.current.close();
eventSourceRef.current = null;
}
setCoreConnected(false);
};
}, [loadCoreData]);
// ── Initial data load ────────────────────────────────────────────────────
useEffect(() => {
if (!token) return;
void fetchTunnels();
void loadCoreData();
}, [token, fetchTunnels, loadCoreData]);
// ── CRUD actions ─────────────────────────────────────────────────────────
const createTunnel = useCallback(
async (name: string, description?: string) => {
try {
const tunnel = await tunnelsApi.createTunnel({ name, description });
dispatch(addTunnel(tunnel));
log('Created tunnel: %s (%s)', tunnel.name, tunnel.uuid);
return tunnel;
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to create tunnel';
dispatch(setError(msg));
throw err;
}
},
[dispatch]
);
const deleteTunnel = useCallback(
async (id: string) => {
try {
await tunnelsApi.deleteTunnel(id);
dispatch(removeTunnel(id));
log('Deleted tunnel: %s', id);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to delete tunnel';
dispatch(setError(msg));
throw err;
}
},
[dispatch]
);
const refreshTunnels = useCallback(async () => {
await fetchTunnels();
await loadCoreData();
}, [fetchTunnels, loadCoreData]);
// ── Echo registration ────────────────────────────────────────────────────
const registerEcho = useCallback(
async (tunnelUuid: string, tunnelName?: string, backendTunnelId?: string) => {
try {
const response = await openhumanWebhooksRegisterEcho(
tunnelUuid,
tunnelName,
backendTunnelId
);
dispatch(setRegistrations(response.result.result.registrations));
log('Registered echo for tunnel %s', tunnelUuid);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to register echo';
dispatch(setError(msg));
throw err;
}
},
[dispatch]
);
const unregisterEcho = useCallback(
async (tunnelUuid: string) => {
try {
const response = await openhumanWebhooksUnregisterEcho(tunnelUuid);
dispatch(setRegistrations(response.result.result.registrations));
log('Unregistered echo for tunnel %s', tunnelUuid);
} catch (err) {
const msg = err instanceof Error ? err.message : 'Failed to unregister echo';
dispatch(setError(msg));
throw err;
}
},
[dispatch]
);
return {
tunnels,
registrations,
activity,
loading,
error,
coreConnected,
createTunnel,
deleteTunnel,
refreshTunnels,
registerEcho,
unregisterEcho,
};
}