From b96e4563c539342bf1d498d058833397c019db29 Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Thu, 12 Feb 2026 16:34:56 +0100 Subject: [PATCH 1/4] find iri#issue --- src/authn/SolidAuthnLogic.ts | 3 ++ src/typeIndex/typeIndexLogic.ts | 54 ++++++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/authn/SolidAuthnLogic.ts b/src/authn/SolidAuthnLogic.ts index 6d49a8e..5d7be15 100644 --- a/src/authn/SolidAuthnLogic.ts +++ b/src/authn/SolidAuthnLogic.ts @@ -17,11 +17,14 @@ export class SolidAuthnLogic implements AuthnLogic { currentUser(): NamedNode | null { const app = appContext() if (app.viewingNoAuthPage) { + debug.log(`currentUser: viewingNoAuthPage webId=${app.webId}`) return sym(app.webId) } if (this && this.session && this.session.info && this.session.info.webId && this.session.info.isLoggedIn) { + debug.log(`currentUser: session webId=${this.session.info.webId}`) return sym(this.session.info.webId) } + debug.log('currentUser: no user found, falling back to offlineTestID') return offlineTestID() // null unless testing } diff --git a/src/typeIndex/typeIndexLogic.ts b/src/typeIndex/typeIndexLogic.ts index 178687a..e005557 100644 --- a/src/typeIndex/typeIndexLogic.ts +++ b/src/typeIndex/typeIndexLogic.ts @@ -19,14 +19,22 @@ export function createTypeIndexLogic(store, authn, profileLogic, utilityLogic): if (!user) throw new Error('loadTypeIndexesFor: No user given') const profile = await profileLogic.loadProfile(user) - const suggestion = suggestPublicTypeIndex(user) - let publicTypeIndex + let suggestion: NamedNode | null = null try { - publicTypeIndex = await utilityLogic.followOrCreateLink(user, ns.solid('publicTypeIndex') as NamedNode, suggestion, profile) + suggestion = suggestPublicTypeIndex(user) } catch (err) { - const message = `User ${user} has no pointer in profile to publicTypeIndex file.` + const message = `User ${user} has no usable profile document directory for publicTypeIndex.` debug.warn(message) } + let publicTypeIndex + if (suggestion) { + try { + publicTypeIndex = await utilityLogic.followOrCreateLink(user, ns.solid('publicTypeIndex') as NamedNode, suggestion, profile) + } catch (err) { + const message = `User ${user} has no pointer in profile to publicTypeIndex file.` + debug.warn(message) + } + } const publicScopes = publicTypeIndex ? [{ label: 'public', index: publicTypeIndex as NamedNode, agent: user }] : [] let preferencesFile @@ -40,11 +48,19 @@ export function createTypeIndexLogic(store, authn, profileLogic, utilityLogic): if (preferencesFile) { // watch out - can be in either as spec was not clear. Legacy is profile. // If there is a legacy one linked from the profile, use that. // Otherwiae use or make one linked from Preferences - const suggestedPrivateTypeIndex = suggestPrivateTypeIndex(preferencesFile) + let suggestedPrivateTypeIndex: NamedNode | null = null + try { + suggestedPrivateTypeIndex = suggestPrivateTypeIndex(preferencesFile) + } catch (err) { + const message = `User ${user} has no usable preferences document directory for privateTypeIndex.` + debug.warn(message) + } let privateTypeIndex try { privateTypeIndex = store.any(user, ns.solid('privateTypeIndex'), undefined, profile) || - await utilityLogic.followOrCreateLink(user, ns.solid('privateTypeIndex') as NamedNode, suggestedPrivateTypeIndex, preferencesFile) + (suggestedPrivateTypeIndex + ? await utilityLogic.followOrCreateLink(user, ns.solid('privateTypeIndex') as NamedNode, suggestedPrivateTypeIndex, preferencesFile) + : null) } catch (err) { const message = `User ${user} has no pointer in preference file to privateTypeIndex file.` debug.warn(message) @@ -109,13 +125,35 @@ export function createTypeIndexLogic(store, authn, profileLogic, utilityLogic): return scopedAppInstances.map(scoped => scoped.instance) } + function docDirUri(node: NamedNode): string | null { + const doc = node.doc() + const dir = doc.dir() + if (dir?.uri) return dir.uri + const docUri = doc.uri + if (!docUri) { + debug.log(`docDirUri: missing doc uri for ${node?.uri}`) + return null + } + const withoutFragment = docUri.split('#')[0] + const lastSlash = withoutFragment.lastIndexOf('/') + if (lastSlash === -1) { + debug.log(`docDirUri: no slash in doc uri ${docUri}`) + return null + } + return withoutFragment.slice(0, lastSlash + 1) + } + function suggestPublicTypeIndex(me: NamedNode) { - return sym(me.doc().dir()?.uri + 'publicTypeIndex.ttl') + const dirUri = docDirUri(me) + if (!dirUri) throw new Error(`suggestPublicTypeIndex: Cannot derive directory for ${me.uri}`) + return sym(dirUri + 'publicTypeIndex.ttl') } // Note this one is based off the pref file not the profile function suggestPrivateTypeIndex(preferencesFile: NamedNode) { - return sym(preferencesFile.doc().dir()?.uri + 'privateTypeIndex.ttl') + const dirUri = docDirUri(preferencesFile) + if (!dirUri) throw new Error(`suggestPrivateTypeIndex: Cannot derive directory for ${preferencesFile.uri}`) + return sym(dirUri + 'privateTypeIndex.ttl') } /* From bb41d04520176ff8d2dfd82fe5003abda0777b99 Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Fri, 13 Feb 2026 12:01:48 +0100 Subject: [PATCH 2/4] catch error iri#issue --- src/authn/SolidAuthnLogic.ts | 3 --- src/typeIndex/typeIndexLogic.ts | 18 +++++++------ test/typeIndexLogic.test.ts | 48 +++++++++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/src/authn/SolidAuthnLogic.ts b/src/authn/SolidAuthnLogic.ts index 5d7be15..6d49a8e 100644 --- a/src/authn/SolidAuthnLogic.ts +++ b/src/authn/SolidAuthnLogic.ts @@ -17,14 +17,11 @@ export class SolidAuthnLogic implements AuthnLogic { currentUser(): NamedNode | null { const app = appContext() if (app.viewingNoAuthPage) { - debug.log(`currentUser: viewingNoAuthPage webId=${app.webId}`) return sym(app.webId) } if (this && this.session && this.session.info && this.session.info.webId && this.session.info.isLoggedIn) { - debug.log(`currentUser: session webId=${this.session.info.webId}`) return sym(this.session.info.webId) } - debug.log('currentUser: no user found, falling back to offlineTestID') return offlineTestID() // null unless testing } diff --git a/src/typeIndex/typeIndexLogic.ts b/src/typeIndex/typeIndexLogic.ts index e005557..0871438 100644 --- a/src/typeIndex/typeIndexLogic.ts +++ b/src/typeIndex/typeIndexLogic.ts @@ -27,13 +27,15 @@ export function createTypeIndexLogic(store, authn, profileLogic, utilityLogic): debug.warn(message) } let publicTypeIndex - if (suggestion) { - try { - publicTypeIndex = await utilityLogic.followOrCreateLink(user, ns.solid('publicTypeIndex') as NamedNode, suggestion, profile) - } catch (err) { - const message = `User ${user} has no pointer in profile to publicTypeIndex file.` - debug.warn(message) - } + try { + publicTypeIndex = + store.any(user, ns.solid('publicTypeIndex'), undefined, profile) || + (suggestion + ? await utilityLogic.followOrCreateLink(user, ns.solid('publicTypeIndex') as NamedNode, suggestion, profile) + : null) + } catch (err) { + const message = `User ${user} has no pointer in profile to publicTypeIndex file: ${err}` + debug.warn(message) } const publicScopes = publicTypeIndex ? [{ label: 'public', index: publicTypeIndex as NamedNode, agent: user }] : [] @@ -62,7 +64,7 @@ export function createTypeIndexLogic(store, authn, profileLogic, utilityLogic): ? await utilityLogic.followOrCreateLink(user, ns.solid('privateTypeIndex') as NamedNode, suggestedPrivateTypeIndex, preferencesFile) : null) } catch (err) { - const message = `User ${user} has no pointer in preference file to privateTypeIndex file.` + const message = `User ${user} has no pointer in preference file to privateTypeIndex file: ${err}` debug.warn(message) } privateScopes = privateTypeIndex ? [{ label: 'private', index: privateTypeIndex as NamedNode, agent: user }] : [] diff --git a/test/typeIndexLogic.test.ts b/test/typeIndexLogic.test.ts index 18f4338..75a7b77 100644 --- a/test/typeIndexLogic.test.ts +++ b/test/typeIndexLogic.test.ts @@ -2,7 +2,7 @@ * @jest-environment jsdom * */ -import { Fetcher, Store, sym, UpdateManager } from 'rdflib' +import { Fetcher, parse, Store, sym, UpdateManager } from 'rdflib' import { createAclLogic } from '../src/acl/aclLogic' import { createProfileLogic } from '../src/profile/profileLogic' import { createTypeIndexLogic} from '../src/typeIndex/typeIndexLogic' @@ -51,7 +51,7 @@ describe('TypeIndex logic NEW', () => { requests = [] statustoBeReturned = 200 - fetchMock.mockIf(/^https?.*$/, async req => { + fetchMock.mockIf(/^(https?|mailto):.*$/, async req => { if (req.method !== 'GET') { requests.push(req) @@ -131,6 +131,50 @@ describe('TypeIndex logic NEW', () => { expect(store.statementsMatching(null, null, null, AlicePrivateTypeIndex).length).toEqual(8) expect(store.statementsMatching(null, null, null, AlicePublicTypeIndex).length).toEqual(8) }) + it('uses existing publicTypeIndex when suggestion fails', async () => { + const carol = sym('urn:uuid:carol#me') + const CarolProfileDoc = carol.doc() + const CarolPreferencesFile = sym('https://carol.example.com/settings/prefs.ttl') + const CarolPublicTypeIndex = sym('https://carol.example.com/profile/public-type-index.ttl') + const CarolPrivateTypeIndex = sym('https://carol.example.com/settings/private-type-index.ttl') + + const CarolProfile = ` +<#me> a vcard:Individual; + space:preferencesFile ${CarolPreferencesFile}; + solid:publicTypeIndex ${CarolPublicTypeIndex}. +` + const CarolPreferences = ` + ${carol} solid:privateTypeIndex ${CarolPrivateTypeIndex} . +` + + web[CarolPreferencesFile.uri] = CarolPreferences + web[CarolPublicTypeIndex.uri] = ` +:t solid:forClass wf:Tracker; solid:instance <../publicStuff/actionItems.ttl#this> . +` + web[CarolPrivateTypeIndex.uri] = ` +:t solid:forClass wf:Tracker; solid:instance <../privateStuff/ToDo.ttl#this> . +` + + const util = createUtilityLogic(store, createAclLogic(store), createContainerLogic(store)) + const profileLogic = { + loadProfile: async (user) => { + parse(prefixes + CarolProfile, store, CarolProfileDoc.uri, 'text/turtle') + return user.doc() + }, + silencedLoadPreferences: async () => { + parse(prefixes + CarolPreferences, store, CarolPreferencesFile.uri, 'text/turtle') + return CarolPreferencesFile + } + } + const typeIndexLogicWithStub = createTypeIndexLogic(store, authn, profileLogic as any, util) + + const result = await typeIndexLogicWithStub.loadTypeIndexesFor(carol) + expect(result).toEqual([ + { label: 'public', index: CarolPublicTypeIndex as any, agent: carol as any }, + { label: 'private', index: CarolPrivateTypeIndex as any, agent: carol as any } + ]) + expect(requests.length).toEqual(0) + }) }) const ClubScopes = From 575c3da6239bb34fe6d38a15c58c95381cabfb17 Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Sun, 15 Feb 2026 13:10:20 +0100 Subject: [PATCH 3/4] add test --- test/typeIndexLogic.test.ts | 48 ++----------------------------------- 1 file changed, 2 insertions(+), 46 deletions(-) diff --git a/test/typeIndexLogic.test.ts b/test/typeIndexLogic.test.ts index 75a7b77..18f4338 100644 --- a/test/typeIndexLogic.test.ts +++ b/test/typeIndexLogic.test.ts @@ -2,7 +2,7 @@ * @jest-environment jsdom * */ -import { Fetcher, parse, Store, sym, UpdateManager } from 'rdflib' +import { Fetcher, Store, sym, UpdateManager } from 'rdflib' import { createAclLogic } from '../src/acl/aclLogic' import { createProfileLogic } from '../src/profile/profileLogic' import { createTypeIndexLogic} from '../src/typeIndex/typeIndexLogic' @@ -51,7 +51,7 @@ describe('TypeIndex logic NEW', () => { requests = [] statustoBeReturned = 200 - fetchMock.mockIf(/^(https?|mailto):.*$/, async req => { + fetchMock.mockIf(/^https?.*$/, async req => { if (req.method !== 'GET') { requests.push(req) @@ -131,50 +131,6 @@ describe('TypeIndex logic NEW', () => { expect(store.statementsMatching(null, null, null, AlicePrivateTypeIndex).length).toEqual(8) expect(store.statementsMatching(null, null, null, AlicePublicTypeIndex).length).toEqual(8) }) - it('uses existing publicTypeIndex when suggestion fails', async () => { - const carol = sym('urn:uuid:carol#me') - const CarolProfileDoc = carol.doc() - const CarolPreferencesFile = sym('https://carol.example.com/settings/prefs.ttl') - const CarolPublicTypeIndex = sym('https://carol.example.com/profile/public-type-index.ttl') - const CarolPrivateTypeIndex = sym('https://carol.example.com/settings/private-type-index.ttl') - - const CarolProfile = ` -<#me> a vcard:Individual; - space:preferencesFile ${CarolPreferencesFile}; - solid:publicTypeIndex ${CarolPublicTypeIndex}. -` - const CarolPreferences = ` - ${carol} solid:privateTypeIndex ${CarolPrivateTypeIndex} . -` - - web[CarolPreferencesFile.uri] = CarolPreferences - web[CarolPublicTypeIndex.uri] = ` -:t solid:forClass wf:Tracker; solid:instance <../publicStuff/actionItems.ttl#this> . -` - web[CarolPrivateTypeIndex.uri] = ` -:t solid:forClass wf:Tracker; solid:instance <../privateStuff/ToDo.ttl#this> . -` - - const util = createUtilityLogic(store, createAclLogic(store), createContainerLogic(store)) - const profileLogic = { - loadProfile: async (user) => { - parse(prefixes + CarolProfile, store, CarolProfileDoc.uri, 'text/turtle') - return user.doc() - }, - silencedLoadPreferences: async () => { - parse(prefixes + CarolPreferences, store, CarolPreferencesFile.uri, 'text/turtle') - return CarolPreferencesFile - } - } - const typeIndexLogicWithStub = createTypeIndexLogic(store, authn, profileLogic as any, util) - - const result = await typeIndexLogicWithStub.loadTypeIndexesFor(carol) - expect(result).toEqual([ - { label: 'public', index: CarolPublicTypeIndex as any, agent: carol as any }, - { label: 'private', index: CarolPrivateTypeIndex as any, agent: carol as any } - ]) - expect(requests.length).toEqual(0) - }) }) const ClubScopes = From bad8dd820f0997f21af3015e349e6ab37ca25849 Mon Sep 17 00:00:00 2001 From: bourgeoa Date: Sun, 15 Feb 2026 13:12:32 +0100 Subject: [PATCH 4/4] 4.0.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3b5554..2f428c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "solid-logic", - "version": "4.0.2", + "version": "4.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "solid-logic", - "version": "4.0.2", + "version": "4.0.3", "license": "MIT", "dependencies": { "@inrupt/solid-client-authn-browser": "^3.1.0", diff --git a/package.json b/package.json index 4df1a9e..cca7391 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solid-logic", - "version": "4.0.2", + "version": "4.0.3", "description": "Core business logic of SolidOS", "type": "module", "main": "dist/solid-logic.js",