javascript-solid-server 0.0.35 → 0.0.37

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.
@@ -97,7 +97,26 @@
97
97
  "Bash(done)",
98
98
  "Bash(for domain in jss.dev jss.sh jss.io jss.app solidserver.dev)",
99
99
  "Bash(host:*)",
100
- "WebFetch(domain:nostr-components.github.io)"
100
+ "WebFetch(domain:nostr-components.github.io)",
101
+ "Bash(ssh melvincarvalho.com \"pm2 list && echo ''---HAPROXY---'' && cat /etc/haproxy/haproxy.cfg 2>/dev/null | grep -A5 ''melvin\\|backend\\|frontend\\|acl host''\")",
102
+ "Bash(ssh:*)",
103
+ "Bash(time curl -s --connect-timeout 10 https://melvin.solid.live/credit/count.ttl)",
104
+ "Bash(time curl -s --connect-timeout 10 https://melvin.solid.live/)",
105
+ "Bash(time curl:*)",
106
+ "Bash(time curl -s 'https://melvin.solid.live/credit/count.ttl')",
107
+ "Bash(grep:*)",
108
+ "Bash(scp:*)",
109
+ "Bash(for i in 1 2 3)",
110
+ "Bash(do echo \"Attempt $i:\")",
111
+ "Bash(for i in 1 2 3 4 5)",
112
+ "Bash(do curl -so /dev/null -w \"%{http_code} \" https://melvincarvalho.com/js/handlemutation.js)",
113
+ "Bash(for i in 1 2 3 4 5 6 7 8 9 10)",
114
+ "Bash(if [ ! -d \"jose\" ])",
115
+ "Bash(then git clone --depth 1 --branch v0.7.0 https://github.com/solid/jose.git)",
116
+ "Bash(fi)",
117
+ "Bash(timeout 45 node:*)",
118
+ "Bash(gh issue list:*)",
119
+ "Bash(DATA_ROOT=/tmp/jss-git-test JSS_PORT=4444 timeout 3 node:*)"
101
120
  ]
102
121
  }
103
122
  }
