@mattrglobal/verifier-sdk-web 2.2.1-unstable.9 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,7 @@
21
21
  - [Define wallet identifiers](#define-wallet-identifiers)
22
22
  - [Configure redirectUri](#configure-redirecturi)
23
23
  - [Correlate sessions with your own records using state](#correlate-sessions-with-your-own-records-using-state)
24
+ - [React to presentation requests using onPresentationRequest](#react-to-presentation-requests-using-onpresentationrequest)
24
25
  - [Request credentials using the DC API](#request-credentials-using-the-dc-api)
25
26
  - [Request credentials examples](#request-credentials-examples)
26
27
  - [Request credentials with automatic flow selection](#request-credentials-with-automatic-flow-selection)
@@ -39,12 +40,10 @@ Request or download the
39
40
  ## Overview
40
41
 
41
42
  The Verifier Web SDK is a powerful tool for integrating online credential verification capabilities into your web
42
- applications. It enables secure and efficient verification of [mDocs](https://learn.mattr.global/docs/mdocs), supporting
43
- both [same-device](https://learn.mattr.global/docs/verification/online/mdocs/overview#same-device-verification-workflow)
44
- and
45
- [cross-device](https://learn.mattr.global/docs/verification/online/mdocs/overview#cross-device-verification-workflow)
46
- verification workflows. The SDK leverages the MATTR VII platform to handle credential presentation and verification
47
- processes.
43
+ applications. It enables secure and efficient verification of [mDocs](https://learn.mattr.global/docs/concepts/mdocs),
44
+ supporting both [same-device](https://learn.mattr.global/docs/verification/remote-overview#verification-flows) and
45
+ [cross-device](https://learn.mattr.global/docs/verification/remote-overview#verification-flows) verification workflows.
46
+ The SDK leverages the MATTR VII platform to handle credential presentation and verification processes.
48
47
 
49
48
  ## Features
50
49
 
@@ -60,8 +59,8 @@ processes.
60
59
 
61
60
  ## How to get access to the MATTR Pi Verifier Web SDK
62
61
 
63
- Refer to our [SDK Docs landing page](https://learn.mattr.global/guides/get-started-pi) for step-by-step instructions to
64
- gain access to any of our SDKs.
62
+ Refer to our [SDK Docs landing page](https://learn.mattr.global/docs/resources/get-started) for step-by-step
63
+ instructions to gain access to any of our SDKs.
65
64
 
66
65
  > Please [reach out](mailto:dev-support@mattr.global) if you need any assistance.
67
66
 
@@ -115,6 +114,8 @@ the following configurations and settings:
115
114
  same-device flows).
116
115
  - Optionally pass a `state` string to correlate the session with a record in your own system (see
117
116
  [Correlate sessions with your own records using state](#correlate-sessions-with-your-own-records-using-state)).
117
+ - Optionally pass an `onPresentationRequest` hook to run your own logic each time a presentation request is issued (see
118
+ [React to presentation requests using onPresentationRequest](#react-to-presentation-requests-using-onpresentationrequest)).
118
119
 
119
120
  ## Initialize the SDK
120
121
 
@@ -126,9 +127,9 @@ MATTRVerifierSDK.initialize({ apiBaseUrl, applicationId });
126
127
 
127
128
  - `apiBaseUrl` (required): URL of the MATTR VII verifier tenant.
128
129
  - `applicationId` (required): Unique identifier of the verifier application. This must match the
129
- [`id`](https://learn.mattr.global/api-reference/latest/tag/Verifier-applications#operation/postVerifierApplication!c=201&path=0/id&t=response)
130
- parameter in the response returned when
131
- [creating a Verifier application](https://learn.mattr.global/api-reference/latest/tag/Verifier-applications#operation/postVerifierApplication)
130
+ [`id`](https://learn.mattr.global/docs/api-reference/platform/verifier-applications/postVerifierApplication) parameter
131
+ in the response returned when
132
+ [creating a Verifier application](https://learn.mattr.global/docs/api-reference/platform/verifier-applications/postVerifierApplication)
132
133
  on the MATTR VII verifier tenant.
133
134
 
134
135
  ## Prepare a credential query
@@ -227,13 +228,13 @@ This behavior can be explicitly overridden by specifying the desired mode in the
227
228
 
228
229
  You can define an identifier of a specific wallet you want to invoke with this verification request. The identifier
229
230
  defined by the SDK in the credential request must match one of the identifiers of a
230
- [wallet provider](https://learn.mattr.global/api-reference/latest/tag/Wallet-providers#operation/postWalletProvider!path=name&t=request)
231
- created on the MATTR VII verifier tenant.
231
+ [wallet provider](https://learn.mattr.global/docs/api-reference/platform/wallet-providers/postWalletProvider) created on
232
+ the MATTR VII verifier tenant.
232
233
 
233
234
  - If an identifier is provided and matches the `id` of one of the objects in the `walletProviders` array, the verifier
234
235
  tenant will invoke that specific wallet using its corresponding
235
- [`authorizationEndpoint`](https://learn.mattr.global/api-reference/latest/tag/Wallet-providers#operation/postWalletProvider!path=openid4vpConfiguration/authorizationEndpoint&t=request).
236
- - If an identifier is provided and does not match the `id` of any of the objects in the `walletProviders array`, the
236
+ [`authorizationEndpoint`](https://learn.mattr.global/docs/api-reference/platform/wallet-providers/postWalletProvider).
237
+ - If an identifier is provided and does not match the `id` of any of the objects in the `walletProviders` array, the
237
238
  request will fail.
238
239
  - If an identifier is not provided, the verifier tenant will use `mdoc-openid4vp://` (default OID4VP scheme) to invoke
239
240
  any wallet.
@@ -243,7 +244,7 @@ created on the MATTR VII verifier tenant.
243
244
  When using the same-device presentation flow, the SDK must define what URI to redirect the user to once they complete
244
245
  the verification workflow in their wallet app. This can be any URI (including custom URI schemes), and must match one of
245
246
  the values defined in the
246
- [`redirectUris` array](https://learn.mattr.global/api-reference/latest/tag/Verifier-applications#operation/postVerifierApplication!path=0/openid4vpConfiguration/redirectUris&t=request)
247
+ [`redirectUris` array](https://learn.mattr.global/docs/api-reference/platform/verifier-applications/postVerifierApplication)
247
248
  when creating a verifier application.
248
249
 
249
250
  ## Correlate sessions with your own records using `state`
@@ -340,6 +341,67 @@ MATTR VII platform and the SDK, see the
340
341
  [Correlating verification sessions with your system](https://learn.mattr.global/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions)
341
342
  guide on MATTR Learn.
342
343
 
344
+ ## React to presentation requests using `onPresentationRequest`
345
+
346
+ `requestCredentials()` accepts an optional `onPresentationRequest` hook that the SDK invokes once immediately after the
347
+ presentation session has been created on the MATTR VII verifier tenant, and again every time the OpenID4VP auth-request
348
+ URI is regenerated — for cross-device sessions this happens whenever the QR code shown in the iframe is refreshed.
349
+ Same-device and Digital Credentials API sessions only ever invoke the hook once, since there is no refreshable request
350
+ URI in those flows. Use it to run your own logic against the current request, for example mirroring the `authRequestUri`
351
+ into your own system, starting a timer, or emitting analytics.
352
+
353
+ The hook receives the current presentation request and may be synchronous or asynchronous; the SDK awaits it before
354
+ continuing:
355
+
356
+ ```typescript
357
+ const options: MATTRVerifierSDK.RequestCredentialsOptions = {
358
+ credentialQuery: [credentialQuery],
359
+ challenge: MATTRVerifierSDK.utils.generateChallenge(),
360
+ openid4vpConfiguration: {
361
+ redirectUri: window.location.origin,
362
+ walletProviderId,
363
+ },
364
+ onPresentationRequest: async (request) => {
365
+ // e.g. record the current auth-request URI against your own system
366
+ if (request.type === "openid4vp") {
367
+ await fetch("/api/presentation-requests", {
368
+ method: "POST",
369
+ headers: { "Content-Type": "application/json" },
370
+ body: JSON.stringify({ sessionId: request.sessionId, authRequestUri: request.authRequestUri }),
371
+ });
372
+ }
373
+ },
374
+ };
375
+
376
+ const results = await MATTRVerifierSDK.requestCredentials(options);
377
+ ```
378
+
379
+ - The hook fires once for same-device and Digital Credentials API sessions, and once immediately plus again on every QR
380
+ refresh for cross-device sessions.
381
+ - The `request` argument is a discriminated union on `type`: for OpenID4VP sessions (`type: "openid4vp"`) it exposes
382
+ `sessionId` and `authRequestUri`; for Digital Credentials API sessions (`type: "digital-credentials-api"`) it exposes
383
+ only `sessionId`.
384
+ - If the hook throws or its returned promise rejects on any firing, the in-progress presentation is aborted — the
385
+ session is aborted server-side and, for cross-device flows, the iframe is closed — and `requestCredentials()` resolves
386
+ with an `OnPresentationRequestFailed` error (see `RequestCredentialsErrorType`). This applies consistently whether the
387
+ failing firing was the initial one or a later cross-device refresh. If you want the presentation flow to continue
388
+ regardless of an error in the hook, catch and handle the error yourself inside the hook, for example:
389
+
390
+ ```typescript
391
+ onPresentationRequest: async (request) => {
392
+ try {
393
+ await fetch("/api/presentation-requests", {
394
+ method: "POST",
395
+ headers: { "Content-Type": "application/json" },
396
+ body: JSON.stringify(request),
397
+ });
398
+ } catch (error) {
399
+ // Swallow the error here so requestCredentials() continues with the presentation flow
400
+ console.error("Failed to record presentation request", error);
401
+ }
402
+ },
403
+ ```
404
+
343
405
  ## Request credentials using the DC API
344
406
 
345
407
  > DC API support is currently offered as a **tech preview**. As such, functionality may be limited, may not work in all
@@ -396,6 +458,10 @@ const result = await MATTRVerifierSDK.requestCredentials({
396
458
  walletProviderId, // Define the wallet identifier
397
459
  redirectUri, // Define the redirect URI (not required for cross-device only requests)
398
460
  },
461
+ onPresentationRequest: (request) => {
462
+ // Optional: run your own logic each time a presentation request is issued
463
+ console.info("Presentation request", request.sessionId);
464
+ },
399
465
  });
400
466
 
401
467
  if (result.isErr()) {
@@ -405,24 +471,30 @@ if (result.isErr()) {
405
471
  }
406
472
  ```
407
473
 
408
- - `apiBaseUrl` : Replace with the [`tenant_url`](https://learn.mattr.global/docs/platform-management/authentication) of
409
- your MATTR VII verifier tenant.
474
+ - `apiBaseUrl`: Replace with the
475
+ [`tenant_url`](https://learn.mattr.global/docs/platform-management/management-api/overview) of your MATTR VII verifier
476
+ tenant.
410
477
  - `credentialQuery`: The credential query to be used in the request.
411
478
  - `challenge`: The challenge that will be passed to the MATTR VII tenant with the request to create a presentation
412
479
  session. This example uses the SDK built-in method to generate the challenge, but you can replace it with a challenge
413
480
  generated by your backend system.
414
481
  - `walletProviderId`: Replace with a wallet identifier that matches one of the identifiers of a
415
- [wallet provider](https://learn.mattr.global/api-reference/latest/tag/Wallet-providers#operation/postWalletProvider!path=name&t=request)
416
- created on the MATTR VII verifier tenant..
482
+ [wallet provider](https://learn.mattr.global/docs/api-reference/platform/wallet-providers/postWalletProvider) created
483
+ on the MATTR VII verifier tenant.
417
484
  - `state` (optional): Your own opaque correlation reference for this presentation session. Do not include personally
418
485
  identifiable information (PII), credentials, or anything sensitive, as the value is transmitted in plain text on the
419
486
  wallet-facing request URI and stored in session records. Returned in the response from `requestCredentials()`
420
487
  (cross-device) or `handleRedirectCallback()` (same-device). See
421
488
  [Correlate sessions with your own records using state](#correlate-sessions-with-your-own-records-using-state).
489
+ - `onPresentationRequest` (optional): A hook invoked once with the current presentation request after the session is
490
+ created, and again on every cross-device QR refresh. If the hook throws or rejects on any firing, the in-progress
491
+ presentation is aborted and `requestCredentials()` resolves with an `OnPresentationRequestFailed` error; catch and
492
+ handle errors inside the hook yourself if you want the flow to continue regardless. See
493
+ [React to presentation requests using onPresentationRequest](#react-to-presentation-requests-using-onpresentationrequest).
422
494
  - `mode`: When omitted, the SDK defaults to automatically selecting a flow based on the browser's user agent (set to
423
495
  `undefined` in the example for clarity).
424
- - `redirectUri` Replace with a URI that matches one of the values in the
425
- [`redirectUris` array](https://learn.mattr.global/api-reference/latest/tag/Verifier-applications#operation/postVerifierApplication!path=0/openid4vpConfiguration/redirectUris&t=request)
496
+ - `redirectUri`: Replace with a URI that matches one of the values in the
497
+ [`redirectUris` array](https://learn.mattr.global/docs/api-reference/platform/verifier-applications/postVerifierApplication)
426
498
  in the MATTR VII tenant's verifier application.
427
499
 
428
500
  ## Request credentials with explicit same-device flow
@@ -7,8 +7,8 @@
7
7
  * Do Not Translate or Localize
8
8
  *
9
9
  * Bundle of @mattrglobal/verifier-sdk-web
10
- * Generated: 2026-06-19
11
- * Version: 2.2.0
10
+ * Generated: 2026-08-17
11
+ * Version: 2.3.0
12
12
  * Dependencies:
13
13
  */
14
14
 
@@ -221,6 +221,7 @@ const CreateSessionOpenId4vpResponseValidator = v__namespace.object({
221
221
  sessionId: v__namespace.string(),
222
222
  sessionKey: v__namespace.string(),
223
223
  sessionUrl: v__namespace.string(),
224
+ authRequestUri: v__namespace.string(),
224
225
  state: v__namespace.optional(v__namespace.string())
225
226
  });
226
227
 
@@ -254,7 +255,7 @@ var LocalStorageKey;
254
255
 
255
256
  const MATTR_SDK_VERSION_HEADER = "x-mattr-sdk-version";
256
257
 
257
- const MATTR_SDK_VERSION_VALUE = "2.2.0";
258
+ const MATTR_SDK_VERSION_VALUE = "2.3.0";
258
259
 
259
260
  var MessageEventDataType;
260
261
 
@@ -262,6 +263,7 @@ var MessageEventDataType;
262
263
  MessageEventDataType["PresentationCompleted"] = "PresentationCompleted";
263
264
  MessageEventDataType["PresentationTimeout"] = "PresentationTimeout";
264
265
  MessageEventDataType["PresentationAbort"] = "PresentationAbort";
266
+ MessageEventDataType["PresentationRequestUpdated"] = "PresentationRequestUpdated";
265
267
  })(MessageEventDataType || (MessageEventDataType = {}));
266
268
 
267
269
  const OpenId4vpConfigSameDeviceOptionsValidator = v__namespace.object({
@@ -294,6 +296,7 @@ exports.RequestCredentialsErrorType = void 0;
294
296
  RequestCredentialsErrorType["RequestCredentialsFailed"] = "RequestCredentialsFailed";
295
297
  RequestCredentialsErrorType["Timeout"] = "Timeout";
296
298
  RequestCredentialsErrorType["Abort"] = "Abort";
299
+ RequestCredentialsErrorType["OnPresentationRequestFailed"] = "OnPresentationRequestFailed";
297
300
  })(exports.RequestCredentialsErrorType || (exports.RequestCredentialsErrorType = {}));
298
301
 
299
302
  var RequestCredentialsErrorMessage;
@@ -309,6 +312,7 @@ var RequestCredentialsErrorMessage;
309
312
  RequestCredentialsErrorMessage["Abort"] = "User aborted the session";
310
313
  RequestCredentialsErrorMessage["Timeout"] = "User session timeout";
311
314
  RequestCredentialsErrorMessage["StateMismatch"] = "State mismatch between requested session and back-channel result";
315
+ RequestCredentialsErrorMessage["OnPresentationRequestFailed"] = "The onPresentationRequest hook threw an error or rejected";
312
316
  })(RequestCredentialsErrorMessage || (RequestCredentialsErrorMessage = {}));
313
317
 
314
318
  exports.AbortSessionErrorType = void 0;
@@ -579,7 +583,7 @@ const exchangeSessionResult = async ({challenge: challenge, responseCode: respon
579
583
  body: JSON.stringify(postData)
580
584
  });
581
585
  const responseResult = await withRetrySafeFetch(fetchResultFn, {
582
- retries: 2,
586
+ retries: 4,
583
587
  retryHttpStatus: 404
584
588
  });
585
589
  if (responseResult.isErr()) {
@@ -628,71 +632,111 @@ const closeCrossDeviceModal = options => {
628
632
  removeWindowMessageEventListener();
629
633
  };
630
634
 
631
- const receiveMessageHandler = options => async event => {
632
- const {onComplete: onComplete, onFailure: onFailure, container: container, sessionId: sessionId, apiBaseUrl: apiBaseUrl, challenge: challenge, state: state} = options;
633
- if (event.origin !== apiBaseUrl) {
634
- return;
635
- }
636
- if (event.data.type === MessageEventDataType.PresentationCompleted && event.data.responseCode && event.data.sessionId) {
637
- const responseCode = event.data.responseCode;
638
- const result = await exchangeSessionResult({
639
- challenge: challenge,
640
- responseCode: responseCode,
641
- sessionId: sessionId,
642
- apiBaseUrl: apiBaseUrl
643
- });
644
- if (result.isErr()) {
635
+ const receiveMessageHandler = options => {
636
+ let queue = Promise.resolve();
637
+ const processEvent = async event => {
638
+ const {onComplete: onComplete, onFailure: onFailure, container: container, sessionId: sessionId, sessionKey: sessionKey, apiBaseUrl: apiBaseUrl, challenge: challenge, state: state, onPresentationRequest: onPresentationRequest, isSettled: isSettled} = options;
639
+ if (isSettled() || event.origin !== apiBaseUrl) {
640
+ return;
641
+ }
642
+ if (event.data.type === MessageEventDataType.PresentationRequestUpdated && event.data.authRequestUri) {
643
+ if (onPresentationRequest) {
644
+ try {
645
+ await onPresentationRequest({
646
+ type: SessionType.Openid4vp,
647
+ sessionId: sessionId,
648
+ authRequestUri: event.data.authRequestUri
649
+ });
650
+ } catch (cause) {
651
+ if (isSettled()) {
652
+ return;
653
+ }
654
+ onFailure({
655
+ type: exports.RequestCredentialsErrorType.OnPresentationRequestFailed,
656
+ message: RequestCredentialsErrorMessage.OnPresentationRequestFailed,
657
+ cause: cause
658
+ });
659
+ closeCrossDeviceModal({
660
+ container: container
661
+ });
662
+ await abortSession({
663
+ apiBaseUrl: apiBaseUrl,
664
+ sessionId: sessionId,
665
+ sessionKey: sessionKey
666
+ });
667
+ }
668
+ }
669
+ return;
670
+ }
671
+ if (event.data.type === MessageEventDataType.PresentationCompleted && event.data.responseCode && event.data.sessionId) {
672
+ const responseCode = event.data.responseCode;
673
+ const result = await exchangeSessionResult({
674
+ challenge: challenge,
675
+ responseCode: responseCode,
676
+ sessionId: sessionId,
677
+ apiBaseUrl: apiBaseUrl
678
+ });
679
+ if (isSettled()) {
680
+ return;
681
+ }
682
+ if (result.isErr()) {
683
+ onFailure({
684
+ type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
685
+ message: RequestCredentialsErrorMessage.FailedToGetSessionResult
686
+ });
687
+ closeCrossDeviceModal({
688
+ container: container
689
+ });
690
+ return;
691
+ }
692
+ const resultState = "challenge" in result.value ? result.value.state : undefined;
693
+ if (state !== undefined && resultState !== undefined && state !== resultState) {
694
+ onFailure({
695
+ type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
696
+ message: RequestCredentialsErrorMessage.StateMismatch
697
+ });
698
+ closeCrossDeviceModal({
699
+ container: container
700
+ });
701
+ return;
702
+ }
703
+ onComplete({
704
+ result: "challenge" in result.value ? result.value : undefined,
705
+ sessionId: result.value.sessionId,
706
+ sessionCompletedInRedirect: false,
707
+ state: resultState !== null && resultState !== void 0 ? resultState : state
708
+ });
709
+ closeCrossDeviceModal({
710
+ container: container
711
+ });
712
+ return;
713
+ }
714
+ if (event.data.type === MessageEventDataType.PresentationTimeout) {
645
715
  onFailure({
646
- type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
647
- message: RequestCredentialsErrorMessage.FailedToGetSessionResult
716
+ type: exports.RequestCredentialsErrorType.Timeout,
717
+ message: RequestCredentialsErrorMessage.Timeout
648
718
  });
649
719
  closeCrossDeviceModal({
650
720
  container: container
651
721
  });
652
722
  return;
653
723
  }
654
- const resultState = "challenge" in result.value ? result.value.state : undefined;
655
- if (state !== undefined && resultState !== undefined && state !== resultState) {
724
+ if (event.data.type === MessageEventDataType.PresentationAbort) {
656
725
  onFailure({
657
- type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
658
- message: RequestCredentialsErrorMessage.StateMismatch
726
+ type: exports.RequestCredentialsErrorType.Abort,
727
+ message: RequestCredentialsErrorMessage.Abort
659
728
  });
660
729
  closeCrossDeviceModal({
661
730
  container: container
662
731
  });
663
732
  return;
664
733
  }
665
- onComplete({
666
- result: "challenge" in result.value ? result.value : undefined,
667
- sessionId: result.value.sessionId,
668
- sessionCompletedInRedirect: false,
669
- state: resultState !== null && resultState !== void 0 ? resultState : state
670
- });
671
- closeCrossDeviceModal({
672
- container: container
673
- });
674
- return;
675
- }
676
- if (event.data.type === MessageEventDataType.PresentationTimeout) {
677
- onFailure({
678
- type: exports.RequestCredentialsErrorType.Timeout,
679
- message: RequestCredentialsErrorMessage.Timeout
680
- });
681
- closeCrossDeviceModal({
682
- container: container
683
- });
684
- return;
685
- }
686
- if (event.data.type === MessageEventDataType.PresentationAbort) {
687
- onFailure({
688
- type: exports.RequestCredentialsErrorType.Abort,
689
- message: RequestCredentialsErrorMessage.Abort
690
- });
691
- closeCrossDeviceModal({
692
- container: container
693
- });
694
- return;
695
- }
734
+ };
735
+ return event => {
736
+ const next = queue.then((() => processEvent(event)));
737
+ queue = next.catch((() => undefined));
738
+ return next;
739
+ };
696
740
  };
697
741
 
698
742
  const openCrossDeviceModal = options => {
@@ -712,16 +756,18 @@ const openCrossDeviceModal = options => {
712
756
  };
713
757
 
714
758
  const requestCredentialsWithCrossDevice = async options => {
715
- const {challenge: challenge, apiBaseUrl: apiBaseUrl, sessionUrl: sessionUrl, sessionId: sessionId, sessionKey: sessionKey, state: state} = options;
759
+ const {challenge: challenge, apiBaseUrl: apiBaseUrl, sessionUrl: sessionUrl, sessionId: sessionId, sessionKey: sessionKey, state: state, onPresentationRequest: onPresentationRequest} = options;
716
760
  const container = openCrossDeviceModal({
717
761
  sessionUrl: sessionUrl
718
762
  });
763
+ let settled = false;
719
764
  return await new Promise((resolve => {
720
765
  const abortController = setActiveSession({
721
766
  sessionId: sessionId,
722
767
  sessionKey: sessionKey
723
768
  });
724
769
  abortController.signal.addEventListener("abort", (() => {
770
+ settled = true;
725
771
  closeCrossDeviceModal({
726
772
  container: container
727
773
  });
@@ -734,11 +780,20 @@ const requestCredentialsWithCrossDevice = async options => {
734
780
  listener = receiveMessageHandler({
735
781
  container: container,
736
782
  sessionId: sessionId,
783
+ sessionKey: sessionKey,
737
784
  apiBaseUrl: apiBaseUrl,
738
785
  challenge: challenge,
739
786
  state: state,
740
- onComplete: data => resolve(neverthrow.ok(data)),
741
- onFailure: error => resolve(neverthrow.err(error))
787
+ onPresentationRequest: onPresentationRequest,
788
+ isSettled: () => settled,
789
+ onComplete: data => {
790
+ settled = true;
791
+ resolve(neverthrow.ok(data));
792
+ },
793
+ onFailure: error => {
794
+ settled = true;
795
+ resolve(neverthrow.err(error));
796
+ }
742
797
  });
743
798
  window.addEventListener(WindowEventListenerType.message, listener, false);
744
799
  }));
@@ -986,7 +1041,7 @@ const requestCredentials = async options => {
986
1041
  }
987
1042
  assertType(RequestCredentialsOptionsValidator, "Invalid request credential options")(options);
988
1043
  const {apiBaseUrl: apiBaseUrl, applicationId: applicationId} = initializeOptions;
989
- const {challenge: challenge = generateChallenge(), credentialQuery: credentialQuery, openid4vpConfiguration: openid4vpConfiguration, state: state} = options;
1044
+ const {challenge: challenge = generateChallenge(), credentialQuery: credentialQuery, openid4vpConfiguration: openid4vpConfiguration, state: state, onPresentationRequest: onPresentationRequest} = options;
990
1045
  const dcApiSupported = isDigitalCredentialsApiSupported();
991
1046
  const openId4VpRedirectUri = deriveOpenId4vpRedirectUri(openid4vpConfiguration);
992
1047
  const createSessionResult = await createSession({
@@ -1008,6 +1063,30 @@ const requestCredentials = async options => {
1008
1063
  }
1009
1064
  const session = createSessionResult.value;
1010
1065
  const {sessionKey: sessionKey, sessionId: sessionId} = session;
1066
+ if (onPresentationRequest) {
1067
+ const presentationRequest = session.type === SessionType.DigitalCredentialsApi ? {
1068
+ type: SessionType.DigitalCredentialsApi,
1069
+ sessionId: sessionId
1070
+ } : {
1071
+ type: SessionType.Openid4vp,
1072
+ sessionId: sessionId,
1073
+ authRequestUri: session.authRequestUri
1074
+ };
1075
+ try {
1076
+ await onPresentationRequest(presentationRequest);
1077
+ } catch (cause) {
1078
+ await abortSession({
1079
+ apiBaseUrl: apiBaseUrl,
1080
+ sessionId: sessionId,
1081
+ sessionKey: sessionKey
1082
+ });
1083
+ return neverthrow.err({
1084
+ type: exports.RequestCredentialsErrorType.OnPresentationRequestFailed,
1085
+ message: RequestCredentialsErrorMessage.OnPresentationRequestFailed,
1086
+ cause: cause
1087
+ });
1088
+ }
1089
+ }
1011
1090
  if (session.type === SessionType.DigitalCredentialsApi) {
1012
1091
  const {request: request, sessionTtl: sessionTtl} = session;
1013
1092
  return await requestCredentialsWithDigitalCredentialsApi({
@@ -1043,7 +1122,8 @@ const requestCredentials = async options => {
1043
1122
  sessionUrl: sessionUrl,
1044
1123
  sessionKey: sessionKey,
1045
1124
  sessionId: sessionId,
1046
- state: state
1125
+ state: state,
1126
+ onPresentationRequest: onPresentationRequest
1047
1127
  });
1048
1128
  };
1049
1129