roster-server 2.3.8 → 2.3.12

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.
@@ -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,7 @@
1
+ ---
2
+ id: ac8ba0ra21
3
+ type: decision
4
+ title: Roster default IPv6 bind
5
+ created: '2026-03-16 21:57:18'
6
+ ---
7
+ Changed Roster default bind hostname from 0.0.0.0 to :: in index.js constructor (`this.hostname = options.hostname ?? '::'`). This makes default listen IPv6-first. Existing explicit hostname option still overrides. Verified with full test suite: 74 passing.
@@ -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) {
@@ -239,7 +252,7 @@ class Roster {
239
252
  this.portServers = {}; // Store servers by port
240
253
  this.domainPorts = {}; // Store domain → port mapping for local mode
241
254
  this.assignedPorts = new Set(); // Track ports assigned to domains (not OS availability)
242
- this.hostname = options.hostname || '0.0.0.0';
255
+ this.hostname = options.hostname ?? '::';
243
256
  this.filename = options.filename || 'index';
244
257
  this.minLocalPort = options.minLocalPort || 4000;
245
258
  this.maxLocalPort = options.maxLocalPort || 9999;
@@ -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,22 +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
- const labels = cleanDomain.split('.').filter(Boolean);
610
- let localHost = 'localhost';
611
-
612
- // For wildcard matches, preserve the full left-side subdomain chain.
613
- if (pattern.startsWith('*.')) {
614
- const root = wildcardRoot(pattern);
615
- if (root && hostMatchesWildcard(cleanDomain, pattern)) {
616
- const subdomain = cleanDomain.slice(0, -(root.length + 1));
617
- if (subdomain) localHost = `${subdomain}.localhost`;
618
- }
619
- } else if (labels.length > 2) {
620
- // For exact subdomains (e.g. api.example.com), map to api.localhost.
621
- localHost = `${labels.slice(0, -2).join('.')}.localhost`;
622
- }
623
-
624
- return `http://${localHost}:${this.domainPorts[pattern]}`;
622
+ return `http://${localHostForDomain(cleanDomain)}:${this.domainPorts[pattern]}`;
625
623
  }
626
624
  return null;
627
625
  }
@@ -719,7 +717,8 @@ class Roster {
719
717
  });
720
718
 
721
719
  httpServer.listen(port, 'localhost', () => {
722
- log.info(`🌐 ${domain} http://localhost:${port}`);
720
+ const cleanDomain = normalizeDomainForLocalHost(domain);
721
+ log.info(`🌐 ${domain} → http://${localHostForDomain(cleanDomain)}:${port}`);
723
722
  });
724
723
 
725
724
  httpServer.on('error', (error) => {
@@ -794,7 +793,7 @@ class Roster {
794
793
  }
795
794
  if (!msg || msg === 'undefined') msg = `[${event}] (no details)`;
796
795
  if (eventDomain && !msg.includes(`[${eventDomain}]`)) {
797
- msg = `[domain:${eventDomain}] ${msg}`;
796
+ msg = `[${eventDomain}] ${msg}`;
798
797
  }
799
798
  // Suppress known benign warnings from ACME when using acme-dns-01-cli
800
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.8",
3
+ "version": "2.3.12",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,12 +0,0 @@
1
- ---
2
- id: wrxv04lnq9
3
- type: decision
4
- title: 'Decision: Local subdomain URL format'
5
- created: '2026-03-16 17:15:01'
6
- ---
7
- # Decision: Local subdomain URL format
8
-
9
- **What**: In local mode, `getUrl(domain)` now returns `http://<subdomain>.localhost:<port>` for subdomains, while apex domains keep `http://localhost:<port>`.
10
- **Where**: Implemented in `index.js` (`getUrl` local branch), validated in `test/roster-server.test.js`, and documented in `README.md`.
11
- **Why**: Local URLs should preserve subdomain semantics so multi-tenant/subdomain routes are explicit and easier to test.
12
- **Alternatives rejected**: Keeping all local URLs as `http://localhost:<port>` was rejected because it hides host intent for subdomain-based apps.