$ cat /posts/tauri-20-websockets-real-time-communication.md
[tags]Tauri 2.0

Tauri 2.0 WebSockets Real-Time Communication

drwxr-xr-x2026-01-295 min0 views
Tauri 2.0 WebSockets Real-Time Communication

WebSocket integration in Tauri 2.0 enables real-time bidirectional communication creating persistent connections for live data updates—essential technology for building chat applications, live dashboards, notification systems, and collaborative tools maintaining instant data synchronization users expect. WebSocket implementation combines persistent connections establishing long-lived bidirectional channels, real-time messaging sending and receiving instant updates, connection management handling reconnection and error recovery, message serialization encoding data efficiently, authentication securing WebSocket connections, and event-driven architecture maintaining reactive data flow delivering comprehensive real-time communication solution. This comprehensive guide covers understanding WebSocket protocol and architecture, implementing WebSocket client in frontend, building WebSocket server in Rust backend, handling connection lifecycle and reconnection, implementing authentication and authorization, managing message queues and backpressure, troubleshooting connection issues, and real-world examples including chat application, live dashboard with updates, and notification system maintaining professional real-time communication through proper WebSocket implementation. Mastering WebSocket patterns enables building responsive applications delivering instant updates. Before proceeding, understand IPC communication, events, HTTP client, and WebRTC.

WebSocket Protocol Fundamentals

WebSocket provides full-duplex communication over single TCP connection. Understanding WebSocket protocol enables implementing efficient real-time communication maintaining persistent connections through upgrade handshake and bidirectional messaging.

javascriptwebsocket_basics.js
// WebSocket Protocol Overview

// HTTP vs WebSocket:

// HTTP:
// - Request-response model
// - Client initiates every request
// - New connection per request (or connection pool)
// - Overhead: headers sent with each request
// - Good for: REST APIs, document retrieval

// WebSocket:
// - Persistent bidirectional connection
// - Both client and server can send messages
// - Single connection for entire session
// - Low overhead after initial handshake
// - Good for: Real-time updates, chat, live data

// WebSocket handshake (HTTP upgrade):

// Client sends:
// GET /chat HTTP/1.1
// Host: example.com
// Upgrade: websocket
// Connection: Upgrade
// Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
// Sec-WebSocket-Version: 13

// Server responds:
// HTTP/1.1 101 Switching Protocols
// Upgrade: websocket
// Connection: Upgrade
// Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

// After handshake, connection stays open for bidirectional communication

// Message types:
// - Text frames: UTF-8 encoded text
// - Binary frames: Raw binary data
// - Control frames: Ping/Pong (keep-alive), Close

// WebSocket states:
const CONNECTING = 0;  // Connection not yet open
const OPEN = 1;        // Connection open, ready to communicate
const CLOSING = 2;     // Connection closing handshake started
const CLOSED = 3;      // Connection closed or couldn't open

// Basic WebSocket client (browser)
const ws = new WebSocket('ws://localhost:8080');

