@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.
Files changed (42) hide show
  1. package/README.md +252 -240
  2. package/dist/__sw__.js +31 -5
  3. package/dist/{child_process-bGGe8mTj.cjs → child_process-DldQfPd9.cjs} +7431 -7435
  4. package/dist/child_process-DldQfPd9.cjs.map +1 -0
  5. package/dist/{child_process-CgnmoilU.js → child_process-eiM1_nmq.js} +8231 -8234
  6. package/dist/child_process-eiM1_nmq.js.map +1 -0
  7. package/dist/cross-origin.d.ts +2 -0
  8. package/dist/{index-DZpqX03n.js → index-CK6KRbI1.js} +37316 -36899
  9. package/dist/index-CK6KRbI1.js.map +1 -0
  10. package/dist/{index-NinyWmnj.cjs → index-DfyUKyNH.cjs} +39254 -38824
  11. package/dist/index-DfyUKyNH.cjs.map +1 -0
  12. package/dist/index.cjs +67 -67
  13. package/dist/index.mjs +61 -61
  14. package/dist/isolation-helpers.d.ts +3 -0
  15. package/dist/packages/archive-extractor.d.ts +2 -0
  16. package/dist/packages/version-resolver.d.ts +1 -0
  17. package/dist/request-proxy.d.ts +3 -0
  18. package/dist/script-engine.d.ts +1 -1
  19. package/dist/sdk/nodepod.d.ts +58 -58
  20. package/dist/sdk/types.d.ts +3 -0
  21. package/dist/threading/offload-types.d.ts +1 -0
  22. package/package.json +97 -97
  23. package/src/cross-origin.ts +75 -26
  24. package/src/iframe-sandbox.ts +145 -141
  25. package/src/isolation-helpers.ts +154 -148
  26. package/src/packages/archive-extractor.ts +251 -248
  27. package/src/packages/installer.ts +1 -0
  28. package/src/packages/version-resolver.ts +2 -0
  29. package/src/polyfills/net.ts +353 -353
  30. package/src/polyfills/util.ts +559 -559
  31. package/src/polyfills/worker_threads.ts +326 -326
  32. package/src/request-proxy.ts +43 -9
  33. package/src/script-engine.ts +3733 -3722
  34. package/src/sdk/nodepod.ts +8 -0
  35. package/src/sdk/types.ts +3 -0
  36. package/src/shell/shell-builtins.ts +19 -19
  37. package/src/threading/offload-types.ts +113 -112
  38. package/src/threading/offload-worker.ts +15 -0
  39. package/dist/child_process-CgnmoilU.js.map +0 -1
  40. package/dist/child_process-bGGe8mTj.cjs.map +0 -1
  41. package/dist/index-DZpqX03n.js.map +0 -1
  42. package/dist/index-NinyWmnj.cjs.map +0 -1
