Question about retrieving data

Oh, and beware that the web-oriented operations in the fetcher are all async using promises.

It took me awhile to figure out why my “refresh list” operation did not include a newly added resource. That was because the “put” operation executed asynchronously and did not complete before the refresh list called the server for a an update.

Be careful to add await .then() and .catch() to the right places in your code.

3 Likes

I posted this some time ago. I didn’t get very far and so i gave up and moved on. However, I want to try again.

In my ttl file I have the entry
n:hasEmail "me@myemail.com";

I can retrieve this email address using…

let email = store.any($rdf.sym(webIdFromUrl), VCARD('hasEmail'));
console.log(email.value); // me@myemail.com

This works with my ttl file, but not with others. The email stored in other ttl files look something like this…
n:hasEmail :id1586990017267;
Or…
:id1586990017267 n:value <mailto:someone@something.com>.

Of course, when i try to access this data, I get the id, and not the address. So how do i access the email address? I’ve also tried various other things such as…

const email = store.any($rdf.sym(webIdFromUrl), FOAF('mbox'));
and…
const email = store.any($rdf.sym(webIdFromUrl), FOAF('email'));

But nothing is returning the email address.

To be clear, i’m not really interested in linked data. I’m sure it’s really cool, and perhaps i’ll look into it more at a later date, but right now i’m much more interested in the concept of a personal datastore. So I don’t want to spend days learning about RDF in order to do something that should be very simple.

P.S. This post tends to come up on most related Google search terms. So instead of posting a link to the documentation, which people can find anyway through a quick search, it would make more sense for the Solid team to provide some clear examples here, that way other people can use it as a reference :wink:

1 Like

I had another go with ldflex. Again, the last time tried to use ldflex I wasn’t able to get it to work. The good news (if you want to call it that) is that i have finally manage to read ONE persons email address. And this is because the email is stored as foaf:mbox <Ruben’s email>. So basically, there seems to be a problem reading the Vcard.

Below are some statements that i’ve tried, and the results.

solid.data['https://devolution.inrupt.net/profile/card#me'].name
returns my name
solid.data['https://devolution.inrupt.net/profile/card#me'].email
returns undefined
solid.data['https://rubenverborgh.inrupt.net/profile/card#me'].email
returns undefined
solid.data['https://ruben.verborgh.org/profile/#me'].email
returns mailto:<ruben’s email address>
solid.data['https://ruben.verborgh.org/profile/#me'].name
returns <ruben’s name>
solid.data['https://ruben.verborgh.org/profile/#me'].role
returns undefined

I tried multiple statements with multiple different webId’s. I’m able to retrieve most peoples names, but that’s about it. So does ldflex only work with foaf?

EDIT: I’m coming to the conclusion that it’s not possible. I assume that the reason why both the vcard email and address are encrypted is to prevent applications from scraping peoples personal data. Would that be a correct assumption? If that’s the case, i’m not sure why I was able to get it through foaf.

Hi @glensimister ,
Welcome back to your exploration…
First, “undefined” is not an error in that case, it’s often because the data is not there :wink:

Don’t be affraid with RDF & linked Data, it 's all about things and links between those things…

i’m using ldflex largely for my projects as you can see
agora/config-get-view.js at dd67463799c22ce1dbfa01495807659796f94f13 · scenaristeur/agora · GitHub .
but i think email is a little particular as it can have many values and can not be directly accessed on a POD.
First , ldflex does not only have foaf, here are all the vocabs I’ve found in the @solid/context of the package.json
context/context.json at master · solid/context · GitHub

I’ve never used email, but i just created emails on a test POD to try
TO get email you can test that POD https://spoggy-test.solid.community/profile/card#me

When you put email through the databrowser (see the “</>” icon to look at the data ) , the data is stored like that

...
:id1587748083850 a n:Home; n:value <mailto:test@solid.test>.

:id1587748108886 a n:Dom; n:value <mailto:swing@pop.boo>.

