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 +67 -2
- package/demo/custom-port-range.js +32 -0
- package/demo/environment-aware-url.js +55 -0
- package/demo/local-url-example.js +36 -0
- package/demo/socketio.js +5 -1
- package/demo/test-geturl.js +64 -0
- package/index.js +43 -5
- package/package.json +1 -1
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
|
|
372
|
-
let port = domainToPort(domain,
|
|
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 >
|
|
378
|
-
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
|
|
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);
|