@imenam/self-register-proxy 1.0.41 → 1.0.43

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
@@ -82,7 +82,8 @@ const response = await fetch('http://localhost:3000/proxy/register', {
82
82
  headers: { 'Content-Type': 'application/json' },
83
83
  body: JSON.stringify({
84
84
  path: '/myapp', // Chemin d'accès via le proxy
85
- name: 'My App' // Nom de l'application (optionnel)
85
+ name: 'My App', // Nom de l'application (optionnel)
86
+ group: 'Google' // Section repliable du dashboard (optionnel)
86
87
  })
87
88
  });
88
89
 
@@ -163,6 +164,7 @@ Enregistre une nouvelle route et attribue un port.
163
164
  | `path` | string | ✅ | Chemin URL pour accéder à l'app |
164
165
  | `name` | string | ❌ | Nom descriptif |
165
166
  | `port` | number | ❌ | Port spécifique (sinon auto-attribué) |
167
+ | `group` | string | ❌ | Section repliable du dashboard (repliée par défaut). Vide/absent ⇒ section « Non groupées » |
166
168
 
167
169
  **Response (201):**
168
170
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imenam/self-register-proxy",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "description": "Dynamic reverse proxy with self-registration API - applications can register themselves and receive an automatically assigned port",
5
5
  "main": "proxy-client.js",
