4bnode 4.0.0 → 4.0.3
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 +10 -0
- package/add-login.js +2 -2
- package/add-serialport.js +1 -1
- package/index.js +60 -6
- package/lib/indexFile.js +60 -13
- package/lib/ui.js +1 -0
- package/package.json +1 -1
- package/skeleton/.4bnode/assets/js/App.js +115 -0
- package/skeleton/.4bnode/assets/js/Root.js +44 -0
- package/skeleton/.4bnode/assets/js/components/Modal.js +52 -0
- package/skeleton/.4bnode/assets/js/components/Sidebar.js +68 -0
- package/skeleton/.4bnode/assets/js/components/ToastContainer.js +15 -0
- package/skeleton/.4bnode/assets/js/context.js +7 -0
- package/skeleton/.4bnode/assets/js/lib.js +83 -0
- package/skeleton/.4bnode/assets/js/main.js +5 -0
- package/skeleton/.4bnode/assets/js/pages/ApiTesterPage.js +1594 -0
- package/skeleton/.4bnode/assets/js/pages/DashboardPage.js +435 -0
- package/skeleton/.4bnode/assets/js/pages/DatabasePage.js +461 -0
- package/skeleton/.4bnode/assets/js/pages/EnvironmentPage.js +141 -0
- package/skeleton/.4bnode/assets/js/pages/RealtimePage.js +186 -0
- package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +1656 -0
- package/skeleton/.4bnode/assets/js/pages/SchemasPage.js +901 -0
- package/skeleton/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
- package/skeleton/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
- package/skeleton/.4bnode/assets/js/screens/SplashScreen.js +66 -0
- package/skeleton/{dev-api.js → .4bnode/dev-api.js} +259 -114
- package/skeleton/.4bnode/ui.html +1556 -0
- package/skeleton/index.js +62 -16
- package/skeleton/package.json +1 -1
- package/skeleton/public/index.html +290 -7586
- /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Bold.woff2 +0 -0
- /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Light.woff2 +0 -0
- /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Medium.woff2 +0 -0
- /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-Regular.woff2 +0 -0
- /package/skeleton/{public → .4bnode/assets}/fonts/NotoSans-SemiBold.woff2 +0 -0
- /package/skeleton/{public → .4bnode/assets}/images/mongo.png +0 -0
- /package/skeleton/{public → .4bnode/assets}/images/mysql.png +0 -0
- /package/skeleton/{public → .4bnode/assets}/images/postgresql.png +0 -0
- /package/skeleton/{public → .4bnode/assets}/images/sqlite.png +0 -0
|
@@ -8,6 +8,47 @@ import crypto from 'crypto';
|
|
|
8
8
|
|
|
9
9
|
const router = express.Router();
|
|
10
10
|
|
|
11
|
+
// Serve dashboard UI and assets
|
|
12
|
+
const __4bnodeDir = path.join(process.cwd(), '.4bnode');
|
|
13
|
+
router.get('/', (req, res) => {
|
|
14
|
+
res.sendFile('ui.html', { root: __4bnodeDir });
|
|
15
|
+
});
|
|
16
|
+
router.use('/assets', express.static(path.join(__4bnodeDir, 'assets')));
|
|
17
|
+
|
|
18
|
+
// ── Passkey Authentication ──────────────────────────────────────────────────
|
|
19
|
+
function getPasskeyHash() {
|
|
20
|
+
const file = path.join(__4bnodeDir, '.passkey');
|
|
21
|
+
if (!fs.existsSync(file)) return null;
|
|
22
|
+
return fs.readFileSync(file, 'utf8').trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function hashPasskey(pin) {
|
|
26
|
+
return crypto.createHash('sha256').update(pin).digest('hex');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Verify passkey endpoint (no auth middleware needed here)
|
|
30
|
+
router.post('/api/auth', (req, res) => {
|
|
31
|
+
const { passkey } = req.body;
|
|
32
|
+
const stored = getPasskeyHash();
|
|
33
|
+
if (!stored) return res.json({ ok: true });
|
|
34
|
+
if (!passkey || hashPasskey(passkey) !== stored) {
|
|
35
|
+
return res.status(401).json({ error: 'Invalid passkey' });
|
|
36
|
+
}
|
|
37
|
+
res.json({ ok: true });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Auth middleware — protect all /api/* routes below
|
|
41
|
+
router.use('/api', (req, res, next) => {
|
|
42
|
+
if (req.path === '/auth') return next();
|
|
43
|
+
const stored = getPasskeyHash();
|
|
44
|
+
if (!stored) return next();
|
|
45
|
+
const passkey = req.headers['x-passkey'];
|
|
46
|
+
if (!passkey || hashPasskey(passkey) !== stored) {
|
|
47
|
+
return res.status(401).json({ error: 'Unauthorized' });
|
|
48
|
+
}
|
|
49
|
+
next();
|
|
50
|
+
});
|
|
51
|
+
|
|
11
52
|
// ---------------------------------------------------------------------------
|
|
12
53
|
// Utility functions (self-contained, no external imports)
|
|
13
54
|
// ---------------------------------------------------------------------------
|
|
@@ -405,7 +446,8 @@ router.get('/api/models/:name', (req, res) => {
|
|
|
405
446
|
router.get('/api/models/:name/data', async (req, res) => {
|
|
406
447
|
try {
|
|
407
448
|
const name = req.params.name;
|
|
408
|
-
const
|
|
449
|
+
const rawLimit = parseInt(req.query.limit);
|
|
450
|
+
const limit = rawLimit === 0 ? 0 : Math.min(rawLimit || 50, 500);
|
|
409
451
|
const skip = parseInt(req.query.skip) || 0;
|
|
410
452
|
const pascalName = toPascalCase(name);
|
|
411
453
|
|
|
@@ -426,7 +468,9 @@ router.get('/api/models/:name/data', async (req, res) => {
|
|
|
426
468
|
|
|
427
469
|
const collection = mongoose.connection.db.collection(name + 's');
|
|
428
470
|
const total = await collection.countDocuments();
|
|
429
|
-
const
|
|
471
|
+
const query = collection.find({}).skip(skip);
|
|
472
|
+
if (limit > 0) query.limit(limit);
|
|
473
|
+
const docs = await query.toArray();
|
|
430
474
|
|
|
431
475
|
res.json({ docs, total, limit, skip });
|
|
432
476
|
} catch (err) {
|
|
@@ -729,9 +773,9 @@ router.post('/api/routes/:name/add-method', async (req, res) => {
|
|
|
729
773
|
packagesToInstall.push('jsonwebtoken');
|
|
730
774
|
|
|
731
775
|
if (hasPassword) {
|
|
732
|
-
handlers += `\n// Login\nrouter.post('/', async (req, res) => {\n try {\n const { ${findField}, ${passwordField} } = req.body;\n const ${modelName} = await ${pascalModel}.findOne({ ${findField} });\n if (!${modelName}) return res.status(
|
|
776
|
+
handlers += `\n// Login\nrouter.post('/', async (req, res) => {\n try {\n const { ${findField}, ${passwordField} } = req.body;\n const ${modelName} = await ${pascalModel}.findOne({ ${findField} });\n if (!${modelName}) return res.status(401).json({ message: 'Invalid credentials' });\n\n const isMatch = await bcrypt.compare(${passwordField}, ${modelName}.${passwordField});\n if (!isMatch) return res.status(401).json({ message: 'Invalid credentials' });\n\n const token = jwt.sign({ id: ${modelName}._id }, process.env.JWT_SECRET, { expiresIn: '7d' });\n res.json({ token, ${modelName}: { id: ${modelName}._id, ${findField}: ${modelName}.${findField} } });\n } catch (err) {\n res.status(500).json({ message: err.message });\n }\n});\n`;
|
|
733
777
|
} else {
|
|
734
|
-
handlers += `\n// Login\nrouter.post('/', async (req, res) => {\n try {\n const { ${findField} } = req.body;\n const ${modelName} = await ${pascalModel}.findOne({ ${findField} });\n if (!${modelName}) return res.status(
|
|
778
|
+
handlers += `\n// Login\nrouter.post('/', async (req, res) => {\n try {\n const { ${findField} } = req.body;\n const ${modelName} = await ${pascalModel}.findOne({ ${findField} });\n if (!${modelName}) return res.status(401).json({ message: 'Invalid credentials' });\n\n const token = jwt.sign({ id: ${modelName}._id }, process.env.JWT_SECRET, { expiresIn: '7d' });\n res.json({ token, ${modelName}: { id: ${modelName}._id, ${findField}: ${modelName}.${findField} } });\n } catch (err) {\n res.status(500).json({ message: err.message });\n }\n});\n`;
|
|
735
779
|
}
|
|
736
780
|
|
|
737
781
|
// Ensure JWT_SECRET
|
|
@@ -1028,16 +1072,27 @@ function addImportToIndex(importLine) {
|
|
|
1028
1072
|
function insertBeforeListen(codeBlock) {
|
|
1029
1073
|
let content = readIndexFile();
|
|
1030
1074
|
const listenRegex = /(app|server)\.listen\(port/m;
|
|
1031
|
-
|
|
1075
|
+
let match = content.match(listenRegex);
|
|
1076
|
+
if (!match) {
|
|
1077
|
+
// Match the startServer() call, not the function declaration
|
|
1078
|
+
match = content.match(/^startServer\(/m);
|
|
1079
|
+
}
|
|
1032
1080
|
if (match) {
|
|
1033
|
-
content = content.slice(0, match.index) + codeBlock.trim() + '\n
|
|
1081
|
+
content = content.slice(0, match.index) + codeBlock.trim() + '\n' + content.slice(match.index);
|
|
1034
1082
|
}
|
|
1035
1083
|
writeIndexFile(content);
|
|
1036
1084
|
}
|
|
1037
1085
|
|
|
1038
1086
|
function replaceAppListen(newBlock) {
|
|
1039
1087
|
let content = readIndexFile();
|
|
1040
|
-
|
|
1088
|
+
if (content.includes('startServer(')) {
|
|
1089
|
+
content = content.replace(/const listener = app\.listen\(tryPort\)/, 'const listener = server.listen(tryPort)');
|
|
1090
|
+
if (!content.includes('server.listen(tryPort)')) {
|
|
1091
|
+
content = content.replace(/app\.listen\(tryPort\)/, 'server.listen(tryPort)');
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
content = content.replace(/app\.listen\(port[\s\S]*?\}\);?\n?/m, newBlock.trim() + '\n');
|
|
1095
|
+
}
|
|
1041
1096
|
writeIndexFile(content);
|
|
1042
1097
|
}
|
|
1043
1098
|
|
|
@@ -1125,8 +1180,8 @@ router.get('/api/status', (req, res) => {
|
|
|
1125
1180
|
const mongoConfigured = indexContent.includes("import connectDB") || indexContent.includes("connectDB");
|
|
1126
1181
|
const routesDirExists = fs.existsSync(path.join(process.cwd(), 'src', 'routes'));
|
|
1127
1182
|
const authMiddlewareExists = fs.existsSync(path.join(process.cwd(), 'src', 'middleware', 'auth.js'));
|
|
1128
|
-
const socketConfigured = indexContent.includes('socket.io') || indexContent.includes('Server(server');
|
|
1129
|
-
const websocketConfigured = indexContent.includes('WebSocketServer') || indexContent.includes("from 'ws'");
|
|
1183
|
+
const socketConfigured = indexContent.includes('setupSocketIO') || indexContent.includes('socket.io') || indexContent.includes('Server(server');
|
|
1184
|
+
const websocketConfigured = indexContent.includes('setupWebSocket') || indexContent.includes('WebSocketServer') || indexContent.includes("from 'ws'");
|
|
1130
1185
|
const serialportConfigured = indexContent.includes('SerialPort') || indexContent.includes("from 'serialport'");
|
|
1131
1186
|
|
|
1132
1187
|
res.json({
|
|
@@ -1719,154 +1774,242 @@ export default router;
|
|
|
1719
1774
|
});
|
|
1720
1775
|
|
|
1721
1776
|
// ---------------------------------------------------------------------------
|
|
1722
|
-
// POST /api/add-socket
|
|
1777
|
+
// POST /api/add-socket — create src/services/socket.js
|
|
1723
1778
|
// ---------------------------------------------------------------------------
|
|
1724
1779
|
router.post('/api/add-socket', async (req, res) => {
|
|
1725
1780
|
try {
|
|
1726
1781
|
const { corsOrigins } = req.body;
|
|
1727
1782
|
|
|
1728
|
-
//
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
try { await execAsync('npm uninstall ws', { cwd: process.cwd() }); } catch {}
|
|
1732
|
-
}
|
|
1783
|
+
// Install packages first
|
|
1784
|
+
const currentContent = readIndexFile();
|
|
1785
|
+
const hasWebSocket = currentContent.includes('setupWebSocket') || currentContent.includes('WebSocketServer');
|
|
1733
1786
|
|
|
1734
|
-
|
|
1735
|
-
|
|
1787
|
+
if (hasWebSocket) {
|
|
1788
|
+
try { await execAsync('npm uninstall ws && npm install socket.io', { cwd: process.cwd() }); } catch {
|
|
1789
|
+
try { await execAsync('npm install socket.io', { cwd: process.cwd() }); } catch {}
|
|
1790
|
+
}
|
|
1791
|
+
} else {
|
|
1792
|
+
await execAsync('npm install socket.io', { cwd: process.cwd() });
|
|
1793
|
+
}
|
|
1736
1794
|
|
|
1737
|
-
//
|
|
1738
|
-
|
|
1739
|
-
addImportToIndex('import http from "http";');
|
|
1795
|
+
// Send response before modifying files
|
|
1796
|
+
res.json({ message: 'Socket.IO setup complete.', files: ['src/services/socket.js', 'index.js'] });
|
|
1740
1797
|
|
|
1741
1798
|
// Build cors origin value
|
|
1742
1799
|
let originValue;
|
|
1743
|
-
if (corsOrigins === '*')
|
|
1744
|
-
|
|
1745
|
-
} else {
|
|
1746
|
-
const origins = corsOrigins.split(',').map(s => s.trim());
|
|
1747
|
-
originValue = JSON.stringify(origins);
|
|
1748
|
-
}
|
|
1800
|
+
if (corsOrigins === '*') originValue = '"*"';
|
|
1801
|
+
else originValue = JSON.stringify(corsOrigins.split(',').map(s => s.trim()));
|
|
1749
1802
|
|
|
1750
|
-
//
|
|
1751
|
-
const
|
|
1752
|
-
|
|
1753
|
-
cors: {
|
|
1754
|
-
origin: ${originValue},
|
|
1755
|
-
methods: ["GET", "POST"]
|
|
1756
|
-
}
|
|
1757
|
-
});
|
|
1803
|
+
// Create src/services/socket.js
|
|
1804
|
+
const servicesDir = path.join(process.cwd(), 'src', 'services');
|
|
1805
|
+
if (!fs.existsSync(servicesDir)) fs.mkdirSync(servicesDir, { recursive: true });
|
|
1758
1806
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
socket.on("disconnect", () => {
|
|
1762
|
-
console.log("User disconnected");
|
|
1763
|
-
});
|
|
1764
|
-
});
|
|
1807
|
+
const socketFile = `import { Server } from "socket.io";
|
|
1808
|
+
import http from "http";
|
|
1765
1809
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
next();
|
|
1769
|
-
});`;
|
|
1810
|
+
export function setupSocketIO(app) {
|
|
1811
|
+
const server = http.createServer(app);
|
|
1770
1812
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1813
|
+
const io = new Server(server, {
|
|
1814
|
+
cors: {
|
|
1815
|
+
origin: ${originValue},
|
|
1816
|
+
methods: ["GET", "POST"],
|
|
1817
|
+
},
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
io.on("connection", (socket) => {
|
|
1821
|
+
console.log("A user connected");
|
|
1822
|
+
socket.on("disconnect", () => {
|
|
1823
|
+
console.log("User disconnected");
|
|
1775
1824
|
});
|
|
1825
|
+
});
|
|
1776
1826
|
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1827
|
+
// Make io accessible in routes via req.io
|
|
1828
|
+
app.use((req, res, next) => {
|
|
1829
|
+
req.io = io;
|
|
1830
|
+
next();
|
|
1831
|
+
});
|
|
1780
1832
|
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1833
|
+
return { server, io };
|
|
1834
|
+
}
|
|
1835
|
+
`;
|
|
1836
|
+
|
|
1837
|
+
fs.writeFileSync(path.join(servicesDir, 'socket.js'), socketFile, 'utf8');
|
|
1838
|
+
|
|
1839
|
+
// Remove old WebSocket service file if present
|
|
1840
|
+
const wsFile = path.join(servicesDir, 'websocket.js');
|
|
1841
|
+
if (fs.existsSync(wsFile)) fs.unlinkSync(wsFile);
|
|
1842
|
+
|
|
1843
|
+
// Update index.js — clean approach
|
|
1844
|
+
let content = readIndexFile();
|
|
1845
|
+
|
|
1846
|
+
// Remove old inline socket.io/websocket code
|
|
1847
|
+
content = content.replace(/import\s*\{\s*Server\s*\}\s*from\s*["']socket\.io["'];\s*\n?/g, '');
|
|
1848
|
+
content = content.replace(/import\s*\{\s*WebSocketServer\s*\}\s*from\s*['"]ws['"];\s*\n?/g, '');
|
|
1849
|
+
content = content.replace(/import\s+http\s+from\s*["']http["'];\s*\n?/g, '');
|
|
1850
|
+
content = content.replace(/const\s+server\s*=\s*http\.createServer\(app\);\s*\n?/g, '');
|
|
1851
|
+
content = content.replace(/const\s+io\s*=\s*new\s+Server\(server[\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1852
|
+
content = content.replace(/io\.on\(["']connection["'][\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1853
|
+
content = content.replace(/app\.use\(\(req,\s*res,\s*next\)\s*=>\s*\{\s*\n?\s*req\.io\s*=\s*io;\s*\n?\s*next\(\);\s*\n?\s*\}\);\s*\n?/g, '');
|
|
1854
|
+
content = content.replace(/const\s+wss\s*=\s*new\s+WebSocketServer\(\{[\s\S]*?\n?\}\);\s*\n?/g, '');
|
|
1855
|
+
content = content.replace(/wss\.on\(['"]connection['"][\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1856
|
+
content = content.replace(/console\.log\(['"]WebSocket server running on port.*?['"]?\);\s*\n?/g, '');
|
|
1857
|
+
// Remove old import lines for services
|
|
1858
|
+
content = content.replace(/import\s*\{\s*setupSocketIO\s*\}\s*from\s*['"]\.\/src\/services\/socket\.js['"];\s*\n?/g, '');
|
|
1859
|
+
content = content.replace(/import\s*\{\s*setupWebSocket\s*\}\s*from\s*['"]\.\/src\/services\/websocket\.js['"];\s*\n?/g, '');
|
|
1860
|
+
content = content.replace(/const\s*\{\s*server[^}]*\}\s*=\s*setupSocketIO\(app\);\s*\n?/g, '');
|
|
1861
|
+
content = content.replace(/const\s*\{\s*server[^}]*\}\s*=\s*setupWebSocket\(app\);\s*\n?/g, '');
|
|
1862
|
+
content = content.replace(/setupWebSocket\(app[^)]*\);\s*\n?/g, '');
|
|
1863
|
+
|
|
1864
|
+
// Add import for socket service
|
|
1865
|
+
const lastImport = content.lastIndexOf('import ');
|
|
1866
|
+
const lineEnd = content.indexOf('\n', lastImport);
|
|
1867
|
+
content = content.slice(0, lineEnd + 1) + 'import { setupSocketIO } from "./src/services/socket.js";\n' + content.slice(lineEnd + 1);
|
|
1868
|
+
|
|
1869
|
+
// Add setup call before listen
|
|
1870
|
+
const listenIdx = content.search(/\/\/ ── Start Server|async function startServer|function startServer/);
|
|
1871
|
+
if (listenIdx !== -1) {
|
|
1872
|
+
content = content.slice(0, listenIdx) + 'const { server } = setupSocketIO(app);\n\n' + content.slice(listenIdx);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// Replace app.listen with server.listen in startServer
|
|
1876
|
+
content = content.replace('const listener = app.listen(tryPort)', 'const listener = server.listen(tryPort)');
|
|
1877
|
+
// Also handle direct app.listen
|
|
1878
|
+
if (content.includes('app.listen(port') && !content.includes('startServer')) {
|
|
1879
|
+
content = content.replace(/app\.listen\(port[\s\S]*?\}\);?\n?/m, 'server.listen(port, () => {\n console.log(`Server is running on port ${port}`);\n});\n');
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
// Revert server.listen back to app.listen for old-style
|
|
1883
|
+
content = content.replace('server.listen(port', 'server.listen(port');
|
|
1884
|
+
|
|
1885
|
+
content = content.replace(/\n{3,}/g, '\n\n');
|
|
1886
|
+
writeIndexFile(content);
|
|
1790
1887
|
} catch (err) {
|
|
1791
1888
|
if (!res.headersSent) res.status(500).json({ error: err.message });
|
|
1792
1889
|
}
|
|
1793
1890
|
});
|
|
1794
1891
|
|
|
1795
1892
|
// ---------------------------------------------------------------------------
|
|
1796
|
-
// POST /api/add-websocket
|
|
1893
|
+
// POST /api/add-websocket — create src/services/websocket.js
|
|
1797
1894
|
// ---------------------------------------------------------------------------
|
|
1798
1895
|
router.post('/api/add-websocket', async (req, res) => {
|
|
1799
1896
|
try {
|
|
1800
1897
|
const { samePort, wsPort } = req.body;
|
|
1801
1898
|
|
|
1802
|
-
//
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
try { await execAsync('npm uninstall socket.io', { cwd: process.cwd() }); } catch {}
|
|
1806
|
-
}
|
|
1899
|
+
// Install packages first
|
|
1900
|
+
const currentContent = readIndexFile();
|
|
1901
|
+
const hasSocketIO = currentContent.includes('setupSocketIO') || currentContent.includes('socket.io');
|
|
1807
1902
|
|
|
1808
|
-
|
|
1809
|
-
|
|
1903
|
+
if (hasSocketIO) {
|
|
1904
|
+
try { await execAsync('npm uninstall socket.io && npm install ws', { cwd: process.cwd() }); } catch {
|
|
1905
|
+
try { await execAsync('npm install ws', { cwd: process.cwd() }); } catch {}
|
|
1906
|
+
}
|
|
1907
|
+
} else {
|
|
1908
|
+
await execAsync('npm install ws', { cwd: process.cwd() });
|
|
1909
|
+
}
|
|
1810
1910
|
|
|
1811
|
-
// Send response
|
|
1812
|
-
res.json({
|
|
1813
|
-
message: 'WebSocket setup complete.',
|
|
1814
|
-
files: ['index.js']
|
|
1815
|
-
});
|
|
1911
|
+
// Send response before modifying files
|
|
1912
|
+
res.json({ message: 'WebSocket setup complete.', files: ['src/services/websocket.js', 'index.js'] });
|
|
1816
1913
|
|
|
1817
|
-
//
|
|
1818
|
-
|
|
1914
|
+
// Create src/services/websocket.js
|
|
1915
|
+
const servicesDir = path.join(process.cwd(), 'src', 'services');
|
|
1916
|
+
if (!fs.existsSync(servicesDir)) fs.mkdirSync(servicesDir, { recursive: true });
|
|
1819
1917
|
|
|
1918
|
+
let wsFileContent;
|
|
1820
1919
|
if (samePort) {
|
|
1821
|
-
|
|
1822
|
-
|
|
1920
|
+
wsFileContent = `import { WebSocketServer } from "ws";
|
|
1921
|
+
import http from "http";
|
|
1823
1922
|
|
|
1824
|
-
|
|
1825
|
-
const
|
|
1923
|
+
export function setupWebSocket(app) {
|
|
1924
|
+
const server = http.createServer(app);
|
|
1925
|
+
const wss = new WebSocketServer({ server });
|
|
1826
1926
|
|
|
1827
|
-
wss.on(
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1927
|
+
wss.on("connection", (ws) => {
|
|
1928
|
+
console.log("WebSocket client connected");
|
|
1929
|
+
ws.on("message", (message) => {
|
|
1930
|
+
console.log("Received:", message.toString());
|
|
1931
|
+
});
|
|
1932
|
+
ws.on("close", () => {
|
|
1933
|
+
console.log("WebSocket client disconnected");
|
|
1934
|
+
});
|
|
1834
1935
|
});
|
|
1835
|
-
});`;
|
|
1836
1936
|
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
// Replace app.listen with server.listen
|
|
1841
|
-
let currentContent = readIndexFile();
|
|
1842
|
-
if (currentContent.includes('app.listen(port')) {
|
|
1843
|
-
const serverListenBlock = `server.listen(port, () => {
|
|
1844
|
-
console.log(\`Server is running on port \${port}\`);
|
|
1845
|
-
});`;
|
|
1846
|
-
replaceAppListen(serverListenBlock);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1937
|
+
return { server, wss };
|
|
1938
|
+
}
|
|
1939
|
+
`;
|
|
1849
1940
|
} else {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
wss
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
console.log(
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1941
|
+
const p = wsPort || 8080;
|
|
1942
|
+
wsFileContent = `import { WebSocketServer } from "ws";
|
|
1943
|
+
|
|
1944
|
+
export function setupWebSocket() {
|
|
1945
|
+
const wss = new WebSocketServer({ port: ${p} });
|
|
1946
|
+
|
|
1947
|
+
wss.on("connection", (ws) => {
|
|
1948
|
+
console.log("WebSocket client connected");
|
|
1949
|
+
ws.on("message", (message) => {
|
|
1950
|
+
console.log("Received:", message.toString());
|
|
1951
|
+
});
|
|
1952
|
+
ws.on("close", () => {
|
|
1953
|
+
console.log("WebSocket client disconnected");
|
|
1954
|
+
});
|
|
1861
1955
|
});
|
|
1862
|
-
});
|
|
1863
1956
|
|
|
1864
|
-
console.log(
|
|
1957
|
+
console.log("WebSocket server running on port ${p}");
|
|
1958
|
+
return { wss };
|
|
1959
|
+
}
|
|
1960
|
+
`;
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
fs.writeFileSync(path.join(servicesDir, 'websocket.js'), wsFileContent, 'utf8');
|
|
1865
1964
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1965
|
+
// Remove old Socket.IO service file if present
|
|
1966
|
+
const sioFile = path.join(servicesDir, 'socket.js');
|
|
1967
|
+
if (fs.existsSync(sioFile)) fs.unlinkSync(sioFile);
|
|
1968
|
+
|
|
1969
|
+
// Update index.js — clean approach
|
|
1970
|
+
let content = readIndexFile();
|
|
1971
|
+
|
|
1972
|
+
// Remove old inline socket.io/websocket code
|
|
1973
|
+
content = content.replace(/import\s*\{\s*Server\s*\}\s*from\s*["']socket\.io["'];\s*\n?/g, '');
|
|
1974
|
+
content = content.replace(/import\s*\{\s*WebSocketServer\s*\}\s*from\s*['"]ws['"];\s*\n?/g, '');
|
|
1975
|
+
content = content.replace(/import\s+http\s+from\s*["']http["'];\s*\n?/g, '');
|
|
1976
|
+
content = content.replace(/const\s+server\s*=\s*http\.createServer\(app\);\s*\n?/g, '');
|
|
1977
|
+
content = content.replace(/const\s+io\s*=\s*new\s+Server\(server[\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1978
|
+
content = content.replace(/io\.on\(["']connection["'][\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1979
|
+
content = content.replace(/app\.use\(\(req,\s*res,\s*next\)\s*=>\s*\{\s*\n?\s*req\.io\s*=\s*io;\s*\n?\s*next\(\);\s*\n?\s*\}\);\s*\n?/g, '');
|
|
1980
|
+
content = content.replace(/const\s+wss\s*=\s*new\s+WebSocketServer\(\{[\s\S]*?\n?\}\);\s*\n?/g, '');
|
|
1981
|
+
content = content.replace(/wss\.on\(['"]connection['"][\s\S]*?\n\}\);\s*\n?/g, '');
|
|
1982
|
+
content = content.replace(/console\.log\(['"]WebSocket server running on port.*?['"]?\);\s*\n?/g, '');
|
|
1983
|
+
// Remove old service imports
|
|
1984
|
+
content = content.replace(/import\s*\{\s*setupSocketIO\s*\}\s*from\s*['"]\.\/src\/services\/socket\.js['"];\s*\n?/g, '');
|
|
1985
|
+
content = content.replace(/import\s*\{\s*setupWebSocket\s*\}\s*from\s*['"]\.\/src\/services\/websocket\.js['"];\s*\n?/g, '');
|
|
1986
|
+
content = content.replace(/const\s*\{\s*server[^}]*\}\s*=\s*setupSocketIO\(app\);\s*\n?/g, '');
|
|
1987
|
+
content = content.replace(/const\s*\{\s*server[^}]*\}\s*=\s*setupWebSocket\(app\);\s*\n?/g, '');
|
|
1988
|
+
content = content.replace(/setupWebSocket\(app[^)]*\);\s*\n?/g, '');
|
|
1989
|
+
content = content.replace(/setupWebSocket\(\);\s*\n?/g, '');
|
|
1990
|
+
|
|
1991
|
+
// Add import for websocket service
|
|
1992
|
+
const lastImport = content.lastIndexOf('import ');
|
|
1993
|
+
const lineEnd = content.indexOf('\n', lastImport);
|
|
1994
|
+
content = content.slice(0, lineEnd + 1) + 'import { setupWebSocket } from "./src/services/websocket.js";\n' + content.slice(lineEnd + 1);
|
|
1995
|
+
|
|
1996
|
+
// Add setup call before listen
|
|
1997
|
+
const listenIdx = content.search(/\/\/ ── Start Server|async function startServer|function startServer/);
|
|
1998
|
+
if (listenIdx !== -1) {
|
|
1999
|
+
if (samePort) {
|
|
2000
|
+
content = content.slice(0, listenIdx) + 'const { server } = setupWebSocket(app);\n\n' + content.slice(listenIdx);
|
|
2001
|
+
// Replace app.listen with server.listen
|
|
2002
|
+
content = content.replace('const listener = app.listen(tryPort)', 'const listener = server.listen(tryPort)');
|
|
2003
|
+
if (content.includes('app.listen(port') && !content.includes('startServer')) {
|
|
2004
|
+
content = content.replace(/app\.listen\(port[\s\S]*?\}\);?\n?/m, 'server.listen(port, () => {\n console.log(`Server is running on port ${port}`);\n});\n');
|
|
2005
|
+
}
|
|
2006
|
+
} else {
|
|
2007
|
+
content = content.slice(0, listenIdx) + 'setupWebSocket();\n\n' + content.slice(listenIdx);
|
|
1868
2008
|
}
|
|
1869
2009
|
}
|
|
2010
|
+
|
|
2011
|
+
content = content.replace(/\n{3,}/g, '\n\n');
|
|
2012
|
+
writeIndexFile(content);
|
|
1870
2013
|
} catch (err) {
|
|
1871
2014
|
if (!res.headersSent) res.status(500).json({ error: err.message });
|
|
1872
2015
|
}
|
|
@@ -2266,6 +2409,8 @@ router.get('/shared/:id', (req, res) => {
|
|
|
2266
2409
|
const filePath = path.join(sharedDir, req.params.id + '.json');
|
|
2267
2410
|
if (!fs.existsSync(filePath)) return res.status(404).send('Collection not found');
|
|
2268
2411
|
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
2412
|
+
if (!data.tests) data.tests = [];
|
|
2413
|
+
if (!data.responses) data.responses = {};
|
|
2269
2414
|
const rawAppName = data.appName || 'My App';
|
|
2270
2415
|
// Convert kebab-case/snake_case to Title Case: "test-app" → "Test App"
|
|
2271
2416
|
const appName = rawAppName.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|