roster-server 2.3.6 → 2.3.10

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
@@ -309,12 +309,13 @@ await roster.start();
309
309
  const url = roster.getUrl('example.com');
310
310
  console.log(url);
311
311
  // Local mode: http://localhost:9465
312
+ // Local subdomain: http://api.localhost:9465
312
313
  // Production mode: https://example.com
313
314
  ```
314
315
 
315
316
  This method:
316
317
  - Returns the correct URL based on your environment (`local: true/false`)
317
- - In **local mode**: Returns `http://localhost:{port}` with the assigned port
318
+ - In **local mode**: Returns `http://localhost:{port}` for apex domains and `http://{subdomain}.localhost:{port}` for subdomains
318
319
  - In **production mode**: Returns `https://{domain}` (or with custom port if configured)
319
320
  - Handles `www.` prefix automatically (returns same URL)
320
321
  - Returns `null` for domains that aren't registered
@@ -327,9 +328,12 @@ import Roster from 'roster-server';
327
328
  // Local development
328
329
  const localRoster = new Roster({ local: true });
329
330
  localRoster.register('example.com', handler);
331
+ localRoster.register('api.example.com', handler);
330
332
  await localRoster.start();
331
333
  console.log(localRoster.getUrl('example.com'));
332
334
  // → http://localhost:9465
335
+ console.log(localRoster.getUrl('api.example.com'));
336
+ // → http://api.localhost:7342
333
337
 
334
338
  // Production
335
339
  const prodRoster = new Roster({ local: false });
@@ -1,6 +1,6 @@
1
1
  module.exports = (httpsServer) => {
2
2
  return (req, res) => {
3
3
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
4
- res.end('"subdomain.example.com: Crazy from thinking, wanting to be reasonable, and the heart has reasons that reason itself will never understand."');
4
+ res.end('"api.example.com: Crazy from thinking, wanting to be reasonable, and the heart has reasons that reason itself will never understand."');
5
5
  };
6
6
  };
@@ -4,7 +4,7 @@ module.exports = (httpsServer) => {
4
4
  const app = express();
5
5
  app.get('/', (req, res) => {
6
6
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
7
- res.send('"subdomain.example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
7
+ res.send('"express.example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
8
8
  });
9
9
 
10
10
  return app;
@@ -0,0 +1,12 @@
1
+ ---
2
+ id: fralm92cih
3
+ type: bugfix
4
+ title: 'Bugfix: local mode startup logs show subdomain.localhost'
5
+ created: '2026-03-16 17:19:26'
6
+ ---
7
+ # Bug: local mode startup logs ignored subdomain localhost format
8
+
9
+ **Symptom**: Startup logs still printed `http://localhost:<port>` for subdomains (e.g. `api.example.com`) even after local URL behavior changed.
10
+ **Root cause**: `startLocalMode()` log message used a hardcoded `localhost` string.
11
+ **Solution**: Added `localHostForDomain(domain)` helper and reused it in both `getUrl()` and local-mode startup logs so they stay consistent.
12
+ **Location**: `index.js` (`localHostForDomain`, `getUrl`, `startLocalMode` listen log).
package/index.js CHANGED
@@ -109,6 +109,19 @@ function parseBooleanFlag(value, fallback = false) {
109
109
  return fallback;
110
110
  }
111
111
 
112
+ function normalizeDomainForLocalHost(domain) {
113
+ return (domain || '').trim().toLowerCase().replace(/^www\./, '');
114
+ }
115
+
116
+ function localHostForDomain(normalizedDomain) {
117
+ const normalized = normalizedDomain;
118
+ if (!normalized) return 'localhost';
119
+ if (normalized.startsWith('*.')) return '*.localhost';
120
+ const labels = normalized.split('.').filter(Boolean);
121
+ if (labels.length > 2) return `${labels.slice(0, -2).join('.')}.localhost`;
122
+ return 'localhost';
123
+ }
124
+
112
125
  // Virtual Server that completely isolates applications
113
126
  class VirtualServer extends EventEmitter {
114
127
  constructor(domain) {
@@ -597,7 +610,7 @@ class Roster {
597
610
  * @returns {string|null} The URL if domain is registered (exact or wildcard), null otherwise
598
611
  */
599
612
  getUrl(domain) {
600
- const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
613
+ const cleanDomain = normalizeDomainForLocalHost(domain);
601
614
 
602
615
  const exactMatch = this.sites[cleanDomain] || this.sites[`www.${cleanDomain}`];
603
616
  const resolved = exactMatch ? { handler: exactMatch, siteKey: cleanDomain } : this.getHandlerAndKeyForHost(cleanDomain);
@@ -606,7 +619,7 @@ class Roster {
606
619
  if (this.local) {
607
620
  const pattern = resolved.siteKey.split(':')[0];
608
621
  if (this.domainPorts && this.domainPorts[pattern] !== undefined) {
609
- return `http://localhost:${this.domainPorts[pattern]}`;
622
+ return `http://${localHostForDomain(cleanDomain)}:${this.domainPorts[pattern]}`;
610
623
  }
611
624
  return null;
612
625
  }
@@ -704,7 +717,8 @@ class Roster {
704
717
  });
705
718
 
706
719
  httpServer.listen(port, 'localhost', () => {
707
- log.info(`🌐 ${domain} http://localhost:${port}`);
720
+ const cleanDomain = normalizeDomainForLocalHost(domain);
721
+ log.info(`🌐 ${domain} → http://${localHostForDomain(cleanDomain)}:${port}`);
708
722
  });
709
723
 
710
724
  httpServer.on('error', (error) => {
@@ -778,8 +792,8 @@ class Roster {
778
792
  }
779
793
  }
780
794
  if (!msg || msg === 'undefined') msg = `[${event}] (no details)`;
781
- if (eventDomain && !msg.includes(`[domain:${eventDomain}]`)) {
782
- msg = `[domain:${eventDomain}] ${msg}`;
795
+ if (eventDomain && !msg.includes(`[${eventDomain}]`)) {
796
+ msg = `[${eventDomain}] ${msg}`;
783
797
  }
784
798
  // Suppress known benign warnings from ACME when using acme-dns-01-cli
785
799
  if (event === 'warning' && typeof msg === 'string') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.3.6",
3
+ "version": "2.3.10",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -218,7 +218,7 @@ describe('Roster', () => {
218
218
  roster.register('*.example.com', () => {});
219
219
  roster.domainPorts = { '*.example.com': 9999 };
220
220
  roster.local = true;
221
- assert.strictEqual(roster.getUrl('api.example.com'), 'http://localhost:9999');
221
+ assert.strictEqual(roster.getUrl('api.example.com'), 'http://api.localhost:9999');
222
222
  });
223
223
  it('returns https URL for wildcard-matched host in production', () => {
224
224
  const roster = new Roster({ local: false });
@@ -359,6 +359,13 @@ describe('Roster', () => {
359
359
  roster.local = true;
360
360
  assert.strictEqual(roster.getUrl('exact.local'), 'http://localhost:4567');
361
361
  });
362
+ it('returns http://subdomain.localhost:PORT in local mode for exact subdomain', () => {
363
+ const roster = new Roster({ local: true });
364
+ roster.register('api.example.com', () => {});
365
+ roster.domainPorts = { 'api.example.com': 5678 };
366
+ roster.local = true;
367
+ assert.strictEqual(roster.getUrl('api.example.com'), 'http://api.localhost:5678');
368
+ });
362
369
  it('returns https URL in production for registered domain', () => {
363
370
  const roster = new Roster({ local: false });
364
371
  roster.register('example.com', () => {});