roster-server 1.2.6 → 1.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
@@ -30,7 +30,7 @@ Your project should look something like this:
30
30
  └── www/
31
31
  ├── example.com/
32
32
  │ └── index.js
33
- └── anotherdomain.com/
33
+ └── subdomain.example.com/
34
34
  └── index.js
35
35
  ```
36
36
 
@@ -38,10 +38,11 @@ Your project should look something like this:
38
38
 
39
39
  ```javascript
40
40
  // /srv/roster/server.js
41
- const Roster = require('roster-express');
41
+ const Roster = require('roster-server');
42
42
 
43
43
  const options = {
44
44
  maintainerEmail: 'admin@example.com',
45
+ greenlockConfigDir: '/srv/greenlock.d', // Path to your Greenlock configuration directory
45
46
  wwwPath: '/srv/www', // Path to your 'www' directory (default: '../www')
46
47
  staging: false // Set to true for Let's Encrypt staging environment
47
48
  };
@@ -54,12 +55,59 @@ server.start();
54
55
 
55
56
  Each domain should have its own folder under `www`, containing an `index.js` that exports a request handler function.
56
57
 
57
- For example, `www/example.com/index.js`:
58
+ ### Examples
58
59
 
59
- ```javascript
60
- module.exports = (req, res) => {
61
- res.writeHead(200, { 'Content-Type': 'text/plain' });
62
- res.end('Hello from example.com!');
60
+ I'll help analyze the example files shown. You have 3 different implementations demonstrating various ways to handle requests in RosterServer:
61
+
62
+ 1. **Basic HTTP Handler**:
63
+ ```javascript:demo/www/example.com/index.js
64
+ module.exports = (server) => {
65
+ return (req, res) => {
66
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
67
+ res.end('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
68
+ };
69
+ };
70
+ ```
71
+
72
+ 2. **Express App**:
73
+ ```javascript:demo/www/express.example.com/index.js
74
+ const express = require('express');
75
+
76
+ module.exports = (server) => {
77
+ const app = express();
78
+ app.get('/', (req, res) => {
79
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
80
+ res.send('"Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."');
81
+ });
82
+
83
+ return app;
84
+ }
85
+ ```
86
+
87
+ 3. **Socket.IO Server**:
88
+ ```javascript:demo/www/sio.example.com/index.js
89
+ const { Server } = require('socket.io');
90
+
91
+ module.exports = (server) => {
92
+ const io = new Server(server);
93
+
94
+ io.on('connection', (socket) => {
95
+ console.log('A user connected');
96
+
97
+ socket.on('chat:message', (msg) => {
98
+ console.log('Message received:', msg);
99
+ io.emit('chat:message', msg);
100
+ });
101
+
102
+ socket.on('disconnect', () => {
103
+ console.log('User disconnected');
104
+ });
105
+ });
106
+
107
+ return (req, res) => {
108
+ res.writeHead(200);
109
+ res.end('Socket.IO server running');
110
+ };
63
111
  };
64
112
  ```
65
113
 
@@ -76,7 +124,7 @@ And that's it! Your server is now hosting multiple HTTPS-enabled sites. 🎉
76
124
 
77
125
  ### Automatic SSL Certificate Management
78
126
 
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. 🧐
127
+ 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
128
 
81
129
  ### Redirects from `www`
82
130
 
@@ -84,11 +132,11 @@ All requests to `www.yourdomain.com` are automatically redirected to `yourdomain
84
132
 
85
133
  ### Dynamic Site Loading
86
134
 
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? 😅
135
+ 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
136
 
89
137
  ## ⚙️ Configuration Options
90
138
 
91
- When creating a new `RosterExpress` instance, you can pass the following options:
139
+ When creating a new `RosterServer` instance, you can pass the following options:
92
140
 
93
141
  - `maintainerEmail` (string): Your email for Let's Encrypt notifications.
94
142
  - `wwwPath` (string): Path to your `www` directory containing your sites.
@@ -97,14 +145,14 @@ When creating a new `RosterExpress` instance, you can pass the following options
97
145
 
98
146
  ## 🧂 A Touch of Magic
99
147
 
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! 👾👾👾
148
+ 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
149
 
102
150
 
103
151
  ## 🤝 Contributing
104
152
 
105
153
  Feel free to submit issues or pull requests. Or don't. I'm not your boss. 😜
106
154
 
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).
155
+ 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
156
 
