javascript-solid-server 0.0.87 → 0.0.88
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/README.md +3 -1
- package/bin/jss.js +1 -0
- package/package.json +1 -1
- package/src/auth/did-nostr.js +33 -6
- package/src/config.js +9 -0
- package/src/server.js +16 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# JavaScript Solid Server
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/javascript-solid-server)
|
|
4
|
+
|
|
3
5
|
A minimal, fast, JSON-LD native Solid server.
|
|
4
6
|
|
|
5
7
|
**[Documentation](https://javascriptsolidserver.github.io/docs/)** | **[GitHub](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer)**
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
|
-
### Implemented
|
|
11
|
+
### Implemented
|
|
10
12
|
|
|
11
13
|
- **Live Reload** - Auto-refresh browser on file changes (`--live-reload`)
|
|
12
14
|
- **Read-Only Mode** - Disable write operations for static hosting (`--read-only`)
|
package/bin/jss.js
CHANGED
|
@@ -80,6 +80,7 @@ program
|
|
|
80
80
|
.option('--read-only', 'Disable PUT/DELETE/PATCH methods (read-only mode)')
|
|
81
81
|
.option('--live-reload', 'Inject live reload script into HTML (auto-refresh on changes)')
|
|
82
82
|
.option('-q, --quiet', 'Suppress log output')
|
|
83
|
+
.option('--log-level <level>', 'Log level: error, warn, info, debug (default: info)')
|
|
83
84
|
.option('--print-config', 'Print configuration and exit')
|
|
84
85
|
.action(async (options) => {
|
|
85
86
|
try {
|
package/package.json
CHANGED
package/src/auth/did-nostr.js
CHANGED
|
@@ -13,6 +13,28 @@ const DEFAULT_DID_RESOLVER = 'https://nostr.social/.well-known/did/nostr';
|
|
|
13
13
|
// Cache for resolved DIDs (pubkey -> webId or null)
|
|
14
14
|
const cache = new Map();
|
|
15
15
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
16
|
+
const FAILURE_CACHE_TTL = 60 * 1000; // 1 minute for failed lookups
|
|
17
|
+
|
|
18
|
+
// Rate-limit repeated error logs (key -> { count, lastLogged })
|
|
19
|
+
const errorLogTracker = new Map();
|
|
20
|
+
const ERROR_LOG_INTERVAL = 60_000;
|
|
21
|
+
|
|
22
|
+
function rateLimitedError(key, message) {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const entry = errorLogTracker.get(key);
|
|
25
|
+
if (entry && now - entry.lastLogged < ERROR_LOG_INTERVAL) {
|
|
26
|
+
entry.count++;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// Clean up stale entries while we're here
|
|
30
|
+
for (const [k, v] of errorLogTracker) {
|
|
31
|
+
if (now - v.lastLogged > ERROR_LOG_INTERVAL) errorLogTracker.delete(k);
|
|
32
|
+
}
|
|
33
|
+
const suppressed = entry ? entry.count : 0;
|
|
34
|
+
const suffix = suppressed > 0 ? ` (${suppressed} similar suppressed)` : '';
|
|
35
|
+
console.error(`${message}${suffix}`);
|
|
36
|
+
errorLogTracker.set(key, { count: 0, lastLogged: now });
|
|
37
|
+
}
|
|
16
38
|
|
|
17
39
|
/**
|
|
18
40
|
* Fetch with timeout
|
|
@@ -41,11 +63,15 @@ export async function resolveDidNostrToWebId(pubkey, resolverUrl = DEFAULT_DID_R
|
|
|
41
63
|
return null;
|
|
42
64
|
}
|
|
43
65
|
|
|
44
|
-
// Check cache
|
|
66
|
+
// Check cache (lazy eviction of expired entries)
|
|
45
67
|
const cacheKey = pubkey.toLowerCase();
|
|
46
68
|
const cached = cache.get(cacheKey);
|
|
47
|
-
if (cached
|
|
48
|
-
|
|
69
|
+
if (cached) {
|
|
70
|
+
const ttl = cached.failureTtl ? FAILURE_CACHE_TTL : CACHE_TTL;
|
|
71
|
+
if (Date.now() - cached.timestamp < ttl) {
|
|
72
|
+
return cached.webId;
|
|
73
|
+
}
|
|
74
|
+
cache.delete(cacheKey);
|
|
49
75
|
}
|
|
50
76
|
|
|
51
77
|
try {
|
|
@@ -93,8 +119,9 @@ export async function resolveDidNostrToWebId(pubkey, resolverUrl = DEFAULT_DID_R
|
|
|
93
119
|
return null;
|
|
94
120
|
|
|
95
121
|
} catch (err) {
|
|
96
|
-
//
|
|
97
|
-
|
|
122
|
+
// Cache failures with short TTL to avoid hammering a down service
|
|
123
|
+
cache.set(cacheKey, { webId: null, timestamp: Date.now(), failureTtl: true });
|
|
124
|
+
rateLimitedError(`did:${pubkey.substring(0, 8)}`, `DID resolution error for ${pubkey}: ${err.message}`);
|
|
98
125
|
return null;
|
|
99
126
|
}
|
|
100
127
|
}
|
|
@@ -148,7 +175,7 @@ async function verifyWebIdBacklink(webId, pubkey) {
|
|
|
148
175
|
return false;
|
|
149
176
|
|
|
150
177
|
} catch (err) {
|
|
151
|
-
|
|
178
|
+
rateLimitedError(`backlink:${webId}`, `WebID backlink verification error for ${webId}: ${err.message}`);
|
|
152
179
|
return false;
|
|
153
180
|
}
|
|
154
181
|
}
|
package/src/config.js
CHANGED
|
@@ -85,6 +85,7 @@ export const defaults = {
|
|
|
85
85
|
// Logging
|
|
86
86
|
logger: true,
|
|
87
87
|
quiet: false,
|
|
88
|
+
logLevel: 'info',
|
|
88
89
|
|
|
89
90
|
// Paths
|
|
90
91
|
configPath: './.jss',
|
|
@@ -103,6 +104,7 @@ const envMap = {
|
|
|
103
104
|
JSS_CONNEG: 'conneg',
|
|
104
105
|
JSS_NOTIFICATIONS: 'notifications',
|
|
105
106
|
JSS_QUIET: 'quiet',
|
|
107
|
+
JSS_LOG_LEVEL: 'logLevel',
|
|
106
108
|
JSS_CONFIG_PATH: 'configPath',
|
|
107
109
|
JSS_IDP: 'idp',
|
|
108
110
|
JSS_IDP_ISSUER: 'idpIssuer',
|
|
@@ -228,6 +230,13 @@ export async function loadConfig(cliOptions = {}, configFile = null) {
|
|
|
228
230
|
config.logger = false;
|
|
229
231
|
}
|
|
230
232
|
|
|
233
|
+
// Validate log level
|
|
234
|
+
const validLevels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace'];
|
|
235
|
+
if (!validLevels.includes(config.logLevel)) {
|
|
236
|
+
console.warn(`Invalid log level '${config.logLevel}', falling back to 'info'. Valid levels: ${validLevels.join(', ')}`);
|
|
237
|
+
config.logLevel = 'info';
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
// Mashlib requires content negotiation for Turtle support
|
|
232
241
|
if (config.mashlib || config.mashlibCdn) {
|
|
233
242
|
config.conneg = true;
|
package/src/server.js
CHANGED
|
@@ -89,8 +89,10 @@ export function createServer(options = {}) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// Fastify options
|
|
92
|
+
const loggerEnabled = options.logger ?? true;
|
|
92
93
|
const fastifyOptions = {
|
|
93
|
-
logger: options.
|
|
94
|
+
logger: loggerEnabled ? { level: options.logLevel || 'info' } : false,
|
|
95
|
+
disableRequestLogging: true,
|
|
94
96
|
trustProxy: true,
|
|
95
97
|
// Handle raw body for non-JSON content
|
|
96
98
|
bodyLimit: 10 * 1024 * 1024 // 10MB
|
|
@@ -169,6 +171,19 @@ export function createServer(options = {}) {
|
|
|
169
171
|
}
|
|
170
172
|
});
|
|
171
173
|
|
|
174
|
+
// Unified access log — one line per request
|
|
175
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
176
|
+
if (!request.log.isLevelEnabled('info')) return;
|
|
177
|
+
request.log.info({
|
|
178
|
+
req: { method: request.method, url: request.url, remoteAddress: request.ip },
|
|
179
|
+
res: { statusCode: reply.statusCode },
|
|
180
|
+
responseTime: Math.round(reply.elapsedTime * 100) / 100,
|
|
181
|
+
userAgent: request.headers['user-agent'] || undefined,
|
|
182
|
+
referrer: request.headers.referer || undefined,
|
|
183
|
+
contentLength: reply.getHeader('content-length') || undefined,
|
|
184
|
+
}, `${request.method} ${request.url} ${reply.statusCode} ${Math.round(reply.elapsedTime)}ms`);
|
|
185
|
+
});
|
|
186
|
+
|
|
172
187
|
// Register WebSocket notifications plugin if enabled (or live reload needs it)
|
|
173
188
|
if (notificationsEnabled || liveReloadEnabled) {
|
|
174
189
|
fastify.register(notificationsPlugin);
|