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.
- package/.claude/settings.local.json +20 -1
- package/AGENTS.md +152 -0
- package/bin/jss.js +4 -0
- package/docs/design/nostr-relay-integration.md +353 -0
- package/docs/git-support.md +283 -0
- package/package.json +1 -1
- package/src/handlers/git.js +207 -0
- package/src/handlers/resource.js +75 -23
- package/src/idp/credentials.js +3 -2
- package/src/idp/index.js +1 -1
- package/src/idp/keys.js +57 -8
- package/src/idp/provider.js +100 -2
- package/src/rdf/turtle.js +4 -2
- package/src/server.js +53 -1
|
@@ -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)
|