roster-server 1.9.0 β†’ 1.9.4

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
@@ -166,6 +166,8 @@ When creating a new `RosterServer` instance, you can pass the following options:
166
166
  - `greenlockStorePath` (string): Directory for Greenlock configuration.
167
167
  - `staging` (boolean): Set to `true` to use Let's Encrypt's staging environment (for testing).
168
168
  - `local` (boolean): Set to `true` to run in local development mode.
169
+ - `minLocalPort` (number): Minimum port for local mode (default: 4000).
170
+ - `maxLocalPort` (number): Maximum port for local mode (default: 9999).
169
171
 
170
172
  ## 🏠 Local Development Mode
171
173
 
@@ -178,19 +180,84 @@ When `{ local: true }` is enabled, RosterServer **Skips SSL/HTTPS**: Runs pure H
178
180
  ```javascript
179
181
  const server = new Roster({
180
182
  wwwPath: '/srv/www',
181
- local: true // Enable local development mode
183
+ local: true, // Enable local development mode
184
+ minLocalPort: 4000, // Optional: minimum port (default: 4000)
185
+ maxLocalPort: 9999 // Optional: maximum port (default: 9999)
182
186
  });
183
187
  server.start();
184
188
  ```
185
189
 
186
190
  ### Port Assignment
187
191
 
188
- In local mode, domains are automatically assigned ports starting from 3000:
192
+ In local mode, domains are automatically assigned ports based on a CRC32 hash of the domain name (default range 4000-9999, configurable via `minLocalPort` and `maxLocalPort`):
189
193
 
190
- - `example.com` β†’ `http://localhost:3000`
191
- - `api.example.com` β†’ `http://localhost:3001`
194
+ - `example.com` β†’ `http://localhost:9465`
195
+ - `api.example.com` β†’ `http://localhost:9388`
192
196
  - And so on...
193
197
 
198
+ You can customize the port range:
199
+
200
+ ```javascript
201
+ const roster = new Roster({
202
+ local: true,
203
+ minLocalPort: 5000, // Start from port 5000
204
+ maxLocalPort: 6000 // Up to port 6000
205
+ });
206
+ ```
207
+
208
+ ### Getting Local URLs
209
+
210
+ RosterServer provides two methods to get the local URL for a domain:
211
+
212
+ **1. Static Method (Predictable, No Instance Required):**
213
+
214
+ ```javascript
215
+ // Get the URL before starting the server (using default range 4000-9999)
216
+ const url = Roster.getLocalUrl('example.com');
217
+ console.log(url); // http://localhost:9465
218
+
219
+ // Or specify custom port range
220
+ const customUrl = Roster.getLocalUrl('example.com', {
221
+ minLocalPort: 5000,
222
+ maxLocalPort: 6000
223
+ });
224
+ ```
225
+
226
+ This static method calculates the port deterministically using CRC32, so you can predict the URL before even creating a Roster instance.
227
+
228
+ **2. Instance Method (After Registration):**
229
+
230
+ ```javascript
231
+ const roster = new Roster({ local: true });
232
+ roster.register('example.com', handler);
233
+
234
+ await roster.start();
235
+
236
+ // Get the actual URL assigned to the domain
237
+ const url = roster.getLocalUrl('example.com');
238
+ console.log(url); // http://localhost:9465
239
+ ```
240
+
241
+ This instance method returns the actual URL assigned to the domain after the server starts. It's useful when you need to confirm the URL or when there might be port collisions.
242
+
243
+ **Example Usage:**
244
+
245
+ ```javascript
246
+ const roster = new Roster({ local: true });
247
+
248
+ roster.register('example.com', (httpsServer) => {
249
+ return (req, res) => {
250
+ res.writeHead(200);
251
+ res.end('Hello World!');
252
+ };
253
+ });
254
+
255
+ roster.start().then(() => {
256
+ const url = roster.getLocalUrl('example.com');
257
+ console.log(`Server available at: ${url}`);
258
+ });
259
+ ```
260
+
194
261
  ## πŸ§‚ A Touch of Magic
195
262
 
196
263
  You might be thinking, "But setting up HTTPS and virtual hosts is supposed to be complicated and time-consuming!" Well, not anymore. With RosterServer, you can get back to writing code that matters, like defending Earth from alien invaders! πŸ‘ΎπŸ‘ΎπŸ‘Ύ
