Profile Pane
The profile pane renders WebID profiles — the identity cards of the Solid ecosystem.
What It Renders
- WebID profile documents
foaf:Personresourcesschema:Personresources
Features
- Avatar display
- Name and bio
- Contact information
- Social links
- Friends list (foaf:knows)
- Trusted apps
- Profile editing
- Public key management
Screenshot
┌─────────────────────────────────────────────────┐
│ ┌────────┐ │
│ │ 👤 │ Alice Smith │
│ │ │ @alice │
│ └────────┘ │
│ │
│ Software developer passionate about │
│ decentralization and data sovereignty. │
│ │
├─────────────────────────────────────────────────┤
│ 📧 alice@example.org │
│ 🌐 https://alice.example │
│ 📍 San Francisco, CA │
├─────────────────────────────────────────────────┤
│ Friends (12) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │Bob │ │Carol│ │Dave│ │Eve │ ... │
│ └────┘ └────┘ └────┘ └────┘ │
├─────────────────────────────────────────────────┤
│ [Edit Profile] [Settings] │
└─────────────────────────────────────────────────┘
RDF Structure
A typical WebID profile:
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix vcard: <http://www.w3.org/2006/vcard/ns#> .
@prefix solid: <http://www.w3.org/ns/solid/terms#> .
@prefix ldp: <http://www.w3.org/ns/ldp#> .
@prefix space: <http://www.w3.org/ns/pim/space#> .
<#me>
a foaf:Person ;
foaf:name "Alice Smith" ;
foaf:nick "alice" ;
foaf:img <photo.jpg> ;
foaf:bio "Software developer passionate about decentralization." ;
foaf:mbox <mailto:alice@example.org> ;
foaf:homepage <https://alice.example> ;
# Location
vcard:hasAddress [
vcard:locality "San Francisco" ;
vcard:region "CA"
] ;
# Friends
foaf:knows
<https://bob.example/profile/card#me>,
<https://carol.example/profile/card#me> ;
# Solid-specific
solid:oidcIssuer <https://solidcommunity.net> ;
solid:publicTypeIndex </settings/publicTypeIndex.ttl> ;
solid:privateTypeIndex </settings/privateTypeIndex.ttl> ;
space:preferencesFile </settings/prefs.ttl> ;
space:storage </> ;
ldp:inbox </inbox/> .
Usage
// Navigate to a profile
panes.runDataBrowser(document, sym('https://alice.example/profile/card#me'))
// Get the pane directly
const profilePane = paneRegistry.byName('profile')
Reading Profile Data
import { store, sym } from 'solid-logic'
const FOAF = Namespace('http://xmlns.com/foaf/0.1/')
const SOLID = Namespace('http://www.w3.org/ns/solid/terms#')
async function getProfile(webId) {
const person = sym(webId)
await store.fetcher.load(person.doc())
return {
name: store.any(person, FOAF('name'), null, null)?.value,
nick: store.any(person, FOAF('nick'), null, null)?.value,
photo: store.any(person, FOAF('img'), null, null)?.uri,
bio: store.any(person, FOAF('bio'), null, null)?.value,
email: store.any(person, FOAF('mbox'), null, null)?.uri?.replace('mailto:', ''),
homepage: store.any(person, FOAF('homepage'), null, null)?.uri,
storage: store.any(person, SPACE('storage'), null, null)?.uri,
inbox: store.any(person, LDP('inbox'), null, null)?.uri,
}
}
Editing Profiles
import { store, sym, lit, st } from 'solid-logic'
async function updateProfile(webId, updates) {
const person = sym(webId)
const doc = person.doc()
const deletions = []
const insertions = []
// Update name
if (updates.name !== undefined) {
const oldName = store.any(person, FOAF('name'), null, doc)
if (oldName) {
deletions.push(st(person, FOAF('name'), oldName, doc))
}
if (updates.name) {
insertions.push(st(person, FOAF('name'), lit(updates.name), doc))
}
}
// Update bio
if (updates.bio !== undefined) {
const oldBio = store.any(person, FOAF('bio'), null, doc)
if (oldBio) {
deletions.push(st(person, FOAF('bio'), oldBio, doc))
}
if (updates.bio) {
insertions.push(st(person, FOAF('bio'), lit(updates.bio), doc))
}
}
await store.updater.update(deletions, insertions)
}
Friends (foaf:knows)
Listing Friends
async function getFriends(webId) {
const person = sym(webId)
await store.fetcher.load(person.doc())
const friends = store.each(person, FOAF('knows'), null, null)
// Load friend profiles
const friendProfiles = []
for (const friend of friends) {
try {
await store.fetcher.load(friend.doc())
friendProfiles.push({
webId: friend.uri,
name: store.any(friend, FOAF('name'), null, null)?.value,
photo: store.any(friend, FOAF('img'), null, null)?.uri
})
} catch (e) {
// Friend profile not accessible
friendProfiles.push({ webId: friend.uri, name: null, photo: null })
}
}
return friendProfiles
}
Adding Friends
async function addFriend(myWebId, friendWebId) {
const me = sym(myWebId)
const friend = sym(friendWebId)
const doc = me.doc()
await store.updater.update([], [
st(me, FOAF('knows'), friend, doc)
])
}
Removing Friends
async function removeFriend(myWebId, friendWebId) {
const me = sym(myWebId)
const friend = sym(friendWebId)
const doc = me.doc()
await store.updater.update([
st(me, FOAF('knows'), friend, doc)
], [])
}
Profile Photo
Uploading Photo
async function uploadPhoto(webId, file) {
const person = sym(webId)
const photoUri = sym(`${person.doc().dir().uri}photo.${file.name.split('.').pop()}`)
// Upload the image
await store.fetcher.putResourceAs(photoUri, file, file.type)
// Update profile
const oldPhoto = store.any(person, FOAF('img'), null, person.doc())
await store.updater.update(
oldPhoto ? [st(person, FOAF('img'), oldPhoto, person.doc())] : [],
[st(person, FOAF('img'), photoUri, person.doc())]
)
}
Solid-Specific Properties
Storage
// Get pod root
const storage = store.any(person, SPACE('storage'), null, null)
console.log('Pod root:', storage?.uri) // https://alice.example/
Inbox
// Get inbox for notifications
const inbox = store.any(person, LDP('inbox'), null, null)
console.log('Inbox:', inbox?.uri) // https://alice.example/inbox/
Type Indexes
// Get type indexes
const publicTypeIndex = store.any(person, SOLID('publicTypeIndex'), null, null)
const privateTypeIndex = store.any(person, SOLID('privateTypeIndex'), null, null)
OIDC Issuer
// Get identity provider
const issuer = store.any(person, SOLID('oidcIssuer'), null, null)
console.log('IDP:', issuer?.uri) // https://solidcommunity.net
Trusted Apps
Manage which apps have access:
<#me>
acl:trustedApp [
acl:mode acl:Read, acl:Write ;
acl:origin <https://trusted-app.example>
] .
// List trusted apps
const trustedApps = store.each(person, ACL('trustedApp'), null, null)
trustedApps.forEach(app => {
const origin = store.any(app, ACL('origin'), null, null)
const modes = store.each(app, ACL('mode'), null, null)
console.log(`${origin?.uri}: ${modes.map(m => m.uri).join(', ')}`)
})