@scelar/nodepod 1.0.7 → 1.0.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/README.md +252 -240
- package/dist/__sw__.js +31 -5
- package/dist/{child_process-bGGe8mTj.cjs → child_process-DldQfPd9.cjs} +7431 -7435
- package/dist/child_process-DldQfPd9.cjs.map +1 -0
- package/dist/{child_process-CgnmoilU.js → child_process-eiM1_nmq.js} +8231 -8234
- package/dist/child_process-eiM1_nmq.js.map +1 -0
- package/dist/cross-origin.d.ts +2 -0
- package/dist/{index-DZpqX03n.js → index-CK6KRbI1.js} +37316 -36899
- package/dist/index-CK6KRbI1.js.map +1 -0
- package/dist/{index-NinyWmnj.cjs → index-DfyUKyNH.cjs} +39254 -38824
- package/dist/index-DfyUKyNH.cjs.map +1 -0
- package/dist/index.cjs +67 -67
- package/dist/index.mjs +61 -61
- package/dist/isolation-helpers.d.ts +3 -0
- package/dist/packages/archive-extractor.d.ts +2 -0
- package/dist/packages/version-resolver.d.ts +1 -0
- package/dist/request-proxy.d.ts +3 -0
- package/dist/script-engine.d.ts +1 -1
- package/dist/sdk/nodepod.d.ts +58 -58
- package/dist/sdk/types.d.ts +3 -0
- package/dist/threading/offload-types.d.ts +1 -0
- package/package.json +97 -97
- package/src/cross-origin.ts +75 -26
- package/src/iframe-sandbox.ts +145 -141
- package/src/isolation-helpers.ts +154 -148
- package/src/packages/archive-extractor.ts +251 -248
- package/src/packages/installer.ts +1 -0
- package/src/packages/version-resolver.ts +2 -0
- package/src/polyfills/net.ts +353 -353
- package/src/polyfills/util.ts +559 -559
- package/src/polyfills/worker_threads.ts +326 -326
- package/src/request-proxy.ts +43 -9
- package/src/script-engine.ts +3733 -3722
- package/src/sdk/nodepod.ts +8 -0
- package/src/sdk/types.ts +3 -0
- package/src/shell/shell-builtins.ts +19 -19
- package/src/threading/offload-types.ts +113 -112
- package/src/threading/offload-worker.ts +15 -0
- package/dist/child_process-CgnmoilU.js.map +0 -1
- package/dist/child_process-bGGe8mTj.cjs.map +0 -1
- package/dist/index-DZpqX03n.js.map +0 -1
- package/dist/index-NinyWmnj.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,240 +1,252 @@
|
|
|
1
|
-
# nodepod
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@scelar/nodepod)
|
|
4
|
-
[](https://github.com/ScelarOrg/Nodepod/blob/main/LICENSE)
|
|
5
|
-
|
|
6
|
-
Browser-native Node.js runtime. Run real Node.js code — filesystem, modules, `require()`, npm packages, HTTP servers — entirely inside the browser.
|
|
7
|
-
|
|
8
|
-
No backend. No containers. No WASM Node binary. Just polyfills, an in-memory filesystem, and a JavaScript execution engine.
|
|
9
|
-
|
|
10
|
-
Built by [@R1ck404](https://github.com/R1ck404) — powering [Scelar](https://scelar.com), the AI app builder that actually COMPLETELY builds your apps, from idea to production in a matter of minutes.
|
|
11
|
-
|
|
12
|
-
## Features
|
|
13
|
-
|
|
14
|
-
- **Virtual Filesystem** — Full in-memory `fs` API (read, write, watch, streams, symlinks, glob)
|
|
15
|
-
- **Module System** — `require()`, `import`, `module.exports`, `package.json` resolution, conditional exports
|
|
16
|
-
- **npm Packages** — Install real packages from the npm registry, extracted and resolved in-browser
|
|
17
|
-
- **HTTP Servers** — Run Express, Hono, Elysia, vite and other frameworks with real request/response routing
|
|
18
|
-
- **Shell** — Built-in bash-like shell with 35+ commands (ls, cat, grep, find, sed, etc.), pipes, redirections, variable expansion
|
|
19
|
-
- **Process Model** — Web Worker-based processes with `child_process.exec/spawn/fork`, `worker_threads`, and IPC
|
|
20
|
-
- **Terminal** — Drop-in xterm.js integration with line editing, history, raw mode, Ctrl+C
|
|
21
|
-
- **Preview** — Service Worker-based iframe preview with script injection and WebSocket bridging
|
|
22
|
-
- **Snapshots** — Save and restore the entire filesystem state
|
|
23
|
-
|
|
24
|
-
## Install
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npm install @scelar/nodepod
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Quick Start
|
|
31
|
-
|
|
32
|
-
```typescript
|
|
33
|
-
import { Nodepod } from '@scelar/nodepod';
|
|
34
|
-
|
|
35
|
-
// Boot a nodepod instance with some files
|
|
36
|
-
const nodepod = await Nodepod.boot({
|
|
37
|
-
files: {
|
|
38
|
-
'/index.js': 'console.log("Hello from the browser!")',
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Run a script
|
|
43
|
-
const proc = await nodepod.spawn('node', ['index.js']);
|
|
44
|
-
proc.on('output', (text) => console.log(text));
|
|
45
|
-
await proc.completion;
|
|
46
|
-
|
|
47
|
-
// Read/write files
|
|
48
|
-
await nodepod.fs.writeFile('/data.json', JSON.stringify({ hello: 'world' }));
|
|
49
|
-
const content = await nodepod.fs.readFile('/data.json', 'utf8');
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Terminal Integration
|
|
53
|
-
|
|
54
|
-
nodepod provides built-in xterm.js terminal support:
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
import { Nodepod } from '@scelar/nodepod';
|
|
58
|
-
import { Terminal } from '@xterm/xterm';
|
|
59
|
-
import { FitAddon } from '@xterm/addon-fit';
|
|
60
|
-
|
|
61
|
-
const nodepod = await Nodepod.boot();
|
|
62
|
-
const terminal = nodepod.createTerminal({ Terminal, FitAddon });
|
|
63
|
-
terminal.attach('#terminal-container');
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The terminal handles line editing, command history, prompt rendering, raw/cooked mode, and streaming output out of the box.
|
|
67
|
-
|
|
68
|
-
## Running an Express Server
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
const nodepod = await Nodepod.boot({
|
|
72
|
-
files: {
|
|
73
|
-
'/server.js': `
|
|
74
|
-
const express = require('express');
|
|
75
|
-
const app = express();
|
|
76
|
-
app.get('/', (req, res) => res.json({ status: 'ok' }));
|
|
77
|
-
app.listen(3000, () => console.log('Server running on port 3000'));
|
|
78
|
-
`,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Install express
|
|
83
|
-
await nodepod.install(['express']);
|
|
84
|
-
|
|
85
|
-
// Run the server
|
|
86
|
-
const proc = await nodepod.spawn('node', ['server.js']);
|
|
87
|
-
|
|
88
|
-
// Dispatch requests to the virtual server
|
|
89
|
-
const response = await nodepod.request(3000, 'GET', '/');
|
|
90
|
-
console.log(response.body); // { status: 'ok' }
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Preview Script Injection
|
|
94
|
-
|
|
95
|
-
Inject scripts into preview iframes before any page content loads — useful for setting up bridges between the main window and the iframe:
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
await nodepod.setPreviewScript(`
|
|
99
|
-
window.__bridge = {
|
|
100
|
-
sendToParent(msg) { window.parent.postMessage(msg, '*'); }
|
|
101
|
-
};
|
|
102
|
-
`);
|
|
103
|
-
|
|
104
|
-
// Remove it later
|
|
105
|
-
await nodepod.clearPreviewScript();
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## SDK API
|
|
109
|
-
|
|
110
|
-
### `Nodepod.boot(options?)`
|
|
111
|
-
|
|
112
|
-
Creates a fully wired nodepod instance.
|
|
113
|
-
|
|
114
|
-
| Option | Type | Default | Description |
|
|
115
|
-
|--------|------|---------|-------------|
|
|
116
|
-
| `files` | `Record<string, string>` | — | Initial files to populate the filesystem |
|
|
117
|
-
| `workdir` | `string` | `"/"` | Working directory |
|
|
118
|
-
| `env` | `Record<string, string>` | — | Environment variables |
|
|
119
|
-
| `swUrl` | `string` | — | Service Worker URL (enables preview iframes) |
|
|
120
|
-
| `watermark` | `boolean` | `true` | Show a small "nodepod" badge in preview iframes |
|
|
121
|
-
| `onServerReady` | `(port, url) => void` | — | Callback when a virtual server starts listening |
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `fs.
|
|
132
|
-
| `fs.
|
|
133
|
-
| `fs.
|
|
134
|
-
| `fs.
|
|
135
|
-
| `fs.
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
| `
|
|
150
|
-
| `
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
nodepod
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
│
|
|
172
|
-
│
|
|
173
|
-
│ ├──
|
|
174
|
-
│
|
|
175
|
-
│
|
|
176
|
-
│ │
|
|
177
|
-
│ ├──
|
|
178
|
-
│ │ ├──
|
|
179
|
-
│ │ ├──
|
|
180
|
-
│ │
|
|
181
|
-
│ ├──
|
|
182
|
-
│ │ ├──
|
|
183
|
-
│ │
|
|
184
|
-
│ └──
|
|
185
|
-
│
|
|
186
|
-
│
|
|
187
|
-
│
|
|
188
|
-
│
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
1
|
+
# nodepod
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@scelar/nodepod)
|
|
4
|
+
[](https://github.com/ScelarOrg/Nodepod/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Browser-native Node.js runtime. Run real Node.js code — filesystem, modules, `require()`, npm packages, HTTP servers — entirely inside the browser.
|
|
7
|
+
|
|
8
|
+
No backend. No containers. No WASM Node binary. Just polyfills, an in-memory filesystem, and a JavaScript execution engine.
|
|
9
|
+
|
|
10
|
+
Built by [@R1ck404](https://github.com/R1ck404) — powering [Scelar](https://scelar.com), the AI app builder that actually COMPLETELY builds your apps, from idea to production in a matter of minutes.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Virtual Filesystem** — Full in-memory `fs` API (read, write, watch, streams, symlinks, glob)
|
|
15
|
+
- **Module System** — `require()`, `import`, `module.exports`, `package.json` resolution, conditional exports
|
|
16
|
+
- **npm Packages** — Install real packages from the npm registry, extracted and resolved in-browser
|
|
17
|
+
- **HTTP Servers** — Run Express, Hono, Elysia, vite and other frameworks with real request/response routing
|
|
18
|
+
- **Shell** — Built-in bash-like shell with 35+ commands (ls, cat, grep, find, sed, etc.), pipes, redirections, variable expansion
|
|
19
|
+
- **Process Model** — Web Worker-based processes with `child_process.exec/spawn/fork`, `worker_threads`, and IPC
|
|
20
|
+
- **Terminal** — Drop-in xterm.js integration with line editing, history, raw mode, Ctrl+C
|
|
21
|
+
- **Preview** — Service Worker-based iframe preview with script injection and WebSocket bridging
|
|
22
|
+
- **Snapshots** — Save and restore the entire filesystem state
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @scelar/nodepod
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { Nodepod } from '@scelar/nodepod';
|
|
34
|
+
|
|
35
|
+
// Boot a nodepod instance with some files
|
|
36
|
+
const nodepod = await Nodepod.boot({
|
|
37
|
+
files: {
|
|
38
|
+
'/index.js': 'console.log("Hello from the browser!")',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Run a script
|
|
43
|
+
const proc = await nodepod.spawn('node', ['index.js']);
|
|
44
|
+
proc.on('output', (text) => console.log(text));
|
|
45
|
+
await proc.completion;
|
|
46
|
+
|
|
47
|
+
// Read/write files
|
|
48
|
+
await nodepod.fs.writeFile('/data.json', JSON.stringify({ hello: 'world' }));
|
|
49
|
+
const content = await nodepod.fs.readFile('/data.json', 'utf8');
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Terminal Integration
|
|
53
|
+
|
|
54
|
+
nodepod provides built-in xterm.js terminal support:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Nodepod } from '@scelar/nodepod';
|
|
58
|
+
import { Terminal } from '@xterm/xterm';
|
|
59
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
60
|
+
|
|
61
|
+
const nodepod = await Nodepod.boot();
|
|
62
|
+
const terminal = nodepod.createTerminal({ Terminal, FitAddon });
|
|
63
|
+
terminal.attach('#terminal-container');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The terminal handles line editing, command history, prompt rendering, raw/cooked mode, and streaming output out of the box.
|
|
67
|
+
|
|
68
|
+
## Running an Express Server
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const nodepod = await Nodepod.boot({
|
|
72
|
+
files: {
|
|
73
|
+
'/server.js': `
|
|
74
|
+
const express = require('express');
|
|
75
|
+
const app = express();
|
|
76
|
+
app.get('/', (req, res) => res.json({ status: 'ok' }));
|
|
77
|
+
app.listen(3000, () => console.log('Server running on port 3000'));
|
|
78
|
+
`,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Install express
|
|
83
|
+
await nodepod.install(['express']);
|
|
84
|
+
|
|
85
|
+
// Run the server
|
|
86
|
+
const proc = await nodepod.spawn('node', ['server.js']);
|
|
87
|
+
|
|
88
|
+
// Dispatch requests to the virtual server
|
|
89
|
+
const response = await nodepod.request(3000, 'GET', '/');
|
|
90
|
+
console.log(response.body); // { status: 'ok' }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Preview Script Injection
|
|
94
|
+
|
|
95
|
+
Inject scripts into preview iframes before any page content loads — useful for setting up bridges between the main window and the iframe:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await nodepod.setPreviewScript(`
|
|
99
|
+
window.__bridge = {
|
|
100
|
+
sendToParent(msg) { window.parent.postMessage(msg, '*'); }
|
|
101
|
+
};
|
|
102
|
+
`);
|
|
103
|
+
|
|
104
|
+
// Remove it later
|
|
105
|
+
await nodepod.clearPreviewScript();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## SDK API
|
|
109
|
+
|
|
110
|
+
### `Nodepod.boot(options?)`
|
|
111
|
+
|
|
112
|
+
Creates a fully wired nodepod instance.
|
|
113
|
+
|
|
114
|
+
| Option | Type | Default | Description |
|
|
115
|
+
|--------|------|---------|-------------|
|
|
116
|
+
| `files` | `Record<string, string>` | — | Initial files to populate the filesystem |
|
|
117
|
+
| `workdir` | `string` | `"/"` | Working directory |
|
|
118
|
+
| `env` | `Record<string, string>` | — | Environment variables |
|
|
119
|
+
| `swUrl` | `string` | — | Service Worker URL (enables preview iframes) |
|
|
120
|
+
| `watermark` | `boolean` | `true` | Show a small "nodepod" badge in preview iframes |
|
|
121
|
+
| `onServerReady` | `(port, url) => void` | — | Callback when a virtual server starts listening |
|
|
122
|
+
| `allowedFetchDomains` | `string[] \| null` | npm/github defaults | Extra domains allowed through the CORS proxy. Pass `null` to allow all |
|
|
123
|
+
|
|
124
|
+
### Instance Methods
|
|
125
|
+
|
|
126
|
+
| Method | Description |
|
|
127
|
+
|--------|-------------|
|
|
128
|
+
| `spawn(cmd, args?)` | Run a command, returns `NodepodProcess` |
|
|
129
|
+
| `createTerminal(opts)` | Create an interactive terminal |
|
|
130
|
+
| `install(packages)` | Install npm packages |
|
|
131
|
+
| `fs.readFile(path, enc?)` | Read a file |
|
|
132
|
+
| `fs.writeFile(path, data)` | Write a file |
|
|
133
|
+
| `fs.readdir(path)` | List directory contents |
|
|
134
|
+
| `fs.stat(path)` | Get file stats |
|
|
135
|
+
| `fs.mkdir(path, opts?)` | Create a directory |
|
|
136
|
+
| `fs.rm(path, opts?)` | Remove files/directories |
|
|
137
|
+
| `snapshot()` | Capture filesystem state |
|
|
138
|
+
| `restore(snapshot)` | Restore filesystem from snapshot |
|
|
139
|
+
| `setPreviewScript(script)` | Inject JS into preview iframes |
|
|
140
|
+
| `clearPreviewScript()` | Remove injected preview script |
|
|
141
|
+
| `port(num)` | Get preview URL for a virtual server port |
|
|
142
|
+
|
|
143
|
+
### `NodepodProcess`
|
|
144
|
+
|
|
145
|
+
Returned by `spawn()`. An EventEmitter with:
|
|
146
|
+
|
|
147
|
+
| Event | Payload | Description |
|
|
148
|
+
|-------|---------|-------------|
|
|
149
|
+
| `output` | `string` | stdout data |
|
|
150
|
+
| `error` | `string` | stderr data |
|
|
151
|
+
| `exit` | `number` | exit code |
|
|
152
|
+
|
|
153
|
+
Property: `completion` — a `Promise<void>` that resolves when the process exits.
|
|
154
|
+
|
|
155
|
+
## Security
|
|
156
|
+
|
|
157
|
+
nodepod includes several security measures for running untrusted code:
|
|
158
|
+
|
|
159
|
+
- **CORS proxy domain whitelist** — Proxied fetch requests only go through to whitelisted domains (npm registry, GitHub, esm.sh, etc by default). Extend via the `allowedFetchDomains` boot option or pass `null` to disable
|
|
160
|
+
- **Service Worker auth** — Control messages to the SW require a random token generated at boot, so other scripts on the same origin can't inject preview content
|
|
161
|
+
- **WebSocket bridge auth** — The BroadcastChannel used for WS bridging between preview iframes and the main thread is token-authenticated
|
|
162
|
+
- **Package integrity** — Downloaded npm tarballs are checked against the registry's `shasum` before extraction
|
|
163
|
+
- **Iframe sandbox** — The cross-origin iframe mode uses `sandbox="allow-scripts"` to prevent top-frame navigation, popups, and form submissions
|
|
164
|
+
- **Origin-checked messaging** — The sandbox page validates `event.origin` on incoming messages and only responds to the configured parent origin
|
|
165
|
+
|
|
166
|
+
## Architecture
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
nodepod
|
|
170
|
+
├── src/
|
|
171
|
+
│ ├── script-engine.ts # JavaScript execution engine (require, ESM→CJS, REPL)
|
|
172
|
+
│ ├── memory-volume.ts # In-memory virtual filesystem
|
|
173
|
+
│ ├── syntax-transforms.ts # ESM-to-CJS conversion via acorn
|
|
174
|
+
│ ├── module-transformer.ts # esbuild-wasm code transforms
|
|
175
|
+
│ ├── polyfills/ # Node.js built-in module polyfills
|
|
176
|
+
│ │ ├── fs.ts # Filesystem (read, write, watch, streams, glob)
|
|
177
|
+
│ │ ├── http.ts # HTTP server/client
|
|
178
|
+
│ │ ├── stream.ts # Readable, Writable, Transform, Duplex
|
|
179
|
+
│ │ ├── events.ts # EventEmitter
|
|
180
|
+
│ │ ├── path.ts # Path operations
|
|
181
|
+
│ │ ├── crypto.ts # Hashing, randomBytes, randomUUID
|
|
182
|
+
│ │ ├── child_process.ts # exec, spawn, fork, execSync
|
|
183
|
+
│ │ ├── net.ts # TCP Socket, Server
|
|
184
|
+
│ │ └── ... # 40+ more polyfills
|
|
185
|
+
│ ├── shell/ # Bash-like shell interpreter
|
|
186
|
+
│ │ ├── shell-parser.ts # Tokenizer + recursive-descent parser
|
|
187
|
+
│ │ ├── shell-builtins.ts # 35+ built-in commands
|
|
188
|
+
│ │ └── shell-interpreter.ts # AST executor, pipes, redirections
|
|
189
|
+
│ ├── packages/ # npm package management
|
|
190
|
+
│ │ ├── installer.ts # Package installer
|
|
191
|
+
│ │ ├── registry-client.ts # npm registry client
|
|
192
|
+
│ │ └── version-resolver.ts # Semver resolution
|
|
193
|
+
│ ├── threading/ # Worker-based process model
|
|
194
|
+
│ │ ├── process-manager.ts # Process lifecycle management
|
|
195
|
+
│ │ └── process-handle.ts # Process I/O handle
|
|
196
|
+
│ └── sdk/ # Public SDK layer
|
|
197
|
+
│ ├── nodepod.ts # Nodepod.boot() entry point
|
|
198
|
+
│ ├── nodepod-fs.ts # Async filesystem facade
|
|
199
|
+
│ ├── nodepod-process.ts # Process handle
|
|
200
|
+
│ └── nodepod-terminal.ts # xterm.js terminal integration
|
|
201
|
+
└── static/
|
|
202
|
+
└── __sw__.js # Service Worker for HTTP request interception
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Supported Node.js Modules
|
|
206
|
+
|
|
207
|
+
**Full implementations:** fs, path, events, stream, buffer, process, http, https, net, crypto, zlib, url, querystring, util, os, tty, child_process, assert, readline, module, timers, string_decoder, perf_hooks, constants, punycode
|
|
208
|
+
|
|
209
|
+
**Shims/stubs:** dns, worker_threads, vm, v8, tls, dgram, cluster, http2, inspector, domain, diagnostics_channel, async_hooks
|
|
210
|
+
|
|
211
|
+
## CDN Usage
|
|
212
|
+
Once published to npm, nodepod is automatically available on CDNs:
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<!-- unpkg -->
|
|
216
|
+
<script src="https://unpkg.com/@scelar/nodepod"></script>
|
|
217
|
+
|
|
218
|
+
<!-- jsDelivr -->
|
|
219
|
+
<script src="https://cdn.jsdelivr.net/npm/@scelar/nodepod"></script>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
git clone https://github.com/ScelarOrg/Nodepod.git
|
|
226
|
+
cd Nodepod
|
|
227
|
+
npm install
|
|
228
|
+
npm run type-check # TypeScript validation
|
|
229
|
+
npm run build:lib # Build ESM + CJS bundles
|
|
230
|
+
npm test # Run tests
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions and guidelines.
|
|
236
|
+
|
|
237
|
+
## Author
|
|
238
|
+
|
|
239
|
+
Created by [@R1ck404](https://github.com/R1ck404). Part of the [Scelar](https://scelar.com) ecosystem.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
[MIT + Commons Clause](./LICENSE)
|
|
244
|
+
|
|
245
|
+
This project uses the MIT license with the [Commons Clause](https://commonsclause.com/) restriction. In plain terms:
|
|
246
|
+
|
|
247
|
+
- **Use it** freely in your own projects, including commercial ones
|
|
248
|
+
- **Modify it**, fork it, learn from it
|
|
249
|
+
- **Ship products** built with nodepod — that's totally fine
|
|
250
|
+
- **Don't resell nodepod itself** as a standalone product or hosted service
|
|
251
|
+
|
|
252
|
+
Basically, build whatever you want with it — just don't take nodepod, rebrand it, and sell it as your own thing.
|
package/dist/__sw__.js
CHANGED
|
@@ -29,6 +29,12 @@ let previewScript = null;
|
|
|
29
29
|
// Watermark badge shown in preview iframes. On by default.
|
|
30
30
|
let watermarkEnabled = true;
|
|
31
31
|
|
|
32
|
+
// auth token from init, checked on control messages
|
|
33
|
+
let authToken = null;
|
|
34
|
+
|
|
35
|
+
// ws bridge token, gets baked into the shim script
|
|
36
|
+
let wsToken = null;
|
|
37
|
+
|
|
32
38
|
// Standard MIME types by file extension — used as a safety net when
|
|
33
39
|
// the virtual server returns text/html (SPA fallback) or omits Content-Type
|
|
34
40
|
// for paths that are clearly not HTML.
|
|
@@ -110,10 +116,20 @@ self.addEventListener("activate", (event) => {
|
|
|
110
116
|
|
|
111
117
|
self.addEventListener("message", (event) => {
|
|
112
118
|
const data = event.data;
|
|
119
|
+
|
|
120
|
+
// init sets up the port + grabs the auth token
|
|
113
121
|
if (data?.type === "init" && data.port) {
|
|
114
122
|
port = data.port;
|
|
115
123
|
port.onmessage = onPortMessage;
|
|
124
|
+
if (data.token) {
|
|
125
|
+
authToken = data.token;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
116
128
|
}
|
|
129
|
+
|
|
130
|
+
// everything else needs the right token
|
|
131
|
+
if (authToken && data?.token !== authToken) return;
|
|
132
|
+
|
|
117
133
|
// Allow main thread to register/unregister preview clients
|
|
118
134
|
if (data?.type === "register-preview") {
|
|
119
135
|
previewClients.set(data.clientId, data.serverPort);
|
|
@@ -127,6 +143,9 @@ self.addEventListener("message", (event) => {
|
|
|
127
143
|
if (data?.type === "set-watermark") {
|
|
128
144
|
watermarkEnabled = !!data.enabled;
|
|
129
145
|
}
|
|
146
|
+
if (data?.type === "set-ws-token") {
|
|
147
|
+
wsToken = data.wsToken ?? null;
|
|
148
|
+
}
|
|
130
149
|
});
|
|
131
150
|
|
|
132
151
|
function onPortMessage(event) {
|
|
@@ -245,12 +264,15 @@ self.addEventListener("fetch", (event) => {
|
|
|
245
264
|
// to the main thread's request-proxy, which dispatches upgrade events on the
|
|
246
265
|
// virtual HTTP server. Works with any framework/library, not specific to Vite.
|
|
247
266
|
|
|
248
|
-
|
|
267
|
+
function getWsShimScript() {
|
|
268
|
+
const tokenStr = wsToken ? JSON.stringify(wsToken) : "null";
|
|
269
|
+
return `<script>
|
|
249
270
|
(function() {
|
|
250
271
|
if (window.__nodepodWsShim) return;
|
|
251
272
|
window.__nodepodWsShim = true;
|
|
252
273
|
var NativeWS = window.WebSocket;
|
|
253
274
|
var bc = new BroadcastChannel("nodepod-ws");
|
|
275
|
+
var _wsToken = ${tokenStr};
|
|
254
276
|
var nextId = 0;
|
|
255
277
|
var active = {};
|
|
256
278
|
|
|
@@ -300,7 +322,8 @@ const WS_SHIM_SCRIPT = `<script>
|
|
|
300
322
|
uid: uid,
|
|
301
323
|
port: port,
|
|
302
324
|
path: path,
|
|
303
|
-
protocols: Array.isArray(protocols) ? protocols.join(",") : (protocols || "")
|
|
325
|
+
protocols: Array.isArray(protocols) ? protocols.join(",") : (protocols || ""),
|
|
326
|
+
token: _wsToken
|
|
304
327
|
});
|
|
305
328
|
|
|
306
329
|
// Timeout: if no ws-open within 5s, fire error
|
|
@@ -347,12 +370,12 @@ const WS_SHIM_SCRIPT = `<script>
|
|
|
347
370
|
type = "binary";
|
|
348
371
|
payload = Array.from(data);
|
|
349
372
|
}
|
|
350
|
-
bc.postMessage({ kind: "ws-send", uid: this._uid, data: payload, type: type });
|
|
373
|
+
bc.postMessage({ kind: "ws-send", uid: this._uid, data: payload, type: type, token: _wsToken });
|
|
351
374
|
};
|
|
352
375
|
NodepodWS.prototype.close = function(code, reason) {
|
|
353
376
|
if (this.readyState >= 2) return;
|
|
354
377
|
this.readyState = 2;
|
|
355
|
-
bc.postMessage({ kind: "ws-close", uid: this._uid, code: code || 1000, reason: reason || "" });
|
|
378
|
+
bc.postMessage({ kind: "ws-close", uid: this._uid, code: code || 1000, reason: reason || "", token: _wsToken });
|
|
356
379
|
var self = this;
|
|
357
380
|
setTimeout(function() {
|
|
358
381
|
self.readyState = 3;
|
|
@@ -375,6 +398,8 @@ const WS_SHIM_SCRIPT = `<script>
|
|
|
375
398
|
bc.onmessage = function(ev) {
|
|
376
399
|
var d = ev.data;
|
|
377
400
|
if (!d || !d.uid) return;
|
|
401
|
+
// check bridge token
|
|
402
|
+
if (_wsToken && d.token !== _wsToken) return;
|
|
378
403
|
var ws = active[d.uid];
|
|
379
404
|
if (!ws) return;
|
|
380
405
|
|
|
@@ -414,6 +439,7 @@ const WS_SHIM_SCRIPT = `<script>
|
|
|
414
439
|
window.WebSocket = NodepodWS;
|
|
415
440
|
})();
|
|
416
441
|
</script>`;
|
|
442
|
+
}
|
|
417
443
|
|
|
418
444
|
// Small "nodepod" badge in the bottom-right corner of preview iframes.
|
|
419
445
|
const WATERMARK_SCRIPT = `<script>
|
|
@@ -571,7 +597,7 @@ async function proxyToVirtualServer(request, serverPort, path, originalRequest)
|
|
|
571
597
|
let finalBody = responseBody;
|
|
572
598
|
const ct = respHeaders["content-type"] || respHeaders["Content-Type"] || "";
|
|
573
599
|
if (ct.includes("text/html") && responseBody) {
|
|
574
|
-
let injection =
|
|
600
|
+
let injection = getWsShimScript();
|
|
575
601
|
if (previewScript) {
|
|
576
602
|
injection += `<script>${previewScript}<` + `/script>`;
|
|
577
603
|
}
|