package/AGENTS.md ADDED
@@ -0,0 +1,152 @@
1
+ # AGENTS.md - AI Assistant Context for JSS
2
+
3
+ This document provides context for AI assistants working on JavaScript Solid Server (JSS).
4
+
5
+ ## What is JSS?
6
+
7
+ A lightweight Solid server implementation focused on simplicity and modern JavaScript. Alternative to Node Solid Server (NSS) and Community Solid Server (CSS).
8
+
9
+ **Key differences from other Solid servers:**
10
+ - Single-file JSON-LD storage (no quad stores)
11
+ - Content negotiation converts JSON-LD ↔ Turtle on the fly
12
+ - Built on Fastify (not Express)
13
+ - Uses oidc-provider for identity
14
+ - Supports Nostr NIP-98 authentication (unique to JSS)
15
+
16
+ ## Architecture
17
+
18
+ ```
19
+ src/
20
+ ├── server.js # Fastify setup, route registration
21
+ ├── handlers/ # LDP operations (GET, PUT, POST, PATCH, DELETE)
22
+ │ ├── resource.js # File operations
23
+ │ └── container.js # Directory operations, pod creation
24
+ ├── auth/ # Authentication
25
+ │ ├── middleware.js # WAC authorization hook
26
+ │ ├── solid-oidc.js # DPoP token verification
27
+ │ ├── nostr.js # NIP-98 Schnorr signatures
28
+ │ └── token.js # Simple Bearer tokens
29
+ ├── idp/ # Identity Provider (oidc-provider)
30
+ │ ├── provider.js # OIDC configuration
31
+ │ ├── interactions.js # Login/consent UI handlers
32
+ │ └── accounts.js # User account storage
33
+ ├── wac/ # Web Access Control
34
+ │ ├── checker.js # Permission checking
35
+ │ └── parser.js # ACL file parsing/generation
36
+ ├── rdf/ # RDF handling
37
+ │ ├── conneg.js # Content negotiation
38
+ │ └── turtle.js # Turtle ↔ JSON-LD conversion
39
+ ├── notifications/ # WebSocket real-time updates
40
+ │ ├── websocket.js # solid-0.1 protocol handler
41
+ │ └── events.js # Event emitter for changes
42
+ ├── ldp/ # Linked Data Platform
43
+ │ ├── headers.js # LDP response headers
44
+ │ └── container.js # Container JSON-LD generation
45
+ └── storage/ # File system operations
46
+ └── filesystem.js # Read/write/stat/list
47
+ ```
48
+
49
+ ## Key Design Decisions
50
+
51
+ ### JSON-LD as canonical storage
52
+ All RDF is stored as JSON-LD. When clients request Turtle, we convert on the fly. This simplifies storage and allows non-RDF tools to read the data.
53
+
54
+ ### HTML profiles with JSON-LD data islands
55
+ WebID profiles are HTML documents with embedded `<script type="application/ld+json">`. This allows:
56
+ - Human-readable profiles in browsers
57
+ - Machine-readable RDF via content negotiation
58
+ - Mashlib renders the profile using the embedded data
59
+
60
+ ### Subdomain mode for XSS isolation
61
+ When `subdomains: true`, each pod gets its own subdomain (alice.example.com). This provides browser security isolation. Storage path includes pod name, but URLs use subdomains.
62
+
63
+ ### Settings folder conventions
64
+ Mashlib expects `Settings/` (capital S) with:
65
+ - `Settings/Preferences.ttl`
66
+ - `Settings/publicTypeIndex.ttl`
67
+ - `Settings/privateTypeIndex.ttl`
68
+
69
+ Earlier versions used lowercase `settings/prefs` which broke mashlib.
70
+
71
+ ## Common Gotchas
72
+
73
+ ### Content negotiation
74
+ - Files stored as JSON-LD regardless of upload format
75
+ - `.ttl` extension triggers Turtle response regardless of Accept header
76
+ - Container listings need conneg too (fixed in v0.0.33)
77
+
78
+ ### Authentication paths that skip auth
79
+ These paths bypass the auth middleware:
80
+ - `/.pods` - Pod creation
81
+ - `/.notifications` - WebSocket endpoint
82
+ - `/idp/*` - Identity provider routes
83
+ - `/.well-known/*` - Discovery endpoints
84
+ - OPTIONS requests
85
+
86
+ ### WebSocket notifications
87
+ Uses legacy `solid-0.1` protocol (not Solid Notifications Protocol):
88
+ ```
89
+ Server: protocol solid-0.1
90
+ Client: sub https://example.org/resource
91
+ Server: ack https://example.org/resource
92
+ Server: pub https://example.org/resource (on change)
93
+ ```
94
+ Discovered via `Updates-Via` header.
95
+
96
+ ### DPoP token verification
97
+ Solid-OIDC uses DPoP-bound tokens. The DPoP proof must match:
98
+ - HTTP method (htm)
99
+ - Request URL (htu)
100
+ - Be recent (iat within 5 minutes)
101
+ - Key thumbprint matches token binding (cnf.jkt)
102
+
103
+ ### ACL inheritance
104
+ WAC ACLs use `acl:default` for inheritance. When checking permissions:
105
+ 1. Look for resource-specific ACL (resource.acl or .acl for containers)
106
+ 2. Walk up to parent containers checking for `acl:default` rules
107
+ 3. Stop at pod root
108
+
109
+ ## Testing
110
+
111
+ ```bash
112
+ npm test # Run all tests
113
+ npm run test:cth # Conformance Test Harness (requires setup)
114
+ ```
115
+
116
+ Tests use in-memory server instances. See `test/helpers.js` for test utilities.
117
+
118
+ ## Deployment
119
+
120
+ ### Production setup
121
+ ```bash
122
+ npm install -g javascript-solid-server
123
+ jss --port 443 --ssl-key key.pem --ssl-cert cert.pem --idp --multiuser
124
+ ```
125
+
126
+ ### With HAProxy (recommended)
127
+ HAProxy handles SSL termination, JSS runs on localhost:8443. Wildcard cert needed for subdomain mode.
128
+
129
+ ### PM2 process management
130
+ ```bash
131
+ pm2 start "jss --config config.json" --name solid
132
+ pm2 logs solid
133
+ pm2 restart solid
134
+ ```
135
+
136
+ ## External Dependencies
137
+
138
+ - **oidc-provider**: OpenID Connect implementation (complex, many warnings are normal)
139
+ - **n3**: Turtle/N3 parsing and serialization
140
+ - **jose**: JWT/JWK handling for Solid-OIDC
141
+ - **@fastify/websocket**: WebSocket support
142
+
143
+ ## Related Specs
144
+
145
+ - [Solid Protocol](https://solidproject.org/TR/protocol)
146
+ - [Solid-OIDC](https://solidproject.org/TR/oidc)
147
+ - [Web Access Control](https://solidproject.org/TR/wac)
148
+ - [Linked Data Platform](https://www.w3.org/TR/ldp/)
149
+
150
+ ## Contact
151
+
152
+ Issues: https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues
package/bin/jss.js CHANGED
@@ -54,6 +54,8 @@ program
54
54
  .option('--mashlib-cdn', 'Enable Mashlib data browser (CDN mode, no local files needed)')
55
55
  .option('--no-mashlib', 'Disable Mashlib data browser')
56
56
  .option('--mashlib-version <version>', 'Mashlib version for CDN mode (default: 2.0.0)')
57
+ .option('--git', 'Enable Git HTTP backend (clone/push support)')
58
+ .option('--no-git', 'Disable Git HTTP backend')
57
59
  .option('-q, --quiet', 'Suppress log output')
58
60
  .option('--print-config', 'Print configuration and exit')
59
61
  .action(async (options) => {
@@ -95,6 +97,7 @@ program
95
97
  mashlib: config.mashlib || config.mashlibCdn,
96
98
  mashlibCdn: config.mashlibCdn,
97
99
  mashlibVersion: config.mashlibVersion,
100
+ git: config.git,
98
101
  });
99
102
 
100
103
  await server.listen({ port: config.port, host: config.host });
@@ -113,6 +116,7 @@ program
113
116
  } else if (config.mashlib) {
114
117
  console.log(` Mashlib: local (data browser enabled)`);
115
118
  }
119
+ if (config.git) console.log(' Git: enabled (clone/push support)');
116
120
  console.log('\n Press Ctrl+C to stop\n');
117
121
  }
118
122
 
@@ -0,0 +1,353 @@
1
+ # Design: JSS as Nostr Relay++
2
+
3
+ ## Overview
4
+
5
+ Replace the Solid Notifications Protocol with Nostr relay functionality. JSS becomes a Nostr relay that also serves LDP resources, unifying identity, storage, and real-time notifications.
6
+
7
+ ## Motivation
8
+
9
+ **Solid Notifications Protocol problems:**
10
+ - Complex discovery mechanism
11
+ - JSON-LD channel descriptions
12
+ - No federation
13
+ - No existing ecosystem
14
+ - Reinvents pub/sub poorly
15
+
16
+ **Nostr advantages:**
17
+ - Simple WebSocket protocol (NIP-01)
18
+ - Cryptographic identity built-in
19
+ - Federation via relay gossip
20
+ - Millions of existing users
21
+ - Mobile push infrastructure exists
22
+ - Battle-tested
23
+
24
+ **JSS already has:**
25
+ - NIP-98 HTTP authentication
26
+ - WebSocket infrastructure (solid-0.1)
27
+ - JSON-LD storage
28
+
29
+ ## Architecture
30
+
31
+ ```
32
+ ┌─────────────────────────────────────────────────────────────┐
33
+ │ JSS Server │
34
+ ├──────────────────────┬──────────────────────────────────────┤
35
+ │ LDP Layer │ Nostr Relay Layer │
36
+ │ │ │
37
+ │ GET/PUT/POST/PATCH │ EVENT/REQ/CLOSE/EOSE │
38
+ │ DELETE/OPTIONS │ │
39
+ │ │ │
40
+ │ ┌────────────────┐ │ ┌─────────────────────────────────┐ │
41
+ │ │ Resources │◄─┼─►│ Events (kind:30078) │ │
42
+ │ │ /alice/doc.ttl│ │ │ Addressable by d-tag = URI │ │
43
+ │ └────────────────┘ │ └─────────────────────────────────┘ │
44
+ │ │ │
45
+ │ Auth: Solid-OIDC │ Auth: NIP-98 / NIP-42 │
46
+ │ NIP-98 │ │
47
+ └──────────────────────┴──────────────────────────────────────┘
48
+
49
+
50
+ ┌─────────────────┐
51
+ │ Other Relays │
52
+ │ (Federation) │
53
+ └─────────────────┘
54
+ ```
55
+
56
+ ## Protocol Mapping
57
+
58
+ ### Resource ↔ Event Mapping
59
+
60
+ LDP resources map to Nostr replaceable events:
61
+
62
+ ```
63
+ Resource URL: https://alice.solid.social/notes/idea.json
64
+
65
+ Nostr Event:
66
+ {
67
+ "kind": 30078, // Arbitrary JSON (NIP-78)
68
+ "pubkey": "<alice-pubkey>",
69
+ "created_at": 1703888888,
70
+ "tags": [
71
+ ["d", "https://alice.solid.social/notes/idea.json"],
72
+ ["solid:type", "ldp:Resource"],
73
+ ["solid:contentType", "application/ld+json"]
74
+ ],
75
+ "content": "{\"@context\": ..., \"title\": \"My Idea\"}",
76
+ "sig": "<signature>"
77
+ }
78
+ ```
79
+
80
+ ### Kind Assignments
81
+
82
+ | Kind | Purpose | NIP |
83
+ |------|---------|-----|
84
+ | 30078 | LDP Resource (JSON content) | NIP-78 |
85
+ | 30079 | LDP Container listing | Custom |
86
+ | 30080 | ACL document | Custom |
87
+ | 10078 | Resource deletion marker | Custom |
88
+ | 1 | Social posts (optional integration) | NIP-01 |
89
+
90
+ Using 30xxx range for addressable replaceable events (d-tag = resource URI).
91
+
92
+ ### Subscription Filters
93
+
94
+ Subscribe to resource changes:
95
+
96
+ ```json
97
+ // Subscribe to single resource
98
+ ["REQ", "sub1", {
99
+ "kinds": [30078],
100
+ "#d": ["https://alice.solid.social/notes/idea.json"]
101
+ }]
102
+
103
+ // Subscribe to container (all resources under path)
104
+ ["REQ", "sub2", {
105
+ "kinds": [30078, 30079],
106
+ "#d": ["https://alice.solid.social/notes/"]
107
+ }]
108
+
109
+ // Subscribe to all changes by user
110
+ ["REQ", "sub3", {
111
+ "kinds": [30078],
112
+ "authors": ["<alice-pubkey>"]
113
+ }]
114
+ ```
115
+
116
+ ## Implementation
117
+
118
+ ### Phase 1: Basic Relay
119
+
120
+ Add NIP-01 relay functionality to existing WebSocket endpoint:
121
+
122
+ ```javascript
123
+ // src/notifications/nostr-relay.js
124
+
125
+ export function handleNostrMessage(socket, message) {
126
+ const [type, ...params] = JSON.parse(message);
127
+
128
+ switch (type) {
129
+ case 'EVENT':
130
+ return handleEvent(socket, params[0]);
131
+ case 'REQ':
132
+ return handleSubscription(socket, params[0], params.slice(1));
133
+ case 'CLOSE':
134
+ return handleClose(socket, params[0]);
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Phase 2: LDP-Event Bridge
140
+
141
+ When LDP resources change, emit Nostr events:
142
+
143
+ ```javascript
144
+ // src/handlers/resource.js (modified)
145
+
146
+ export async function handlePut(request, reply) {
147
+ // ... existing LDP logic ...
148
+
149
+ // After successful write, emit Nostr event
150
+ if (request.nostrPubkey) {
151
+ await emitResourceEvent({
152
+ pubkey: request.nostrPubkey,
153
+ resourceUrl,
154
+ content,
155
+ contentType
156
+ });
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Phase 3: Federation
162
+
163
+ Connect to other relays for event propagation:
164
+
165
+ ```javascript
166
+ // src/notifications/federation.js
167
+
168
+ const FEDERATION_RELAYS = [
169
+ 'wss://relay.damus.io',
170
+ 'wss://nos.lol',
171
+ 'wss://relay.nostr.band'
172
+ ];
173
+
174
+ export async function federateEvent(event) {
175
+ // Only federate public resources
176
+ if (await isPublicResource(event.tags.find(t => t[0] === 'd')[1])) {
177
+ for (const relay of FEDERATION_RELAYS) {
178
+ publishToRelay(relay, event);
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ ### Phase 4: Identity Unification
185
+
186
+ WebID document includes Nostr pubkey:
187
+
188
+ ```json
189
+ {
190
+ "@context": {...},
191
+ "@id": "https://alice.solid.social/profile/card#me",
192
+ "foaf:name": "Alice",
193
+ "nostr:pubkey": "npub1abc...",
194
+ "nostr:relays": ["wss://alice.solid.social"]
195
+ }
196
+ ```
197
+
198
+ Nostr profile (kind:0) links to WebID:
199
+
200
+ ```json
201
+ {
202
+ "kind": 0,
203
+ "content": "{\"name\":\"Alice\",\"webid\":\"https://alice.solid.social/profile/card#me\"}"
204
+ }
205
+ ```
206
+
207
+ ## WebSocket Endpoint
208
+
209
+ Single endpoint handles both protocols:
210
+
211
+ ```
212
+ wss://alice.solid.social/.notifications
213
+
214
+ Protocol detection:
215
+ - If first message is JSON array starting with "EVENT"/"REQ" → Nostr
216
+ - If first message is "sub <uri>" → Legacy solid-0.1
217
+ ```
218
+
219
+ ```javascript
220
+ // src/notifications/websocket.js (modified)
221
+
222
+ export function handleWebSocket(socket, request) {
223
+ socket.on('message', (message) => {
224
+ const msg = message.toString().trim();
225
+
226
+ // Detect protocol
227
+ if (msg.startsWith('[')) {
228
+ // Nostr protocol
229
+ handleNostrMessage(socket, msg);
230
+ } else if (msg.startsWith('sub ') || msg.startsWith('unsub ')) {
231
+ // Legacy solid-0.1
232
+ handleSolidMessage(socket, msg);
233
+ }
234
+ });
235
+ }
236
+ ```
237
+
238
+ ## Access Control
239
+
240
+ ### Public Resources
241
+ - Events federate to other relays
242
+ - Anyone can subscribe
243
+
244
+ ### Private Resources
245
+ - Events stay local (no federation)
246
+ - NIP-42 AUTH required to subscribe
247
+ - Subscription filter must match authorized pubkeys
248
+
249
+ ```javascript
250
+ // NIP-42 AUTH flow
251
+ ["AUTH", "<signed-event>"]
252
+
253
+ // Server validates and restricts subscriptions
254
+ // to resources the pubkey has access to
255
+ ```
256
+
257
+ ### ACL Mapping
258
+
259
+ ```
260
+ acl:Read → Can subscribe to events
261
+ acl:Write → Can publish events (create/update)
262
+ acl:Control → Can modify ACL events
263
+ ```
264
+
265
+ ## Storage
266
+
267
+ Two options:
268
+
269
+ ### Option A: Dual Storage (Recommended for Phase 1)
270
+ - LDP resources in filesystem (existing)
271
+ - Nostr events in SQLite/memory (relay state)
272
+ - Bridge syncs between them
273
+
274
+ ### Option B: Event-Native Storage (Future)
275
+ - All resources stored as Nostr events
276
+ - LDP is a view over event history
277
+ - Full audit trail built-in
278
+ - Replaces filesystem storage
279
+
280
+ ## Configuration
281
+
282
+ ```json
283
+ {
284
+ "nostr": {
285
+ "enabled": true,
286
+ "relay": {
287
+ "nip01": true,
288
+ "nip42": true,
289
+ "nip78": true
290
+ },
291
+ "federation": {
292
+ "enabled": false,
293
+ "relays": [],
294
+ "publicOnly": true
295
+ },
296
+ "kinds": {
297
+ "resource": 30078,
298
+ "container": 30079,
299
+ "acl": 30080
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ ## Migration Path
306
+
307
+ 1. **Phase 1**: Add relay alongside existing WebSocket
308
+ - Both protocols on same endpoint
309
+ - No breaking changes
310
+
311
+ 2. **Phase 2**: LDP-Event bridge
312
+ - Changes emit events
313
+ - Subscriptions work via Nostr
314
+
315
+ 3. **Phase 3**: Federation (optional)
316
+ - Public resources propagate
317
+ - Discovery via relay network
318
+
319
+ 4. **Phase 4**: Deprecate solid-0.1
320
+ - Nostr becomes primary notification protocol
321
+ - Mashlib adapter if needed
322
+
323
+ ## Benefits
324
+
325
+ | Feature | Solid Notifications | Nostr Relay++ |
326
+ |---------|--------------------|--------------|
327
+ | Protocol complexity | High | Low |
328
+ | Existing clients | ~0 | Millions |
329
+ | Federation | No | Yes |
330
+ | Mobile push | Build it yourself | Existing infrastructure |
331
+ | Identity | Separate (WebID) | Integrated (npub) |
332
+ | Signatures | Optional | Every event |
333
+ | Ecosystem | Academic | Active |
334
+
335
+ ## Open Questions
336
+
337
+ 1. **Kind numbers**: Apply for official NIP allocation or use 30078-30080 range?
338
+
339
+ 2. **Content encoding**: Store JSON-LD directly in content, or reference by hash?
340
+
341
+ 3. **Large resources**: Nostr events have size limits. Use NIP-94/NIP-96 for large files?
342
+
343
+ 4. **Container semantics**: How to represent ldp:contains in events?
344
+
345
+ 5. **Conflict resolution**: Last-write-wins via created_at, or something smarter?
346
+
347
+ ## References
348
+
349
+ - [NIP-01: Basic Protocol](https://github.com/nostr-protocol/nips/blob/master/01.md)
350
+ - [NIP-42: Authentication](https://github.com/nostr-protocol/nips/blob/master/42.md)
351
+ - [NIP-78: Arbitrary Custom App Data](https://github.com/nostr-protocol/nips/blob/master/78.md)
352
+ - [NIP-98: HTTP Auth](https://github.com/nostr-protocol/nips/blob/master/98.md)
353
+ - [Solid Protocol](https://solidproject.org/TR/protocol)