UNPKG

socket.io-react-hooks-advanced

Version:

A modular and extensible React + Socket.IO hook library designed for real-world applications. Supports namespaced sockets, reconnection strategies, offline queues, latency monitoring, middleware, encryption, and more.

442 lines (335 loc) 12 kB
# socket.io-react-hooks-advanced A modular and extensible React + Socket.IO hook library designed for real-world applications. Supports namespaced sockets, reconnection strategies, offline queues, latency monitoring, middleware, encryption, and more. --- ## ✨ Features - **Auto-Reconnect** with exponential backoff, customizable retry strategy - **Token Injection** with support for refresh on unauthorized (401 responses) - **Latency Monitoring**: track round-trip latency in real time (ping-pong events) - **Offline Background Queue**: queue `emit` calls while offline, flush after reconnect - **LocalStorage Queue Persistence**: retain queue across page reloads, with TTL expiration - **Middleware** for intercepting and transforming outgoing/incoming socket events - **AES Encryption** of payloads (optional, using `crypto-js`) - **Namespaced Socket Providers** with `createNamespacedSocket()` for modular isolation - **Fully Typed Hooks**: `useSocketContext`, `useEvent`, `useLatency`, etc. - **Timeout-based ack() emits**: emit events with callback, with timeout fallback behavior - **Event Subscription Helpers**: auto cleanup with `useEvent()` hook - **Queue Overflow Handling**: detect and manage maximum queue size violations - **Scoped Event Middleware**: different middlewares per namespace - **Integrated Debug Logging**: toggle log output for development --- ## 📦 Installation ```bash npm install socket.io-react-hooks-advanced ``` Also install peer dependencies: ```bash npm install react socket.io-client crypto-js ``` --- ## 🧠 Basic Usage ```tsx import React, {useEffect, useState} from "react"; import { SocketProvider, useSocketContext, useEvent } from "socket.io-react-hooks-advanced"; function App() { return ( <SocketProvider url="http://localhost:3000" getToken={() => localStorage.getItem("token") || ""} useEncryption={true} encryptionKey="my-secret-key" debug={true} extraHeaders={{platform: "web", appId: "myAppId123"}} // x-platform, x-appId, other... > <Main/> </SocketProvider> ); } function Main() { const {connected, emit, queue, latency} = useSocketContext(); const [message, setMessage] = useState(""); const [response, setResponse] = useState<string | null>(null); const [received, setReceived] = useState<string[]>([]); useEvent("chat:receive", (msg) => { setReceived((prev) => [...prev, JSON.stringify(msg)]); }); const sendMessage = () => { emit( "chat:send", {text: message}, { ack: (res) => setResponse(JSON.stringify(res)), timeout: 3000, encrypt: true } ); setMessage(""); }; return ( <div> <h2>Socket.IO React Example</h2> <p>Status: {connected ? "🟢 Connected" : "🔴 Disconnected"}</p> <p>Ping: {latency ?? "..."} ms</p> <input type="text" value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Type your message" /> <button onClick={sendMessage}>Send</button> {response && <pre>Response: {response}</pre>} {received.length > 0 && ( <div> <h4>Received Messages</h4> <ul> {received.map((msg, i) => ( <li key={i}>{msg}</li> ))} </ul> </div> )} {queue.length > 0 && ( <div> <h4>Offline Queue</h4> <ul> {queue.map((q, i) => ( <li key={i}>{q.event} - {JSON.stringify(q.data)}</li> ))} </ul> </div> )} </div> ); } ``` --- ## 🧪 Namespaced Sockets ```tsx // chatSocket.ts import {createNamespacedSocket} from "socket.io-react-hooks-advanced"; export const chatSocket = createNamespacedSocket(); // notifSocket.ts import {createNamespacedSocket} from "socket.io-react-hooks-advanced"; export const notifSocket = createNamespacedSocket(); // App.tsx import {chatSocket} from "./chatSocket"; import {notifSocket} from "./notifSocket"; function App() { return ( <notifSocket.Provider url="http://localhost:3000" namespace="/notifications"> <chatSocket.Provider url="http://localhost:3000" namespace="/chat"> <Main/> </chatSocket.Provider> </notifSocket.Provider> ); } // Main.tsx import React from "react"; import {Chat} from "./Chat"; import {Notifications} from "./Notifications"; export function Main() { return ( <> <Chat/> <Notifications/> </> ); } // Chat.tsx import React, {useState, useEffect} from "react"; import {chatSocket} from "./chatSocket"; export function Chat() { const {emit, connected} = chatSocket.useSocket(); const [msg, setMsg] = useState(""); const [log, setLog] = useState<string[]>([]); chatSocket.useEvent("chat:message", (data) => { setLog((prev) => [...prev, JSON.stringify(data)]); }); return ( <div> <h3>Chat Room</h3> <p>Status: {connected ? "🟢" : "🔴"}</p> <input value={msg} onChange={(e) => setMsg(e.target.value)} placeholder="Type" /> <button onClick={() => emit("chat:message", {msg})}>Send</button> <ul> {log.map((l, i) => ( <li key={i}>{l}</li> ))} </ul> </div> ); } // Notifications.tsx import React, {useEffect, useState} from "react"; import {notifSocket} from "./notifSocket"; export function Notifications() { const {connected} = notifSocket.useSocket(); const [alerts, setAlerts] = useState<string[]>([]); notifSocket.useEvent("notif:alert", (data) => { setAlerts((prev) => [...prev, JSON.stringify(data)]); }); return ( <div> <h3>Notifications</h3> <p>Status: {connected ? "🟢" : "🔴"}</p> <ul> {alerts.map((alert, i) => ( <li key={i}>{alert}</li> ))} </ul> </div> ); } ``` You can create and use multiple isolated namespaces simultaneously. --- ## 📊 Latency Monitoring ```tsx import {useLatency} from "socket.io-react-hooks-advanced"; const PingIndicator = () => { const latency = useLatency(); return <div>Ping: {latency ?? "..."} ms</div>; }; ``` Latency is measured using `ping`/`pong` roundtrip at regular intervals. --- ## 🔐 Encrypted Emit ```tsx emit("secure-data", {value: "secret"}, {encrypt: true}); ``` Enable encryption in provider: ```tsx <SocketProvider url="http://localhost:3000" encryptionKey="my-secret-key" useEncryption={true} > <App/> </SocketProvider> ``` > Server must also decrypt the payload using the same AES key. --- ## ⚙️ Provider Options | Prop | Type | Description | |-------------------|-----------------------------------|--------------------------------------| | `url` | `string` | Required socket server URL | | `namespace` | `string?` | Socket.IO namespace | | `getToken` | `() => string \| Promise<string>` | JWT token retriever | | `onUnauthorized` | `() => string \| Promise<string>` | Token refresh when unauthorized | | `maxRetries` | `number` | Max reconnection attempts | | `initialDelayMs` | `number` | Backoff initial delay | | `backoffFactor` | `number` | Backoff multiplier | | `persistQueue` | `boolean` | Save queue in `localStorage` | | `queueKey` | `string` | Storage key name | | `queueTTL` | `number` | Expiry in ms for stored queue | | `maxQueueSize` | `number` | Maximum offline queue size | | `onQueueOverflow` | `(event) => void` | Called when queue exceeds max size | | `useEncryption` | `boolean` | Enable payload encryption | | `encryptionKey` | `string` | AES key for encryption | | `debug` | `boolean` | Log connection/retry/status info | | `extraHeaders` | `record` | add custom extra header when connect | --- ## 🧰 Hooks API ### `useSocketContext()` Returns: - `socket`: active socket instance - `connected`: current connection status - `emit(event, data, options)`: emit with ack/encryption - `queue`: access queued events - `latency`: current latency in ms ### `useEvent(event, handler)` Subscribes to a specific event and removes it automatically on unmount. ### `useLatency()` Returns current `ping`/`pong` latency (updated every 5s). ### `useConnectionStatus()` Returns the current socket lifecycle status, one of: - `"connecting"` - `"connected"` - `"reconnecting"` - `"disconnected"` - `"unauthorized"` - `"failed"` Example usage: ```tsx import {useConnectionStatus} from "socket.io-react-hooks-advanced"; function StatusTag() { const status = useConnectionStatus(); return <p>Status: <strong>{status}</strong></p>; } ``` --- ## 🧱 Example: Multi-Namespace Setup ```tsx const notifSocket = createNamespacedSocket(); const gameSocket = createNamespacedSocket(); <notifSocket.Provider url="..." namespace="/notif"> <gameSocket.Provider url="..." namespace="/game"> <App/> </gameSocket.Provider> </notifSocket.Provider> ``` Each namespaced socket provider is fully isolated. --- ## 📦 Offline Queue Handling - Emit calls made when disconnected are pushed to a queue - On reconnect, queued emits are flushed to server - Optionally persisted in `localStorage` with TTL - Overflow handler called if queue exceeds `maxQueueSize` Queue is type-safe and timestamped for tracing/debugging. --- ## 🧩 Middleware Support Register middleware to intercept emit or received events: ```ts registerMiddleware({ onOutgoing: (event, data) => encrypt(data), onIncoming: (event, data) => decrypt(data), }); ``` Scoped middleware can also be registered per namespace. --- ## 🔐 AES Encryption AES-256 encryption is implemented using `crypto-js`. Encryption must be enabled with a shared key: ```tsx <SocketProvider url="..." encryptionKey="shared-secret-key" useEncryption={true} > <App/> </SocketProvider> ``` ### How it works: - `emit(..., { encrypt: true })` wraps payload - Middleware encrypts using AES - Server must decrypt with same key (Node.js: `crypto`) --- ## 🌐 TypeScript Support All types are exposed and extensible: - `SocketContextType` - `QueuedEmit` - `SocketProviderProps` - `NamespacedSocket` - `SocketEmitOptions` Works out of the box with strict TypeScript settings. --- ## 🧪 Example: Emit with ACK and Timeout ```tsx emit("joinRoom", {roomId: 123}, { ack: (response) => console.log("joined", response), timeout: 3000, }); ``` --- ## 📜 License MIT © [Jingx](https://github.com/jingx157/socket.io-react-hooks-advanced) --- Feel free to contribute, submit issues, or request enhancements! > Built with ❤️ for scalable, socket-driven apps.