Skip to content

feat(sockets/stellar): WebSocket health checks, offline sync, location broadcast & Soroban RPC setup#75

Open
gregemax wants to merge 4 commits into
SwiftChainn:mainfrom
gregemax:feat/socket-location-broadcast
Open

feat(sockets/stellar): WebSocket health checks, offline sync, location broadcast & Soroban RPC setup#75
gregemax wants to merge 4 commits into
SwiftChainn:mainfrom
gregemax:feat/socket-location-broadcast

Conversation

@gregemax

Copy link
Copy Markdown

Summary

This PR resolves four issues in a single cohesive branch that builds each feature on top of the previous:


Closes #26 — WebSocket connection health checks and disconnect cleanup

Branch origin: feat/socket-health-checks

Files added/modified:

  • src/sockets/socket.types.ts — Strongly-typed interfaces: SocketConnectionMeta, PingPayload, PongPayload, HealthCheckResult, typed server/client event maps
  • src/sockets/socket.service.tsSocketService class: connection registration, periodic ping/pong health-check loop (SOCKET_PING_INTERVAL_MS, SOCKET_MAX_MISSED_PONGS), stale-connection eviction, room join/leave tracking, full Winston logging
  • src/sockets/connectionHandler.tsinitializeSocketServer() wires typed Socket.IO to the HTTP server; registers all per-socket event handlers; shutdownSocketServer() for graceful teardown
  • src/server.ts — Wraps Express in http.Server, attaches Socket.IO, includes Socket.IO in graceful shutdown sequence
  • tests/socket.service.test.ts — 22 unit tests covering all service methods including health-check tick eviction

Key behaviours:

  • Server emits ping every 25 s (configurable); missing pongs increment a counter; after MAX_MISSED_PONGS consecutive misses the socket is force-disconnected
  • handleDisconnect removes the connection record, logs duration, rooms, and reason
  • Orphaned sockets are cleaned from the registry directly

Closes #27 — Offline data sync/catch-up for drivers reconnecting to WebSockets

Branch origin: feat/socket-offline-sync

Files added/modified:

  • src/models/LocationUpdate.ts — Mongoose schema with driverId, deliveryId, coordinates (lat/lng), capturedAt, isOfflineSync flag, status (pending|processed|failed), compound indexes
  • src/sockets/socket.types.ts — Extended with OfflineLocationPoint, LocationSyncPayload, SyncItemResult, LocationSyncAck; location_sync client event, location_sync_ack server event
  • src/sockets/sync.service.tsSyncService.processBatch(): validates each point, deduplicates against DB + within batch, bulk-inserts via insertMany(ordered:false), returns full per-item ack; SYNC_BATCH_SIZE_LIMIT env guard
  • src/sockets/syncHandler.tsregisterSyncHandler(): auth guard, payload guard, delegates to service, emits location_sync_ack with error fallback
  • src/sockets/connectionHandler.ts — Wired registerSyncHandler into connection handler
  • tests/sync.service.test.ts — 23 unit tests (MongoMemoryServer): persistence, DB dedup, within-batch dedup, validation boundaries, guard rails, ack shape

Closes #28 — Install Stellar SDK and configure Soroban RPC client

Branch origin: feat/soroban-rpc-setup

Files added/modified:

  • package.json — Added @stellar/stellar-sdk@13.1.0
  • .env.example — Added SOROBAN_RPC_URL, STELLAR_NETWORK_PASSPHRASE, STELLAR_NETWORK, SOROBAN_RPC_TIMEOUT_MS with documentation comments
  • src/config/stellar.tsresolveStellarConfig() validates env at startup (fast-fail); createSorobanRpcClient() factory; sorobanRpcClient singleton; supports mainnet/testnet/futurenet
  • src/blockchain/soroban.service.tsSorobanService.checkConnectivity(): calls getHealth() + getLatestLedger() in parallel, returns typed result or error (never throws); getLatestLedger(), getNetworkInfo()
  • src/controllers/stellar.controller.tscheckHealth (200/503), getNetworkInfo (200), getLatestLedger (200)
  • src/routes/stellar.routes.ts + src/routes/index.ts — Mounts GET /api/v1/stellar/health, GET /api/v1/stellar/network, GET /api/v1/stellar/ledger/latest
  • tests/soroban.service.test.ts — 11 unit tests (fully offline, RPC client mocked via constructor injection)

Closes #25 — WebSocket events for real-time driver location broadcasting

Branch origin: feat/socket-location-broadcast

