roster-server 1.9.2 β†’ 1.9.6

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,82 @@ 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 based on a CRC32 hash of the domain name (range 4000-9999):
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
194
  - `example.com` β†’ `http://localhost:9465`
191
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 URLs
209
+
210
+ RosterServer provides a method to get the URL for a domain that adapts automatically to your environment:
211
+
212
+ **Instance Method: `roster.getUrl(domain)`**
213
+
214
+ ```javascript
215
+ const roster = new Roster({ local: true });
216
+ roster.register('example.com', handler);
217
+
218
+ await roster.start();
219
+
220
+ // Get the URL - automatically adapts to environment
221
+ const url = roster.getUrl('example.com');
222
+ console.log(url);
223
+ // Local mode: http://localhost:9465
224
+ // Production mode: https://example.com
225
+ ```
226
+
227
+ This method:
228
+ - Returns the correct URL based on your environment (`local: true/false`)
229
+ - In **local mode**: Returns `http://localhost:{port}` with the assigned port
230
+ - In **production mode**: Returns `https://{domain}` (or with custom port if configured)
231
+ - Handles `www.` prefix automatically (returns same URL)
232
+ - Returns `null` for domains that aren't registered
233
+
234
+ **Example Usage:**
235
+
236
+ ```javascript
237
+ // Local development
238
+ const localRoster = new Roster({ local: true });
239
+ localRoster.register('example.com', handler);
240
+ await localRoster.start();
241
+ console.log(localRoster.getUrl('example.com'));
242
+ // β†’ http://localhost:9465
243
+
244
+ // Production
245
+ const prodRoster = new Roster({ local: false });
246
+ prodRoster.register('example.com', handler);
247
+ await prodRoster.start();
248
+ console.log(prodRoster.getUrl('example.com'));
249
+ // β†’ https://example.com
250
+
251
+ // Production with custom port
252
+ const customRoster = new Roster({ local: false, port: 8443 });
253
+ customRoster.register('api.example.com', handler);
254
+ await customRoster.start();
255
+ console.log(customRoster.getUrl('api.example.com'));
256
+ // β†’ https://api.example.com:8443
257
+ ```
258
+
194
259
  ## πŸ§‚ A Touch of Magic
195
260
 
196
261
  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,32 @@
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πŸ”§ Creating server with custom port range (5000-5100)...\n');
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 with custom port range!');
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 with custom port range' }));
23
+ };
24
+ });
25
+
26
+ roster.start().then(() => {
27
+ console.log('πŸš€ Server Started with custom port range:');
28
+ console.log('example.com β†’', roster.getUrl('example.com'));
29
+ console.log('api.example.com β†’', roster.getUrl('api.example.com'));
30
+
31
+ console.log('\nβœ… Both domains running in custom port range (5000-5100)!');
32
+ });
@@ -0,0 +1,55 @@
1
+ const Roster = require('../index.js');
2
+
3
+ // Example 1: Local mode
4
+ console.log('\nπŸ“ EXAMPLE 1: Local Development Mode\n');
5
+ const localRoster = new Roster({ local: true });
6
+
7
+ localRoster.register('example.com', (httpsServer) => {
8
+ return (req, res) => {
9
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
10
+ res.end('Hello from local!');
11
+ };
12
+ });
13
+
14
+ localRoster.start().then(() => {
15
+ console.log('Local URL:', localRoster.getUrl('example.com'));
16
+ console.log('β†’ Returns: http://localhost:{port}\n');
17
+ });
18
+
19
+ // Example 2: Production mode (simulated, without actually starting)
20
+ console.log('πŸ“ EXAMPLE 2: Production Mode (simulated)\n');
21
+ const prodRoster = new Roster({
22
+ local: false,
23
+ email: 'admin@example.com'
24
+ });
25
+
26
+ prodRoster.register('example.com', (httpsServer) => {
27
+ return (req, res) => {
28
+ res.writeHead(200);
29
+ res.end('Hello from production!');
30
+ };
31
+ });
32
+
33
+ // Don't actually start (would need real SSL), just show what URL would be
34
+ console.log('Production URL (without starting):', 'https://example.com');
35
+ console.log('β†’ Would return: https://example.com\n');
36
+
37
+ // Example 3: Production with custom port
38
+ console.log('πŸ“ EXAMPLE 3: Production with Custom Port (simulated)\n');
39
+ const customPortRoster = new Roster({
40
+ local: false,
41
+ port: 8443,
42
+ email: 'admin@example.com'
43
+ });
44
+
45
+ customPortRoster.register('api.example.com', (httpsServer) => {
46
+ return (req, res) => {
47
+ res.writeHead(200);
48
+ res.end('API');
49
+ };
50
+ });
51
+
52
+ console.log('Custom port URL (without starting):', 'https://api.example.com:8443');
53
+ console.log('β†’ Would return: https://api.example.com:8443\n');
54
+
55
+ console.log('βœ… getUrl() adapts to the environment automatically!');
@@ -0,0 +1,36 @@
1
+ const Roster = require('../index.js');
2
+
3
+ // Example: Get URL after registration (adapts to environment)
4
+ console.log('\nπŸ”§ Creating local development server...\n');
5
+
6
+ const roster = new Roster({ local: true });
7
+
8
+ roster.register('example.com', (httpsServer) => {
9
+ return (req, res) => {
10
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
11
+ res.end('Hello from example.com!');
12
+ };
13
+ });
14
+
15
+ roster.register('api.example.com', (httpsServer) => {
16
+ return (req, res) => {
17
+ res.writeHead(200, { 'Content-Type': 'application/json' });
18
+ res.end(JSON.stringify({ message: 'API endpoint' }));
19
+ };
20
+ });
21
+
22
+ roster.start().then(() => {
23
+ console.log('πŸš€ Server Started - URLs (based on environment):');
24
+ console.log('example.com β†’', roster.getUrl('example.com'));
25
+ console.log('api.example.com β†’', roster.getUrl('api.example.com'));
26
+
27
+ // Test with www prefix (should return same URL)
28
+ console.log('\nπŸ”„ Testing www prefix handling:');
29
+ console.log('www.example.com β†’', roster.getUrl('www.example.com'));
30
+
31
+ // Test non-existent domain
32
+ console.log('\n❌ Testing non-existent domain:');
33
+ console.log('nonexistent.com β†’', roster.getUrl('nonexistent.com') || 'null (domain not registered)');
34
+
35
+ console.log('\nβœ… All domains running!');
36
+ });
package/demo/socketio.js CHANGED
@@ -26,4 +26,8 @@ roster.register('example.com', (httpsServer) => {
26
26
  };
27
27
  });