ws.onopen = () => {
  console.log('Connected to WebSocket server');
  ws.send('Hello Server!');
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = (event) => {
  console.log('Disconnected:', event.code, event.reason);
};

// Close codes:
// 1000: Normal closure
// 1001: Going away (page navigation)
// 1002: Protocol error
// 1003: Unsupported data
// 1006: Abnormal closure (no close frame)
// 1009: Message too big
// 1011: Server error

// Sending different data types:

// Text
ws.send('Hello');

// JSON
ws.send(JSON.stringify({ type: 'message', content: 'Hello' }));

// Binary (ArrayBuffer)
const buffer = new ArrayBuffer(8);
ws.send(buffer);

// Blob
const blob = new Blob(['Hello'], { type: 'text/plain' });
ws.send(blob);

// Checking connection state
if (ws.readyState === WebSocket.OPEN) {
  ws.send('Message');
}

// Closing connection
ws.close(1000, 'Normal closure');

// WebSocket URL schemes:
// - ws://  : Unencrypted WebSocket
// - wss:// : Encrypted WebSocket (over TLS)

// Security considerations:
// - Always use wss:// in production
// - Validate origin in server
// - Implement authentication
// - Rate limiting to prevent abuse
// - Message size limits

WebSocket Client Implementation

WebSocket client handles connection establishment and message exchange. Understanding client implementation enables building robust connections maintaining automatic reconnection through proper error handling and state management.

javascriptwebsocket_client.jsx
// WebSocket client with auto-reconnect
// src/lib/websocket.js

class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      reconnectInterval: 5000,
      maxReconnectAttempts: 10,
      heartbeatInterval: 30000,
      ...options
    };
    
    this.ws = null;
    this.reconnectAttempts = 0;
    this.messageHandlers = [];
    this.reconnectTimer = null;
    this.heartbeatTimer = null;
    this.isIntentionallyClosed = false;
  }
  
  connect() {
    try {
      this.ws = new WebSocket(this.url);
      
      this.ws.onopen = () => {
        console.log('WebSocket connected');
        this.reconnectAttempts = 0;
        this.isIntentionallyClosed = false;
        this.startHeartbeat();
        this.onConnectionOpen();
      };
      
      this.ws.onmessage = (event) => {
        this.handleMessage(event.data);
      };
      
      this.ws.onerror = (error) => {
        console.error('WebSocket error:', error);
        this.onConnectionError(error);
      };
      
      this.ws.onclose = (event) => {
        console.log('WebSocket closed:', event.code, event.reason);
        this.stopHeartbeat();
        this.onConnectionClose(event);
        
        if (!this.isIntentionallyClosed) {
          this.scheduleReconnect();
        }
      };
    } catch (error) {
      console.error('Failed to create WebSocket:', error);
      this.scheduleReconnect();
    }
  }
  
  scheduleReconnect() {
    if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
      console.error('Max reconnect attempts reached');
      return;
    }
    
    this.reconnectAttempts++;
    
    const delay = this.options.reconnectInterval * 
      Math.min(this.reconnectAttempts, 5); // Exponential backoff (capped)
    
    console.log(
      `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
    );
    
    this.reconnectTimer = setTimeout(() => {
      this.connect();
    }, delay);
  }
  
  startHeartbeat() {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.send({ type: 'ping' });
      }
    }, this.options.heartbeatInterval);
  }
  
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
  
  handleMessage(data) {
    try {
      const message = JSON.parse(data);
      
      // Handle pong (heartbeat response)
      if (message.type === 'pong') {
        return;
      }
      
      // Notify all message handlers
      this.messageHandlers.forEach(handler => {
        try {
          handler(message);
        } catch (error) {
          console.error('Error in message handler:', error);
        }
      });
    } catch (error) {
      console.error('Failed to parse message:', error);
    }
  }
  
  send(data) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      const message = typeof data === 'string' ? data : JSON.stringify(data);
      this.ws.send(message);
      return true;
    } else {
      console.warn('WebSocket not connected, message not sent');
      return false;
    }
  }
  
  close(code = 1000, reason = 'Client closing') {
    this.isIntentionallyClosed = true;
    
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
    
    this.stopHeartbeat();
    
    if (this.ws) {
      this.ws.close(code, reason);
      this.ws = null;
    }
  }
  
  // Event handlers (can be overridden)
  onConnectionOpen() {}
  onConnectionClose(event) {}
  onConnectionError(error) {}
  
  // Subscribe to messages
  onMessage(handler) {
    this.messageHandlers.push(handler);
    
    // Return unsubscribe function
    return () => {
      const index = this.messageHandlers.indexOf(handler);
      if (index > -1) {
        this.messageHandlers.splice(index, 1);
      }
    };
  }
  
  get isConnected() {
    return this.ws?.readyState === WebSocket.OPEN;
  }
}

export default WebSocketClient;

// Usage in React component
import { useState, useEffect, useRef } from 'react';
import WebSocketClient from './lib/websocket';

function ChatComponent() {
  const [messages, setMessages] = useState([]);
  const [inputText, setInputText] = useState('');
  const [isConnected, setIsConnected] = useState(false);
  const wsRef = useRef(null);
  
  useEffect(() => {
    // Initialize WebSocket
    const ws = new WebSocketClient('ws://localhost:8080/chat');
    
    ws.onConnectionOpen = () => {
      console.log('Chat connected');
      setIsConnected(true);
    };
    
    ws.onConnectionClose = () => {
      console.log('Chat disconnected');
      setIsConnected(false);
    };
    
    // Subscribe to messages
    const unsubscribe = ws.onMessage((message) => {
      if (message.type === 'chat') {
        setMessages(prev => [...prev, message]);
      }
    });
    
    ws.connect();
    wsRef.current = ws;
    
    return () => {
      unsubscribe();
      ws.close();
    };
  }, []);
  
  const sendMessage = () => {
    if (inputText.trim() && wsRef.current?.isConnected) {
      wsRef.current.send({
        type: 'chat',
        content: inputText,
        timestamp: Date.now()
      });
      setInputText('');
    }
  };
  
  return (
    <div className="chat">
      <div className="status">
        Status: {isConnected ? '🟢 Connected' : '🔴 Disconnected'}
      </div>
      
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className="message">
            <strong>{msg.username}:</strong> {msg.content}
          </div>
        ))}
      </div>
      
      <div className="input">
        <input
          value={inputText}
          onChange={(e) => setInputText(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
          placeholder="Type a message..."
          disabled={!isConnected}
        />
        <button onClick={sendMessage} disabled={!isConnected}>
          Send
        </button>
      </div>
    </div>
  );
}

export default ChatComponent;

Rust WebSocket Server

Rust WebSocket server handles multiple concurrent connections efficiently. Understanding server implementation enables building scalable backends maintaining connection state through async Rust and tokio runtime.

rustwebsocket_server.rs
// Cargo.toml dependencies
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.21"
futures-util = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4"] }

// src-tauri/src/websocket_server.rs

use tokio::net::TcpListener;
use tokio_tungstenite::accept_async;
use tokio_tungstenite::tungstenite::Message;
use futures_util::{StreamExt, SinkExt};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct ChatMessage {
    #[serde(rename = "type")]
    msg_type: String,
    username: String,
    content: String,
    timestamp: u64,
}

type Clients = Arc<Mutex<HashMap<String, tokio::sync::mpsc::UnboundedSender<Message>>>>;

pub async fn start_websocket_server(port: u16) -> Result<(), Box<dyn std::error::Error>> {
    let addr = format!("127.0.0.1:{}", port);
    let listener = TcpListener::bind(&addr).await?;
    
    println!("WebSocket server listening on: {}", addr);
    
    let clients: Clients = Arc::new(Mutex::new(HashMap::new()));
    
    while let Ok((stream, addr)) = listener.accept().await {
        println!("New connection from: {}", addr);
        
        let clients = clients.clone();
        
        tokio::spawn(async move {
            if let Err(e) = handle_connection(stream, clients).await {
                eprintln!("Error handling connection: {}", e);
            }
        });
    }
    
    Ok(())
}

async fn handle_connection(
    stream: tokio::net::TcpStream,
    clients: Clients
) -> Result<(), Box<dyn std::error::Error>> {
    // Perform WebSocket handshake
    let ws_stream = accept_async(stream).await?;
    println!("WebSocket handshake completed");
    
    let (mut ws_sender, mut ws_receiver) = ws_stream.split();
    
    // Generate unique client ID
    let client_id = uuid::Uuid::new_v4().to_string();
    
    // Create channel for this client
    let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
    
    // Add client to clients map
    clients.lock().unwrap().insert(client_id.clone(), tx.clone());
    
    // Send welcome message
    let welcome = serde_json::json!({
        "type": "connected",
        "clientId": client_id,
        "message": "Welcome to the chat!"
    });
    
    if let Ok(msg) = serde_json::to_string(&welcome) {
        let _ = tx.send(Message::Text(msg));
    }
    
    // Spawn task to send messages from channel to WebSocket
    let send_task = tokio::spawn(async move {
        while let Some(msg) = rx.recv().await {
            if ws_sender.send(msg).await.is_err() {
                break;
            }
        }
    });
    
    // Handle incoming messages
    let clients_for_recv = clients.clone();
    let client_id_for_recv = client_id.clone();
    
    let recv_task = tokio::spawn(async move {
        while let Some(msg) = ws_receiver.next().await {
            match msg {
                Ok(Message::Text(text)) => {
                    if let Err(e) = handle_message(
                        &text,
                        &client_id_for_recv,
                        &clients_for_recv
                    ).await {
                        eprintln!("Error handling message: {}", e);
                    }
                }
                Ok(Message::Binary(data)) => {
                    println!("Received binary data: {} bytes", data.len());
                }
                Ok(Message::Ping(data)) => {
                    // Pong is automatically sent by tokio-tungstenite
                    println!("Received ping");
                }
                Ok(Message::Pong(_)) => {
                    println!("Received pong");
                }
                Ok(Message::Close(frame)) => {
                    println!("Client closing: {:?}", frame);
                    break;
                }
                Err(e) => {
                    eprintln!("WebSocket error: {}", e);
                    break;
                }
                _ => {}
            }
        }
        
        // Remove client from clients map
        clients_for_recv.lock().unwrap().remove(&client_id_for_recv);
        
        // Notify others that client disconnected
        broadcast_message(
            &client_id_for_recv,
            &serde_json::json!({
                "type": "user_left",
                "clientId": client_id_for_recv
            }),
            &clients_for_recv
        ).await;
    });
    
    // Wait for tasks to complete
    tokio::select! {
        _ = send_task => {},
        _ = recv_task => {},
    }
    
    println!("Connection closed for client: {}", client_id);
    
    Ok(())
}

async fn handle_message(
    text: &str,
    sender_id: &str,
    clients: &Clients
) -> Result<(), Box<dyn std::error::Error>> {
    let message: serde_json::Value = serde_json::from_str(text)?;
    
    match message["type"].as_str() {
        Some("ping") => {
            // Respond with pong
            if let Some(tx) = clients.lock().unwrap().get(sender_id) {
                let pong = serde_json::json!({ "type": "pong" });
                let _ = tx.send(Message::Text(pong.to_string()));
            }
        }
        Some("chat") => {
            // Broadcast chat message to all clients
            broadcast_message(sender_id, &message, clients).await;
        }
        _ => {
            println!("Unknown message type: {:?}", message["type"]);
        }
    }
    
    Ok(())
}

async fn broadcast_message(
    sender_id: &str,
    message: &serde_json::Value,
    clients: &Clients
) {
    let msg_text = message.to_string();
    let clients_lock = clients.lock().unwrap();
    
    for (client_id, tx) in clients_lock.iter() {
        // Skip sender (or send to all including sender)
        if client_id != sender_id {
            let _ = tx.send(Message::Text(msg_text.clone()));
        }
    }
}

// Integrate with Tauri main
// src-tauri/src/main.rs

mod websocket_server;

#[tauri::command]
async fn start_ws_server(port: u16) -> Result<String, String> {
    tokio::spawn(async move {
        if let Err(e) = websocket_server::start_websocket_server(port).await {
            eprintln!("WebSocket server error: {}", e);
        }
    });
    
    Ok(format!("WebSocket server started on port {}", port))
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![start_ws_server])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

WebSocket Authentication

Authentication secures WebSocket connections. Understanding authentication implementation enables building secure connections maintaining user identity through token validation and session management.

rustwebsocket_auth.rs
// Authentication strategies for WebSocket

// 1. Token in URL query parameter
const token = 'your-jwt-token';
const ws = new WebSocket(`ws://localhost:8080?token=${token}`);

// 2. Token in first message
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'auth',
    token: 'your-jwt-token'
  }));
};