Files added/modified:

  • src/sockets/socket.types.ts — Added DriverLocationUpdatePayload, LocationBroadcastPayload, LocationUpdateAck; driver_location_update client event; location:update + location_update_ack server events
  • src/sockets/location.service.tsLocationService.processLiveUpdate(): validates payload, persists to MongoDB (isOfflineSync=false), broadcasts location:update to delivery:<id> room, returns typed ack (never throws); deliveryRoom() helper; DELIVERY_ROOM_PREFIX constant
  • src/sockets/locationHandler.tsregisterLocationHandler(io, socket): auth guard, payload guard, delegates to service, emits location_update_ack back to driver
  • src/sockets/connectionHandler.ts — Wired registerLocationHandler(io, socket) into connection handler
  • tests/location.service.test.ts — 20 unit tests (MongoMemoryServer): ack shape, DB persistence, room broadcast correctness, receivedAt ISO, fallback capturedAt, single-emit, validation boundaries, no-persist/no-broadcast on failure

Architecture

All features follow the mandated Controller → Service → Model layered pattern:

  • Model: Mongoose LocationUpdate — all data retrieved from/persisted to MongoDB
  • Service: Pure business logic, no Express/Socket.IO coupling
  • Controller: Event wiring, auth guards, delegates to services

Testing

Suite Tests
socket.service.test.ts 22
sync.service.test.ts 23
soroban.service.test.ts 11
location.service.test.ts 20
health.test.ts 1
Total 76

All 76 tests pass. TypeScript build is clean (pnpm build exits 0).

Environment Variables Added

SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
STELLAR_NETWORK_PASSPHRASE=Test SDF Network ; September 2015
STELLAR_NETWORK=testnet
SOROBAN_RPC_TIMEOUT_MS=10000
SOCKET_PING_INTERVAL_MS=25000
SOCKET_PING_TIMEOUT_MS=20000
SOCKET_MAX_MISSED_PONGS=2
SYNC_BATCH_SIZE_LIMIT=500

