@usermetrics/queuebit 1.0.0 → 1.0.1

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,15 +1,16 @@
1
1
  # QueueBit
2
2
 
3
- A high performance socket-based message queue server with guaranteed delivery, compatible with NATS queue patterns.
3
+ A high performance socket-based message queue server with guaranteed delivery, compatible with NATS queue patterns.
4
+ Built in Load Balancer. (see examples).
4
5
 
5
- It can run in-process in an existing nodejs app, separately as a nodejs server, and has clients
6
- for the backend and frontend.
6
+ It can run in-process in an existing nodejs app, separately as a nodejs server, or run clients
7
+ in the backend and/or frontend.
7
8
 
8
9
  ## Features
9
10
 
10
11
  - WebSocket-based message queue
11
12
  - Subject-based message routing
12
- - Queue groups for load-balanced message delivery
13
+ - Load-balancer
13
14
  - Message expiry support
14
15
  - Remove after read (ephemeral messages)
15
16
  - Guaranteed delivery to all subscribers
@@ -82,7 +83,8 @@ Include Socket.IO and QueueBit client in your HTML:
82
83
  </script>
83
84
  ```
84
85
 
85
- See `examples/browser-example.html` for a complete browser example.
86
+ See `examples/qpanel.html` for a complete browser example.
87
+ Open it with Live Server in vscode to test.
86
88
 
87
89
  ### Server
88
90
 
@@ -185,89 +187,6 @@ Unsubscribe from messages.
185
187
  ##### `disconnect()`
186
188
  Disconnect from the server.
187
189
 
188
- ## Publishing to NPM
189
-
190
- ### First Time Setup
191
-
192
- 1. Create an NPM account at https://www.npmjs.com/signup
193
- 2. Run authentication setup:
194
- ```cmd
195
- setup-npm-auth.cmd
196
- ```
197
- 3. Update package.json with your username:
198
- - Change `@yourusername/queuebit` to `@YOUR_NPM_USERNAME/queuebit`
199
- - Or use an unscoped name like `queuebit-yourname` if available
200
- - Update author field with your information
201
-
202
- ### Publishing
203
-
204
- 1. Update version number:
205
- ```cmd
206
- update-version.cmd
207
- ```
208
-
209
- 2. **Without 2FA:**
210
- ```cmd
211
- publish.cmd
212
- ```
213
-
214
- 3. **With 2FA enabled:**
215
- ```cmd
216
- publish-with-otp.cmd
217
- ```
218
-
219
- ### Troubleshooting
220
-
221
- - **403 Forbidden / 2FA Required**: Use `publish-with-otp.cmd`
222
- - **Package name taken**: Change name in package.json to something unique
223
- - **Not logged in**: Run `npm login` or `setup-npm-auth.cmd`
224
- - **Version already exists**: Increment version with `update-version.cmd`
225
-
226
- ## Development
227
-
228
- ### Install Dependencies
229
- ```bash
230
- npm install
231
- ```
232
- Or on Windows:
233
- ```cmd
234
- install-deps.cmd
235
- ```
236
-
237
- ### Run Tests
238
- ```bash
239
- npm test
240
- ```
241
-
242
- ### Publishing
243
-
244
- #### Update Version
245
- ```cmd
246
- update-version.cmd
247
- ```
248
-
249
- #### Dry Run (test without publishing)
250
- ```cmd
251
- publish-dry-run.cmd
252
- ```
253
-
254
- #### Publish to NPM
255
- ```cmd
256
- publish.cmd
257
- ```
258
-
259
- Or manually:
260
- ```bash
261
- npm login
262
- npm test
263
- npm publish
264
- ```
265
-
266
- ## Testing
267
-
268
- ```bash
269
- npm test
270
- ```
271
190
 
272
191
  ## License
273
192
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * In-process QueueBit demo.
3
+ * Starts the QueueBit server and an HTTP server in the same process.
4
+ * Use the qpanel.html dashboard to publish messages to the queue and see them received by the HTTP server.
5
+ * Run this example with /examples/start_node_inprocess.cmd
6
+ * No need to run a separate QueueBit server.
7
+ */
8
+ const http = require('http');
9
+ const { QueueBitServer } = require('../src/server');
10
+ const { QueueBitClient } = require('../src/client-node');
11
+
12
+ const webserverPORT = 3000;
13
+ const queuebitPORT = 3333;
14
+
15
+ // Start the QueueBit server in-process (constructor starts listening immediately)
16
+ const queuebitServer = new QueueBitServer({ port: queuebitPORT });
17
+
18
+ // Connect a client to the in-process QueueBit server
19
+ // this is just for testing. typically you would connect from another process/frontend/server
20
+ const messageQueue = new QueueBitClient(`http://localhost:${queuebitPORT}`);
21
+
22
+ messageQueue.subscribe((msg) => {
23
+ console.log('Received message from queue:', msg);
24
+ }, { subject: 'default' });
25
+
26
+ // Create an HTTP server
27
+ const server = http.createServer((req, res) => {
28
+ res.setHeader('Content-Type', 'application/json');
29
+
30
+ if (req.method === 'POST' && req.url === '/enqueue') {
31
+ let body = '';
32
+ req.on('data', chunk => { body += chunk.toString(); });
33
+ req.on('end', async () => {
34
+ try {
35
+ const message = JSON.parse(body);
36
+ const result = await messageQueue.publish(message);
37
+ res.writeHead(200);
38
+ res.end(JSON.stringify({ success: true, result }));
39
+ } catch (error) {
40
+ res.writeHead(400);
41
+ res.end(JSON.stringify({ error: error.message }));
42
+ }
43
+ });
44
+ } else if (req.method === 'GET' && req.url === '/messages') {
45
+ messageQueue.getMessages({ subject: 'default' }).then((result) => {
46
+ res.writeHead(200);
47
+ res.end(JSON.stringify(result));
48
+ });
49
+ } else {
50
+ res.writeHead(404);
51
+ res.end(JSON.stringify({ error: 'Not found' }));
52
+ }
53
+ });
54
+
55
+ server.listen(webserverPORT, () => {
56
+ console.log(`HTTP server running at http://localhost:${webserverPORT}`);
57
+ console.log('Endpoints:');
58
+ console.log(' POST /enqueue - Publish message to QueueBit');
59
+ console.log(' GET /messages - Get messages from QueueBit');
60
+ });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Load Balancer demo - load balanced message delivery.
3
+ * Messages are distributed round-robin across workers in the same load balancer.
4
+ * Only ONE worker receives each message (unlike regular subscribe where ALL receive).
5
+ *
6
+ * Run this example with: node examples/queuegroup.js
7
+ */
8
+ const { QueueBitServer } = require('../src/server');
9
+ const { QueueBitClient } = require('../src/client-node');
10
+
11
+ const PORT = 3333;
12
+
13
+ // Start QueueBit server in-process
14
+ new QueueBitServer({ port: PORT });
15
+
16
+ async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
17
+
18
+ async function main() {
19
+ await sleep(500); // wait for server to start
20
+
21
+ // Create 3 worker clients - all in the same load balancer 'workers' on subject 'jobs'
22
+ const worker1 = new QueueBitClient(`http://localhost:${PORT}`);
23
+ const worker2 = new QueueBitClient(`http://localhost:${PORT}`);
24
+ const worker3 = new QueueBitClient(`http://localhost:${PORT}`);
25
+
26
+ await sleep(500); // wait for clients to connect
27
+
28
+ await worker1.subscribe((msg) => {
29
+ console.log(`Worker 1 received:`, msg.data);
30
+ }, { subject: 'jobs', queue: 'workers' });
31
+
32
+ await worker2.subscribe((msg) => {
33
+ console.log(`Worker 2 received:`, msg.data);
34
+ }, { subject: 'jobs', queue: 'workers' });
35
+
36
+ await worker3.subscribe((msg) => {
37
+ console.log(`Worker 3 received:`, msg.data);
38
+ }, { subject: 'jobs', queue: 'workers' });
39
+
40
+ // Publisher client
41
+ const publisher = new QueueBitClient(`http://localhost:${PORT}`);
42
+ await sleep(500);
43
+
44
+ console.log('\nPublishing 6 jobs - each worker should receive 2 (round-robin):\n');
45
+
46
+ for (let i = 1; i <= 6; i++) {
47
+ await publisher.publish({ job: `task-${i}`, payload: `data-${i}` }, { subject: 'jobs' });
48
+ await sleep(100);
49
+ }
50
+
51
+ await sleep(500);
52
+ console.log('\nDone. Each worker received ~2 messages.');
53
+ process.exit(0);
54
+ }
55
+
56
+ main();
@@ -124,7 +124,7 @@
124
124
  <h2>Subscribe</h2>
