javascript-solid-server 0.0.110 → 0.0.112

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.
@@ -0,0 +1,94 @@
1
+ ## HTTP 402 Paid Access
2
+
3
+ Monetize API endpoints with per-request satoshi payments. Resources under `/pay/*` require NIP-98 authentication and a positive balance.
4
+
5
+ ```bash
6
+ jss start --pay --pay-cost 10 --pay-address your-address --pay-token PODS --pay-rate 10
7
+ ```
8
+
9
+ ### Routes
10
+
11
+ | Method | Path | Description |
12
+ |--------|------|-------------|
13
+ | GET | `/pay/.info` | Public: cost, token info, chains, pool |
14
+ | GET | `/pay/.balance` | Check your balance (NIP-98 auth) |
15
+ | POST | `/pay/.deposit` | Deposit sats via TXO URI or MRC20 state proof |
16
+ | POST | `/pay/.buy` | Buy tokens with sat balance (requires `--pay-token`) |
17
+ | POST | `/pay/.withdraw` | Withdraw balance as portable tokens (requires `--pay-token`) |
18
+ | GET | `/pay/.offers` | List open sell orders (secondary market) |
19
+ | POST | `/pay/.sell` | Create a sell order (requires `--pay-token`) |
20
+ | POST | `/pay/.swap` | Execute a swap against a sell order |
21
+ | GET | `/pay/.pool` | AMM pool state (requires `--pay-chains`) |
22
+ | POST | `/pay/.pool` | AMM swap, add/remove liquidity |
23
+ | GET | `/pay/*` | Paid resource access (deducts balance) |
24
+
25
+ ### How It Works
26
+
27
+ 1. Authenticate with NIP-98 (Nostr HTTP Auth)
28
+ 2. Check balance at `/pay/.balance`
29
+ 3. Deposit sats by POSTing a TXO URI to `/pay/.deposit`
30
+ 4. Access paid resources — each request deducts the configured cost
31
+ 5. Optionally buy tokens (`/pay/.buy`) or withdraw as portable tokens (`/pay/.withdraw`)
32
+ 6. Balance tracked in a [Web Ledger](https://webledgers.org/) at `/.well-known/webledgers/webledgers.json`
33
+
34
+ ### Example
35
+
36
+ ```bash
37
+ # Check balance
38
+ curl -H "Authorization: Nostr <base64-event>" http://localhost:3000/pay/.balance
39
+
40
+ # Deposit (post a confirmed transaction output)
41
+ curl -X POST -H "Authorization: Nostr <base64-event>" \
42
+ http://localhost:3000/pay/.deposit \
43
+ -d "txid:vout"
44
+
45
+ # Access paid resource
46
+ curl -H "Authorization: Nostr <base64-event>" http://localhost:3000/pay/my-resource
47
+
48
+ # Buy tokens with sat balance
49
+ curl -X POST -H "Authorization: Nostr <base64-event>" \
50
+ -H "Content-Type: application/json" \
51
+ http://localhost:3000/pay/.buy \
52
+ -d '{"amount": 100}'
53
+
54
+ # Withdraw entire balance as portable tokens
55
+ curl -X POST -H "Authorization: Nostr <base64-event>" \
56
+ -H "Content-Type: application/json" \
57
+ http://localhost:3000/pay/.withdraw \
58
+ -d '{"all": true}'
59
+ ```
60
+
61
+ Deposit verification uses the mempool API (default: testnet4). The `X-Balance` and `X-Cost` headers are returned on successful paid requests. Buy and withdraw return portable MRC20 proofs with Bitcoin anchor data for independent verification.
62
+
63
+ ### Secondary Market
64
+
65
+ Users can trade tokens peer-to-peer through the pod. Sell orders are created via `/pay/.sell` and filled via `/pay/.swap`. The pod acts as escrow — transferring tokens on the Bitcoin-anchored MRC20 trail and settling sats in the webledger.
66
+
67
+ ### Multi-Chain AMM
68
+
69
+ Enable multi-chain deposits and an automated market maker:
70
+
71
+ ```bash
72
+ jss start --pay --pay-chains "tbtc3,tbtc4"
73
+ ```
74
+
75
+ Deposits detect the chain from the TXO URI prefix (`txo:tbtc3:txid:vout`). Each chain's balance is tracked separately. The AMM uses a constant-product formula (x × y = k) with a 0.3% fee.
76
+
77
+ ```bash
78
+ # Add liquidity
79
+ curl -X POST -H "Authorization: Nostr <token>" \
80
+ -H "Content-Type: application/json" \
81
+ http://localhost:3000/pay/.pool \
82
+ -d '{"action": "add-liquidity", "tbtc3": 1000, "tbtc4": 5000}'
83
+
84
+ # Swap
85
+ curl -X POST -H "Authorization: Nostr <token>" \
86
+ -H "Content-Type: application/json" \
87
+ http://localhost:3000/pay/.pool \
88
+ -d '{"action": "swap", "sell": "tbtc3", "amount": 100}'
89
+
90
+ # Check pool state
91
+ curl http://localhost:3000/pay/.pool
92
+ ```
93
+
94
+ Supported chains: `btc`, `tbtc3`, `tbtc4`, `ltc`, `signet`.
package/docs/quotas.md ADDED
@@ -0,0 +1,36 @@
1
+ # Storage Quotas
2
+
3
+ Limit storage per pod to prevent abuse and manage resources.
4
+
5
+ ```bash
6
+ jss start --default-quota 50MB
7
+ ```
8
+
9
+ ## Managing Quotas
10
+
11
+ ```bash
12
+ # Set quota for a user (overrides default)
13
+ jss quota set alice 100MB
14
+
15
+ # Show quota info
16
+ jss quota show alice
17
+ # alice:
18
+ # Used: 12.5 MB
19
+ # Limit: 100 MB
20
+ # Free: 87.5 MB
21
+ # Usage: 12%
22
+
23
+ # Recalculate from actual disk usage
24
+ jss quota reconcile alice
25
+ ```
26
+
27
+ ## How It Works
28
+
29
+ - Quotas are tracked incrementally on PUT, POST, and DELETE operations
30
+ - When quota is exceeded, the server returns HTTP 507 Insufficient Storage
31
+ - Each pod stores its quota in `/{pod}/.quota.json`
32
+ - Use `reconcile` to fix quota drift from manual file changes
33
+
34
+ ## Size Formats
35
+
36
+ Supported formats: `50MB`, `1GB`, `500KB`, `1TB`
@@ -0,0 +1,86 @@
1
+ ## remoteStorage
2
+
3
+ JSS implements the [remoteStorage protocol](https://remotestorage.io/spec/draft-dejong-remotestorage-22). The storage routes are always available, but WebFinger discovery and OAuth require `--activitypub` (which provides the WebFinger and OAuth endpoints). Any remoteStorage-compatible app can store and sync data on your pod.
4
+
5
+ ```bash
6
+ jss start --activitypub --idp
7
+ ```
8
+
9
+ ### Discovery
10
+
11
+ remoteStorage clients discover the storage endpoint via WebFinger:
12
+
13
+ ```bash
14
+ curl "http://localhost:3000/.well-known/webfinger?resource=acct:me@localhost:3000"
15
+ ```
16
+
17
+ The response includes a `remotestorage` link relation pointing to `/storage/me/`.
18
+
19
+ ### Endpoints
20
+
21
+ | Method | Endpoint | Description |
22
+ |--------|----------|-------------|
23
+ | `GET` | `/storage/:user/*` | Read file or list folder (JSON-LD) |
24
+ | `HEAD` | `/storage/:user/*` | Get metadata (ETag, Content-Type, size) |
25
+ | `PUT` | `/storage/:user/*` | Write file (creates parent folders) |
26
+ | `DELETE` | `/storage/:user/*` | Delete file |
27
+
28
+ ### How It Works
29
+
30
+ - **Auth**: Bearer token via OAuth 2.0 (same flow as Mastodon clients)
31
+ - **Public folder**: `/storage/me/public/*` is readable without auth
32
+ - **Conditional requests**: If-Match, If-None-Match (uses shared ETag utilities)
33
+ - **Dotfile protection**: `.acl`, `.meta`, and other dotfiles are blocked
34
+ - **Read-only mode**: Respects `--read-only` flag
35
+ - **Streaming**: Large files are streamed, not buffered
36
+
37
+ ### Testing
38
+
39
+ ```bash
40
+ # Write a file (needs Bearer token from OAuth flow)
41
+ curl -X PUT http://localhost:3000/storage/me/documents/hello.txt \
42
+ -H "Authorization: Bearer YOUR_TOKEN" \
43
+ -H "Content-Type: text/plain" \
44
+ -d "Hello, remoteStorage!"
45
+
46
+ # Read it back
47
+ curl -H "Authorization: Bearer YOUR_TOKEN" \
48
+ http://localhost:3000/storage/me/documents/hello.txt
49
+
50
+ # List a folder
51
+ curl -H "Authorization: Bearer YOUR_TOKEN" \
52
+ http://localhost:3000/storage/me/documents/
53
+
54
+ # Read from public folder (no auth needed)
55
+ curl http://localhost:3000/storage/me/public/readme.txt
56
+ ```
57
+
58
+ ### Linking Nostr to WebID (did:nostr)
59
+
60
+ Bridge your Nostr identity to a Solid WebID for seamless authentication:
61
+
62
+ **Step 1:** Add your WebID to your Nostr profile (kind 0 event):
63
+ ```json
64
+ {
65
+ "name": "alice",
66
+ "alsoKnownAs": ["https://solid.social/alice/profile/card#me"]
67
+ }
68
+ ```
69
+
70
+ **Step 2:** Add the did:nostr link to your WebID profile:
71
+ ```json
72
+ {
73
+ "@id": "#me",
74
+ "owl:sameAs": "did:nostr:<your-64-char-hex-pubkey>"
75
+ }
76
+ ```
77
+
78
+ **How it works:**
79
+ 1. NIP-98 signature is verified (existing flow)
80
+ 2. DID document is fetched from `nostr.social/.well-known/did/nostr/<pubkey>.json`
81
+ 3. `alsoKnownAs` is checked for a WebID URL
82
+ 4. WebID profile is fetched and `owl:sameAs` verified
83
+ 5. If bidirectional link exists → authenticated as WebID
84
+
85
+ This enables Nostr users to access their Solid pods using existing NIP-07 browser extensions.
86
+
@@ -0,0 +1,96 @@
1
+ ## Security
2
+
3
+ ### Root ACL Required
4
+
5
+ JSS uses **restrictive mode** by default: if no ACL file exists for a resource, access is denied. This prevents unauthorized writes to unprotected containers.
6
+
7
+ **You must create a root `.acl` file** in your data directory. Example (JSON-LD format):
8
+
9
+ ```json
10
+ {
11
+ "@context": {
12
+ "acl": "http://www.w3.org/ns/auth/acl#",
13
+ "foaf": "http://xmlns.com/foaf/0.1/"
14
+ },
15
+ "@graph": [
16
+ {
17
+ "@id": "#owner",
18
+ "@type": "acl:Authorization",
19
+ "acl:agent": { "@id": "https://your-domain.com/profile/card#me" },
20
+ "acl:accessTo": { "@id": "https://your-domain.com/" },
21
+ "acl:default": { "@id": "https://your-domain.com/" },
22
+ "acl:mode": [
23
+ { "@id": "acl:Read" },
24
+ { "@id": "acl:Write" },
25
+ { "@id": "acl:Control" }
26
+ ]
27
+ },
28
+ {
29
+ "@id": "#public",
30
+ "@type": "acl:Authorization",
31
+ "acl:agentClass": { "@id": "foaf:Agent" },
32
+ "acl:accessTo": { "@id": "https://your-domain.com/" },
33
+ "acl:default": { "@id": "https://your-domain.com/" },
34
+ "acl:mode": [
35
+ { "@id": "acl:Read" }
36
+ ]
37
+ }
38
+ ]
39
+ }
40
+ ```
41
+
42
+ Save this as `data/.acl` (replacing `your-domain.com` with your actual domain).
43
+
44
+ See [Issue #32](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/32) for background.
45
+
46
+ ## Subdomain Mode (XSS Protection)
47
+
48
+ By default, JSS uses **path-based pods** (`/alice/`, `/bob/`). This is simple but has a security limitation: all pods share the same origin, making cross-site scripting (XSS) attacks possible between pods.
49
+
50
+ **Subdomain mode** provides **origin isolation** - each pod gets its own subdomain (`alice.example.com`, `bob.example.com`), preventing XSS attacks between pods.
51
+
52
+ ### Why Subdomain Mode?
53
+
54
+ | Mode | URL | Origin | XSS Risk |
55
+ |------|-----|--------|----------|
56
+ | Path-based | `example.com/alice/` | `example.com` | Shared origin - pods can XSS each other |
57
+ | Subdomain | `alice.example.com/` | `alice.example.com` | Isolated - browser's Same-Origin Policy protects |
58
+
59
+ ### Enabling Subdomain Mode
60
+
61
+ ```bash
62
+ jss start --subdomains --base-domain example.com
63
+ ```
64
+
65
+ Or via environment variables:
66
+
67
+ ```bash
68
+ export JSS_SUBDOMAINS=true
69
+ export JSS_BASE_DOMAIN=example.com
70
+ jss start
71
+ ```
72
+
73
+ ### DNS Configuration
74
+
75
+ You need a **wildcard DNS record** pointing to your server:
76
+
77
+ ```
78
+ *.example.com A <your-server-ip>
79
+ ```
80
+
81
+ ### Pod URLs in Subdomain Mode
82
+
83
+ | Path Mode | Subdomain Mode |
84
+ |-----------|----------------|
85
+ | `example.com/alice/` | `alice.example.com/` |
86
+ | `example.com/alice/public/file.txt` | `alice.example.com/public/file.txt` |
87
+ | `example.com/alice/#me` | `alice.example.com/#me` |
88
+
89
+ Pod creation still uses the main domain:
90
+
91
+ ```bash
92
+ curl -X POST https://example.com/.pods \
93
+ -H "Content-Type: application/json" \
94
+ -d '{"name": "alice"}'
95
+ ```
96
+
package/docs/webrtc.md ADDED
@@ -0,0 +1,66 @@
1
+ ## WebRTC Signaling
2
+
3
+ Peer-to-peer communication via WebRTC, using JSS as the signaling server. Once peers are connected, all media and data flows directly between them.
4
+
5
+ ```bash
6
+ jss start --webrtc
7
+ ```
8
+
9
+ ### How It Works
10
+
11
+ 1. Both peers connect to `wss://your.pod/.webrtc` (WebID auth required)
12
+ 2. Caller sends an SDP offer targeting the callee's WebID
13
+ 3. JSS relays the offer/answer and ICE candidates between peers
14
+ 4. Once a direct path is found, the peer-to-peer connection is established
15
+ 5. JSS steps out — video, audio, files, and data flow directly between peers
16
+
17
+ ### Protocol
18
+
19
+ Messages are JSON over WebSocket:
20
+
21
+ ```js
22
+ // Send an offer to another user
23
+ { "type": "offer", "to": "https://bob.example/profile/card#me", "sdp": "..." }
24
+
25
+ // Receive an offer from another user
26
+ { "type": "offer", "from": "https://alice.example/profile/card#me", "sdp": "..." }
27
+
28
+ // ICE candidate exchange
29
+ { "type": "candidate", "to": "https://bob.example/profile/card#me", "candidate": {...} }
30
+
31
+ // Hang up
32
+ { "type": "hangup", "to": "https://bob.example/profile/card#me" }
33
+ ```
34
+
35
+ On connect, peers receive a list of online users and get notified when others join or leave.
36
+
37
+ ## Tunnel Proxy (Decentralized ngrok)
38
+
39
+ Expose a local dev server to the internet through your JSS pod. A tunnel client connects via WebSocket, registers a name, and receives proxied HTTP requests.
40
+
41
+ ```bash
42
+ jss start --tunnel
43
+ ```
44
+
45
+ ### How It Works
46
+
47
+ 1. Tunnel client connects to `wss://your.pod/.tunnel` (WebID auth required)
48
+ 2. Client registers a name: `{ "type": "register", "name": "myapp" }`
49
+ 3. Public URL becomes available at `https://your.pod/tunnel/myapp/`
50
+ 4. HTTP requests to that URL are serialized and sent to the tunnel client over WebSocket
51
+ 5. Tunnel client forwards to localhost, returns the response
52
+
53
+ ### Tunnel Client Protocol
54
+
55
+ ```js
56
+ // 1. Register a tunnel
57
+ → { "type": "register", "name": "myapp" }
58
+ ← { "type": "registered", "name": "myapp", "url": "/tunnel/myapp/" }
59
+
60
+ // 2. Receive proxied HTTP requests
61
+ ← { "type": "request", "id": "uuid", "method": "GET", "path": "/api/hello", "headers": {...} }
62
+
63
+ // 3. Return the response
64
+ → { "type": "response", "id": "uuid", "status": 200, "headers": {...}, "body": "..." }
65
+ ```
66
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.110",
3
+ "version": "0.0.112",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -9,7 +9,7 @@ import { checkAccess, getRequiredMode } from '../wac/checker.js';
9
9
  import { AccessMode } from '../wac/parser.js';
10
10
  import * as storage from '../storage/filesystem.js';
11
11
  import { getEffectiveUrlPath } from '../utils/url.js';
12
- import { generateDatabrowserHtml, generateModuleDatabrowserHtml, generateSolidosUiHtml } from '../mashlib/index.js';
12
+ import { generateDatabrowserHtml, generateModuleDatabrowserHtml } from '../mashlib/index.js';
13
13
 
14
14
  /**
15
15
  * Build a resource URL for WAC checking, normalizing path-based pod access
@@ -148,12 +148,9 @@ export function handleUnauthorized(request, reply, isAuthenticated, wacAllow, au
148
148
  // If mashlib is enabled, serve mashlib instead of static error page
149
149
  // Mashlib has built-in login functionality via panes.runDataBrowser()
150
150
  if (request.mashlibEnabled) {
151
- // Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
152
- const html = request.solidosUiEnabled
153
- ? generateSolidosUiHtml()
154
- : request.mashlibModule
155
- ? generateModuleDatabrowserHtml(request.mashlibModule)
156
- : generateDatabrowserHtml(request.url, request.mashlibCdn ? request.mashlibVersion : null);
151
+ const html = request.mashlibModule
152
+ ? generateModuleDatabrowserHtml(request.mashlibModule)
153
+ : generateDatabrowserHtml(request.url, request.mashlibCdn ? request.mashlibVersion : null);
157
154
  return reply.code(statusCode).type('text/html').send(html);
158
155
  }
159
156
  return reply.code(statusCode).type('text/html').send(getErrorPage(statusCode, isAuthenticated, request));
package/src/config.js CHANGED
@@ -43,9 +43,6 @@ export const defaults = {
43
43
  mashlibVersion: '2.0.0',
44
44
  mashlibModule: false,
45
45
 
46
- // SolidOS UI (modern Nextcloud-style interface)
47
- solidosUi: false,
48
-
49
46
  // Git HTTP backend
50
47
  git: false,
51
48
 
@@ -137,7 +134,6 @@ const envMap = {
137
134
  JSS_MASHLIB_CDN: 'mashlibCdn',
138
135
  JSS_MASHLIB_VERSION: 'mashlibVersion',
139
136
  JSS_MASHLIB_MODULE: 'mashlibModule',
140
- JSS_SOLIDOS_UI: 'solidosUi',
141
137
  JSS_GIT: 'git',
142
138
  JSS_NOSTR: 'nostr',
143
139
  JSS_NOSTR_PATH: 'nostrPath',
@@ -331,8 +327,7 @@ export function printConfig(config) {
331
327
  console.log(` Notifications: ${config.notifications}`);
332
328
  console.log(` IdP: ${config.idp ? (config.idpIssuer || 'enabled') : 'disabled'}`);
333
329
  console.log(` Subdomains: ${config.subdomains ? (config.baseDomain || 'enabled') : 'disabled'}`);
334
- console.log(` Mashlib: ${config.mashlibModule ? `module (${config.mashlibModule})` : config.mashlibCdn ? `CDN v${config.mashlibVersion}` : config.mashlib ? 'local' : 'disabled'}`);
335
- console.log(` SolidOS UI: ${config.solidosUi ? 'enabled' : 'disabled'}`);
330
+ console.log(` Mashlib: ${config.mashlibModule ? `module (${config.mashlibModule})` : config.mashlibCdn ? `CDN v${config.mashlibVersion}` : 'disabled'}`);
336
331
  if (config.pay) {
337
332
  console.log(` Pay: ${config.payCost} sat/req`);
338
333
  if (config.payToken) console.log(` Token: ${config.payToken} @ ${config.payRate} sat/token`);
@@ -15,7 +15,7 @@ import {
15
15
  } from '../rdf/conneg.js';
16
16
  import { emitChange } from '../notifications/events.js';
17
17
  import { checkIfMatch, checkIfNoneMatchForGet, checkIfNoneMatchForWrite } from '../utils/conditional.js';
18
- import { generateDatabrowserHtml, generateModuleDatabrowserHtml, generateSolidosUiHtml, shouldServeMashlib } from '../mashlib/index.js';
18
+ import { generateDatabrowserHtml, generateModuleDatabrowserHtml, shouldServeMashlib } from '../mashlib/index.js';
19
19
 
20
20
  /**
21
21
  * Live reload script - injected into HTML when --live-reload is enabled
@@ -230,12 +230,9 @@ export async function handleGet(request, reply) {
230
230
 
231
231
  // Check if we should serve Mashlib data browser for containers
232
232
  if (shouldServeMashlib(request, request.mashlibEnabled, 'application/ld+json')) {
233
- // Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
234
- const html = request.solidosUiEnabled
235
- ? generateSolidosUiHtml()
236
- : request.mashlibModule
237
- ? generateModuleDatabrowserHtml(request.mashlibModule)
238
- : generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
233
+ const html = request.mashlibModule
234
+ ? generateModuleDatabrowserHtml(request.mashlibModule)
235
+ : generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
239
236
  const headers = getAllHeaders({
240
237
  isContainer: true,
241
238
  etag: stats.etag,
@@ -309,12 +306,9 @@ export async function handleGet(request, reply) {
309
306
  // Check if we should serve Mashlib data browser
310
307
  // Only for RDF resources when Accept: text/html is requested
311
308
  if (shouldServeMashlib(request, request.mashlibEnabled, storedContentType)) {
312
- // Use SolidOS UI if enabled, ES module if configured, otherwise classic mashlib
313
- const html = request.solidosUiEnabled
314
- ? generateSolidosUiHtml()
315
- : request.mashlibModule
316
- ? generateModuleDatabrowserHtml(request.mashlibModule)
317
- : generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
309
+ const html = request.mashlibModule
310
+ ? generateModuleDatabrowserHtml(request.mashlibModule)
311
+ : generateDatabrowserHtml(resourceUrl, request.mashlibCdn ? request.mashlibVersion : null);
318
312
  const headers = getAllHeaders({
319
313
  isContainer: false,
320
314
  etag: stats.etag,
package/src/server.js CHANGED
@@ -62,14 +62,11 @@ export function createServer(options = {}) {
62
62
  const subdomainsEnabled = options.subdomains ?? false;
63
63
  const baseDomain = options.baseDomain || null;
64
64
  // Mashlib data browser is OFF by default
65
- // mashlibCdn: if true, load from CDN; if false, serve locally
66
- // mashlibModule: URL to ES module entry point (alternative to classic mashlib)
65
+ // mashlibCdn: load from CDN; mashlibModule: URL to ES module entry point
67
66
  const mashlibModule = options.mashlibModule ?? false;
68
- const mashlibEnabled = options.mashlib || !!mashlibModule;
69
67
  const mashlibCdn = options.mashlibCdn ?? false;
68
+ const mashlibEnabled = mashlibCdn || !!mashlibModule;
70
69
  const mashlibVersion = options.mashlibVersion ?? '2.0.0';
71
- // SolidOS UI (modern Nextcloud-style interface) - requires mashlib
72
- const solidosUiEnabled = options.solidosUi ?? false;
73
70
  // Git HTTP backend is OFF by default - enables clone/push via git protocol
74
71
  const gitEnabled = options.git ?? false;
75
72
  // Nostr relay is OFF by default
@@ -179,7 +176,6 @@ export function createServer(options = {}) {
179
176
  fastify.decorateRequest('mashlibCdn', null);
180
177
  fastify.decorateRequest('mashlibVersion', null);
181
178
  fastify.decorateRequest('mashlibModule', null);
182
- fastify.decorateRequest('solidosUiEnabled', null);
183
179
  fastify.decorateRequest('defaultQuota', null);
184
180
  fastify.decorateRequest('config', null);
185
181
  fastify.decorateRequest('liveReloadEnabled', null);
@@ -193,7 +189,6 @@ export function createServer(options = {}) {
193
189
  request.mashlibCdn = mashlibCdn;
194
190
  request.mashlibVersion = mashlibVersion;
195
191
  request.mashlibModule = mashlibModule;
196
- request.solidosUiEnabled = solidosUiEnabled;
197
192
  request.defaultQuota = defaultQuota;
198
193
  request.config = { public: options.public, readOnly: options.readOnly };
199
194
  request.liveReloadEnabled = liveReloadEnabled;
@@ -410,7 +405,7 @@ export function createServer(options = {}) {
410
405
  // Authorization hook - check WAC permissions
411
406
  // Skip for pod creation endpoint (needs special handling)
412
407
  fastify.addHook('preHandler', async (request, reply) => {
413
- // Skip auth for pod creation, OPTIONS, IdP routes, mashlib, solidos-ui, well-known, notifications, nostr, git, and AP
408
+ // Skip auth for pod creation, OPTIONS, IdP routes, mashlib, well-known, notifications, nostr, git, and AP
414
409
  const mashlibPaths = ['/mashlib.min.js', '/mash.css', '/841.mashlib.min.js'];
415
410
  const apPaths = ['/inbox', '/profile/card/inbox', '/profile/card/outbox', '/profile/card/followers', '/profile/card/following',
416
411
  '/api/v1/apps', '/api/v1/instance', '/api/v1/accounts/verify_credentials',
@@ -424,7 +419,6 @@ export function createServer(options = {}) {
424
419
  request.method === 'OPTIONS' ||
425
420
  request.url.startsWith('/idp/') ||
426
421
  request.url.startsWith('/.well-known/') ||
427
- request.url.startsWith('/solidos-ui/') ||
428
422
  (nostrEnabled && request.url.startsWith(nostrPath)) ||
429
423
  (gitEnabled && isGitRequest(request.url)) ||
430
424
  (activitypubEnabled && apPaths.some(p => request.url === p || request.url.startsWith(p + '?'))) ||
@@ -464,72 +458,15 @@ export function createServer(options = {}) {
464
458
  }
465
459
  }, handleCreatePod);
466
460
 
467
- // Mashlib static files (served from root like NSS does)
468
- if (mashlibEnabled) {
469
- if (mashlibCdn) {
470
- // CDN mode: redirect chunk requests to CDN
471
- // Mashlib uses code splitting, so it loads chunks like 789.mashlib.min.js
472
- const cdnBase = `https://unpkg.com/mashlib@${mashlibVersion}/dist`;
473
- const chunkPattern = /^\/\d+\.mashlib\.min\.js(\.map)?$/;
474
-
475
- fastify.addHook('onRequest', async (request, reply) => {
476
- if (chunkPattern.test(request.url)) {
477
- const filename = request.url.split('/').pop();
478
- return reply.redirect(302, `${cdnBase}/${filename}`);
479
- }
480
- });
481
- } else {
482
- // Local mode: serve from local files
483
- const mashlibDir = join(__dirname, 'mashlib-local', 'dist');
484
- const mashlibFiles = {
485
- '/mashlib.min.js': { file: 'mashlib.min.js', type: 'application/javascript' },
486
- '/mashlib.min.js.map': { file: 'mashlib.min.js.map', type: 'application/json' },
487
- '/mash.css': { file: 'mash.css', type: 'text/css' },
488
- '/mash.css.map': { file: 'mash.css.map', type: 'application/json' },
489
- '/841.mashlib.min.js': { file: '841.mashlib.min.js', type: 'application/javascript' },
490
- '/841.mashlib.min.js.map': { file: '841.mashlib.min.js.map', type: 'application/json' }
491
- };
492
-
493
- for (const [path, config] of Object.entries(mashlibFiles)) {
494
- fastify.get(path, async (request, reply) => {
495
- try {
496
- const content = await readFile(join(mashlibDir, config.file));
497
- return reply.type(config.type).send(content);
498
- } catch {
499
- return reply.code(404).send({ error: 'Not Found' });
500
- }
501
- });
502
- }
503
- }
504
- }
461
+ // Mashlib CDN mode: redirect chunk requests to CDN
462
+ if (mashlibEnabled && mashlibCdn) {
463
+ const cdnBase = `https://unpkg.com/mashlib@${mashlibVersion}/dist`;
464
+ const chunkPattern = /^\/\d+\.mashlib\.min\.js(\.map)?$/;
505
465
 
506
- // SolidOS UI static files (modern Nextcloud-style interface)
507
- // Serves from /solidos-ui/* - requires mashlib to be enabled as well
508
- if (solidosUiEnabled && mashlibEnabled) {
509
- const solidosUiDir = join(__dirname, 'mashlib-local', 'dist', 'solidos-ui');
510
-
511
- // Serve all files under /solidos-ui/* path
512
- fastify.get('/solidos-ui/*', async (request, reply) => {
513
- try {
514
- // Get the path after /solidos-ui/
515
- const filePath = request.url.replace('/solidos-ui/', '').split('?')[0];
516
- const fullPath = join(solidosUiDir, filePath);
517
-
518
- // Determine content type based on extension
519
- const ext = filePath.split('.').pop()?.toLowerCase();
520
- const contentTypes = {
521
- 'js': 'application/javascript',
522
- 'css': 'text/css',
523
- 'map': 'application/json',
524
- 'html': 'text/html'
525
- };
526
- const contentType = contentTypes[ext] || 'application/octet-stream';
527
-
528
- const content = await readFile(fullPath);
529
- return reply.type(contentType).send(content);
530
- } catch (err) {
531
- request.log.error(err, 'Failed to serve solidos-ui file');
532
- return reply.code(404).send({ error: 'Not Found' });
466
+ fastify.addHook('onRequest', async (request, reply) => {
467
+ if (chunkPattern.test(request.url)) {
468
+ const filename = request.url.split('/').pop();
469
+ return reply.redirect(302, `${cdnBase}/${filename}`);
533
470
  }
534
471
  });
535
472
  }