javascript-solid-server 0.0.17 → 0.0.19

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.
@@ -55,7 +55,17 @@
55
55
  "WebFetch(domain:communitysolidserver.github.io)",
56
56
  "WebFetch(domain:solidos.github.io)",
57
57
  "WebFetch(domain:solidcommunity.net)",
58
- "WebFetch(domain:www.npmjs.com)"
58
+ "WebFetch(domain:www.npmjs.com)",
59
+ "Bash(pm2 list:*)",
60
+ "Bash(pm2 logs:*)",
61
+ "Bash(pm2 restart:*)",
62
+ "Bash(timeout 60 npm test:*)",
63
+ "Bash(pm2 stop:*)",
64
+ "Bash(pm2 delete:*)",
65
+ "Bash(pm2 start:*)",
66
+ "Bash(DATA_ROOT=/home/melvin/jss/data pm2 start:*)",
67
+ "Bash(pm2 save:*)",
68
+ "Bash(gh issue create:*)"
59
69
  ]
60
70
  }
61
71
  }
package/bin/jss.js CHANGED
@@ -59,6 +59,9 @@ program
59
59
  try {
60
60
  const config = await loadConfig(options, options.config);
61
61
 
62
+ // Set DATA_ROOT env var so all modules use the same data directory
63
+ process.env.DATA_ROOT = path.resolve(config.root);
64
+
62
65
  if (options.printConfig) {
63
66
  printConfig(config);
64
67
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  import * as storage from '../storage/filesystem.js';
2
- import { getAllHeaders } from '../ldp/headers.js';
2
+ import { getAllHeaders, getNotFoundHeaders } from '../ldp/headers.js';
3
3
  import { generateContainerJsonLd, serializeJsonLd } from '../ldp/container.js';
4
4
  import { isContainer, getContentType, isRdfContentType, getEffectiveUrlPath } from '../utils/url.js';
5
5
  import { parseN3Patch, applyN3Patch, validatePatch } from '../patch/n3-patch.js';
@@ -37,6 +37,10 @@ export async function handleGet(request, reply) {
37
37
  const stats = await storage.stat(storagePath);
38
38
 
39
39
  if (!stats) {
40
+ const origin = request.headers.origin;
41
+ const connegEnabled = request.connegEnabled || false;
42
+ const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
43
+ Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
40
44
  return reply.code(404).send({ error: 'Not Found' });
41
45
  }
42
46
 
@@ -219,6 +223,10 @@ export async function handleHead(request, reply) {
219
223
  const stats = await storage.stat(storagePath);
220
224
 
221
225
  if (!stats) {
226
+ const origin = request.headers.origin;
227
+ const connegEnabled = request.connegEnabled || false;
228
+ const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
229
+ Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
222
230
  return reply.code(404).send();
223
231
  }
224
232
 
@@ -366,6 +374,10 @@ export async function handleDelete(request, reply) {
366
374
  // Check if resource exists and get current ETag
367
375
  const stats = await storage.stat(storagePath);
368
376
  if (!stats) {
377
+ const origin = request.headers.origin;
378
+ const connegEnabled = request.connegEnabled || false;
379
+ const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
380
+ Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
369
381
  return reply.code(404).send({ error: 'Not Found' });
370
382
  }
371
383
 
@@ -442,6 +454,10 @@ export async function handlePatch(request, reply) {
442
454
  // Check if resource exists
443
455
  const stats = await storage.stat(storagePath);
444
456
  if (!stats) {
457
+ const origin = request.headers.origin;
458
+ const connegEnabled = request.connegEnabled || false;
459
+ const headers = getNotFoundHeaders({ resourceUrl, origin, connegEnabled });
460
+ Object.entries(headers).forEach(([k, v]) => reply.header(k, v));
445
461
  return reply.code(404).send({ error: 'Not Found' });
446
462
  }
447
463
 
@@ -118,20 +118,22 @@ export async function handleLogin(request, reply, provider) {
118
118
 
119
119
  request.log.info({ accountId: account.id, uid }, 'Login successful');
120
120
 
121
- // For CTH compatibility, we need to return a response that CTH can handle.
122
- // CTH expects either:
123
- // 1. A redirect it can follow (but Java HttpClient follows to final destination which fails)
124
- // 2. A 200 response with "location" in body (CSS v3+ style)
125
- //
126
- // We use interactionResult to get the redirect URL, then save it and return JSON
127
-
128
- // Save the login result to the interaction for programmatic clients
129
- // This allows the auth endpoint to continue the flow when resumed
121
+ // Detect if this is a browser (wants HTML/redirect) or programmatic client (wants JSON)
122
+ const acceptHeader = request.headers.accept || '';
123
+ const wantsBrowserRedirect = acceptHeader.includes('text/html') && !acceptHeader.includes('application/json');
124
+
125
+ // Save the login result to the interaction
130
126
  interaction.result = result;
131
127
  await interaction.save(interaction.exp - Math.floor(Date.now() / 1000));
132
128
 
133
- // For CTH and programmatic clients: use interactionFinished with hijacked response
134
- // to properly complete the interaction while returning JSON
129
+ // For browsers (mashlib, etc): do a proper HTTP redirect
130
+ if (wantsBrowserRedirect) {
131
+ reply.hijack();
132
+ return provider.interactionFinished(request.raw, reply.raw, result, { mergeWithLastSubmission: false });
133
+ }
134
+
135
+ // For CTH and programmatic clients: return JSON with location
136
+ // CTH expects a 200 response with "location" in body (CSS v3+ style)
135
137
  try {
136
138
  reply.hijack();
137
139
 
@@ -188,7 +190,6 @@ export async function handleLogin(request, reply, provider) {
188
190
  request.log.warn({ err: err.message, errName: err.name, uid }, 'interactionFinished failed, using fallback');
189
191
 
190
192
  // Fallback: return the redirect URL for manual following
191
- // The interaction result is already saved above
192
193
  const redirectTo = `/idp/auth/${uid}`;
193
194
  return reply
194
195
  .code(200)
@@ -108,3 +108,33 @@ export function getAllHeaders({ isContainer = false, etag = null, contentType =
108
108
  ...getCorsHeaders(origin)
109
109
  };
110
110
  }
111
+
112
+ /**
113
+ * Get headers for 404 responses (non-existent resources)
114
+ * These headers tell clients what methods are supported for creating the resource
115
+ * @param {object} options
116
+ * @returns {object}
117
+ */
118
+ export function getNotFoundHeaders({ resourceUrl = null, origin = null, connegEnabled = false }) {
119
+ // Determine if this would be a container based on URL ending with /
120
+ const isContainer = resourceUrl?.endsWith('/') || false;
121
+ const aclUrl = resourceUrl ? getAclUrl(resourceUrl, isContainer) : null;
122
+
123
+ // Get Accept-* headers
124
+ const acceptHeaders = getAcceptHeaders(connegEnabled, isContainer);
125
+
126
+ const headers = {
127
+ ...getCorsHeaders(origin),
128
+ 'Link': aclUrl ? `<${aclUrl}>; rel="acl"` : '',
129
+ 'Accept-Patch': 'text/n3, application/sparql-update',
130
+ 'Accept-Put': acceptHeaders['Accept-Put'] || 'application/ld+json, */*',
131
+ 'Allow': 'GET, HEAD, PUT, PATCH, OPTIONS' + (isContainer ? ', POST' : ''),
132
+ 'Vary': connegEnabled ? 'Accept, Authorization, Origin' : 'Authorization, Origin'
133
+ };
134
+
135
+ if (isContainer && acceptHeaders['Accept-Post']) {
136
+ headers['Accept-Post'] = acceptHeaders['Accept-Post'];
137
+ }
138
+
139
+ return headers;
140
+ }
package/src/utils/url.js CHANGED
@@ -120,7 +120,13 @@ export function getContentType(filePath) {
120
120
  '.jpeg': 'image/jpeg',
121
121
  '.gif': 'image/gif',
122
122
  '.svg': 'image/svg+xml',
123
- '.pdf': 'application/pdf'
123
+ '.pdf': 'application/pdf',
124
+ '.ttl': 'text/turtle',
125
+ '.n3': 'text/n3',
126
+ '.nt': 'application/n-triples',
127
+ '.rdf': 'application/rdf+xml',
128
+ '.nq': 'application/n-quads',
129
+ '.trig': 'application/trig'
124
130
  };
125
131
  return types[ext] || 'application/octet-stream';
126
132
  }
@@ -131,5 +137,15 @@ export function getContentType(filePath) {
131
137
  * @returns {boolean}
132
138
  */
133
139
  export function isRdfContentType(contentType) {
134
- return contentType === 'application/ld+json' || contentType === 'application/json';
140
+ const rdfTypes = [
141
+ 'application/ld+json',
142
+ 'application/json',
143
+ 'text/turtle',
144
+ 'text/n3',
145
+ 'application/n-triples',
146
+ 'application/rdf+xml',
147
+ 'application/n-quads',
148
+ 'application/trig'
149
+ ];
150
+ return rdfTypes.includes(contentType);
135
151
  }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "95d4d470-1e8c-4b17-bcff-a62b8a54813b",
3
+ "email": "credtest@example.com",
4
+ "passwordHash": "$2b$10$dfsBq19GkIsL/qaer5LlxeSD8cMFJS4U8o7Wt06f/u29Cdhc8eJ3i",
5
+ "webId": "http://localhost:3101/credtest/#me",
6
+ "podName": "credtest",
7
+ "createdAt": "2025-12-27T13:57:26.941Z",
8
+ "lastLogin": "2025-12-27T13:57:27.182Z"
9
+ }
@@ -1,3 +1,3 @@
1
1
  {
2
- "credtest@example.com": "ea61c611-2dda-41b8-8787-c6a22c5f33cc"
2
+ "credtest@example.com": "95d4d470-1e8c-4b17-bcff-a62b8a54813b"
3
3
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "http://localhost:3101/credtest/#me": "ea61c611-2dda-41b8-8787-c6a22c5f33cc"
2
+ "http://localhost:3101/credtest/#me": "95d4d470-1e8c-4b17-bcff-a62b8a54813b"
3
3
  }
@@ -3,20 +3,20 @@
3
3
  "keys": [
4
4
  {
5
5
  "kty": "EC",
6
- "x": "hn3QE_mBUEFiANsvmP5MpvQWUCvTxfhBUYsJYbtCG_g",
7
- "y": "0F9jdfVkLit5_xYsHsCstuMsRIa5R6GdciY8ZF1zcgs",
6
+ "x": "_0WHR5nff4EnQiPYLZEuPKNmV7qWxizbdY6NeepX4gc",
7
+ "y": "JowkolFdgQSs4GZ7nGN5ljMII8QAxHDYv2Qp4x5eHgo",
8
8
  "crv": "P-256",
9
- "d": "ippBE99bkrgDX1EQa3awsciu035kbhukikfYwZYh1Jc",
10
- "kid": "267aed32-f9f4-4c72-9925-3e025b775bca",
9
+ "d": "zvFCMQvrEJwSTkfloHws3EFQRA0o5abR22so5LcCTao",
10
+ "kid": "ed8976e4-f907-4fb4-82ff-846a62807dd7",
11
11
  "use": "sig",
12
12
  "alg": "ES256",
13
- "iat": 1766841163
13
+ "iat": 1766843846
14
14
  }
15
15
  ]
16
16
  },
17
17
  "cookieKeys": [
18
- "Zf-WDQBZkGGOED37Z3NzHsO9Jy1L7AEgjIDfHbFbyp4",
19
- "8MTMciOb4PQqVcG2SrwmPSAYFrhW8eS46WB7gDqK0CQ"
18
+ "oQ-IdseWVLpOiwPmpYeY1ShFHI3lLHRiW6NsYeNBpP0",
19
+ "xrKoXpxC4O7wOZ65jNr4Kw7DnFYdkyK4pw2HOJE4D10"
20
20
  ],
21
- "createdAt": "2025-12-27T13:12:43.907Z"
21
+ "createdAt": "2025-12-27T13:57:26.882Z"
22
22
  }
@@ -1,9 +0,0 @@
1
- {
2
- "id": "ea61c611-2dda-41b8-8787-c6a22c5f33cc",
3
- "email": "credtest@example.com",
4
- "passwordHash": "$2b$10$EVWVKsbQ4A6DdswLEK/0ZOclDrBHBo/9GbWeP1s5uvxy4jWjTnY0m",
5
- "webId": "http://localhost:3101/credtest/#me",
6
- "podName": "credtest",
7
- "createdAt": "2025-12-27T13:12:43.961Z",
8
- "lastLogin": "2025-12-27T13:12:44.207Z"
9
- }