UNPKG

sayance-matrix-native-module

Version:
323 lines (268 loc) 8.67 kB
# Native-Side Polling Implementation ## Problem Solved The original implementation relied on Rust async tasks in a static library, which caused: - ❌ No callbacks reaching JavaScript - ❌ No visible logs from Rust - ❌ Sync service not working properly - ❌ Events not being delivered ## Solution: Native-Side Polling We moved the **event loop management** from Rust to the native iOS/Android sides, creating a reliable polling system. ## Architecture Changes ### Before (Broken) ``` Rust Static Library → Async Tasks → Callbacks → JavaScript ❌ Async tasks don't run properly in static library ``` ### After (Working) ``` iOS/Android Timer → Rust Polling Functions → Native Events → JavaScript ✅ Platform timers ensure reliable execution ``` ## Implementation Details ### 1. Rust Changes (`rust/src/lib.rs`) **Added New Functions:** - `start_sync_non_blocking()` - Starts sync without blocking - `poll_sync_updates()` - Returns pending events and clears queue - `get_current_sync_state()` - Returns current sync status - `has_pending_events()` - Quick check for pending events - `get_poll_stats()` - Polling statistics and diagnostics **Added Event Queue:** ```rust static PENDING_EVENTS: Lazy<Arc<Mutex<Vec<MatrixEvent>>>> = Lazy::new(|| Arc::new(Mutex::new(Vec::new()))); fn queue_pending_event(event: MatrixEvent) { // Events are queued here for polling } ``` **Comprehensive Logging:** ```rust tracing::info!("🚨 STARTING NON-BLOCKING MATRIX SYNC SERVICE 🚨"); eprintln!("🚨 STARTING NON-BLOCKING MATRIX SYNC SERVICE 🚨"); ``` ### 2. iOS Implementation (`ios/MatrixModule.swift`) **Timer-Based Polling:** ```swift private func startPolling() { syncTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in self.pollForUpdates() } } private func pollForUpdates() { let events = try pollSyncUpdates() for event in events { processMatrixEvent(event) } } ``` **Event Processing:** ```swift private func processMatrixEvent(_ event: MatrixEvent) { switch event { case .syncUpdate(let state): self.sendEvent("onSyncUpdate", [...]) case .timelineEvent(let roomId, let event): self.sendEvent("onTimelineEvent", [...]) // ... other event types } } ``` ### 3. Android Implementation (`android/.../MatrixModule.kt`) **Handler-Based Polling:** ```kotlin private fun startPolling() { syncHandler = Handler(Looper.getMainLooper()) syncRunnable = object : Runnable { override fun run() { pollForUpdates() syncHandler?.postDelayed(this, 1000) } } syncHandler?.post(syncRunnable!!) } ``` **Event Processing:** ```kotlin private fun processMatrixEvent(event: MatrixEvent) { when (event) { is MatrixEvent.SyncUpdate -> { sendEvent("onSyncUpdate", mapOf(...)) } is MatrixEvent.TimelineEvent -> { sendEvent("onTimelineEvent", mapOf(...)) } // ... other event types } } ``` ### 4. TypeScript Interface (`src/index.ts`) **Enhanced Logging:** ```typescript async startSync(): Promise<void> { console.log('🚨 [JS] Starting Matrix sync...'); try { await MatrixModule.startSync(); console.log('✅ [JS] Matrix sync started successfully'); } catch (error) { console.error('❌ [JS] Failed to start Matrix sync:', error); throw error; } } ``` **New Functions:** ```typescript async getPollStats(): Promise<any> { const stats = await MatrixModule.getPollStats(); console.log('📊 [JS] Poll stats:', stats); return stats; } ``` ## Logging Strategy ### Log Levels and Emojis - 🚨 **Critical operations** (start/stop sync) - 🔍 **Polling activity** (every poll cycle) - 📊 **Statistics and diagnostics** - 💬 **Message events** - 📨 **Room invites** - 👤 **Membership changes** -**Success operations** -**Errors and failures** - ⚠️ **Warnings** ### Multi-Platform Logging - **Rust**: `tracing::info!()` + `eprintln!()` for immediate visibility - **iOS**: `NSLog()` for Xcode console - **Android**: `Log.d()` for adb logcat - **JavaScript**: `console.log()` for Metro/Hermes ## Testing Instructions ### 1. Build the Module ```bash # Full build ./build.sh # Or step by step cd rust && cargo build --release cd ../ios && ./build.sh # macOS only cd ../android && ./gradlew build cd .. && npm run build ``` ### 2. Run Tests ```bash # Test module availability node test_polling.js # Test in Expo app expo run:ios # or expo run:android ``` ### 3. Monitor Logs **iOS (Xcode Console):** ``` 🚨 [iOS] Starting sync with polling... 🔍 [iOS] Polling for updates... 📊 [iOS] Polled 2 events 💬 [iOS] Timeline Event - Room: !abc:matrix.org ``` **Android (adb logcat):** ```bash adb logcat | grep MatrixModule ``` Output: ``` 🚨 [Android] Starting sync with polling... 🔍 [Android] Polling for updates... 📊 [Android] Polled 2 events 💬 [Android] Timeline Event - Room: !abc:matrix.org ``` **JavaScript (Metro):** ``` 🚨 [JS] Starting Matrix sync... ✅ [JS] Matrix sync started successfully 📊 [JS] Poll stats: {pending_events: 0, ...} ``` ## Performance Characteristics ### Polling Frequency - **Default**: 1 second intervals - **Configurable**: Can be adjusted per platform - **Efficient**: Only polls when events are pending ### Memory Management - **Event Queue**: Limited to 1000 events (prevents memory leaks) - **Stats Tracking**: Lightweight counters - **Timer Cleanup**: Proper cleanup on stop ### Battery Optimization - **iOS**: Compatible with background app refresh - **Android**: Uses main looper for efficiency - **Smart Polling**: Checks `has_pending_events()` before full poll ## Expected Log Output ### Successful Initialization ``` 🚀 [Rust] Starting Matrix client initialization 📁 [iOS] Using storage path: /Documents/matrix_storage ✅ [iOS] Matrix client initialized successfully 🚨 [iOS] Starting sync with polling... 🔄 [iOS] Starting polling with interval: 1.0 seconds ✅ [iOS] Polling timer created and scheduled ``` ### Active Polling ``` 🔍 [iOS] Polling for updates... 📊 [Rust] POLLED 3 PENDING EVENTS 🔄 Sync Update: SYNCING 💬 Timeline Event: $event123 in !room:matrix.org 📨 Room Invite: !newroom:matrix.org from @user:matrix.org 🎯 [iOS] Processing event: SyncUpdate 🔄 [iOS] Sync Update - State: SYNCING, Rooms: 5 ``` ### Statistics Output ```javascript { "pending_events": 0, "last_poll_seconds_ago": 1, "sync_state": "SYNCING", "ios_polling": true, "ios_poll_interval": 1.0, "ios_stats": { "start_time": 1699123456, "total_polls": 147, "events_received": 23, "last_poll_time": 1699123503 } } ``` ## Troubleshooting ### No Logs Appearing 1. **Check build**: Ensure `./build.sh` completed successfully 2. **Check linking**: Verify native module is properly linked in your app 3. **Check console**: Look in the right console (Xcode/Metro/adb) ### No Events Received 1. **Check authentication**: Verify access token and user ID 2. **Check network**: Ensure homeserver is reachable 3. **Check rooms**: User must be in active rooms 4. **Check polling**: Verify `getPollStats()` shows active polling ### Performance Issues 1. **Adjust poll interval**: Increase from 1 second if needed 2. **Monitor memory**: Check for event queue growth 3. **Check battery usage**: Monitor background activity ## Migration from Old Implementation ### Code Changes Required 1. **Replace `startSync()`**: No code changes needed (same API) 2. **Monitor logs**: Look for new emoji-based logs 3. **Check stats**: Use `getPollStats()` for diagnostics 4. **Event handling**: Existing event listeners continue to work ### Verification Steps 1. ✅ Logs appear in native console 2. ✅ Events reach JavaScript listeners 3. ✅ Sync state updates properly 4. ✅ Messages and invites work 5. ✅ Polling stats show activity ## Benefits of New Implementation ### Reliability -**Platform timers** ensure execution -**Visible debugging** through comprehensive logs -**Predictable behavior** across iOS/Android ### Performance -**Lower latency** (1-second polling vs async delays) -**Better battery life** (optimized polling) -**Memory safety** (bounded event queues) ### Maintainability -**Clear separation** of concerns -**Easy debugging** with emoji logs -**Platform-specific optimization** The implementation successfully resolves the original async runtime issues by moving event loop management to the native platform side where it belongs in a static library architecture.