package/README.md CHANGED
@@ -1,240 +1,252 @@
1
- # nodepod
2
-
3
- [![npm version](https://img.shields.io/npm/v/@scelar/nodepod.svg)](https://www.npmjs.com/package/@scelar/nodepod)
4
- [![license](https://img.shields.io/npm/l/@scelar/nodepod.svg)](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
- ### Instance Methods
124
-
125
- | Method | Description |
126
- |--------|-------------|
127
- | `spawn(cmd, args?)` | Run a command, returns `NodepodProcess` |
128
- | `createTerminal(opts)` | Create an interactive terminal |
129
- | `install(packages)` | Install npm packages |
130
- | `fs.readFile(path, enc?)` | Read a file |
131
- | `fs.writeFile(path, data)` | Write a file |
132
- | `fs.readdir(path)` | List directory contents |
133
- | `fs.stat(path)` | Get file stats |
134
- | `fs.mkdir(path, opts?)` | Create a directory |
135
- | `fs.rm(path, opts?)` | Remove files/directories |
136
- | `snapshot()` | Capture filesystem state |
137
- | `restore(snapshot)` | Restore filesystem from snapshot |
138
- | `setPreviewScript(script)` | Inject JS into preview iframes |
139
- | `clearPreviewScript()` | Remove injected preview script |
140
- | `port(num)` | Get preview URL for a virtual server port |
141
-
142
- ### `NodepodProcess`
143
-
144
- Returned by `spawn()`. An EventEmitter with:
145
-
146
- | Event | Payload | Description |
147
- |-------|---------|-------------|
148
- | `output` | `string` | stdout data |
149
- | `error` | `string` | stderr data |
150
- | `exit` | `number` | exit code |
151
-
152
- Property: `completion` — a `Promise<void>` that resolves when the process exits.
153
-
154
- ## Architecture
155
-
156
- ```
157
- nodepod
158
- ├── src/
159
- │ ├── script-engine.ts # JavaScript execution engine (require, ESM→CJS, REPL)
160
- │ ├── memory-volume.ts # In-memory virtual filesystem
161
- │ ├── syntax-transforms.ts # ESM-to-CJS conversion via acorn
162
- │ ├── module-transformer.ts # esbuild-wasm code transforms
163
- │ ├── polyfills/ # Node.js built-in module polyfills
164
- │ │ ├── fs.ts # Filesystem (read, write, watch, streams, glob)
165
- │ │ ├── http.ts # HTTP server/client
166
- │ │ ├── stream.ts # Readable, Writable, Transform, Duplex
167
- │ │ ├── events.ts # EventEmitter
168
- │ │ ├── path.ts # Path operations
169
- │ │ ├── crypto.ts # Hashing, randomBytes, randomUUID
170
- │ │ ├── child_process.ts # exec, spawn, fork, execSync
171
- ├── net.ts # TCP Socket, Server
172
- │ └── ... # 40+ more polyfills
173
- │ ├── shell/ # Bash-like shell interpreter
174
- ├── shell-parser.ts # Tokenizer + recursive-descent parser
175
- ├── shell-builtins.ts # 35+ built-in commands
176
- │ │ └── shell-interpreter.ts # AST executor, pipes, redirections
177
- │ ├── packages/ # npm package management
178
- │ │ ├── installer.ts # Package installer
179
- │ │ ├── registry-client.ts # npm registry client
180
- │ │ └── version-resolver.ts # Semver resolution
181
- │ ├── threading/ # Worker-based process model
182
- │ │ ├── process-manager.ts # Process lifecycle management
183
- │ │ └── process-handle.ts # Process I/O handle
184
- │ └── sdk/ # Public SDK layer
185
- ├── nodepod.ts # Nodepod.boot() entry point
186
- ├── nodepod-fs.ts # Async filesystem facade
187
- ├── nodepod-process.ts # Process handle
188
- └── nodepod-terminal.ts # xterm.js terminal integration
189
- └── static/
190
- └── __sw__.js # Service Worker for HTTP request interception
191
- ```
192
-
193
- ## Supported Node.js Modules
194
-
195
- **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
196
-
197
- **Shims/stubs:** dns, worker_threads, vm, v8, tls, dgram, cluster, http2, inspector, domain, diagnostics_channel, async_hooks
198
-
199
- ## CDN Usage
200
- Once published to npm, nodepod is automatically available on CDNs:
201
-
202
- ```html
203
- <!-- unpkg -->
204
- <script src="https://unpkg.com/@scelar/nodepod"></script>
205
-
206
- <!-- jsDelivr -->
207
- <script src="https://cdn.jsdelivr.net/npm/@scelar/nodepod"></script>
208
- ```
209
-
210
- ## Development
211
-
212
- ```bash
213
- git clone https://github.com/ScelarOrg/Nodepod.git
214
- cd Nodepod
215
- npm install
216
- npm run type-check # TypeScript validation
217
- npm run build:lib # Build ESM + CJS bundles
218
- npm test # Run tests
219
- ```
220
-
221
- ## Contributing
222
-
223
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup instructions and guidelines.
224
-
225
- ## Author
226
-
227
- Created by [@R1ck404](https://github.com/R1ck404). Part of the [Scelar](https://scelar.com) ecosystem.
228
-
229
- ## License
230
-
231
- [MIT + Commons Clause](./LICENSE)
232
-
233
- This project uses the MIT license with the [Commons Clause](https://commonsclause.com/) restriction. In plain terms:
234
-
235
- - **Use it** freely in your own projects, including commercial ones
236
- - **Modify it**, fork it, learn from it
237
- - **Ship products** built with nodepod — that's totally fine
238
- - **Don't resell nodepod itself** as a standalone product or hosted service
239
-
240
- Basically, build whatever you want with it — just don't take nodepod, rebrand it, and sell it as your own thing.
1
+ # nodepod
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@scelar/nodepod.svg)](https://www.npmjs.com/package/@scelar/nodepod)
4
+ [![license](https://img.shields.io/npm/l/@scelar/nodepod.svg)](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
- const WS_SHIM_SCRIPT = `<script>
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 = WS_SHIM_SCRIPT;
600
+ let injection = getWsShimScript();
575
601
  if (previewScript) {
576
602
  injection += `<script>${previewScript}<` + `/script>`;
577
603
  }