roster-server 3.4.0 → 3.4.2

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
@@ -28,7 +28,7 @@ await roster.start();
28
28
  console.log(roster.getUrl('example.com'));
29
29
  ```
30
30
 
31
- Local mode binds to `localhost`, skips certificates, and assigns domain ports within `minLocalPort`–`maxLocalPort` (default `4000`–`9999`). Assignments use a domain hash and resolve collisions within the instance; they do not probe for available OS ports. A bind failure rejects startup. Read the assigned URL with `getUrl()` instead of hardcoding a sample port.
31
+ Local mode binds to `hostname` (default `::`, IPv4 and IPv6 on all interfaces), skips certificates, and assigns domain ports within `minLocalPort`–`maxLocalPort` (default `4000`–`9999`). Assignments use a domain hash and resolve collisions within the instance; they do not probe for available OS ports. A bind failure rejects startup. Read the assigned URL with `getUrl()` instead of hardcoding a sample port.
32
32
 
33
33
  For production HTTPS, use a real contact email, public DNS pointing to the host, and explicit storage paths:
34
34
 
@@ -313,7 +313,7 @@ Pass operational settings through the constructor, using the consuming applicati
313
313
  | `filename` | `index` | Script entry basename, without extension. |
314
314
  | `local` | `false` | Local HTTP mode with assigned ports and no certificates. |
315
315
  | `port` | `443` | Default port for manual registrations and routing helpers. |
316
- | `hostname` | `::` | Production bind address; local mode uses `localhost`. |
316
+ | `hostname` | `::` | Bind address in production and local mode. |
317
317
  | `minLocalPort`, `maxLocalPort` | `4000`, `9999` | Inclusive local assignment range. |
318
318
  | `staging` | `false` | ACME staging environment. |
319
319
  | `autoCertificates` | `true` | Certificate runtime during `init()`; disable for serving-only workers. |
package/index.js CHANGED
@@ -785,6 +785,12 @@ class Roster {
785
785
  }
786
786
  }
787
787
 
788
+ // Names the production site and the local URL that serves it, e.g. `example.com:3334 → tcp://localhost:6702`.
789
+ _describeLocalSite(domain, sitePort, localPort, scheme) {
790
+ const site = `${domain}${sitePort !== this.defaultPort ? ':' + sitePort : ''}`;
791
+ return `${site} → ${scheme}://${localHostForDomain(normalizeDomainForLocalHost(domain))}:${localPort}`;
792
+ }
793
+
788
794
  parseDomainWithPort(domainString) {
789
795
  const parts = domainString.split(':');
790
796
  if (parts.length === 2) {
@@ -950,6 +956,7 @@ class Roster {
950
956
  const plugins = this.plugins.map(isolatedPluginSource);
951
957
  const options = {
952
958
  local: this.local,
959
+ hostname: this.hostname,
953
960
  port: this.defaultPort,
954
961
  filename: this.filename,
955
962
  greenlockStorePath: this.greenlockStorePath,
@@ -1630,9 +1637,8 @@ class Roster {
1630
1637
  virtualServer.processUpgrade(req, socket, head);
1631
1638
  });
1632
1639
 
1633
- await this._listen(httpServer, port, 'localhost');
1634
- const cleanDomain = normalizeDomainForLocalHost(domain);
1635
- log.info(`🌐 ${domain} → http://${localHostForDomain(cleanDomain)}:${port}`);
1640
+ await this._listen(httpServer, port, this.hostname);
1641
+ log.info(`🌐 ${this._describeLocalSite(domain, Number(sitePort), port, 'http')}`);
1636
1642
  }
1637
1643
  }
1638
1644
 
@@ -56,15 +56,16 @@ class SiteWorkerRoster extends Roster {
56
56
  for (const key of this._site.keys) {
57
57
  if (key.startsWith('www.')) continue;
58
58
  const { domain, port } = this.parseDomainWithPort(key);
59
+ const localPort = this._site.localPorts[`${domain}:${port}`];
59
60
  const server = net.createServer({ pauseOnConnect: true });
60
- const listening = this._listen(server, this._site.localPorts[`${domain}:${port}`], 'localhost');
61
+ const listening = this._listen(server, localPort, this.hostname);
61
62
  server.on('connection', socket => {
62
63
  // The site owns the connection from here; it may pass it to another process.
63
64
  this._sockets.delete(socket);
64
65
  this._handOff(head, socket);
65
66
  });
66
67
  await listening;
67
- log.info(`🌐 ${domain} → tcp://localhost:${this._site.localPorts[`${domain}:${port}`]} (socket site)`);
68
+ log.info(`🌐 ${this._describeLocalSite(domain, port, localPort, 'tcp')} (socket site)`);
68
69
  }
69
70
  }
70
71
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -9,7 +9,7 @@ Use this skill to configure a consuming application. Keep its existing configura
9
9
 
10
10
  ## Choose the lifecycle owner
11
11
 
12
- - **Local development:** `new Roster({ local: true, wwwPath: absolutePath })`, register sites, then `await roster.start()`. Local mode binds to `localhost` and assigns ports; read URLs with `roster.getUrl(domain)` after startup.
12
+ - **Local development:** `new Roster({ local: true, wwwPath: absolutePath })`, register sites, then `await roster.start()`. Local mode binds to `hostname` (default `::`, IPv4 and IPv6) and assigns ports; read URLs with `roster.getUrl(domain)` after startup.
13
13
  - **Standalone HTTPS:** set the real `email`, absolute `wwwPath` and `greenlockStorePath`; leave `local` false. `await roster.start()` owns ACME HTTP on port 80 and the configured HTTPS listeners. A different HTTPS port does not eliminate the need for port 80.
14
14
  - **External server/worker:** `await roster.init()`, then `roster.attach(server, { port })` or an HTTPS helper. The caller owns listening and closing that server. `port` on `attach()` selects the routing table, not the TCP port.
15
15
  - **Serving-only worker:** use `autoCertificates: false` with `init()` and `createServingHttpsServer({ servername })`. A separate manager must provide certificates first. Calling `start()` still enables the standalone ACME lifecycle.
@@ -91,9 +91,9 @@ function get(port, servername, { pathname = '/', headers = {} } = {}) {
91
91
  });