125
125
  <input type="text" id="subscribeSubject" placeholder="Subject" class="small">
126
126
  <button onclick="subscribe()">Subscribe</button>
127
- <button onclick="subscribeQueue()">Queue Group</button>
127
+ <button onclick="subscribeQueue()">Add Load Balancer</button>
128
128
  </div>
129
129
 
130
130
  <div class="col container">
@@ -162,6 +162,7 @@
162
162
  let lastUpdateTime = 0;
163
163
  let publishEndTime = 0;
164
164
  let isConnected = false;
165
+ let lbSubscriptionCount = 0; // track how many LB subscriptions added
165
166
 
166
167
  function connect() {
167
168
  const url = document.getElementById('serverUrl').value;
@@ -194,6 +195,7 @@
194
195
  client.disconnect();
195
196
  client = null;
196
197
  isConnected = false;
198
+ lbSubscriptionCount = 0;
197
199
  document.getElementById('status').textContent = 'Disconnected';
198
200
  document.getElementById('status').style.color = 'black';
199
201
  document.getElementById('serverVersion').textContent = '';
@@ -270,14 +272,25 @@
270
272
  }
271
273
 
272
274
  const subject = document.getElementById('subscribeSubject').value || 'default';
275
+ lbSubscriptionCount++;
276
+ const workerNum = lbSubscriptionCount;
277
+ const uniqueQueueName = `browser-worker-${workerNum}`; // unique name per worker
273
278
 