@@ -0,0 +1,40 @@
1
+ const Roster = require('../index.js');
2
+
3
+ // Example with custom port range
4
+ const roster = new Roster({
5
+ local: true,
6
+ minLocalPort: 5000, // Custom minimum port
7
+ maxLocalPort: 5100 // Custom maximum port
8
+ });
9
+
10
+ console.log('\nπŸ“ Static URL Prediction with custom range (5000-5100):');
11
+ console.log('example.com β†’', Roster.getLocalUrl('example.com', {
12
+ minLocalPort: 5000,
13
+ maxLocalPort: 5100
14
+ }));
15
+ console.log('api.example.com β†’', Roster.getLocalUrl('api.example.com', {
16
+ minLocalPort: 5000,
17
+ maxLocalPort: 5100
18
+ }));
19
+
20
+ roster.register('example.com', (httpsServer) => {
21
+ return (req, res) => {
22
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
23
+ res.end('Hello from example.com with custom port range!');
24
+ };
25
+ });
26
+
27
+ roster.register('api.example.com', (httpsServer) => {
28
+ return (req, res) => {
29
+ res.writeHead(200, { 'Content-Type': 'application/json' });
30
+ res.end(JSON.stringify({ message: 'API with custom port range' }));
31
+ };
32
+ });
33
+
34
+ roster.start().then(() => {
35
+ console.log('\nπŸš€ Server Started with custom port range:');
36
+ console.log('example.com β†’', roster.getLocalUrl('example.com'));
37
+ console.log('api.example.com β†’', roster.getLocalUrl('api.example.com'));
38
+
39
+ console.log('\nβœ… Both domains running in custom port range (5000-5100)!');
40
+ });
@@ -0,0 +1,40 @@
1
+ const Roster = require('../index.js');
2
+
3
+ // Example 1: Get URL before creating instance (static method)
4
+ console.log('\nπŸ“ Static URL Prediction (before server starts):');
5
+ console.log('example.com β†’', Roster.getLocalUrl('example.com'));
6
+ console.log('api.example.com β†’', Roster.getLocalUrl('api.example.com'));
7
+ console.log('test.example.com β†’', Roster.getLocalUrl('test.example.com'));
8
+
9
+ // Example 2: Get URL after registration (instance method)
10
+ const roster = new Roster({ local: true });
11
+
12
+ roster.register('example.com', (httpsServer) => {
13
+ return (req, res) => {
14
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
15
+ res.end('Hello from example.com!');
16
+ };
17
+ });
18
+
19
+ roster.register('api.example.com', (httpsServer) => {
20
+ return (req, res) => {
21
+ res.writeHead(200, { 'Content-Type': 'application/json' });
22
+ res.end(JSON.stringify({ message: 'API endpoint' }));
23
+ };
24
+ });
25
+
26
+ roster.start().then(() => {
27
+ console.log('\nπŸš€ Server Started - Actual URLs:');
28
+ console.log('example.com β†’', roster.getLocalUrl('example.com'));
29
+ console.log('api.example.com β†’', roster.getLocalUrl('api.example.com'));
30
+
31
+ // Test with www prefix (should return same URL)
32
+ console.log('\nπŸ”„ Testing www prefix handling:');
33
+ console.log('www.example.com β†’', roster.getLocalUrl('www.example.com'));
34
+
35
+ // Test non-existent domain
36
+ console.log('\n❌ Testing non-existent domain:');
37
+ console.log('nonexistent.com β†’', roster.getLocalUrl('nonexistent.com') || 'null (domain not registered)');
38
+
39
+ console.log('\nβœ… All domains running!');
40
+ });
package/demo/socketio.js CHANGED
@@ -26,4 +26,12 @@ roster.register('example.com', (httpsServer) => {
26
26
  };
27
27
  });
28
28
 
29
- roster.start();
29
+ roster.start().then(() => {
30
+ // Get local URL for registered domain (requires instance)
31
+ const url = roster.getLocalUrl('example.com');
32
+ console.log(`βœ… Socket.IO server available at: ${url}`);
33
+
34
+ // Get local URL without instance (static method - predictable port)
35
+ const staticUrl = Roster.getLocalUrl('example.com');
36
+ console.log(`ℹ️ Static prediction: ${staticUrl}`);
37
+ });
package/index.js CHANGED
@@ -7,6 +7,32 @@ const { EventEmitter } = require('events');
7
7
  const Greenlock = require('greenlock-express');
