edc_web_components/components/
request_verifiable_credential.rs1use 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}