Introduction: The Real-Time Constraint in Developing Markets
Modern applications increasingly rely on real-time capabilities to keep interfaces dynamic and interactive. Whether it is an instant messaging system for buyers and sellers in a marketplace or live notifications for students in an educational portal, users expect instant updates.
However, deploying real-time applications in regions with constrained infrastructure—such as Syria and the broader Levant—presents major technical challenges:
- Unstable Cellular Networks: Frequent handovers between weak 3G and spotty 4G cell towers result in high packet loss and sudden TCP connection resets.
- High Mobile Data Costs: Expensive cellular plans make continuous HTTP polling or verbose JSON streams impractical for users.
- Power Interruptions: Devices shut down unexpectedly, and cellular base stations reboot, causing large-scale reconnections.
This case study examines how Dragonfly Soft designed and engineered a resilient, bandwidth-efficient real-time communication architecture for its two hero assets: Lernce Web Learning Platform and Husomat Marketplace Website and Mobile App.
---
Architectural Design: Unidirectional vs. Bidirectional Streams
To address bandwidth limits and avoid the battery drain of short-polling, Dragonfly Soft divided real-time traffic into two distinct paradigms:
- Unidirectional Event Streaming (Server-Sent Events):
Used in Lernce for broadcasting live course status updates, exam start signals, and system-wide announcements. * Why SSE? Unlike WebSockets, SSE runs over standard HTTP/2, supports native automatic reconnection, uses clean text protocols, and passes easily through strict firewalls without custom proxies.
- Bidirectional Stateful Communication (WebSockets):
Used in Husomat for the peer-to-peer negotiation chat system, and in Lernce for interactive live classroom chat. * Why WebSockets? Bidirectional, low-overhead communication is essential for immediate message exchanges between buyers and sellers, avoiding the latency of individual HTTP request-response cycles.
graph TD
subgraph Client Application [Client Layer]
App[App Instance] -->|SSE Hook| EventSource[EventSource Client]
App -->|WS Hook| WSClient[WebSocket ClientManager]
WSClient -->|Local Cache| Storage[(IndexedDB / SQLite)]
end
subgraph API Gateway [Ingress Gateway]
EventSource -->|GET /api/events| SSEGate[SSE Connection Pool]
WSClient -->|Upgrade Header| WSGate[WebSocket Gateway]
end
subgraph Backend Services [Application Layer]
SSEGate -->|Redis PubSub| Notification[Notification Engine]
WSGate -->|Direct Socket| Chat[Chat & Sync Service]
Chat -->|PostgreSQL Transaction| DB[(Central Database)]
end
---
Case Study 1: Lernce — Lightweight Notification Streaming via Server-Sent Events
The Challenge
For the Lernce online classroom catalog and live student portal, the system had to push immediate events, such as when an instructor uploads a document, updates a live video URL, or starts an online quiz. Short polling was ruled out as it bloated server requests and consumed student data packages even when no updates occurred.
The Solution: SSE with Heartbeat and Catch-Up
Dragonfly Soft implemented an SSE endpoint utilizing a persistent HTTP/2 connection. To prevent cellular gateways from closing idle TCP sockets (which typically time out after 60–120 seconds of inactivity on regional mobile networks), the server broadcasts a lightweight heartbeat frame (:keepalive\n\n) every 30 seconds.
Additionally, to prevent students from missing critical updates during a network drop, each event contains a unique, monotonic EventID. When the client disconnects, the native browser EventSource automatically attempts reconnection and includes the last successfully received ID in the Last-Event-ID header. The server then replays any events missed during the downtime.
Here is the simplified backend implementation of Lernce’s event-streaming handler:
// Express SSE router implementation for Lernce
app.get('/api/events/subscribe', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // Establish stream immediately
const clientLastEventId = parseInt(req.headers['last-event-id'], 10) || 0;
// Send missed events if Client was disconnected
if (clientLastEventId > 0) {
const missedEvents = eventHistory.getMissedEventsSince(clientLastEventId);
missedEvents.forEach(evt => {
res.write(`id: ${evt.id}\nevent: ${evt.type}\ndata: ${JSON.stringify(evt.payload)}\n\n`);
});
}
// Add client connection to connection pool
const clientId = Date.now();
clients.set(clientId, res);
// Periodic heartbeat (keep-alive) to prevent cellular carrier timeout
const heartbeat = setInterval(() => {
res.write(':keepalive\n\n');
}, 30000);
req.on('close', () => {
clearInterval(heartbeat);
clients.delete(clientId);
});
});
---
Case Study 2: Husomat — Resilient Peer-to-Peer Chat with WebSocket Outbox Queueing
The Challenge
Husomat requires a fast, interactive chat application allowing buyers and sellers to negotiate prices and plan meetings. In Syria’s mobile network environment, sudden connectivity dropouts (e.g., when entering an elevator or during a power rotation) occur frequently. If a user presses "Send" during a signal fade, the application must not lose the message, nor should it send duplicates when connection returns.
The Solution: Client-Side Outbox Buffering & Binary Framing
Dragonfly Soft engineered a robust WebSocket management layer with three architectural features:
- Client-Side Outbox Queue (IndexedDB/SQLite):
When a user clicks \"Send\", the message is immediately saved in a local outbox database with a status of pending. The socket manager attempts to write the message frame. Only when the server returns a confirmation receipt (ACK) carrying the same transaction ID is the local message status updated to delivered.
- Binary Frame Optimization (MessagePack):
Instead of using standard JSON strings (which carry heavy overhead due to repeated string keys), Husomat’s WebSocket connection uses MessagePack serialization. By packing message schemas into compact binary formats, Dragonfly Soft reduced websocket frame payloads by up to 60%, decreasing transmission latency and user bandwidth consumption.
- Reconnection State Machine with Jittered Backoff:
If the WebSocket closes unexpectedly, the client-side socket manager executes an exponential reconnect algorithm with randomized jitter to prevent server-side load spikes.
Here is the client-side socket manager implementation:
class ReconnectingWebSocket {
constructor(url, onMessageCallback) {
this.url = url;
this.onMessage = onMessageCallback;
this.ws = null;
this.reconnectAttempts = 0;
this.maxDelay = 30000; // Cap backoff at 30 seconds
this.outboxQueue = []; // In-memory fallback (or write to IndexedDB)
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.binaryType = 'arraybuffer'; // Setup binary frames
this.ws.onopen = () => {
this.reconnectAttempts = 0; // Reset backoff counter
this.flushOutbox();
};
this.ws.onmessage = (event) => {
// Decode binary frame (MessagePack decoding)
const data = msgpack.decode(new Uint8Array(event.data));
this.onMessage(data);
};
this.ws.onclose = () => {
this.scheduleReconnect();
};
this.ws.onerror = () => {
this.ws.close();
};
}
sendMessage(msg) {
const payload = msgpack.encode(msg);
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(payload);
} else {
// Buffer in outbox if socket is down
this.outboxQueue.push(msg);
}
}
flushOutbox() {
while (this.outboxQueue.length > 0 && this.ws.readyState === WebSocket.OPEN) {
const msg = this.outboxQueue.shift();
this.sendMessage(msg);
}
}
scheduleReconnect() {
// Exponential backoff: 2^n * 1000ms + random jitter (0-1000ms)
const delay = Math.min(
Math.pow(2, this.reconnectAttempts) * 1000 + Math.random() * 1000,
this.maxDelay
);
this.reconnectAttempts++;
setTimeout(() => this.connect(), delay);
}
}
---
Core Engineering Takeaways for Businesses
Deploying interactive applications in low-bandwidth, high-latency regions requires planning for failure. The technical lessons from Lernce and Husomat highlight best practices:
- Choose the Right Tool for the Flow: Unidirectional data (notifications, status updates) should use low-overhead SSE, while highly interactive stateful conversations require WebSockets.
- Buffer Locally, Sync Globally: Never rely on a continuous connection to execute client actions. Let the frontend store messages in a local outbox and synchronize them when the link is active.
- Minimize Payload Footprints: Minimize wire formats. Packing data into binary formats like MessagePack or Protobuf reduces data usage, helping users with expensive mobile plans.
---
Partner with Dragonfly Soft
Struggling with slow loading speeds, connection dropouts, or high infrastructure costs for your web and mobile applications? Dragonfly Soft builds highly optimized, resilient digital solutions designed to succeed under real-world network conditions.
Contact Dragonfly Soft today to discuss how our software engineering team can optimize your digital platforms.