roster-server 1.3.0 → 1.4.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 CHANGED
@@ -26,6 +26,7 @@ Your project should look something like this:
26
26
 
27
27
  ```
28
28
  /srv/
29
+ ├── greenlock.d/
29
30
  ├── roster/server.js
30
31
  └── www/
31
32
  ├── example.com/
@@ -38,10 +39,11 @@ Your project should look something like this:
38
39
 
39
40
  ```javascript
40
41
  // /srv/roster/server.js
41
- const Roster = require('roster-express');
42
+ const Roster = require('roster-server');
42
43
 
43
44
  const options = {
44
45
  maintainerEmail: 'admin@example.com',
46
+ greenlockConfigDir: '/srv/greenlock.d', // Path to your Greenlock configuration directory
45
47
  wwwPath: '/srv/www', // Path to your 'www' directory (default: '../www')
46
48
  staging: false // Set to true for Let's Encrypt staging environment
47
49
  };
@@ -54,12 +56,59 @@ server.start();
54
56
 
55
57
  Each domain should have its own folder under `www`, containing an `index.js` that exports a request handler function.
56
58
 
57
- For example, `www/example.com/index.js`:
59
+ ### Examples
58
60
 
59
- ```javascript
60
- module.exports = (req, res) => {
61
- res.writeHead(200, { 'Content-Type': 'text/plain' });
62
- res.end('Hello from example.com!');
61
+ I'll help analyze the example files shown. You have 3 different implementations demonstrating various ways to handle requests in RosterServer:
62
+
63
+ 1. **Basic HTTP Handler**:
64
+ ```javascript:demo/www/example.com/index.js
65
+ module.exports = (httpsServer) => {
66
+ return (req, res) => {
67
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
68
+ res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
69
+ };
70
+ };
71
+ ```
72
+
73
+ 2. **Express App**:
74
+ ```javascript:demo/www/express.example.com/index.js
75
+ const express = require('express');
76
+
77
+ module.exports = (httpsServer) => {
78
+ const app = express();
79
+ app.get('/', (req, res) => {
80
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
81
+ res.send('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
82
+ });
83
+
84
+ return app;
85
+ }
86
+ ```
87
+
88
+ 3. **Socket.IO Server**:
89
+ ```javascript:demo/www/sio.example.com/index.js
90
+ const { Server } = require('socket.io');
91
+
92
+ module.exports = (httpsServer) => {
93
+ const io = new Server(httpsServer);
94
+
95
+ io.on('connection', (socket) => {
96
+ console.log('A user connected');
97
+
98
+ socket.on('chat:message', (msg) => {
99
+ console.log('Message received:', msg);
100
+ io.emit('chat:message', msg);
101
+ });
102
+
103
+ socket.on('disconnect', () => {
104
+ console.log('User disconnected');
105
+ });
106
+ });
107
+
108
+ return (req, res) => {
109
+ res.writeHead(200);
110
+ res.end('Socket.IO server running');
111
+ };
63
112
  };
64
113
  ```
65
114
 
@@ -76,7 +125,7 @@ And that's it! Your server is now hosting multiple HTTPS-enabled sites. 🎉
76
125
 
77
126
  ### Automatic SSL Certificate Management
78
127
 
79
- RosterExpress uses [greenlock-express](https://www.npmjs.com/package/greenlock-express) to automatically obtain and renew SSL certificates from Let's Encrypt. No need to manually manage certificates ever again. Unless you enjoy that sort of thing. 🧐
128
+ RosterServer uses [greenlock-express](https://www.npmjs.com/package/greenlock-express) to automatically obtain and renew SSL certificates from Let's Encrypt. No need to manually manage certificates ever again. Unless you enjoy that sort of thing. 🧐
80
129
 
81
130
  ### Redirects from `www`
82
131
 
@@ -84,11 +133,11 @@ All requests to `www.yourdomain.com` are automatically redirected to `yourdomain
84
133
 
85
134
  ### Dynamic Site Loading
86
135
 
87
- Add a new site? Just drop it into the `www` folder with an `index.js` file, and RosterExpress will handle the rest. No need to restart the server. Well, you might need to restart the server. But that's what `nodemon` is for, right? 😅
136
+ Add a new site? Just drop it into the `www` folder with an `index.js` file, and RosterServer will handle the rest. No need to restart the server. Well, you might need to restart the server. But that's what `nodemon` is for, right? 😅
88
137
 
89
138
  ## ⚙️ Configuration Options
90
139
 
91
- When creating a new `RosterExpress` instance, you can pass the following options:
140
+ When creating a new `RosterServer` instance, you can pass the following options:
92
141
 
93
142
  - `maintainerEmail` (string): Your email for Let's Encrypt notifications.
94
143
  - `wwwPath` (string): Path to your `www` directory containing your sites.
@@ -97,14 +146,14 @@ When creating a new `RosterExpress` instance, you can pass the following options
97
146
 
98
147
  ## 🧂 A Touch of Magic
99
148
 
100
- You might be thinking, "But setting up HTTPS and virtual hosts is supposed to be complicated and time-consuming!" Well, not anymore. With RosterExpress, you can get back to writing code that matters, like defending Earth from alien invaders! 👾👾👾
149
+ 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! 👾👾👾
101
150
 
102
151
 
103
152
  ## 🤝 Contributing
104
153
 
105
154
  Feel free to submit issues or pull requests. Or don't. I'm not your boss. 😜
106
155
 
107
- If you find any issues or have suggestions for improvement, please open an issue or submit a pull request on the [GitHub repository](https://github.com/clasen/RosterExpress).
156
+ If you find any issues or have suggestions for improvement, please open an issue or submit a pull request on the [GitHub repository](https://github.com/clasen/RosterServer).
108
157
 
109
158
  ## 🙏 Acknowledgments
110
159
 
@@ -1,4 +1,6 @@
1
- module.exports = (req, res) => {
2
- res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
3
- res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
1
+ module.exports = (httpsServer) => {
2
+ return (req, res) => {
3
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
4
+ res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
5
+ };
4
6
  };
@@ -0,0 +1,11 @@
1
+ const express = require('express');
2
+
3
+ module.exports = (httpsServer) => {
4
+ const app = express();
5
+ app.get('/', (req, res) => {
6
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
7
+ res.send('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
8
+ });
9
+
10
+ return app;
11
+ }
@@ -1,7 +1,7 @@
1
1
  const { io } = require("socket.io-client");
2
2
 
3
3
  // Connect to the server
4
- const socket = io('https://test.zentara.group', {
4
+ const socket = io('https://sio.example.com', {
5
5
  rejectUnauthorized: false // Only use this in development
6
6
  });
7
7
 
@@ -1,18 +1,24 @@
1
1
  const { Server } = require('socket.io');
2
2
 
3
- const attach = new Server();
3
+ module.exports = (httpsServer) => {
4
+ const io = new Server(httpsServer);
4
5
 
5
- attach.on('connection', (socket) => {
6
- console.log('A user connected');
6
+ io.on('connection', (socket) => {
7
+ console.log('A user connected');
7
8
 
8
- socket.on('chat:message', (msg) => {
9
- console.log('Message received:', msg);
10
- io.emit('chat:message', msg);
11
- });
9
+ socket.on('chat:message', (msg) => {
10
+ console.log('Message received:', msg);
11
+ io.emit('chat:message', msg);
12
+ });
12
13
 
13
- socket.on('disconnect', () => {
14
- console.log('User disconnected');
14
+ socket.on('disconnect', () => {
15
+ console.log('User disconnected');
16
+ });
15
17
  });
16
- });
17
18
 
18
- module.exports = { attach };
19
+ // Devolvemos el handler para las peticiones HTTP
20
+ return (req, res) => {
21
+ res.writeHead(200);
22
+ res.end('Socket.IO server running');
23
+ };
24
+ };
@@ -1,8 +1,6 @@
1
- const https = require('https');
2
-
3
- const server = https.createServer((req, res) => {
4
- res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
5
- res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
6
- });
7
-
8
- module.exports = server;
1
+ module.exports = (httpsServer) => {
2
+ return (req, res) => {
3
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
4
+ res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
5
+ };
6
+ };
package/index.js CHANGED
@@ -7,19 +7,18 @@ class Roster {
7
7
  this.maintainerEmail = options.maintainerEmail || 'admin@example.com';
8
8
  this.wwwPath = options.wwwPath || path.join(__dirname, '..', '..', '..', 'www');
9
9
  this.greenlockConfigDir = options.greenlockConfigDir || path.join(__dirname, '..', '..', 'greenlock.d');
10
- this.staging = options.staging || false; // Set to true for testing
10
+ this.staging = options.staging || false;
11
11
  this.domains = [];
12
12
  this.sites = {};
13
13
  }
14
14
 
15
- // Function to dynamically load domain applications
16
15
  loadSites() {
17
16
  fs.readdirSync(this.wwwPath, { withFileTypes: true })
18
17
  .filter(dirent => dirent.isDirectory())
19
18
  .forEach((dirent) => {
20
19
  const domain = dirent.name;
21
20
  const domainPath = path.join(this.wwwPath, domain);
22
-
21
+
23
22
  const possibleIndexFiles = ['index.js', 'index.mjs', 'index.cjs'];
24
23
  let siteApp;
25
24
  let loadedFile;
@@ -27,8 +26,7 @@ class Roster {
27
26
  for (const indexFile of possibleIndexFiles) {
28
27
  const indexPath = path.join(domainPath, indexFile);
29
28
  if (fs.existsSync(indexPath)) {
30
- const module = require(indexPath);
31
- siteApp = typeof module === 'function' ? module() : module;
29
+ siteApp = require(indexPath);
32
30
  loadedFile = indexFile;
33
31
  break;
34
32
  }
@@ -42,9 +40,6 @@ class Roster {
42
40
  });
43
41
 
44
42
  console.log(`✅ Loaded site: ${domain} (using ${loadedFile})`);
45
- if (siteApp.attach) {
46
- console.log(`🔌 Attachable server detected for ${domain}`);
47
- }
48
43
  } else {
49
44
  console.warn(`⚠️ No index file (js/mjs/cjs) found in ${domainPath}`);
50
45
  }
@@ -55,7 +50,6 @@ class Roster {
55
50
  const configDir = this.greenlockConfigDir;
56
51
  const configPath = path.join(configDir, 'config.json');
57
52
 
58
- // Create the directory if it does not exist
59
53
  if (!fs.existsSync(configDir)) {
60
54
  fs.mkdirSync(configDir, { recursive: true });
61
55
  }
@@ -68,10 +62,8 @@ class Roster {
68
62
  uniqueDomains.add(rootDomain);
69
63
  });
70
64
 
71
- // Read the existing config.json if it exists
72
65
  let existingConfig = {};
73
66
  if (fs.existsSync(configPath)) {
74
- // Read the current content
75
67
  const currentConfigContent = fs.readFileSync(configPath, 'utf8');
76
68
  existingConfig = JSON.parse(currentConfigContent);
77
69
  }
@@ -82,7 +74,6 @@ class Roster {
82
74
  altnames.push(`www.${domain}`);
83
75
  }
84
76
 
85
- // Find the existing site to preserve renewAt
86
77
  let existingSite = null;
87
78
  if (existingConfig.sites) {
88
79
  existingSite = existingConfig.sites.find(site => site.subject === domain);
@@ -93,7 +84,6 @@ class Roster {
93
84
  altnames: altnames
94
85
  };
95
86
 
96
- // Preserve renewAt if it exists
97
87
  if (existingSite && existingSite.renewAt) {
98
88
  siteConfig.renewAt = existingSite.renewAt;
99
89
  }
@@ -121,34 +111,29 @@ class Roster {
121
111
  sites: sitesConfig
122
112
  };
123
113
 
124
- // Check if config.json already exists and compare
125
114
  if (fs.existsSync(configPath)) {
126
- // Read the current content
127
115
  const currentConfigContent = fs.readFileSync(configPath, 'utf8');
128
116
  const currentConfig = JSON.parse(currentConfigContent);
129
117
 
130
- // Compare the entire configurations
131
118
  const newConfigContent = JSON.stringify(newConfig, null, 2);
132
119
  const currentConfigContentFormatted = JSON.stringify(currentConfig, null, 2);
133
120
 
134
121
  if (newConfigContent === currentConfigContentFormatted) {
135
122
  console.log('ℹ️ Configuration has not changed. config.json will not be overwritten.');
136
- return; // Exit the function without overwriting
137
- } else {
138
- console.log('🔄 Configuration has changed. config.json will be updated.');
123
+ return;
139
124
  }
125
+ console.log('🔄 Configuration has changed. config.json will be updated.');
140
126
  } else {
141
127
  console.log('🆕 config.json does not exist. A new one will be created.');
142
128
  }
143
129
 
144
- // Write the new config.json
145
130
  fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
146
131
  console.log(`📁 config.json generated at ${configPath}`);
147
132
  }
148
133
 
149
134
  handleRequest(req, res) {
150
135
  const host = req.headers.host || '';
151
-
136
+
152
137
  if (host.startsWith('www.')) {
153
138
  const newHost = host.slice(4);
154
139
  res.writeHead(301, { Location: `https://${newHost}${req.url}` });
@@ -158,79 +143,69 @@ class Roster {
158
143
 
159
144
  const siteApp = this.sites[host];
160
145
  if (siteApp) {
161
- if (siteApp.attach) {
162
- // Para servidores attachables (como Socket.IO)
163
- res.writeHead(200);
164
- res.end('Server running');
165
- } else if (siteApp.emit) {
166
- // Para servidores http/https (como subdomain.example.com)
167
- siteApp.emit('request', req, res);
168
- } else {
169
- // Para funciones de manejo directo (como example.com)
170
- siteApp(req, res);
171
- }
146
+ siteApp(req, res);
172
147
  } else {
173
148
  res.writeHead(404);
174
149
  res.end('Site not found');
175
150
  }
176
151
  }
177
152
 
178
- initGreenlock() {
179
- Greenlock.init({
180
- packageRoot: __dirname,
181
- configDir: this.greenlockConfigDir,
182
- maintainerEmail: this.maintainerEmail,
183
- cluster: false,
184
- staging: this.staging,
185
- manager: { module: "@greenlock/manager" },
186
- approveDomains: (opts, certs, cb) => {
187
- // If certs is defined, we already have a certificate and are renewing it
188
- if (certs) {
189
- opts.domains = certs.altnames;
190
- } else {
191
- // If it's a new request, verify if the domain is in our list
192
- if (this.domains.includes(opts.domain)) {
193
- opts.email = this.maintainerEmail;
194
- opts.agreeTos = true;
195
- opts.domains = [opts.domain];
196
- } else {
197
- console.warn(`⚠️ Domain not approved: ${opts.domain}`);
198
- return cb(new Error(`Domain not approved: ${opts.domain}`));
199
- }
200
- }
201
- cb(null, { options: opts, certs });
202
- }
203
- }).ready((glx) => {
204
- // Setup HTTPS server
205
- const httpsServer = glx.httpsServer(null, (req, res) => {
206
- this.handleRequest(req, res);
207
- });
153
+ initServers(glx) {
154
+ const app = (req, res) => {
155
+ this.handleRequest(req, res);
156
+ };
208
157
 
209
- // Attach any servers to the HTTPS server
210
- for (const [host, siteApp] of Object.entries(this.sites)) {
211
- if (siteApp.attach) {
212
- siteApp.attach.attach(httpsServer);
213
- console.log(`🔌 Server attached for ${host}`);
214
- }
158
+ // Obtener los servidores sin iniciarlos
159
+ const httpsServer = glx.httpsServer(null, app);
160
+ const httpServer = glx.httpServer();
161
+
162
+ // Inicializar las aplicaciones Socket.IO con el servidor HTTPS
163
+ for (const [host, siteApp] of Object.entries(this.sites)) {
164
+ if (!host.startsWith('www.')) {
165
+ const appInstance = siteApp(httpsServer);
166
+ this.sites[host] = appInstance;
167
+ this.sites[`www.${host}`] = appInstance;
168
+ console.log(`🔧 Initialized server for ${host}`);
215
169
  }
170
+ }
216
171
 
217
- httpsServer.listen(443, "0.0.0.0", () => {
218
- console.info("ℹ️ HTTPS Listening on", httpsServer.address());
219
- });
220
-
221
- // Setup HTTP server for ACME challenges
222
- const httpServer = glx.httpServer();
223
-
224
- httpServer.listen(80, "0.0.0.0", () => {
225
- console.info("ℹ️ HTTP Listening on", httpServer.address());
226
- });
227
- });
172
+ // Retornar los servidores para iniciarlos después
173
+ return { httpsServer, httpServer };
228
174
  }
229
175
 
230
176
  start() {
231
177
  this.loadSites();
232
178
  this.generateConfigJson();
233
- this.initGreenlock();
179
+
180
+ const greenlock = Greenlock.init({
181
+ packageRoot: __dirname,
182
+ configDir: this.greenlockConfigDir,
183
+ maintainerEmail: this.maintainerEmail,
184
+ cluster: false,
185
+ staging: this.staging
186
+ });
187
+
188
+ // Usar una promesa para manejar la inicialización
189
+ return new Promise((resolve, reject) => {
190
+ try {
191
+ greenlock.ready((glx) => {
192
+ const { httpsServer, httpServer } = this.initServers(glx);
193
+
194
+ // Primero iniciar el servidor HTTPS
195
+ httpsServer.listen(443, '0.0.0.0', () => {
196
+ console.log('ℹ️ HTTPS server listening on port 443');
197
+
198
+ // Después iniciar el servidor HTTP
199
+ httpServer.listen(80, '0.0.0.0', () => {
200
+ console.log('ℹ️ HTTP server listening on port 80');
201
+ resolve({ httpsServer, httpServer });
202
+ });
203
+ });
204
+ });
205
+ } catch (error) {
206
+ reject(error);
207
+ }
208
+ });
234
209
  }
235
210
  }
236
211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.4",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,6 +19,11 @@
19
19
  "hosting",
20
20
  "ssl",
21
21
  "host",
22
+ "vhost",
23
+ "virtual-host",
24
+ "socket.io",
25
+ "websocket",
26
+ "express",
22
27
  "greenlock-express",
23
28
  "clasen"
24
29
  ],