:id1587748123046 n:value <mailto:hello@me.cool>.

:id1587748147974 a n:Dom; n:value <tel:0123456789>.

:id1587748173361 a n:Home; n:value <tel:9876543210>.

:id1587748195447
    n:country-name "My Contry";
    n:locality "The town";
    n:postal-code "12345";
    n:region "Cool region";
    n:street-address "00 my street".
:id1587748237533
    n:country-name "Somewhere";
    n:locality "UnderTheSea";
    n:postal-code "888888";
    n:region "Sea Under";
    n:street-address "123 another street".
:me
    a schem:Person, n0:Person;
    n:fn "Spoggy-Test";
    n:hasAddress :id1587748195447, :id1587748237533;
    n:hasEmail :id1587748083850, :id1587748108886, :id1587748123046;
    n:hasPhoto <highres_274291645%5B1%5D.jpeg>;
    n:hasTelephone :id1587748147974, :id1587748173361;
    n:note "what is the note";
    n:organization-name "Smag0";
    n:role "Tester";
...

Well now, How to retrieve that ?
The example on ldflex-query repo are pointing to @RubenVerborgh server and seems to be a little bit outdated or not really compatible with the Pod implementation.

For example to retrieve the name on a POD
solid.data['https://devolution.inrupt.net/profile/card#me'].name
that is a shortcut of foaf:name does not work on a pod as it search as you mentionned to foaf:name…

You must use solid.data['https://devolution.inrupt.net/profile/card#me'].vcard$fn

:wink:

For the data on my pod :

solid.data['https://spoggy-test.solid.community/profile/card#me'].vcard$fn --> NAME

same with vcard$role, vcard$hasPhoto …and so on… a little issue with organization-name as it is a composed predicate… :thinking:

so to get organization-name, you can replace vcard prefix by its uri, and something like

await solid.data['https://spoggy-test.solid.community/profile/card#me']["http://www.w3.org/2006/vcard/ns#organization-name"]

Well, now when you suppose there are many values (like emails), ldflex-query must be used with for await ... as indicated on Ldflex/ldflex repo GitHub - LDflex/LDflex: A JavaScript DSL for querying Linked Data on the Web

So looking at the data structure stored on the POD,
you must find the objects of triples whose predicate is vcard$hasEmail then when you got the objects of that triple, follow the predicate vcard$value.

I’ve not tried yet but something like the following must give you what you expected

 for await (const emailId of solid.data['https://spoggy-test.solid.community/profile/card#me'].vcard$hasEmail)
    console.log(`- ${emailId}`);
    let email = await solid.data[${emailId }].vcard$value
    console.log(`${email}`)
}

Be carefull of the backquotes that allow to wait for the promise to be complete :wink:
Is that ok for you ??

Last but not least, to help to build Solidarity Chat, i’ve put a layer on top of ldflex to retrieve the basic data of a pod (not email yet but could be ;-)) Here is the Shighl lib

1 Like

Hi. Thanks for taking the time to answer. I quickly tried the following code based on your suggestion…

for await (const emailId of solid.data['https://spoggy-test.solid.community/profile/card#me'].vcard$hasEmail) {
console.log(`- ${emailId}`);
let email = solid.data[`${emailId }`].vcard$value
 console.log(`${email}`)
 }

However, I still seem to be faced with the same problem. In the console I get…

https://spoggy-test.solid.community/profile/card#id1587748083850
undefined
https://spoggy-test.solid.community/profile/card#id1587748108886
undefined
https://spoggy-test.solid.community/profile/card#id1587748123046
undefined

So it’s finding the email addresses, but i’m not able to extract the value. Anyway, i’ll spend a bit more time on this tomorrow (it’s getting late now) and see if i can figure out what the problem is.

I got it! I just needed to add await!

let email = await solid.data[${emailId }].vcard$value

Thanks dude! :smile:

3 Likes

Really Nice :+1:it’s not really hard but there are some tricks to know… so the best way I think is to share experience :blush:
Edited my first answer to fix

