@usermetrics/queuebit 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/LICENSE +21 -0
- package/README.md +276 -0
- package/docs/API.md +577 -0
- package/docs/EXAMPLES.md +330 -0
- package/docs/QUICKSTART.md +122 -0
- package/examples/browser-example.html +573 -0
- package/package.json +52 -0
- package/src/client-browser.js +134 -0
- package/src/client-node.js +112 -0
- package/src/client.js +73 -0
- package/src/index.js +7 -0
- package/src/server-runner.js +30 -0
- package/src/server.js +292 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueueBit Browser Client
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* Include socket.io-client in your HTML:
|
|
6
|
+
* <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
|
|
7
|
+
* <script src="client-browser.js"></script>
|
|
8
|
+
*
|
|
9
|
+
* Then use:
|
|
10
|
+
* const client = new QueueBitClient('http://localhost:3000');
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class QueueBitClient {
|
|
14
|
+
constructor(url = 'http://localhost:3000') {
|
|
15
|
+
if (typeof io === 'undefined') {
|
|
16
|
+
throw new Error('Socket.IO client library not loaded. Please include: <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.socket = io(url, {
|
|
20
|
+
transports: ['websocket'],
|
|
21
|
+
upgrade: false,
|
|
22
|
+
reconnection: true,
|
|
23
|
+
reconnectionDelay: 1000,
|
|
24
|
+
reconnectionDelayMax: 5000,
|
|
25
|
+
reconnectionAttempts: Infinity,
|
|
26
|
+
perMessageDeflate: false
|
|
27
|
+
});
|
|
28
|
+
this.messageHandlers = new Map();
|
|
29
|
+
this.connected = false;
|
|
30
|
+
this.serverVersion = null;
|
|
31
|
+
this.receivedMessages = 0;
|
|
32
|
+
|
|
33
|
+
this.socket.on('connect', () => {
|
|
34
|
+
console.log('Connected to QueueBit server');
|
|
35
|
+
this.connected = true;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.socket.on('serverInfo', (info) => {
|
|
39
|
+
this.serverVersion = info.version;
|
|
40
|
+
console.log(`QueueBit Server v${info.version} - Connected at ${new Date(info.timestamp).toLocaleString()}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.socket.on('message', (message) => {
|
|
44
|
+
this.receivedMessages++;
|
|
45
|
+
this.handleMessage(message);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.socket.on('disconnect', () => {
|
|
49
|
+
console.log('Disconnected from QueueBit server');
|
|
50
|
+
this.connected = false;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.socket.on('connect_error', (error) => {
|
|
54
|
+
console.error('Connection error:', error);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
publish(message, options = {}) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
console.log('Publishing message...');
|
|
61
|
+
|
|
62
|
+
const timeout = setTimeout(() => {
|
|
63
|
+
console.error('Publish timeout - no response from server');
|
|
64
|
+
reject(new Error('Publish timeout - no response from server'));
|
|
65
|
+
}, 5000);
|
|
66
|
+
|
|
67
|
+
this.socket.emit('publish', { message, options }, (response) => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
console.log('Received publish response:', response);
|
|
70
|
+
if (!response) {
|
|
71
|
+
console.warn('No response from server');
|
|
72
|
+
resolve({ success: false, error: 'No response from server' });
|
|
73
|
+
} else {
|
|
74
|
+
resolve(response);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
subscribe(callback, options = {}) {
|
|
81
|
+
const subject = options.subject || 'default';
|
|
82
|
+
|
|
83
|
+
if (!this.messageHandlers.has(subject)) {
|
|
84
|
+
this.messageHandlers.set(subject, new Set());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.messageHandlers.get(subject).add(callback);
|
|
88
|
+
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
this.socket.emit('subscribe', options, (response) => {
|
|
91
|
+
resolve(response);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
unsubscribe(options = {}) {
|
|
97
|
+
const subject = options.subject || 'default';
|
|
98
|
+
this.messageHandlers.delete(subject);
|
|
99
|
+
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
this.socket.emit('unsubscribe', options, (response) => {
|
|
102
|
+
resolve(response);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getMessages(options = {}) {
|
|
108
|
+
return new Promise((resolve) => {
|
|
109
|
+
this.socket.emit('getMessages', options, (response) => {
|
|
110
|
+
resolve(response);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
handleMessage(message) {
|
|
116
|
+
const subject = message.subject || 'default';
|
|
117
|
+
const handlers = this.messageHandlers.get(subject);
|
|
118
|
+
|
|
119
|
+
if (handlers) {
|
|
120
|
+
for (const handler of handlers) {
|
|
121
|
+
handler(message);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
disconnect() {
|
|
127
|
+
this.socket.disconnect();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Export for browsers
|
|
132
|
+
if (typeof window !== 'undefined') {
|
|
133
|
+
window.QueueBitClient = QueueBitClient;
|
|
134
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const { io } = require('socket.io-client');
|
|
2
|
+
|
|
3
|
+
class QueueBitClient {
|
|
4
|
+
constructor(url = 'http://localhost:3000') {
|
|
5
|
+
this.socket = io(url, {
|
|
6
|
+
transports: ['websocket'],
|
|
7
|
+
upgrade: false,
|
|
8
|
+
reconnection: true,
|
|
9
|
+
reconnectionDelay: 1000,
|
|
10
|
+
reconnectionDelayMax: 5000,
|
|
11
|
+
reconnectionAttempts: Infinity,
|
|
12
|
+
perMessageDeflate: false
|
|
13
|
+
});
|
|
14
|
+
this.messageHandlers = new Map();
|
|
15
|
+
this.connected = false;
|
|
16
|
+
this.serverVersion = null;
|
|
17
|
+
this.receivedMessages = 0;
|
|
18
|
+
|
|
19
|
+
this.socket.on('connect', () => {
|
|
20
|
+
console.log('Connected to QueueBit server');
|
|
21
|
+
this.connected = true;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
this.socket.on('serverInfo', (info) => {
|
|
25
|
+
this.serverVersion = info.version;
|
|
26
|
+
console.log(`QueueBit Server v${info.version} - Connected at ${new Date(info.timestamp).toLocaleString()}`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.socket.on('message', (message) => {
|
|
30
|
+
this.receivedMessages++;
|
|
31
|
+
this.handleMessage(message);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.socket.on('disconnect', () => {
|
|
35
|
+
console.log('Disconnected from QueueBit server');
|
|
36
|
+
this.connected = false;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.socket.on('connect_error', (error) => {
|
|
40
|
+
console.error('Connection error:', error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
publish(message, options = {}) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const timeout = setTimeout(() => {
|
|
47
|
+
reject(new Error('Publish timeout - no response from server'));
|
|
48
|
+
}, 5000); // 5 second timeout
|
|
49
|
+
|
|
50
|
+
this.socket.emit('publish', { message, options }, (response) => {
|
|
51
|
+
clearTimeout(timeout);
|
|
52
|
+
if (!response) {
|
|
53
|
+
resolve({ success: false, error: 'No response from server' });
|
|
54
|
+
} else {
|
|
55
|
+
resolve(response);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
subscribe(callback, options = {}) {
|
|
62
|
+
const subject = options.subject || 'default';
|
|
63
|
+
|
|
64
|
+
if (!this.messageHandlers.has(subject)) {
|
|
65
|
+
this.messageHandlers.set(subject, new Set());
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.messageHandlers.get(subject).add(callback);
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
this.socket.emit('subscribe', options, (response) => {
|
|
72
|
+
resolve(response);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
unsubscribe(options = {}) {
|
|
78
|
+
const subject = options.subject || 'default';
|
|
79
|
+
this.messageHandlers.delete(subject);
|
|
80
|
+
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
this.socket.emit('unsubscribe', options, (response) => {
|
|
83
|
+
resolve(response);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getMessages(options = {}) {
|
|
89
|
+
return new Promise((resolve) => {
|
|
90
|
+
this.socket.emit('getMessages', options, (response) => {
|
|
91
|
+
resolve(response);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
handleMessage(message) {
|
|
97
|
+
const subject = message.subject || 'default';
|
|
98
|
+
const handlers = this.messageHandlers.get(subject);
|
|
99
|
+
|
|
100
|
+
if (handlers) {
|
|
101
|
+
for (const handler of handlers) {
|
|
102
|
+
handler(message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
disconnect() {
|
|
108
|
+
this.socket.disconnect();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { QueueBitClient };
|
package/src/client.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { io } = require('socket.io-client');
|
|
2
|
+
|
|
3
|
+
class QueueBitClient {
|
|
4
|
+
constructor(url = 'http://localhost:3333') {
|
|
5
|
+
this.socket = io(url);
|
|
6
|
+
this.messageHandlers = new Map();
|
|
7
|
+
|
|
8
|
+
this.socket.on('connect', () => {
|
|
9
|
+
console.log('Connected to QueueBit server');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
this.socket.on('message', (message) => {
|
|
13
|
+
this.handleMessage(message);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
this.socket.on('disconnect', () => {
|
|
17
|
+
console.log('Disconnected from QueueBit server');
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
publish(message, options = {}) {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
this.socket.emit('publish', { message, options }, (response) => {
|
|
24
|
+
resolve(response);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subscribe(callback, options = {}) {
|
|
30
|
+
const subject = options.subject || 'default';
|
|
31
|
+
|
|
32
|
+
if (!this.messageHandlers.has(subject)) {
|
|
33
|
+
this.messageHandlers.set(subject, new Set());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.messageHandlers.get(subject).add(callback);
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
this.socket.emit('subscribe', options, (response) => {
|
|
40
|
+
resolve(response);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
unsubscribe(options = {}) {
|
|
46
|
+
const subject = options.subject || 'default';
|
|
47
|
+
this.messageHandlers.delete(subject);
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
this.socket.emit('unsubscribe', options, (response) => {
|
|
51
|
+
resolve(response);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
handleMessage(message) {
|
|
57
|
+
const subject = message.subject || 'default';
|
|
58
|
+
const handlers = this.messageHandlers.get(subject);
|
|
59
|
+
|
|
60
|
+
if (handlers) {
|
|
61
|
+
for (const handler of handlers) {
|
|
62
|
+
handler(message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
disconnect() {
|
|
68
|
+
this.socket.disconnect();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Node.js client (default export for npm package)
|
|
73
|
+
module.exports = require('./client-node');
|
package/src/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { QueueBitServer } = require('./server');
|
|
2
|
+
|
|
3
|
+
// Parse command line arguments
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const portArg = args.find(arg => arg.startsWith('--port='));
|
|
6
|
+
const maxQueueArg = args.find(arg => arg.startsWith('--max-queue='));
|
|
7
|
+
|
|
8
|
+
const options = {
|
|
9
|
+
port: portArg ? parseInt(portArg.split('=')[1]) : 3333,
|
|
10
|
+
maxQueueSize: maxQueueArg ? parseInt(maxQueueArg.split('=')[1]) : 1000000
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
console.log('Starting QueueBit Server...');
|
|
14
|
+
console.log('Options:', options);
|
|
15
|
+
console.log('');
|
|
16
|
+
|
|
17
|
+
const server = new QueueBitServer(options);
|
|
18
|
+
|
|
19
|
+
// Handle graceful shutdown
|
|
20
|
+
process.on('SIGINT', () => {
|
|
21
|
+
console.log('\nShutting down server...');
|
|
22
|
+
server.close();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
process.on('SIGTERM', () => {
|
|
27
|
+
console.log('\nShutting down server...');
|
|
28
|
+
server.close();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
});
|
package/src/server.js
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
const { Server } = require('socket.io');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
const packageJson = require('../package.json');
|
|
4
|
+
|
|
5
|
+
class QueueBitServer {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
const port = options.port || 3000;
|
|
8
|
+
this.maxQueueSize = options.maxQueueSize || 10000;
|
|
9
|
+
this.version = packageJson.version;
|
|
10
|
+
|
|
11
|
+
this.messages = new Map();
|
|
12
|
+
this.subscribers = new Map();
|
|
13
|
+
this.queueGroups = new Map();
|
|
14
|
+
this.deliveryQueue = [];
|
|
15
|
+
this.deliveryBatchSize = 100;
|
|
16
|
+
this.isDelivering = false;
|
|
17
|
+
|
|
18
|
+
this.io = new Server(port, {
|
|
19
|
+
cors: {
|
|
20
|
+
origin: '*',
|
|
21
|
+
methods: ['GET', 'POST']
|
|
22
|
+
},
|
|
23
|
+
pingTimeout: 60000,
|
|
24
|
+
pingInterval: 25000,
|
|
25
|
+
maxHttpBufferSize: 1e8,
|
|
26
|
+
transports: ['websocket'],
|
|
27
|
+
allowUpgrades: false,
|
|
28
|
+
perMessageDeflate: false,
|
|
29
|
+
httpCompression: false
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.setupHandlers();
|
|
33
|
+
this.startExpiryCheck();
|
|
34
|
+
this.startDeliveryProcessor();
|
|
35
|
+
|
|
36
|
+
console.log(`QueueBit server v${this.version} listening on port ${port}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setupHandlers() {
|
|
40
|
+
this.io.on('connection', (socket) => {
|
|
41
|
+
console.log(`Client connected: ${socket.id}`);
|
|
42
|
+
|
|
43
|
+
socket.emit('serverInfo', {
|
|
44
|
+
version: this.version,
|
|
45
|
+
name: 'QueueBit',
|
|
46
|
+
timestamp: new Date()
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
socket.on('publish', (data, callback) => {
|
|
50
|
+
this.handlePublish(socket, data.message, data.options || {}, callback);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on('subscribe', (options, callback) => {
|
|
54
|
+
this.handleSubscribe(socket, options, callback);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
socket.on('unsubscribe', (options, callback) => {
|
|
58
|
+
this.handleUnsubscribe(socket, options, callback);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
socket.on('getMessages', (options, callback) => {
|
|
62
|
+
this.handleGetMessages(socket, options, callback);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
socket.on('disconnect', () => {
|
|
66
|
+
this.handleDisconnect(socket);
|
|
67
|
+
console.log(`Client disconnected: ${socket.id}`);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
handlePublish(socket, message, options = {}, callback) {
|
|
73
|
+
const subject = options.subject || 'default';
|
|
74
|
+
const queueMessage = {
|
|
75
|
+
id: uuidv4(),
|
|
76
|
+
data: message,
|
|
77
|
+
expiry: options.expiry ? new Date(options.expiry) : undefined,
|
|
78
|
+
removeAfterRead: options.removeAfterRead || false,
|
|
79
|
+
timestamp: new Date(),
|
|
80
|
+
subject
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (!this.messages.has(subject)) {
|
|
84
|
+
this.messages.set(subject, []);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const queue = this.messages.get(subject);
|
|
88
|
+
|
|
89
|
+
if (queue.length >= this.maxQueueSize) {
|
|
90
|
+
if (callback) {
|
|
91
|
+
callback({ success: false, error: 'Queue is full' });
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
queue.push(queueMessage);
|
|
97
|
+
|
|
98
|
+
// Add to delivery queue for batch processing
|
|
99
|
+
this.deliveryQueue.push(queueMessage);
|
|
100
|
+
|
|
101
|
+
// Immediately respond to client
|
|
102
|
+
if (callback) {
|
|
103
|
+
callback({ success: true, messageId: queueMessage.id });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
startDeliveryProcessor() {
|
|
108
|
+
// Process deliveries continuously
|
|
109
|
+
setImmediate(() => this.processDeliveries());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
processDeliveries() {
|
|
113
|
+
if (this.deliveryQueue.length > 0) {
|
|
114
|
+
const batch = this.deliveryQueue.splice(0, this.deliveryBatchSize);
|
|
115
|
+
|
|
116
|
+
for (const message of batch) {
|
|
117
|
+
this.deliverMessage(message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Continue processing
|
|
122
|
+
setImmediate(() => this.processDeliveries());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
deliverMessage(message) {
|
|
126
|
+
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
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Deliver to regular subscribers (all receive)
|
|
149
|
+
const subscribers = this.subscribers.get(subject);
|
|
150
|
+
if (subscribers) {
|
|
151
|
+
for (const socket of subscribers) {
|
|
152
|
+
socket.emit('message', message);
|
|
153
|
+
delivered = true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (message.removeAfterRead && !queueGroups) {
|
|
157
|
+
this.removeMessage(message.id, subject);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
handleSubscribe(socket, options, callback) {
|
|
163
|
+
const subject = options.subject || 'default';
|
|
164
|
+
const queueName = options.queue;
|
|
165
|
+
|
|
166
|
+
if (queueName) {
|
|
167
|
+
// Queue group subscription (load balanced)
|
|
168
|
+
if (!this.queueGroups.has(subject)) {
|
|
169
|
+
this.queueGroups.set(subject, new Map());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const queues = this.queueGroups.get(subject);
|
|
173
|
+
if (!queues.has(queueName)) {
|
|
174
|
+
queues.set(queueName, []);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
queues.get(queueName).push(socket);
|
|
178
|
+
} else {
|
|
179
|
+
// Regular subscription (all subscribers get messages)
|
|
180
|
+
if (!this.subscribers.has(subject)) {
|
|
181
|
+
this.subscribers.set(subject, new Set());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.subscribers.get(subject).add(socket);
|
|
185
|
+
}
|
|
186
|
+
|
|
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);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (callback) {
|
|
196
|
+
callback({ success: true, subject, queue: queueName });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
handleUnsubscribe(socket, options, callback) {
|
|
201
|
+
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);
|
|
210
|
+
if (index > -1) {
|
|
211
|
+
sockets.splice(index, 1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const subscribers = this.subscribers.get(subject);
|
|
217
|
+
if (subscribers) {
|
|
218
|
+
subscribers.delete(socket);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (callback) {
|
|
223
|
+
callback({ success: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
handleDisconnect(socket) {
|
|
228
|
+
// Remove from all subscriptions
|
|
229
|
+
for (const subscribers of this.subscribers.values()) {
|
|
230
|
+
subscribers.delete(socket);
|
|
231
|
+
}
|
|
232
|
+
|
|
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);
|
|
237
|
+
if (index > -1) {
|
|
238
|
+
sockets.splice(index, 1);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
handleGetMessages(socket, options, callback) {
|
|
245
|
+
const subject = options.subject || 'default';
|
|
246
|
+
const messages = this.messages.get(subject) || [];
|
|
247
|
+
|
|
248
|
+
if (callback) {
|
|
249
|
+
callback({
|
|
250
|
+
success: true,
|
|
251
|
+
messages: messages.map(msg => ({
|
|
252
|
+
id: msg.id,
|
|
253
|
+
data: msg.data,
|
|
254
|
+
subject: msg.subject,
|
|
255
|
+
timestamp: msg.timestamp,
|
|
256
|
+
expiry: msg.expiry,
|
|
257
|
+
removeAfterRead: msg.removeAfterRead
|
|
258
|
+
})),
|
|
259
|
+
count: messages.length
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
removeMessage(messageId, subject) {
|
|
265
|
+
const queue = this.messages.get(subject);
|
|
266
|
+
if (queue) {
|
|
267
|
+
const index = queue.findIndex(m => m.id === messageId);
|
|
268
|
+
if (index > -1) {
|
|
269
|
+
queue.splice(index, 1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
startExpiryCheck() {
|
|
275
|
+
setInterval(() => {
|
|
276
|
+
const now = new Date();
|
|
277
|
+
|
|
278
|
+
for (const [subject, queue] of this.messages.entries()) {
|
|
279
|
+
this.messages.set(
|
|
280
|
+
subject,
|
|
281
|
+
queue.filter(msg => !msg.expiry || msg.expiry > now)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}, 1000); // Check every second
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
close() {
|
|
288
|
+
this.io.close();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = { QueueBitServer };
|