28
28
 
29
- roster.start();
29
+ roster.start().then(() => {
30
+ // Get URL for registered domain (adapts to environment)
31
+ const url = roster.getUrl('example.com');
32
+ console.log(`βœ… Socket.IO server available at: ${url}`);
33
+ });
@@ -0,0 +1,64 @@
1
+ const Roster = require('../index.js');
2
+
3
+ console.log('\nπŸ§ͺ Testing getUrl() method in different scenarios\n');
4
+
5
+ // Test 1: Local mode with default port
6
+ console.log('TEST 1: Local mode (default port)');
7
+ const local1 = new Roster({ local: true });
8
+ local1.register('example.com', () => (req, res) => res.end('OK'));
9
+ local1.start().then(() => {
10
+ const url = local1.getUrl('example.com');
11
+ console.log(`βœ“ example.com β†’ ${url}`);
12
+ console.assert(url.startsWith('http://localhost:'), 'Should be localhost HTTP');
13
+ });
14
+
15
+ // Test 2: Local mode with custom port range
16
+ console.log('\nTEST 2: Local mode (custom port range)');
17
+ const local2 = new Roster({ local: true, minLocalPort: 6000, maxLocalPort: 6100 });
18
+ local2.register('test.com', () => (req, res) => res.end('OK'));
19
+ local2.start().then(() => {
20
+ const url = local2.getUrl('test.com');
21
+ console.log(`βœ“ test.com β†’ ${url}`);
22
+ const port = parseInt(url.split(':')[2]);
23
+ console.assert(port >= 6000 && port <= 6100, 'Port should be in custom range');
24
+ });
25
+
26
+ // Test 3: Production mode (default HTTPS port)
27
+ console.log('\nTEST 3: Production mode (default HTTPS)');
28
+ const prod1 = new Roster({ local: false, email: 'admin@example.com' });
29
+ prod1.register('example.com', () => (req, res) => res.end('OK'));
30
+ const prodUrl1 = prod1.getUrl('example.com');
31
+ console.log(`βœ“ example.com β†’ ${prodUrl1}`);
32
+ console.assert(prodUrl1 === 'https://example.com', 'Should be HTTPS without port');
33
+
34
+ // Test 4: Production mode (custom port)
35
+ console.log('\nTEST 4: Production mode (custom port)');
36
+ const prod2 = new Roster({ local: false, port: 8443, email: 'admin@example.com' });
37
+ prod2.register('api.example.com', () => (req, res) => res.end('OK'));
38
+ const prodUrl2 = prod2.getUrl('api.example.com');
39
+ console.log(`βœ“ api.example.com β†’ ${prodUrl2}`);
40
+ console.assert(prodUrl2 === 'https://api.example.com:8443', 'Should include custom port');
41
+
42
+ // Test 5: www prefix handling
43
+ console.log('\nTEST 5: www prefix handling');
44
+ const local3 = new Roster({ local: true });
45
+ local3.register('example.com', () => (req, res) => res.end('OK'));
46
+ local3.start().then(() => {
47
+ const url1 = local3.getUrl('example.com');
48
+ const url2 = local3.getUrl('www.example.com');
49
+ console.log(`βœ“ example.com β†’ ${url1}`);
50
+ console.log(`βœ“ www.example.com β†’ ${url2}`);
51
+ console.assert(url1 === url2, 'www should return same URL');
52
+ });
53
+
54
+ // Test 6: Non-existent domain
55
+ console.log('\nTEST 6: Non-existent domain');
56
+ const local4 = new Roster({ local: true });
57
+ local4.register('example.com', () => (req, res) => res.end('OK'));
58
+ local4.start().then(() => {
59
+ const url = local4.getUrl('nonexistent.com');
60
+ console.log(`βœ“ nonexistent.com β†’ ${url}`);
61
+ console.assert(url === null, 'Should return null for unregistered domain');
62
+
63
+ console.log('\nβœ… All tests passed!\n');
64
+ });
package/index.js CHANGED
@@ -160,9 +160,12 @@ class Roster {
160
160
  this.sites = {};
161
161
  this.domainServers = {}; // Store separate servers for each domain
162
162
  this.portServers = {}; // Store servers by port
163
+ this.domainPorts = {}; // Store domain β†’ port mapping for local mode
163
164
  this.assignedPorts = new Set(); // Track ports assigned to domains (not OS availability)
164
165
  this.hostname = options.hostname || '0.0.0.0';
165
166
  this.filename = options.filename || 'index';
167
+ this.minLocalPort = options.minLocalPort || 4000;
168
+ this.maxLocalPort = options.maxLocalPort || 9999;
166
169
 
167
170
  const port = options.port === undefined ? 443 : options.port;
168
171
  if (port === 80 && !this.local) {
@@ -363,19 +366,48 @@ class Roster {
363
366
  return { domain: domainString, port: this.defaultPort };
364
367
  }
365
368
 
369
+ /**
370
+ * Get the URL for a domain based on the current environment
371
+ * @param {string} domain - The domain name
372
+ * @returns {string|null} The URL if domain is registered, null otherwise
373
+ */
374
+ getUrl(domain) {
375
+ // Remove www prefix if present
376
+ const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
377
+
378
+ // Check if domain is registered
379
+ const isRegistered = this.sites[cleanDomain] || this.sites[`www.${cleanDomain}`];
380
+ if (!isRegistered) {
381
+ return null;
382
+ }
383
+
384
+ // Return URL based on environment
385
+ if (this.local) {
386
+ // Local mode: return localhost URL with assigned port
387
+ if (this.domainPorts && this.domainPorts[cleanDomain]) {
388
+ return `http://localhost:${this.domainPorts[cleanDomain]}`;
389
+ }
390
+ return null;
391
+ } else {
392
+ // Production mode: return HTTPS URL
393
+ const port = this.defaultPort === 443 ? '' : `:${this.defaultPort}`;
394
+ return `https://${cleanDomain}${port}`;
395
+ }
396
+ }
397
+
366
398
  createVirtualServer(domain) {
367
399
  return new VirtualServer(domain);
368
400
  }
369
401
 
370
402
  // Assign port to domain, detecting collisions with already assigned ports
371
- assignPortToDomain(domain, minPort = 4000, maxPort = 9999) {
372
- let port = domainToPort(domain, minPort, maxPort);
403
+ assignPortToDomain(domain) {
404
+ let port = domainToPort(domain, this.minLocalPort, this.maxLocalPort);
373
405
 
374
406
  // If port is already assigned to another domain, increment until we find a free one
375
407
  while (this.assignedPorts.has(port)) {
376
408
  port++;
377
- if (port > maxPort) {
378
- port = minPort; // Wrap around if we exceed max port
409
+ if (port > this.maxLocalPort) {
410
+ port = this.minLocalPort; // Wrap around if we exceed max port
379
411
  }
380
412
  }
381
413
 
@@ -403,6 +435,9 @@ class Roster {
403
435
 
404
436
  // Start server in local mode with HTTP - simplified version
405
437
  startLocalMode() {
438
+ // Store mapping of domain to port for later retrieval
439
+ this.domainPorts = {};
440
+
406
441
  // Create a simple HTTP server for each domain with CRC32-based ports
407
442
  for (const [hostKey, siteApp] of Object.entries(this.sites)) {
408
443
  const domain = hostKey.split(':')[0]; // Remove port if present
@@ -413,7 +448,10 @@ class Roster {
413
448
  }
414
449
 
415
450
  // Calculate deterministic port based on domain CRC32, with collision detection
416
- const port = this.assignPortToDomain(domain, 4000, 9999);
451
+ const port = this.assignPortToDomain(domain);
452
+
453
+ // Store domain β†’ port mapping
454
+ this.domainPorts[domain] = port;
417
455
 
418
456
  // Create virtual server for the domain
419
457
  const virtualServer = this.createVirtualServer(domain);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "1.9.2",
3
+ "version": "1.9.6",
4
4
  "description": "πŸ‘Ύ RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {