honeydrop 1.0.1 → 1.0.3

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 CHANGED
@@ -1,298 +1,248 @@
1
+ <div align="center">
2
+
1
3
  # Honeydrop 🍯
2
4
 
3
- [![npm version](https://img.shields.io/npm/v/honeydrop.svg)](https://www.npmjs.com/package/honeydrop)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
5
+ ### The Developer-Friendly Socket.IO Helper
6
+
7
+ [![npm version](https://img.shields.io/npm/v/honeydrop.svg?style=flat-square)](https://www.npmjs.com/package/honeydrop)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
9
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg?style=flat-square)](https://nodejs.org)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
11
+
12
+ **Simplify your real-time applications.**<br>
13
+ Effortless connection management, automatic reconnection, and powerful utilities for Socket.IO.
14
+
15
+ [Installation](#-installation) • [Quick Start](#-quick-start) • [Features](#-features) • [API Reference](#-api-reference) • [Contributing](#-contributing)
16
+
17
+ </div>
18
+
19
+ ---
6
20
 
7
- A lightweight, developer-friendly helper library for Socket.IO applications. Simplifies real-time communication in web apps with easy connection management, automatic reconnection, and powerful utilities.
21
+ ## 💡 Motivation
8
22
 
9
- ## 💡 Why Honeydrop?
23
+ Building robust Socket.IO applications often involves repetitive boilerplate: managing room joins, handling reconnections gracefully, queuing events when the network drops, and implementing retry logic.
10
24
 
11
- I was working on a real-time project using Socket.IO myself and realized how much repetitive work was involved — managing rooms, handling reconnections, queuing events while offline, and retrying failed emits. That's why I decided to create **Honeydrop.js**, a lightweight, developer-friendly library that simplifies real-time communication and makes building robust Socket.IO apps faster and easier.
25
+ **Honeydrop** handles this complexity for you. It wraps the standard `socket.io-client` with a powerful, developer-friendly API that makes your real-time code cleaner, more reliable, and easier to maintain.
12
26
 
13
27
  ## ✨ Features
14
28
 
15
- - 🔌 **Easy Connection Management** - Simple API to connect, disconnect, and manage Socket.IO connections
16
- - 🎯 **Event Handling with Auto-Cleanup** - Register events that are automatically cleaned up on disconnect
17
- - 🔄 **Automatic Reconnection** - Configurable retry strategies (linear/exponential backoff)
18
- - 📛 **Namespaced Events** - Organize events into logical channels
19
- - **Utility Functions** - Multi-emit, multi-listen, throttle, debounce, and more
20
- - 🐛 **Debug Logging** - Pretty-printed development logs
21
- - 📦 **Lightweight** - No bundled dependencies, Socket.IO as peer dependency
22
- - 🎨 **TypeScript Support** - Full type definitions included
29
+ - **🔌 effortless Connection Management**: Simple API to connect, disconnect, and monitor health.
30
+ - **🎯 Smart Event Handling**: Auto-cleanup of listeners on disconnect.
31
+ - **🔄 Robust Reconnection**: Configurable strategies (linear/exponential backoff) with hooks.
32
+ - **📛 Namespacing Made Easy**: Organize your events into logical channels (`chat:message`, `game:score`).
33
+ - **⚡ Powerful Utilities**: Multi-emit, multi-listen, throttle, debounce, and specific event waiting.
34
+ - **📦 Offline Queue**: Automatically queue events when disconnected and flush them on reconnect.
35
+ - **🐛 Dev-Friendly**: Built-in debug logging and full TypeScript support.
23
36
 
24
37
  ## 📦 Installation
25
38
 
26
- ```bash
27
- npm install honeydrop socket.io-client
28
- ```
39
+ > [!IMPORTANT]
40
+ > Run the following command to install the package:
41
+ > ```bash
42
+ > npm install honeydrop
43
+ > ```
44
+ > *Note: `socket.io-client` is a peer dependency and will be installed if not present.*
29
45
 
30
46
  ## 🚀 Quick Start
31
47
 
32
- ```javascript
48
+ Here's how easy it is to get started:
49
+
50
+ ```typescript
33
51
  import Honeydrop from 'honeydrop';
34
52
 
35
- // Create a client with auto-connect
53
+ // 1. Initialize the client
36
54
  const client = new Honeydrop('http://localhost:3000', {
37
- debug: true,
38
- reconnection: {
39
- enabled: true,
40
- maxAttempts: 5,
41
- strategy: 'exponential'
42
- }
55
+ autoConnect: true,
56
+ debug: true
43
57
  });
44
58
 
45
- // Listen for events
59
+ // 2. Listen for events
46
60
  client.on('message', (data) => {
47
61
  console.log('Received:', data);
48
62
  });
49
63
 
50
- // Emit events
64
+ // 3. Emit events (even if currently disconnected!)
51
65
  client.emit('chat:message', { text: 'Hello, World!' });
52
66
 
53
- // Disconnect (all listeners automatically cleaned up)
54
- client.disconnect();
67
+ // 4. Cleanup when done
68
+ // client.disconnect();
55
69
  ```
56
70
 
57
71
  ## 📖 API Reference
58
72
 
59
- ### Constructor
60
-
61
- ```javascript
62
- new Honeydrop(url, options?)
63
- ```
64
-
65
- | Option | Type | Default | Description |
66
- |--------|------|---------|-------------|
67
- | `debug` | `boolean` | `false` | Enable debug logging |
68
- | `logLevel` | `'debug' \| 'info' \| 'warn' \| 'error'` | `'info'` | Minimum log level |
69
- | `autoConnect` | `boolean` | `true` | Auto-connect on instantiation |
70
- | `namespaceDelimiter` | `':' \| '/' \| '.' \| '_'` | `':'` | Delimiter for namespaced events |
71
- | `reconnection` | `ReconnectionOptions` | — | Reconnection configuration |
72
- | `offlineQueue` | `OfflineQueueOptions` | `{ enabled: true }` | Offline message queue configuration |
73
- | `connectionMonitor` | `ConnectionMonitorOptions` | `{ enabled: true }` | Connection health monitoring |
74
- | `roomManager` | `RoomManagerOptions` | — | Room join/leave event names |
75
- | `socketOptions` | `object` | — | Socket.IO client options |
76
-
77
- #### Reconnection Options
73
+ ### Client Configuration
78
74
 
79
75
  ```javascript
80
- {
81
- enabled: true, // Enable auto-reconnection
82
- maxAttempts: 10, // Maximum retry attempts
83
- delay: 1000, // Initial delay (ms)
84
- maxDelay: 30000, // Maximum delay (ms)
85
- strategy: 'exponential', // 'linear' or 'exponential'
86
- onReconnecting: (attempt) => {}, // Called on each attempt
87
- onReconnected: () => {}, // Called on success
88
- onFailed: () => {} // Called when max attempts reached
89
- }
76
+ new Honeydrop(url, {
77
+ debug: false, // Enable debug logs
78
+ autoConnect: true, // Connect immediately
79
+ reconnection: { // Reconnection strategy
80
+ enabled: true,
81
+ maxAttempts: 10,
82
+ strategy: 'exponential' // 'linear' | 'exponential'
83
+ },
84
+ offlineQueue: { // Offline behavior
85
+ enabled: true,
86
+ maxSize: 100
87
+ }
88
+ })
90
89
  ```
91
90
 
92
- #### Offline Queue Options
91
+ ### Core Methods
93
92
 
94
- ```javascript
95
- {
96
- enabled: true, // Enable offline queueing (default: true)
97
- maxSize: 100, // Maximum events to queue (0 = unlimited)
98
- maxAge: 0, // Max age in ms before discard (0 = no expiry)
99
- onQueued: (event) => {}, // Called when event is queued
100
- onFlushed: (count) => {}, // Called when queue is flushed on reconnect
101
- onDropped: (event) => {} // Called when event is dropped (queue full)
102
- }
103
- ```
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `connect()` | Manually connect to the server. |
96
+ | `disconnect()` | Disconnect and clean up all listeners. |
97
+ | `reconnect()` | Force a manual reconnection attempt. |
98
+ | `setDebug(bool)` | Toggle debug logging at runtime. |
104
99
 
100
+ ### Event Handling
105
101
 
106
- ### Connection Methods
102
+ Honeydrop provides a rich API for event management:
107
103
 
108
- ```javascript
109
- // Connect to server
110
- client.connect();
104
+ ```typescript
105
+ // Standard listener
106
+ client.on('user:login', (user) => console.log(user));
111
107
 
112
- // Disconnect and cleanup
113
- client.disconnect();
108
+ // One-time listener
109
+ client.once('init', () => console.log('Initialized'));
114
110
 
115
- // Check connection status
116
- client.connected; // boolean
117
- client.id; // socket ID
111
+ // Remove listeners
112
+ client.off('user:login');
118
113
 
119
- // Get detailed connection info
120
- client.getConnectionInfo();
121
- // { connected: boolean, id: string | null, transport: string | null }
114
+ // Listen to MULTIPLE events with one handler
115
+ client.onMultiple(['connect', 'reconnect'], () => updateStatus('online'));
122
116
 
123
- // Manual reconnection
124
- client.reconnect();
117
+ // Wait for a specific event (Promise-based)
118
+ const data = await client.waitFor('ready', 5000);
125
119
  ```
126
120
 
127
- ### Event Handling
128
-
129
- ```javascript
130
- // Register event listener
131
- client.on('event', (data) => { /* ... */ });
132
-
133
- // One-time listener
134
- client.once('event', (data) => { /* ... */ });
121
+ ### Emitting Events
135
122
 
136
- // Remove listener
137
- client.off('event', handler);
138
- client.off('event'); // Remove all listeners for event
123
+ Send data with confidence using advanced emit patterns:
139
124
 
140
- // Listen to multiple events
141
- client.onMultiple(['user:join', 'user:leave'], (event, data) => {
142
- console.log(`${event}:`, data);
143
- });
144
-
145
- // Fire once on any of the events
146
- client.onceAny(['success', 'error'], (event, data) => {
147
- console.log(`Got ${event}:`, data);
148
- });
149
- ```
125
+ ```typescript
126
+ // Standard emit
127
+ client.emit('update', data);
150
128
 
151
- ### Emitting Events
129
+ // Emit with Acknowledgment (Async/Await)
130
+ try {
131
+ const response = await client.emitWithAck('createUser', userData, 5000);
132
+ } catch (err) {
133
+ console.error('Server did not acknowledge in time');
134
+ }
152
135
 
153
- ```javascript
154
- // Basic emit
155
- client.emit('event', data);
156
-
157
- // Emit multiple events
158
- client.emitMultiple([
159
- { event: 'init', data: { userId: 1 } },
160
- { event: 'status', data: { online: true } }
161
- ]);
162
-
163
- // Emit with acknowledgment
164
- const response = await client.emitWithAck('request', data, 5000);
165
-
166
- // Emit multiple with acknowledgment
167
- const responses = await client.emitMultipleWithAck([
168
- { event: 'validate', data: input1 },
169
- { event: 'validate', data: input2, timeout: 3000 }
170
- ]);
171
-
172
- // Request/Response (RPC pattern)
173
- const user = await client.request('getUser', { id: 123 });
174
- // Server should emit 'getUser:response' with the result
175
-
176
- // Emit with automatic retry
177
- const result = await client.emitWithRetry('criticalAction', data, {
136
+ // Emit with Automatic Retry
137
+ // Great for critical actions that must reach the server
138
+ await client.emitWithRetry('saveData', payload, {
178
139
  maxRetries: 3,
179
- retryDelay: 1000,
180
- onRetry: (attempt, error) => console.log(`Retry ${attempt}`)
140
+ retryDelay: 1000
181
141
  });
182
- ```
183
142
 
184
- ### Room Management
185
-
186
- ```javascript
187
- // Join a room
188
- client.join('room-1');
143
+ // Throttled Emit (e.g., for mouse movement or window resize)
144
+ const updatePosition = client.throttle('cursor:move', 100);
145
+ updatePosition({ x: 10, y: 20 });
146
+ ```
189
147
 
190
- // Leave a room
191
- client.leave('room-1');
148
+ ### Namespaces
192
149
 
193
- // Emit to a specific room
194
- client.toRoom('room-1').emit('message', { text: 'Hello room!' });
150
+ Organize your events into logical groups without creating multiple socket connections.
195
151
 
196
- // Get joined rooms
197
- console.log(client.getRooms()); // ['room-1']
152
+ ```typescript
153
+ const chat = client.namespace('chat'); // prefixes events with 'chat:'
198
154
 
199
- // Check if in a room
200
- console.log(client.isInRoom('room-1')); // true
155
+ chat.emit('msg', 'hello'); // Emits 'chat:msg'
156
+ chat.on('typing', showTyping); // Listens for 'chat:typing'
201
157
  ```
202
158
 
203
- ### Waiting for Events
159
+ ### Room Management
204
160
 
205
- ```javascript
206
- // Wait for a specific event
207
- const data = await client.waitFor('ready', 5000);
161
+ Helper methods for room-based logic (requires server-side support for room events).
208
162
 
209
- // Wait for any of the events
210
- const { event, data } = await client.waitForAny(['success', 'error']);
163
+ ```typescript
164
+ client.join('room-123');
165
+ client.toRoom('room-123').emit('announcement', 'Welcome!');
166
+ const inRoom = client.isInRoom('room-123'); // true
211
167
  ```
212
168
 
213
- ### Namespaced Events
169
+ ## ⚛️ Using with React
214
170
 
215
- ```javascript
216
- // Create a namespace
217
- const chat = client.namespace('chat');
171
+ Honeydrop provides first-class support for React with powerful hooks that handle lifecycle and cleanup for you.
218
172
 
219
- // All events are prefixed with 'chat:'
220
- chat.on('message', handler); // Listens to 'chat:message'
221
- chat.emit('message', data); // Emits 'chat:message'
173
+ ### Setup Provider
222
174
 
223
- // Create sub-namespaces
224
- const room = chat.sub('room1');
225
- room.emit('join'); // Emits 'chat:room1:join'
175
+ Wrap your app in the `HoneydropProvider` to make the client available throughout your component tree.
226
176
 
227
- // Custom delimiter
228
- const api = client.namespace('api', { delimiter: '/' });
229
- api.emit('users', query); // Emits 'api/users'
230
- ```
177
+ ```tsx
178
+ import { Honeydrop, HoneydropProvider } from 'honeydrop';
231
179
 
232
- ### Throttle & Debounce
180
+ const client = new Honeydrop('http://localhost:3000');
233
181
 
234
- ```javascript
235
- // Throttled emit (max once per interval)
236
- const throttledUpdate = client.throttle('position', 100);
237
- // Call as often as you want, emits max 10 times/sec
238
- throttledUpdate({ x: 100, y: 200 });
239
-
240
- // Debounced emit (waits for pause in calls)
241
- const debouncedSearch = client.debounce('search', 300);
242
- // Only emits after 300ms of no calls
243
- debouncedSearch({ query: 'hello' });
182
+ function App() {
183
+ return (
184
+ <HoneydropProvider client={client}>
185
+ <Dashboard />
186
+ </HoneydropProvider>
187
+ );
188
+ }
244
189
  ```
245
190
 
246
- ### Offline Queue
191
+ ### Hooks API
247
192
 
248
- Events are automatically queued when disconnected and sent when reconnected:
193
+ #### useSocketEvent
194
+ Listens for an event and automatically removes the listener when the component unmounts. No more `useEffect` boilerplate!
249
195
 
250
- ```javascript
251
- // Events emitted while offline are queued automatically
252
- client.emit('message', { text: 'Hello' }); // Queued if offline
253
-
254
- // Check queue status
255
- console.log(client.getQueueLength()); // Number of queued events
256
- console.log(client.getQueuedEvents()); // Array of queued events
196
+ ```tsx
197
+ import { useSocketEvent } from 'honeydrop';
257
198
 
258
- // Clear the queue
259
- client.clearQueue();
199
+ function LiveCounter() {
200
+ // Listen for 'count:update' event
201
+ useSocketEvent('count:update', (count) => {
202
+ console.log('New count:', count);
203
+ });
260
204
 
261
- // Disable/enable offline queueing
262
- client.setOfflineQueue(false);
205
+ return <div>Check console for updates</div>;
206
+ }
263
207
  ```
264
208
 
265
- ### Connection Health
266
-
267
- Monitor connection latency and quality:
268
-
269
- ```javascript
270
- // Perform a ping and get latency
271
- const latency = await client.ping(); // Returns latency in ms
209
+ #### useSocketStatus
210
+ Easily track your connection status to show loading spinners or offline badges.
272
211
 
273
- // Get average latency
274
- console.log(client.getLatency()); // Average latency in ms
212
+ ```tsx
213
+ import { useSocketStatus } from 'honeydrop';
275
214
 
276
- // Get connection quality
277
- console.log(client.getConnectionQuality()); // 'excellent' | 'good' | 'fair' | 'poor' | 'disconnected'
215
+ function ConnectionBadge() {
216
+ const status = useSocketStatus(); // 'connected' | 'disconnected' | 'connecting'
278
217
 
279
- // Disable/enable monitoring
280
- client.setConnectionMonitoring(false);
218
+ if (status === 'disconnected') {
219
+ return <span style={{ color: 'red' }}>Offline</span>;
220
+ }
221
+ return <span style={{ color: 'green' }}>Online</span>;
222
+ }
281
223
  ```
282
224
 
283
- ### Debugging
225
+ ## 🌐 Browser Support
284
226
 
227
+ Honeydrop works seamlessly in both Node.js and the Browser.
228
+
229
+ ### Using with Bundlers (Vite, Webpack, etc.)
285
230
  ```javascript
286
- // Enable/disable debug mode
287
- client.setDebug(true);
231
+ import Honeydrop from 'honeydrop';
232
+ ```
288
233
 
289
- // Set log level
290
- client.setLogLevel('debug'); // 'debug' | 'info' | 'warn' | 'error'
234
+ ### Using via CDN
235
+ ```html
236
+ <script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
237
+ <script src="https://unpkg.com/honeydrop/dist/honeydrop.umd.js"></script>
238
+ <script>
239
+ const client = new Honeydrop.Honeydrop('http://localhost:3000');
240
+ </script>
291
241
  ```
292
242
 
293
- ## 🎮 Demo
243
+ ## 🤝 Contributing
294
244
 
295
- Run the included demo to see Honeydrop in action:
245
+ We welcome contributions! Please feel free to verify the correctness of your changes by running the demo app:
296
246
 
297
247
  ```bash
298
248
  cd demo
@@ -300,26 +250,12 @@ npm install
300
250
  npm start
301
251
  ```
302
252
 
303
- Then open `http://localhost:3000` in multiple browser tabs to test real-time communication.
304
-
305
- ## 🌐 Browser Usage
306
-
307
- ### With Bundler (Webpack, Vite, etc.)
308
-
309
- ```javascript
310
- import Honeydrop from 'honeydrop';
311
- ```
312
-
313
- ### Via Script Tag
253
+ ## 📄 License
314
254
 
315
- ```html
316
- <script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
317
- <script src="path/to/honeydrop.umd.js"></script>
318
- <script>
319
- const client = new Honeydrop.Honeydrop('http://localhost:3000');
320
- </script>
321
- ```
255
+ MIT License © 2024 Neeraj
322
256
 
323
- ## 📄 License
257
+ <br>
324
258
 
325
- MIT © 2024
259
+ <p align="center">
260
+ Made with ❤️ by <a href="https://github.com/neeer4j">neeer4j</a>
261
+ </p>
@@ -1,2 +1,11 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("socket.io-client");const t={debug:0,info:1,warn:2,error:3,none:4},n={debug:"#9E9E9E",info:"#2196F3",warn:"#FF9800",error:"#F44336",none:""};class o{constructor(e={}){this.level=e.level??"info",this.prefix=e.prefix??"[Honeydrop]",this.enabled=e.enabled??!1,this.isBrowser="undefined"!=typeof window}setLevel(e){this.level=e}setEnabled(e){this.enabled=e}shouldLog(e){return!!this.enabled&&t[e]>=t[this.level]}formatMessage(e,t){const o=(new Date).toISOString().split("T")[1].slice(0,-1);return this.isBrowser?[`%c${this.prefix} %c${e.toUpperCase()} %c[${o}] %c${t}`,"color: #FFC107; font-weight: bold",`color: ${n[e]}; font-weight: bold`,"color: #9E9E9E","color: inherit"]:[`${this.prefix} ${e.toUpperCase()} [${o}] ${t}`]}debug(e,...t){if(this.shouldLog("debug")){const n=this.formatMessage("debug",e);console.debug(...n,...t)}}info(e,...t){if(this.shouldLog("info")){const n=this.formatMessage("info",e);console.info(...n,...t)}}warn(e,...t){if(this.shouldLog("warn")){const n=this.formatMessage("warn",e);console.warn(...n,...t)}}error(e,...t){if(this.shouldLog("error")){const n=this.formatMessage("error",e);console.error(...n,...t)}}connection(e,t){this.info({connected:"🟢 Connected to server",disconnected:"🔴 Disconnected from server",reconnecting:"🟡 Attempting to reconnect...",reconnected:"🟢 Reconnected to server"}[e],t??"")}emit(e,t){this.debug(`📤 Emit: ${e}`,void 0!==t?t:"")}receive(e,t){this.debug(`📥 Received: ${e}`,void 0!==t?t:"")}}new o;class i{constructor(e){this.listeners=new Map,this.socket=null,this.logger=e}setSocket(e){this.socket=e}on(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const n=(...n)=>{this.logger.receive(e,1===n.length?n[0]:n),t(...n)};this.socket.on(e,n),this.addListener(e,n,!1)}once(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const n=(...o)=>{this.logger.receive(e,1===o.length?o[0]:o),this.removeListener(e,n),t(...o)};this.socket.once(e,n),this.addListener(e,n,!0)}off(e,t){if(this.socket)if(t)this.socket.off(e,t),this.removeListener(e,t);else{const t=this.listeners.get(e);t&&(t.forEach(t=>{this.socket?.off(e,t.handler)}),this.listeners.delete(e))}}onMultiple(e,t){e.forEach(e=>{this.on(e,(...n)=>t(e,...n))})}onceAny(e,t){let n=!1;const o=()=>{n||(n=!0,e.forEach(e=>this.off(e)))};e.forEach(e=>{this.on(e,(...i)=>{n||(o(),t(e,...i))})})}listenerCount(e){return this.listeners.get(e)?.length??0}eventNames(){return Array.from(this.listeners.keys())}removeAllListeners(){this.socket&&(this.listeners.forEach((e,t)=>{e.forEach(e=>{this.socket?.off(t,e.handler)})}),this.listeners.clear(),this.logger.debug("All event listeners removed"))}addListener(e,t,n){const o=this.listeners.get(e)??[];o.push({event:e,handler:t,once:n}),this.listeners.set(e,o)}removeListener(e,t){const n=this.listeners.get(e);if(n){const o=n.findIndex(e=>e.handler===t);-1!==o&&(n.splice(o,1),0===n.length&&this.listeners.delete(e))}}}const s={enabled:!0,maxAttempts:10,delay:1e3,maxDelay:3e4,strategy:"exponential"};class r{constructor(e,t){this.socket=null,this.attempts=0,this.timer=null,this.isReconnecting=!1,this.options={...s,...e},this.callbacks={onReconnecting:e.onReconnecting,onReconnected:e.onReconnected,onFailed:e.onFailed},this.logger=t}setSocket(e){this.socket=e,this.setupListeners()}calculateDelay(){let e;return e="exponential"===this.options.strategy?this.options.delay*Math.pow(2,this.attempts-1):this.options.delay,Math.min(e,this.options.maxDelay)}setupListeners(){this.socket&&(this.socket.on("disconnect",e=>{this.logger.connection("disconnected",{reason:e}),"io client disconnect"!==e&&this.options.enabled&&this.startReconnection()}),this.socket.on("connect",()=>{this.isReconnecting?(this.logger.connection("reconnected"),this.callbacks.onReconnected?.(),this.reset()):this.logger.connection("connected")}),this.socket.on("connect_error",()=>{this.isReconnecting&&this.attemptReconnection()}))}startReconnection(){this.isReconnecting||(this.isReconnecting=!0,this.attempts=0,this.attemptReconnection())}attemptReconnection(){if(!this.socket||!this.isReconnecting)return;if(this.attempts++,this.attempts>this.options.maxAttempts)return this.logger.error(`Reconnection failed after ${this.options.maxAttempts} attempts`),this.callbacks.onFailed?.(),void this.reset();const e=this.calculateDelay();this.logger.connection("reconnecting",{attempt:this.attempts,delay:e}),this.callbacks.onReconnecting?.(this.attempts),this.timer=setTimeout(()=>{this.socket&&this.isReconnecting&&this.socket.connect()},e)}reconnect(){if(!this.socket)throw new Error("Socket not initialized");this.reset(),this.socket.connect()}reset(){this.isReconnecting=!1,this.attempts=0,this.timer&&(clearTimeout(this.timer),this.timer=null)}stop(){this.reset(),this.logger.debug("Reconnection handler stopped")}getStatus(){return{isReconnecting:this.isReconnecting,attempts:this.attempts}}updateOptions(e){Object.assign(this.options,e),void 0!==e.onReconnecting&&(this.callbacks.onReconnecting=e.onReconnecting),void 0!==e.onReconnected&&(this.callbacks.onReconnected=e.onReconnected),void 0!==e.onFailed&&(this.callbacks.onFailed=e.onFailed)}}class c{constructor(e,t,n={}){this.name=e,this.emitter=t,this.delimiter=n.delimiter??":"}getEventName(e){return`${this.name}${this.delimiter}${e}`}on(e,t){this.emitter.on(this.getEventName(e),t)}once(e,t){this.emitter.once(this.getEventName(e),t)}off(e,t){this.emitter.off(this.getEventName(e),t)}emit(e,...t){this.emitter.emit(this.getEventName(e),...t)}getName(){return this.name}getDelimiter(){return this.delimiter}sub(e){return new c(`${this.name}${this.delimiter}${e}`,this.emitter,{delimiter:this.delimiter})}}const h={enabled:!0,maxSize:100,maxAge:0};class l{constructor(e={},t){this.queue=[],this.socket=null,this.options={...h,...e},this.logger=t}setSocket(e){this.socket=e,e.on("connect",()=>{this.flush()})}enqueue(e,...t){if(!this.options.enabled)return!1;const n={event:e,args:t,timestamp:Date.now()};return this.options.maxSize&&this.options.maxSize>0&&this.queue.length>=this.options.maxSize?(this.logger.warn(`Offline queue full. Dropping event: ${e}`),this.options.onDropped?.(n),!1):(this.queue.push(n),this.logger.debug(`Event queued offline: ${e} (queue size: ${this.queue.length})`),this.options.onQueued?.(n),!0)}flush(){if(!this.socket?.connected||0===this.queue.length)return;if(this.options.maxAge&&this.options.maxAge>0){const e=Date.now(),t=this.queue.length;this.queue=this.queue.filter(t=>e-t.timestamp<this.options.maxAge);const n=t-this.queue.length;n>0&&this.logger.debug(`Discarded ${n} expired events from queue`)}const e=this.queue.length;if(0!==e){for(this.logger.info(`Flushing ${e} queued events...`);this.queue.length>0;){const e=this.queue.shift();this.socket.emit(e.event,...e.args),this.logger.debug(`Flushed: ${e.event}`)}this.options.onFlushed?.(e),this.logger.info(`Successfully flushed ${e} events`)}}get length(){return this.queue.length}getQueue(){return[...this.queue]}clear(){const e=this.queue.length;this.queue=[],this.logger.debug(`Cleared ${e} events from offline queue`)}get enabled(){return this.options.enabled??!0}setEnabled(e){this.options.enabled=e}}const a={enabled:!0,pingInterval:5e3,pingTimeout:3e3,sampleSize:5,thresholds:{excellent:50,good:100,fair:300}};class u{constructor(e={},t){this.socket=null,this.intervalId=null,this.latencySamples=[],this.currentQuality="disconnected",this.lastPingTime=0,this.options={...a,...e,thresholds:{...a.thresholds,...e.thresholds}},this.logger=t}setSocket(e){this.socket=e,e.on("connect",()=>{this.options.enabled&&this.start()}),e.on("disconnect",()=>{this.stop(),this.updateQuality("disconnected",0)}),e.on("pong",()=>{const e=Date.now()-this.lastPingTime;this.recordLatency(e)}),e.connected&&this.options.enabled&&this.start()}start(){this.intervalId||(this.logger.debug("Connection monitoring started"),this.intervalId=setInterval(()=>{this.ping().catch(()=>{})},this.options.pingInterval??a.pingInterval),this.ping().catch(()=>{}))}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null,this.logger.debug("Connection monitoring stopped"))}async ping(){if(!this.socket?.connected)throw new Error("Socket not connected");return new Promise((e,t)=>{const n=this.options.pingTimeout??a.pingTimeout,o=setTimeout(()=>{t(new Error("Ping timeout")),this.recordLatency(n)},n);this.lastPingTime=Date.now(),this.socket.volatile.emit("ping",()=>{clearTimeout(o);const t=Date.now()-this.lastPingTime;this.recordLatency(t),e(t)})})}recordLatency(e){const t=this.options.sampleSize??a.sampleSize;this.latencySamples.push(e),this.latencySamples.length>t&&this.latencySamples.shift();const n=this.getAverageLatency(),o=this.calculateQuality(n);o!==this.currentQuality&&this.updateQuality(o,n)}updateQuality(e,t){const n=this.currentQuality;this.currentQuality=e,this.logger.debug(`Connection quality: ${n} -> ${e} (${t}ms)`),this.options.onQualityChange?.(e,t)}calculateQuality(e){const t={...a.thresholds,...this.options.thresholds};return e<=t.excellent?"excellent":e<=t.good?"good":e<=t.fair?"fair":"poor"}getAverageLatency(){if(0===this.latencySamples.length)return 0;const e=this.latencySamples.reduce((e,t)=>e+t,0);return Math.round(e/this.latencySamples.length)}getLatency(){return this.latencySamples[this.latencySamples.length-1]??0}getQuality(){return this.currentQuality}getLatencySamples(){return[...this.latencySamples]}get isMonitoring(){return null!==this.intervalId}setEnabled(e){this.options.enabled=e,e&&this.socket?.connected?this.start():e||this.stop()}}const g={joinEvent:"join",leaveEvent:"leave"};class d{constructor(e={},t){this.socket=null,this.joinedRooms=new Set,this.options={...g,...e},this.logger=t}setSocket(e){this.socket=e,e.on("disconnect",()=>{this.joinedRooms.clear(),this.logger.debug("Cleared room list on disconnect")})}join(e,t){if(!this.socket?.connected)throw new Error("Socket not connected. Cannot join room.");const n=this.options.joinEvent??g.joinEvent;void 0!==t?this.socket.emit(n,e,t):this.socket.emit(n,e),this.joinedRooms.add(e),this.logger.debug(`Joined room: ${e}`)}leave(e,t){if(!this.socket?.connected)throw new Error("Socket not connected. Cannot leave room.");const n=this.options.leaveEvent??g.leaveEvent;void 0!==t?this.socket.emit(n,e,t):this.socket.emit(n,e),this.joinedRooms.delete(e),this.logger.debug(`Left room: ${e}`)}toRoom(e){return{emit:(t,...n)=>{if(!this.socket?.connected)throw new Error("Socket not connected. Cannot emit to room.");this.socket.emit(t,e,...n),this.logger.debug(`Emitted to room ${e}: ${t}`)}}}getRooms(){return Array.from(this.joinedRooms)}isInRoom(e){return this.joinedRooms.has(e)}leaveAll(){for(const e of this.joinedRooms)this.leave(e)}}function m(e,t){t.forEach(({event:t,data:n})=>{void 0!==n?e.emit(t,n):e.emit(t)})}async function p(e,t){const n=t.map(async({event:t,data:n,timeout:o=5e3})=>new Promise((i,s)=>{const r=setTimeout(()=>{s(new Error(`Timeout waiting for ack: ${t}`))},o),c=void 0!==n?[n]:[];e.emit(t,...c,e=>{clearTimeout(r),i(e)})}));return Promise.all(n)}function f(e,t,n=5e3){return new Promise((o,i)=>{const s=setTimeout(()=>{e.off(t,r),i(new Error(`Timeout waiting for event: ${t}`))},n),r=e=>{clearTimeout(s),o(e)};e.once(t,r)})}function k(e,t,n=5e3){return new Promise((o,i)=>{const s=new Map,r=setTimeout(()=>{c(),i(new Error(`Timeout waiting for events: ${t.join(", ")}`))},n),c=()=>{s.forEach((t,n)=>{e.off(n,t)})};t.forEach(t=>{const n=e=>{clearTimeout(r),c(),o({event:t,data:e})};s.set(t,n),e.once(t,n)})})}function v(e,t,n,o=5e3){return new Promise((i,s)=>{const r=setTimeout(()=>{s(new Error(`Timeout waiting for ack: ${t}`))},o),c=void 0!==n?[n]:[];e.emit(t,...c,e=>{clearTimeout(r),i(e)})})}function w(e){return e?.connected??!1}function y(e){return e?{connected:e.connected,id:e.id??null,transport:e.io?.engine?.transport?.name??null}:{connected:!1,id:null,transport:null}}function E(e,t,n){let o,i=0,s=null;return r=>{const c=Date.now(),h=c-i;h>=n?(i=c,void 0!==r?e.emit(t,r):e.emit(t)):(o=r,s||(s=setTimeout(()=>{i=Date.now(),void 0!==o?e.emit(t,o):e.emit(t),s=null},n-h)))}}function b(e,t,n){let o=null;return i=>{o&&clearTimeout(o),o=setTimeout(()=>{void 0!==i?e.emit(t,i):e.emit(t),o=null},n)}}const R={debug:!1,logLevel:"info",namespaceDelimiter:":",autoConnect:!0};class S{constructor(e,t={}){this.socket=null,this.reconnectionHandler=null,this.namespaces=new Map,this.url=e,this.options={...R,...t},this.logger=new o({enabled:this.options.debug,level:this.options.logLevel}),this.eventManager=new i(this.logger),this.offlineQueue=new l(this.options.offlineQueue??{},this.logger),this.connectionMonitor=new u(this.options.connectionMonitor??{},this.logger),this.roomManager=new d(this.options.roomManager??{},this.logger),!1!==this.options.reconnection?.enabled&&(this.reconnectionHandler=new r(this.options.reconnection??{},this.logger)),this.options.autoConnect&&this.connect()}connect(){if(this.socket?.connected)return this.logger.warn("Already connected"),this.socket;const t={...this.options.socketOptions,reconnection:!1};return this.socket=e.io(this.url,t),this.eventManager.setSocket(this.socket),this.offlineQueue.setSocket(this.socket),this.connectionMonitor.setSocket(this.socket),this.roomManager.setSocket(this.socket),this.reconnectionHandler&&this.reconnectionHandler.setSocket(this.socket),this.socket}disconnect(){this.socket&&(this.eventManager.removeAllListeners(),this.reconnectionHandler?.stop(),this.connectionMonitor.stop(),this.socket.disconnect(),this.logger.connection("disconnected"))}on(e,t){return this.eventManager.on(e,t),this}once(e,t){return this.eventManager.once(e,t),this}off(e,t){return this.eventManager.off(e,t),this}emit(e,...t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return!this.socket.connected&&this.offlineQueue.enabled?(this.offlineQueue.enqueue(e,...t),this):(this.logger.emit(e,1===t.length?t[0]:t),this.socket.emit(e,...t),this)}emitMultiple(e){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return m(this.socket,e),this}async emitWithAck(e,t,n){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return v(this.socket,e,t,n)}async request(e,t,n={}){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const o=n.timeout??5e3,i=n.responseEvent??`${e}:response`;return new Promise((n,s)=>{const r=setTimeout(()=>{this.socket?.off(i,c),s(new Error(`Request timeout: ${e} (${o}ms)`))},o),c=e=>{clearTimeout(r),n(e)};this.socket.once(i,c),this.logger.emit(e,t),void 0!==t?this.socket.emit(e,t):this.socket.emit(e)})}async emitWithRetry(e,t,n={}){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const o=n.maxRetries??3,i=n.retryDelay??1e3,s=n.timeout??5e3;let r=new Error("Unknown error");for(let c=1;c<=o;c++)try{return await v(this.socket,e,t,s)}catch(t){r=t instanceof Error?t:new Error(String(t)),this.logger.warn(`Emit failed (attempt ${c}/${o}): ${e}`),n.onRetry?.(c,r),c<o&&await new Promise(e=>setTimeout(e,i))}throw new Error(`Emit failed after ${o} attempts: ${e}. Last error: ${r.message}`)}async emitMultipleWithAck(e){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return p(this.socket,e)}onMultiple(e,t){return this.eventManager.onMultiple(e,t),this}onceAny(e,t){return this.eventManager.onceAny(e,t),this}async waitFor(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return f(this.socket,e,t)}async waitForAny(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return k(this.socket,e,t)}namespace(e,t){const n=e;if(!this.namespaces.has(n)){const o={delimiter:t?.delimiter??this.options.namespaceDelimiter};this.namespaces.set(n,new c(e,this,o))}return this.namespaces.get(n)}throttle(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return E(this.socket,e,t)}debounce(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return b(this.socket,e,t)}getSocket(){return this.socket}get connected(){return w(this.socket)}get id(){return this.socket?.id}getConnectionInfo(){return y(this.socket)}reconnect(){if(!this.reconnectionHandler)throw new Error("Reconnection is not enabled");this.reconnectionHandler.reconnect()}getReconnectionStatus(){return this.reconnectionHandler?.getStatus()??null}listenerCount(e){return this.eventManager.listenerCount(e)}eventNames(){return this.eventManager.eventNames()}setDebug(e){return this.logger.setEnabled(e),this}setLogLevel(e){return this.logger.setLevel(e),this}getQueueLength(){return this.offlineQueue.length}getQueuedEvents(){return this.offlineQueue.getQueue()}clearQueue(){return this.offlineQueue.clear(),this}setOfflineQueue(e){return this.offlineQueue.setEnabled(e),this}async ping(){return this.connectionMonitor.ping()}getLatency(){return this.connectionMonitor.getAverageLatency()}getConnectionQuality(){return this.connectionMonitor.getQuality()}setConnectionMonitoring(e){return this.connectionMonitor.setEnabled(e),this}join(e,t){return this.roomManager.join(e,t),this}leave(e,t){return this.roomManager.leave(e,t),this}toRoom(e){return this.roomManager.toRoom(e)}getRooms(){return this.roomManager.getRooms()}isInRoom(e){return this.roomManager.isInRoom(e)}}exports.ConnectionMonitor=u,exports.EventManager=i,exports.Honeydrop=S,exports.Logger=o,exports.NamespacedEvents=c,exports.OfflineQueue=l,exports.ReconnectionHandler=r,exports.RoomManager=d,exports.createDebouncedEmit=b,exports.createThrottledEmit=E,exports.default=S,exports.emitMultiple=m,exports.emitMultipleWithAck=p,exports.emitWithAck=v,exports.getConnectionInfo=y,exports.isConnected=w,exports.waitForAnyEvent=k,exports.waitForEvent=f;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("socket.io-client"),t=require("react");const n={debug:0,info:1,warn:2,error:3,none:4},o={debug:"#9E9E9E",info:"#2196F3",warn:"#FF9800",error:"#F44336",none:""};class i{constructor(e={}){this.level=e.level??"info",this.prefix=e.prefix??"[Honeydrop]",this.enabled=e.enabled??!1,this.isBrowser="undefined"!=typeof window}setLevel(e){this.level=e}setEnabled(e){this.enabled=e}shouldLog(e){return!!this.enabled&&n[e]>=n[this.level]}formatMessage(e,t){const n=(new Date).toISOString().split("T")[1].slice(0,-1);return this.isBrowser?[`%c${this.prefix} %c${e.toUpperCase()} %c[${n}] %c${t}`,"color: #FFC107; font-weight: bold",`color: ${o[e]}; font-weight: bold`,"color: #9E9E9E","color: inherit"]:[`${this.prefix} ${e.toUpperCase()} [${n}] ${t}`]}debug(e,...t){if(this.shouldLog("debug")){const n=this.formatMessage("debug",e);console.debug(...n,...t)}}info(e,...t){if(this.shouldLog("info")){const n=this.formatMessage("info",e);console.info(...n,...t)}}warn(e,...t){if(this.shouldLog("warn")){const n=this.formatMessage("warn",e);console.warn(...n,...t)}}error(e,...t){if(this.shouldLog("error")){const n=this.formatMessage("error",e);console.error(...n,...t)}}connection(e,t){this.info({connected:"🟢 Connected to server",disconnected:"🔴 Disconnected from server",reconnecting:"🟡 Attempting to reconnect...",reconnected:"🟢 Reconnected to server"}[e],t??"")}emit(e,t){this.debug(`📤 Emit: ${e}`,void 0!==t?t:"")}receive(e,t){this.debug(`📥 Received: ${e}`,void 0!==t?t:"")}}new i;class s{constructor(e){this.listeners=new Map,this.socket=null,this.logger=e}setSocket(e){this.socket=e}on(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const n=(...n)=>{this.logger.receive(e,1===n.length?n[0]:n),t(...n)};this.socket.on(e,n),this.addListener(e,n,!1)}once(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const n=(...o)=>{this.logger.receive(e,1===o.length?o[0]:o),this.removeListener(e,n),t(...o)};this.socket.once(e,n),this.addListener(e,n,!0)}off(e,t){if(this.socket)if(t)this.socket.off(e,t),this.removeListener(e,t);else{const t=this.listeners.get(e);t&&(t.forEach(t=>{this.socket?.off(e,t.handler)}),this.listeners.delete(e))}}onMultiple(e,t){e.forEach(e=>{this.on(e,(...n)=>t(e,...n))})}onceAny(e,t){let n=!1;const o=()=>{n||(n=!0,e.forEach(e=>this.off(e)))};e.forEach(e=>{this.on(e,(...i)=>{n||(o(),t(e,...i))})})}listenerCount(e){return this.listeners.get(e)?.length??0}eventNames(){return Array.from(this.listeners.keys())}removeAllListeners(){this.socket&&(this.listeners.forEach((e,t)=>{e.forEach(e=>{this.socket?.off(t,e.handler)})}),this.listeners.clear(),this.logger.debug("All event listeners removed"))}addListener(e,t,n){const o=this.listeners.get(e)??[];o.push({event:e,handler:t,once:n}),this.listeners.set(e,o)}removeListener(e,t){const n=this.listeners.get(e);if(n){const o=n.findIndex(e=>e.handler===t);-1!==o&&(n.splice(o,1),0===n.length&&this.listeners.delete(e))}}}const r={enabled:!0,maxAttempts:10,delay:1e3,maxDelay:3e4,strategy:"exponential"};class c{constructor(e,t){this.socket=null,this.attempts=0,this.timer=null,this.isReconnecting=!1,this.options={...r,...e},this.callbacks={onReconnecting:e.onReconnecting,onReconnected:e.onReconnected,onFailed:e.onFailed},this.logger=t}setSocket(e){this.socket=e,this.setupListeners()}calculateDelay(){let e;return e="exponential"===this.options.strategy?this.options.delay*Math.pow(2,this.attempts-1):this.options.delay,Math.min(e,this.options.maxDelay)}setupListeners(){this.socket&&(this.socket.on("disconnect",e=>{this.logger.connection("disconnected",{reason:e}),"io client disconnect"!==e&&this.options.enabled&&this.startReconnection()}),this.socket.on("connect",()=>{this.isReconnecting?(this.logger.connection("reconnected"),this.callbacks.onReconnected?.(),this.reset()):this.logger.connection("connected")}),this.socket.on("connect_error",()=>{this.isReconnecting&&this.attemptReconnection()}))}startReconnection(){this.isReconnecting||(this.isReconnecting=!0,this.attempts=0,this.attemptReconnection())}attemptReconnection(){if(!this.socket||!this.isReconnecting)return;if(this.attempts++,this.attempts>this.options.maxAttempts)return this.logger.error(`Reconnection failed after ${this.options.maxAttempts} attempts`),this.callbacks.onFailed?.(),void this.reset();const e=this.calculateDelay();this.logger.connection("reconnecting",{attempt:this.attempts,delay:e}),this.callbacks.onReconnecting?.(this.attempts),this.timer=setTimeout(()=>{this.socket&&this.isReconnecting&&this.socket.connect()},e)}reconnect(){if(!this.socket)throw new Error("Socket not initialized");this.reset(),this.socket.connect()}reset(){this.isReconnecting=!1,this.attempts=0,this.timer&&(clearTimeout(this.timer),this.timer=null)}stop(){this.reset(),this.logger.debug("Reconnection handler stopped")}getStatus(){return{isReconnecting:this.isReconnecting,attempts:this.attempts}}updateOptions(e){Object.assign(this.options,e),void 0!==e.onReconnecting&&(this.callbacks.onReconnecting=e.onReconnecting),void 0!==e.onReconnected&&(this.callbacks.onReconnected=e.onReconnected),void 0!==e.onFailed&&(this.callbacks.onFailed=e.onFailed)}}class a{constructor(e,t,n={}){this.name=e,this.emitter=t,this.delimiter=n.delimiter??":"}getEventName(e){return`${this.name}${this.delimiter}${e}`}on(e,t){this.emitter.on(this.getEventName(e),t)}once(e,t){this.emitter.once(this.getEventName(e),t)}off(e,t){this.emitter.off(this.getEventName(e),t)}emit(e,...t){this.emitter.emit(this.getEventName(e),...t)}getName(){return this.name}getDelimiter(){return this.delimiter}sub(e){return new a(`${this.name}${this.delimiter}${e}`,this.emitter,{delimiter:this.delimiter})}}const l={enabled:!0,maxSize:100,maxAge:0};class h{constructor(e={},t){this.queue=[],this.socket=null,this.options={...l,...e},this.logger=t}setSocket(e){this.socket=e,e.on("connect",()=>{this.flush()})}enqueue(e,...t){if(!this.options.enabled)return!1;const n={event:e,args:t,timestamp:Date.now()};return this.options.maxSize&&this.options.maxSize>0&&this.queue.length>=this.options.maxSize?(this.logger.warn(`Offline queue full. Dropping event: ${e}`),this.options.onDropped?.(n),!1):(this.queue.push(n),this.logger.debug(`Event queued offline: ${e} (queue size: ${this.queue.length})`),this.options.onQueued?.(n),!0)}flush(){if(!this.socket?.connected||0===this.queue.length)return;if(this.options.maxAge&&this.options.maxAge>0){const e=Date.now(),t=this.queue.length;this.queue=this.queue.filter(t=>e-t.timestamp<this.options.maxAge);const n=t-this.queue.length;n>0&&this.logger.debug(`Discarded ${n} expired events from queue`)}const e=this.queue.length;if(0!==e){for(this.logger.info(`Flushing ${e} queued events...`);this.queue.length>0;){const e=this.queue.shift();this.socket.emit(e.event,...e.args),this.logger.debug(`Flushed: ${e.event}`)}this.options.onFlushed?.(e),this.logger.info(`Successfully flushed ${e} events`)}}get length(){return this.queue.length}getQueue(){return[...this.queue]}clear(){const e=this.queue.length;this.queue=[],this.logger.debug(`Cleared ${e} events from offline queue`)}get enabled(){return this.options.enabled??!0}setEnabled(e){this.options.enabled=e}}const u={enabled:!0,pingInterval:5e3,pingTimeout:3e3,sampleSize:5,thresholds:{excellent:50,good:100,fair:300}};class d{constructor(e={},t){this.socket=null,this.intervalId=null,this.latencySamples=[],this.currentQuality="disconnected",this.lastPingTime=0,this.options={...u,...e,thresholds:{...u.thresholds,...e.thresholds}},this.logger=t}setSocket(e){this.socket=e,e.on("connect",()=>{this.options.enabled&&this.start()}),e.on("disconnect",()=>{this.stop(),this.updateQuality("disconnected",0)}),e.on("pong",()=>{const e=Date.now()-this.lastPingTime;this.recordLatency(e)}),e.connected&&this.options.enabled&&this.start()}start(){this.intervalId||(this.logger.debug("Connection monitoring started"),this.intervalId=setInterval(()=>{this.ping().catch(()=>{})},this.options.pingInterval??u.pingInterval),this.ping().catch(()=>{}))}stop(){this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null,this.logger.debug("Connection monitoring stopped"))}async ping(){if(!this.socket?.connected)throw new Error("Socket not connected");return new Promise((e,t)=>{const n=this.options.pingTimeout??u.pingTimeout,o=setTimeout(()=>{t(new Error("Ping timeout")),this.recordLatency(n)},n);this.lastPingTime=Date.now(),this.socket.volatile.emit("ping",()=>{clearTimeout(o);const t=Date.now()-this.lastPingTime;this.recordLatency(t),e(t)})})}recordLatency(e){const t=this.options.sampleSize??u.sampleSize;this.latencySamples.push(e),this.latencySamples.length>t&&this.latencySamples.shift();const n=this.getAverageLatency(),o=this.calculateQuality(n);o!==this.currentQuality&&this.updateQuality(o,n)}updateQuality(e,t){const n=this.currentQuality;this.currentQuality=e,this.logger.debug(`Connection quality: ${n} -> ${e} (${t}ms)`),this.options.onQualityChange?.(e,t)}calculateQuality(e){const t={...u.thresholds,...this.options.thresholds};return e<=t.excellent?"excellent":e<=t.good?"good":e<=t.fair?"fair":"poor"}getAverageLatency(){if(0===this.latencySamples.length)return 0;const e=this.latencySamples.reduce((e,t)=>e+t,0);return Math.round(e/this.latencySamples.length)}getLatency(){return this.latencySamples[this.latencySamples.length-1]??0}getQuality(){return this.currentQuality}getLatencySamples(){return[...this.latencySamples]}get isMonitoring(){return null!==this.intervalId}setEnabled(e){this.options.enabled=e,e&&this.socket?.connected?this.start():e||this.stop()}}const f={joinEvent:"join",leaveEvent:"leave"};class m{constructor(e={},t){this.socket=null,this.joinedRooms=new Set,this.options={...f,...e},this.logger=t}setSocket(e){this.socket=e,e.on("disconnect",()=>{this.joinedRooms.clear(),this.logger.debug("Cleared room list on disconnect")})}join(e,t){if(!this.socket?.connected)throw new Error("Socket not connected. Cannot join room.");const n=this.options.joinEvent??f.joinEvent;void 0!==t?this.socket.emit(n,e,t):this.socket.emit(n,e),this.joinedRooms.add(e),this.logger.debug(`Joined room: ${e}`)}leave(e,t){if(!this.socket?.connected)throw new Error("Socket not connected. Cannot leave room.");const n=this.options.leaveEvent??f.leaveEvent;void 0!==t?this.socket.emit(n,e,t):this.socket.emit(n,e),this.joinedRooms.delete(e),this.logger.debug(`Left room: ${e}`)}toRoom(e){return{emit:(t,...n)=>{if(!this.socket?.connected)throw new Error("Socket not connected. Cannot emit to room.");this.socket.emit(t,e,...n),this.logger.debug(`Emitted to room ${e}: ${t}`)}}}getRooms(){return Array.from(this.joinedRooms)}isInRoom(e){return this.joinedRooms.has(e)}leaveAll(){for(const e of this.joinedRooms)this.leave(e)}}function g(e,t){t.forEach(({event:t,data:n})=>{void 0!==n?e.emit(t,n):e.emit(t)})}async function p(e,t){const n=t.map(async({event:t,data:n,timeout:o=5e3})=>new Promise((i,s)=>{const r=setTimeout(()=>{s(new Error(`Timeout waiting for ack: ${t}`))},o),c=void 0!==n?[n]:[];e.emit(t,...c,e=>{clearTimeout(r),i(e)})}));return Promise.all(n)}function y(e,t,n=5e3){return new Promise((o,i)=>{const s=setTimeout(()=>{e.off(t,r),i(new Error(`Timeout waiting for event: ${t}`))},n),r=e=>{clearTimeout(s),o(e)};e.once(t,r)})}function k(e,t,n=5e3){return new Promise((o,i)=>{const s=new Map,r=setTimeout(()=>{c(),i(new Error(`Timeout waiting for events: ${t.join(", ")}`))},n),c=()=>{s.forEach((t,n)=>{e.off(n,t)})};t.forEach(t=>{const n=e=>{clearTimeout(r),c(),o({event:t,data:e})};s.set(t,n),e.once(t,n)})})}function v(e,t,n,o=5e3){return new Promise((i,s)=>{const r=setTimeout(()=>{s(new Error(`Timeout waiting for ack: ${t}`))},o),c=void 0!==n?[n]:[];e.emit(t,...c,e=>{clearTimeout(r),i(e)})})}function b(e){return e?.connected??!1}function w(e){return e?{connected:e.connected,id:e.id??null,transport:e.io?.engine?.transport?.name??null}:{connected:!1,id:null,transport:null}}function E(e,t,n){let o,i=0,s=null;return r=>{const c=Date.now(),a=c-i;a>=n?(i=c,void 0!==r?e.emit(t,r):e.emit(t)):(o=r,s||(s=setTimeout(()=>{i=Date.now(),void 0!==o?e.emit(t,o):e.emit(t),s=null},n-a)))}}function S(e,t,n){let o=null;return i=>{o&&clearTimeout(o),o=setTimeout(()=>{void 0!==i?e.emit(t,i):e.emit(t),o=null},n)}}const R={debug:!1,logLevel:"info",namespaceDelimiter:":",autoConnect:!0};class x{constructor(e,t={}){this.socket=null,this.reconnectionHandler=null,this.namespaces=new Map,this.url=e,this.options={...R,...t},this.logger=new i({enabled:this.options.debug,level:this.options.logLevel}),this.eventManager=new s(this.logger),this.offlineQueue=new h(this.options.offlineQueue??{},this.logger),this.connectionMonitor=new d(this.options.connectionMonitor??{},this.logger),this.roomManager=new m(this.options.roomManager??{},this.logger),!1!==this.options.reconnection?.enabled&&(this.reconnectionHandler=new c(this.options.reconnection??{},this.logger)),this.options.autoConnect&&this.connect()}connect(){if(this.socket?.connected)return this.logger.warn("Already connected"),this.socket;const t={...this.options.socketOptions,reconnection:!1};return this.socket=e.io(this.url,t),this.eventManager.setSocket(this.socket),this.offlineQueue.setSocket(this.socket),this.connectionMonitor.setSocket(this.socket),this.roomManager.setSocket(this.socket),this.reconnectionHandler&&this.reconnectionHandler.setSocket(this.socket),this.socket}disconnect(){this.socket&&(this.eventManager.removeAllListeners(),this.reconnectionHandler?.stop(),this.connectionMonitor.stop(),this.socket.disconnect(),this.logger.connection("disconnected"))}on(e,t){return this.eventManager.on(e,t),this}once(e,t){return this.eventManager.once(e,t),this}off(e,t){return this.eventManager.off(e,t),this}emit(e,...t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return!this.socket.connected&&this.offlineQueue.enabled?(this.offlineQueue.enqueue(e,...t),this):(this.logger.emit(e,1===t.length?t[0]:t),this.socket.emit(e,...t),this)}emitMultiple(e){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return g(this.socket,e),this}async emitWithAck(e,t,n){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return v(this.socket,e,t,n)}async request(e,t,n={}){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const o=n.timeout??5e3,i=n.responseEvent??`${e}:response`;return new Promise((n,s)=>{const r=setTimeout(()=>{this.socket?.off(i,c),s(new Error(`Request timeout: ${e} (${o}ms)`))},o),c=e=>{clearTimeout(r),n(e)};this.socket.once(i,c),this.logger.emit(e,t),void 0!==t?this.socket.emit(e,t):this.socket.emit(e)})}async emitWithRetry(e,t,n={}){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");const o=n.maxRetries??3,i=n.retryDelay??1e3,s=n.timeout??5e3;let r=new Error("Unknown error");for(let c=1;c<=o;c++)try{return await v(this.socket,e,t,s)}catch(t){r=t instanceof Error?t:new Error(String(t)),this.logger.warn(`Emit failed (attempt ${c}/${o}): ${e}`),n.onRetry?.(c,r),c<o&&await new Promise(e=>setTimeout(e,i))}throw new Error(`Emit failed after ${o} attempts: ${e}. Last error: ${r.message}`)}async emitMultipleWithAck(e){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return p(this.socket,e)}onMultiple(e,t){return this.eventManager.onMultiple(e,t),this}onceAny(e,t){return this.eventManager.onceAny(e,t),this}async waitFor(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return y(this.socket,e,t)}async waitForAny(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return k(this.socket,e,t)}namespace(e,t){const n=e;if(!this.namespaces.has(n)){const o={delimiter:t?.delimiter??this.options.namespaceDelimiter};this.namespaces.set(n,new a(e,this,o))}return this.namespaces.get(n)}throttle(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return E(this.socket,e,t)}debounce(e,t){if(!this.socket)throw new Error("Socket not initialized. Call connect() first.");return S(this.socket,e,t)}getSocket(){return this.socket}get connected(){return b(this.socket)}get id(){return this.socket?.id}getConnectionInfo(){return w(this.socket)}reconnect(){if(!this.reconnectionHandler)throw new Error("Reconnection is not enabled");this.reconnectionHandler.reconnect()}getReconnectionStatus(){return this.reconnectionHandler?.getStatus()??null}listenerCount(e){return this.eventManager.listenerCount(e)}eventNames(){return this.eventManager.eventNames()}setDebug(e){return this.logger.setEnabled(e),this}setLogLevel(e){return this.logger.setLevel(e),this}getQueueLength(){return this.offlineQueue.length}getQueuedEvents(){return this.offlineQueue.getQueue()}clearQueue(){return this.offlineQueue.clear(),this}setOfflineQueue(e){return this.offlineQueue.setEnabled(e),this}async ping(){return this.connectionMonitor.ping()}getLatency(){return this.connectionMonitor.getAverageLatency()}getConnectionQuality(){return this.connectionMonitor.getQuality()}setConnectionMonitoring(e){return this.connectionMonitor.setEnabled(e),this}join(e,t){return this.roomManager.join(e,t),this}leave(e,t){return this.roomManager.leave(e,t),this}toRoom(e){return this.roomManager.toRoom(e)}getRooms(){return this.roomManager.getRooms()}isInRoom(e){return this.roomManager.isInRoom(e)}}var $,M={exports:{}},j={};var _,C={};
2
+ /**
3
+ * @license React
4
+ * react-jsx-runtime.development.js
5
+ *
6
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
7
+ *
8
+ * This source code is licensed under the MIT license found in the
9
+ * LICENSE file in the root directory of this source tree.
10
+ */"production"===process.env.NODE_ENV?M.exports=function(){if($)return j;$=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function n(t,n,o){var i=null;if(void 0!==o&&(i=""+o),void 0!==n.key&&(i=""+n.key),"key"in n)for(var s in o={},n)"key"!==s&&(o[s]=n[s]);else o=n;return n=o.ref,{$$typeof:e,type:t,key:i,ref:void 0!==n?n:null,props:o}}return j.Fragment=t,j.jsx=n,j.jsxs=n,j}():M.exports=(_||(_=1,"production"!==process.env.NODE_ENV&&function(){function e(t){if(null==t)return null;if("function"==typeof t)return t.$$typeof===x?null:t.displayName||t.name||null;if("string"==typeof t)return t;switch(t){case m:return"Fragment";case p:return"Profiler";case g:return"StrictMode";case b:return"Suspense";case w:return"SuspenseList";case R:return"Activity"}if("object"==typeof t)switch("number"==typeof t.tag&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),t.$$typeof){case f:return"Portal";case k:return t.displayName||"Context";case y:return(t._context.displayName||"Context")+".Consumer";case v:var n=t.render;return(t=t.displayName)||(t=""!==(t=n.displayName||n.name||"")?"ForwardRef("+t+")":"ForwardRef"),t;case E:return null!==(n=t.displayName||null)?n:e(t.type)||"Memo";case S:n=t._payload,t=t._init;try{return e(t(n))}catch(e){}}return null}function n(e){return""+e}function o(e){try{n(e);var t=!1}catch(e){t=!0}if(t){var o=(t=console).error,i="function"==typeof Symbol&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return o.call(t,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",i),n(e)}}function i(t){if(t===m)return"<>";if("object"==typeof t&&null!==t&&t.$$typeof===S)return"<...>";try{var n=e(t);return n?"<"+n+">":"<...>"}catch(e){return"<...>"}}function s(){return Error("react-stack-top-frame")}function r(){var t=e(this.type);return T[t]||(T[t]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),void 0!==(t=this.props.ref)?t:null}function c(t,n,i,s,c,l){var u,f=n.children;if(void 0!==f)if(s)if(j(f)){for(s=0;s<f.length;s++)a(f[s]);Object.freeze&&Object.freeze(f)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else a(f);if(M.call(n,"key")){f=e(t);var m=Object.keys(n).filter(function(e){return"key"!==e});s=0<m.length?"{key: someKey, "+m.join(": ..., ")+": ...}":"{key: someKey}",O[f+s]||(m=0<m.length?"{"+m.join(": ..., ")+": ...}":"{}",console.error('A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />',s,f,m,f),O[f+s]=!0)}if(f=null,void 0!==i&&(o(i),f=""+i),function(e){if(M.call(e,"key")){var t=Object.getOwnPropertyDescriptor(e,"key").get;if(t&&t.isReactWarning)return!1}return void 0!==e.key}(n)&&(o(n.key),f=""+n.key),"key"in n)for(var g in i={},n)"key"!==g&&(i[g]=n[g]);else i=n;return f&&function(e,t){function n(){h||(h=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",t))}n.isReactWarning=!0,Object.defineProperty(e,"key",{get:n,configurable:!0})}(i,"function"==typeof t?t.displayName||t.name||"Unknown":t),function(e,t,n,o,i,s){var c=n.ref;return e={$$typeof:d,type:e,key:t,props:n,_owner:o},null!==(void 0!==c?c:null)?Object.defineProperty(e,"ref",{enumerable:!1,get:r}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:i}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:s}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}(t,f,i,null===(u=$.A)?null:u.getOwner(),c,l)}function a(e){l(e)?e._store&&(e._store.validated=1):"object"==typeof e&&null!==e&&e.$$typeof===S&&("fulfilled"===e._payload.status?l(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function l(e){return"object"==typeof e&&null!==e&&e.$$typeof===d}var h,u=t,d=Symbol.for("react.transitional.element"),f=Symbol.for("react.portal"),m=Symbol.for("react.fragment"),g=Symbol.for("react.strict_mode"),p=Symbol.for("react.profiler"),y=Symbol.for("react.consumer"),k=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),b=Symbol.for("react.suspense"),w=Symbol.for("react.suspense_list"),E=Symbol.for("react.memo"),S=Symbol.for("react.lazy"),R=Symbol.for("react.activity"),x=Symbol.for("react.client.reference"),$=u.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,M=Object.prototype.hasOwnProperty,j=Array.isArray,_=console.createTask?console.createTask:function(){return null},T={},L=(u={react_stack_bottom_frame:function(e){return e()}}).react_stack_bottom_frame.bind(u,s)(),A=_(i(s)),O={};C.Fragment=m,C.jsx=function(e,t,n){var o=1e4>$.recentlyCreatedOwnerStacks++;return c(e,t,n,!1,o?Error("react-stack-top-frame"):L,o?_(i(e)):A)},C.jsxs=function(e,t,n){var o=1e4>$.recentlyCreatedOwnerStacks++;return c(e,t,n,!0,o?Error("react-stack-top-frame"):L,o?_(i(e)):A)}}()),C);var T=M.exports;const L=t.createContext(null),A=()=>{const e=t.useContext(L);if(!e)throw new Error("useHoneydrop must be used within a HoneydropProvider");return e};exports.ConnectionMonitor=d,exports.EventManager=s,exports.Honeydrop=x,exports.HoneydropProvider=({client:e,children:t})=>T.jsx(L.Provider,{value:e,children:t}),exports.Logger=i,exports.NamespacedEvents=a,exports.OfflineQueue=h,exports.ReconnectionHandler=c,exports.RoomManager=m,exports.createDebouncedEmit=S,exports.createThrottledEmit=E,exports.default=x,exports.emitMultiple=g,exports.emitMultipleWithAck=p,exports.emitWithAck=v,exports.getConnectionInfo=w,exports.isConnected=b,exports.useHoneydrop=A,exports.useSocketEvent=function(e,n,o){const i=A(),s=t.useRef(n);t.useEffect(()=>{s.current=n},[n]),t.useEffect(()=>{const t=o?i.namespace(o):i,n=(...e)=>{s.current&&s.current(e[0])};return t.on(e,n),()=>{t.off(e,n)}},[e,o,i])},exports.useSocketStatus=function(){const e=A(),[n,o]=t.useState(e.connected?"connected":"disconnected");return t.useEffect(()=>{const t=()=>o("connected"),n=()=>o("disconnected"),i=()=>o("connecting");return e.on("connect",t),e.on("disconnect",n),e.on("reconnect_attempt",i),()=>{e.off("connect",t),e.off("disconnect",n),e.off("reconnect_attempt",i)}},[e]),n},exports.waitForAnyEvent=k,exports.waitForEvent=y;
2
11
  //# sourceMappingURL=honeydrop.cjs.js.map