@@ -24,6 +24,63 @@ interface UseSolidStoragesResult {
2424 error : Error | null ;
2525}
2626
27+ /**
28+ * Resolves and normalizes a storage URL, handling relative URLs, undefined prefixes, etc.
29+ * @param {string } storageUrl - The storage URL to resolve
30+ * @param {string } baseUrl - The base URL to resolve relative URLs against
31+ * @returns {string | null } - The resolved absolute URL, or null if invalid
32+ */
33+ function resolveStorageUrl ( storageUrl : string , baseUrl : string ) : string | null {
34+ // Handle the case where n3 parser didn't resolve the prefix correctly
35+ // "pre:" prefix resolves to "</.>" which should be the root "/"
36+ if ( storageUrl === 'undefined/' || storageUrl . includes ( 'undefined' ) ) {
37+ const baseUrlObj = new URL ( baseUrl ) ;
38+ return `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
39+ }
40+
41+ // Handle relative URLs that end with "/." or are just "/"
42+ if ( storageUrl . endsWith ( '/.' ) || storageUrl . endsWith ( '/./' ) ||
43+ storageUrl === './' || storageUrl === '/' ||
44+ ( storageUrl . startsWith ( '/' ) && ! storageUrl . startsWith ( 'http' ) ) ) {
45+ const baseUrlObj = new URL ( baseUrl ) ;
46+ if ( storageUrl . endsWith ( '/.' ) || storageUrl === './' || storageUrl === '/' ) {
47+ return `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
48+ } else {
49+ // Handle paths like "/path" -> "https://domain.com/path"
50+ try {
51+ return new URL ( storageUrl , baseUrl ) . href ;
52+ } catch ( e ) {
53+ // If URL construction fails, try manual resolution
54+ if ( storageUrl . startsWith ( '/' ) ) {
55+ return `${ baseUrlObj . protocol } //${ baseUrlObj . host } ${ storageUrl } ` ;
56+ }
57+ }
58+ }
59+ }
60+
61+ // Also check if it's a relative URL without protocol
62+ if ( ! storageUrl . startsWith ( 'http://' ) && ! storageUrl . startsWith ( 'https://' ) ) {
63+ try {
64+ const baseUrlObj = new URL ( baseUrl ) ;
65+ if ( storageUrl . startsWith ( '/' ) ) {
66+ return `${ baseUrlObj . protocol } //${ baseUrlObj . host } ${ storageUrl } ` ;
67+ } else {
68+ return new URL ( storageUrl , baseUrl ) . href ;
69+ }
70+ } catch ( e ) {
71+ // Silent error handling
72+ return null ;
73+ }
74+ }
75+
76+ // Final validation - ensure it's a valid absolute URL
77+ if ( storageUrl && storageUrl . startsWith ( 'http' ) ) {
78+ return storageUrl ;
79+ }
80+
81+ return null ;
82+ }
83+
2784/**
2885 * Discovers storage by traversing up the folder hierarchy from the WebID
2986 * Based on: https://github.com/SolidLabResearch/Bashlib/blob/80de25cbb4b3ed057f95e25bc057f1be9b00cef3/src/utils/util.ts#L73-L104
@@ -238,16 +295,19 @@ export function useSolidStorages(): UseSolidStoragesResult {
238295 // Parse the RDF content
239296 const store = new Store ( ) ;
240297
298+ // Extract base URL for resolving relative URIs like <#me>
299+ const baseUrl = webId . split ( '#' ) [ 0 ] ;
300+
241301 if ( contentType . includes ( 'text/turtle' ) || contentType . includes ( 'application/turtle' ) ||
242302 contentType . includes ( 'text/n3' ) || contentType . includes ( 'application/n3' ) ) {
243- const parser = new Parser ( ) ;
303+ const parser = new Parser ( { baseIRI : baseUrl } ) ;
244304 const quads = parser . parse ( content ) ;
245305 store . addQuads ( quads ) ;
246306 } else if ( contentType . includes ( 'application/ld+json' ) ) {
247307 // For JSON-LD, we'd need a different parser, but for now let's try to extract from Turtle
248308 // Most Solid servers return Turtle even if JSON-LD is requested
249309 try {
250- const parser = new Parser ( ) ;
310+ const parser = new Parser ( { baseIRI : baseUrl } ) ;
251311 const quads = parser . parse ( content ) ;
252312 store . addQuads ( quads ) ;
253313 } catch ( e ) {
@@ -256,7 +316,6 @@ export function useSolidStorages(): UseSolidStoragesResult {
256316 }
257317
258318 // Find the main subject - try different variants
259- const baseUrl = webId . split ( '#' ) [ 0 ] ;
260319 const subjectVariants = [
261320 new NamedNode ( webId ) ,
262321 new NamedNode ( baseUrl + '#me' ) ,
@@ -313,26 +372,9 @@ export function useSolidStorages(): UseSolidStoragesResult {
313372 const pimStorageQuads = store . getQuads ( mainSubject , new NamedNode ( PIM_STORAGE ) , null , null ) ;
314373 pimStorageQuads . forEach ( quad => {
315374 if ( quad . object instanceof NamedNode ) {
316- let storageUrl = quad . object . value ;
317- const originalValue = storageUrl ;
318-
319- // Handle the case where n3 parser didn't resolve the prefix correctly
320- // "pre:" prefix resolves to "</.>" which should be the root "/"
321- if ( storageUrl === 'undefined/' || storageUrl . includes ( 'undefined' ) ) {
322- const baseUrlObj = new URL ( baseUrl ) ;
323- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
324- } else if ( ! storageUrl . startsWith ( 'http://' ) && ! storageUrl . startsWith ( 'https://' ) ) {
325- // Resolve relative URLs
326- const baseUrlObj = new URL ( baseUrl ) ;
327- if ( storageUrl === './' || storageUrl === '/' || storageUrl . endsWith ( '/.' ) ) {
328- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
329- } else {
330- storageUrl = new URL ( storageUrl , baseUrl ) . href ;
331- }
332- }
333-
334- if ( storageUrl && storageUrl . startsWith ( 'http' ) && ! storageUrls . includes ( storageUrl ) ) {
335- storageUrls . push ( storageUrl ) ;
375+ const resolvedUrl = resolveStorageUrl ( quad . object . value , baseUrl ) ;
376+ if ( resolvedUrl && ! storageUrls . includes ( resolvedUrl ) ) {
377+ storageUrls . push ( resolvedUrl ) ;
336378 }
337379 }
338380 } ) ;
@@ -352,68 +394,15 @@ export function useSolidStorages(): UseSolidStoragesResult {
352394 const allPimStorageQuads = store . getQuads ( null , new NamedNode ( PIM_STORAGE ) , null , null ) ;
353395 allPimStorageQuads . forEach ( quad => {
354396 if ( quad . object instanceof NamedNode ) {
355- let storageUrl = quad . object . value ;
356- const originalValue = storageUrl ;
357-
358- // Handle the case where n3 parser didn't resolve the prefix correctly
359- // "pre:" prefix resolves to "</.>" which should be the root "/"
360- // If we see "undefined/" it means the prefix wasn't resolved
361- if ( storageUrl === 'undefined/' || storageUrl . includes ( 'undefined' ) ) {
362- // The prefix "pre:" resolves to "</.>" which is the root
363- const baseUrlObj = new URL ( baseUrl ) ;
364- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
365- }
366- // Handle relative URLs that end with "/." or are just "/"
367- else if ( storageUrl . endsWith ( '/.' ) || storageUrl . endsWith ( '/./' ) ||
368- storageUrl === './' || storageUrl === '/' ||
369- ( storageUrl . startsWith ( '/' ) && ! storageUrl . startsWith ( 'http' ) ) ) {
370- // Resolve relative URL to absolute
371- const baseUrlObj = new URL ( baseUrl ) ;
372- if ( storageUrl . endsWith ( '/.' ) || storageUrl === './' || storageUrl === '/' ) {
373- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } /` ;
374- } else {
375- // Handle paths like "/path" -> "https://domain.com/path"
376- try {
377- storageUrl = new URL ( storageUrl , baseUrl ) . href ;
378- } catch ( e ) {
379- // If URL construction fails, try manual resolution
380- if ( storageUrl . startsWith ( '/' ) ) {
381- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } ${ storageUrl } ` ;
382- }
383- }
384- }
385- }
386- // Also check if it's a relative URL without protocol
387- else if ( ! storageUrl . startsWith ( 'http://' ) && ! storageUrl . startsWith ( 'https://' ) ) {
388- try {
389- const baseUrlObj = new URL ( baseUrl ) ;
390- if ( storageUrl . startsWith ( '/' ) ) {
391- storageUrl = `${ baseUrlObj . protocol } //${ baseUrlObj . host } ${ storageUrl } ` ;
392- } else {
393- storageUrl = new URL ( storageUrl , baseUrl ) . href ;
394- }
395- } catch ( e ) {
396- // Silent error handling
397- }
398- }
399-
400- // Final validation - ensure it's a valid absolute URL
401- if ( storageUrl && storageUrl . startsWith ( 'http' ) ) {
402- if ( ! storageUrls . includes ( storageUrl ) ) {
403- storageUrls . push ( storageUrl ) ;
404- }
397+ const resolvedUrl = resolveStorageUrl ( quad . object . value , baseUrl ) ;
398+ if ( resolvedUrl && ! storageUrls . includes ( resolvedUrl ) ) {
399+ storageUrls . push ( resolvedUrl ) ;
405400 }
406401 } else if ( quad . object instanceof Literal ) {
407402 // Sometimes storage might be a literal, try to resolve it
408- const storageValue = quad . object . value ;
409- if ( storageValue === './' || storageValue === '/' || storageValue . startsWith ( '/' ) ) {
410- const baseUrlObj = new URL ( baseUrl ) ;
411- const resolvedUrl = storageValue === './' || storageValue === '/'
412- ? `${ baseUrlObj . protocol } //${ baseUrlObj . host } /`
413- : new URL ( storageValue , baseUrl ) . href ;
414- if ( ! storageUrls . includes ( resolvedUrl ) ) {
415- storageUrls . push ( resolvedUrl ) ;
416- }
403+ const resolvedUrl = resolveStorageUrl ( quad . object . value , baseUrl ) ;
404+ if ( resolvedUrl && ! storageUrls . includes ( resolvedUrl ) ) {
405+ storageUrls . push ( resolvedUrl ) ;
417406 }
418407 }
419408 } ) ;
0 commit comments