// 3. Token in WebSocket subprotocol header
const ws = new WebSocket('ws://localhost:8080', ['access_token', token]);

// 4. Cookie-based authentication (automatic)
const ws = new WebSocket('ws://localhost:8080');
// Cookies are automatically sent with handshake

// Rust server with token authentication
// src-tauri/src/websocket_auth.rs

use tokio_tungstenite::tungstenite::http::Request;
use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    sub: String,  // Subject (user ID)
    exp: usize,   // Expiration time
}

// Validate token from URL query
fn validate_token_from_url(request: &Request<()>) -> Result<String, String> {
    let uri = request.uri();
    let query = uri.query().ok_or("No query string")?;
    
    // Parse query string
    let params: HashMap<_, _> = url::form_urlencoded::parse(query.as_bytes())
        .into_owned()
        .collect();
    
    let token = params.get("token")
        .ok_or("Token not found in query")?;
    
    // Verify JWT token
    let secret = b"your-secret-key";
    let token_data = decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret),
        &Validation::default()
    ).map_err(|e| format!("Invalid token: {}", e))?;
    
    Ok(token_data.claims.sub)
}

// Enhanced connection handler with authentication
async fn handle_authenticated_connection(
    stream: tokio::net::TcpStream,
    request: Request<()>,
    clients: Clients
) -> Result<(), Box<dyn std::error::Error>> {
    // Validate token
    let user_id = match validate_token_from_url(&request) {
        Ok(id) => id,
        Err(e) => {
            eprintln!("Authentication failed: {}", e);
            return Ok(()); // Close connection
        }
    };
    
    println!("User {} authenticated", user_id);
    
    // Continue with WebSocket connection
    let ws_stream = accept_async(stream).await?;
    
    // ... rest of connection handling
    
    Ok(())
}

