Skip to main content

edc_web_components/components/
request_verifiable_credential.rs

1use crate::contexts::use_edc_identity_hub_context;
2use edc_identity_hub_client::IdentityHubClient;
3use edc_identity_hub_client::models::{
4  CredentialFormat, CredentialQuery, CredentialsSupported, DidWeb, RequestCredentialBody,
5  RequestCredentialState,
6};
7use patternfly_yew::prelude::*;
8use uuid::Uuid;
9use yew::platform::spawn_local;
10use yew::platform::time::sleep;
11use yew::prelude::*;
12
13#[derive(Clone, Debug, PartialEq)]
14pub struct Issuer {
15  pub did: String,
16  pub name: String,
17}
18
19#[derive(Clone, Debug, PartialEq, Properties)]
20pub struct RequestVerifiableCredentialProps {
21  #[prop_or_default]
22  pub issuers: Vec<Issuer>,
23  #[prop_or_default]
24  pub on_create: Callback<()>,
25}
26
27#[component]
28pub fn RequestVerifiableCredential(props: &RequestVerifiableCredentialProps) -> Html {
29  let requesting = use_state(|| false);
30  let issuer_did = use_state(String::new);
31  let credentials_supported = use_state(Vec::new);
32
33  let selected_credentials_supported = use_state(Vec::<CredentialsSupported>::new);
34
35  let onchange_issuer_did = use_callback(
36    (issuer_did.setter(), credentials_supported.setter()),
37    move |issuer_did: String, (issuer_did_setter, credentials_supported_setter)| {
38      issuer_did_setter.set(issuer_did.clone());
39      credentials_supported_setter.set(vec![]);
40      let credentials_supported_setter = credentials_supported_setter.clone();
41
42      spawn_local(async move {
43        if let Some(issuer_did_web) = DidWeb::new(&issuer_did)
44          && let Ok(identity) =
45            IdentityHubClient::get_identity(reqwest::Client::new(), issuer_did_web).await
46        {
47          log::debug!("Identity: {:?}", identity);
48          if let Some(issuer_service_client) = identity
49            .get_identity_services("IssuerService")
50            .first()
51            .and_then(|identity_service| identity_service.get_issuer_service_client())
52            && let Ok(issuer_service_metadata) = issuer_service_client.get_metadata().await
53          {
54            log::debug!("Issuer Service Metadata: {:?}", issuer_service_metadata);
55            credentials_supported_setter.set(issuer_service_metadata.credentials_supported);
56          }
57        }
58      })
59    },
60  );
61
62  let edc_identity_hub_context = use_edc_identity_hub_context();
63
64  let onsubmit = use_callback(
65    (
66      issuer_did.clone(),
67      selected_credentials_supported.clone(),
68      props.on_create.clone(),
69      edc_identity_hub_context.clone(),
70      requesting.clone(),
71    ),
72    |event: SubmitEvent,
73     (
74      issuer_did,
75      selected_credentials_supported,
76      on_create,
77      edc_identity_hub_context,
78      requesting,
79    )| {
80      event.prevent_default();
81      let edc_identity_hub_context = edc_identity_hub_context.clone();
82      let on_create = on_create.clone();
83      let selected_credentials_supported = (**selected_credentials_supported).clone();
84      let issuer_did = (**issuer_did).clone();
85      let requesting = requesting.clone();
86
87      spawn_local(async move {
88        requesting.set(true);
89        let holder_pid = Uuid::new_v4().to_string();
90
91        let credentials = selected_credentials_supported
92          .iter()
93          .map(|credential| CredentialQuery {
94            format: CredentialFormat::Vc10Jwt,
95            r#type: credential.credential_type.clone(),
96            id: credential.id.clone(),
97          })
98          .collect::<Vec<_>>();
99
100        let body = RequestCredentialBody {
101          issuer_did,
102          holder_pid: holder_pid.clone(),
103          credentials,
104        };
105
106        let identity_hub_client = edc_identity_hub_context.get_client();
107
108        if let Err(error) = identity_hub_client
109          .request_verifiable_credential(edc_identity_hub_context.participant_id(), &body)
110          .await
111        {
112          log::error!("Error requesting verifiable credential: {:?}", error);
113        }
114
115        loop {
116          sleep(std::time::Duration::from_millis(500)).await;
117          log::info!("Waiting for verifiable credential status to be updated...");
118
119          match edc_identity_hub_context
120            .get_client()
121            .get_request_verifiable_credential_status(
122              edc_identity_hub_context.participant_id(),
123              &holder_pid,
124            )
125            .await
126          {
127            Ok(status) if status.status == RequestCredentialState::Issued => {
128              break;
129            }
130            Ok(status) if status.status == RequestCredentialState::Error => {
131              break;
132            }
133            _ => {}
134          }
135        }
136
137        on_create.emit(());
138        requesting.set(false);
139      });
140    },
141  );
142
143  let disabled =
144    (*issuer_did).is_empty() || (*selected_credentials_supported).is_empty() || *requesting;
145
146  let issuer_field = if props.issuers.is_empty() {
147    html!(
148      <TextInput
149        required=true
150        value={(*issuer_did).to_string()}
151        onchange={onchange_issuer_did.clone()}
152      />
153    )
154  } else {
155    let options = props.issuers.iter().map(|issuer| {
156      let did = issuer.did.clone();
157
158      html_nested!(
159        <MenuAction
160          onclick={onchange_issuer_did.reform(move |_| did.clone())}
161          selected={issuer.did == *issuer_did}
162        >
163          <DescriptionList>
164            <DescriptionGroup term={issuer.name.clone()}>
165              <small>{ issuer.did.clone() }</small>
166            </DescriptionGroup>
167          </DescriptionList>
168        </MenuAction>
169      )
170    });
171
172    let label = props
173      .issuers
174      .iter()
175      .find(|issuer| issuer.did == *issuer_did)
176      .map(|issuer| issuer.name.clone())
177      .unwrap_or("Select an issuer".to_string());
178
179    html!(<Dropdown text={label}>{ for options }</Dropdown>)
180  };
181
182  let onselected = use_callback(
183    selected_credentials_supported.clone(),
184    |(checkbox_state, credentials_supported): (CheckboxState, CredentialsSupported),
185     selected_credentials_supported| {
186      let mut list = (**selected_credentials_supported).clone();
187
188      if checkbox_state == CheckboxState::Checked {
189        list.push(credentials_supported)
190      } else {
191        list.retain(|credential| *credential != credentials_supported);
192      }
193
194      selected_credentials_supported.set(list);
195    },
196  );
197
198  let credentials_supported = credentials_supported.iter().map(|credential| {
199    let checked = if (*selected_credentials_supported).contains(credential) { CheckboxState::Checked } else { CheckboxState::Unchecked };
200    let credential_type = credential.credential_type.clone();
201    let credential = credential.clone();
202
203    html!(
204      <Card selectable=true>
205        <CardHeader
206          selectable_actions={yew::props!(CardSelectableActionsObjectProperties {
207                            action:CardSelectableActionsVariant::MultiSelect {
208                                checked,
209                                onchange: onselected.reform(move |checkbox_state| (checkbox_state, credential.clone())),
210                            },
211                            base: yew::props!(CardSelectableActionsObjectBase {
212                                name: "clickable-credentials"
213                            })
214                        })}
215        >
216          { credential_type }
217        </CardHeader>
218      </Card>
219    )
220  });
221
222  html!(
223    <Form {onsubmit}>
224      <FormGroup label="Issuer" required=true>{ issuer_field }</FormGroup>
225      <FormGroup label="Credential Type" required=true>{ for credentials_supported }</FormGroup>
226      <ActionGroup>
227        <Button
228          variant={ButtonVariant::Primary}
229          label="Generate my Verifiable Credential"
230          r#type={ButtonType::Submit}
231          {disabled}
232        />
233      </ActionGroup>
234    </Form>
235  )
236}