gregemax added 4 commits June 30, 2026 12:05
…up (SwiftChainn#26)

- Add socket.io@4.7.2 to dependencies
- Create src/sockets/socket.types.ts: strongly-typed interfaces for
  connection metadata, ping/pong payloads, server/client events, and
  HealthCheckResult
- Create src/sockets/socket.service.ts: SocketService class (business
  logic layer) with:
  * Connection registration and in-memory tracking
  * Ping/pong health-check loop with configurable interval and
    max-missed-pong threshold (env: SOCKET_PING_INTERVAL_MS,
    SOCKET_MAX_MISSED_PONGS)
  * Stale connection eviction after exceeding missed-pong threshold
  * Room join/leave tracking for clean state on disconnect
  * Full Winston logging for connect, pong, disconnect, and eviction events
- Create src/sockets/connectionHandler.ts: controller layer that
  initialises the typed Socket.IO server, registers per-socket event
  handlers, and exposes initializeSocketServer / shutdownSocketServer
- Update src/server.ts: wrap Express in http.Server, attach Socket.IO,
  include Socket.IO in graceful shutdown sequence
- Add tests/socket.service.test.ts: 22 unit tests covering all service
  methods including health-check tick eviction logic

Closes SwiftChainn#26
…tChainn#27)

Model layer:
- Add src/models/LocationUpdate.ts: Mongoose schema with driverId,
  deliveryId, coordinates (lat/lng), capturedAt, isOfflineSync flag,
  status (pending|processed|failed), and compound indexes for efficient
  per-driver chronological queries

Service layer:
- Add src/sockets/sync.service.ts: SyncService class with processBatch()
  * Validates each OfflineLocationPoint (capturedAt, lat/lng bounds,
    deliveryId ObjectId format)
  * Detects duplicates against DB (driverId + capturedAt) and within
    the same batch using a Set
  * Bulk-inserts valid unique points via insertMany(ordered:false)
  * Returns full LocationSyncAck with per-item SyncItemResult breakdown
  * Enforces configurable SYNC_BATCH_SIZE_LIMIT (default 500)

Controller layer:
- Add src/sockets/syncHandler.ts: registerSyncHandler() wires the
  location_sync socket event, guards unauthenticated sockets, delegates
  to SyncService, emits location_sync_ack (success or error fallback)
- Update src/sockets/connectionHandler.ts: call registerSyncHandler
  inside io.on('connection')

Types:
- Extend src/sockets/socket.types.ts with OfflineLocationPoint,
  LocationSyncPayload, SyncItemResult, LocationSyncAck; add
  location_sync to ClientToServerEvents and location_sync_ack to
  ServerToClientEvents

Tests:
- Add tests/sync.service.test.ts: 23 unit tests using MongoMemoryServer
  covering persistence, deduplication (DB + within-batch), validation
  (boundary values, invalid fields), guard rails, and ack shape
- Update jest.config.js: set testTimeout=30000 for MongoMemoryServer

Closes SwiftChainn#27
…wiftChainn#28)

- Install @stellar/stellar-sdk@13.1.0

Config layer (src/config/stellar.ts):
  * resolveStellarConfig() validates env vars at startup (fast-fail on
    bad config); resolves SOROBAN_RPC_URL, STELLAR_NETWORK_PASSPHRASE,
    STELLAR_NETWORK, SOROBAN_RPC_TIMEOUT_MS with sensible defaults
  * createSorobanRpcClient() factory for rpc.Server instances
  * Pre-built sorobanRpcClient singleton for production code paths
  * Supports mainnet | testnet | futurenet

Service layer (src/blockchain/soroban.service.ts):
  * SorobanService.checkConnectivity(): calls getHealth() and
    getLatestLedger() in parallel; returns typed ConnectivityCheckResult
    on success or ConnectivityCheckError on failure (never throws)
  * SorobanService.getLatestLedger(): returns ledger sequence number
  * SorobanService.getNetworkInfo(): returns raw getNetwork() response
  * Client injected via constructor for testability

Controller layer (src/controllers/stellar.controller.ts):
  * StellarController.checkHealth(): 200 on healthy, 503 on unhealthy
  * StellarController.getNetworkInfo(): 200 with passphrase + protocol
  * StellarController.getLatestLedger(): 200 with sequence number

Routes (src/routes/stellar.routes.ts, src/routes/index.ts):
  * GET /api/v1/stellar/health
  * GET /api/v1/stellar/network
  * GET /api/v1/stellar/ledger/latest

Tests (tests/soroban.service.test.ts): 11 unit tests
  * Healthy node: correct fields, parallel calls, latency, ISO dates
  * Unhealthy node: ECONNREFUSED, timeout, non-Error throws, rpcUrl present
  * getLatestLedger: success and error propagation
  * getNetworkInfo: success and error propagation

.env.example: added SOROBAN_RPC_URL, STELLAR_NETWORK_PASSPHRASE,
  STELLAR_NETWORK, SOROBAN_RPC_TIMEOUT_MS with documentation comments

Closes SwiftChainn#28
…on broadcasting (SwiftChainn#25)

Types (src/sockets/socket.types.ts):
  * Add DriverLocationUpdatePayload: deliveryId, lat, lng, capturedAt
  * Add LocationBroadcastPayload: deliveryId, driverId, lat, lng,
    capturedAt, receivedAt (ISO)
  * Add LocationUpdateAck: success flag, locationId, optional error
  * Add driver_location_update to ClientToServerEvents
  * Add location:update and location_update_ack to ServerToClientEvents

Service layer (src/sockets/location.service.ts):
  * LocationService.processLiveUpdate():
    - Validates payload (driverId, deliveryId ObjectId, lat/lng bounds,
      capturedAt epoch)
    - Persists to MongoDB via LocationUpdate model (isOfflineSync=false,
      status=pending)
    - Broadcasts location:update to delivery:<deliveryId> room via io.to()
    - Returns typed LocationUpdateAck (never throws)
  * deliveryRoom() helper for canonical room name construction
  * DELIVERY_ROOM_PREFIX constant exported for reuse

Controller layer (src/sockets/locationHandler.ts):
  * registerLocationHandler(io, socket):
    - Auth guard: rejects unauthenticated drivers with ack error
    - Payload guard: rejects malformed objects
    - Delegates to LocationService, emits location_update_ack back to driver
    - Adds delivery-specific validation/logging to join_room for
      delivery: prefixed rooms

Integration (src/sockets/connectionHandler.ts):
  * Wire registerLocationHandler(io, socket) inside io.on('connection')

Tests (tests/location.service.test.ts): 20 unit tests using MongoMemoryServer
  * deliveryRoom helper: prefix format, uniqueness
  * processLiveUpdate success: ack shape, DB persistence (isOfflineSync,
    coordinates, deliveryId), broadcast to correct room, receivedAt ISO,
    fallback capturedAt, single emit
  * processLiveUpdate validation: invalid driverId, empty/invalid deliveryId,
    lat/lng OOB, NaN, capturedAt=0, boundary values, no-broadcast on failure,
    no-persist on failure

Closes SwiftChainn#25
@drips-wave

drips-wave Bot commented Jun 30, 2026

Copy link
Copy Markdown

@gregemax Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

Labels

None yet

Projects

None yet

1 participant