// Client with token refresh
class AuthenticatedWebSocket extends WebSocketClient {
  constructor(url, getToken) {
    super(url);
    this.getToken = getToken;
  }
  
  async connect() {
    try {
      // Get fresh token
      const token = await this.getToken();
      
      // Add token to URL
      const urlWithToken = `${this.url}?token=${token}`;
      
      // Create WebSocket with token
      this.ws = new WebSocket(urlWithToken);
      
      // Setup event handlers
      this.setupHandlers();
    } catch (error) {
      console.error('Failed to connect:', error);
      this.scheduleReconnect();
    }
  }
  
  async scheduleReconnect() {
    // Get new token before reconnecting
    await super.scheduleReconnect();
  }
}

// Usage with token refresh
const wsClient = new AuthenticatedWebSocket(
  'ws://localhost:8080',
  async () => {
    // Get token from Tauri backend
    const token = await invoke('get_auth_token');
    return token;
  }
);

wsClient.connect();

// Rate limiting on server
use std::time::{Duration, Instant};
use std::collections::VecDeque;

struct RateLimiter {
    requests: VecDeque<Instant>,
    max_requests: usize,
    window: Duration,
}

impl RateLimiter {
    fn new(max_requests: usize, window: Duration) -> Self {
        Self {
            requests: VecDeque::new(),
            max_requests,
            window,
        }
    }
    