8
8
  const log = require('lemonlog')('roster');
9
9
 
10
+ // CRC32 implementation for deterministic port assignment
11
+ function crc32(str) {
12
+ const crcTable = [];
13
+ for (let i = 0; i < 256; i++) {
14
+ let crc = i;
15
+ for (let j = 0; j < 8; j++) {
16
+ crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
17
+ }
18
+ crcTable[i] = crc;
19
+ }
20
+
21
+ let crc = 0xFFFFFFFF;
22
+ for (let i = 0; i < str.length; i++) {
23
+ const byte = str.charCodeAt(i);
24
+ crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xFF];
25
+ }
26
+ return (crc ^ 0xFFFFFFFF) >>> 0;
27
+ }
28
+
29
+ // Convert CRC32 hash to a port number in available range
30
+ function domainToPort(domain, minPort = 3000, maxPort = 65535) {
31
+ const hash = crc32(domain);
32
+ const portRange = maxPort - minPort + 1;
33
+ return minPort + (hash % portRange);
34
+ }
35
+
10
36
  // Virtual Server that completely isolates applications
11
37
  class VirtualServer extends EventEmitter {
12
38
  constructor(domain) {
@@ -14,7 +40,7 @@ class VirtualServer extends EventEmitter {
14
40
  this.domain = domain;
15
41
  this.requestListeners = [];
16
42
  this.upgradeListeners = [];
17
-
43
+
18
44
  // Simulate http.Server properties
19
45
  this.listening = false;
20
46
  this.address = () => ({ port: 443, family: 'IPv4', address: '0.0.0.0' });
@@ -23,7 +49,7 @@ class VirtualServer extends EventEmitter {
23
49
  this.headersTimeout = 60000;
24
50
  this.maxHeadersCount = null;
25
51
  }
26
-
52
+
27
53
  // Override listener methods to capture them
28
54
  on(event, listener) {
29
55
  if (event === 'request') {
@@ -33,11 +59,11 @@ class VirtualServer extends EventEmitter {
33
59
  }
34
60
  return super.on(event, listener);
35
61
  }
36
-
62
+
37
63
  addListener(event, listener) {
38
64
  return this.on(event, listener);
39
65
  }
40
-
66
+
41
67
  // Socket.IO compatibility methods
42
68
  listeners(event) {
43
69
  if (event === 'request') {
@@ -47,7 +73,7 @@ class VirtualServer extends EventEmitter {
47
73
  }
48
74
  return super.listeners(event);
49
75
  }
50
-
76
+
51
77
  removeListener(event, listener) {
52
78
  if (event === 'request') {
53
79
  const index = this.requestListeners.indexOf(listener);
@@ -62,7 +88,7 @@ class VirtualServer extends EventEmitter {
62
88
  }
63
89
  return super.removeListener(event, listener);
64
90
  }
65
-
91
+
66
92
  removeAllListeners(event) {
67
93
  if (event === 'request') {
68
94
  this.requestListeners = [];
@@ -71,33 +97,33 @@ class VirtualServer extends EventEmitter {
71
97
  }
72
98
  return super.removeAllListeners(event);
73
99
  }
74
-
100
+
75
101
  // Simulate other http.Server methods
76
102
  listen() { this.listening = true; return this; }
77
103
  close() { this.listening = false; return this; }
78
104
  setTimeout() { return this; }
79
-
105
+
80
106
  // Process request with this virtual server's listeners
81
107
  processRequest(req, res) {
82
108
  let handled = false;
83
-
109
+
84
110
  // Track if response was handled
85
111
  const originalEnd = res.end;
86
- res.end = function(...args) {
112
+ res.end = function (...args) {
87
113
  handled = true;
88
114
  return originalEnd.apply(this, args);
89
115
  };
90
-
116
+
91
117
  // Try all listeners
92
118
  for (const listener of this.requestListeners) {
93
119
  if (!handled) {
94
120
  listener(req, res);
95
121
  }
96
122
  }
97
-
123
+
98
124
  // Restore original end method
99
125
  res.end = originalEnd;
100
-
126
+
101
127
  // If no listener handled the request, try fallback handler
102
128
  if (!handled && this.fallbackHandler) {
103
129
  this.fallbackHandler(req, res);
@@ -106,14 +132,14 @@ class VirtualServer extends EventEmitter {
106
132
  res.end('No handler found');
107
133
  }
108
134
  }
109
-
135
+
110
136
  // Process upgrade events (WebSocket)
111
137
  processUpgrade(req, socket, head) {
112
138
  // Emit to all registered upgrade listeners
113
139
  for (const listener of this.upgradeListeners) {
114
140
  listener(req, socket, head);
115
141
  }
116
-
142
+
117
143
  // If no listeners, destroy the socket
118
144
  if (this.upgradeListeners.length === 0) {
119
145
  socket.destroy();
@@ -134,8 +160,12 @@ class Roster {
134
160
  this.sites = {};
135
161
  this.domainServers = {}; // Store separate servers for each domain
136
162
  this.portServers = {}; // Store servers by port
163
+ this.domainPorts = {}; // Store domain β†’ port mapping for local mode
164
+ this.assignedPorts = new Set(); // Track ports assigned to domains (not OS availability)
137
165
  this.hostname = options.hostname || '0.0.0.0';
138
166
  this.filename = options.filename || 'index';
167
+ this.minLocalPort = options.minLocalPort || 4000;
168
+ this.maxLocalPort = options.maxLocalPort || 9999;
139
169
 
140
170
  const port = options.port === undefined ? 443 : options.port;
141
171
  if (port === 80 && !this.local) {
@@ -306,7 +336,7 @@ class Roster {
306
336
  }
307
337
 
308
338
  const { domain, port } = this.parseDomainWithPort(domainString);
309
-
339
+
310
340
  const domainEntries = [domain];
311
341
  if ((domain.match(/\./g) || []).length < 2) {
312
342
  domainEntries.push(`www.${domain}`);
@@ -336,10 +366,64 @@ class Roster {
336
366
  return { domain: domainString, port: this.defaultPort };
337
367
  }
338
368
 
369
+ /**
370
+ * Get the local URL for a domain when running in local mode
371
+ * @param {string} domain - The domain name (e.g., 'example.com')
372
+ * @param {Object} options - Optional configuration
373
+ * @param {number} options.minLocalPort - Minimum port range (default: 4000)
374
+ * @param {number} options.maxLocalPort - Maximum port range (default: 9999)
375
+ * @returns {string} The local URL (e.g., 'http://localhost:4321')
376
+ */
377
+ static getLocalUrl(domain, options = {}) {
378
+ const minPort = options.minLocalPort || 4000;
379
+ const maxPort = options.maxLocalPort || 9999;
380
+
381
+ // Remove www prefix if present
382
+ const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
383
+
384
+ // Calculate deterministic port
385
+ const port = domainToPort(cleanDomain, minPort, maxPort);
386
+
387
+ return `http://localhost:${port}`;
388
+ }
389
+
390
+ /**
391
+ * Get the local URL for a domain that was registered on this instance
392
+ * @param {string} domain - The domain name
393
+ * @returns {string|null} The local URL if found, null otherwise
394
+ */
395
+ getLocalUrl(domain) {
396
+ // Remove www prefix if present
397
+ const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
398
+
399
+ // Check if domain has a port assigned
400
+ if (this.domainPorts && this.domainPorts[cleanDomain]) {
401
+ return `http://localhost:${this.domainPorts[cleanDomain]}`;
402
+ }
403
+
404
+ return null;
405
+ }
406
+
339
407
  createVirtualServer(domain) {
340
408
  return new VirtualServer(domain);
341
409
  }
342
410
 
411
+ // Assign port to domain, detecting collisions with already assigned ports
412
+ assignPortToDomain(domain) {
413
+ let port = domainToPort(domain, this.minLocalPort, this.maxLocalPort);
414
+
415
+ // If port is already assigned to another domain, increment until we find a free one
416
+ while (this.assignedPorts.has(port)) {
417
+ port++;
418
+ if (port > this.maxLocalPort) {
419
+ port = this.minLocalPort; // Wrap around if we exceed max port
420
+ }
421
+ }
422
+
423
+ this.assignedPorts.add(port);
424
+ return port;
425
+ }
426
+
343
427
  // Get SSL context from Greenlock for custom ports
344
428
  async getSSLContext(domain, greenlock) {
345
429
  try {
@@ -353,39 +437,43 @@ class Roster {
353
437
  }
354
438
  } catch (error) {
355
439
  }
356
-
440
+
357
441
  // Return undefined to let HTTPS server handle SNI callback
358
442
  return null;
359
443
  }
360
444
 
361
445
  // Start server in local mode with HTTP - simplified version
362
446
  startLocalMode() {
363
- const startPort = 3000;
364
- let currentPort = startPort;
447
+ // Store mapping of domain to port for later retrieval
448
+ this.domainPorts = {};
365
449
 
366
- // Create a simple HTTP server for each domain with sequential ports
450
+ // Create a simple HTTP server for each domain with CRC32-based ports
367
451
  for (const [hostKey, siteApp] of Object.entries(this.sites)) {
368
452
  const domain = hostKey.split(':')[0]; // Remove port if present
369
-
453
+
370
454
  // Skip www domains in local mode
371
455
  if (domain.startsWith('www.')) {
372
456
  continue;
373
457
  }
458
+
459
+ // Calculate deterministic port based on domain CRC32, with collision detection
460
+ const port = this.assignPortToDomain(domain);
374
461
 
375
- const port = currentPort; // Capture current port value
376
-
462
+ // Store domain β†’ port mapping
463
+ this.domainPorts[domain] = port;
464
+
377
465
  // Create virtual server for the domain
378
466
  const virtualServer = this.createVirtualServer(domain);
379
467
  this.domainServers[domain] = virtualServer;
380
-
468
+
381
469
  // Initialize app with virtual server
382
470
  const appHandler = siteApp(virtualServer);
383
-
471
+
384
472
  // Create simple dispatcher for this domain
385
473
  const dispatcher = (req, res) => {
386
474
  // Set fallback handler on virtual server for non-Socket.IO requests
387
475
  virtualServer.fallbackHandler = appHandler;
388
-
476
+
389
477
  if (virtualServer.requestListeners.length > 0) {
390
478
  virtualServer.processRequest(req, res);
391
479
  } else if (appHandler) {
@@ -395,34 +483,32 @@ class Roster {
395
483
  res.end('Site not found');
396
484
  }
397
485
  };
398
-
486
+
399
487
  // Create HTTP server for this domain
400
488
  const httpServer = http.createServer(dispatcher);
401
489
  this.portServers[port] = httpServer;
402
-
490
+
403
491
  // Handle WebSocket upgrade events
404
492
  httpServer.on('upgrade', (req, socket, head) => {
405
493
  virtualServer.processUpgrade(req, socket, head);
406
494
  });
407
-
495
+
408
496
  httpServer.listen(port, 'localhost', () => {
409
497
  log.info(`🌐 ${domain} β†’ http://localhost:${port}`);
410
498
  });
411
-
499
+
412
500
  httpServer.on('error', (error) => {
413
501
  log.error(`❌ Error on port ${port} for ${domain}:`, error.message);
414
502
  });
415
-
416
- currentPort++;
417
503
  }
418
-
419
- log.info(`(βœ”) Started ${currentPort - startPort} sites in local mode`);
504
+
505
+ log.info(`(βœ”) Started ${Object.keys(this.portServers).length} sites in local mode`);
420
506
  return Promise.resolve();
421
507
  }
422
508
 
423
509
  async start() {
424
510
  await this.loadSites();
425
-
511
+
426
512
  // Skip Greenlock configuration generation in local mode
427
513
  if (!this.local) {
428
514
  this.generateConfigJson();
@@ -443,7 +529,7 @@ class Roster {
443
529
 
444
530
  return greenlock.ready(glx => {
445
531
  const httpServer = glx.httpServer();
446
-
532
+
447
533
  // Group sites by port
448
534
  const sitesByPort = {};
449
535
  for (const [hostKey, siteApp] of Object.entries(this.sites)) {
@@ -455,12 +541,12 @@ class Roster {
455
541
  appHandlers: {}
456
542
  };
457
543
  }
458
-
544
+
459
545
  // Create completely isolated virtual server
460
546
  const virtualServer = this.createVirtualServer(domain);
461
547
  sitesByPort[port].virtualServers[domain] = virtualServer;
462
548
  this.domainServers[domain] = virtualServer;
463
-
549
+
464
550
  // Initialize app with virtual server
465
551
  const appHandler = siteApp(virtualServer);
466
552
  sitesByPort[port].appHandlers[domain] = appHandler;
@@ -472,11 +558,11 @@ class Roster {
472
558
  const createDispatcher = (portData) => {
473
559
  return (req, res) => {
474
560
  const host = req.headers.host || '';
475
-
561
+
476
562
  // Remove port from host header if present (e.g., "domain.com:8080" -> "domain.com")
477
563
  const hostWithoutPort = host.split(':')[0];
478
564
  const domain = hostWithoutPort.startsWith('www.') ? hostWithoutPort.slice(4) : hostWithoutPort;
479
-
565
+
480
566
  // Handle www redirects
481
567
  if (hostWithoutPort.startsWith('www.')) {
482
568
  res.writeHead(301, { Location: `https://${domain}${req.url}` });
@@ -486,7 +572,7 @@ class Roster {
486
572
 
487
573
  const virtualServer = portData.virtualServers[domain];
488
574
  const appHandler = portData.appHandlers[domain];
489
-
575
+
490
576
  if (virtualServer && virtualServer.requestListeners.length > 0) {
491
577
  // Set fallback handler on virtual server for non-Socket.IO requests
492
578
  virtualServer.fallbackHandler = appHandler;
@@ -512,9 +598,9 @@ class Roster {
512
598
  const host = req.headers.host || '';
513
599
  const hostWithoutPort = host.split(':')[0];
514
600
  const domain = hostWithoutPort.startsWith('www.') ? hostWithoutPort.slice(4) : hostWithoutPort;
515
-
601
+
516
602
  const virtualServer = portData.virtualServers[domain];
517
-
603
+
518
604
  if (virtualServer) {
519
605
  virtualServer.processUpgrade(req, socket, head);
520
606
  } else {
@@ -529,15 +615,15 @@ class Roster {
529
615
  const portNum = parseInt(port);
530
616
  const dispatcher = createDispatcher(portData);
531
617
  const upgradeHandler = createUpgradeHandler(portData);
532
-
618
+
533
619
  if (portNum === this.defaultPort) {
534
620
  // Use Greenlock for default port (443) with SSL
535
621
  const httpsServer = glx.httpsServer(null, dispatcher);
536
622
  this.portServers[portNum] = httpsServer;
537
-
623
+
538
624
  // Handle WebSocket upgrade events
539
625
  httpsServer.on('upgrade', upgradeHandler);
540
-
626
+
541
627
  httpsServer.listen(portNum, this.hostname, () => {
542
628
  log.info(`HTTPS server listening on port ${portNum}`);
543
629
  });
@@ -551,12 +637,12 @@ class Roster {
551
637
  const keyPath = path.join(certPath, 'privkey.pem');
552
638
  const certFilePath = path.join(certPath, 'cert.pem');
553
639
  const chainPath = path.join(certPath, 'chain.pem');
554
-
640
+
555
641
  if (fs.existsSync(keyPath) && fs.existsSync(certFilePath) && fs.existsSync(chainPath)) {
556
642
  const key = fs.readFileSync(keyPath, 'utf8');
557
643
  const cert = fs.readFileSync(certFilePath, 'utf8');
558
644
  const chain = fs.readFileSync(chainPath, 'utf8');
559
-
645
+
560
646
  callback(null, tls.createSecureContext({
561
647
  key: key,
562
648
  cert: cert + chain
@@ -569,25 +655,25 @@ class Roster {
569
655
  }
570
656
  }
571
657
  };
572
-
658
+
573
659
  const httpsServer = https.createServer(httpsOptions, dispatcher);
574
-
660
+
575
661
  // Handle WebSocket upgrade events
576
662
  httpsServer.on('upgrade', upgradeHandler);
577
-
663
+
578
664
  httpsServer.on('error', (error) => {
579
665
  log.error(`HTTPS server error on port ${portNum}:`, error.message);
580
666
  });
581
-
667
+
582
668
  httpsServer.on('tlsClientError', (error) => {
583
669
  // Suppress HTTP request errors to avoid log spam
584
670
  if (!error.message.includes('http request')) {
585
671
  log.error(`TLS error on port ${portNum}:`, error.message);
586
672
  }
587
673
  });
588
-
674
+
589
675
  this.portServers[portNum] = httpsServer;
590
-
676
+
591
677
  httpsServer.listen(portNum, this.hostname, (error) => {
592
678
  if (error) {
593
679
  log.error(`Failed to start HTTPS server on port ${portNum}:`, error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "1.9.0",
3
+ "version": "1.9.4",
4
4
  "description": "πŸ‘Ύ RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {