almostnode 0.1.0 → 0.2.0

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 CHANGED
@@ -139,6 +139,156 @@ bridge.registerServer(server, 3000);
139
139
 
140
140
  ---
141
141
 
142
+ ## Service Worker Setup
143
+
144
+ almostnode uses a Service Worker to intercept HTTP requests and route them to virtual dev servers (e.g., `ViteDevServer`, `NextDevServer`).
145
+
146
+ > **Note:** The service worker is only needed if you're using dev servers with URL access (e.g., `/__virtual__/3000/`). If you're only executing code with `runtime.execute()`, you don't need the service worker.
147
+
148
+ ### Which Setup Do I Need?
149
+
150
+ | Use Case | Setup Required |
151
+ |----------|----------------|
152
+ | Cross-origin sandbox (recommended for untrusted code) | `generateSandboxFiles()` - includes everything |
153
+ | Same-origin with Vite | `almostnodePlugin` from `almostnode/vite` |
154
+ | Same-origin with Next.js | `getServiceWorkerContent` from `almostnode/next` |
155
+ | Same-origin with other frameworks | Manual copy to public directory |
156
+
157
+ ---
158
+
159
+ ### Option 1: Cross-Origin Sandbox (Recommended)
160
+
161
+ When using `createRuntime()` with a cross-origin `sandbox` URL, the service worker must be deployed **with the sandbox**, not your main app.
162
+
163
+ The `generateSandboxFiles()` helper generates all required files:
164
+
165
+ ```typescript
166
+ import { generateSandboxFiles } from 'almostnode';
167
+ import fs from 'fs';
168
+
169
+ const files = generateSandboxFiles();
170
+
171
+ // Creates: index.html, vercel.json, __sw__.js
172
+ fs.mkdirSync('sandbox', { recursive: true });
173
+ for (const [filename, content] of Object.entries(files)) {
174
+ fs.writeFileSync(`sandbox/${filename}`, content);
175
+ }
176
+
177
+ // Deploy to a different origin:
178
+ // cd sandbox && vercel --prod
179
+ ```
180
+
181
+ **Generated files:**
182
+ | File | Purpose |
183
+ |------|---------|
184
+ | `index.html` | Sandbox page that loads almostnode and registers the service worker |
185
+ | `vercel.json` | CORS headers for cross-origin iframe embedding |
186
+ | `__sw__.js` | Service worker for intercepting dev server requests |
187
+
188
+ See [Sandbox Setup](#sandbox-setup) for full deployment instructions.
189
+
190
+ ---
191
+
192
+ ### Option 2: Same-Origin with Vite
193
+
194
+ For trusted code using `dangerouslyAllowSameOrigin: true`:
195
+
196
+ ```typescript
197
+ // vite.config.ts
198
+ import { defineConfig } from 'vite';
199
+ import { almostnodePlugin } from 'almostnode/vite';
200
+
201
+ export default defineConfig({
202
+ plugins: [almostnodePlugin()]
203
+ });
204
+ ```
205
+
206
+ The plugin serves `/__sw__.js` automatically during development.
207
+
208
+ **Custom path:**
209
+
210
+ ```typescript
211
+ // vite.config.ts
212
+ almostnodePlugin({ swPath: '/custom/__sw__.js' })
213
+
214
+ // Then in your app:
215
+ await bridge.initServiceWorker({ swUrl: '/custom/__sw__.js' });
216
+ ```
217
+
218
+ ---
219
+
220
+ ### Option 3: Same-Origin with Next.js
221
+
222
+ For trusted code using `dangerouslyAllowSameOrigin: true`:
223
+
224
+ **App Router:**
225
+
226
+ ```typescript
227
+ // app/__sw__.js/route.ts
228
+ import { getServiceWorkerContent } from 'almostnode/next';
229
+
230
+ export async function GET() {
231
+ return new Response(getServiceWorkerContent(), {
232
+ headers: {
233
+ 'Content-Type': 'application/javascript',
234
+ 'Cache-Control': 'no-cache',
235
+ },
236
+ });
237
+ }
238
+ ```
239
+
240
+ **Pages Router:**
241
+
242
+ ```typescript
243
+ // pages/api/__sw__.ts
244
+ import { getServiceWorkerContent } from 'almostnode/next';
245
+ import type { NextApiRequest, NextApiResponse } from 'next';
246
+
247
+ export default function handler(req: NextApiRequest, res: NextApiResponse) {
248
+ res.setHeader('Content-Type', 'application/javascript');
249
+ res.setHeader('Cache-Control', 'no-cache');
250
+ res.send(getServiceWorkerContent());
251
+ }
252
+ ```
253
+
254
+ **Initialize with the correct path:**
255
+
256
+ ```typescript
257
+ // App Router (file-based route)
258
+ await bridge.initServiceWorker({ swUrl: '/__sw__.js' });
259
+
260
+ // Pages Router (API route)
261
+ await bridge.initServiceWorker({ swUrl: '/api/__sw__' });
262
+ ```
263
+
264
+ **Available exports from `almostnode/next`:**
265
+
266
+ | Export | Description |
267
+ |--------|-------------|
268
+ | `getServiceWorkerContent()` | Returns the service worker file content as a string |
269
+ | `getServiceWorkerPath()` | Returns the absolute path to the service worker file |
270
+
271
+ ---
272
+
273
+ ### Option 4: Manual Setup (Other Frameworks)
274
+
275
+ Copy the service worker to your public directory:
276
+
277
+ ```bash
278
+ cp node_modules/almostnode/dist/__sw__.js ./public/
279
+ ```
280
+
281
+ Or programmatically:
282
+
283
+ ```typescript
284
+ import { getServiceWorkerPath } from 'almostnode/next';
285
+ import fs from 'fs';
286
+
287
+ fs.copyFileSync(getServiceWorkerPath(), './public/__sw__.js');
288
+ ```
289
+
290
+ ---
291
+
142
292
  ## Comparison with WebContainers
143
293
 
144
294
  | Feature | almostnode | WebContainers |
@@ -312,12 +462,24 @@ For running untrusted code securely, deploy a cross-origin sandbox. The key requ
312
462
 
313
463
  ```typescript
314
464
  import { generateSandboxFiles } from 'almostnode';
465
+ import fs from 'fs';
315
466
 
316
467
  const files = generateSandboxFiles();
317
- // Write files['index.html'] and files['vercel.json'] to a directory
468
+ // Generates: index.html, vercel.json, __sw__.js
469
+
470
+ fs.mkdirSync('sandbox', { recursive: true });
471
+ for (const [filename, content] of Object.entries(files)) {
472
+ fs.writeFileSync(`sandbox/${filename}`, content);
473
+ }
474
+
318
475
  // Deploy: cd sandbox && vercel --prod
319
476
  ```
320
477
 
478
+ The generated files include:
479
+ - `index.html` - Sandbox page with service worker registration
480
+ - `vercel.json` - CORS headers for cross-origin iframe embedding
481
+ - `__sw__.js` - Service worker for dev server URL access
482
+
321
483
  ### Manual Setup (Any Platform)
322
484
 
323
485
  The sandbox requires two things:
package/dist/index.cjs CHANGED
@@ -6,8 +6,31 @@ const pako = require('pako');
6
6
  const justBash = require('just-bash');
7
7
  const resolve_exports = require('resolve.exports');
8
8
  const comlink = require('comlink');
9
+ const fs$1 = require('fs');
10
+ const path$1 = require('path');
11
+ const url$2 = require('url');
9
12
 
10
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
+ function _interopNamespaceDefault(e) {
15
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
16
+ if (e) {
17
+ for (const k in e) {
18
+ if (k !== 'default') {
19
+ const d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: () => e[k]
23
+ });
24
+ }
25
+ }
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ const fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
32
+ const path__namespace = /*#__PURE__*/_interopNamespaceDefault(path$1);
33
+
11
34
  function createNodeError(code, syscall, path, message) {
12
35
  const errno = {
13
36
  ENOENT: -2,
@@ -9909,7 +9932,34 @@ async function createRuntime(vfs, options = {}) {
9909
9932
  return new AsyncRuntimeWrapper(vfs, runtimeOptions);
9910
9933
  }
9911
9934
 
9912
- function getSandboxHtml(justNodeUrl = "https://unpkg.com/almostnode/dist/index.js") {
9935
+ const __dirname$1 = path__namespace.dirname(url$2.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
9936
+ function getServiceWorkerContent() {
9937
+ try {
9938
+ let swPath = path__namespace.join(__dirname$1, "__sw__.js");
9939
+ if (fs__namespace.existsSync(swPath)) {
9940
+ return fs__namespace.readFileSync(swPath, "utf-8");
9941
+ }
9942
+ swPath = path__namespace.join(__dirname$1, "../dist/__sw__.js");
9943
+ if (fs__namespace.existsSync(swPath)) {
9944
+ return fs__namespace.readFileSync(swPath, "utf-8");
9945
+ }
9946
+ return null;
9947
+ } catch {
9948
+ return null;
9949
+ }
9950
+ }
9951
+ function getSandboxHtml(options = {}) {
9952
+ const opts = typeof options === "string" ? { almostnodeUrl: options } : options;
9953
+ const almostnodeUrl = opts.almostnodeUrl ?? "https://unpkg.com/almostnode/dist/index.js";
9954
+ const includeServiceWorker = opts.includeServiceWorker ?? true;
9955
+ const serviceWorkerScript = includeServiceWorker ? `
9956
+ // Register service worker for dev server support
9957
+ if ('serviceWorker' in navigator) {
9958
+ navigator.serviceWorker.register('/__sw__.js', { scope: '/' })
9959
+ .then(reg => console.log('[Sandbox] Service worker registered'))
9960
+ .catch(err => console.warn('[Sandbox] Service worker registration failed:', err));
9961
+ }
9962
+ ` : "";
9913
9963
  return `<!DOCTYPE html>
9914
9964
  <html>
9915
9965
  <head>
@@ -9918,7 +9968,8 @@ function getSandboxHtml(justNodeUrl = "https://unpkg.com/almostnode/dist/index.j
9918
9968
  </head>
9919
9969
  <body>
9920
9970
  <script type="module">
9921
- import { VirtualFS, Runtime } from '${justNodeUrl}';
9971
+ import { VirtualFS, Runtime } from '${almostnodeUrl}';
9972
+ ${serviceWorkerScript}
9922
9973
 
9923
9974
  let vfs = null;
9924
9975
  let runtime = null;
@@ -10014,11 +10065,18 @@ function getSandboxVercelConfig() {
10014
10065
  ]
10015
10066
  };
10016
10067
  }
10017
- function generateSandboxFiles(justNodeUrl) {
10018
- return {
10019
- "index.html": getSandboxHtml(justNodeUrl),
10068
+ function generateSandboxFiles(options = {}) {
10069
+ const opts = typeof options === "string" ? { almostnodeUrl: options } : options;
10070
+ const includeServiceWorker = opts.includeServiceWorker ?? true;
10071
+ const swContent = includeServiceWorker ? getServiceWorkerContent() : null;
10072
+ const files = {
10073
+ "index.html": getSandboxHtml(opts),
10020
10074
  "vercel.json": JSON.stringify(getSandboxVercelConfig(), null, 2)
10021
10075
  };
10076
+ if (swContent) {
10077
+ files["__sw__.js"] = swContent;
10078
+ }
10079
+ return files;
10022
10080
  }
10023
10081
  const SANDBOX_SETUP_INSTRUCTIONS = `
10024
10082
  # Setting up a almostnode Sandbox on Vercel
@@ -11006,12 +11064,15 @@ class ServerBridge extends EventEmitter {
11006
11064
  }
11007
11065
  /**
11008
11066
  * Initialize Service Worker communication
11067
+ * @param options - Configuration options for the service worker
11068
+ * @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
11009
11069
  */
11010
- async initServiceWorker() {
11070
+ async initServiceWorker(options) {
11011
11071
  if (!("serviceWorker" in navigator)) {
11012
11072
  throw new Error("Service Workers not supported");
11013
11073
  }
11014
- const registration = await navigator.serviceWorker.register("/__sw__.js", {
11074
+ const swUrl = options?.swUrl ?? "/__sw__.js";
11075
+ const registration = await navigator.serviceWorker.register(swUrl, {
11015
11076
  scope: "/"
11016
11077
  });
11017
11078
  const sw = registration.active || registration.waiting || registration.installing;
@@ -13421,8 +13482,8 @@ class NextDevServer extends DevServer {
13421
13482
  React.useEffect(() => {
13422
13483
  const handlePopState = () => {
13423
13484
  setCurrentPath(window.location.pathname);
13424
- // Re-render the page component
13425
- window.location.reload();
13485
+ // Defer reload outside React's update cycle
13486
+ setTimeout(() => window.location.reload(), 0);
13426
13487
  };
13427
13488
 
13428
13489
  window.addEventListener('popstate', handlePopState);