92
92
  }
93
93
 
94
- function localGet(port, pathname = '/') {
94
+ function localGet(port, pathname = '/', hostname = 'localhost') {
95
95
  return new Promise((resolve, reject) => {
96
- http.get({ hostname: 'localhost', port, path: pathname, agent: false }, res => {
96
+ http.get({ hostname, port, path: pathname, agent: false }, res => {
97
97
  let body = '';
98
98
  res.on('data', chunk => { body += chunk; });
99
99
  res.on('end', () => resolve({ status: res.statusCode, body }));
@@ -315,6 +315,9 @@ it('isolates directory sites in local mode, including aliases and request plugin
315
315
  assert.equal(alias.pid, site.pid);
316
316
  assert.notEqual(other.pid, site.pid);
317
317
  assert.deepEqual(await localGet(port('other.example'), '/wp-login.php'), { status: 404, body: 'Not Found' });
318
+ for (const address of ['127.0.0.1', '::1']) {
319
+ assert.equal(JSON.parse((await localGet(port('site.example'), '/', address)).body).pid, site.pid);
320
+ }
318
321
  });
319
322
 
320
323
  it('serves a site module already loaded by the main process from that process', { timeout: 20000 }, async t => {
@@ -479,6 +482,7 @@ it('gives a socket site plain connections on its local port', { timeout: 20000 }
479
482
  await roster.start();
480
483
  const { port } = new URL(roster.getUrl('raw.example'));
481
484
  assert.notEqual(JSON.parse((await localGet(port)).body).pid, process.pid);
485
+ for (const address of ['127.0.0.1', '::1']) assert.equal((await localGet(port, '/', address)).status, 200);
482
486
  assert.equal(fs.readFileSync(`${marker}.head`, 'utf8'), '0');
483
487
  await roster.close();
484
488
  assert.equal(fs.readFileSync(marker, 'utf8'), 'closed');
@@ -94,7 +94,7 @@ it('waits for listening, shares start, and closes owned servers once', async t =
94
94
 
95
95
  it('rejects bind failures and rolls back already opened ports', async t => {
96
96
  const blocker = http.createServer();
97
- await new Promise(resolve => blocker.listen(0, 'localhost', resolve));
97
+ await new Promise(resolve => blocker.listen(0, '::', resolve));
98
98
  t.after(() => new Promise(resolve => blocker.close(resolve)));
99
99
  const roster = createRoster(t);
100
100
  const ports = [0, blocker.address().port];
@@ -372,6 +372,14 @@ describe('Roster', () => {
372
372
  });
373
373
  });
374
374
 
375
+ describe('local site description', () => {
376
+ it('names the production port only when it differs from the default', () => {
377
+ const roster = new Roster({ local: true });
378
+ assert.strictEqual(roster._describeLocalSite('truco.example.com', 443, 6701, 'http'), 'truco.example.com → http://truco.localhost:6701');
379
+ assert.strictEqual(roster._describeLocalSite('truco.example.com', 3334, 6702, 'tcp'), 'truco.example.com:3334 → tcp://truco.localhost:6702');
380
+ });
381
+ });
382
+
375
383
  describe('getUrl (exact domain)', () => {
376
384
  it('returns http://localhost:PORT in local mode for registered domain', () => {
377
385
  const roster = new Roster({ local: true });