instaserve 1.1.6 → 1.1.9
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/module.mjs +165 -158
- package/package.json +13 -13
- package/test.js +0 -28
package/module.mjs
CHANGED
|
@@ -6,183 +6,190 @@ import path from 'node:path'
|
|
|
6
6
|
const args = process.argv.slice(2)
|
|
7
7
|
const params = {}
|
|
8
8
|
for (let i = 0; i < args.length; i++) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
9
|
+
const arg = args[i]
|
|
10
|
+
if (arg.startsWith('-')) {
|
|
11
|
+
const key = arg.slice(1)
|
|
12
|
+
const nextArg = args[i + 1]
|
|
13
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
14
|
+
params[key] = nextArg
|
|
15
|
+
i++ // Skip the next argument since we used it
|
|
16
|
+
} else {
|
|
17
|
+
params[key] = true // Boolean flag
|
|
19
18
|
}
|
|
19
|
+
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function public_file(r, s, publicDir) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
s.end(content)
|
|
33
|
-
return true // Indicate file was served
|
|
23
|
+
if (r.url == '/') r.url = '/index.html'
|
|
24
|
+
const fn = path.resolve(publicDir, r.url.replace(/\.\./g, '').replace(/^\//, ''))
|
|
25
|
+
if (fs.existsSync(fn)) {
|
|
26
|
+
const content = fs.readFileSync(fn, 'utf-8')
|
|
27
|
+
if (fn.match(/.js$/)) {
|
|
28
|
+
s.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
29
|
+
} else {
|
|
30
|
+
s.writeHead(200)
|
|
34
31
|
}
|
|
35
|
-
|
|
32
|
+
s.end(content)
|
|
33
|
+
return true // Indicate file was served
|
|
34
|
+
}
|
|
35
|
+
return false // Indicate no file was served
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1', routesFilePath = null) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
const publicDirParam = params.public || './public'
|
|
40
|
+
const publicDir = path.isAbsolute(publicDirParam)
|
|
41
|
+
? publicDirParam
|
|
42
|
+
: path.resolve(process.cwd(), publicDirParam)
|
|
43
|
+
if (publicDir.includes('..')) {
|
|
44
|
+
throw new Error('Public directory path cannot contain ".."')
|
|
45
|
+
}
|
|
46
|
+
if (!fs.existsSync(publicDir)) {
|
|
47
|
+
throw new Error(`Public directory "${publicDir}" does not exist`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const requestHandler = async (r, s) => {
|
|
51
|
+
let sdata = '', rrurl = r.url || ''
|
|
52
|
+
let responseSent = false
|
|
53
|
+
|
|
54
|
+
// Helper to check if value is a 3-digit HTTP status code
|
|
55
|
+
const isStatusCode = (val) => {
|
|
56
|
+
return typeof val === 'number' && val >= 100 && val <= 999 && Math.floor(val) === val
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
|
|
59
|
+
// Helper to send response, handling status codes
|
|
60
|
+
const sendResponse = (result) => {
|
|
61
|
+
if (isStatusCode(result)) {
|
|
62
|
+
s.writeHead(result)
|
|
63
|
+
s.end()
|
|
64
|
+
} else {
|
|
65
|
+
if (result != "SSE") {
|
|
66
|
+
s.end(typeof result === 'string' ? result : JSON.stringify(result))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
48
69
|
}
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
71
|
+
r.on('data', (s) => sdata += s.toString().trim())
|
|
72
|
+
r.on('end', async (x) => {
|
|
73
|
+
try {
|
|
74
|
+
// Compose data object
|
|
75
|
+
const data = sdata ? JSON.parse(sdata) : {}
|
|
76
|
+
const qs = rrurl.split('?')
|
|
77
|
+
if (qs && qs[1]) {
|
|
78
|
+
const o = JSON.parse('{"' + decodeURI(qs[1].replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}')
|
|
79
|
+
Object.assign(data, o)
|
|
57
80
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
81
|
+
|
|
82
|
+
const midwareKeys = Object.keys(routes).filter((k) => k.startsWith('_'))
|
|
83
|
+
for (const k of midwareKeys) {
|
|
84
|
+
let result = routes[k](r, s, data)
|
|
85
|
+
if (result instanceof Promise) result = await result
|
|
86
|
+
|
|
87
|
+
if (result) {
|
|
88
|
+
if (!responseSent) {
|
|
89
|
+
responseSent = true
|
|
90
|
+
sendResponse(result)
|
|
66
91
|
}
|
|
92
|
+
break
|
|
93
|
+
}
|
|
67
94
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const url = urlParts.length > 1 ? urlParts[1].split('?')[0] : ''
|
|
102
|
-
const method = (r.method || 'GET').toUpperCase()
|
|
103
|
-
|
|
104
|
-
// Try method-specific route first (e.g., "POST /endp", "GET /")
|
|
105
|
-
const methodRoute = url ? `${method} /${url}` : `${method} /`
|
|
106
|
-
let routeHandler = routes[methodRoute]
|
|
107
|
-
|
|
108
|
-
// If no exact match, check if any method-specific route exists for this path
|
|
109
|
-
if (!routeHandler) {
|
|
110
|
-
const methods = ['GET', 'POST', 'PUT', 'DELETE']
|
|
111
|
-
const pathRoute = url ? `/${url}` : `/`
|
|
112
|
-
const hasMethodSpecificRoute = methods.some(m => {
|
|
113
|
-
const checkRoute = url ? `${m} /${url}` : `${m} /`
|
|
114
|
-
return routes[checkRoute] !== undefined
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
// If method-specific route exists but for different method, return 405
|
|
118
|
-
if (hasMethodSpecificRoute) {
|
|
119
|
-
if (!responseSent && !s.writableEnded) {
|
|
120
|
-
responseSent = true
|
|
121
|
-
s.writeHead(405)
|
|
122
|
-
s.end()
|
|
123
|
-
}
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Fall back to path-only route (backward compatible)
|
|
128
|
-
routeHandler = routes[url]
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (routeHandler) {
|
|
132
|
-
const resp = routeHandler(r, s, data)
|
|
133
|
-
if (!responseSent && !s.writableEnded) {
|
|
134
|
-
responseSent = true
|
|
135
|
-
sendResponse(resp)
|
|
136
|
-
}
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!responseSent && !s.writableEnded) {
|
|
141
|
-
responseSent = true
|
|
142
|
-
s.writeHead(404);
|
|
143
|
-
s.end();
|
|
144
|
-
}
|
|
145
|
-
} catch (e) {
|
|
146
|
-
console.error(e.stack)
|
|
147
|
-
if (!responseSent && !s.writableEnded) {
|
|
148
|
-
responseSent = true
|
|
149
|
-
s.writeHead(500).end()
|
|
150
|
-
}
|
|
95
|
+
|
|
96
|
+
// Response closed by middleware
|
|
97
|
+
if (responseSent || s.writableEnded) return
|
|
98
|
+
|
|
99
|
+
// Try to serve public file
|
|
100
|
+
if (public_file(r, s, publicDir)) {
|
|
101
|
+
responseSent = true
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const urlParts = rrurl.split('/')
|
|
106
|
+
const url = urlParts.length > 1 ? urlParts[1].split('?')[0] : ''
|
|
107
|
+
const method = (r.method || 'GET').toUpperCase()
|
|
108
|
+
|
|
109
|
+
// Try method-specific route first (e.g., "POST /endp", "GET /")
|
|
110
|
+
const methodRoute = url ? `${method} /${url}` : `${method} /`
|
|
111
|
+
let routeHandler = routes[methodRoute]
|
|
112
|
+
|
|
113
|
+
// If no exact match, check if any method-specific route exists for this path
|
|
114
|
+
if (!routeHandler) {
|
|
115
|
+
const methods = ['GET', 'POST', 'PUT', 'DELETE']
|
|
116
|
+
const pathRoute = url ? `/${url}` : `/`
|
|
117
|
+
const hasMethodSpecificRoute = methods.some(m => {
|
|
118
|
+
const checkRoute = url ? `${m} /${url}` : `${m} /`
|
|
119
|
+
return routes[checkRoute] !== undefined
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// If method-specific route exists but for different method, return 405
|
|
123
|
+
if (hasMethodSpecificRoute) {
|
|
124
|
+
if (!responseSent && !s.writableEnded) {
|
|
125
|
+
responseSent = true
|
|
126
|
+
s.writeHead(405)
|
|
127
|
+
s.end()
|
|
151
128
|
}
|
|
152
|
-
|
|
153
|
-
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Fall back to path-only route (backward compatible)
|
|
133
|
+
routeHandler = routes[url]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (routeHandler) {
|
|
137
|
+
let resp = routeHandler(r, s, data)
|
|
138
|
+
if (resp instanceof Promise) resp = await resp
|
|
154
139
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
|
|
161
|
-
throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
|
|
140
|
+
if (!responseSent && !s.writableEnded) {
|
|
141
|
+
responseSent = true
|
|
142
|
+
sendResponse(resp)
|
|
143
|
+
}
|
|
144
|
+
return
|
|
162
145
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
146
|
+
|
|
147
|
+
if (!responseSent && !s.writableEnded) {
|
|
148
|
+
responseSent = true
|
|
149
|
+
s.writeHead(404);
|
|
150
|
+
s.end();
|
|
167
151
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
152
|
+
} catch (e) {
|
|
153
|
+
console.error(e.stack)
|
|
154
|
+
if (!responseSent && !s.writableEnded) {
|
|
155
|
+
responseSent = true
|
|
156
|
+
s.writeHead(500).end()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let server
|
|
163
|
+
if (params.secure) {
|
|
164
|
+
const certPath = path.resolve(process.cwd(), './cert.pem')
|
|
165
|
+
const keyPath = path.resolve(process.cwd(), './key.pem')
|
|
166
|
+
|
|
167
|
+
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
|
|
168
|
+
throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
|
|
172
169
|
}
|
|
173
170
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const routesInfo = Object.keys(routes).length > 0
|
|
178
|
-
? (routesFilePath ? `using routes: ${Object.keys(routes)} (${routesFilePath})` : `using routes: ${Object.keys(routes)}`)
|
|
179
|
-
: 'not using routes'
|
|
180
|
-
console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${routesInfo}`)
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
routes: routes,
|
|
184
|
-
port: port,
|
|
185
|
-
server: server,
|
|
186
|
-
stop: () => { server.close(); return true }
|
|
171
|
+
const options = {
|
|
172
|
+
cert: fs.readFileSync(certPath),
|
|
173
|
+
key: fs.readFileSync(keyPath)
|
|
187
174
|
}
|
|
175
|
+
|
|
176
|
+
server = https.createServer(options, requestHandler)
|
|
177
|
+
} else {
|
|
178
|
+
server = http.createServer(requestHandler)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
server.listen(port || 3000, ip || '')
|
|
182
|
+
|
|
183
|
+
const protocol = params.secure ? 'https' : 'http'
|
|
184
|
+
const routesInfo = Object.keys(routes).length > 0
|
|
185
|
+
? (routesFilePath ? `using routes: ${Object.keys(routes)} (${routesFilePath})` : `using routes: ${Object.keys(routes)}`)
|
|
186
|
+
: 'not using routes'
|
|
187
|
+
console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${routesInfo}`)
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
routes: routes,
|
|
191
|
+
port: port,
|
|
192
|
+
server: server,
|
|
193
|
+
stop: () => { server.close(); return true }
|
|
194
|
+
}
|
|
188
195
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
2
|
+
"name": "instaserve",
|
|
3
|
+
"version": "1.1.9",
|
|
4
|
+
"description": "Instant web stack",
|
|
5
|
+
"main": "module.mjs",
|
|
6
|
+
"bin": "./instaserve",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node instaserve"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"chalk": "^5.6.2"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/test.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
// Middleware functions (prefixed with _) run on every request
|
|
3
|
-
// Return false to continue processing, or a value to use as response
|
|
4
|
-
|
|
5
|
-
// Example: Log all requests
|
|
6
|
-
_log: (req, res, data) => {
|
|
7
|
-
console.log(`${req.method} ${req.url}`)
|
|
8
|
-
return false // Continue to next middleware or route
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
// Example: Basic authentication (commented out)
|
|
12
|
-
// _auth: (req, res, data) => {
|
|
13
|
-
// if (!data.token) {
|
|
14
|
-
// res.writeHead(401)
|
|
15
|
-
// return 'Unauthorized'
|
|
16
|
-
// }
|
|
17
|
-
// return false // Continue if authorized
|
|
18
|
-
// },
|
|
19
|
-
|
|
20
|
-
// Regular route handlers
|
|
21
|
-
hello: (req, res, data) => {
|
|
22
|
-
return { message: 'Hello World' }
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
api: (req, res, data) => {
|
|
26
|
-
return { message: 'API endpoint', data }
|
|
27
|
-
}
|
|
28
|
-
}
|