274
- await client.subscribe((message) => {
279
+ const response = await client.subscribe((message) => {
275
280
  if (!perfTestRunning) {
276
- addMessage(`[Queue: ${subject}] ${JSON.stringify(message.data)}`);
281
+ console.log('LB message received:', JSON.stringify({
282
+ queueName: message.queueName,
283
+ loadBalancerId: message.loadBalancerId,
284
+ subject: message.subject
285
+ }));
286
+ const lbId = message.loadBalancerId !== undefined ? ` LB#${message.loadBalancerId}` : '';
287
+ addMessage(`[Worker #${workerNum}${lbId}: ${subject}] ${JSON.stringify(message.data)}`);
277
288
  }
278
- }, { subject, queue: 'browser-workers' });
289
+ }, { subject, queue: uniqueQueueName });
279
290
 
280
- console.log('Subscribed to queue group:', subject);
291
+ const lbId = response?.loadBalancerId !== undefined ? ` (LB#${response.loadBalancerId})` : '';
292
+ addMessage(`✓ Load Balancer Worker #${workerNum}${lbId} added for subject "${subject}"`);
293
+ console.log(`Worker #${workerNum} subscribed to load balancer: ${subject} (queue: ${uniqueQueueName})`);
281
294
  }
282
295
 
283
296
  function addMessage(text, isPerf = false) {
@@ -350,12 +363,6 @@
350
363
  const totalMessages = parseInt(document.getElementById('testMessageCount').value);
351
364
  console.log('totalMessages:', totalMessages);
352
365
 
353
- const confirmed = confirm(`This will send ${totalMessages.toLocaleString()} messages to the queue. Continue?`);
354
- if (!confirmed) {
355
- console.log('Test cancelled by user');
356
- return;
357
- }
358
-
359
366
  console.log('Starting performance test...');
360
367
  perfTestRunning = true;
361
368
  receivedCount = 0;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Server to server queue.
3
+ * This adds queue functionality to a simple http server (the queue server is running elsewhere)
4
+ * Use the qpanel.html dashboard to publish messages to the queue and see them received by the HTTP server.
5
+ * run the server with /start_server.cmd and then run this example with /examples/start_node_client.cmd
6
+ */
7
+ const http = require('http');
8
+ const { QueueBitClient } = require('../src/client-node');
9
+
10
+ const webserverPORT = 3000;
11
+ const queuebitPORT = 3333;
12
+
13
+ // Initialize a queuebit message queue. It will connect to the server running on port 3333
14
+ const messageQueue = new QueueBitClient(`http://localhost:${queuebitPORT}`);
15
+
16
+ // Create an HTTP server
17
+ const server = http.createServer((req, res) => {
18
+ res.setHeader('Content-Type', 'application/json');
19
+
20
+ if (req.method === 'POST' && req.url === '/enqueue') {
21
+ let body = '';
22
+ req.on('data', chunk => {
23
+ body += chunk.toString();
24
+ });
25
+
26
+ req.on('end', () => {
27
+ try {
28
+ const message = JSON.parse(body);
29
+ messageQueue.enqueue(message);
30
+ res.writeHead(200);
31
+ res.end(JSON.stringify({ success: true, message: 'Message enqueued' }));
32
+ } catch (error) {
33
+ res.writeHead(400);
34
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
35
+ }
36
+ });
37
+ } else if (req.method === 'GET' && req.url === '/dequeue') {
38
+ const message = messageQueue.dequeue();
39
+ res.writeHead(200);
40
+ res.end(JSON.stringify({ message: message || 'Queue is empty' }));
41
+ } else if (req.method === 'GET' && req.url === '/size') {
42
+ res.writeHead(200);
43
+ res.end(JSON.stringify({ size: messageQueue.size() }));
44
+ } else {
45
+ res.writeHead(404);
46
+ res.end(JSON.stringify({ error: 'Not found' }));
47
+ }
48
+ });
49
+
50
+ server.listen(webserverPORT, () => {
51
+ console.log(`Server running at http://localhost:${webserverPORT}`);
52
+ console.log('Endpoints:');
53
+ console.log(' POST /enqueue - Add message to queue');
54
+ console.log(' GET /dequeue - Remove and return message from queue');
55
+ console.log(' GET /size - Get current queue size');
56
+ });
@@ -0,0 +1 @@
1
+ node ./loadbalancer.js
@@ -0,0 +1 @@
1
+ node ./server2server.js
@@ -0,0 +1 @@
1
+ call node inprocessserver.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usermetrics/queuebit",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,8 +9,9 @@
9
9
  "browser": "src/client-browser.js",
10
10
  "scripts": {
11
11
  "test": "node test/test-harness.js",
12
- "start": "node src/index.js",
12
+ "start": "node src/server-runner.js",
13
13
  "server": "node src/server-runner.js",
14
+ "auto": "nodemon src/server-runner.js",
14
15
  "prepublishOnly": "npm test"
15
16
  },
16
17
  "keywords": [
@@ -79,12 +79,14 @@ class QueueBitClient {
79
79
 
80
80
  subscribe(callback, options = {}) {
81
81
  const subject = options.subject || 'default';
82
+ const queueName = options.queue || null;
83
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
82
84
 
83
- if (!this.messageHandlers.has(subject)) {
84
- this.messageHandlers.set(subject, new Set());
85
+ if (!this.messageHandlers.has(handlerKey)) {
86
+ this.messageHandlers.set(handlerKey, new Set());
85
87
  }
86
88
 
87
- this.messageHandlers.get(subject).add(callback);
89
+ this.messageHandlers.get(handlerKey).add(callback);
88
90
 
89
91
  return new Promise((resolve) => {
90
92
  this.socket.emit('subscribe', options, (response) => {
@@ -95,7 +97,9 @@ class QueueBitClient {
95
97
 
96
98
  unsubscribe(options = {}) {
97
99
  const subject = options.subject || 'default';
98
- this.messageHandlers.delete(subject);
100
+ const queueName = options.queue || null;
101
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
102
+ this.messageHandlers.delete(handlerKey);
99
103
 
100
104
  return new Promise((resolve) => {
101
105
  this.socket.emit('unsubscribe', options, (response) => {
@@ -114,8 +118,10 @@ class QueueBitClient {
114
118
 
115
119
  handleMessage(message) {
116
120
  const subject = message.subject || 'default';
117
- const handlers = this.messageHandlers.get(subject);
118
-
121
+ const queueName = message.queueName || null;
122
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
123
+
124
+ const handlers = this.messageHandlers.get(handlerKey);
119
125
  if (handlers) {
120
126
  for (const handler of handlers) {
121
127
  handler(message);
@@ -1,7 +1,7 @@
1
1
  const { io } = require('socket.io-client');
2
2
 
3
3
  class QueueBitClient {
4
- constructor(url = 'http://localhost:3000') {
4
+ constructor(url = 'http://localhost:3333') {
5
5
  this.socket = io(url, {
6
6
  transports: ['websocket'],
7
7
  upgrade: false,
@@ -60,12 +60,14 @@ class QueueBitClient {
60
60
 
61
61
  subscribe(callback, options = {}) {
62
62
  const subject = options.subject || 'default';
63
+ const queueName = options.queue || null;
64
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
63
65
 
64
- if (!this.messageHandlers.has(subject)) {
65
- this.messageHandlers.set(subject, new Set());
66
+ if (!this.messageHandlers.has(handlerKey)) {
67
+ this.messageHandlers.set(handlerKey, new Set());
66
68
  }
67
69
 
68
- this.messageHandlers.get(subject).add(callback);
70
+ this.messageHandlers.get(handlerKey).add(callback);
69
71
 
70
72
  return new Promise((resolve) => {
71
73
  this.socket.emit('subscribe', options, (response) => {
@@ -76,7 +78,9 @@ class QueueBitClient {
76
78
 
77
79
  unsubscribe(options = {}) {
78
80
  const subject = options.subject || 'default';
79
- this.messageHandlers.delete(subject);
81
+ const queueName = options.queue || null;
82
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
83
+ this.messageHandlers.delete(handlerKey);
80
84
 
81
85
  return new Promise((resolve) => {
82
86
  this.socket.emit('unsubscribe', options, (response) => {
@@ -95,8 +99,10 @@ class QueueBitClient {
95
99
 
96
100
  handleMessage(message) {
97
101
  const subject = message.subject || 'default';
98
- const handlers = this.messageHandlers.get(subject);
99
-
102
+ const queueName = message.queueName || null;
103
+ const handlerKey = queueName ? `${subject}:${queueName}` : subject;
104
+
105
+ const handlers = this.messageHandlers.get(handlerKey);
100
106
  if (handlers) {
101
107
  for (const handler of handlers) {
102
108
  handler(message);
@@ -109,4 +115,4 @@ class QueueBitClient {
109
115
  }
110
116
  }
111
117
 
112
- module.exports = { QueueBitClient };
118
+ module.exports = { QueueBitClient, Queue: QueueBitClient };
package/src/server.js CHANGED
@@ -10,7 +10,8 @@ class QueueBitServer {
10
10
 
11
11
  this.messages = new Map();
12
12
  this.subscribers = new Map();
13
- this.queueGroups = new Map();
13
+ this.loadBalancers = new Map();
14
+ this.loadBalancerIdCounter = 0;
14
15
  this.deliveryQueue = [];
15
16
  this.deliveryBatchSize = 100;
16
17
  this.isDelivering = false;
@@ -124,36 +125,42 @@ class QueueBitServer {
124
125
 
125
126
  deliverMessage(message) {
126
127
  const subject = message.subject || 'default';
127
- let delivered = false;
128
-
129
- // Deliver to queue groups (load balanced)
130
- const queueGroups = this.queueGroups.get(subject);
131
- if (queueGroups) {
132
- for (const [queueName, sockets] of queueGroups.entries()) {
133
- if (sockets.length > 0) {
134
- const socket = sockets[0];
135
- sockets.push(sockets.shift());
136
-
137
- socket.emit('message', message);
138
- delivered = true;
139
-
140
- if (message.removeAfterRead) {
141
- this.removeMessage(message.id, subject);
142
- return;
143
- }
128
+
129
+ const loadBalancers = this.loadBalancers.get(subject);
130
+ if (loadBalancers && loadBalancers.size > 0) {
131
+ const activeLBs = [];
132
+ for (const [lbName, lb] of loadBalancers.entries()) {
133
+ if (lb.sockets.length > 0) {
134
+ activeLBs.push(lb);
144
135
  }
145
136
  }
137
+
138
+ if (activeLBs.length > 0) {
139
+ if (!this._lbRoundRobinIndex) this._lbRoundRobinIndex = {};
140
+ if (this._lbRoundRobinIndex[subject] === undefined) this._lbRoundRobinIndex[subject] = 0;
141
+
142
+ const idx = this._lbRoundRobinIndex[subject] % activeLBs.length;
143
+ this._lbRoundRobinIndex[subject]++;
144
+
145
+ const lb = activeLBs[idx];
146
+ const socket = lb.sockets[0];
147
+ lb.sockets.push(lb.sockets.shift()); // rotate within LB
148
+
149
+ socket.emit('message', { ...message, loadBalancerId: lb.id, queueName: lb.name });
150
+
151
+ // Always remove from store after LB delivery - LB messages are consumed, not persistent
152
+ this.removeMessage(message.id, subject);
153
+ return;
154
+ }
146
155
  }
147
156
 
148
- // Deliver to regular subscribers (all receive)
157
+ // Only deliver to regular subscribers if no load balancer handled it
149
158
  const subscribers = this.subscribers.get(subject);
150
159
  if (subscribers) {
151
160
  for (const socket of subscribers) {
152
161
  socket.emit('message', message);
153
- delivered = true;
154
162
  }
155
-
156
- if (message.removeAfterRead && !queueGroups) {
163
+ if (message.removeAfterRead) {
157
164
  this.removeMessage(message.id, subject);
158
165
  }
159
166
  }
@@ -161,20 +168,20 @@ class QueueBitServer {
161
168
 
162
169
  handleSubscribe(socket, options, callback) {
163
170
  const subject = options.subject || 'default';
164
- const queueName = options.queue;
171
+ const lbName = options.queue;
165
172
 
166
- if (queueName) {
167
- // Queue group subscription (load balanced)
168
- if (!this.queueGroups.has(subject)) {
169
- this.queueGroups.set(subject, new Map());
173
+ if (lbName) {
174
+ if (!this.loadBalancers.has(subject)) {
175
+ this.loadBalancers.set(subject, new Map());
170
176
  }
171
177
 
172
- const queues = this.queueGroups.get(subject);
173
- if (!queues.has(queueName)) {
174
- queues.set(queueName, []);
178
+ const lbs = this.loadBalancers.get(subject);
179
+ if (!lbs.has(lbName)) {
180
+ lbs.set(lbName, { id: ++this.loadBalancerIdCounter, name: lbName, sockets: [] });
175
181
  }
176
182
 
177
- queues.get(queueName).push(socket);
183
+ lbs.get(lbName).sockets.push(socket);
184
+ // Do NOT replay existing messages to load balancer subscribers
178
185
  } else {
179
186
  // Regular subscription (all subscribers get messages)
180
187
  if (!this.subscribers.has(subject)) {
@@ -182,33 +189,38 @@ class QueueBitServer {
182
189
  }
183
190
 
184
191
  this.subscribers.get(subject).add(socket);
185
- }
186
192
 
187
- // Deliver any existing messages
188
- const messages = this.messages.get(subject) || [];
189
- for (const message of messages) {
190
- if (!message.removeAfterRead) {
191
- socket.emit('message', message);
193
+ // Replay existing messages only for regular subscribers
194
+ const messages = this.messages.get(subject) || [];
195
+ for (const message of messages) {
196
+ if (!message.removeAfterRead) {
197
+ socket.emit('message', message);
198
+ }
192
199
  }
193
200
  }
194
201
 
195
202
  if (callback) {
196
- callback({ success: true, subject, queue: queueName });
203
+ callback({
204
+ success: true,
205
+ subject,
206
+ loadBalancer: lbName,
207
+ loadBalancerId: lbName ? this.loadBalancers.get(subject)?.get(lbName)?.id : undefined
208
+ });
197
209
  }
198
210
  }
199
211
 
200
212
  handleUnsubscribe(socket, options, callback) {
201
213
  const subject = options.subject || 'default';
202
- const queueName = options.queue;
203
-
204
- if (queueName) {
205
- const queues = this.queueGroups.get(subject);
206
- if (queues) {
207
- const sockets = queues.get(queueName);
208
- if (sockets) {
209
- const index = sockets.indexOf(socket);
214
+ const lbName = options.queue;
215
+
216
+ if (lbName) {
217
+ const lbs = this.loadBalancers.get(subject);
218
+ if (lbs) {
219
+ const lb = lbs.get(lbName);
220
+ if (lb) {
221
+ const index = lb.sockets.indexOf(socket);
210
222
  if (index > -1) {
211
- sockets.splice(index, 1);
223
+ lb.sockets.splice(index, 1);
212
224
  }
213
225
  }
214
226
  }
@@ -230,12 +242,12 @@ class QueueBitServer {
230
242
  subscribers.delete(socket);
231
243
  }
232
244
 
233
- // Remove from all queue groups
234
- for (const queues of this.queueGroups.values()) {
235
- for (const sockets of queues.values()) {
236
- const index = sockets.indexOf(socket);
245
+ // Remove from all load balancers
246
+ for (const lbs of this.loadBalancers.values()) {
247
+ for (const lb of lbs.values()) {
248
+ const index = lb.sockets.indexOf(socket);
237
249
  if (index > -1) {
238
- sockets.splice(index, 1);
250
+ lb.sockets.splice(index, 1);
239
251
  }
240
252
  }
241
253
  }