use-realtime 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ use-realtime ๐Ÿš€
2
+ <div align="center">
3
+
4
+ https://img.shields.io/npm/v/use-realtime
5
+ https://img.shields.io/npm/l/use-realtime
6
+ https://img.shields.io/badge/TypeScript-Ready-blue
7
+
8
+ Stop rewriting WebSocket boilerplate in every React project
9
+ </div>
10
+
11
+ โœจ Why use-realtime?
12
+
13
+ Building real-time features in React usually means:
14
+
15
+ ๐Ÿ”„ Rewriting WebSocket logic again and again
16
+
17
+ ๐Ÿ”Œ Handling reconnects manually (and often incorrectly)
18
+
19
+ ๐Ÿ“จ Losing messages when the connection drops
20
+
21
+ ๐Ÿงน Managing event listeners and cleanup yourself
22
+
23
+ use-realtime solves these problems for you.
24
+
25
+
26
+
27
+ ๐Ÿš€ Features
28
+
29
+ โœ… WebSocket connection management
30
+ ๐Ÿ”„ Auto-reconnect with retry limits
31
+ ๐Ÿ“ฆ Message queue (no lost messages)
32
+ ๐Ÿ“ก Event-based subscribe/unsubscribe API
33
+ ๐Ÿ’“ Optional heartbeat (keep connection alive)
34
+ ๐Ÿงน Automatic cleanup on unmount
35
+ ๐Ÿง  React-safe & TypeScript-first
36
+ ๐ŸŒ Works with any WebSocket backend
37
+
38
+
39
+ ๐Ÿ“ฆ Installation
40
+
41
+ npm install use-realtime
42
+ # or
43
+ yarn add use-realtime
44
+ # or
45
+ pnpm add use-realtime
46
+
47
+ โšก Quick Start
48
+ import { useRealtime } from 'use-realtime';
49
+
50
+ function Chat() {
51
+ const { emit, subscribe, isConnected } = useRealtime({
52
+ url: 'wss://example.com/ws',
53
+ autoReconnect: true,
54
+ });
55
+
56
+ // Subscribe to events
57
+ useEffect(() => {
58
+ const unsubscribe = subscribe('chat', (message) => {
59
+ console.log('New message:', message);
60
+ });
61
+ return unsubscribe;
62
+ }, [subscribe]);
63
+
64
+ return (
65
+ <button onClick={() => emit('chat', { text: 'Hello!' })}>
66
+ Send Message
67
+ </button>
68
+ );
69
+ }
70
+
71
+ ๐Ÿง  Core API
72
+ useRealtime(options)
73
+
74
+ const realtime = useRealtime({
75
+ url: string, // WebSocket server URL (required)
76
+ autoConnect?: boolean, // Auto connect on mount (default: true)
77
+ autoReconnect?: boolean, // Auto reconnect on disconnect (default: true)
78
+ reconnectAttempts?: number, // Max reconnect attempts (default: 10)
79
+ reconnectInterval?: number, // Reconnect delay in ms (default: 3000)
80
+ heartbeat?: { // Heartbeat configuration
81
+ enabled?: boolean, // Enable heartbeat (default: false)
82
+ interval?: number, // Heartbeat interval in ms (default: 30000)
83
+ message?: any, // Heartbeat message (default: 'ping')
84
+ },
85
+ messageQueue?: { // Offline message queue
86
+ enabled?: boolean, // Enable message queue (default: true)
87
+ maxSize?: number, // Max queue size (default: 100)
88
+ },
89
+ debug?: boolean, // Enable debug logging (default: false)
90
+ });
91
+
92
+
93
+ ๐Ÿ“ก Sending Messages
94
+ emit(event, data)
95
+
96
+ Send a named event. If the socket is not connected, the message is queued automatically.
97
+ emit('notification', { title: 'New order' });
98
+
99
+
100
+ send(data)
101
+
102
+ Send a raw message (no event name).
103
+
104
+ send({ ping: true });
105
+
106
+
107
+ ๐Ÿ”” Subscribing to Events
108
+ subscribe(event, callback)
109
+ jsx
110
+
111
+ const unsubscribe = subscribe('order:update', (data) => {
112
+ console.log(data);
113
+ });
114
+
115
+ // Always unsubscribe on cleanup
116
+ useEffect(() => {
117
+ const unsubscribe = subscribe('event', handler);
118
+ return unsubscribe;
119
+ }, [subscribe]);
120
+
121
+ ๐Ÿ”Œ Connection State
122
+ jsx
123
+
124
+ const {
125
+ isConnected, // boolean - Connection is active
126
+ isConnecting, // boolean - Connection in progress
127
+ isReconnecting, // boolean - Reconnection in progress
128
+ connection, // Full connection object
129
+ } = useRealtime(...);
130
+
131
+ connection includes:
132
+
133
+ connected - Connection is active
134
+
135
+ connecting - Connection in progress
136
+
137
+ reconnecting - Reconnection in progress
138
+
139
+ error - Last error that occurred
140
+
141
+ lastMessageAt - Timestamp of last message
142
+
143
+ connectionId - Unique connection identifier
144
+
145
+ ๐Ÿ”„ Manual Control
146
+ jsx
147
+
148
+ const { connect, disconnect, reconnect } = useRealtime(...);
149
+
150
+ connect(); // Manually connect
151
+ disconnect(); // Close connection safely
152
+ reconnect(); // Force reconnect
153
+
154
+ ๐Ÿ“ฆ Message Queue Utilities
155
+ jsx
156
+
157
+ const { getQueueSize, flushQueue } = useRealtime(...);
158
+
159
+ getQueueSize(); // Number of queued messages
160
+ flushQueue(); // Clear queued messages
161
+
162
+ ๐Ÿงน Cleanup & Safety
163
+
164
+ โœ… Automatically closes WebSocket on unmount
165
+ โœ… Prevents duplicate connections
166
+ โœ… Prevents reconnect loops
167
+ โœ… Prevents memory leaks
168
+
169
+ Safe to use in:
170
+
171
+ React
172
+
173
+ Next.js
174
+
175
+ Vite
176
+
177
+ CRA
178
+
179
+ Any React 16.8+ environment
180
+
181
+ ๐Ÿงฉ TypeScript Support
182
+
183
+ Fully typed and tree-shakable:
184
+ typescript
185
+
186
+ import { useRealtime } from 'use-realtime';
187
+ import type {
188
+ RealtimeOptions,
189
+ ConnectionState,
190
+ UseRealtimeReturn
191
+ } from 'use-realtime';
192
+
193
+ ๐Ÿง‘โ€๐Ÿ’ป When should I use this?
194
+
195
+ Perfect for:
196
+
197
+ ๐Ÿ’ฌ Chat applications
198
+
199
+ ๐Ÿ”” Live notifications
200
+
201
+ ๐Ÿ“Š Dashboards
202
+
203
+ ๐ŸŽฎ Multiplayer features
204
+
205
+ ๐Ÿ“ก Real-time feeds
206
+
207
+ ๐Ÿข Admin panels
208
+
209
+ ๐Ÿ”ง IoT dashboards
210
+
211
+ ๐Ÿ“ˆ Live analytics
212
+
213
+ ๐Ÿ“„ License
214
+
215
+ MIT โ€” free for personal and commercial use. See LICENSE for details.
216
+ <div align="center">
217
+
218
+
219
+
220
+
221
+
222
+
@@ -0,0 +1,2 @@
1
+ export { useRealtime } from './useRealtime';
2
+ export type { RealtimeOptions, ConnectionState, UseRealtimeReturn } from './useRealtime';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useRealtime = void 0;
4
+ //indexed.ts
5
+ var useRealtime_1 = require("./useRealtime");
6
+ Object.defineProperty(exports, "useRealtime", { enumerable: true, get: function () { return useRealtime_1.useRealtime; } });
@@ -0,0 +1,42 @@
1
+ export interface RealtimeOptions {
2
+ url: string;
3
+ autoConnect?: boolean;
4
+ autoReconnect?: boolean;
5
+ reconnectAttempts?: number;
6
+ reconnectInterval?: number;
7
+ heartbeat?: {
8
+ enabled?: boolean;
9
+ interval?: number;
10
+ message?: any;
11
+ };
12
+ messageQueue?: {
13
+ enabled?: boolean;
14
+ maxSize?: number;
15
+ };
16
+ debug?: boolean;
17
+ }
18
+ export interface ConnectionState {
19
+ connected: boolean;
20
+ connecting: boolean;
21
+ reconnecting: boolean;
22
+ error: Error | null;
23
+ lastMessageAt: Date | null;
24
+ connectionId: string | null;
25
+ }
26
+ export interface UseRealtimeReturn {
27
+ connect: () => void;
28
+ disconnect: () => void;
29
+ reconnect: () => void;
30
+ subscribe: <T = any>(event: string, callback: (data: T) => void) => () => void;
31
+ unsubscribe: <T = any>(event: string, callback: (data: T) => void) => void;
32
+ emit: <T = any>(event: string, data: T) => void;
33
+ send: <T = any>(data: T) => void;
34
+ connection: ConnectionState;
35
+ isConnected: boolean;
36
+ isConnecting: boolean;
37
+ isReconnecting: boolean;
38
+ getQueueSize: () => number;
39
+ flushQueue: () => void;
40
+ clearSubscriptions: () => void;
41
+ }
42
+ export declare function useRealtime(options: RealtimeOptions): UseRealtimeReturn;
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
+ if (ar || !(i in from)) {
16
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
17
+ ar[i] = from[i];
18
+ }
19
+ }
20
+ return to.concat(ar || Array.prototype.slice.call(from));
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.useRealtime = useRealtime;
24
+ var react_1 = require("react");
25
+ function useRealtime(options) {
26
+ var url = options.url, _a = options.autoConnect, autoConnect = _a === void 0 ? true : _a, _b = options.autoReconnect, autoReconnect = _b === void 0 ? true : _b, _c = options.reconnectAttempts, reconnectAttempts = _c === void 0 ? 10 : _c, _d = options.reconnectInterval, reconnectInterval = _d === void 0 ? 3000 : _d, _e = options.heartbeat, heartbeat = _e === void 0 ? { enabled: false, interval: 30000, message: 'ping' } : _e, _f = options.messageQueue, messageQueue = _f === void 0 ? { enabled: true, maxSize: 100 } : _f, _g = options.debug, debug = _g === void 0 ? false : _g;
27
+ // Fixed: Separate types for setTimeout and setInterval
28
+ var wsRef = (0, react_1.useRef)(null);
29
+ var reconnectTimeoutRef = (0, react_1.useRef)(null);
30
+ var heartbeatIntervalRef = (0, react_1.useRef)(null);
31
+ var reconnectAttemptsRef = (0, react_1.useRef)(0);
32
+ var eventsRef = (0, react_1.useRef)({});
33
+ var messageQueueRef = (0, react_1.useRef)([]);
34
+ var _h = (0, react_1.useState)({
35
+ connected: false,
36
+ connecting: false,
37
+ reconnecting: false,
38
+ error: null,
39
+ lastMessageAt: null,
40
+ connectionId: null,
41
+ }), connection = _h[0], setConnection = _h[1];
42
+ var log = (0, react_1.useCallback)(function () {
43
+ var args = [];
44
+ for (var _i = 0; _i < arguments.length; _i++) {
45
+ args[_i] = arguments[_i];
46
+ }
47
+ if (debug)
48
+ console.log.apply(console, __spreadArray(['[useRealtime]'], args, false));
49
+ }, [debug]);
50
+ var connect = (0, react_1.useCallback)(function () {
51
+ if (wsRef.current &&
52
+ (wsRef.current.readyState === WebSocket.OPEN ||
53
+ wsRef.current.readyState === WebSocket.CONNECTING)) {
54
+ log('Connection already open or connecting');
55
+ return;
56
+ }
57
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { connecting: true, reconnecting: false, error: null })); });
58
+ try {
59
+ var ws_1 = new WebSocket(url);
60
+ wsRef.current = ws_1;
61
+ ws_1.onopen = function () {
62
+ log('Connected to', url);
63
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { connected: true, connecting: false, reconnecting: false, error: null, connectionId: Math.random().toString(36).substring(7) })); });
64
+ reconnectAttemptsRef.current = 0;
65
+ if (heartbeat.enabled) {
66
+ heartbeatIntervalRef.current = setInterval(function () {
67
+ if (ws_1.readyState === WebSocket.OPEN) {
68
+ ws_1.send(JSON.stringify(heartbeat.message));
69
+ log('Heartbeat sent');
70
+ }
71
+ }, heartbeat.interval);
72
+ }
73
+ if (messageQueue.enabled && messageQueueRef.current.length > 0) {
74
+ log('Flushing queued messages:', messageQueueRef.current.length);
75
+ var queueCopy = __spreadArray([], messageQueueRef.current, true);
76
+ messageQueueRef.current = [];
77
+ queueCopy.forEach(function (msg) {
78
+ if (ws_1.readyState === WebSocket.OPEN) {
79
+ ws_1.send(JSON.stringify(msg));
80
+ }
81
+ else {
82
+ // Re-queue if not connected
83
+ messageQueueRef.current.push(msg);
84
+ }
85
+ });
86
+ }
87
+ };
88
+ ws_1.onclose = function (event) {
89
+ log("Connection closed: Code ".concat(event.code, ", Reason: ").concat(event.reason || 'No reason provided'));
90
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { connected: false, connecting: false, lastMessageAt: prev.lastMessageAt })); });
91
+ if (heartbeatIntervalRef.current) {
92
+ clearInterval(heartbeatIntervalRef.current);
93
+ heartbeatIntervalRef.current = null;
94
+ }
95
+ if (autoReconnect && reconnectAttemptsRef.current < reconnectAttempts) {
96
+ reconnectAttemptsRef.current++;
97
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { reconnecting: true })); });
98
+ reconnectTimeoutRef.current = setTimeout(function () {
99
+ log("Reconnecting (attempt ".concat(reconnectAttemptsRef.current, "/").concat(reconnectAttempts, ")"));
100
+ connect();
101
+ }, reconnectInterval);
102
+ }
103
+ else {
104
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { reconnecting: false })); });
105
+ }
106
+ };
107
+ ws_1.onerror = function (error) {
108
+ log('WebSocket error:', error);
109
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { error: new Error('WebSocket connection error') })); });
110
+ };
111
+ ws_1.onmessage = function (event) {
112
+ try {
113
+ var data_1 = JSON.parse(event.data);
114
+ var timestamp_1 = new Date();
115
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { lastMessageAt: timestamp_1 })); });
116
+ // Handle specific events
117
+ if (data_1.event && eventsRef.current[data_1.event]) {
118
+ eventsRef.current[data_1.event].forEach(function (callback) { return callback(data_1.data); });
119
+ }
120
+ // Handle generic messages
121
+ if (eventsRef.current['message']) {
122
+ eventsRef.current['message'].forEach(function (callback) { return callback(data_1); });
123
+ }
124
+ // Handle all messages (wildcard)
125
+ if (eventsRef.current['*']) {
126
+ eventsRef.current['*'].forEach(function (callback) { return callback({ event: data_1.event, data: data_1.data }); });
127
+ }
128
+ }
129
+ catch (error) {
130
+ log('Failed to parse message:', event.data, error);
131
+ }
132
+ };
133
+ }
134
+ catch (error) {
135
+ log('Failed to create WebSocket:', error);
136
+ setConnection(function (prev) { return (__assign(__assign({}, prev), { connecting: false, error: error })); });
137
+ }
138
+ }, [url, autoReconnect, reconnectAttempts, reconnectInterval, heartbeat, messageQueue.enabled, log]);
139
+ var disconnect = (0, react_1.useCallback)(function () {
140
+ log('Disconnecting...');
141
+ // Clear timeouts
142
+ if (reconnectTimeoutRef.current) {
143
+ clearTimeout(reconnectTimeoutRef.current);
144
+ reconnectTimeoutRef.current = null;
145
+ }
146
+ if (heartbeatIntervalRef.current) {
147
+ clearInterval(heartbeatIntervalRef.current);
148
+ heartbeatIntervalRef.current = null;
149
+ }
150
+ // Close WebSocket
151
+ if (wsRef.current) {
152
+ wsRef.current.close(1000, 'Manual disconnect');
153
+ wsRef.current = null;
154
+ }
155
+ setConnection({
156
+ connected: false,
157
+ connecting: false,
158
+ reconnecting: false,
159
+ error: null,
160
+ lastMessageAt: null,
161
+ connectionId: null,
162
+ });
163
+ }, [log]);
164
+ var reconnect = (0, react_1.useCallback)(function () {
165
+ log('Manual reconnect triggered');
166
+ disconnect();
167
+ reconnectAttemptsRef.current = 0;
168
+ setTimeout(function () { return connect(); }, 100);
169
+ }, [connect, disconnect, log]);
170
+ var subscribe = (0, react_1.useCallback)(function (event, callback) {
171
+ if (!eventsRef.current[event]) {
172
+ eventsRef.current[event] = [];
173
+ }
174
+ eventsRef.current[event].push(callback);
175
+ log("Subscribed to event: ".concat(event));
176
+ // Return unsubscribe function
177
+ return function () {
178
+ if (eventsRef.current[event]) {
179
+ eventsRef.current[event] = eventsRef.current[event].filter(function (cb) { return cb !== callback; });
180
+ log("Unsubscribed from event: ".concat(event));
181
+ }
182
+ };
183
+ }, [log]);
184
+ var unsubscribe = (0, react_1.useCallback)(function (event, callback) {
185
+ if (eventsRef.current[event]) {
186
+ eventsRef.current[event] = eventsRef.current[event].filter(function (cb) { return cb !== callback; });
187
+ log("Unsubscribed from event: ".concat(event));
188
+ }
189
+ }, [log]);
190
+ var emit = (0, react_1.useCallback)(function (event, data) {
191
+ var _a;
192
+ var message = {
193
+ event: event,
194
+ data: data,
195
+ timestamp: new Date().toISOString(),
196
+ id: Math.random().toString(36).substring(7)
197
+ };
198
+ if (((_a = wsRef.current) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN) {
199
+ wsRef.current.send(JSON.stringify(message));
200
+ log("Event emitted: ".concat(event), data);
201
+ }
202
+ else if (messageQueue.enabled) {
203
+ if (messageQueueRef.current.length < (messageQueue.maxSize || 100)) {
204
+ messageQueueRef.current.push(message);
205
+ log("Message queued (".concat(messageQueueRef.current.length, "/").concat(messageQueue.maxSize, "): ").concat(event));
206
+ }
207
+ else {
208
+ log('Message queue full, dropping message:', event);
209
+ }
210
+ }
211
+ else {
212
+ log('WebSocket not connected and message queue disabled:', event);
213
+ }
214
+ }, [messageQueue.enabled, messageQueue.maxSize, log]);
215
+ var send = (0, react_1.useCallback)(function (data) {
216
+ emit('message', data);
217
+ }, [emit]);
218
+ var getQueueSize = (0, react_1.useCallback)(function () {
219
+ return messageQueueRef.current.length;
220
+ }, []);
221
+ var flushQueue = (0, react_1.useCallback)(function () {
222
+ var count = messageQueueRef.current.length;
223
+ messageQueueRef.current = [];
224
+ log("Message queue flushed (".concat(count, " messages removed)"));
225
+ }, [log]);
226
+ var clearSubscriptions = (0, react_1.useCallback)(function () {
227
+ var eventCount = Object.keys(eventsRef.current).length;
228
+ eventsRef.current = {};
229
+ log("All subscriptions cleared (".concat(eventCount, " events)"));
230
+ }, [log]);
231
+ // Auto-connect on mount
232
+ (0, react_1.useEffect)(function () {
233
+ if (autoConnect) {
234
+ log('Auto-connecting...');
235
+ connect();
236
+ }
237
+ return function () {
238
+ log('Cleaning up...');
239
+ disconnect();
240
+ };
241
+ }, [connect, disconnect, autoConnect, log]);
242
+ return {
243
+ connect: connect,
244
+ disconnect: disconnect,
245
+ reconnect: reconnect,
246
+ subscribe: subscribe,
247
+ unsubscribe: unsubscribe,
248
+ emit: emit,
249
+ send: send,
250
+ connection: connection,
251
+ isConnected: connection.connected,
252
+ isConnecting: connection.connecting,
253
+ isReconnecting: connection.reconnecting,
254
+ getQueueSize: getQueueSize,
255
+ flushQueue: flushQueue,
256
+ clearSubscriptions: clearSubscriptions,
257
+ };
258
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "use-realtime",
3
+ "version": "1.0.0",
4
+ "description": "A powerful React hook for real-time WebSocket connections with auto-reconnect, message queuing, and event management",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepublishOnly": "npm run build",
15
+ "prepare": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "react",
19
+ "websocket",
20
+ "realtime",
21
+ "hook",
22
+ "typescript",
23
+ "socket",
24
+ "events",
25
+ "connection",
26
+ "auto-reconnect"
27
+ ],
28
+ "author": "yared abebe",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/yaredabebe/use-realtime.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/yaredabebe/use-realtime/issues"
36
+ },
37
+ "homepage": "https://github.com/yaredabebe/use-realtime#readme",
38
+ "peerDependencies": {
39
+ "react": ">=16.8.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/react": "^18.2.0",
43
+ "typescript": "^5.1.0"
44
+ }
45
+ }