6
6
  "bin": {
@@ -77,6 +77,7 @@ router.get('/', requireAuth, (req, res) => {
77
77
  target: value.target || 'http://localhost',
78
78
  name: value.name,
79
79
  healthCheck: value.healthCheck,
80
+ group: value.group || null,
80
81
  registeredAt: value.registeredAt,
81
82
  fullTarget: `${value.target || 'http://localhost'}:${value.port}`
82
83
  });
@@ -3,7 +3,7 @@ const metaConfig = require('../meta-config');
3
3
  const { requireAuth } = require('../services/auth');
4
4
  const { registry } = require('../services/registry');
5
5
  const { renderMetaConfigs } = require('../views/metaConfigs');
6
- const { PROXY_NAME } = require('../config');
6
+ const { PROXY_NAME, OPENCODE_CLEAN_UP, ADMIN_PASSWORD } = require('../config');
7
7
 
8
8
  const router = Router();
9
9
 
@@ -173,7 +173,7 @@ router.get('/proxy/meta-configs/active/sync', requireAuth, async (req, res) => {
173
173
  */
174
174
  router.get('/proxy/meta-configs-ui', requireAuth, (req, res) => {
175
175
  const data = metaConfig.getAll();
176
- const html = renderMetaConfigs(PROXY_NAME, data.metaConfigs, data.activeMetaConfig);
176
+ const html = renderMetaConfigs(PROXY_NAME, data.metaConfigs, data.activeMetaConfig, OPENCODE_CLEAN_UP, !!ADMIN_PASSWORD);
177
177
  res.type('html').send(html);
178
178
  });
179
179
 
@@ -10,8 +10,18 @@ const router = Router();
10
10
  /**
11
11
  * Logique commune d'enregistrement (auto et manuel)
12
12
  */
13
- async function registerRouteInternal({ path, name, port, target, healthCheck, autoAssignPort }) {
13
+ /**
14
+ * Normalise un nom de groupe : trim, et chaîne vide => undefined (non groupé).
15
+ */
16
+ function normalizeGroup(group) {
17
+ if (typeof group !== 'string') return undefined;
18
+ const trimmed = group.trim();
19
+ return trimmed === '' ? undefined : trimmed;
20
+ }
21
+
22
+ async function registerRouteInternal({ path, name, port, target, healthCheck, group, autoAssignPort }) {
14
23
  const normalizedPath = path.startsWith('/') ? path : `/${path}`;
24
+ const normalizedGroup = normalizeGroup(group);
15
25
 
16
26
  let finalTarget = target || 'http://localhost';
17
27
  if (!finalTarget.startsWith('http://') && !finalTarget.startsWith('https://')) {
@@ -63,10 +73,11 @@ async function registerRouteInternal({ path, name, port, target, healthCheck, au
63
73
  target: finalTarget,
64
74
  name: name || normalizedPath,
65
75
  healthCheck: healthCheck !== undefined ? healthCheck === true || healthCheck === 'true' : true,
76
+ group: normalizedGroup,
66
77
  registeredAt: new Date().toISOString()
67
78
  });
68
79
 
69
- console.log(`[REGISTER] ${normalizedPath} -> ${finalTarget}:${assignedPort} (${name || 'unnamed'}) [Health: ${healthCheck !== undefined ? healthCheck : 'default'}]`);
80
+ console.log(`[REGISTER] ${normalizedPath} -> ${finalTarget}:${assignedPort} (${name || 'unnamed'}) [Health: ${healthCheck !== undefined ? healthCheck : 'default'}]${normalizedGroup ? ` [Group: ${normalizedGroup}]` : ''}`);
70
81
 
71
82
  return {
72
83
  success: true,
@@ -75,6 +86,7 @@ async function registerRouteInternal({ path, name, port, target, healthCheck, au
75
86
  target: finalTarget,
76
87
  name: name || normalizedPath,
77
88
  healthCheck: healthCheck !== undefined ? healthCheck === true || healthCheck === 'true' : true,
89
+ group: normalizedGroup,
78
90
  message: `Route registered. Target: ${finalTarget}:${assignedPort}`
79
91
  };
80
92
 
@@ -95,12 +107,12 @@ async function registerRouteInternal({ path, name, port, target, healthCheck, au
95
107
  * Enregistrement automatique (rétro-compatibilité)
96
108
  */
97
109
  router.post('/proxy/register', requireLocalOnly, async (req, res) => {
98
- const { path, name, target, healthCheck } = req.body;
110
+ const { path, name, target, healthCheck, group } = req.body;
99
111
 
100
112
  if (!path) return res.status(400).json({ success: false, error: 'Path is required' });
101
113
 
102
114
  try {
103
- const result = await registerRouteInternal({ path, name, target, healthCheck, autoAssignPort: true });
115
+ const result = await registerRouteInternal({ path, name, target, healthCheck, group, autoAssignPort: true });
104
116
  res.json(result);
105
117
  } catch (error) {
106
118
  console.error('[REGISTER AUTO ERROR]', error.message);
@@ -114,13 +126,13 @@ router.post('/proxy/register', requireLocalOnly, async (req, res) => {
114
126
  * Enregistrement manuel via l'interface graphique (port obligatoire)
115
127
  */
116
128
  router.post('/proxy/register-manual', requireAuth, async (req, res) => {
117
- const { path, name, port, target, healthCheck } = req.body;
129
+ const { path, name, port, target, healthCheck, group } = req.body;
118
130
 
119
131
  if (!path) return res.status(400).json({ success: false, error: 'Path is required' });
120
132
  if (!port) return res.status(400).json({ success: false, error: 'Port is required for manual registration' });
121
133
 
122
134
  try {
123
- const result = await registerRouteInternal({ path, name, port, target, healthCheck, autoAssignPort: false });
135
+ const result = await registerRouteInternal({ path, name, port, target, healthCheck, group, autoAssignPort: false });
124
136
  res.json(result);
125
137
  } catch (error) {
126
138
  console.error('[REGISTER MANUAL ERROR]', error.message);
@@ -178,6 +190,7 @@ router.get('/proxy/routes', requireAuth, (req, res) => {
178
190
  target: value.target || 'http://localhost',
179
191
  name: value.name,
180
192
  healthCheck: value.healthCheck,
193
+ group: value.group || null,
181
194
  registeredAt: value.registeredAt,
182
195
  fullTarget: `${value.target || 'http://localhost'}:${value.port}`
183
196
  });
@@ -1,99 +1,99 @@
1
- const { createProxyMiddleware } = require('http-proxy-middleware');
2
- const { registry } = require('./registry');
3
- const { findRouteForPath } = require('./portManager');
4
- const { unregisterServer } = require('./healthCheck');
5
- const { requireAuth } = require('./auth');
6
-
7
- /**
8
- * Échappe les caractères HTML pour éviter les injections XSS
9
- */
10
- function escapeHtml(text) {
11
- const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
12
- return String(text).replace(/[&<>"']/g, char => map[char]);
13
- }
14
-
15
- /**
16
- * Middleware de proxy persistant — créé une seule fois pour éviter les fuites mémoire
17
- */
18
- const persistentProxyMiddleware = createProxyMiddleware({
19
- target: 'http://localhost',
20
- router: (req) => req.proxyTarget,
21
- changeOrigin: true,
22
- pathRewrite: (path, req) => {
23
- const routePath = req.proxyRoutePath;
24
- if (routePath && path.startsWith(routePath)) {
25
- return path.slice(routePath.length) || '/';
26
- }
27
- return path;
28
- },
29
- on: {
30
- proxyReq: (proxyReq, req) => {
31
- if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
32
- const bodyData = JSON.stringify(req.body);
33
- proxyReq.setHeader('Content-Type', 'application/json');
34
- proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
35
- proxyReq.write(bodyData);
36
- }
37
- console.log(`[PROXY] ${req.method} ${req.path} -> ${req.proxyTarget}`);
38
- },
39
- error: (err, req, res) => {
40
- if (res.headersSent) return;
41
- console.error(`[PROXY ERROR] ${req.path}:`, err.message);
42
-
43
- if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
44
- const route = findRouteForPath(req.path);
45
- if (route) {
46
- const routePath = [...registry.routes.entries()].find(([, v]) => v === route)?.[0];
47
- if (routePath) {
48
- unregisterServer(routePath, `connection error (${err.code})`).catch(() => {});
49
- console.log(`[PROXY] Auto-unregistered ${routePath} due to connection error`);
50
- }
51
- }
52
- }
53
-
54
- res.status(502).json({ error: 'Proxy error' });
55
- }
56
- }
57
- });
58
-
59
- /**
60
- * Middleware principal de routage vers le proxy
61
- * Inclut le Smart Routing (Referer Fallback)
62
- */
63
- function routingMiddleware(req, res, next) {
64
- if (req.path.startsWith('/proxy/')) return next();
65
-
66
- requireAuth(req, res, () => {
67
- let route = findRouteForPath(req.path);
68
-
69
- if (!route && req.get('Referer')) {
70
- try {
71
- const refererUrl = new URL(req.get('Referer'));
72
- if (refererUrl.host === req.get('host')) {
73
- const refererPath = refererUrl.pathname;
74
- route = findRouteForPath(refererPath);
75
- if (route) {
76
- console.log(`[SMART ROUTE] Inferred ${route.name} from referer: ${refererPath}`);
77
- }
78
- }
79
- } catch {
80
- // URL de referer invalide, on ignore
81
- }
82
- }
83
-
84
- if (!route) {
85
- return res.status(404).json({
86
- error: 'No route registered for this path',
87
- path: req.path,
88
- hint: 'Register a route with POST /proxy/register or use the UI'
89
- });
90
- }
91
-
92
- const target = route.target || 'http://localhost';
93
- req.proxyTarget = `${target}:${route.port}`;
94
- req.proxyRoutePath = [...registry.routes.entries()].find(([, v]) => v === route)?.[0];
95
- persistentProxyMiddleware(req, res, next);
96
- });
97
- }
98
-
99
- module.exports = { persistentProxyMiddleware, routingMiddleware, escapeHtml };
1
+ const { createProxyMiddleware } = require('http-proxy-middleware');
2
+ const { registry } = require('./registry');
3
+ const { findRouteForPath } = require('./portManager');
4
+ const { unregisterServer } = require('./healthCheck');
5
+ const { requireAuth } = require('./auth');
6
+
7
+ /**
8
+ * Échappe les caractères HTML pour éviter les injections XSS
9
+ */
10
+ function escapeHtml(text) {
11
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
12
+ return String(text).replace(/[&<>"']/g, char => map[char]);
13
+ }
14
+
15
+ /**
16
+ * Middleware de proxy persistant — créé une seule fois pour éviter les fuites mémoire
17
+ */
18
+ const persistentProxyMiddleware = createProxyMiddleware({
19
+ target: 'http://localhost',
20
+ router: (req) => req.proxyTarget,
21
+ changeOrigin: true,
22
+ pathRewrite: (path, req) => {
23
+ const routePath = req.proxyRoutePath;
24
+ if (routePath && path.startsWith(routePath)) {
25
+ return path.slice(routePath.length) || '/';
26
+ }
27
+ return path;
28
+ },
29
+ on: {
30
+ proxyReq: (proxyReq, req) => {
31
+ if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
32
+ const bodyData = JSON.stringify(req.body);
33
+ proxyReq.setHeader('Content-Type', 'application/json');
34
+ proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
35
+ proxyReq.write(bodyData);
36
+ }
37
+ console.log(`[PROXY] ${req.method} ${req.path} -> ${req.proxyTarget}`);
38
+ },
39
+ error: (err, req, res) => {
40
+ if (res.headersSent) return;
41
+ console.error(`[PROXY ERROR] ${req.path}:`, err.message);
42
+
43
+ if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
44
+ const route = findRouteForPath(req.path);
45
+ if (route) {
46
+ const routePath = [...registry.routes.entries()].find(([, v]) => v === route)?.[0];
47
+ if (routePath) {
48
+ unregisterServer(routePath, `connection error (${err.code})`).catch(() => {});
49
+ console.log(`[PROXY] Auto-unregistered ${routePath} due to connection error`);
50
+ }
51
+ }
52
+ }
53
+
54
+ res.status(502).json({ error: 'Proxy error' });
55
+ }
56
+ }
57
+ });
58
+
59
+ /**
60
+ * Middleware principal de routage vers le proxy
61
+ * Inclut le Smart Routing (Referer Fallback)
62
+ */
63
+ function routingMiddleware(req, res, next) {
64
+ if (req.path.startsWith('/proxy/')) return next();
65
+
66
+ requireAuth(req, res, () => {
67
+ let route = findRouteForPath(req.path);
68
+
69
+ if (!route && req.get('Referer')) {
70
+ try {
71
+ const refererUrl = new URL(req.get('Referer'));
72
+ if (refererUrl.host === req.get('host')) {
73
+ const refererPath = refererUrl.pathname;
74
+ route = findRouteForPath(refererPath);
75
+ if (route) {
76
+ console.log(`[SMART ROUTE] Inferred ${route.name} from referer: ${refererPath}`);
77
+ }
78
+ }
79
+ } catch {
80
+ // URL de referer invalide, on ignore
81
+ }
82
+ }
83
+
84
+ if (!route) {
85
+ return res.status(404).json({
86
+ error: 'No route registered for this path',
87
+ path: req.path,
88
+ hint: 'Register a route with POST /proxy/register or use the UI'
89
+ });
90
+ }
91
+
92
+ const target = route.target || 'http://localhost';
93
+ req.proxyTarget = `${target}:${route.port}`;
94
+ req.proxyRoutePath = [...registry.routes.entries()].find(([, v]) => v === route)?.[0];
95
+ persistentProxyMiddleware(req, res, next);
96
+ });
97
+ }
98
+
99
+ module.exports = { persistentProxyMiddleware, routingMiddleware, escapeHtml };
@@ -31,7 +31,7 @@ class Mutex {
31
31
  }
32
32
 
33
33
  const registry = {
34
- routes: new Map(), // path -> { port, target, name, healthCheck, registeredAt }
34
+ routes: new Map(), // path -> { port, target, name, healthCheck, group, registeredAt }
35
35
  usedPorts: new Set(), // Ports déjà attribués
36
36
  nextPort: INTERNAL_PORT_START
37
37
  };