roster-server 1.9.0 β 1.9.4
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 +71 -4
- package/demo/custom-port-range.js +40 -0
- package/demo/local-url-example.js +40 -0
- package/demo/socketio.js +9 -1
- package/index.js +140 -54
- package/package.json +1 -1
- /package/demo/www/{subdomain.example.com β api.example.com}/index.js +0 -0
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,84 @@ 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
|
|
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
|
-
- `example.com` β `http://localhost:
|
|
191
|
-
- `api.example.com` β `http://localhost:
|
|
194
|
+
- `example.com` β `http://localhost:9465`
|
|
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 Local URLs
|
|
209
|
+
|
|
210
|
+
RosterServer provides two methods to get the local URL for a domain:
|
|
211
|
+
|
|
212
|
+
**1. Static Method (Predictable, No Instance Required):**
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Get the URL before starting the server (using default range 4000-9999)
|
|
216
|
+
const url = Roster.getLocalUrl('example.com');
|
|
217
|
+
console.log(url); // http://localhost:9465
|
|
218
|
+
|
|
219
|
+
// Or specify custom port range
|
|
220
|
+
const customUrl = Roster.getLocalUrl('example.com', {
|
|
221
|
+
minLocalPort: 5000,
|
|
222
|
+
maxLocalPort: 6000
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
This static method calculates the port deterministically using CRC32, so you can predict the URL before even creating a Roster instance.
|
|
227
|
+
|
|
228
|
+
**2. Instance Method (After Registration):**
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
const roster = new Roster({ local: true });
|
|
232
|
+
roster.register('example.com', handler);
|
|
233
|
+
|
|
234
|
+
await roster.start();
|
|
235
|
+
|
|
236
|
+
// Get the actual URL assigned to the domain
|
|
237
|
+
const url = roster.getLocalUrl('example.com');
|
|
238
|
+
console.log(url); // http://localhost:9465
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This instance method returns the actual URL assigned to the domain after the server starts. It's useful when you need to confirm the URL or when there might be port collisions.
|
|
242
|
+
|
|
243
|
+
**Example Usage:**
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
const roster = new Roster({ local: true });
|
|
247
|
+
|
|
248
|
+
roster.register('example.com', (httpsServer) => {
|
|
249
|
+
return (req, res) => {
|
|
250
|
+
res.writeHead(200);
|
|
251
|
+
res.end('Hello World!');
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
roster.start().then(() => {
|
|
256
|
+
const url = roster.getLocalUrl('example.com');
|
|
257
|
+
console.log(`Server available at: ${url}`);
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
194
261
|
## π§ A Touch of Magic
|
|
195
262
|
|
|
196
263
|
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,40 @@
|
|
|
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π Static URL Prediction with custom range (5000-5100):');
|
|
11
|
+
console.log('example.com β', Roster.getLocalUrl('example.com', {
|
|
12
|
+
minLocalPort: 5000,
|
|
13
|
+
maxLocalPort: 5100
|
|
14
|
+
}));
|
|
15
|
+
console.log('api.example.com β', Roster.getLocalUrl('api.example.com', {
|
|
16
|
+
minLocalPort: 5000,
|
|
17
|
+
maxLocalPort: 5100
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
roster.register('example.com', (httpsServer) => {
|
|
21
|
+
return (req, res) => {
|
|
22
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
23
|
+
res.end('Hello from example.com with custom port range!');
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
roster.register('api.example.com', (httpsServer) => {
|
|
28
|
+
return (req, res) => {
|
|
29
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
30
|
+
res.end(JSON.stringify({ message: 'API with custom port range' }));
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
roster.start().then(() => {
|
|
35
|
+
console.log('\nπ Server Started with custom port range:');
|
|
36
|
+
console.log('example.com β', roster.getLocalUrl('example.com'));
|
|
37
|
+
console.log('api.example.com β', roster.getLocalUrl('api.example.com'));
|
|
38
|
+
|
|
39
|
+
console.log('\nβ
Both domains running in custom port range (5000-5100)!');
|
|
40
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const Roster = require('../index.js');
|
|
2
|
+
|
|
3
|
+
// Example 1: Get URL before creating instance (static method)
|
|
4
|
+
console.log('\nπ Static URL Prediction (before server starts):');
|
|
5
|
+
console.log('example.com β', Roster.getLocalUrl('example.com'));
|
|
6
|
+
console.log('api.example.com β', Roster.getLocalUrl('api.example.com'));
|
|
7
|
+
console.log('test.example.com β', Roster.getLocalUrl('test.example.com'));
|
|
8
|
+
|
|
9
|
+
// Example 2: Get URL after registration (instance method)
|
|
10
|
+
const roster = new Roster({ local: true });
|
|
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!');
|
|
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 endpoint' }));
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
roster.start().then(() => {
|
|
27
|
+
console.log('\nπ Server Started - Actual URLs:');
|
|
28
|
+
console.log('example.com β', roster.getLocalUrl('example.com'));
|
|
29
|
+
console.log('api.example.com β', roster.getLocalUrl('api.example.com'));
|
|
30
|
+
|
|
31
|
+
// Test with www prefix (should return same URL)
|
|
32
|
+
console.log('\nπ Testing www prefix handling:');
|
|
33
|
+
console.log('www.example.com β', roster.getLocalUrl('www.example.com'));
|
|
34
|
+
|
|
35
|
+
// Test non-existent domain
|
|
36
|
+
console.log('\nβ Testing non-existent domain:');
|
|
37
|
+
console.log('nonexistent.com β', roster.getLocalUrl('nonexistent.com') || 'null (domain not registered)');
|
|
38
|
+
|
|
39
|
+
console.log('\nβ
All domains running!');
|
|
40
|
+
});
|
package/demo/socketio.js
CHANGED
|
@@ -26,4 +26,12 @@ roster.register('example.com', (httpsServer) => {
|
|
|
26
26
|
};
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
roster.start()
|
|
29
|
+
roster.start().then(() => {
|
|
30
|
+
// Get local URL for registered domain (requires instance)
|
|
31
|
+
const url = roster.getLocalUrl('example.com');
|
|
32
|
+
console.log(`β
Socket.IO server available at: ${url}`);
|
|
33
|
+
|
|
34
|
+
// Get local URL without instance (static method - predictable port)
|
|
35
|
+
const staticUrl = Roster.getLocalUrl('example.com');
|
|
36
|
+
console.log(`βΉοΈ Static prediction: ${staticUrl}`);
|
|
37
|
+
});
|
package/index.js
CHANGED
|
@@ -7,6 +7,32 @@ const { EventEmitter } = require('events');
|
|
|
7
7
|
const Greenlock = require('greenlock-express');
|
|
8
8
|
const log = require('lemonlog')('roster');
|
|
9
9
|
|
|
10
|
+
// CRC32 implementation for deterministic port assignment
|
|
11
|
+
function crc32(str) {
|
|
12
|
+
const crcTable = [];
|
|
13
|
+
for (let i = 0; i < 256; i++) {
|
|
14
|
+
let crc = i;
|
|
15
|
+
for (let j = 0; j < 8; j++) {
|
|
16
|
+
crc = (crc & 1) ? (0xEDB88320 ^ (crc >>> 1)) : (crc >>> 1);
|
|
17
|
+
}
|
|
18
|
+
crcTable[i] = crc;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let crc = 0xFFFFFFFF;
|
|
22
|
+
for (let i = 0; i < str.length; i++) {
|
|
23
|
+
const byte = str.charCodeAt(i);
|
|
24
|
+
crc = (crc >>> 8) ^ crcTable[(crc ^ byte) & 0xFF];
|
|
25
|
+
}
|
|
26
|
+
return (crc ^ 0xFFFFFFFF) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Convert CRC32 hash to a port number in available range
|
|
30
|
+
function domainToPort(domain, minPort = 3000, maxPort = 65535) {
|
|
31
|
+
const hash = crc32(domain);
|
|
32
|
+
const portRange = maxPort - minPort + 1;
|
|
33
|
+
return minPort + (hash % portRange);
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
// Virtual Server that completely isolates applications
|
|
11
37
|
class VirtualServer extends EventEmitter {
|
|
12
38
|
constructor(domain) {
|
|
@@ -14,7 +40,7 @@ class VirtualServer extends EventEmitter {
|
|
|
14
40
|
this.domain = domain;
|
|
15
41
|
this.requestListeners = [];
|
|
16
42
|
this.upgradeListeners = [];
|
|
17
|
-
|
|
43
|
+
|
|
18
44
|
// Simulate http.Server properties
|
|
19
45
|
this.listening = false;
|
|
20
46
|
this.address = () => ({ port: 443, family: 'IPv4', address: '0.0.0.0' });
|
|
@@ -23,7 +49,7 @@ class VirtualServer extends EventEmitter {
|
|
|
23
49
|
this.headersTimeout = 60000;
|
|
24
50
|
this.maxHeadersCount = null;
|
|
25
51
|
}
|
|
26
|
-
|
|
52
|
+
|
|
27
53
|
// Override listener methods to capture them
|
|
28
54
|
on(event, listener) {
|
|
29
55
|
if (event === 'request') {
|
|
@@ -33,11 +59,11 @@ class VirtualServer extends EventEmitter {
|
|
|
33
59
|
}
|
|
34
60
|
return super.on(event, listener);
|
|
35
61
|
}
|
|
36
|
-
|
|
62
|
+
|
|
37
63
|
addListener(event, listener) {
|
|
38
64
|
return this.on(event, listener);
|
|
39
65
|
}
|
|
40
|
-
|
|
66
|
+
|
|
41
67
|
// Socket.IO compatibility methods
|
|
42
68
|
listeners(event) {
|
|
43
69
|
if (event === 'request') {
|
|
@@ -47,7 +73,7 @@ class VirtualServer extends EventEmitter {
|
|
|
47
73
|
}
|
|
48
74
|
return super.listeners(event);
|
|
49
75
|
}
|
|
50
|
-
|
|
76
|
+
|
|
51
77
|
removeListener(event, listener) {
|
|
52
78
|
if (event === 'request') {
|
|
53
79
|
const index = this.requestListeners.indexOf(listener);
|
|
@@ -62,7 +88,7 @@ class VirtualServer extends EventEmitter {
|
|
|
62
88
|
}
|
|
63
89
|
return super.removeListener(event, listener);
|
|
64
90
|
}
|
|
65
|
-
|
|
91
|
+
|
|
66
92
|
removeAllListeners(event) {
|
|
67
93
|
if (event === 'request') {
|
|
68
94
|
this.requestListeners = [];
|
|
@@ -71,33 +97,33 @@ class VirtualServer extends EventEmitter {
|
|
|
71
97
|
}
|
|
72
98
|
return super.removeAllListeners(event);
|
|
73
99
|
}
|
|
74
|
-
|
|
100
|
+
|
|
75
101
|
// Simulate other http.Server methods
|
|
76
102
|
listen() { this.listening = true; return this; }
|
|
77
103
|
close() { this.listening = false; return this; }
|
|
78
104
|
setTimeout() { return this; }
|
|
79
|
-
|
|
105
|
+
|
|
80
106
|
// Process request with this virtual server's listeners
|
|
81
107
|
processRequest(req, res) {
|
|
82
108
|
let handled = false;
|
|
83
|
-
|
|
109
|
+
|
|
84
110
|
// Track if response was handled
|
|
85
111
|
const originalEnd = res.end;
|
|
86
|
-
res.end = function(...args) {
|
|
112
|
+
res.end = function (...args) {
|
|
87
113
|
handled = true;
|
|
88
114
|
return originalEnd.apply(this, args);
|
|
89
115
|
};
|
|
90
|
-
|
|
116
|
+
|
|
91
117
|
// Try all listeners
|
|
92
118
|
for (const listener of this.requestListeners) {
|
|
93
119
|
if (!handled) {
|
|
94
120
|
listener(req, res);
|
|
95
121
|
}
|
|
96
122
|
}
|
|
97
|
-
|
|
123
|
+
|
|
98
124
|
// Restore original end method
|
|
99
125
|
res.end = originalEnd;
|
|
100
|
-
|
|
126
|
+
|
|
101
127
|
// If no listener handled the request, try fallback handler
|
|
102
128
|
if (!handled && this.fallbackHandler) {
|
|
103
129
|
this.fallbackHandler(req, res);
|
|
@@ -106,14 +132,14 @@ class VirtualServer extends EventEmitter {
|
|
|
106
132
|
res.end('No handler found');
|
|
107
133
|
}
|
|
108
134
|
}
|
|
109
|
-
|
|
135
|
+
|
|
110
136
|
// Process upgrade events (WebSocket)
|
|
111
137
|
processUpgrade(req, socket, head) {
|
|
112
138
|
// Emit to all registered upgrade listeners
|
|
113
139
|
for (const listener of this.upgradeListeners) {
|
|
114
140
|
listener(req, socket, head);
|
|
115
141
|
}
|
|
116
|
-
|
|
142
|
+
|
|
117
143
|
// If no listeners, destroy the socket
|
|
118
144
|
if (this.upgradeListeners.length === 0) {
|
|
119
145
|
socket.destroy();
|
|
@@ -134,8 +160,12 @@ class Roster {
|
|
|
134
160
|
this.sites = {};
|
|
135
161
|
this.domainServers = {}; // Store separate servers for each domain
|
|
136
162
|
this.portServers = {}; // Store servers by port
|
|
163
|
+
this.domainPorts = {}; // Store domain β port mapping for local mode
|
|
164
|
+
this.assignedPorts = new Set(); // Track ports assigned to domains (not OS availability)
|
|
137
165
|
this.hostname = options.hostname || '0.0.0.0';
|
|
138
166
|
this.filename = options.filename || 'index';
|
|
167
|
+
this.minLocalPort = options.minLocalPort || 4000;
|
|
168
|
+
this.maxLocalPort = options.maxLocalPort || 9999;
|
|
139
169
|
|
|
140
170
|
const port = options.port === undefined ? 443 : options.port;
|
|
141
171
|
if (port === 80 && !this.local) {
|
|
@@ -306,7 +336,7 @@ class Roster {
|
|
|
306
336
|
}
|
|
307
337
|
|
|
308
338
|
const { domain, port } = this.parseDomainWithPort(domainString);
|
|
309
|
-
|
|
339
|
+
|
|
310
340
|
const domainEntries = [domain];
|
|
311
341
|
if ((domain.match(/\./g) || []).length < 2) {
|
|
312
342
|
domainEntries.push(`www.${domain}`);
|
|
@@ -336,10 +366,64 @@ class Roster {
|
|
|
336
366
|
return { domain: domainString, port: this.defaultPort };
|
|
337
367
|
}
|
|
338
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Get the local URL for a domain when running in local mode
|
|
371
|
+
* @param {string} domain - The domain name (e.g., 'example.com')
|
|
372
|
+
* @param {Object} options - Optional configuration
|
|
373
|
+
* @param {number} options.minLocalPort - Minimum port range (default: 4000)
|
|
374
|
+
* @param {number} options.maxLocalPort - Maximum port range (default: 9999)
|
|
375
|
+
* @returns {string} The local URL (e.g., 'http://localhost:4321')
|
|
376
|
+
*/
|
|
377
|
+
static getLocalUrl(domain, options = {}) {
|
|
378
|
+
const minPort = options.minLocalPort || 4000;
|
|
379
|
+
const maxPort = options.maxLocalPort || 9999;
|
|
380
|
+
|
|
381
|
+
// Remove www prefix if present
|
|
382
|
+
const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
|
|
383
|
+
|
|
384
|
+
// Calculate deterministic port
|
|
385
|
+
const port = domainToPort(cleanDomain, minPort, maxPort);
|
|
386
|
+
|
|
387
|
+
return `http://localhost:${port}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the local URL for a domain that was registered on this instance
|
|
392
|
+
* @param {string} domain - The domain name
|
|
393
|
+
* @returns {string|null} The local URL if found, null otherwise
|
|
394
|
+
*/
|
|
395
|
+
getLocalUrl(domain) {
|
|
396
|
+
// Remove www prefix if present
|
|
397
|
+
const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain;
|
|
398
|
+
|
|
399
|
+
// Check if domain has a port assigned
|
|
400
|
+
if (this.domainPorts && this.domainPorts[cleanDomain]) {
|
|
401
|
+
return `http://localhost:${this.domainPorts[cleanDomain]}`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
339
407
|
createVirtualServer(domain) {
|
|
340
408
|
return new VirtualServer(domain);
|
|
341
409
|
}
|
|
342
410
|
|
|
411
|
+
// Assign port to domain, detecting collisions with already assigned ports
|
|
412
|
+
assignPortToDomain(domain) {
|
|
413
|
+
let port = domainToPort(domain, this.minLocalPort, this.maxLocalPort);
|
|
414
|
+
|
|
415
|
+
// If port is already assigned to another domain, increment until we find a free one
|
|
416
|
+
while (this.assignedPorts.has(port)) {
|
|
417
|
+
port++;
|
|
418
|
+
if (port > this.maxLocalPort) {
|
|
419
|
+
port = this.minLocalPort; // Wrap around if we exceed max port
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
this.assignedPorts.add(port);
|
|
424
|
+
return port;
|
|
425
|
+
}
|
|
426
|
+
|
|
343
427
|
// Get SSL context from Greenlock for custom ports
|
|
344
428
|
async getSSLContext(domain, greenlock) {
|
|
345
429
|
try {
|
|
@@ -353,39 +437,43 @@ class Roster {
|
|
|
353
437
|
}
|
|
354
438
|
} catch (error) {
|
|
355
439
|
}
|
|
356
|
-
|
|
440
|
+
|
|
357
441
|
// Return undefined to let HTTPS server handle SNI callback
|
|
358
442
|
return null;
|
|
359
443
|
}
|
|
360
444
|
|
|
361
445
|
// Start server in local mode with HTTP - simplified version
|
|
362
446
|
startLocalMode() {
|
|
363
|
-
|
|
364
|
-
|
|
447
|
+
// Store mapping of domain to port for later retrieval
|
|
448
|
+
this.domainPorts = {};
|
|
365
449
|
|
|
366
|
-
// Create a simple HTTP server for each domain with
|
|
450
|
+
// Create a simple HTTP server for each domain with CRC32-based ports
|
|
367
451
|
for (const [hostKey, siteApp] of Object.entries(this.sites)) {
|
|
368
452
|
const domain = hostKey.split(':')[0]; // Remove port if present
|
|
369
|
-
|
|
453
|
+
|
|
370
454
|
// Skip www domains in local mode
|
|
371
455
|
if (domain.startsWith('www.')) {
|
|
372
456
|
continue;
|
|
373
457
|
}
|
|
458
|
+
|
|
459
|
+
// Calculate deterministic port based on domain CRC32, with collision detection
|
|
460
|
+
const port = this.assignPortToDomain(domain);
|
|
374
461
|
|
|
375
|
-
|
|
376
|
-
|
|
462
|
+
// Store domain β port mapping
|
|
463
|
+
this.domainPorts[domain] = port;
|
|
464
|
+
|
|
377
465
|
// Create virtual server for the domain
|
|
378
466
|
const virtualServer = this.createVirtualServer(domain);
|
|
379
467
|
this.domainServers[domain] = virtualServer;
|
|
380
|
-
|
|
468
|
+
|
|
381
469
|
// Initialize app with virtual server
|
|
382
470
|
const appHandler = siteApp(virtualServer);
|
|
383
|
-
|
|
471
|
+
|
|
384
472
|
// Create simple dispatcher for this domain
|
|
385
473
|
const dispatcher = (req, res) => {
|
|
386
474
|
// Set fallback handler on virtual server for non-Socket.IO requests
|
|
387
475
|
virtualServer.fallbackHandler = appHandler;
|
|
388
|
-
|
|
476
|
+
|
|
389
477
|
if (virtualServer.requestListeners.length > 0) {
|
|
390
478
|
virtualServer.processRequest(req, res);
|
|
391
479
|
} else if (appHandler) {
|
|
@@ -395,34 +483,32 @@ class Roster {
|
|
|
395
483
|
res.end('Site not found');
|
|
396
484
|
}
|
|
397
485
|
};
|
|
398
|
-
|
|
486
|
+
|
|
399
487
|
// Create HTTP server for this domain
|
|
400
488
|
const httpServer = http.createServer(dispatcher);
|
|
401
489
|
this.portServers[port] = httpServer;
|
|
402
|
-
|
|
490
|
+
|
|
403
491
|
// Handle WebSocket upgrade events
|
|
404
492
|
httpServer.on('upgrade', (req, socket, head) => {
|
|
405
493
|
virtualServer.processUpgrade(req, socket, head);
|
|
406
494
|
});
|
|
407
|
-
|
|
495
|
+
|
|
408
496
|
httpServer.listen(port, 'localhost', () => {
|
|
409
497
|
log.info(`π ${domain} β http://localhost:${port}`);
|
|
410
498
|
});
|
|
411
|
-
|
|
499
|
+
|
|
412
500
|
httpServer.on('error', (error) => {
|
|
413
501
|
log.error(`β Error on port ${port} for ${domain}:`, error.message);
|
|
414
502
|
});
|
|
415
|
-
|
|
416
|
-
currentPort++;
|
|
417
503
|
}
|
|
418
|
-
|
|
419
|
-
log.info(`(β) Started ${
|
|
504
|
+
|
|
505
|
+
log.info(`(β) Started ${Object.keys(this.portServers).length} sites in local mode`);
|
|
420
506
|
return Promise.resolve();
|
|
421
507
|
}
|
|
422
508
|
|
|
423
509
|
async start() {
|
|
424
510
|
await this.loadSites();
|
|
425
|
-
|
|
511
|
+
|
|
426
512
|
// Skip Greenlock configuration generation in local mode
|
|
427
513
|
if (!this.local) {
|
|
428
514
|
this.generateConfigJson();
|
|
@@ -443,7 +529,7 @@ class Roster {
|
|
|
443
529
|
|
|
444
530
|
return greenlock.ready(glx => {
|
|
445
531
|
const httpServer = glx.httpServer();
|
|
446
|
-
|
|
532
|
+
|
|
447
533
|
// Group sites by port
|
|
448
534
|
const sitesByPort = {};
|
|
449
535
|
for (const [hostKey, siteApp] of Object.entries(this.sites)) {
|
|
@@ -455,12 +541,12 @@ class Roster {
|
|
|
455
541
|
appHandlers: {}
|
|
456
542
|
};
|
|
457
543
|
}
|
|
458
|
-
|
|
544
|
+
|
|
459
545
|
// Create completely isolated virtual server
|
|
460
546
|
const virtualServer = this.createVirtualServer(domain);
|
|
461
547
|
sitesByPort[port].virtualServers[domain] = virtualServer;
|
|
462
548
|
this.domainServers[domain] = virtualServer;
|
|
463
|
-
|
|
549
|
+
|
|
464
550
|
// Initialize app with virtual server
|
|
465
551
|
const appHandler = siteApp(virtualServer);
|
|
466
552
|
sitesByPort[port].appHandlers[domain] = appHandler;
|
|
@@ -472,11 +558,11 @@ class Roster {
|
|
|
472
558
|
const createDispatcher = (portData) => {
|
|
473
559
|
return (req, res) => {
|
|
474
560
|
const host = req.headers.host || '';
|
|
475
|
-
|
|
561
|
+
|
|
476
562
|
// Remove port from host header if present (e.g., "domain.com:8080" -> "domain.com")
|
|
477
563
|
const hostWithoutPort = host.split(':')[0];
|
|
478
564
|
const domain = hostWithoutPort.startsWith('www.') ? hostWithoutPort.slice(4) : hostWithoutPort;
|
|
479
|
-
|
|
565
|
+
|
|
480
566
|
// Handle www redirects
|
|
481
567
|
if (hostWithoutPort.startsWith('www.')) {
|
|
482
568
|
res.writeHead(301, { Location: `https://${domain}${req.url}` });
|
|
@@ -486,7 +572,7 @@ class Roster {
|
|
|
486
572
|
|
|
487
573
|
const virtualServer = portData.virtualServers[domain];
|
|
488
574
|
const appHandler = portData.appHandlers[domain];
|
|
489
|
-
|
|
575
|
+
|
|
490
576
|
if (virtualServer && virtualServer.requestListeners.length > 0) {
|
|
491
577
|
// Set fallback handler on virtual server for non-Socket.IO requests
|
|
492
578
|
virtualServer.fallbackHandler = appHandler;
|
|
@@ -512,9 +598,9 @@ class Roster {
|
|
|
512
598
|
const host = req.headers.host || '';
|
|
513
599
|
const hostWithoutPort = host.split(':')[0];
|
|
514
600
|
const domain = hostWithoutPort.startsWith('www.') ? hostWithoutPort.slice(4) : hostWithoutPort;
|
|
515
|
-
|
|
601
|
+
|
|
516
602
|
const virtualServer = portData.virtualServers[domain];
|
|
517
|
-
|
|
603
|
+
|
|
518
604
|
if (virtualServer) {
|
|
519
605
|
virtualServer.processUpgrade(req, socket, head);
|
|
520
606
|
} else {
|
|
@@ -529,15 +615,15 @@ class Roster {
|
|
|
529
615
|
const portNum = parseInt(port);
|
|
530
616
|
const dispatcher = createDispatcher(portData);
|
|
531
617
|
const upgradeHandler = createUpgradeHandler(portData);
|
|
532
|
-
|
|
618
|
+
|
|
533
619
|
if (portNum === this.defaultPort) {
|
|
534
620
|
// Use Greenlock for default port (443) with SSL
|
|
535
621
|
const httpsServer = glx.httpsServer(null, dispatcher);
|
|
536
622
|
this.portServers[portNum] = httpsServer;
|
|
537
|
-
|
|
623
|
+
|
|
538
624
|
// Handle WebSocket upgrade events
|
|
539
625
|
httpsServer.on('upgrade', upgradeHandler);
|
|
540
|
-
|
|
626
|
+
|
|
541
627
|
httpsServer.listen(portNum, this.hostname, () => {
|
|
542
628
|
log.info(`HTTPS server listening on port ${portNum}`);
|
|
543
629
|
});
|
|
@@ -551,12 +637,12 @@ class Roster {
|
|
|
551
637
|
const keyPath = path.join(certPath, 'privkey.pem');
|
|
552
638
|
const certFilePath = path.join(certPath, 'cert.pem');
|
|
553
639
|
const chainPath = path.join(certPath, 'chain.pem');
|
|
554
|
-
|
|
640
|
+
|
|
555
641
|
if (fs.existsSync(keyPath) && fs.existsSync(certFilePath) && fs.existsSync(chainPath)) {
|
|
556
642
|
const key = fs.readFileSync(keyPath, 'utf8');
|
|
557
643
|
const cert = fs.readFileSync(certFilePath, 'utf8');
|
|
558
644
|
const chain = fs.readFileSync(chainPath, 'utf8');
|
|
559
|
-
|
|
645
|
+
|
|
560
646
|
callback(null, tls.createSecureContext({
|
|
561
647
|
key: key,
|
|
562
648
|
cert: cert + chain
|
|
@@ -569,25 +655,25 @@ class Roster {
|
|
|
569
655
|
}
|
|
570
656
|
}
|
|
571
657
|
};
|
|
572
|
-
|
|
658
|
+
|
|
573
659
|
const httpsServer = https.createServer(httpsOptions, dispatcher);
|
|
574
|
-
|
|
660
|
+
|
|
575
661
|
// Handle WebSocket upgrade events
|
|
576
662
|
httpsServer.on('upgrade', upgradeHandler);
|
|
577
|
-
|
|
663
|
+
|
|
578
664
|
httpsServer.on('error', (error) => {
|
|
579
665
|
log.error(`HTTPS server error on port ${portNum}:`, error.message);
|
|
580
666
|
});
|
|
581
|
-
|
|
667
|
+
|
|
582
668
|
httpsServer.on('tlsClientError', (error) => {
|
|
583
669
|
// Suppress HTTP request errors to avoid log spam
|
|
584
670
|
if (!error.message.includes('http request')) {
|
|
585
671
|
log.error(`TLS error on port ${portNum}:`, error.message);
|
|
586
672
|
}
|
|
587
673
|
});
|
|
588
|
-
|
|
674
|
+
|
|
589
675
|
this.portServers[portNum] = httpsServer;
|
|
590
|
-
|
|
676
|
+
|
|
591
677
|
httpsServer.listen(portNum, this.hostname, (error) => {
|
|
592
678
|
if (error) {
|
|
593
679
|
log.error(`Failed to start HTTPS server on port ${portNum}:`, error.message);
|
package/package.json
CHANGED
|
File without changes
|