You will certainly need to write data too, so some tricks :

2 Likes

Ldflex is great. I’ve been able to add friends. However, I couldn’t seem to find an example of how to remove them. I had a wild guess at: await me.friends.remove(friend), which threw a type error “me.friends.remove is not a function” - not surprisingly. Any ideas about this?

Also, i managed to check if someone was my friend, but i think my code is a bit long winded. This is what I’ve tried…

export async function checkFriends(me, them) {
    return new Promise(async resolve => {
        for await (const friend of me.friends) {
            // remove /profile/card#me to ensure consistency
            let isFriend = await getWebIdOrigin(friend); 
            if (them === isFriend) {
                resolve(true);
            }
        }
        resolve(false);
    });
}

Is that about right? Or is there a simpler way?

Have you tried the delete keyword? Never used ldflex myself, but it looks like it’s what you want

Thanks for the info. I did actually try to use delete(), but nothing happened, not even an error message. e.g. await me.friends.delete('https://rubenverborgh.inrupt.net/profile/card#me');

However, at least I now know the correct keyword :wink:

I’ll keep playing around…

Hi~when i try to user solid.data[‘https://spoggy-test.solid.community/profile/card#me’].vcard$fn get the name,there is an error in the console:
ERROR in ./src/index.js 47:15
Module parse failed: Unexpected token (47:15)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|
| async function showPerson(){

solid.data[]

| }
|

webpack 5.28.0 compiled with 1 error in 19 ms
i 「wdm」: Failed to compile.
i 「wdm」: Compiling…
× 「wdm」: asset index.js 7.69 MiB [emitted] (name: main)
cached modules 6.65 MiB [cached] 1291 modules
runtime modules 1.25 KiB 6 modules
./src/index.js 1.71 KiB [built] [code generated]

ERROR in ./src/index.js 9:0-60
Module not found: Error: Package path ./dist/constants is not exported from package D:\solid\demoapp\node_modules@inrupt\solid-client (see exports field in D:\solid\demoapp\node_modules@inrupt\solid-client\package.json)

Are there the solid need any other operations to make it userful?

Hi @wenjingli-qa, welcome to the Solid forum! Could you show the actual code you’re using, and the libraries you’re trying to use.

In lieu of that I’m going to make some assumptions here that might help you. The first is that your reference to solid.data implies that you’re using @solid/query-ldflex. I’m not an expert in LDflex, but your query (i.e. solid.data[‘https://spoggy-test.solid.community/profile/card#me’].vcard$fn) looks good, in the sense that I don’t see any errors when executing it in the LDflex playground. That said, the profile you’re looking at (spoggy-test.sol...) doesn’t load for me at this time, so I’m not getting any results, but trying it with my profile it seems to work fine.

Then there is the actual error message, which refers to @inrupt/solid-client. That seems to indicate that there’s a statement like the following somewhere in your code:

import { /* something */ } from "@inrupt/solid-client/dist/constants";

It might be that your editor inserted that, but solid-client does not allow you to import from dist/constants, hence the error message. Removing that import statement (which I’m assuming is unused) should solve that message for you.

1m

Thanks for your reply~ :grinning: i removed that import statement, run in my browser has a new error as follows:
index.js:43 Uncaught (in promise) ReferenceError: solid is not defined
at showPerson (index.js:43)
at HTMLButtonElement.buttonRead.onclick (index.js:53)
showPerson @ index.js:43
buttonRead.onclick @ index.js:53

my the actual code as follow,don’t i do something wrong?:
import { login,handleIncomingRedirect,getDefaultSession, fetch} from “@inrupt/solid-client-authn-browser”;
const { PathFactory } = require(‘ldflex’);
const { default: ComunicaEngine } = require(’@ldflex/comunica’);
const { namedNode } = require(’@rdfjs/data-model’);
const buttonLogin = document.querySelector("#btnLogin");
const buttonRead = document.querySelector("#btnRead");
// 1a. Start Login Process. Call login() function.
function loginToInruptDotCom() {return login({oidcIssuer: “https://liwenjing.solid.fuxitechnology.cn/”,
redirectUrl: window.location.href,}); }
async function handleRedirectAfterLogin() {
await handleIncomingRedirect();
const session = getDefaultSession();
if (session.info.isLoggedIn) {
// Update the page with the status.
document.getElementById(“labelStatus”).textContent = “Your session is logged in.”;
document.getElementById(“labelStatus”).setAttribute(“role”, “alert”);}}
// The example has the login redirect back to the index.html.
// This calls the function to process login information.
// If the function is called when not part of the login redirect, the function is a no-op.
handleRedirectAfterLogin();
async function showPerson(){
const name = solid.data[‘https://liwenjing.solid.fuxitechnology.cn/profile/card#me’].vcard$fn;
console.log(name);
}
buttonLogin.onclick = function() {
loginToInruptDotCom();
};
buttonRead.onclick = function() {
showPerson();
};

You’re trying to use LDflex together with @inrupt/solid-client-authn-browser, which unfortunately at the moment is not possible - @solid/query-ldflex assumes you are using solid-auth-client.

Additionally, you’re referencing solid.data, but you’re not importing the solid module from anywhere. It might be worth reading up on how modules in JavaScript work - it’s a bit too much for me to explain all that in a short forum post, sorry :slight_smile:

(And one other tip: if you use three backticks (i.e. `) on a line, then paste your code, then add three additional backticks on a new line, your code will be more readable to others. It looks as follows.)

const someCode = "example";
console.log(someCode);

Hi @wenjingli-qa the first issue is totaly normal as solid.community does not existe anymore, the new server is solidcommunity.net, so this should work Solid LDflex playground instead of

as @Vincent said

but for many reason I personnally feel more easy with the first stack (folder files & subfolders with solid-file-client, one line access with ldflex…) whereas i know that the second should certainely replace the first

then for the ldflex/webpack issue, @RubenVerborgh gave me a solution here that could help you how to externalize query-ldflex in shighl webpack.config.js ? · Issue #57 · solid/query-ldflex · GitHub but Make Webpack configuration for an ES6 module · Issue #25 · solid/query-ldflex · GitHub

that work but for other projects, the solution i first found is importing solid-auth-client & ldflex in the index.html as an “old” js ( lines 9 to 11)

and then get it in my modules (line 1 and line 16) with

At some point there was an issue with communica lib that is used by ldflex so i swithched to rdflib Help using different libs: rdflibjs, tripledoc, ldflex, solid-file-client - #14 by RubenVerborgh

Another way I recently used is (line 11 & 48)

Does it solve your issue ?

1 Like

thank you very much!!! it’s worked to import solid-auth-client & ldflex in the index.html, i have got the email from profile, :smiley: :cherry_blossom: :cherry_blossom:

2 Likes

Nice :slightly_smiling_face::+1: please don’t spam my mail :wink:

1 Like

I’m playing around and I made a small React application, when I login it takes my profile card:

https://pod.inrupt.com/dieter/profile/card

I’m able to get my first name, role, etc… but not my home & work email address.
Can someone give me a suggestion how to do that?

This is how my application looks like:

import React, { useEffect, useState } from 'react'
import {
  LoginButton,
  LogoutButton,
  CombinedDataProvider,
  Text,
  Image
} from '@inrupt/solid-ui-react'
import { FOAF, VCARD } from '@inrupt/vocab-common-rdf'
import { handleIncomingRedirect } from '@inrupt/solid-client-authn-browser'

const App = () => {
  const [idp] = useState('https://broker.pod.inrupt.com')
  const [currentUrl] = useState("http://localhost:8080")
  const [info, setInfo] = useState(false)

  const logout = () => {
    setInfo(false)
  }

  useEffect(() => {
    handleIncomingRedirect({
      restorePreviousSession: true
    }).then(async info => {
      setInfo(info)
      console.log(`Logged in with WebID [${info.webId}]`)
    })
  }, [])

  return (
    <div>
      <LoginButton
        authOptions={{ clientName: 'test' }}
        oidcIssuer={idp}
        redirectUrl={currentUrl}
        onError={console.error}
      ></LoginButton>
      <LogoutButton
        onLogout={logout}
      ></LogoutButton>
      {info.isLoggedIn ? 'yes' : 'no'}<br />
      <CombinedDataProvider datasetUrl={info.webId} thingUrl={info.webId}>
        FOAF.name: <Text property={FOAF.name} /><br />
        VCARD.fn: <Text property={VCARD.fn} /><br />
        VCARD.role: <Text property={VCARD.role} /><br />
        VCARD.organization_name: <Text property={VCARD.organization_name} /><br />
        VCARD.photo: <Image property={VCARD.photo} width={480} />
      </CombinedDataProvider>
    </div>
  )
}

export default App

That’s because those addresses are listed in separate Things (i.e. https://pod.inrupt.com/dieter/profile/card#164552511412522672518959692933 and https://pod.inrupt.com/dieter/profile/card#16455414726144387054963402375, rather than https://pod.inrupt.com/dieter/profile/card#me.

I’m not familiar with the React components and <CombinedDataProvider>, but with plain solid-client, you’d obtain them something like this:

const dataset = await getSolidDataset("https://pod.inrupt.com/dieter/profile/card");

const profileThing = getThing(dataset, "https://pod.inrupt.com/dieter/profile/card#me");

const addressThingUrls = getUrlAll(profileThing, "http://www.w3.org/2006/vcard/ns#hasEmail");
const addressThings = addressUrls.map(addressThingUrl => getThing(dataset, addressThingUrl));

const workAddressThing = addressThings.find(addressThing => getUrl(addressThing, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") === "http://www.w3.org/2006/vcard/ns#Work");
const workAddress = getUrl(workAddressThing, "http://www.w3.org/2006/vcard/ns#value");

const homeAddressThing = addressThings.find(addressThing => getUrl(addressThing, "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") === "http://www.w3.org/2006/vcard/ns#Home");
const homeAddress = getUrl(homeAddressThing, "http://www.w3.org/2006/vcard/ns#value");

Possibly, this structure makes more sense if you use a Thing-based viewer like Penny: https://penny.vincenttunru.com/explore/?url=https%3A%2F%2Fpod.inrupt.com%2Fdieter%2Fprofile%2Fcard

1 Like

Thanks for pushing me in the right direction. I was able to make it work with plain solid-client and solid-ui-react. Also @inrupt/vocab-common-rdf is a very useful package!

Maybe it is helpful for someone else:

App.js component

import React, { useEffect, useState } from 'react'
import {
  LoginButton,
  LogoutButton,
  CombinedDataProvider,
  Text,
  Image
} from '@inrupt/solid-ui-react'
import {
  getSolidDataset,
  getThing,
  getUrl,
  getUrlAll,
  getStringNoLocale
} from '@inrupt/solid-client'
import { FOAF, VCARD, RDF } from '@inrupt/vocab-common-rdf'
import { handleIncomingRedirect, fetch } from '@inrupt/solid-client-authn-browser'
import _ from 'lodash'
import Emails from './Emails'

const App = () => {
  const [idp] = useState('https://broker.pod.inrupt.com')
  const [currentUrl] = useState("http://localhost:8080")
  const [auth, setAuth] = useState(false)
  const [profile, setProfile] = useState(false)
  const [dataset, setDataset] = useState(false)

  const logout = () => {
    setAuth(false)
  }

  useEffect(() => {
    handleIncomingRedirect({
      restorePreviousSession: true
    }).then(async info => {
      setAuth(info)
      console.log(`Logged in with WebID [${info.webId}]`)
    })
  }, [])

  useEffect(async () => {
    if (!auth.webId) {
      return
    }

    setDataset(
      await getSolidDataset(
        auth.webId,
        { fetch: fetch }
      )
    )
  }, [auth.webId])

  useEffect(() => {
    if (!auth.webId || !dataset) {
      setProfile(false)
      return
    }

    setProfile(
      getThing(dataset, auth.webId)
    )
  }, [auth.webId, dataset])

  const renderEmails = () => {
    if (!profile) {
      return
    }

    const emailAddressUrls = getUrlAll(profile, VCARD.hasEmail)
    const emailAddressThings = emailAddressUrls.map(url => getThing(dataset, url))

    const workEmailAddressThing = emailAddressThings.find(thing => getUrl(thing, RDF.type) === VCARD.Work)
    const workEmailAddress = getUrl(workEmailAddressThing, VCARD.value)

    const homeEmailAddressThing = emailAddressThings.find(thing => getUrl(thing, RDF.type) === VCARD.Home)
    const homeEmailAddress = getUrl(homeEmailAddressThing, VCARD.value)

    return (
      <>
        Work: {workEmailAddress}<br />
        Home: {homeEmailAddress}<br />
      </>
    )
  }

  const renderInfo = () => profile &&
    <div>
      <strong>Using solid-client</strong><br />
      FOAF.name: {getStringNoLocale(profile, FOAF.name)}<br />
      VCARD.fn: {getStringNoLocale(profile, VCARD.fn)}<br />
      VCARD.role: {getStringNoLocale(profile, VCARD.role)}<br />
      VCARD.organization_name: {getStringNoLocale(profile, VCARD.organization_name)}<br />
      VCARD.hasPhoto:<br />
      <img src={getUrl(profile, VCARD.hasPhoto)}/><br />
      {renderEmails()}
    </div>

  return (
    <div>
      <LoginButton
        authOptions={{ clientName: 'test' }}
        oidcIssuer={idp}
        redirectUrl={currentUrl}
        onError={console.error}
      ></LoginButton>
      <LogoutButton
        onLogout={logout}
      ></LogoutButton>
      {auth.isLoggedIn ? 'yes' : 'no'}<br />

      {renderInfo()}

      <strong>Using solid-ui-react</strong><br />
      <CombinedDataProvider
        datasetUrl={auth.webId}
        thingUrl={auth.webId}
      >
        FOAF.name: <Text property={FOAF.name} /><br />
        VCARD.fn: <Text property={VCARD.fn} /><br />
        VCARD.role: <Text property={VCARD.role} /><br />
        VCARD.organization_name: <Text property={VCARD.organization_name} /><br />
        VCARD.photo:<br /><Image property={VCARD.hasPhoto} width={480} /><br />
        <Emails></Emails>
      </CombinedDataProvider>
    </div>
  )
}

export default App

emails.js component

import { getThing, getUrl, getUrlAll } from '@inrupt/solid-client'
import { DatasetContext, useThing, CombinedDataProvider, Value } from '@inrupt/solid-ui-react'
import React, { useContext } from 'react'
import { RDF, VCARD } from '@inrupt/vocab-common-rdf'

const emails = () => {
  const { solidDataset } = useContext(DatasetContext)
  const { thing } = useThing()
  const contactDetailUrls = getUrlAll(thing, VCARD.hasEmail)
  const contactDetailThings = contactDetailUrls.map((url) => ({
    solidDataset,
    thing: getThing(solidDataset, url)
  }));

  const getLabel = (thing, type) => {
    switch(getUrl(thing, type)) {
      case VCARD.Work:
        return 'Work'
      case VCARD.Home:
        return 'Home'
      default:
        return
    }
  }

  return contactDetailThings.map((contactDetailThing, index) =>
    <React.Fragment key={index}>
      <CombinedDataProvider
        solidDataset={contactDetailThing.solidDataset}
        thing={contactDetailThing.thing}
      >
        {getLabel(contactDetailThing.thing, RDF.type)}
        <Value dataType="url" property={VCARD.value} />
      </CombinedDataProvider><br/>
    </React.Fragment>
  )
}

export default emails
1 Like