javascript-solid-server 0.0.83 → 0.0.84

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.
@@ -320,7 +320,10 @@
320
320
  "WebFetch(domain:remotestorage.io)",
321
321
  "Bash(gh issue create --title \"Feature: --public flag to skip WAC and allow open access\" --body \"$\\(cat << ''ENDOFFILE''\n## Summary\n\nAdd a `--public` flag that disables WAC \\(Web Access Control\\) checks, allowing unauthenticated read/write access to all resources. This enables JSS to be used as a simple file server similar to `npx serve`, but with REST write capabilities.\n\n**Difficulty**: 15/100 \n**Estimated Effort**: 2-4 hours \n**Dependencies**: None\n\n---\n\n## Motivation\n\nCurrently, JSS requires either:\n1. ACL files to grant access, or\n2. Authentication via Solid-OIDC\n\nThis makes it impossible to use JSS as a simple \"just serve this folder\" tool like `npx serve`. The `--public` flag would enable:\n\n1. **Quick local development** - No auth setup needed\n2. **Simple file sharing** - LAN file server with write support\n3. **jsserve wrapper** - Foundation for `npx jsserve` \\(see future issue\\)\n4. **WebDAV alternative** - Simple REST-based file server\n5. **Testing/demos** - Quick Solid server without auth complexity\n\n---\n\n## Proposed Implementation\n\n### CLI Flag\n\n```bash\njss start --public # Open read/write, no auth\njss start --public --read-only # Open read, no writes \\(like npx serve\\)\n```\n\n### Environment Variable\n\n```bash\nJSS_PUBLIC=true jss start\n```\n\n### Config File\n\n```json\n{\n \"public\": true\n}\n```\n\n---\n\n## Implementation Details\n\n### 1. Add flag to CLI \\(`bin/jss.js`\\)\n\n```javascript\n.option\\(''--public'', ''Allow unauthenticated access to all resources \\(disables WAC\\)''\\)\n.option\\(''--read-only'', ''Disable PUT/DELETE methods \\(read-only mode\\)''\\)\n```\n\n### 2. Add to config \\(`src/config.js`\\)\n\n```javascript\nconst DEFAULTS = {\n // ... existing ...\n public: false,\n readOnly: false,\n};\n\n// Environment variable mapping\nif \\(process.env.JSS_PUBLIC\\) {\n config.public = process.env.JSS_PUBLIC === ''true'';\n}\nif \\(process.env.JSS_READ_ONLY\\) {\n config.readOnly = process.env.JSS_READ_ONLY === ''true'';\n}\n```\n\n### 3. Skip WAC when public \\(`src/auth/middleware.js`\\)\n\n```javascript\nexport async function authorize\\(request, reply\\) {\n // Public mode - skip all auth/WAC checks\n if \\(request.config?.public\\) {\n return; // Allow request to proceed\n }\n \n // ... existing WAC logic ...\n}\n```\n\n### 4. Block writes when read-only \\(`src/handlers/resource.js`, `src/handlers/container.js`\\)\n\n```javascript\n// At start of PUT/DELETE handlers\nif \\(request.config?.readOnly\\) {\n return reply.code\\(405\\).send\\({ \n error: ''Method Not Allowed'',\n message: ''Server is in read-only mode''\n }\\);\n}\n```\n\n---\n\n## Behavior Matrix\n\n| Flag Combination | GET | PUT/DELETE | Auth Required |\n|------------------|-----|------------|---------------|\n| \\(default\\) | ACL | ACL | Yes \\(if ACL requires\\) |\n| `--public` | ✅ Allow | ✅ Allow | No |\n| `--public --read-only` | ✅ Allow | ❌ Block | No |\n| `--read-only` \\(no public\\) | ACL | ❌ Block | Yes |\n\n---\n\n## Security Considerations\n\n### Warning on Startup\n\nWhen `--public` is enabled, show a clear warning:\n\n```\n⚠️ WARNING: Server running in PUBLIC mode\n All files are readable and writable without authentication.\n Do not use in production or expose to the internet.\n```\n\n### Binding to localhost by default?\n\nConsider: When `--public` is set, should the default host be `localhost` instead of `0.0.0.0`?\n\n```javascript\nif \\(config.public && !explicitHostSet\\) {\n config.host = ''localhost''; // Safer default for public mode\n}\n```\n\nUser can override with `--public --host 0.0.0.0` if they explicitly want network access.\n\n---\n\n## Examples\n\n### Local Development\n```bash\n# Quick Solid-compatible file server for development\njss start --public --port 3000 --root ./test-data\n```\n\n### Read-Only File Sharing\n```bash\n# Share files on LAN, no writes allowed\njss start --public --read-only --host 0.0.0.0 --root ~/shared\n```\n\n### Testing Solid Apps\n```bash\n# Test app without auth complexity\njss start --public --root ./fixtures\nnpm test\n```\n\n---\n\n## Files to Modify\n\n| File | Changes |\n|------|---------|\n| `bin/jss.js` | Add `--public` and `--read-only` options \\(~5 LOC\\) |\n| `src/config.js` | Add defaults and env var mapping \\(~10 LOC\\) |\n| `src/auth/middleware.js` | Skip WAC when public \\(~5 LOC\\) |\n| `src/handlers/resource.js` | Block writes when read-only \\(~5 LOC\\) |\n| `src/handlers/container.js` | Block writes when read-only \\(~5 LOC\\) |\n| **Total** | **~30 LOC** |\n\n---\n\n## Testing\n\n```javascript\ndescribe\\(''--public flag'', \\(\\) => {\n it\\(''should allow unauthenticated GET'', async \\(\\) => {\n const server = await createServer\\({ public: true, root: tmpDir }\\);\n const res = await request\\(server\\).get\\(''/file.txt''\\);\n expect\\(res.status\\).toBe\\(200\\);\n }\\);\n\n it\\(''should allow unauthenticated PUT'', async \\(\\) => {\n const server = await createServer\\({ public: true, root: tmpDir }\\);\n const res = await request\\(server\\)\n .put\\(''/new-file.txt''\\)\n .send\\(''content''\\);\n expect\\(res.status\\).toBe\\(201\\);\n }\\);\n\n it\\(''should block PUT when read-only'', async \\(\\) => {\n const server = await createServer\\({ public: true, readOnly: true, root: tmpDir }\\);\n const res = await request\\(server\\)\n .put\\(''/new-file.txt''\\)\n .send\\(''content''\\);\n expect\\(res.status\\).toBe\\(405\\);\n }\\);\n}\\);\n```\n\n---\n\n## Related Issues\n\n- Future: `jsserve` package \\(thin wrapper using this flag\\)\n- #100 - Production Readiness \\(this is a dev/convenience feature\\)\n\n---\n\n## Open Questions\n\n1. Should `--public` default to `localhost` binding for safety?\n2. Should there be a `--public-read` \\(read-only public\\) shorthand?\n3. Should `--public` disable IdP/login UI entirely, or just make it optional?\nENDOFFILE\n\\)\")",
