javascript-solid-server 0.0.27 → 0.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javascript-solid-server",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "A minimal, fast Solid server",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -138,10 +138,11 @@ export async function createPodStructure(name, webId, baseUrl) {
138
138
  await storage.createContainer(`${podPath}public/`);
139
139
  await storage.createContainer(`${podPath}private/`);
140
140
  await storage.createContainer(`${podPath}settings/`);
141
+ await storage.createContainer(`${podPath}profile/`);
141
142
 
142
- // Generate and write WebID profile as index.html at pod root
143
+ // Generate and write WebID profile at /profile/card (standard Solid location)
143
144
  const profileHtml = generateProfile({ webId, name, podUri, issuer });
144
- await storage.write(`${podPath}index.html`, profileHtml);
145
+ await storage.write(`${podPath}profile/card`, profileHtml);
145
146
 
146
147
  // Generate and write preferences
147
148
  const prefs = generatePreferences({ webId, podUri });
@@ -175,6 +176,11 @@ export async function createPodStructure(name, webId, baseUrl) {
175
176
  const publicAcl = generatePublicFolderAcl(`${podUri}public/`, webId);
176
177
  await storage.write(`${podPath}public/.acl`, serializeAcl(publicAcl));
177
178
 
179
+ // Profile folder: owner full, public read (with inheritance)
180
+ // Profile documents must be publicly readable for WebID verification
181
+ const profileAcl = generatePublicFolderAcl(`${podUri}profile/`, webId);
182
+ await storage.write(`${podPath}profile/.acl`, serializeAcl(profileAcl));
183
+
178
184
  return { podPath, podUri };
179
185
  }
180
186
 
@@ -224,22 +230,22 @@ export async function handleCreatePod(request, reply) {
224
230
  }
225
231
 
226
232
  // Build URIs
227
- // WebID is at pod root: /alice/#me (path mode) or alice.example.com/#me (subdomain mode)
233
+ // WebID follows standard Solid convention: /alice/profile/card#me
228
234
  const subdomainsEnabled = request.subdomainsEnabled;
229
235
  const baseDomain = request.baseDomain;
230
236
 
231
237
  let baseUri, podUri, webId;
232
238
  if (subdomainsEnabled && baseDomain) {
233
- // Subdomain mode: alice.example.com/
239
+ // Subdomain mode: alice.example.com/profile/card#me
234
240
  const podHost = `${name}.${baseDomain}`;
235
241
  baseUri = `${request.protocol}://${baseDomain}`;
236
242
  podUri = `${request.protocol}://${podHost}/`;
237
- webId = `${podUri}#me`;
243
+ webId = `${podUri}profile/card#me`;
238
244
  } else {
239
- // Path mode: example.com/alice/
245
+ // Path mode: example.com/alice/profile/card#me
240
246
  baseUri = `${request.protocol}://${request.hostname}`;
241
247
  podUri = `${baseUri}${podPath}`;
242
- webId = `${podUri}#me`;
248
+ webId = `${podUri}profile/card#me`;
243
249
  }
244
250
 
245
251
  // Issuer needs trailing slash for CTH compatibility
