instaserve 0.1.24 → 1.0.1
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 +131 -33
- package/generate-certs.sh +59 -0
- package/instaserve +51 -0
- package/module.mjs +91 -25
- package/package.json +7 -6
- package/public/index.html +208 -12
- package/routes.js +54 -0
- package/.devcontainer/Dockerfile +0 -9
- package/.devcontainer/devcontainer.json +0 -22
- package/bun/http.js +0 -50
- package/deno/server.js +0 -39
- package/public/enigmatic.js +0 -150
- package/public/sw.js +0 -18
- package/public/test.html +0 -1
- package/routes.mjs +0 -13
- package/server.mjs +0 -22
- package/test.mjs +0 -48
package/README.md
CHANGED
|
@@ -1,47 +1,145 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1 style="color: #3b82f6; margin: 10px 0 5px;">Instaserve</h1>
|
|
3
|
+
<p style="color: #6b7280; margin: 0;">Instant web stack for Node.js</p>
|
|
4
|
+
</div>
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
## Usage
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Use a routes.mjs or one will be created automatically
|
|
8
|
+
<div style="background: white; padding: 15px; border-radius: 6px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); margin: 15px 0;">
|
|
9
|
+
<pre style="margin: 0;"><code>npx instaserve [options]</code></pre>
|
|
10
|
+
</div>
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
Starts a deno server using routes.mjs and static serving
|
|
12
|
+
## Options
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
15
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-port <number></code>
|
|
16
|
+
<span>Port to listen on (default: 3000)</span>
|
|
17
|
+
</div>
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
20
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-ip <address></code>
|
|
21
|
+
<span>IP address to bind to (default: 127.0.0.1)</span>
|
|
22
|
+
</div>
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
25
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-public <path></code>
|
|
26
|
+
<span>Public directory path (default: ./public)</span>
|
|
27
|
+
</div>
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
30
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-api <file></code>
|
|
31
|
+
<span>Path to routes file (default: ./routes.js)</span>
|
|
32
|
+
</div>
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
34
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
35
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-secure</code>
|
|
36
|
+
<span>Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</span>
|
|
37
|
+
</div>
|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
<div style="display: flex; gap: 8px; margin: 8px 0;">
|
|
40
|
+
<code style="background: #f3f4f6; padding: 2px 4px; border-radius: 4px; color: #2563eb;">-help</code>
|
|
41
|
+
<span>Show help message</span>
|
|
42
|
+
</div>
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
````
|
|
44
|
+
## HTTPS Support
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:
|
|
47
|
+
|
|
48
|
+
1. **Generate certificates:**
|
|
49
|
+
```bash
|
|
50
|
+
./generate-certs.sh
|
|
51
|
+
```
|
|
52
|
+
This creates `cert.pem` and `key.pem` files and adds them to your system's trust store.
|
|
53
|
+
|
|
54
|
+
2. **Run with HTTPS:**
|
|
55
|
+
```bash
|
|
56
|
+
npx instaserve -secure
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The certificate generation script:
|
|
60
|
+
- Creates a self-signed certificate valid for 365 days
|
|
61
|
+
- Automatically adds the certificate to your system trust store (macOS/Linux)
|
|
62
|
+
- Prevents browser security warnings
|
|
63
|
+
|
|
64
|
+
## Routes
|
|
65
|
+
|
|
66
|
+
The routes file (`routes.js` by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.
|
|
67
|
+
|
|
68
|
+
### Basic Route Example
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
37
71
|
export default {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
// Handle GET /hello
|
|
73
|
+
hello: (req, res, data) => {
|
|
74
|
+
return { message: 'Hello World' }
|
|
75
|
+
}
|
|
41
76
|
}
|
|
42
|
-
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Special Routes (Middleware)
|
|
80
|
+
|
|
81
|
+
Routes starting with `_` are middleware functions that run on **every request** before the main route handler. They are useful for:
|
|
82
|
+
|
|
83
|
+
- Logging requests
|
|
84
|
+
- Authentication
|
|
85
|
+
- Request modification
|
|
86
|
+
- Response headers
|
|
87
|
+
|
|
88
|
+
Middleware functions can:
|
|
89
|
+
- Return `false` to continue to the next middleware or main route
|
|
90
|
+
- Return a truthy value to stop processing and use that as the response
|
|
91
|
+
- Modify the request or response objects
|
|
92
|
+
|
|
93
|
+
#### Middleware Example
|
|
43
94
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
95
|
+
```javascript
|
|
96
|
+
export default {
|
|
97
|
+
// Log every request
|
|
98
|
+
_log: (req, res, data) => {
|
|
99
|
+
console.log(`${req.method} ${req.url}`)
|
|
100
|
+
return false // Continue processing
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Block unauthorized requests
|
|
104
|
+
_auth: (req, res, data) => {
|
|
105
|
+
if (!data.token) {
|
|
106
|
+
res.writeHead(401)
|
|
107
|
+
return 'Unauthorized'
|
|
108
|
+
}
|
|
109
|
+
return false // Continue if authorized
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Route Parameters
|
|
115
|
+
|
|
116
|
+
Each route function receives:
|
|
117
|
+
- `req` - The HTTP request object
|
|
118
|
+
- `res` - The HTTP response object
|
|
119
|
+
- `data` - Combined data from:
|
|
120
|
+
- POST body (if JSON)
|
|
121
|
+
- URL query parameters
|
|
122
|
+
- Form data
|
|
123
|
+
|
|
124
|
+
### Example Routes File
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// routes.js
|
|
128
|
+
export default {
|
|
129
|
+
// Middleware example
|
|
130
|
+
_debug: (req, res, data) => {
|
|
131
|
+
console.log('Request:', req.url)
|
|
132
|
+
return false // Continue to next route
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// API endpoint
|
|
136
|
+
api: (req, res, data) => {
|
|
137
|
+
return { status: 'ok', data }
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Error handling
|
|
141
|
+
testerror: () => {
|
|
142
|
+
throw new Error('Test error')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "Generating self-signed certificates for Instaserve..."
|
|
4
|
+
|
|
5
|
+
# Create openssl config file
|
|
6
|
+
cat > openssl.conf << EOF
|
|
7
|
+
[req]
|
|
8
|
+
distinguished_name = req_distinguished_name
|
|
9
|
+
req_extensions = v3_req
|
|
10
|
+
prompt = no
|
|
11
|
+
|
|
12
|
+
[req_distinguished_name]
|
|
13
|
+
C = US
|
|
14
|
+
ST = State
|
|
15
|
+
L = City
|
|
16
|
+
O = Instaserve
|
|
17
|
+
OU = Development
|
|
18
|
+
CN = localhost
|
|
19
|
+
|
|
20
|
+
[v3_req]
|
|
21
|
+
basicConstraints = CA:FALSE
|
|
22
|
+
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
|
23
|
+
extendedKeyUsage = serverAuth, clientAuth
|
|
24
|
+
subjectAltName = @alt_names
|
|
25
|
+
|
|
26
|
+
[alt_names]
|
|
27
|
+
DNS.1 = localhost
|
|
28
|
+
DNS.2 = *.localhost
|
|
29
|
+
IP.1 = 127.0.0.1
|
|
30
|
+
IP.2 = ::1
|
|
31
|
+
EOF
|
|
32
|
+
|
|
33
|
+
# Generate private key
|
|
34
|
+
openssl genrsa -out key.pem 2048
|
|
35
|
+
|
|
36
|
+
# Generate certificate with config
|
|
37
|
+
openssl req -new -x509 -key key.pem -out cert.pem -days 365 -config openssl.conf -extensions v3_req
|
|
38
|
+
|
|
39
|
+
# Clean up config
|
|
40
|
+
rm openssl.conf
|
|
41
|
+
|
|
42
|
+
echo "Certificates generated: cert.pem and key.pem"
|
|
43
|
+
|
|
44
|
+
# Add to system trust store (macOS)
|
|
45
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
46
|
+
echo "Adding certificate to macOS trust store..."
|
|
47
|
+
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain cert.pem
|
|
48
|
+
echo "Certificate added to macOS trust store"
|
|
49
|
+
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
|
50
|
+
echo "Adding certificate to Linux trust store..."
|
|
51
|
+
sudo cp cert.pem /usr/local/share/ca-certificates/instaserve.crt
|
|
52
|
+
sudo update-ca-certificates
|
|
53
|
+
echo "Certificate added to Linux trust store"
|
|
54
|
+
else
|
|
55
|
+
echo "Please manually add cert.pem to your system's trust store"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
echo "HTTPS certificates ready! Use 'npx instaserve -secure' to enable HTTPS"
|
|
59
|
+
echo "Note: You may still see browser warnings for self-signed certificates"
|
package/instaserve
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
|
|
6
|
+
console.log(chalk.cyan('\nInstaserve - Instant Web Stack\n'))
|
|
7
|
+
console.log(chalk.yellow('Usage:'))
|
|
8
|
+
console.log(chalk.green(' npx instaserve [options]\n'))
|
|
9
|
+
console.log(chalk.yellow('Options:'))
|
|
10
|
+
console.log(chalk.green(' -port <number>') + ' Port to listen on (default: 3000)')
|
|
11
|
+
console.log(chalk.green(' -ip <address>') + ' IP address to bind to (default: 127.0.0.1)')
|
|
12
|
+
console.log(chalk.green(' -public <path>') + ' Public directory path (default: ./public)')
|
|
13
|
+
console.log(chalk.green(' -api <file>') + ' Path to routes file (default: ./routes.js)')
|
|
14
|
+
console.log(chalk.green(' -secure') + ' Enable HTTPS (requires cert.pem and key.pem: run generate-certs.sh)')
|
|
15
|
+
console.log(chalk.green(' -help') + ' Show this help message\n')
|
|
16
|
+
|
|
17
|
+
if (process.argv.includes('-help')) {
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
import server from './module.mjs'
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2)
|
|
24
|
+
const params = {}
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i]
|
|
27
|
+
if (arg.startsWith('-')) {
|
|
28
|
+
const key = arg.slice(1)
|
|
29
|
+
const nextArg = args[i + 1]
|
|
30
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
31
|
+
params[key] = nextArg
|
|
32
|
+
i++ // Skip the next argument since we used it
|
|
33
|
+
} else {
|
|
34
|
+
params[key] = true // Boolean flag
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Load routes file
|
|
40
|
+
let routes = {}
|
|
41
|
+
const routesFile = params.api || './routes.js'
|
|
42
|
+
if (fs.existsSync(routesFile)) {
|
|
43
|
+
try {
|
|
44
|
+
const imported = await import(routesFile)
|
|
45
|
+
routes = imported.default || imported
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.log(chalk.yellow(`Warning: Could not load routes file ${routesFile}: ${e.message}`))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
server(routes)
|
package/module.mjs
CHANGED
|
@@ -1,31 +1,55 @@
|
|
|
1
1
|
import http from 'node:http'
|
|
2
|
+
import https from 'node:https'
|
|
2
3
|
import fs from 'node:fs'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const args = process.argv.slice(2)
|
|
6
|
+
const params = {}
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i]
|
|
9
|
+
if (arg.startsWith('-')) {
|
|
10
|
+
const key = arg.slice(1)
|
|
11
|
+
const nextArg = args[i + 1]
|
|
12
|
+
if (nextArg && !nextArg.startsWith('-')) {
|
|
13
|
+
params[key] = nextArg
|
|
14
|
+
i++ // Skip the next argument since we used it
|
|
15
|
+
} else {
|
|
16
|
+
params[key] = true // Boolean flag
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
const {debug, port, ip} = process.env
|
|
11
|
-
|
|
12
21
|
function public_file(r, s) {
|
|
13
22
|
if (r.url == '/') r.url = '/index.html'
|
|
14
|
-
const fn =
|
|
23
|
+
const fn = `${params.public || './public'}${r.url.replace(/\.\./g, '')}`
|
|
15
24
|
if (fs.existsSync(fn)) {
|
|
16
|
-
|
|
17
|
-
|
|
25
|
+
const content = fs.readFileSync(fn, 'utf-8')
|
|
26
|
+
if (fn.match(/.js$/)) {
|
|
27
|
+
s.writeHead(200, { 'Content-Type': 'application/javascript' })
|
|
28
|
+
} else {
|
|
29
|
+
s.writeHead(200)
|
|
30
|
+
}
|
|
31
|
+
s.end(content)
|
|
32
|
+
return true // Indicate file was served
|
|
18
33
|
}
|
|
34
|
+
return false // Indicate no file was served
|
|
19
35
|
}
|
|
20
36
|
|
|
21
|
-
export default function (routes, port = 3000, ip = '127.0.0.1') {
|
|
22
|
-
const
|
|
37
|
+
export default async function (routes, port = params.port || 3000, ip = params.ip || '127.0.0.1') {
|
|
38
|
+
const publicDir = params.public || './public'
|
|
39
|
+
if (publicDir.includes('..')) {
|
|
40
|
+
throw new Error('Public directory path cannot contain ".."')
|
|
41
|
+
}
|
|
42
|
+
if (!fs.existsSync(publicDir)) {
|
|
43
|
+
throw new Error(`Public directory "${publicDir}" does not exist`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const requestHandler = async (r, s) => {
|
|
23
47
|
let sdata = '', rrurl = r.url || ''
|
|
48
|
+
let responseSent = false
|
|
49
|
+
|
|
24
50
|
r.on('data', (s) => sdata += s.toString().trim())
|
|
25
51
|
r.on('end', (x) => {
|
|
26
52
|
try {
|
|
27
|
-
if (debug) console.log(`routes: "${JSON.stringify(routes)}"`)
|
|
28
|
-
|
|
29
53
|
// Compose data object
|
|
30
54
|
const data = sdata ? JSON.parse(sdata) : {}
|
|
31
55
|
const qs = rrurl.split('?')
|
|
@@ -36,29 +60,72 @@ export default function (routes, port = 3000, ip = '127.0.0.1') {
|
|
|
36
60
|
|
|
37
61
|
const midware = Object.keys(routes)
|
|
38
62
|
.filter((k) => k.startsWith('_'))
|
|
39
|
-
.find((k) =>
|
|
63
|
+
.find((k) => {
|
|
64
|
+
const result = routes[k](r, s, data)
|
|
65
|
+
if (result && !responseSent) {
|
|
66
|
+
responseSent = true
|
|
67
|
+
s.end(typeof result === 'string' ? result : JSON.stringify(result))
|
|
68
|
+
}
|
|
69
|
+
return result
|
|
70
|
+
})
|
|
40
71
|
|
|
41
72
|
// Response closed by middleware
|
|
42
|
-
if(s.
|
|
73
|
+
if(responseSent || s.writableEnded) return
|
|
43
74
|
|
|
44
|
-
|
|
45
|
-
if(
|
|
75
|
+
// Try to serve public file
|
|
76
|
+
if(public_file(r, s)) {
|
|
77
|
+
responseSent = true
|
|
78
|
+
return
|
|
79
|
+
}
|
|
46
80
|
|
|
47
81
|
const url = rrurl.split('/')[1].split('?')[0]
|
|
48
82
|
if (routes[url]) {
|
|
49
83
|
const resp = routes[url](r, s, data)
|
|
50
|
-
if
|
|
51
|
-
|
|
84
|
+
if (!responseSent && !s.writableEnded) {
|
|
85
|
+
responseSent = true
|
|
86
|
+
s.end(typeof resp === 'string' ? resp:JSON.stringify(resp))
|
|
87
|
+
}
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!responseSent && !s.writableEnded) {
|
|
92
|
+
responseSent = true
|
|
93
|
+
s.writeHead(404);
|
|
94
|
+
s.end();
|
|
52
95
|
}
|
|
53
|
-
throw Error(r.url + ' not found')
|
|
54
96
|
} catch (e) {
|
|
55
97
|
console.error(e.stack)
|
|
56
|
-
s.
|
|
98
|
+
if (!responseSent && !s.writableEnded) {
|
|
99
|
+
responseSent = true
|
|
100
|
+
s.writeHead(500).end()
|
|
101
|
+
}
|
|
57
102
|
}
|
|
58
103
|
})
|
|
59
|
-
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let server
|
|
107
|
+
if (params.secure) {
|
|
108
|
+
const certPath = './cert.pem'
|
|
109
|
+
const keyPath = './key.pem'
|
|
110
|
+
|
|
111
|
+
if (!fs.existsSync(certPath) || !fs.existsSync(keyPath)) {
|
|
112
|
+
throw new Error('Certificate files not found. Run ./generate-certs.sh first.')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const options = {
|
|
116
|
+
cert: fs.readFileSync(certPath),
|
|
117
|
+
key: fs.readFileSync(keyPath)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
server = https.createServer(options, requestHandler)
|
|
121
|
+
} else {
|
|
122
|
+
server = http.createServer(requestHandler)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
server.listen(port || 3000, ip || '')
|
|
60
126
|
|
|
61
|
-
|
|
127
|
+
const protocol = params.secure ? 'https' : 'http'
|
|
128
|
+
console.log(`started on: ${protocol}://${(process.env.ip || ip)}:${(process.env.port || port)}, public: ${publicDir}, ${Object.keys(routes).length > 0 ? `using routes: ${Object.keys(routes)}` : 'not using routes'}`)
|
|
62
129
|
|
|
63
130
|
return {
|
|
64
131
|
routes: routes,
|
|
@@ -66,5 +133,4 @@ export default function (routes, port = 3000, ip = '127.0.0.1') {
|
|
|
66
133
|
server: server,
|
|
67
134
|
stop: () => { server.close(); return true }
|
|
68
135
|
}
|
|
69
|
-
|
|
70
|
-
}
|
|
136
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instaserve",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Instant web stack",
|
|
5
5
|
"main": "module.mjs",
|
|
6
|
-
"bin": "./
|
|
6
|
+
"bin": "./instaserve",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "node
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
"start": "node instaserve"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"chalk": "^5.4.1"
|
|
12
13
|
}
|
|
13
14
|
}
|
package/public/index.html
CHANGED
|
@@ -1,13 +1,209 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Instaserve</title>
|
|
5
|
+
<style>
|
|
6
|
+
:root {
|
|
7
|
+
--primary: #3b82f6;
|
|
8
|
+
--primary-dark: #2563eb;
|
|
9
|
+
--text: #1f2937;
|
|
10
|
+
--text-light: #6b7280;
|
|
11
|
+
--bg: #f9fafb;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
max-width: 800px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 20px 15px;
|
|
19
|
+
color: var(--text);
|
|
20
|
+
background: var(--bg);
|
|
21
|
+
}
|
|
22
|
+
.header {
|
|
23
|
+
text-align: center;
|
|
24
|
+
margin-bottom: 20px;
|
|
25
|
+
}
|
|
26
|
+
.orb {
|
|
27
|
+
width: 60px;
|
|
28
|
+
height: 60px;
|
|
29
|
+
background: radial-gradient(circle at 30% 30%, #60a5fa, var(--primary));
|
|
30
|
+
border-radius: 50%;
|
|
31
|
+
margin: 0 auto 10px;
|
|
32
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
33
|
+
}
|
|
34
|
+
h1 {
|
|
35
|
+
color: var(--primary);
|
|
36
|
+
font-size: 2rem;
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
.subtitle {
|
|
40
|
+
color: var(--text-light);
|
|
41
|
+
font-size: 1rem;
|
|
42
|
+
margin: 5px 0;
|
|
43
|
+
}
|
|
44
|
+
h2 {
|
|
45
|
+
color: var(--text);
|
|
46
|
+
font-size: 1.3rem;
|
|
47
|
+
margin: 20px 0 10px;
|
|
48
|
+
padding-bottom: 5px;
|
|
49
|
+
border-bottom: 2px solid var(--primary);
|
|
50
|
+
}
|
|
51
|
+
h3 {
|
|
52
|
+
color: var(--text);
|
|
53
|
+
font-size: 1.1rem;
|
|
54
|
+
margin: 15px 0 8px;
|
|
55
|
+
}
|
|
56
|
+
code {
|
|
57
|
+
background: #f3f4f6;
|
|
58
|
+
padding: 2px 4px;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
font-family: 'SF Mono', Menlo, monospace;
|
|
61
|
+
color: var(--primary-dark);
|
|
62
|
+
}
|
|
63
|
+
pre {
|
|
64
|
+
background: #f3f4f6;
|
|
65
|
+
padding: 10px;
|
|
66
|
+
border-radius: 6px;
|
|
67
|
+
overflow-x: auto;
|
|
68
|
+
border: 1px solid #e5e7eb;
|
|
69
|
+
margin: 10px 0;
|
|
70
|
+
font-size: 0.9rem;
|
|
71
|
+
}
|
|
72
|
+
.option {
|
|
73
|
+
margin: 8px 0;
|
|
74
|
+
padding-left: 0;
|
|
75
|
+
position: relative;
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: 8px;
|
|
78
|
+
}
|
|
79
|
+
.option:before {
|
|
80
|
+
display: none;
|
|
81
|
+
}
|
|
82
|
+
.option code {
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
}
|
|
85
|
+
ul {
|
|
86
|
+
padding-left: 15px;
|
|
87
|
+
margin: 8px 0;
|
|
88
|
+
}
|
|
89
|
+
li {
|
|
90
|
+
margin: 5px 0;
|
|
91
|
+
}
|
|
92
|
+
.usage {
|
|
93
|
+
background: white;
|
|
94
|
+
padding: 15px;
|
|
95
|
+
border-radius: 6px;
|
|
96
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
97
|
+
margin: 15px 0;
|
|
98
|
+
}
|
|
99
|
+
p {
|
|
100
|
+
margin: 8px 0;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
103
|
+
</head>
|
|
104
|
+
<body>
|
|
105
|
+
<div class="header">
|
|
106
|
+
<div class="orb"></div>
|
|
107
|
+
<h1>Instaserve</h1>
|
|
108
|
+
<p class="subtitle">Instant web stack for Node.js</p>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="usage">
|
|
112
|
+
<h2>Usage</h2>
|
|
113
|
+
<pre><code>npx instaserve [options]</code></pre>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<h2>Options</h2>
|
|
117
|
+
<div class="option"><code>-port <number></code> - Port to listen on (default: 3000)</div>
|
|
118
|
+
<div class="option"><code>-ip <address></code> - IP address to bind to (default: 127.0.0.1)</div>
|
|
119
|
+
<div class="option"><code>-public <path></code> - Public directory path (default: ./public)</div>
|
|
120
|
+
<div class="option"><code>-api <file></code> - Path to routes file (default: ./routes.js)</div>
|
|
121
|
+
<div class="option"><code>-secure</code> - Enable HTTPS (requires cert.pem and key.pem - run ./generate-certs.sh)</div>
|
|
122
|
+
<div class="option"><code>-help</code> - Show help message</div>
|
|
123
|
+
|
|
124
|
+
<h2>HTTPS Support</h2>
|
|
125
|
+
<p>Instaserve supports HTTPS with self-signed certificates. To enable HTTPS:</p>
|
|
126
|
+
<ol>
|
|
127
|
+
<li><strong>Generate certificates:</strong> <code>./generate-certs.sh</code></li>
|
|
128
|
+
<li><strong>Run with HTTPS:</strong> <code>npx instaserve -secure</code></li>
|
|
129
|
+
</ol>
|
|
130
|
+
<p>The certificate generation script creates trusted certificates that won't show browser warnings.</p>
|
|
131
|
+
|
|
132
|
+
<h2>Routes</h2>
|
|
133
|
+
<p>The routes file (<code>routes.js</code> by default) defines your API endpoints. Each route is a function that handles requests to a specific URL path.</p>
|
|
134
|
+
|
|
135
|
+
<h3>Basic Route Example</h3>
|
|
136
|
+
<pre><code>export default {
|
|
137
|
+
// Handle GET /hello
|
|
138
|
+
hello: (req, res, data) => {
|
|
139
|
+
return { message: 'Hello World' }
|
|
140
|
+
}
|
|
141
|
+
}</code></pre>
|
|
142
|
+
|
|
143
|
+
<h3>Special Routes (Middleware)</h3>
|
|
144
|
+
<p>Routes starting with <code>_</code> are middleware functions that run on <strong>every request</strong> before the main route handler. They are useful for:</p>
|
|
145
|
+
<ul>
|
|
146
|
+
<li>Logging requests</li>
|
|
147
|
+
<li>Authentication</li>
|
|
148
|
+
<li>Request modification</li>
|
|
149
|
+
<li>Response headers</li>
|
|
150
|
+
</ul>
|
|
151
|
+
<p>Middleware functions can:</p>
|
|
152
|
+
<ul>
|
|
153
|
+
<li>Return <code>false</code> to continue to the next middleware or main route</li>
|
|
154
|
+
<li>Return a truthy value to stop processing and use that as the response</li>
|
|
155
|
+
<li>Modify the request or response objects</li>
|
|
156
|
+
</ul>
|
|
157
|
+
|
|
158
|
+
<h4>Middleware Example</h4>
|
|
159
|
+
<pre><code>export default {
|
|
160
|
+
// Log every request
|
|
161
|
+
_log: (req, res, data) => {
|
|
162
|
+
console.log(`${req.method} ${req.url}`)
|
|
163
|
+
return false // Continue processing
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Block unauthorized requests
|
|
167
|
+
_auth: (req, res, data) => {
|
|
168
|
+
if (!data.token) {
|
|
169
|
+
res.writeHead(401)
|
|
170
|
+
return 'Unauthorized'
|
|
171
|
+
}
|
|
172
|
+
return false // Continue if authorized
|
|
173
|
+
}
|
|
174
|
+
}</code></pre>
|
|
175
|
+
|
|
176
|
+
<h3>Route Parameters</h3>
|
|
177
|
+
<p>Each route function receives:</p>
|
|
178
|
+
<ul>
|
|
179
|
+
<li><code>req</code> - The HTTP request object</li>
|
|
180
|
+
<li><code>res</code> - The HTTP response object</li>
|
|
181
|
+
<li><code>data</code> - Combined data from:
|
|
182
|
+
<ul>
|
|
183
|
+
<li>POST body (if JSON)</li>
|
|
184
|
+
<li>URL query parameters</li>
|
|
185
|
+
<li>Form data</li>
|
|
186
|
+
</ul>
|
|
187
|
+
</li>
|
|
188
|
+
</ul>
|
|
189
|
+
|
|
190
|
+
<h3>Example Routes File</h3>
|
|
191
|
+
<pre><code>export default {
|
|
192
|
+
// Middleware example
|
|
193
|
+
_debug: (req, res, data) => {
|
|
194
|
+
console.log('Request:', req.url)
|
|
195
|
+
return false // Continue to next route
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// API endpoint
|
|
199
|
+
api: (req, res, data) => {
|
|
200
|
+
return { status: 'ok', data }
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// Error handling
|
|
204
|
+
testerror: () => {
|
|
205
|
+
throw new Error('Test error')
|
|
206
|
+
}
|
|
207
|
+
}</code></pre>
|
|
208
|
+
</body>
|
|
209
|
+
</html>
|
package/routes.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file defines the API routes and middleware for the Instaserve web server.
|
|
3
|
+
*
|
|
4
|
+
* The file exports a default object containing route handlers and middleware functions.
|
|
5
|
+
*
|
|
6
|
+
* Middleware functions (prefixed with _):
|
|
7
|
+
* - _debug: Logs request method, URL and data to console
|
|
8
|
+
* - _returnfalsy: Example middleware that returns true to stop request processing
|
|
9
|
+
* - _example: Demonstrates middleware behavior with console log
|
|
10
|
+
* - _end: Example of early response termination (commented out)
|
|
11
|
+
*
|
|
12
|
+
* Route handlers:
|
|
13
|
+
* - api: Returns a string response with the request data
|
|
14
|
+
* - testerror: Throws an error for testing error handling
|
|
15
|
+
* - testdata: Example route that returns the context parameter
|
|
16
|
+
*
|
|
17
|
+
* Each route handler receives:
|
|
18
|
+
* - req: HTTP request object
|
|
19
|
+
* - s: HTTP response object
|
|
20
|
+
* - data: Combined request data (POST body, query params, form data)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
// Middleware functions (prefixed with _) run on every request
|
|
25
|
+
// Return false to continue processing, or a value to use as response
|
|
26
|
+
|
|
27
|
+
// Example: Log all requests
|
|
28
|
+
_log: (req, res, data) => {
|
|
29
|
+
console.log(`${req.method} ${req.url}`)
|
|
30
|
+
return false // Continue to next middleware or route
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/**** Example: Basic authentication
|
|
34
|
+
_auth: (req, res, data) => {
|
|
35
|
+
if (!data.token) {
|
|
36
|
+
res.writeHead(401)
|
|
37
|
+
return 'Unauthorized'
|
|
38
|
+
}
|
|
39
|
+
return false // Continue if authorized
|
|
40
|
+
}, */
|
|
41
|
+
|
|
42
|
+
// Regular route handlers
|
|
43
|
+
hello: (req, res, data) => {
|
|
44
|
+
return { message: 'Hello World' }
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
_debug: ({method, url}, s, data) => { console.log(method, url, data) },
|
|
48
|
+
_example: (r, s) => console.log('in routes.mjs, returning a truthy value (above) will stop the chain'),
|
|
49
|
+
// _returnfalsy: (r, s) => { return true }, // Commented out - interferes with static file serving
|
|
50
|
+
|
|
51
|
+
api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
|
|
52
|
+
testerror: () => { throw new Error('this from testerror') },
|
|
53
|
+
testdata: (r, s, c, d) => c
|
|
54
|
+
}
|
package/.devcontainer/Dockerfile
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine/.devcontainer/base.Dockerfile
|
|
2
|
-
|
|
3
|
-
# [Choice] Alpine version: 3.16, 3.15, 3.14, 3.13
|
|
4
|
-
ARG VARIANT="3.16"
|
|
5
|
-
FROM mcr.microsoft.com/vscode/devcontainers/base:0-alpine-${VARIANT}
|
|
6
|
-
|
|
7
|
-
# ** [Optional] Uncomment this section to install additional packages. **
|
|
8
|
-
# RUN apk update \
|
|
9
|
-
# && apk add --no-cache <your-package-list-here>
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
|
2
|
-
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/alpine
|
|
3
|
-
{
|
|
4
|
-
"name": "Alpine-NodeJS",
|
|
5
|
-
"build": {
|
|
6
|
-
"dockerfile": "Dockerfile",
|
|
7
|
-
// Update 'VARIANT' to pick an Alpine version: 3.13, 3.14, 3.15, 3.16
|
|
8
|
-
"args": { "VARIANT": "3.16" }
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
12
|
-
// "forwardPorts": [],
|
|
13
|
-
|
|
14
|
-
// Use 'postCreateCommand' to run commands after the container is created.
|
|
15
|
-
// "postCreateCommand": "uname -a",
|
|
16
|
-
|
|
17
|
-
// Replace when using a ptrace-based debugger like C++, Go, and Rust
|
|
18
|
-
// "runArgs": [ "--init", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ],
|
|
19
|
-
|
|
20
|
-
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
|
21
|
-
"remoteUser": "vscode"
|
|
22
|
-
}
|
package/bun/http.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import {existsSync} from 'fs'
|
|
2
|
-
import { pathToFileURL } from 'node:url'
|
|
3
|
-
import { resolve } from 'node:path'
|
|
4
|
-
const routesfile = resolve(process.env.routes || '../routes.mjs')
|
|
5
|
-
const routesurl = pathToFileURL(routesfile).href
|
|
6
|
-
|
|
7
|
-
const routes = (await import(routesurl)).default
|
|
8
|
-
const port = process.env.port || 3000
|
|
9
|
-
|
|
10
|
-
class s {
|
|
11
|
-
end(s) {
|
|
12
|
-
this.resp = s
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
Bun.serve({
|
|
17
|
-
port: port,
|
|
18
|
-
async fetch(r) {
|
|
19
|
-
|
|
20
|
-
let url = new URL(r.url).pathname
|
|
21
|
-
const data = await r.text()
|
|
22
|
-
|
|
23
|
-
const ru = {method: r.method, url: url}
|
|
24
|
-
const rs = new s()
|
|
25
|
-
|
|
26
|
-
const midware = Object.keys(routes)
|
|
27
|
-
.filter(k => k.startsWith('_'))
|
|
28
|
-
.find(k => routes[k](ru, rs, data))
|
|
29
|
-
|
|
30
|
-
// Routes.mjs
|
|
31
|
-
if(routes[url]) {
|
|
32
|
-
const f = routes[url](ru, rs, data)
|
|
33
|
-
return new Response(rs.resp)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Static
|
|
37
|
-
const fn = (url == '/') ? `public/index.html` : `public/${url}`
|
|
38
|
-
if (existsSync(fn))
|
|
39
|
-
return new Response(Bun.file(fn))
|
|
40
|
-
|
|
41
|
-
return new Response('', { status: 404 })
|
|
42
|
-
|
|
43
|
-
},
|
|
44
|
-
error(e) {
|
|
45
|
-
console.error(e)
|
|
46
|
-
return new Response('', { status: 404 })
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
console.log(`Started on: ${port}, using routes: ${Object.keys(routes)}`)
|
package/deno/server.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "https://deno.land/std/fs/mod.ts";
|
|
2
|
-
|
|
3
|
-
const routesfile = Deno.env.get('routes') || '../routes.mjs'
|
|
4
|
-
const port = Deno.env.get('port') || 3000
|
|
5
|
-
const routes = (await import(routesfile)).default
|
|
6
|
-
|
|
7
|
-
class s {
|
|
8
|
-
end(s) {
|
|
9
|
-
this.resp = s
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
Deno.serve(async (r) => {
|
|
14
|
-
|
|
15
|
-
const data = await r.text()
|
|
16
|
-
let url = new URL(r.url).pathname
|
|
17
|
-
const ru = { method: r.method, url: url }
|
|
18
|
-
const rs = new s()
|
|
19
|
-
|
|
20
|
-
const midware = Object.keys(routes)
|
|
21
|
-
.filter((k) => k.startsWith('_'))
|
|
22
|
-
.find((k) => routes[k](ru, rs, data))
|
|
23
|
-
|
|
24
|
-
// Routes.mjs
|
|
25
|
-
if (routes[url]) {
|
|
26
|
-
const f = routes[url](ru, rs, data)
|
|
27
|
-
return new Response(rs.resp)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Static
|
|
31
|
-
const fn = (url == '/') ? `public/index.html` : `public/${url}`
|
|
32
|
-
if (existsSync(fn))
|
|
33
|
-
return new Response(await Deno.readTextFile(fn), { headers: { 'Content-Type': 'text/html' } })
|
|
34
|
-
|
|
35
|
-
return new Response('', { status: 404 })
|
|
36
|
-
|
|
37
|
-
}, {port: port})
|
|
38
|
-
|
|
39
|
-
console.log(`routes: ${Object.keys(routes)}`)
|
package/public/enigmatic.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
const w = {}, d = document
|
|
2
|
-
|
|
3
|
-
// helpers
|
|
4
|
-
|
|
5
|
-
w.$ = d.querySelector.bind(d)
|
|
6
|
-
w.$$ = d.querySelectorAll.bind(d)
|
|
7
|
-
w.loadJS = (src) => {
|
|
8
|
-
return new Promise((r, j) => {
|
|
9
|
-
if ($(`script[src="${src}"]`)) return r(true)
|
|
10
|
-
const s = d.createElement('script')
|
|
11
|
-
s.src = src
|
|
12
|
-
s.addEventListener('load', r)
|
|
13
|
-
d.head.appendChild(s)
|
|
14
|
-
})
|
|
15
|
-
}
|
|
16
|
-
w.loadCSS = (src) => {
|
|
17
|
-
return new Promise((r, j) => {
|
|
18
|
-
const s = document.createElement('link')
|
|
19
|
-
s.rel = 'stylesheet'
|
|
20
|
-
s.href = src
|
|
21
|
-
s.addEventListener('load', r)
|
|
22
|
-
d.head.appendChild(s)
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
w.wait = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
26
|
-
w.ready = async () => {
|
|
27
|
-
return new Promise((r) => {
|
|
28
|
-
if (document.readyState === 'complete') r(true)
|
|
29
|
-
document.onreadystatechange = () => {
|
|
30
|
-
if (document.readyState === 'complete') r()
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
w.child = (type = 'div', html = '') => {
|
|
35
|
-
const e = d.createElement(type)
|
|
36
|
-
e.innerHTML = html
|
|
37
|
-
d.body.appendChild(e)
|
|
38
|
-
return e
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Custom element
|
|
42
|
-
|
|
43
|
-
w.element = (name, { onMount = x => x, beforeData = x => x, style, template = '' }) => {
|
|
44
|
-
customElements.define(name, class extends HTMLElement {
|
|
45
|
-
async connectedCallback(props) {
|
|
46
|
-
await onMount()
|
|
47
|
-
if (style) {
|
|
48
|
-
const s = document.createElement('style')
|
|
49
|
-
s.innerHTML = `${name} {${style}}`
|
|
50
|
-
d.body.appendChild(s)
|
|
51
|
-
}
|
|
52
|
-
this.template = template
|
|
53
|
-
if (!this.template.match('{'))
|
|
54
|
-
this.innerHTML = this.template
|
|
55
|
-
}
|
|
56
|
-
set(o) {
|
|
57
|
-
this.innerHTML = ''
|
|
58
|
-
o = beforeData(o)
|
|
59
|
-
if (!Array.isArray(o)) o = [o]
|
|
60
|
-
const m = new Function('o', 'return `' + this.template + '`')
|
|
61
|
-
o.map((i) => (this.innerHTML += m(o)))
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (window.components) {
|
|
67
|
-
for (let name in window.components)
|
|
68
|
-
w.element(name, window.components[name])
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Data
|
|
72
|
-
|
|
73
|
-
w.state = new Proxy(
|
|
74
|
-
{}, {
|
|
75
|
-
set: (obj, prop, value) => {
|
|
76
|
-
let ret = []
|
|
77
|
-
for (const e of $$(`[data*=${prop}]`)) {
|
|
78
|
-
console.log(['setting e', e.tagName, e.id, value])
|
|
79
|
-
e.set(value)
|
|
80
|
-
}
|
|
81
|
-
obj[prop] = value
|
|
82
|
-
},
|
|
83
|
-
get: (obj, prop, receiver) => {
|
|
84
|
-
if (prop == '_state') return obj
|
|
85
|
-
return obj[prop]
|
|
86
|
-
},
|
|
87
|
-
}
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
w.dataEvent = (x) => console.log(`dataevent: ${x}`)
|
|
91
|
-
|
|
92
|
-
w.fetchJSON = async (url, key) => {
|
|
93
|
-
const j = await (await fetch(url)).json()
|
|
94
|
-
if (key) state[key] = j
|
|
95
|
-
dataEvent(j)
|
|
96
|
-
return j
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
w.streamJSON = async (url, key) => {
|
|
100
|
-
const ev = new EventSource(url)
|
|
101
|
-
ev.onmessage = (ev) => {
|
|
102
|
-
const j = JSON.parse(ev.data)
|
|
103
|
-
if (key) state[key] = j
|
|
104
|
-
dataEvent(j)
|
|
105
|
-
return j
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// State changes
|
|
110
|
-
|
|
111
|
-
w.trackStateChanges = () =>
|
|
112
|
-
(w.dataEvent = (o) =>
|
|
113
|
-
localStorage.set(new Date().toISOString(), JSON.stringify(o)))
|
|
114
|
-
w.untrackStateChanges = () =>
|
|
115
|
-
(w.dataEvent = (o) => console.log('dataevent:', o))
|
|
116
|
-
|
|
117
|
-
// Startup
|
|
118
|
-
|
|
119
|
-
w.start = async () => {
|
|
120
|
-
await w.ready();
|
|
121
|
-
[...$$('div')].map((e) => {
|
|
122
|
-
if (!e.id)
|
|
123
|
-
e.id = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)
|
|
124
|
-
e.pr = {};
|
|
125
|
-
[...e.attributes].map((a) => (e.pr[a.name] = a.value))
|
|
126
|
-
if (e.pr.fetch) e.fetch = fetchJSON.bind(null, e.pr.fetch, e.id)
|
|
127
|
-
if ('immediate' in e.pr) e.fetch()
|
|
128
|
-
if (e.pr.stream) e.stream = streamJSON.bind(null, e.pr.stream, e.id)
|
|
129
|
-
if (e.pr.data) {
|
|
130
|
-
if (e.innerHTML && e.innerHTML.includes('{')) {
|
|
131
|
-
e.template = e.innerHTML.replaceAll('{', '${')
|
|
132
|
-
e.innerHTML = ''
|
|
133
|
-
}
|
|
134
|
-
e.set = (o) => {
|
|
135
|
-
e.innerHTML = ''
|
|
136
|
-
if (!Array.isArray(o)) o = [o]
|
|
137
|
-
const m = new Function('o', 'return `' + e.template + '`')
|
|
138
|
-
o.map((i) => (e.innerHTML += m(i)))
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
w.enigmatic = { version: '2022-03-05 0.10.2' }
|
|
145
|
-
Object.assign(window, w);
|
|
146
|
-
|
|
147
|
-
(async () => {
|
|
148
|
-
await w.start()
|
|
149
|
-
if (window.main) window.main()
|
|
150
|
-
})()
|
package/public/sw.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
self.addEventListener('fetch', event => {
|
|
2
|
-
const cache = await caches.open('e')
|
|
3
|
-
const req = event.request
|
|
4
|
-
|
|
5
|
-
event.respondWith(async () => {
|
|
6
|
-
if(!req.url.match(/\.js$|\.css$/))
|
|
7
|
-
return fetch(req)
|
|
8
|
-
|
|
9
|
-
const response = await cache.match(req)
|
|
10
|
-
if(response) return response
|
|
11
|
-
response = await fetch(req)
|
|
12
|
-
|
|
13
|
-
if(response.status == 200)
|
|
14
|
-
cache.put(req, response)
|
|
15
|
-
|
|
16
|
-
return response
|
|
17
|
-
})
|
|
18
|
-
})
|
package/public/test.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
ok
|
package/routes.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import url from 'node:url'
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
/*_testerror: ()=>console.testerrors(),*/
|
|
5
|
-
_debug: ({method, url}, s, data) => { console.log(method, url, data) },
|
|
6
|
-
_returnfalsy: (r, s) => { return true },
|
|
7
|
-
_example: (r, s) => console.log('returning a truthy value (above) will stop the chain'),
|
|
8
|
-
//_end: (r, s) => s.end('ended early'),
|
|
9
|
-
|
|
10
|
-
api: (r, s, data) => 'an example api response, data:' + JSON.stringify(data),
|
|
11
|
-
testerror: () => { throw new Error('this from testerror') },
|
|
12
|
-
testdata: (r, s, c, d) => c
|
|
13
|
-
}
|
package/server.mjs
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/local/bin/node
|
|
2
|
-
|
|
3
|
-
// > npx instaserve
|
|
4
|
-
// > port=8080 npx instaserve
|
|
5
|
-
|
|
6
|
-
import server from './module.mjs'
|
|
7
|
-
import { pathToFileURL } from 'node:url'
|
|
8
|
-
import fs from 'node:fs'
|
|
9
|
-
const [npx, instaserve, cmd] = process.argv
|
|
10
|
-
const {port, ip} = process.env
|
|
11
|
-
|
|
12
|
-
if (cmd === 'create' && !fs.existsSync('routes.mjs')) {
|
|
13
|
-
fs.writeFileSync('routes.mjs', `export default {
|
|
14
|
-
_debug: ({method, url}, s) => !console.log(method, url),
|
|
15
|
-
_example: (r, s) => console.log('returning a falsy value (above) will stop the chain'),
|
|
16
|
-
api: (r, s) => 'an example api response'
|
|
17
|
-
}`)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const routesurl = pathToFileURL('routes.mjs').href
|
|
21
|
-
const routes = (await import(routesurl)).default
|
|
22
|
-
server(routes, Number(port||3000), ip)
|
package/test.mjs
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import serve from './module.mjs'
|
|
2
|
-
import { get, te, tde } from '../instax/module.mjs'
|
|
3
|
-
|
|
4
|
-
const port = 8080
|
|
5
|
-
const server = serve({
|
|
6
|
-
api: (r, s) => 'Hello!',
|
|
7
|
-
api2: (r, s, data) => JSON.stringify(data)
|
|
8
|
-
}, port)
|
|
9
|
-
te(server.port, port)
|
|
10
|
-
|
|
11
|
-
// Routes
|
|
12
|
-
const resp = await get('http://localhost:8080/api')
|
|
13
|
-
te(resp, 'Hello!')
|
|
14
|
-
const resp2 = await get('http://localhost:8080/api2', {method: 'POST', body: JSON.stringify({a:1})})
|
|
15
|
-
tde(resp2, {a:1})
|
|
16
|
-
|
|
17
|
-
// Public
|
|
18
|
-
const testhtml = await get('http://localhost:8080/test.html')
|
|
19
|
-
te(testhtml, 'ok')
|
|
20
|
-
te(server.stop(), true)
|
|
21
|
-
|
|
22
|
-
// Test route returned values
|
|
23
|
-
const db = {}
|
|
24
|
-
const server2 = serve({
|
|
25
|
-
_: ({url}) => console.log(url),
|
|
26
|
-
__: ({headers: {host}, method, url}) => console.log(host, method, url),
|
|
27
|
-
str: () => 'ok',
|
|
28
|
-
obj: x => ({a: 'ok'}),
|
|
29
|
-
undef: () => undefined,
|
|
30
|
-
testerror: () => { throw new Error('this from testerror')}
|
|
31
|
-
}, 8085)
|
|
32
|
-
te(server2.port, 8085)
|
|
33
|
-
te(server2.routes.str(), 'ok')
|
|
34
|
-
|
|
35
|
-
const return_str = await get('http://localhost:8085/str')
|
|
36
|
-
te(return_str, 'ok')
|
|
37
|
-
|
|
38
|
-
const return_obj = await get('http://localhost:8085/obj')
|
|
39
|
-
te(return_obj.a, 'ok')
|
|
40
|
-
|
|
41
|
-
const return_undefined = await get('http://localhost:8085/undef')
|
|
42
|
-
te(return_undefined, '')
|
|
43
|
-
|
|
44
|
-
const test_error = await get('http://localhost:8085/testerror')
|
|
45
|
-
te(test_error.error, 'this from testerror')
|
|
46
|
-
|
|
47
|
-
server2.stop()
|
|
48
|
-
console.log('tests complete')
|