laju-server 1.0.1 → 1.0.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.
Files changed (2) hide show
  1. package/index.js +395 -93
  2. package/package.json +4 -5
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
- const HyperExpress = require('hyper-express');
1
+ const http = require('http');
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const mime = require('mime-types');
5
+ const os = require('os');
5
6
 
6
7
  function startServer(folderPath, port = 3000) {
7
8
  // Check if folder exists
@@ -16,25 +17,9 @@ function startServer(folderPath, port = 3000) {
16
17
  process.exit(1);
17
18
  }
18
19
 
19
- const server = new HyperExpress.Server({
20
- fast_buffers: true,
21
- max_body_buffer: 64 * 1024, // 64KB buffer for streaming
22
- streaming: {
23
- readable: {
24
- highWaterMark: 64 * 1024, // 64KB chunks
25
- },
26
- writable: {
27
- highWaterMark: 64 * 1024, // 64KB chunks
28
- },
29
- },
30
- });
31
-
32
- // Serve static files
33
- server.get('/*', async (req, res) => {
34
- let requestPath = req.path;
35
-
20
+ const server = http.createServer((req, res) => {
36
21
  // Decode URL
37
- requestPath = decodeURIComponent(requestPath);
22
+ let requestPath = decodeURIComponent(req.url.split('?')[0]);
38
23
 
39
24
  // Remove leading slash
40
25
  if (requestPath.startsWith('/')) {
@@ -51,115 +36,431 @@ function startServer(folderPath, port = 3000) {
51
36
  // Security: prevent directory traversal
52
37
  const resolvedPath = path.resolve(filePath);
53
38
  if (!resolvedPath.startsWith(path.resolve(folderPath))) {
54
- res.status(403).send('Forbidden');
39
+ res.writeHead(403);
40
+ res.end('Forbidden');
55
41
  return;
56
42
  }
57
43
 
58
- try {
59
- // Check if file exists
60
- if (!fs.existsSync(resolvedPath)) {
61
- // Try adding index.html if it's a directory
62
- const indexPath = path.join(resolvedPath, 'index.html');
63
- if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
64
- return serveFile(indexPath, res, req);
65
- }
66
- res.status(404).send('File not found');
44
+ // Check if file exists
45
+ fs.stat(resolvedPath, (err, stat) => {
46
+ if (err) {
47
+ // Check if parent directory exists and show listing
48
+ const parentDir = path.dirname(resolvedPath);
49
+ fs.stat(parentDir, (err2, stat2) => {
50
+ if (!err2 && stat2.isDirectory()) {
51
+ const indexPath = path.join(parentDir, 'index.html');
52
+ fs.stat(indexPath, (err3, stat3) => {
53
+ if (!err3 && stat3.isFile()) {
54
+ return serveFile(indexPath, stat3, req, res);
55
+ }
56
+ return serveDirectoryListing(parentDir, path.dirname(req.url), res);
57
+ });
58
+ return;
59
+ }
60
+ res.writeHead(404);
61
+ res.end('File not found');
62
+ });
67
63
  return;
68
64
  }
69
65
 
70
- const stat = fs.statSync(resolvedPath);
71
-
72
66
  if (stat.isDirectory()) {
73
- // Try serving index.html from directory
74
67
  const indexPath = path.join(resolvedPath, 'index.html');
75
- if (fs.existsSync(indexPath)) {
76
- return serveFile(indexPath, res, req);
77
- }
78
- res.status(404).send('File not found');
68
+ fs.stat(indexPath, (err2, stat2) => {
69
+ if (!err2 && stat2.isFile()) {
70
+ return serveFile(indexPath, stat2, req, res);
71
+ }
72
+ // No index.html, show directory listing
73
+ return serveDirectoryListing(resolvedPath, req.url, res);
74
+ });
79
75
  return;
80
76
  }
81
77
 
82
- return serveFile(resolvedPath, res, req);
83
- } catch (err) {
84
- console.error('Error:', err.message);
85
- res.status(500).send('Internal Server Error');
86
- }
78
+ serveFile(resolvedPath, stat, req, res);
79
+ });
87
80
  });
88
81
 
89
- function serveFile(filePath, res, req) {
90
- const mimeType = mime.lookup(filePath) || 'application/octet-stream';
91
- const stat = fs.statSync(filePath);
92
- const fileSize = stat.size;
82
+ function serveDirectoryListing(dirPath, urlPath, res) {
83
+ fs.readdir(dirPath, { withFileTypes: true }, (err, entries) => {
84
+ if (err) {
85
+ res.writeHead(500);
86
+ res.end('Error reading directory');
87
+ return;
88
+ }
89
+
90
+ // Separate folders and files
91
+ const folders = [];
92
+ const files = [];
93
+
94
+ entries.forEach(entry => {
95
+ if (entry.name.startsWith('.')) return; // Skip hidden files
96
+ if (entry.isDirectory()) {
97
+ folders.push(entry.name);
98
+ } else {
99
+ files.push(entry.name);
100
+ }
101
+ });
102
+
103
+ // Sort alphabetically
104
+ folders.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
105
+ files.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
106
+
107
+ // Normalize URL path
108
+ let basePath = urlPath;
109
+ if (!basePath.endsWith('/')) basePath += '/';
110
+ if (!basePath.startsWith('/')) basePath = '/' + basePath;
111
+
112
+ // Generate HTML
113
+ let html = `<!DOCTYPE html>
114
+ <html lang="id">
115
+ <head>
116
+ <meta charset="UTF-8">
117
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
118
+ <title>Laju Server - ${basePath}</title>
119
+ <style>
120
+ :root {
121
+ --primary: #3b82f6;
122
+ --bg: #f8fafc;
123
+ --surface: #ffffff;
124
+ --text: #1e293b;
125
+ --text-secondary: #64748b;
126
+ --border: #e2e8f0;
127
+ --hover: #f1f5f9;
128
+ }
129
+ * { box-sizing: border-box; margin: 0; padding: 0; }
130
+ body {
131
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
132
+ background: var(--bg);
133
+ color: var(--text);
134
+ line-height: 1.5;
135
+ }
136
+ .container {
137
+ max-width: 900px;
138
+ margin: 40px auto;
139
+ padding: 0 20px;
140
+ }
141
+ header {
142
+ margin-bottom: 24px;
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 12px;
146
+ }
147
+ h1 {
148
+ font-size: 1.25rem;
149
+ font-weight: 600;
150
+ color: var(--text);
151
+ word-break: break-all;
152
+ }
153
+ .badge {
154
+ background: #dbeafe;
155
+ color: #1e40af;
156
+ font-size: 0.75rem;
157
+ padding: 4px 8px;
158
+ border-radius: 6px;
159
+ font-weight: 500;
160
+ }
161
+ .card {
162
+ background: var(--surface);
163
+ border-radius: 12px;
164
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
165
+ border: 1px solid var(--border);
166
+ overflow: hidden;
167
+ }
168
+ .list-header {
169
+ padding: 12px 16px;
170
+ border-bottom: 1px solid var(--border);
171
+ background: #f8fafc;
172
+ font-size: 0.875rem;
173
+ color: var(--text-secondary);
174
+ font-weight: 500;
175
+ display: flex;
176
+ justify-content: space-between;
177
+ }
178
+ .item {
179
+ display: flex;
180
+ align-items: center;
181
+ padding: 12px 16px;
182
+ border-bottom: 1px solid var(--border);
183
+ text-decoration: none;
184
+ color: var(--text);
185
+ transition: all 0.2s ease;
186
+ background: var(--surface);
187
+ }
188
+ .item:last-child {
189
+ border-bottom: none;
190
+ }
191
+ .item:hover {
192
+ background: var(--hover);
193
+ transform: translateX(2px);
194
+ }
195
+ .icon {
196
+ width: 32px;
197
+ height: 32px;
198
+ margin-right: 16px;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ background: #f1f5f9;
203
+ border-radius: 8px;
204
+ color: var(--text-secondary);
205
+ }
206
+ .icon svg {
207
+ width: 20px;
208
+ height: 20px;
209
+ stroke-width: 2px;
210
+ }
211
+ .folder .icon {
212
+ background: #eff6ff;
213
+ color: #3b82f6;
214
+ }
215
+ .file .icon {
216
+ background: #f1f5f9;
217
+ color: #64748b;
218
+ }
219
+ .info {
220
+ flex: 1;
221
+ min-width: 0;
222
+ }
223
+ .name {
224
+ font-weight: 500;
225
+ white-space: nowrap;
226
+ overflow: hidden;
227
+ text-overflow: ellipsis;
228
+ display: block;
229
+ }
230
+ .meta {
231
+ font-size: 0.75rem;
232
+ color: var(--text-secondary);
233
+ margin-top: 2px;
234
+ }
235
+ .action-icon {
236
+ opacity: 0;
237
+ transition: opacity 0.2s;
238
+ color: var(--text-secondary);
239
+ display: flex;
240
+ align-items: center;
241
+ }
242
+ .action-icon svg {
243
+ width: 18px;
244
+ height: 18px;
245
+ }
246
+ .item:hover .action-icon {
247
+ opacity: 1;
248
+ }
249
+ .parent {
250
+ background: #f8fafc;
251
+ }
252
+ .empty {
253
+ padding: 60px 20px;
254
+ text-align: center;
255
+ color: var(--text-secondary);
256
+ display: flex;
257
+ flex-direction: column;
258
+ align-items: center;
259
+ }
260
+ .empty-icon {
261
+ margin-bottom: 16px;
262
+ color: #cbd5e1;
263
+ }
264
+ .empty-icon svg {
265
+ width: 48px;
266
+ height: 48px;
267
+ }
268
+ @media (max-width: 640px) {
269
+ .container { margin: 20px auto; }
270
+ .icon { width: 28px; height: 28px; margin-right: 12px; }
271
+ .icon svg { width: 16px; height: 16px; }
272
+ }
273
+ </style>
274
+ </head>
275
+ <body>
276
+ <div class="container">
277
+ <header>
278
+ <div class="badge">DIR</div>
279
+ <h1>${basePath}</h1>
280
+ </header>
281
+
282
+ <div class="card">
283
+ <div class="list-header">
284
+ <span>Name</span>
285
+ <span>Action</span>
286
+ </div>`;
287
+
288
+ const Icons = {
289
+ folder: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>',
290
+ back: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5v0a5.5 5.5 0 0 1-5.5 5.5H11"/></svg>',
291
+ file: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>',
292
+ image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
293
+ video: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.777-.416L16 10"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>',
294
+ music: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>',
295
+ code: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
296
+ archive: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8v13H3V8"/><path d="M1 3h22v5H1z"/><path d="M10 12h4"/></svg>',
297
+ download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>',
298
+ chevronRight: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>'
299
+ };
300
+
301
+ // Add parent directory link if not at root
302
+ if (basePath !== '/') {
303
+ const parentPath = path.dirname(basePath.slice(0, -1)) || '/';
304
+ html += `\n <a href="${parentPath}" class="item parent folder">
305
+ <span class="icon">${Icons.back}</span>
306
+ <div class="info">
307
+ <span class="name">..</span>
308
+ <div class="meta">Go back</div>
309
+ </div>
310
+ </a>`;
311
+ }
312
+
313
+ // Add folders
314
+ folders.forEach(folder => {
315
+ const href = basePath + encodeURIComponent(folder) + '/';
316
+ html += `\n <a href="${href}" class="item folder">
317
+ <span class="icon">${Icons.folder}</span>
318
+ <div class="info">
319
+ <span class="name">${escapeHtml(folder)}</span>
320
+ <div class="meta">Folder</div>
321
+ </div>
322
+ <span class="action-icon">${Icons.chevronRight}</span>
323
+ </a>`;
324
+ });
325
+
326
+ // Add files with download attribute
327
+ files.forEach(file => {
328
+ const href = basePath + encodeURIComponent(file);
329
+ const icon = getFileIconSvg(file);
330
+ html += `\n <a href="${href}" class="item file" download>
331
+ <span class="icon">${icon}</span>
332
+ <div class="info">
333
+ <span class="name">${escapeHtml(file)}</span>
334
+ <div class="meta">File</div>
335
+ </div>
336
+ <span class="action-icon">${Icons.download}</span>
337
+ </a>`;
338
+ });
339
+
340
+ if (folders.length === 0 && files.length === 0) {
341
+ html += `\n <div class="empty">
342
+ <div class="empty-icon">${Icons.folder}</div>
343
+ <p>Folder ini kosong</p>
344
+ </div>`;
345
+ }
346
+
347
+ html += `\n </div>
348
+ <div style="margin-top: 20px; text-align: center; color: var(--text-secondary); font-size: 0.875rem;">
349
+ Laju Server v1.0.4
350
+ </div>
351
+ </div>
352
+ </body>
353
+ </html>`;
354
+
355
+ res.writeHead(200, {
356
+ 'Content-Type': 'text/html; charset=utf-8',
357
+ 'Content-Length': Buffer.byteLength(html)
358
+ });
359
+ res.end(html);
360
+ });
361
+ }
362
+
363
+ function escapeHtml(text) {
364
+ return text
365
+ .replace(/&/g, '&amp;')
366
+ .replace(/</g, '&lt;')
367
+ .replace(/>/g, '&gt;')
368
+ .replace(/"/g, '&quot;');
369
+ }
370
+
371
+ function getFileIconSvg(filename) {
372
+ const ext = path.extname(filename).toLowerCase();
373
+ // Simple SVG icons
374
+ const svgMap = {
375
+ // Images
376
+ '.jpg': 'image', '.jpeg': 'image', '.png': 'image', '.gif': 'image', '.webp': 'image', '.svg': 'image', '.ico': 'image',
377
+ // Video
378
+ '.mp4': 'video', '.mkv': 'video', '.avi': 'video', '.mov': 'video', '.webm': 'video',
379
+ // Audio
380
+ '.mp3': 'music', '.wav': 'music', '.flac': 'music', '.aac': 'music', '.ogg': 'music',
381
+ // Code
382
+ '.js': 'code', '.ts': 'code', '.py': 'code', '.php': 'code', '.html': 'code', '.css': 'code', '.json': 'code',
383
+ // Archive
384
+ '.zip': 'archive', '.rar': 'archive', '.7z': 'archive', '.tar': 'archive', '.gz': 'archive'
385
+ };
93
386
 
94
- const range = req.headers['range'];
387
+ const iconType = svgMap[ext] || 'file';
388
+
389
+ const icons = {
390
+ file: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>',
391
+ image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>',
392
+ video: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.777-.416L16 10"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg>',
393
+ music: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>',
394
+ code: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>',
395
+ archive: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8v13H3V8"/><path d="M1 3h22v5H1z"/><path d="M10 12h4"/></svg>'
396
+ };
397
+
398
+ return icons[iconType];
399
+ }
95
400
 
96
- // Use larger buffer for big files (64KB chunks)
97
- const highWaterMark = 64 * 1024;
401
+ function serveFile(filePath, stat, req, res) {
402
+ const mimeType = mime.lookup(filePath) || 'application/octet-stream';
403
+ const fileSize = stat.size;
404
+ const range = req.headers.range;
98
405
 
99
406
  if (range) {
100
- // Handle range requests for video streaming / resume download
101
407
  const parts = range.replace(/bytes=/, '').split('-');
102
408
  const start = parseInt(parts[0], 10);
103
- // Limit chunk size to 10MB per request for stability
104
- const maxChunk = 10 * 1024 * 1024;
105
- let end = parts[1] ? parseInt(parts[1], 10) : Math.min(start + maxChunk, fileSize - 1);
106
-
107
- // Ensure end doesn't exceed file size
108
- if (end >= fileSize) end = fileSize - 1;
109
-
409
+ const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
110
410
  const chunkSize = end - start + 1;
111
411
 
112
- res.status(206);
113
- res.setHeader('Content-Range', `bytes ${start}-${end}/${fileSize}`);
114
- res.setHeader('Accept-Ranges', 'bytes');
115
- res.setHeader('Content-Length', chunkSize);
116
- res.setHeader('Content-Type', mimeType);
117
- res.setHeader('Connection', 'keep-alive');
412
+ res.writeHead(206, {
413
+ 'Content-Range': `bytes ${start}-${end}/${fileSize}`,
414
+ 'Accept-Ranges': 'bytes',
415
+ 'Content-Length': chunkSize,
416
+ 'Content-Type': mimeType,
417
+ });
118
418
 
119
- const stream = fs.createReadStream(filePath, { start, end, highWaterMark });
419
+ const stream = fs.createReadStream(filePath, { start, end });
420
+ stream.pipe(res);
120
421
 
121
422
  stream.on('error', (err) => {
122
423
  console.error('Stream error:', err.message);
123
- if (!res.headersSent) {
124
- res.status(500).send('Stream error');
125
- }
424
+ res.end();
126
425
  });
127
426
 
128
- res.stream(stream);
427
+ req.on('close', () => {
428
+ stream.destroy();
429
+ });
129
430
  } else {
130
- // For large files without range, suggest client to use range requests
131
- res.setHeader('Content-Length', fileSize);
132
- res.setHeader('Content-Type', mimeType);
133
- res.setHeader('Accept-Ranges', 'bytes');
134
- res.setHeader('Connection', 'keep-alive');
431
+ res.writeHead(200, {
432
+ 'Content-Length': fileSize,
433
+ 'Content-Type': mimeType,
434
+ 'Accept-Ranges': 'bytes',
435
+ });
135
436
 
136
- const stream = fs.createReadStream(filePath, { highWaterMark });
437
+ const stream = fs.createReadStream(filePath);
438
+ stream.pipe(res);
137
439
 
138
440
  stream.on('error', (err) => {
139
441
  console.error('Stream error:', err.message);
140
- if (!res.headersSent) {
141
- res.status(500).send('Stream error');
142
- }
442
+ res.end();
143
443
  });
144
444
 
145
- res.stream(stream);
445
+ req.on('close', () => {
446
+ stream.destroy();
447
+ });
146
448
  }
147
449
  }
148
450
 
149
- server.listen(port, '0.0.0.0')
150
- .then(() => {
151
- const networkInterfaces = require('os').networkInterfaces();
152
- const addresses = [];
153
-
154
- for (const name of Object.keys(networkInterfaces)) {
155
- for (const net of networkInterfaces[name]) {
156
- if (net.family === 'IPv4' && !net.internal) {
157
- addresses.push(net.address);
158
- }
451
+ server.listen(port, '0.0.0.0', () => {
452
+ const networkInterfaces = os.networkInterfaces();
453
+ const addresses = [];
454
+
455
+ for (const name of Object.keys(networkInterfaces)) {
456
+ for (const net of networkInterfaces[name]) {
457
+ if (net.family === 'IPv4' && !net.internal) {
458
+ addresses.push(net.address);
159
459
  }
160
460
  }
461
+ }
161
462
 
162
- console.log(`
463
+ console.log(`
163
464
  🚀 Laju Server berjalan!
164
465
 
165
466
  📁 Folder: ${folderPath}
@@ -168,11 +469,12 @@ function startServer(folderPath, port = 3000) {
168
469
 
169
470
  Tekan Ctrl+C untuk berhenti
170
471
  `);
171
- })
172
- .catch((err) => {
173
- console.error('❌ Gagal menjalankan server:', err.message);
174
- process.exit(1);
175
- });
472
+ });
473
+
474
+ server.on('error', (err) => {
475
+ console.error('❌ Gagal menjalankan server:', err.message);
476
+ process.exit(1);
477
+ });
176
478
  }
177
479
 
178
480
  module.exports = { startServer };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "laju-server",
3
- "version": "1.0.1",
4
- "description": "Instant static file server using hyper-express",
3
+ "version": "1.0.4",
4
+ "description": "Instant static file server - supports large files up to 100GB+",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "laju-server": "./bin/cli.js"
@@ -12,14 +12,13 @@
12
12
  "keywords": [
13
13
  "static",
14
14
  "server",
15
- "hyper-express",
16
15
  "file-server",
17
- "cli"
16
+ "cli",
17
+ "large-files"
18
18
  ],
19
19
  "author": "",
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "hyper-express": "^6.17.3",
23
22
  "mime-types": "^2.1.35"
24
23
  }
25
24
  }