Skip to content

Conversation

@anivar
Copy link
Contributor

@anivar anivar commented Sep 24, 2025

This PR addresses several long-standing user requests for better WebSocket connection control in the GraphQL API client. Users have reported issues with WebSocket connections not automatically reconnecting after network interruptions and lacking visibility into connection health status (Fixes #9749, #4459, #5403, #7057).

The implementation enhances the existing AWSWebSocketProvider class with four new methods that leverage the existing ConnectionStateMonitor infrastructure and keep-alive tracking. These methods provide both real-time health monitoring and manual connection control that users have been requesting.

Enhanced WebSocket Provider Methods

  • getConnectionHealth() - Returns current health state using in-memory keep-alive tracking
  • getPersistentConnectionHealth() - Returns health state with cross-session persistence via AsyncStorage/localStorage
  • isConnected() - Checks if WebSocket.readyState === OPEN for immediate connection status
  • reconnect() - Manual connection control for user-triggered reconnection scenarios

Technical Implementation

The health monitoring uses the existing 65-second keep-alive threshold (DEFAULT_KEEP_ALIVE_ALERT_TIMEOUT) already established in the AppSync WebSocket protocol. A WebSocket is considered healthy when three conditions are met: connection state is Connected, a keep-alive was received within 65 seconds, and the underlying WebSocket readyState is OPEN.

For persistence, the implementation uses a platform-safe approach with AsyncStorage for React Native and localStorage fallback for web platforms. Keep-alive timestamps are stored at 'AWS_AMPLIFY_LAST_KEEP_ALIVE' to avoid key collisions. When storage is unavailable, the feature gracefully degrades to in-memory tracking only.

Code Quality Improvements (Latest Update)

Refactoring

  • Extracted helper methods: calculateHealthState(), getPersistedKeepAliveTimestamp(), persistKeepAliveTimestamp() for better code organization
  • Improved platform storage initialization: Refactored into testable initializePlatformStorage() function
  • Enhanced reconnect method: Added concurrency guards, proper error handling, and try/catch/finally blocks
  • Type consistency: Fixed WebSocketHealthState to use non-optional fields with sensible defaults (0 for timestamps, Infinity for never-received)

Test Coverage

  • 21 comprehensive unit tests covering all new functionality:
    • getConnectionHealth() - 3 tests (healthy/unhealthy states, stale keep-alive)
    • getPersistentConnectionHealth() - 4 tests (storage scenarios, invalid data)
    • isConnected() - 5 tests (all WebSocket states)
    • reconnect() - 6 tests (concurrency, cleanup, error handling)
    • Error handling - 3 tests (graceful degradation)
  • All 170 tests passing with comprehensive edge case coverage
  • Fast unit test approach (2.8s) using mocked dependencies

Review Feedback Addressed

All previous review comments from @bobbor have been addressed:

  • ✅ Fixed Promise handling with proper async/await and .catch()
  • ✅ Improved type safety (KeyValueStorageInterface instead of any)
  • ✅ Using proper import statements instead of inline imports
  • ✅ Removed unnecessary underscore prefixes
  • ✅ Using Date.now() instead of JSON.stringify(new Date())
  • ✅ Simplified methods by removing redundant logic
  • ✅ Added proper error handling throughout

Backward Compatibility

All changes are backward compatible with no breaking changes to existing functionality. The new methods are additive enhancements that don't affect existing WebSocket behavior.

Testing

Run tests with:

cd packages/api-graphql
npm test

All 170 tests pass including 21 new comprehensive unit tests for the health monitoring features.

Copy link
Member

@bobbor bobbor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for opening the PR.
Please take a look at the comments

@anivar anivar force-pushed the feat/websocket-health-monitoring branch from 1eb4414 to 044fc3c Compare October 31, 2025 13:06
…ction

- Add getConnectionHealth() method for current connection state
- Add getPersistentConnectionHealth() for cross-session health tracking
- Add isConnected() for quick connection status check
- Add reconnect() for manual WebSocket reconnection
- Implement persistent keep-alive tracking using AsyncStorage/localStorage
- Use 65-second threshold for health monitoring
- Track keep-alive timestamps for connection health assessment

Fixes aws-amplify#9749, aws-amplify#4459, aws-amplify#5403, aws-amplify#7057
@anivar anivar force-pushed the feat/websocket-health-monitoring branch from d1cf448 to 9c832cd Compare October 31, 2025 13:35
@anivar anivar requested a review from bobbor November 11, 2025 16:51
@bobbor bobbor self-assigned this Dec 3, 2025
Refactors WebSocket health monitoring implementation to improve
maintainability, testability, and code organization:

- Extract duplicated health check logic into calculateHealthState()
- Refactor platform storage initialization into function
- Extract storage operations into reusable private methods
- Improve reconnect() with error handling and concurrency guards
- Fix type consistency in WebSocketHealthState (non-optional fields)

Add comprehensive unit test coverage (21 new tests):
- getConnectionHealth() - 3 tests covering healthy/unhealthy states
- getPersistentConnectionHealth() - 4 tests with storage scenarios
- isConnected() - 5 tests for all WebSocket states
- reconnect() - 6 tests including concurrency protection
- Error handling - 3 tests for graceful degradation

All tests pass (170 total). No breaking changes to public API.
@anivar
Copy link
Contributor Author

anivar commented Dec 5, 2025

Hi @bobbor - I've addressed all the review feedback and made additional improvements to code quality.

Review feedback addressed:

  • Fixed Promise handling with proper .catch()
  • Using KeyValueStorageInterface instead of any
  • Proper import statements at the top
  • Simplified code where possible
  • Using Date.now() instead of JSON.stringify(new Date())

Additional refactoring:

  • Extracted helper methods (calculateHealthState, getPersistedKeepAliveTimestamp, persistKeepAliveTimestamp)
  • Refactored platform storage initialization into testable function
  • Added concurrency guards and proper error handling to reconnect()
  • Fixed type consistency in WebSocketHealthState (non-optional fields with defaults)

Test coverage:

  • Added 21 unit tests covering all new functionality
  • All 170 tests passing

No breaking changes. Ready for re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unable to reconnect AppSync after connection interruption (web) or app going into background (ios/ android)

2 participants