@@ -495,20 +495,53 @@ export async function handlePatch(request, reply) {
495
495
 
496
496
  // Read existing content or start with empty JSON-LD document
497
497
  let document;
498
+ let htmlWrapper = null; // Track HTML wrapper for data island re-embedding
499
+
498
500
  if (resourceExists) {
499
501
  const existingContent = await storage.read(storagePath);
500
502
  if (existingContent === null) {
501
503
  return reply.code(500).send({ error: 'Read error' });
502
504
  }
503
505
 
504
- // Parse existing document as JSON-LD
505
- try {
506
- document = JSON.parse(existingContent.toString());
507
- } catch (e) {
508
- return reply.code(409).send({
509
- error: 'Conflict',
510
- message: 'Resource is not valid JSON-LD and cannot be patched'
511
- });
506
+ const contentStr = existingContent.toString();
507
+
508
+ // Check if this is HTML with embedded JSON-LD data island
509
+ if (contentStr.trimStart().startsWith('<!DOCTYPE') || contentStr.trimStart().startsWith('<html')) {
510
+ // Extract JSON-LD from <script type="application/ld+json"> tag
511
+ const jsonLdMatch = contentStr.match(/<script\s+type=["']application\/ld\+json["']\s*>([\s\S]*?)<\/script>/i);
512
+
513
+ if (!jsonLdMatch) {
514
+ return reply.code(409).send({
515
+ error: 'Conflict',
516
+ message: 'HTML document does not contain a JSON-LD data island'
517
+ });
518
+ }
519
+
520
+ try {
521
+ document = JSON.parse(jsonLdMatch[1]);
522
+ // Save the HTML parts for re-embedding after patch
523
+ const jsonLdStart = contentStr.indexOf(jsonLdMatch[0]) + jsonLdMatch[0].indexOf('>') + 1;
524
+ const jsonLdEnd = jsonLdStart + jsonLdMatch[1].length;
525
+ htmlWrapper = {
526
+ before: contentStr.substring(0, jsonLdStart),
527
+ after: contentStr.substring(jsonLdEnd)
528
+ };
529
+ } catch (e) {
530
+ return reply.code(409).send({
531
+ error: 'Conflict',
532
+ message: 'HTML data island contains invalid JSON-LD'
533
+ });
534
+ }
535
+ } else {
536
+ // Parse as plain JSON-LD
537
+ try {
538
+ document = JSON.parse(contentStr);
539
+ } catch (e) {
540
+ return reply.code(409).send({
541
+ error: 'Conflict',
542
+ message: 'Resource is not valid JSON-LD and cannot be patched'
543
+ });
544
+ }
512
545
  }
513
546
  } else {
514
547
  // Create empty JSON-LD document for new resource
@@ -568,7 +601,14 @@ export async function handlePatch(request, reply) {
568
601
  }
569
602
 
570
603
  // Write updated document
571
- const updatedContent = JSON.stringify(updatedDocument, null, 2);
604
+ let updatedContent;
605
+ if (htmlWrapper) {
606
+ // Re-embed JSON-LD into HTML wrapper
607
+ const jsonLdStr = JSON.stringify(updatedDocument, null, 2);
608
+ updatedContent = htmlWrapper.before + '\n' + jsonLdStr + '\n ' + htmlWrapper.after;
609
+ } else {
610
+ updatedContent = JSON.stringify(updatedDocument, null, 2);
611
+ }
572
612
  const success = await storage.write(storagePath, Buffer.from(updatedContent));
573
613
 
574
614
  if (!success) {
@@ -354,10 +354,10 @@ export async function handleRegisterPost(request, reply, issuer) {
354
354
  }
355
355
 
356
356
  try {
357
- // Build URLs
357
+ // Build URLs - WebID follows standard Solid convention: /profile/card#me
358
358
  const baseUrl = issuer.endsWith('/') ? issuer.slice(0, -1) : issuer;
359
359
  const podUri = `${baseUrl}/${username}/`;
360
- const webId = `${podUri}#me`;
360
+ const webId = `${podUri}profile/card#me`;
361
361
 
362
362
  // Check if pod already exists
363
363
  const podPath = `${username}/`;
@@ -56,17 +56,17 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
56
56
  }
57
57
 
58
58
  /**
59
- * Generate HTML profile with embedded JSON-LD
59
+ * Generate HTML profile with embedded JSON-LD data island
60
+ * The page uses mashlib + solidos-lite to render the profile from the data island
60
61
  * @param {object} options
61
62
  * @param {string} options.webId - Full WebID URI
62
63
  * @param {string} options.name - Display name
63
64
  * @param {string} options.podUri - Pod root URI
64
65
  * @param {string} options.issuer - OIDC issuer URI
65
- * @returns {string} HTML document with JSON-LD
66
+ * @returns {string} HTML document with JSON-LD data island
66
67
  */
67
68
  export function generateProfile({ webId, name, podUri, issuer }) {
68
69
  const jsonLd = generateProfileJsonLd({ webId, name, podUri, issuer });
69
- const pod = podUri.endsWith('/') ? podUri : podUri + '/';
70
70
 
71
71
  return `<!DOCTYPE html>
72
72
  <html lang="en">
@@ -74,30 +74,52 @@ export function generateProfile({ webId, name, podUri, issuer }) {
74
74
  <meta charset="utf-8">
75
75
  <meta name="viewport" content="width=device-width, initial-scale=1">
76
76
  <title>${escapeHtml(name)}'s Profile</title>
77
+ <link rel="stylesheet" href="https://javascriptsolidserver.github.io/mashlib-jss/dist/mash.css">
77
78
  <script type="application/ld+json">
78
79
  ${JSON.stringify(jsonLd, null, 2)}
79
80
  </script>
80
81
  <style>
81
- body { font-family: system-ui, sans-serif; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
82
- h1 { color: #333; }
83
- .card { background: #f5f5f5; padding: 1.5rem; border-radius: 8px; }
84
- dt { font-weight: bold; margin-top: 1rem; }
85
- dd { margin-left: 0; color: #666; }
86
- a { color: #7c4dff; }
82
+ body { margin: 0; font-family: system-ui, sans-serif; }
83
+ .loading { padding: 2rem; text-align: center; color: #666; }
87
84
  </style>
88
85
  </head>
89
86
  <body>
90
- <div class="card">
91
- <h1>${escapeHtml(name)}</h1>
92
- <dl>
93
- <dt>WebID</dt>
94
- <dd><a href="${escapeHtml(webId)}">${escapeHtml(webId)}</a></dd>
95
- <dt>Storage</dt>
96
- <dd><a href="${escapeHtml(pod)}">${escapeHtml(pod)}</a></dd>
97
- <dt>Inbox</dt>
98
- <dd><a href="${escapeHtml(pod)}inbox/">${escapeHtml(pod)}inbox/</a></dd>
99
- </dl>
87
+ <div class="TabulatorOutline" id="DummyUUID" role="main">
88
+ <table id="outline"></table>
89
+ <div id="GlobalDashboard"></div>
100
90
  </div>
91
+ <div class="loading" id="loading">Loading profile...</div>
92
+
93
+ <script src="https://javascriptsolidserver.github.io/mashlib-jss/dist/mashlib.min.js"></script>
94
+ <script src="https://cdn.jsdelivr.net/npm/solidos-lite/solidos-lite.js"></script>
95
+ <script>
96
+ document.addEventListener('DOMContentLoaded', function() {
97
+ const loadingEl = document.getElementById('loading');
98
+
99
+ // Initialize solidos-lite to handle data islands
100
+ const success = SolidOSLite.init({ verbose: false });
101
+ if (!success) {
102
+ loadingEl.textContent = 'Failed to initialize. Please try refreshing.';
103
+ return;
104
+ }
105
+
106
+ // Parse data islands into the RDF store
107
+ SolidOSLite.parseAllIslands();
108
+
109
+ // Mark this document as already fetched
110
+ const pageBase = window.location.href.split('?')[0].split('#')[0];
111
+ const fetcher = SolidLogic.store.fetcher;
112
+ fetcher.requested[pageBase] = 'done';
113
+ fetcher.requested[pageBase.replace(/\\/$/, '')] = 'done';
114
+
115
+ // Navigate to #me
116
+ const subject = $rdf.sym(pageBase + '#me');
117
+ const outliner = panes.getOutliner(document);
118
+ outliner.GotoSubject(subject, true, undefined, true, undefined);
119
+
120
+ loadingEl.style.display = 'none';
121
+ });
122
+ </script>
101
123
  </body>
102
124
  </html>`;
103
125
  }