javascript-solid-server 0.0.85 → 0.0.87
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 +3 -1
- package/README.md +43 -23
- package/bin/jss.js +2 -0
- package/package.json +1 -1
- package/src/config.js +5 -0
- package/src/notifications/websocket.js +6 -0
- package/src/wac/parser.js +3 -2
- package/test/live-reload.test.js +265 -0
- package/test/wac.test.js +17 -0
- package/fonstr-data/index.html +0 -120
package/README.md
CHANGED
|
@@ -6,8 +6,11 @@ A minimal, fast, JSON-LD native Solid server.
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
### Implemented (v0.0.
|
|
9
|
+
### Implemented (v0.0.86)
|
|
10
10
|
|
|
11
|
+
- **Live Reload** - Auto-refresh browser on file changes (`--live-reload`)
|
|
12
|
+
- **Read-Only Mode** - Disable write operations for static hosting (`--read-only`)
|
|
13
|
+
- **Public Mode** - Skip WAC for open read/write access (`--public`)
|
|
11
14
|
- **Schnorr SSO** - Passwordless login via BIP-340 Schnorr signatures using NIP-07 browser extensions (Podkey, nos2x, Alby)
|
|
12
15
|
- **Passkey Authentication** - WebAuthn/FIDO2 passwordless login with Touch ID, Face ID, or security keys
|
|
13
16
|
- **HTTP Range Requests** - Partial content delivery for large files and media streaming
|
|
@@ -35,7 +38,7 @@ A minimal, fast, JSON-LD native Solid server.
|
|
|
35
38
|
- **Content Negotiation** - Turtle <-> JSON-LD conversion, including HTML data islands
|
|
36
39
|
- **CORS Support** - Full cross-origin resource sharing
|
|
37
40
|
- **Git HTTP Backend** - Clone and push to containers via `git` protocol
|
|
38
|
-
- **Nostr Relay** - Integrated NIP-01 relay on the same port (`wss://your.pod/relay`)
|
|
41
|
+
- **Nostr Relay** - Integrated NIP-01/NIP-11/NIP-16 relay on the same port (`wss://your.pod/relay`)
|
|
39
42
|
- **Invite-Only Registration** - CLI-managed invite codes for controlled signups
|
|
40
43
|
- **Storage Quotas** - Per-user storage limits with CLI management
|
|
41
44
|
- **Security** - Blocks access to dotfiles (`.git/`, `.env`, etc.) except Solid-specific ones
|
|
@@ -139,6 +142,9 @@ jss --help # Show help
|
|
|
139
142
|
| `--ap-display-name <name>` | ActivityPub display name | (username) |
|
|
140
143
|
| `--ap-summary <text>` | ActivityPub bio/summary | - |
|
|
141
144
|
| `--ap-nostr-pubkey <hex>` | Nostr pubkey for identity linking | - |
|
|
145
|
+
| `--public` | Allow unauthenticated access (skip WAC) | false |
|
|
146
|
+
| `--read-only` | Disable PUT/DELETE/PATCH methods | false |
|
|
147
|
+
| `--live-reload` | Auto-refresh browser on file changes | false |
|
|
142
148
|
| `-q, --quiet` | Suppress logs | false |
|
|
143
149
|
|
|
144
150
|
### Environment Variables
|
|
@@ -159,6 +165,10 @@ export JSS_WEBID_TLS=true
|
|
|
159
165
|
export JSS_DEFAULT_QUOTA=100MB
|
|
160
166
|
export JSS_ACTIVITYPUB=true
|
|
161
167
|
export JSS_AP_USERNAME=alice
|
|
168
|
+
export JSS_PUBLIC=true
|
|
169
|
+
export JSS_READ_ONLY=true
|
|
170
|
+
export JSS_LIVE_RELOAD=true
|
|
171
|
+
export JSS_SOLIDOS_UI=true
|
|
162
172
|
jss start
|
|
163
173
|
```
|
|
164
174
|
|
|
@@ -868,7 +878,7 @@ curl -X POST https://example.com/.pods \
|
|
|
868
878
|
|
|
869
879
|
| Server | Size | Deps | Notes |
|
|
870
880
|
|--------|------|------|-------|
|
|
871
|
-
| [JSS](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer) |
|
|
881
|
+
| [JSS](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer) | ~14K LoC | 14 | Minimal, JSON-LD native |
|
|
872
882
|
| [NSS](https://github.com/nodeSolidServer/node-solid-server) | 777 KB | 58 | Original Solid server |
|
|
873
883
|
| [CSS](https://github.com/CommunitySolidServer/CommunitySolidServer) | 5.8 MB | 70 | Modular, configurable |
|
|
874
884
|
| [Pivot](https://github.com/solid-contrib/pivot) | ~6 MB | 70+ | Built on CSS |
|
|
@@ -941,7 +951,7 @@ npm run benchmark
|
|
|
941
951
|
npm test
|
|
942
952
|
```
|
|
943
953
|
|
|
944
|
-
Currently passing: **
|
|
954
|
+
Currently passing: **229 tests** (including 27 conformance tests)
|
|
945
955
|
|
|
946
956
|
### Conformance Test Harness (CTH)
|
|
947
957
|
|
|
@@ -988,12 +998,12 @@ src/
|
|
|
988
998
|
│ ├── filesystem.js # File operations
|
|
989
999
|
│ └── quota.js # Storage quota management
|
|
990
1000
|
├── auth/
|
|
991
|
-
│ ├── middleware.js
|
|
992
|
-
│ ├── token.js
|
|
993
|
-
│ ├── solid-oidc.js
|
|
994
|
-
│ ├── nostr.js
|
|
995
|
-
│ ├── did-nostr.js
|
|
996
|
-
│ └── webid-tls.js
|
|
1001
|
+
│ ├── middleware.js # Auth hook
|
|
1002
|
+
│ ├── token.js # Simple token auth
|
|
1003
|
+
│ ├── solid-oidc.js # DPoP verification
|
|
1004
|
+
│ ├── nostr.js # NIP-98 Nostr authentication
|
|
1005
|
+
│ ├── did-nostr.js # did:nostr → WebID resolution
|
|
1006
|
+
│ └── webid-tls.js # WebID-TLS client certificate auth
|
|
997
1007
|
├── wac/
|
|
998
1008
|
│ ├── parser.js # ACL parsing
|
|
999
1009
|
│ └── checker.js # Permission checking
|
|
@@ -1010,14 +1020,16 @@ src/
|
|
|
1010
1020
|
│ ├── events.js # Event emitter
|
|
1011
1021
|
│ └── websocket.js # solid-0.1 protocol
|
|
1012
1022
|
├── idp/
|
|
1013
|
-
│ ├── index.js
|
|
1014
|
-
│ ├── provider.js
|
|
1015
|
-
│ ├── adapter.js
|
|
1016
|
-
│ ├── accounts.js
|
|
1017
|
-
│ ├──
|
|
1018
|
-
│ ├──
|
|
1019
|
-
│ ├──
|
|
1020
|
-
│
|
|
1023
|
+
│ ├── index.js # Identity Provider plugin
|
|
1024
|
+
│ ├── provider.js # oidc-provider config
|
|
1025
|
+
│ ├── adapter.js # Filesystem adapter
|
|
1026
|
+
│ ├── accounts.js # User account management
|
|
1027
|
+
│ ├── credentials.js # Credentials endpoint
|
|
1028
|
+
│ ├── keys.js # JWKS key management
|
|
1029
|
+
│ ├── interactions.js # Login/consent handlers
|
|
1030
|
+
│ ├── passkey.js # WebAuthn/FIDO2 passkey support
|
|
1031
|
+
│ ├── views.js # HTML templates
|
|
1032
|
+
│ └── invites.js # Invite code management
|
|
1021
1033
|
├── ap/
|
|
1022
1034
|
│ ├── index.js # ActivityPub plugin
|
|
1023
1035
|
│ ├── keys.js # RSA keypair management
|
|
@@ -1030,23 +1042,31 @@ src/
|
|
|
1030
1042
|
├── rdf/
|
|
1031
1043
|
│ ├── turtle.js # Turtle <-> JSON-LD
|
|
1032
1044
|
│ └── conneg.js # Content negotiation
|
|
1045
|
+
├── mashlib/
|
|
1046
|
+
│ └── index.js # Mashlib data browser plugin
|
|
1033
1047
|
└── utils/
|
|
1034
|
-
├── url.js
|
|
1035
|
-
|
|
1048
|
+
├── url.js # URL utilities
|
|
1049
|
+
├── conditional.js # If-Match/If-None-Match
|
|
1050
|
+
└── ssrf.js # SSRF protection
|
|
1036
1051
|
```
|
|
1037
1052
|
|
|
1038
1053
|
## Dependencies
|
|
1039
1054
|
|
|
1040
|
-
|
|
1055
|
+
14 direct dependencies for a fast, secure server:
|
|
1041
1056
|
|
|
1042
1057
|
- **fastify** - High-performance HTTP server
|
|
1058
|
+
- **@fastify/middie** - Express/Connect middleware bridge (for IdP)
|
|
1059
|
+
- **@fastify/rate-limit** - Rate limiting for API endpoints
|
|
1043
1060
|
- **@fastify/websocket** - WebSocket support for notifications
|
|
1061
|
+
- **@simplewebauthn/server** - Passkey/WebAuthn authentication
|
|
1062
|
+
- **bcryptjs** - Password hashing (pure JS, works on Termux/Android)
|
|
1063
|
+
- **commander** - CLI command parsing
|
|
1044
1064
|
- **fs-extra** - Enhanced file operations
|
|
1045
1065
|
- **jose** - JWT/JWK handling for Solid-OIDC
|
|
1066
|
+
- **microfed** - ActivityPub primitives (only when activitypub enabled)
|
|
1046
1067
|
- **n3** - Turtle parsing (only used when conneg enabled)
|
|
1068
|
+
- **nostr-tools** - Nostr protocol and Schnorr signature verification
|
|
1047
1069
|
- **oidc-provider** - OpenID Connect Identity Provider (only when IdP enabled)
|
|
1048
|
-
- **bcryptjs** - Password hashing (only when IdP enabled)
|
|
1049
|
-
- **microfed** - ActivityPub primitives (only when activitypub enabled)
|
|
1050
1070
|
- **sql.js** - SQLite storage for federation data (WASM, cross-platform)
|
|
1051
1071
|
|
|
1052
1072
|
## License
|
package/bin/jss.js
CHANGED
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -228,6 +228,11 @@ export async function loadConfig(cliOptions = {}, configFile = null) {
|
|
|
228
228
|
config.logger = false;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
// Mashlib requires content negotiation for Turtle support
|
|
232
|
+
if (config.mashlib || config.mashlibCdn) {
|
|
233
|
+
config.conneg = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
231
236
|
// Validate SSL config
|
|
232
237
|
if ((config.sslKey && !config.sslCert) || (!config.sslKey && config.sslCert)) {
|
|
233
238
|
throw new Error('Both --ssl-key and --ssl-cert must be provided together');
|
|
@@ -40,6 +40,7 @@ export function handleWebSocket(socket, request, webId = null) {
|
|
|
40
40
|
// Store webId and server info on socket for ACL checks
|
|
41
41
|
socket.webId = webId;
|
|
42
42
|
socket.serverOrigin = `${request.protocol}://${request.hostname}`;
|
|
43
|
+
socket.publicMode = request.config?.public || false;
|
|
43
44
|
|
|
44
45
|
// Send protocol greeting
|
|
45
46
|
socket.send('protocol solid-0.1');
|
|
@@ -123,6 +124,11 @@ async function checkSubscriptionAccess(url, socket) {
|
|
|
123
124
|
const stats = await storage.stat(resourcePath);
|
|
124
125
|
const isContainer = stats?.isDirectory || resourcePath.endsWith('/');
|
|
125
126
|
|
|
127
|
+
// Skip WAC check in public mode
|
|
128
|
+
if (socket.publicMode) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
126
132
|
// Check WAC read permission
|
|
127
133
|
const { allowed } = await checkAccess({
|
|
128
134
|
resourceUrl: url,
|
package/src/wac/parser.js
CHANGED
|
@@ -144,8 +144,9 @@ function parseAuthorization(node, aclUrl) {
|
|
|
144
144
|
auth.default = parseUriArray(node['acl:default'] || node['default'])
|
|
145
145
|
.map(uri => resolveUri(uri, baseUrl));
|
|
146
146
|
|
|
147
|
-
// Parse agents (WebIDs can be relative too)
|
|
148
|
-
auth.agents = parseUriArray(node['acl:agent'] || node['agent'])
|
|
147
|
+
// Parse agents (WebIDs can be relative too) - resolve against ACL URL
|
|
148
|
+
auth.agents = parseUriArray(node['acl:agent'] || node['agent'])
|
|
149
|
+
.map(uri => resolveUri(uri, aclUrl));
|
|
149
150
|
|
|
150
151
|
// Parse agentClass
|
|
151
152
|
auth.agentClasses = parseUriArray(node['acl:agentClass'] || node['agentClass']);
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Reload Test
|
|
3
|
+
*
|
|
4
|
+
* Tests the full chain: file change → WebSocket pub → client receives
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createServer } from '../src/server.js';
|
|
8
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import WebSocket from 'ws';
|
|
11
|
+
|
|
12
|
+
const TEST_PORT = 9876;
|
|
13
|
+
const TEST_DIR = '/tmp/live-reload-test-suite';
|
|
14
|
+
const BASE_URL = `http://localhost:${TEST_PORT}`;
|
|
15
|
+
|
|
16
|
+
// Setup and teardown
|
|
17
|
+
function setupTestDir() {
|
|
18
|
+
if (existsSync(TEST_DIR)) {
|
|
19
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
22
|
+
writeFileSync(join(TEST_DIR, 'index.html'), '<!DOCTYPE html><html><body>Hello</body></html>');
|
|
23
|
+
writeFileSync(join(TEST_DIR, 'test.txt'), 'initial content');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function cleanupTestDir() {
|
|
27
|
+
if (existsSync(TEST_DIR)) {
|
|
28
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Test 1: Verify WebSocket notifications work for HTTP PUT
|
|
33
|
+
async function testHttpPutNotification() {
|
|
34
|
+
console.log('\n=== Test 1: HTTP PUT triggers WebSocket notification ===');
|
|
35
|
+
|
|
36
|
+
setupTestDir();
|
|
37
|
+
|
|
38
|
+
const server = createServer({
|
|
39
|
+
root: TEST_DIR,
|
|
40
|
+
port: TEST_PORT,
|
|
41
|
+
logger: false,
|
|
42
|
+
liveReload: true,
|
|
43
|
+
public: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await server.listen({ port: TEST_PORT, host: '0.0.0.0' });
|
|
47
|
+
console.log('Server started on port', TEST_PORT);
|
|
48
|
+
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const timeout = setTimeout(() => {
|
|
51
|
+
ws.close();
|
|
52
|
+
server.close();
|
|
53
|
+
reject(new Error('Timeout: No WebSocket notification received'));
|
|
54
|
+
}, 5000);
|
|
55
|
+
|
|
56
|
+
const ws = new WebSocket(`ws://localhost:${TEST_PORT}/.notifications`);
|
|
57
|
+
|
|
58
|
+
ws.on('open', () => {
|
|
59
|
+
console.log('WebSocket connected');
|
|
60
|
+
// Subscribe to the test file
|
|
61
|
+
ws.send(`sub ${BASE_URL}/test.txt`);
|
|
62
|
+
console.log('Subscribed to', `${BASE_URL}/test.txt`);
|
|
63
|
+
|
|
64
|
+
// Wait a bit then do HTTP PUT
|
|
65
|
+
setTimeout(async () => {
|
|
66
|
+
console.log('Doing HTTP PUT...');
|
|
67
|
+
const res = await fetch(`${BASE_URL}/test.txt`, {
|
|
68
|
+
method: 'PUT',
|
|
69
|
+
body: 'updated via HTTP',
|
|
70
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
71
|
+
});
|
|
72
|
+
console.log('PUT response:', res.status);
|
|
73
|
+
}, 500);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
ws.on('message', (data) => {
|
|
77
|
+
const msg = data.toString();
|
|
78
|
+
console.log('WebSocket received:', msg);
|
|
79
|
+
|
|
80
|
+
if (msg.startsWith('pub ')) {
|
|
81
|
+
clearTimeout(timeout);
|
|
82
|
+
console.log('SUCCESS: Received pub notification');
|
|
83
|
+
ws.close();
|
|
84
|
+
server.close().then(() => {
|
|
85
|
+
cleanupTestDir();
|
|
86
|
+
resolve(true);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ws.on('error', (err) => {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
server.close();
|
|
94
|
+
reject(err);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Test 2: Verify file watcher detects filesystem changes
|
|
100
|
+
async function testFileWatcherNotification() {
|
|
101
|
+
console.log('\n=== Test 2: Filesystem change triggers WebSocket notification ===');
|
|
102
|
+
|
|
103
|
+
setupTestDir();
|
|
104
|
+
|
|
105
|
+
const server = createServer({
|
|
106
|
+
root: TEST_DIR,
|
|
107
|
+
port: TEST_PORT,
|
|
108
|
+
logger: false,
|
|
109
|
+
liveReload: true,
|
|
110
|
+
public: true,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await server.listen({ port: TEST_PORT, host: '0.0.0.0' });
|
|
114
|
+
console.log('Server started on port', TEST_PORT);
|
|
115
|
+
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
const timeout = setTimeout(() => {
|
|
118
|
+
ws.close();
|
|
119
|
+
server.close();
|
|
120
|
+
reject(new Error('Timeout: No WebSocket notification received for filesystem change'));
|
|
121
|
+
}, 5000);
|
|
122
|
+
|
|
123
|
+
const ws = new WebSocket(`ws://localhost:${TEST_PORT}/.notifications`);
|
|
124
|
+
|
|
125
|
+
ws.on('open', () => {
|
|
126
|
+
console.log('WebSocket connected');
|
|
127
|
+
// Subscribe to the test file
|
|
128
|
+
ws.send(`sub ${BASE_URL}/test.txt`);
|
|
129
|
+
console.log('Subscribed to', `${BASE_URL}/test.txt`);
|
|
130
|
+
|
|
131
|
+
// Wait a bit then modify file directly on filesystem
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
console.log('Modifying file via filesystem...');
|
|
134
|
+
writeFileSync(join(TEST_DIR, 'test.txt'), 'updated via filesystem ' + Date.now());
|
|
135
|
+
console.log('File written');
|
|
136
|
+
}, 1000);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
ws.on('message', (data) => {
|
|
140
|
+
const msg = data.toString();
|
|
141
|
+
console.log('WebSocket received:', msg);
|
|
142
|
+
|
|
143
|
+
if (msg.startsWith('pub ')) {
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
console.log('SUCCESS: Received pub notification for filesystem change');
|
|
146
|
+
ws.close();
|
|
147
|
+
server.close().then(() => {
|
|
148
|
+
cleanupTestDir();
|
|
149
|
+
resolve(true);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
ws.on('error', (err) => {
|
|
155
|
+
clearTimeout(timeout);
|
|
156
|
+
server.close();
|
|
157
|
+
reject(err);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Test 3: Verify fs.watch works on this platform
|
|
163
|
+
async function testFsWatch() {
|
|
164
|
+
console.log('\n=== Test 3: Basic fs.watch functionality ===');
|
|
165
|
+
|
|
166
|
+
const { watch } = await import('fs');
|
|
167
|
+
const testFile = '/tmp/fswatch-test.txt';
|
|
168
|
+
|
|
169
|
+
writeFileSync(testFile, 'initial');
|
|
170
|
+
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const timeout = setTimeout(() => {
|
|
173
|
+
watcher.close();
|
|
174
|
+
reject(new Error('fs.watch did not detect file change'));
|
|
175
|
+
}, 3000);
|
|
176
|
+
|
|
177
|
+
const watcher = watch(testFile, (eventType, filename) => {
|
|
178
|
+
console.log('fs.watch detected:', eventType, filename);
|
|
179
|
+
clearTimeout(timeout);
|
|
180
|
+
watcher.close();
|
|
181
|
+
resolve(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Modify file after short delay
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
console.log('Modifying file...');
|
|
187
|
+
writeFileSync(testFile, 'modified ' + Date.now());
|
|
188
|
+
}, 500);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Test 4: Verify fs.watch with recursive option
|
|
193
|
+
async function testFsWatchRecursive() {
|
|
194
|
+
console.log('\n=== Test 4: fs.watch with recursive option ===');
|
|
195
|
+
|
|
196
|
+
const { watch } = await import('fs');
|
|
197
|
+
const testDir = '/tmp/fswatch-recursive-test';
|
|
198
|
+
|
|
199
|
+
if (existsSync(testDir)) rmSync(testDir, { recursive: true });
|
|
200
|
+
mkdirSync(testDir, { recursive: true });
|
|
201
|
+
writeFileSync(join(testDir, 'file.txt'), 'initial');
|
|
202
|
+
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const timeout = setTimeout(() => {
|
|
205
|
+
watcher.close();
|
|
206
|
+
console.log('FAIL: fs.watch recursive did not detect file change');
|
|
207
|
+
resolve(false); // Don't reject, just report failure
|
|
208
|
+
}, 3000);
|
|
209
|
+
|
|
210
|
+
let detected = false;
|
|
211
|
+
const watcher = watch(testDir, { recursive: true }, (eventType, filename) => {
|
|
212
|
+
if (!detected) {
|
|
213
|
+
detected = true;
|
|
214
|
+
console.log('fs.watch recursive detected:', eventType, filename);
|
|
215
|
+
clearTimeout(timeout);
|
|
216
|
+
watcher.close();
|
|
217
|
+
resolve(true);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
watcher.on('error', (err) => {
|
|
222
|
+
console.log('fs.watch error:', err.message);
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
resolve(false);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Modify file after short delay
|
|
228
|
+
setTimeout(() => {
|
|
229
|
+
console.log('Modifying file in watched directory...');
|
|
230
|
+
writeFileSync(join(testDir, 'file.txt'), 'modified ' + Date.now());
|
|
231
|
+
}, 500);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Run all tests
|
|
236
|
+
async function runTests() {
|
|
237
|
+
console.log('Live Reload Test Suite');
|
|
238
|
+
console.log('======================');
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
// Test basic fs.watch first
|
|
242
|
+
const fsWatchWorks = await testFsWatch();
|
|
243
|
+
console.log('Test 3 result: fs.watch works =', fsWatchWorks);
|
|
244
|
+
|
|
245
|
+
// Test recursive fs.watch
|
|
246
|
+
const fsWatchRecursiveWorks = await testFsWatchRecursive();
|
|
247
|
+
console.log('Test 4 result: fs.watch recursive works =', fsWatchRecursiveWorks);
|
|
248
|
+
|
|
249
|
+
// Test HTTP PUT notification
|
|
250
|
+
await testHttpPutNotification();
|
|
251
|
+
console.log('Test 1 result: PASSED');
|
|
252
|
+
|
|
253
|
+
// Test file watcher notification
|
|
254
|
+
await testFileWatcherNotification();
|
|
255
|
+
console.log('Test 2 result: PASSED');
|
|
256
|
+
|
|
257
|
+
console.log('\n=== All tests passed ===');
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error('\n=== Test FAILED ===');
|
|
260
|
+
console.error(err.message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
runTests();
|
package/test/wac.test.js
CHANGED
|
@@ -187,6 +187,23 @@ describe('WAC Parser', () => {
|
|
|
187
187
|
`Expected accessTo to include 'https://alice.example/other/', got: ${auths[0].accessTo}`);
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
+
it('should resolve relative agent URIs against ACL URL', async () => {
|
|
191
|
+
const acl = {
|
|
192
|
+
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
|
|
193
|
+
'@id': '#owner',
|
|
194
|
+
'@type': 'acl:Authorization',
|
|
195
|
+
'acl:agent': { '@id': './#me' },
|
|
196
|
+
'acl:accessTo': { '@id': './' },
|
|
197
|
+
'acl:mode': [{ '@id': 'acl:Read' }]
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const auths = await parseAcl(JSON.stringify(acl), 'https://alice.example/.acl');
|
|
201
|
+
|
|
202
|
+
assert.strictEqual(auths.length, 1);
|
|
203
|
+
assert.ok(auths[0].agents.includes('https://alice.example/#me'),
|
|
204
|
+
`Expected agents to include 'https://alice.example/#me', got: ${auths[0].agents}`);
|
|
205
|
+
});
|
|
206
|
+
|
|
190
207
|
it('should keep absolute URLs unchanged', async () => {
|
|
191
208
|
const acl = {
|
|
192
209
|
'@context': { 'acl': 'http://www.w3.org/ns/auth/acl#' },
|
package/fonstr-data/index.html
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>fonstr - Your Nostr Relay</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
-
body {
|
|
10
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
11
|
-
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
12
|
-
color: white;
|
|
13
|
-
min-height: 100vh;
|
|
14
|
-
display: flex;
|
|
15
|
-
align-items: center;
|
|
16
|
-
justify-content: center;
|
|
17
|
-
padding: 2rem;
|
|
18
|
-
}
|
|
19
|
-
.container {
|
|
20
|
-
text-align: center;
|
|
21
|
-
max-width: 600px;
|
|
22
|
-
}
|
|
23
|
-
h1 {
|
|
24
|
-
font-size: 3rem;
|
|
25
|
-
margin-bottom: 0.5rem;
|
|
26
|
-
}
|
|
27
|
-
.emoji {
|
|
28
|
-
font-size: 4rem;
|
|
29
|
-
margin-bottom: 1rem;
|
|
30
|
-
}
|
|
31
|
-
p {
|
|
32
|
-
font-size: 1.25rem;
|
|
33
|
-
opacity: 0.95;
|
|
34
|
-
margin-bottom: 2rem;
|
|
35
|
-
line-height: 1.6;
|
|
36
|
-
}
|
|
37
|
-
.relay-info {
|
|
38
|
-
background: rgba(255, 255, 255, 0.2);
|
|
39
|
-
backdrop-filter: blur(10px);
|
|
40
|
-
border-radius: 1rem;
|
|
41
|
-
padding: 2rem;
|
|
42
|
-
margin: 2rem 0;
|
|
43
|
-
}
|
|
44
|
-
code {
|
|
45
|
-
background: rgba(255, 255, 255, 0.3);
|
|
46
|
-
padding: 0.5rem 1rem;
|
|
47
|
-
border-radius: 0.5rem;
|
|
48
|
-
font-size: 1.1rem;
|
|
49
|
-
display: inline-block;
|
|
50
|
-
margin: 0.5rem 0;
|
|
51
|
-
font-family: 'Monaco', 'Menlo', monospace;
|
|
52
|
-
}
|
|
53
|
-
.stats {
|
|
54
|
-
display: grid;
|
|
55
|
-
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
56
|
-
gap: 1rem;
|
|
57
|
-
margin-top: 2rem;
|
|
58
|
-
}
|
|
59
|
-
.stat {
|
|
60
|
-
background: rgba(255, 255, 255, 0.15);
|
|
61
|
-
padding: 1rem;
|
|
62
|
-
border-radius: 0.5rem;
|
|
63
|
-
}
|
|
64
|
-
.stat-label {
|
|
65
|
-
font-size: 0.9rem;
|
|
66
|
-
opacity: 0.8;
|
|
67
|
-
}
|
|
68
|
-
.stat-value {
|
|
69
|
-
font-size: 1.5rem;
|
|
70
|
-
font-weight: 700;
|
|
71
|
-
margin-top: 0.25rem;
|
|
72
|
-
}
|
|
73
|
-
a {
|
|
74
|
-
color: white;
|
|
75
|
-
text-decoration: none;
|
|
76
|
-
border-bottom: 2px solid rgba(255, 255, 255, 0.5);
|
|
77
|
-
transition: border-color 0.2s;
|
|
78
|
-
}
|
|
79
|
-
a:hover {
|
|
80
|
-
border-color: white;
|
|
81
|
-
}
|
|
82
|
-
</style>
|
|
83
|
-
</head>
|
|
84
|
-
<body>
|
|
85
|
-
<div class="container">
|
|
86
|
-
<div class="emoji">⚡</div>
|
|
87
|
-
<h1>fonstr</h1>
|
|
88
|
-
<p>Your Nostr relay is running!</p>
|
|
89
|
-
|
|
90
|
-
<div class="relay-info">
|
|
91
|
-
<p style="font-size: 1rem; margin-bottom: 1rem; opacity: 0.9;">Connect to your relay:</p>
|
|
92
|
-
<code>ws://localhost:4444/relay</code>
|
|
93
|
-
|
|
94
|
-
<div class="stats">
|
|
95
|
-
<div class="stat">
|
|
96
|
-
<div class="stat-label">Status</div>
|
|
97
|
-
<div class="stat-value">✓ Online</div>
|
|
98
|
-
</div>
|
|
99
|
-
<div class="stat">
|
|
100
|
-
<div class="stat-label">Protocol</div>
|
|
101
|
-
<div class="stat-value">NIP-01</div>
|
|
102
|
-
</div>
|
|
103
|
-
<div class="stat">
|
|
104
|
-
<div class="stat-label">Port</div>
|
|
105
|
-
<div class="stat-value">4444</div>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
<p style="font-size: 1rem;">
|
|
111
|
-
Add this relay to your favorite Nostr client and start using it!<br>
|
|
112
|
-
<a href="https://fonstr.com" target="_blank">Learn more about fonstr</a>
|
|
113
|
-
</p>
|
|
114
|
-
|
|
115
|
-
<p style="font-size: 0.9rem; opacity: 0.7; margin-top: 2rem;">
|
|
116
|
-
Replace this page by editing <code style="font-size: 0.8rem;">index.html</code> in your data directory
|
|
117
|
-
</p>
|
|
118
|
-
</div>
|
|
119
|
-
</body>
|
|
120
|
-
</html>
|