    fn check_rate_limit(&mut self) -> bool {
        let now = Instant::now();
        
        // Remove old requests outside window
        while let Some(&oldest) = self.requests.front() {
            if now.duration_since(oldest) > self.window {
                self.requests.pop_front();
            } else {
                break;
            }
        }
        
        // Check if limit exceeded
        if self.requests.len() >= self.max_requests {
            return false;
        }
        
        // Add current request
        self.requests.push_back(now);
        true
    }
}

// Per-client rate limiting
struct Client {
    id: String,
    tx: tokio::sync::mpsc::UnboundedSender<Message>,
    rate_limiter: RateLimiter,
}

impl Client {
    fn new(id: String, tx: tokio::sync::mpsc::UnboundedSender<Message>) -> Self {
        Self {
            id,
            tx,
            rate_limiter: RateLimiter::new(10, Duration::from_secs(1)),
        }
    }
}

WebSocket Best Practices

  • Use wss:// in Production: Always encrypt WebSocket connections
  • Implement Heartbeat: Detect dead connections with ping/pong
  • Auto-Reconnect: Handle disconnections with exponential backoff
  • Authenticate Connections: Validate user identity on connect
  • Rate Limiting: Prevent abuse with message rate limits
  • Message Size Limits: Protect against large message attacks
  • Validate Messages: Parse and validate all incoming data
  • Handle Backpressure: Manage send buffer to prevent memory issues
  • Clean Connection Close: Properly close connections with reason codes
  • Monitor Connection State: Track and log connection metrics
Performance Tip: Implement heartbeat ping/pong every 30 seconds to detect dead connections early. Use exponential backoff for reconnection attempts to avoid overwhelming the server during outages!

Next Steps

Conclusion

Mastering WebSocket integration in Tauri 2.0 enables building real-time communication applications delivering instant bidirectional data exchange through persistent connections creating responsive user experiences maintaining live synchronization users expect. WebSocket implementation combines persistent connections establishing long-lived bidirectional channels with low overhead, robust client implementation handling automatic reconnection and error recovery, efficient Rust server managing multiple concurrent connections through async tokio runtime, authentication mechanisms securing connections with token validation, rate limiting preventing abuse through request throttling, and proper connection management handling lifecycle events delivering comprehensive real-time communication solution. Understanding WebSocket patterns including connection establishment with upgrade handshake, message handling with JSON serialization, heartbeat mechanism detecting dead connections, authentication strategies validating user identity, reconnection logic with exponential backoff, and best practices maintaining secure reliable connections establishes foundation for professional real-time applications delivering instant updates maintaining user engagement through proper WebSocket implementation communication features depend on!

$ cat /comments/ (0)

new_comment.sh

// Email hidden from public

>_

$ cat /comments/

// No comments found. Be the first!

[session] guest@{codershandbook}[timestamp] 2026

Navigation

Categories

Connect

Subscribe

// 2026 {Coders Handbook}. EOF.