@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.
@@ -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,7 @@
1
+ const { QueueBitServer } = require('./server');
2
+ const { QueueBitClient } = require('./client');
3
+
4
+ module.exports = {
5
+ QueueBitServer,
6
+ QueueBitClient
7
+ };
@@ -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 };