322
322
  "Bash(npx serve --help:*)",
323
- "Bash(npm exec serve:*)"
323
+ "Bash(npm exec serve:*)",
324
+ "Bash(npm link)",
325
+ "Bash(npm link:*)",
326
+ "Bash(git push)"
324
327
  ]
325
328
  }
326
329
  }
package/bin/jss.js CHANGED
@@ -78,6 +78,7 @@ program
78
78
  .option('--no-webid-tls', 'Disable WebID-TLS authentication')
79
79
  .option('--public', 'Allow unauthenticated access (skip WAC, open read/write)')
80
80
  .option('--read-only', 'Disable PUT/DELETE/PATCH methods (read-only mode)')
81
+ .option('--live-reload', 'Inject live reload script into HTML (auto-refresh on changes)')
81
82
  .option('-q, --quiet', 'Suppress log output')
82
83
  .option('--print-config', 'Print configuration and exit')
83
84
  .action(async (options) => {
@@ -135,6 +136,7 @@ program
135
136
  singleUserName: config.singleUserName,
136
137
  public: config.public,
137
138
  readOnly: config.readOnly,
139
+ liveReload: config.liveReload,
138
140
  });
139
141
 
140
142
  await server.listen({ port: config.port, host: config.host });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.83",
3
+ "version": "0.0.84",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/config.js CHANGED
@@ -79,6 +79,9 @@ export const defaults = {
79
79
  // Read-only mode - disable PUT/DELETE/PATCH
80
80
  readOnly: false,
81
81
 
82
+ // Live reload - inject script to auto-refresh browser on file changes
83
+ liveReload: false,
84
+
82
85
  // Logging
83
86
  logger: true,
84
87
  quiet: false,
@@ -125,6 +128,7 @@ const envMap = {
125
128
  JSS_DEFAULT_QUOTA: 'defaultQuota',
126
129
  JSS_PUBLIC: 'public',
127
130
  JSS_READ_ONLY: 'readOnly',
131
+ JSS_LIVE_RELOAD: 'liveReload',
128
132
  };
129
133
 
130
134
  /**
@@ -17,6 +17,23 @@ import { emitChange } from '../notifications/events.js';
17
17
  import { checkIfMatch, checkIfNoneMatchForGet, checkIfNoneMatchForWrite } from '../utils/conditional.js';
18
18
  import { generateDatabrowserHtml, generateSolidosUiHtml, shouldServeMashlib } from '../mashlib/index.js';
19
19
 
20
+ /**
21
+ * Live reload script - injected into HTML when --live-reload is enabled
22
+ */
23
+ const LIVE_RELOAD_SCRIPT = `<script>(function(){var ws=new WebSocket((location.protocol==='https:'?'wss:':'ws:')+'//' +location.host+'/.notifications');ws.onopen=function(){ws.send('sub '+location.href)};ws.onmessage=function(e){if(e.data.startsWith('pub '))location.reload()};ws.onclose=function(){setTimeout(function(){location.reload()},1000)}})();</script>`;
24
+
25
+ /**
26
+ * Inject live reload script into HTML content
27
+ */
28
+ function injectLiveReload(content) {
29
+ const html = content.toString();
30
+ // Inject before </body> or at end
31
+ if (html.includes('</body>')) {
32
+ return Buffer.from(html.replace('</body>', LIVE_RELOAD_SCRIPT + '</body>'));
33
+ }
34
+ return Buffer.from(html + LIVE_RELOAD_SCRIPT);
35
+ }
36
+
20
37
  /**
21
38
  * Get the storage path and resource URL for a request
22
39
  * In subdomain mode, storage path includes pod name, URL uses subdomain
@@ -198,6 +215,12 @@ export async function handleGet(request, reply) {
198
215
  });
199
216
 
200
217
  Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
218
+ // Inject live reload script for index.html
219
+ if (request.liveReloadEnabled) {
220
+ reply.header('Cache-Control', 'no-store');
221
+ reply.removeHeader('ETag');
222
+ return reply.send(injectLiveReload(content));
223
+ }
201
224
  return reply.send(content);
202
225
  }
203
226
 
@@ -439,6 +462,13 @@ export async function handleGet(request, reply) {
439
462
  headers['Vary'] = getVaryHeader(connegEnabled, request.mashlibEnabled);
440
463
 
441
464
  Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
465
+
466
+ // Inject live reload script into HTML (disable caching since content is modified)
467
+ if (actualContentType === 'text/html' && request.liveReloadEnabled) {
468
+ reply.header('Cache-Control', 'no-store');
469
+ reply.removeHeader('ETag');
470
+ return reply.send(injectLiveReload(content));
471
+ }
442
472
  return reply.send(content);
443
473
  }
444
474
 
package/src/server.js CHANGED
@@ -79,6 +79,8 @@ export function createServer(options = {}) {
79
79
  const defaultQuota = options.defaultQuota ?? 50 * 1024 * 1024;
80
80
  // WebID-TLS client certificate authentication is OFF by default
81
81
  const webidTlsEnabled = options.webidTls ?? false;
82
+ // Live reload - injects script to auto-refresh browser on file changes
83
+ const liveReloadEnabled = options.liveReload ?? false;
82
84
 
83
85
  // Set data root via environment variable if provided
84
86
  if (options.root) {
@@ -136,9 +138,10 @@ export function createServer(options = {}) {
136
138
  fastify.decorateRequest('solidosUiEnabled', null);
137
139
  fastify.decorateRequest('defaultQuota', null);
138
140
  fastify.decorateRequest('config', null);
141
+ fastify.decorateRequest('liveReloadEnabled', null);
139
142
  fastify.addHook('onRequest', async (request) => {
140
143
  request.connegEnabled = connegEnabled;
141
- request.notificationsEnabled = notificationsEnabled;
144
+ request.notificationsEnabled = notificationsEnabled || liveReloadEnabled;
142
145
  request.idpEnabled = idpEnabled;
143
146
  request.subdomainsEnabled = subdomainsEnabled;
144
147
  request.baseDomain = baseDomain;
@@ -148,6 +151,7 @@ export function createServer(options = {}) {
148
151
  request.solidosUiEnabled = solidosUiEnabled;
149
152
  request.defaultQuota = defaultQuota;
150
153
  request.config = { public: options.public, readOnly: options.readOnly };
154
+ request.liveReloadEnabled = liveReloadEnabled;
151
155
 
152
156
  // Extract pod name from subdomain if enabled
153
157
  if (subdomainsEnabled && baseDomain) {
@@ -164,8 +168,8 @@ export function createServer(options = {}) {
164
168
  }
165
169
  });
166
170
 
167
- // Register WebSocket notifications plugin if enabled
168
- if (notificationsEnabled) {
171
+ // Register WebSocket notifications plugin if enabled (or live reload needs it)
172
+ if (notificationsEnabled || liveReloadEnabled) {
169
173
  fastify.register(notificationsPlugin);
170
174
  }
171
175