109
157
  ## 🙏 Acknowledgments
110
158
 
@@ -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 = (server) => {
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 = (server) => {
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,33 +1,24 @@
1
- const https = require('https');
2
1
  const { Server } = require('socket.io');
3
2
 
4
- const server = https.createServer();
3
+ module.exports = (server) => {
4
+ const io = new Server(server);
5
5
 
6
- // Initialize Socket.IO with the HTTPS server
7
- const io = new Server(server, {
8
- cors: {
9
- origin: "*",
10
- methods: ["GET", "POST"]
11
- },
12
- allowEIO3: true,
13
- transports: ['websocket', 'polling']
14
- });
6
+ io.on('connection', (socket) => {
7
+ console.log('A user connected');
15
8
 
16
- // Handle socket connections
17
- io.on('connection', (socket) => {
18
- console.log('A user connected');
9
+ socket.on('chat:message', (msg) => {
10
+ console.log('Message received:', msg);
11
+ io.emit('chat:message', msg);
12
+ });
19
13
 
20
- // Handle chat messages
21
- socket.on('chat:message', (msg) => {
22
- console.log('Message received:', msg);
23
- // Broadcast the message to all connected clients
24
- io.emit('chat:message', msg);
14
+ socket.on('disconnect', () => {
15
+ console.log('User disconnected');
16
+ });
25
17
  });
26
18
 
27
- // Handle disconnection
28
- socket.on('disconnect', () => {
29
- console.log('User disconnected');
30
- });
31
- });
32
-
33
- module.exports = server;
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 = (server) => {
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,20 +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
-
23
- // Check for different module file extensions
21
+
24
22
  const possibleIndexFiles = ['index.js', 'index.mjs', 'index.cjs'];
25
23
  let siteApp;
26
24
  let loadedFile;
@@ -28,17 +26,13 @@ class Roster {
28
26
  for (const indexFile of possibleIndexFiles) {
29
27
  const indexPath = path.join(domainPath, indexFile);
30
28
  if (fs.existsSync(indexPath)) {
31
- const module = require(indexPath);
32
- // Si el módulo es una función o un servidor, usarlo directamente
33
- siteApp = typeof module === 'function' ? module :
34
- (module.handleRequest || module.handle || module);
29
+ siteApp = require(indexPath);
35
30
  loadedFile = indexFile;
36
31
  break;
37
32
  }
38
33
  }
39
34
 
40
35
  if (siteApp) {
41
- // Add the main domain and 'www' subdomain by default
42
36
  const domainEntries = [domain, `www.${domain}`];
43
37
  this.domains.push(...domainEntries);
44
38
  domainEntries.forEach(d => {
@@ -56,7 +50,6 @@ class Roster {
56
50
  const configDir = this.greenlockConfigDir;
57
51
  const configPath = path.join(configDir, 'config.json');
58
52
 
59
- // Create the directory if it does not exist
60
53
  if (!fs.existsSync(configDir)) {
61
54
  fs.mkdirSync(configDir, { recursive: true });
62
55
  }
@@ -69,10 +62,8 @@ class Roster {
69
62
  uniqueDomains.add(rootDomain);
70
63
  });
71
64
 
72
- // Read the existing config.json if it exists
73
65
  let existingConfig = {};
74
66
  if (fs.existsSync(configPath)) {
75
- // Read the current content
76
67
  const currentConfigContent = fs.readFileSync(configPath, 'utf8');
77
68
  existingConfig = JSON.parse(currentConfigContent);
78
69
  }
@@ -83,7 +74,6 @@ class Roster {
83
74
  altnames.push(`www.${domain}`);
84
75
  }
85
76
 
86
- // Find the existing site to preserve renewAt
87
77
  let existingSite = null;
88
78
  if (existingConfig.sites) {
89
79
  existingSite = existingConfig.sites.find(site => site.subject === domain);
@@ -94,7 +84,6 @@ class Roster {
94
84
  altnames: altnames
95
85
  };
96
86
 
97
- // Preserve renewAt if it exists
98
87
  if (existingSite && existingSite.renewAt) {
99
88
  siteConfig.renewAt = existingSite.renewAt;
100
89
  }
@@ -122,115 +111,101 @@ class Roster {
122
111
  sites: sitesConfig
123
112
  };
124
113
 
125
- // Check if config.json already exists and compare
126
114
  if (fs.existsSync(configPath)) {
127
- // Read the current content
128
115
  const currentConfigContent = fs.readFileSync(configPath, 'utf8');
129
116
  const currentConfig = JSON.parse(currentConfigContent);
130
117
 
131
- // Compare the entire configurations
132
118
  const newConfigContent = JSON.stringify(newConfig, null, 2);
133
119
  const currentConfigContentFormatted = JSON.stringify(currentConfig, null, 2);
134
120
 
135
121
  if (newConfigContent === currentConfigContentFormatted) {
136
122
  console.log('ℹ️ Configuration has not changed. config.json will not be overwritten.');
137
- return; // Exit the function without overwriting
138
- } else {
139
- console.log('🔄 Configuration has changed. config.json will be updated.');
123
+ return;
140
124
  }
125
+ console.log('🔄 Configuration has changed. config.json will be updated.');
141
126
  } else {
142
127
  console.log('🆕 config.json does not exist. A new one will be created.');
143
128
  }
144
129
 
145
- // Write the new config.json
146
130
  fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
147
131
  console.log(`📁 config.json generated at ${configPath}`);
148
132
  }
149
133
 
150
134
  handleRequest(req, res) {
151
135
  const host = req.headers.host || '';
152
-
153
- // Eliminar el puerto del host si está presente
154
- const cleanHost = host.split(':')[0];
155
-
156
- // Handle www redirect
157
- if (cleanHost.startsWith('www.')) {
158
- const newHost = cleanHost.slice(4);
136
+
137
+ if (host.startsWith('www.')) {
138
+ const newHost = host.slice(4);
159
139
  res.writeHead(301, { Location: `https://${newHost}${req.url}` });
160
140
  res.end();
161
141
  return;
162
142
  }
163
143
 
164
- // Find and execute the appropriate site handler
165
- const siteApp = this.sites[cleanHost];
144
+ const siteApp = this.sites[host];
166
145
  if (siteApp) {
167
- if (siteApp.emit) {
168
- siteApp.emit('request', req, res);
169
- } else {
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) {
188
- opts.domains = certs.altnames;
189
- } else {
190
- if (this.domains.includes(opts.domain)) {
191
- opts.email = this.maintainerEmail;
192
- opts.agreeTos = true;
193
- opts.domains = [opts.domain];
194
- } else {
195
- console.warn(`⚠️ Domain not approved: ${opts.domain}`);
196
- return cb(new Error(`Domain not approved: ${opts.domain}`));
197
- }
198
- }
199
- cb(null, { options: opts, certs });
200
- }
201
- }).ready((glx) => {
202
- // Setup HTTPS server
203
- const httpsServer = glx.httpsServer(null, (req, res) => {
204
- this.handleRequest(req, res);
205
- });
206
-
207
- // Agregar soporte para WebSocket
208
- httpsServer.on('upgrade', (req, socket, head) => {
209
- const host = req.headers.host?.split(':')[0] || '';
210
- const siteApp = this.sites[host];
211
-
212
- if (siteApp && siteApp.upgrade) {
213
- siteApp.upgrade(req, socket, head);
214
- }
215
- });
216
-
217
- httpsServer.listen(443, "0.0.0.0", () => {
218
- console.info("ℹ️ HTTPS Listening on", httpsServer.address());
219
- });
153
+ initServers(glx) {
154
+ const app = (req, res) => {
155
+ this.handleRequest(req, res);
156
+ };
220
157
 
221
- // Setup HTTP server for ACME challenges
222
- const httpServer = glx.httpServer();
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}`);
169
+ }
170
+ }
223
171
 
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.2.6",
3
+ "version": "1.4.2",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {