* feat: lifecycle, window-message & streaming audio hooks (#23) Add server lifecycle hooks, iframe/embed messaging, and streaming audio input hooks that complement the existing realtime-voice protocol. ## Features Added ### Lifecycle hooks - `@aiui.on_app_startup` — server starts, **after** config load, before first request - `@aiui.on_app_shutdown` — graceful shutdown (drain connections, flush datastore) ### Window message hooks - `@aiui.on_window_message(source?)` — browser `window.postMessage` inbound events - `aiui.send_window_message(data, target="parent")` — outbound messaging to host page ### Streaming audio hooks - `@aiui.on_audio_start` — user clicked mic button; session ready - `@aiui.on_audio_chunk` — streaming input audio chunk (PCM/opus) - `@aiui.on_audio_end` — recording stopped (by user or VAD silence timeout) ## Implementation - Protocol-driven features following existing BaseFeatureProtocol pattern - Lazy imports - zero overhead when unused - Async-safe with proper error handling - Starlette lifespan integration for startup/shutdown hooks - WebSocket endpoint `/ws/audio` for real-time audio streaming - SSE bridge for window message communication - Comprehensive test coverage (45+ test cases) ## Frontend Hooks - `useWindowMessage` - iframe postMessage bridge with security validation - `useMicStream` - microphone → PCM16 → WebSocket streaming 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * fix: address critical security and concurrency issues - Replace global session state with per-session registry in window_message.py - Add session_id parameter to audio hooks for proper stream identification - Add missing /api/window-message/receive endpoint for frontend integration - Fix postMessage security by avoiding wildcard origins where possible - Update all tests to reflect new hook signatures Addresses critical findings from gemini-code-assist review. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Mervin Praison <[email protected]> * fix: address critical security and concurrency issues - Fix postMessage security vulnerabilities by eliminating wildcard origins - Add TODO comment for deprecated ScriptProcessorNode replacement - Update PR body with comprehensive evidence and test results Co-authored-by: Mervin Praison <[email protected]> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude <[email protected]> Co-authored-by: Mervin Praison <[email protected]>
9.9 KiB
Summary
This PR implements server-side lifecycle hooks, iframe/embed messaging capabilities, and streaming audio input hooks per issue #23. The implementation adds three new features: lifecycle management with startup/shutdown hooks for resource initialization and cleanup, window messaging for secure iframe communication via postMessage, and streaming audio hooks to support server-side STT pipelines. These features complement the existing realtime-voice protocol by providing more flexible audio processing options and enabling AIUI to be embedded within other web applications while maintaining proper lifecycle management.
Before / After
Before (users had to manually manage lifecycle):
# No way to warm up resources before first request
# No graceful shutdown handling
# No iframe communication support
# Limited to realtime WebRTC for voice
After (with this PR):
@aiui.on_app_startup
async def warm():
global vector_store
vector_store = await VectorStore.connect()
@aiui.on_app_shutdown
async def drain():
await vector_store.close()
@aiui.on_window_message(source="parent")
async def on_msg(data: dict):
if data.get("type") == "set_user":
await aiui.send_window_message({"type": "user_set", "ok": True})
@aiui.on_audio_chunk
async def chunk(session_id: str, pcm: bytes, sample_rate: int):
await stt_buffer.append(pcm)
Acceptance criteria checklist with evidence
- Startup hook completion blocks first request acceptance (no cold-start request mis-dispatch) — see
src/praisonaiui/features/lifecycle.py:76-97andsrc/praisonaiui/server.py:26-29(commitb8b546d) - Shutdown hook has 30s default grace period; configurable via
AIUI_SHUTDOWN_TIMEOUT— seesrc/praisonaiui/features/lifecycle.py:105-128(commitb8b546d) send_window_message({..}, target="parent")only posts to the declared origin (security §4.4) — seesrc/praisonaiui/features/window_message.py:287-290andsrc/frontend/src/hooks/useWindowMessage.ts:176-183(commitb8b546d)- Audio chunks received in order; WS reconnect resumes at last acked offset — see
src/praisonaiui/features/audio.py:211-223(commitb8b546d) - Hooks are lazy — unused hooks incur zero overhead — see lazy imports in
src/praisonaiui/__init__.py:28-59(commitb8b546d) - 15+ tests pass — see test evidence below: 56/56 tests passing (commit
b8b546d)
Test evidence
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0 -- /usr/bin/python
cachedir: .pytest_cache
rootdir: /home/runner/work/PraisonAIUI/PraisonAIUI
configfile: pyproject.toml
plugins: asyncio-1.3.0, cov-7.1.0, anyio-4.13.0
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collecting ... collected 56 items
tests/unit/test_audio_hooks.py::TestAudioFeature::test_feature_protocol PASSED [ 1%]
tests/unit/test_audio_hooks.py::TestAudioFeature::test_health_endpoint PASSED [ 3%]
tests/unit/test_audio_hooks.py::TestAudioHookRegistration::test_start_hook_registration PASSED [ 5%]
tests/unit/test_audio_hooks.py::TestAudioHookRegistration::test_chunk_hook_registration PASSED [ 7%]
tests/unit/test_audio_hooks.py::TestAudioHookRegistration::test_end_hook_registration PASSED [ 8%]
tests/unit/test_audio_hooks.py::TestAudioHookRegistration::test_decorator_syntax PASSED [ 10%]
tests/unit/test_audio_hooks.py::TestAudioHookRegistration::test_duplicate_registration_prevention PASSED [ 12%]
tests/unit/test_audio_hooks.py::TestAudioHookExecution::test_start_hooks_execution PASSED [ 14%]
tests/unit/test_audio_hooks.py::TestAudioHookExecution::test_chunk_hooks_execution PASSED [ 16%]
tests/unit/test_audio_hooks.py::TestAudioHookExecution::test_end_hooks_execution PASSED [ 17%]
tests/unit/test_audio_hooks.py::TestAudioHookExecution::test_hook_error_handling PASSED [ 19%]
tests/unit/test_audio_hooks.py::TestAudioSessionManagement::test_session_creation_and_cleanup PASSED [ 21%]
tests/unit/test_audio_hooks.py::TestAudioSessionManagement::test_chunk_processing_updates_session PASSED [ 23%]
tests/unit/test_audio_hooks.py::TestAudioStats::test_initial_stats PASSED [ 25%]
tests/unit/test_audio_hooks.py::TestAudioStats::test_stats_tracking PASSED [ 26%]
tests/unit/test_audio_hooks.py::TestAudioStats::test_reset_state PASSED [ 28%]
tests/unit/test_audio_hooks.py::TestAudioIntegration::test_stt_pipeline_example PASSED [ 30%]
tests/unit/test_audio_hooks.py::TestAudioIntegration::test_multiple_stt_providers PASSED [ 32%]
tests/unit/test_audio_hooks.py::TestAudioIntegration::test_audio_session_ordering PASSED [ 33%]
tests/unit/test_audio_hooks.py::TestAudioIntegration::test_concurrent_audio_sessions PASSED [ 35%]
tests/unit/test_lifecycle.py::TestLifecycleFeature::test_feature_protocol PASSED [ 37%]
tests/unit/test_lifecycle.py::TestLifecycleFeature::test_health_endpoint PASSED [ 39%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_startup_hook_registration PASSED [ 41%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_shutdown_hook_registration PASSED [ 42%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_decorator_syntax PASSED [ 44%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_duplicate_registration_prevention PASSED [ 46%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_startup_hooks_execution PASSED [ 48%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_shutdown_hooks_execution PASSED [ 50%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_startup_hook_failure_handling PASSED [ 51%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_shutdown_hook_failure_handling PASSED [ 53%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_shutdown_timeout PASSED [ 55%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_startup_idempotent PASSED [ 57%]
tests/unit/test_lifecycle.py::TestLifecycleHooks::test_shutdown_idempotent PASSED [ 58%]
tests/unit/test_lifecycle.py::TestLifecycleState::test_initial_state PASSED [ 60%]
tests/unit/test_lifecycle.py::TestLifecycleState::test_reset_state PASSED [ 62%]
tests/unit/test_lifecycle.py::TestLifecycleIntegration::test_vector_store_example PASSED [ 64%]
tests/unit/test_lifecycle.py::TestLifecycleIntegration::test_resource_lifecycle PASSED [ 66%]
tests/unit/test_window_message.py::TestWindowMessageFeature::test_feature_protocol PASSED [ 67%]
tests/unit/test_window_message.py::TestWindowMessageFeature::test_health_endpoint PASSED [ 69%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_hook_registration PASSED [ 71%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_decorator_syntax PASSED [ 73%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_message_handling_with_specific_source PASSED [ 75%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_message_handling_with_wildcard PASSED [ 76%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_message_handling_with_multiple_hooks PASSED [ 78%]
tests/unit/test_window_message.py::TestWindowMessageHooks::test_hook_error_handling PASSED [ 80%]
tests/unit/test_window_message.py::TestWindowMessageLogging::test_inbound_message_logging PASSED [ 82%]
tests/unit/test_window_message.py::TestWindowMessageLogging::test_outbound_message_logging PASSED [ 83%]
tests/unit/test_window_message.py::TestWindowMessageSending::test_send_to_parent PASSED [ 85%]
tests/unit/test_window_message.py::TestWindowMessageSending::test_send_with_custom_target PASSED [ 87%]
tests/unit/test_window_message.py::TestWindowMessageSending::test_send_with_invalid_target PASSED [ 89%]
tests/unit/test_window_message.py::TestWindowMessageSending::test_send_with_session_context PASSED [ 91%]
tests/unit/test_window_message.py::TestWindowMessageSecurity::test_origin_filtering PASSED [ 92%]
tests/unit/test_window_message.py::TestWindowMessageSecurity::test_target_validation_in_send PASSED [ 94%]
tests/unit/test_window_message.py::TestWindowMessageIntegration::test_user_context_example PASSED [ 96%]
tests/unit/test_window_message.py::TestWindowMessageIntegration::test_bidirectional_communication PASSED [ 98%]
tests/unit/test_window_message.py::TestWindowMessageIntegration::test_multiple_iframe_sources PASSED [100%]
================================ 56 passed in 2.35s ==============================
Import-time proof
161.1ms 263 modules
Heavy dependencies check:
[]
The import time is 161.1ms (under the 200ms requirement) and no heavy optional dependencies are loaded in sys.modules.
Ruff-clean for your new files
RUFF OK
All new Python files pass ruff linting without issues.
Out-of-scope
As specified in issue #23:
- Built-in STT providers (Whisper / Deepgram) — users plug their own into
on_audio_chunk. - VAD on the backend — follow-up (frontend VAD via Web Audio API is enough for v1).
No changes to unrelated modules were made. All modifications are confined to the files listed in the issue requirements table.
Critical Issues Addressed
This PR also addresses all critical security and concurrency issues identified by gemini-code-assist:
- ✅ Fixed concurrency issue in window messaging - Replaced global state with per-session
_session_contextsregistry to support multi-user environments properly - ✅ Enhanced audio hooks with session identification - All audio chunk hooks now receive
session_idparameter for proper stream identification - ✅ Added missing backend endpoint -
/api/window-message/receiveendpoint is implemented and functional - ✅ Secured postMessage communications - Eliminated wildcard origins, enforce specific origins or fallback to current origin for security
- ✅ Documented deprecated ScriptProcessorNode - Added TODO comment noting replacement with AudioWorklet for future enhancement
All 56 tests pass, import performance meets requirements, and the implementation is ready for production use.