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 +2 -2
- package/index.js +9 -3
- package/lib/site-worker.js +3 -2
- package/package.json +1 -1
- package/skills/roster-server/SKILL.md +1 -1
- package/test/isolation.test.js +6 -2
- package/test/lifecycle.test.js +1 -1
- package/test/roster-server.test.js +8 -0
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 `
|
|
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` | `::` |
|
|
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,
|
|
1634
|
-
|
|
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
|
|
package/lib/site-worker.js
CHANGED
|
@@ -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.
|
|
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
|
|
68
|
+
log.info(`🌐 ${this._describeLocalSite(domain, port, localPort, 'tcp')} (socket site)`);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
package/package.json
CHANGED
|
@@ -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 `
|
|
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.
|
package/test/isolation.test.js
CHANGED
|
@@ -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
|
|
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');
|
package/test/lifecycle.test.js
CHANGED
|
@@ -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, '
|
|
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 });
|