@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.
@@ -356,8 +356,29 @@ export declare const CreateSessionResponseValidator: v.UnionSchema<[v.ObjectSche
356
356
  readonly sessionId: v.StringSchema<undefined>;
357
357
  readonly sessionKey: v.StringSchema<undefined>;
358
358
  readonly sessionUrl: v.StringSchema<undefined>;
359
+ readonly authRequestUri: v.StringSchema<undefined>;
359
360
  readonly state: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
360
361
  }, undefined>], undefined>;
362
+ /**
363
+ * The payload passed to the {@link RequestCredentialsOptions.onPresentationRequest} hook for an OpenID4VP session.
364
+ */
365
+ export type PresentationRequestOpenId4vp = {
366
+ type: SessionType.Openid4vp;
367
+ sessionId: string;
368
+ authRequestUri: string;
369
+ };
370
+ /**
371
+ * The payload passed to the {@link RequestCredentialsOptions.onPresentationRequest} hook for a Digital Credentials
372
+ * API session.
373
+ */
374
+ export type PresentationRequestDigitalCredentialsApi = {
375
+ type: SessionType.DigitalCredentialsApi;
376
+ sessionId: string;
377
+ };
378
+ /**
379
+ * The payload passed to the {@link RequestCredentialsOptions.onPresentationRequest} hook.
380
+ */
381
+ export type PresentationRequest = PresentationRequestOpenId4vp | PresentationRequestDigitalCredentialsApi;
361
382
  export type AbortSessionRequest = v.InferOutput<typeof AbortSessionRequestValidator>;
362
383
  export declare const AbortSessionRequestValidator: v.ObjectSchema<{
363
384
  readonly sessionId: v.StringSchema<undefined>;
@@ -1,17 +1,18 @@
1
1
  import * as v from "valibot";
2
2
  import { BaseError } from "../../common";
3
- import { CredentialQuery, Mode, PresentationSessionResult } from "./credential-presentation";
3
+ import { CredentialQuery, Mode, PresentationRequest, PresentationSessionResult } from "./credential-presentation";
4
4
  export declare enum LocalStorageKey {
5
5
  challenge = "mattr_chg",
6
6
  sessionId = "mattr_sid",
7
7
  state = "mattr_st"
8
8
  }
9
9
  export declare const MATTR_SDK_VERSION_HEADER = "x-mattr-sdk-version";
10
- export declare const MATTR_SDK_VERSION_VALUE = "2.2.0";
10
+ export declare const MATTR_SDK_VERSION_VALUE = "2.3.0";
11
11
  export declare enum MessageEventDataType {
12
12
  PresentationCompleted = "PresentationCompleted",// { type: "PresentationCompleted", responseCode, sessionId }
13
13
  PresentationTimeout = "PresentationTimeout",// { type: "PresentationTimeout", sessionId }
14
- PresentationAbort = "PresentationAbort"
14
+ PresentationAbort = "PresentationAbort",// { type: "PresentationAbort", sessionId }
15
+ PresentationRequestUpdated = "PresentationRequestUpdated"
15
16
  }
16
17
  export type SameDeviceRequestCredentialsOptions = {
17
18
  challenge: string;
@@ -29,6 +30,7 @@ export type CrossDeviceRequestCredentialsOptions = {
29
30
  sessionKey: string;
30
31
  sessionId: string;
31
32
  state?: string;
33
+ onPresentationRequest?: (request: PresentationRequest) => void | Promise<void>;
32
34
  };
33
35
  /**
34
36
  * Options for openid4vpConfiguration to request credentials via a same-device flow.
@@ -132,6 +134,17 @@ export type RequestCredentialsOptions = {
132
134
  * appended as a plain query parameter to the auth request URI.
133
135
  */
134
136
  state?: string;
137
+ /**
138
+ * An optional hook invoked once after presentation session creation, and again every time the OpenID4VP
139
+ * auth-request URI is regenerated (cross-device QR code refreshes). Not re-invoked beyond the initial call for
140
+ * same-device or Digital Credentials API sessions.
141
+ *
142
+ * If this hook throws or its returned promise rejects, the in-progress presentation is aborted (the session is
143
+ * aborted server-side and, for cross-device, the iframe is closed) and `requestCredentials()` resolves with an
144
+ * {@link RequestCredentialsErrorType.OnPresentationRequestFailed} error. To continue the presentation flow
145
+ * regardless of an error in the hook, catch and handle the error inside the hook.
146
+ */
147
+ onPresentationRequest?: (request: PresentationRequest) => void | Promise<void>;
135
148
  };
136
149
  export declare const RequestCredentialsOptionsValidator: v.ObjectSchema<{
137
150
  readonly credentialQuery: v.SchemaWithPipe<readonly [v.ArraySchema<v.ObjectSchema<{
@@ -171,6 +184,7 @@ export type MessageEvent = {
171
184
  type: MessageEventDataType;
172
185
  responseCode?: string;
173
186
  sessionId?: string;
187
+ authRequestUri?: string;
174
188
  };
175
189
  origin: string;
176
190
  };
@@ -204,7 +218,8 @@ export type RequestCredentialsResponse = {
204
218
  export declare enum RequestCredentialsErrorType {
205
219
  RequestCredentialsFailed = "RequestCredentialsFailed",
206
220
  Timeout = "Timeout",
207
- Abort = "Abort"
221
+ Abort = "Abort",
222
+ OnPresentationRequestFailed = "OnPresentationRequestFailed"
208
223
  }
209
224
  export declare enum RequestCredentialsErrorMessage {
210
225
  FailedToGetSessionResult = "Failed to get session result",
@@ -216,7 +231,8 @@ export declare enum RequestCredentialsErrorMessage {
216
231
  DcApiResponseParseError = "Failed to parse response from Digital Credentials API",
217
232
  Abort = "User aborted the session",
218
233
  Timeout = "User session timeout",
219
- StateMismatch = "State mismatch between requested session and back-channel result"
234
+ StateMismatch = "State mismatch between requested session and back-channel result",
235
+ OnPresentationRequestFailed = "The onPresentationRequest hook threw an error or rejected"
220
236
  }
221
237
  /**
222
238
  * The error type for the `abortCredentialRequest` function
@@ -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
  * neverthrow -- 4.3.0
@@ -1370,6 +1370,7 @@
1370
1370
  sessionId: string(),
1371
1371
  sessionKey: string(),
1372
1372
  sessionUrl: string(),
1373
+ authRequestUri: string(),
1373
1374
  state: optional(string())
1374
1375
  });
1375
1376
  const CreateSessionResponseValidator = union([ CreateSessionDigitalCredentialsValidator, CreateSessionOpenId4vpResponseValidator ]);
@@ -1386,12 +1387,13 @@
1386
1387
  LocalStorageKey["state"] = "mattr_st";
1387
1388
  })(LocalStorageKey || (LocalStorageKey = {}));
1388
1389
  const MATTR_SDK_VERSION_HEADER = "x-mattr-sdk-version";
1389
- const MATTR_SDK_VERSION_VALUE = "2.2.0";
1390
+ const MATTR_SDK_VERSION_VALUE = "2.3.0";
1390
1391
  var MessageEventDataType;
1391
1392
  (function(MessageEventDataType) {
1392
1393
  MessageEventDataType["PresentationCompleted"] = "PresentationCompleted";
1393
1394
  MessageEventDataType["PresentationTimeout"] = "PresentationTimeout";
1394
1395
  MessageEventDataType["PresentationAbort"] = "PresentationAbort";
1396
+ MessageEventDataType["PresentationRequestUpdated"] = "PresentationRequestUpdated";
1395
1397
  })(MessageEventDataType || (MessageEventDataType = {}));
1396
1398
  const OpenId4vpConfigSameDeviceOptionsValidator = object({
1397
1399
  walletProviderId: optional(string()),
@@ -1418,6 +1420,7 @@
1418
1420
  RequestCredentialsErrorType["RequestCredentialsFailed"] = "RequestCredentialsFailed";
1419
1421
  RequestCredentialsErrorType["Timeout"] = "Timeout";
1420
1422
  RequestCredentialsErrorType["Abort"] = "Abort";
1423
+ RequestCredentialsErrorType["OnPresentationRequestFailed"] = "OnPresentationRequestFailed";
1421
1424
  })(exports.RequestCredentialsErrorType || (exports.RequestCredentialsErrorType = {}));
1422
1425
  var RequestCredentialsErrorMessage;
1423
1426
  (function(RequestCredentialsErrorMessage) {
@@ -1431,6 +1434,7 @@
1431
1434
  RequestCredentialsErrorMessage["Abort"] = "User aborted the session";
1432
1435
  RequestCredentialsErrorMessage["Timeout"] = "User session timeout";
1433
1436
  RequestCredentialsErrorMessage["StateMismatch"] = "State mismatch between requested session and back-channel result";
1437
+ RequestCredentialsErrorMessage["OnPresentationRequestFailed"] = "The onPresentationRequest hook threw an error or rejected";
1434
1438
  })(RequestCredentialsErrorMessage || (RequestCredentialsErrorMessage = {}));
1435
1439
  exports.AbortSessionErrorType = void 0;
1436
1440
  (function(AbortSessionErrorType) {
@@ -1691,7 +1695,7 @@
1691
1695
  body: JSON.stringify(postData)
1692
1696
  });
1693
1697
  const responseResult = await withRetrySafeFetch(fetchResultFn, {
1694
- retries: 2,
1698
+ retries: 4,
1695
1699
  retryHttpStatus: 404
1696
1700
  });
1697
1701
  if (responseResult.isErr()) {
@@ -1732,71 +1736,111 @@
1732
1736
  }
1733
1737
  removeWindowMessageEventListener();
1734
1738
  };
1735
- const receiveMessageHandler = options => async event => {
1736
- const {onComplete: onComplete, onFailure: onFailure, container: container, sessionId: sessionId, apiBaseUrl: apiBaseUrl, challenge: challenge, state: state} = options;
1737
- if (event.origin !== apiBaseUrl) {
1738
- return;
1739
- }
1740
- if (event.data.type === MessageEventDataType.PresentationCompleted && event.data.responseCode && event.data.sessionId) {
1741
- const responseCode = event.data.responseCode;
1742
- const result = await exchangeSessionResult({
1743
- challenge: challenge,
1744
- responseCode: responseCode,
1745
- sessionId: sessionId,
1746
- apiBaseUrl: apiBaseUrl
1747
- });
1748
- if (result.isErr()) {
1739
+ const receiveMessageHandler = options => {
1740
+ let queue = Promise.resolve();
1741
+ const processEvent = async event => {
1742
+ const {onComplete: onComplete, onFailure: onFailure, container: container, sessionId: sessionId, sessionKey: sessionKey, apiBaseUrl: apiBaseUrl, challenge: challenge, state: state, onPresentationRequest: onPresentationRequest, isSettled: isSettled} = options;
1743
+ if (isSettled() || event.origin !== apiBaseUrl) {
1744
+ return;
1745
+ }
1746
+ if (event.data.type === MessageEventDataType.PresentationRequestUpdated && event.data.authRequestUri) {
1747
+ if (onPresentationRequest) {
1748
+ try {
1749
+ await onPresentationRequest({
1750
+ type: SessionType.Openid4vp,
1751
+ sessionId: sessionId,
1752
+ authRequestUri: event.data.authRequestUri
1753
+ });
1754
+ } catch (cause) {
1755
+ if (isSettled()) {
1756
+ return;
1757
+ }
1758
+ onFailure({
1759
+ type: exports.RequestCredentialsErrorType.OnPresentationRequestFailed,
1760
+ message: RequestCredentialsErrorMessage.OnPresentationRequestFailed,
1761
+ cause: cause
1762
+ });
1763
+ closeCrossDeviceModal({
1764
+ container: container
1765
+ });
1766
+ await abortSession({
1767
+ apiBaseUrl: apiBaseUrl,
1768
+ sessionId: sessionId,
1769
+ sessionKey: sessionKey
1770
+ });
1771
+ }
1772
+ }
1773
+ return;
1774
+ }
1775
+ if (event.data.type === MessageEventDataType.PresentationCompleted && event.data.responseCode && event.data.sessionId) {
1776
+ const responseCode = event.data.responseCode;
1777
+ const result = await exchangeSessionResult({
1778
+ challenge: challenge,
1779
+ responseCode: responseCode,
1780
+ sessionId: sessionId,
1781
+ apiBaseUrl: apiBaseUrl
1782
+ });
1783
+ if (isSettled()) {
1784
+ return;
1785
+ }
1786
+ if (result.isErr()) {
1787
+ onFailure({
1788
+ type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
1789
+ message: RequestCredentialsErrorMessage.FailedToGetSessionResult
1790
+ });
1791
+ closeCrossDeviceModal({
1792
+ container: container
1793
+ });
1794
+ return;
1795
+ }
1796
+ const resultState = "challenge" in result.value ? result.value.state : undefined;
1797
+ if (state !== undefined && resultState !== undefined && state !== resultState) {
1798
+ onFailure({
1799
+ type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
1800
+ message: RequestCredentialsErrorMessage.StateMismatch
1801
+ });
1802
+ closeCrossDeviceModal({
1803
+ container: container
1804
+ });
1805
+ return;
1806
+ }
1807
+ onComplete({
1808
+ result: "challenge" in result.value ? result.value : undefined,
1809
+ sessionId: result.value.sessionId,
1810
+ sessionCompletedInRedirect: false,
1811
+ state: resultState !== null && resultState !== void 0 ? resultState : state
1812
+ });
1813
+ closeCrossDeviceModal({
1814
+ container: container
1815
+ });
1816
+ return;
1817
+ }
1818
+ if (event.data.type === MessageEventDataType.PresentationTimeout) {
1749
1819
  onFailure({
1750
- type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
1751
- message: RequestCredentialsErrorMessage.FailedToGetSessionResult
1820
+ type: exports.RequestCredentialsErrorType.Timeout,
1821
+ message: RequestCredentialsErrorMessage.Timeout
1752
1822
  });
1753
1823
  closeCrossDeviceModal({
1754
1824
  container: container
1755
1825
  });
1756
1826
  return;
1757
1827
  }
1758
- const resultState = "challenge" in result.value ? result.value.state : undefined;
1759
- if (state !== undefined && resultState !== undefined && state !== resultState) {
1828
+ if (event.data.type === MessageEventDataType.PresentationAbort) {
1760
1829
  onFailure({
1761
- type: exports.RequestCredentialsErrorType.RequestCredentialsFailed,
1762
- message: RequestCredentialsErrorMessage.StateMismatch
1830
+ type: exports.RequestCredentialsErrorType.Abort,
1831
+ message: RequestCredentialsErrorMessage.Abort
1763
1832
  });
1764
1833
  closeCrossDeviceModal({
1765
1834
  container: container
1766
1835
  });
1767
1836
  return;
1768
1837
  }
1769
- onComplete({
1770
- result: "challenge" in result.value ? result.value : undefined,
1771
- sessionId: result.value.sessionId,
1772
- sessionCompletedInRedirect: false,
1773
- state: resultState !== null && resultState !== void 0 ? resultState : state
1774
- });
1775
- closeCrossDeviceModal({
1776
- container: container
1777
- });
1778
- return;
1779
- }
1780
- if (event.data.type === MessageEventDataType.PresentationTimeout) {
1781
- onFailure({
1782
- type: exports.RequestCredentialsErrorType.Timeout,
1783
- message: RequestCredentialsErrorMessage.Timeout
1784
- });
1785
- closeCrossDeviceModal({
1786
- container: container
1787
- });
1788
- return;
1789
- }
1790
- if (event.data.type === MessageEventDataType.PresentationAbort) {
1791
- onFailure({
1792
- type: exports.RequestCredentialsErrorType.Abort,
1793
- message: RequestCredentialsErrorMessage.Abort
1794
- });
1795
- closeCrossDeviceModal({
1796
- container: container
1797
- });
1798
- return;
1799
- }
1838
+ };
1839
+ return event => {
1840
+ const next = queue.then((() => processEvent(event)));
1841
+ queue = next.catch((() => undefined));
1842
+ return next;
1843
+ };
1800
1844
  };
1801
1845
  const openCrossDeviceModal = options => {
1802
1846
  const {sessionUrl: sessionUrl} = options;
@@ -1814,16 +1858,18 @@
1814
1858
  return modalContainer;
1815
1859
  };
1816
1860
  const requestCredentialsWithCrossDevice = async options => {
1817
- const {challenge: challenge, apiBaseUrl: apiBaseUrl, sessionUrl: sessionUrl, sessionId: sessionId, sessionKey: sessionKey, state: state} = options;
1861
+ const {challenge: challenge, apiBaseUrl: apiBaseUrl, sessionUrl: sessionUrl, sessionId: sessionId, sessionKey: sessionKey, state: state, onPresentationRequest: onPresentationRequest} = options;
1818
1862
  const container = openCrossDeviceModal({
1819
1863
  sessionUrl: sessionUrl
1820
1864
  });
1865
+ let settled = false;
1821
1866
  return await new Promise((resolve => {
1822
1867
  const abortController = setActiveSession({
1823
1868
  sessionId: sessionId,
1824
1869
  sessionKey: sessionKey
1825
1870
  });
1826
1871
  abortController.signal.addEventListener("abort", (() => {
1872
+ settled = true;
1827
1873
  closeCrossDeviceModal({
1828
1874
  container: container
1829
1875
  });
@@ -1836,11 +1882,20 @@
1836
1882
  listener = receiveMessageHandler({
1837
1883
  container: container,
1838
1884
  sessionId: sessionId,
1885
+ sessionKey: sessionKey,
1839
1886
  apiBaseUrl: apiBaseUrl,
1840
1887
  challenge: challenge,
1841
1888
  state: state,
1842
- onComplete: data => resolve(ok(data)),
1843
- onFailure: error => resolve(err(error))
1889
+ onPresentationRequest: onPresentationRequest,
1890
+ isSettled: () => settled,
1891
+ onComplete: data => {
1892
+ settled = true;
1893
+ resolve(ok(data));
1894
+ },
1895
+ onFailure: error => {
1896
+ settled = true;
1897
+ resolve(err(error));
1898
+ }
1844
1899
  });
1845
1900
  window.addEventListener(WindowEventListenerType.message, listener, false);
1846
1901
  }));
@@ -2074,7 +2129,7 @@
2074
2129
  }
2075
2130
  assertType(RequestCredentialsOptionsValidator, "Invalid request credential options")(options);
2076
2131
  const {apiBaseUrl: apiBaseUrl, applicationId: applicationId} = initializeOptions;
2077
- const {challenge: challenge = generateChallenge(), credentialQuery: credentialQuery, openid4vpConfiguration: openid4vpConfiguration, state: state} = options;
2132
+ const {challenge: challenge = generateChallenge(), credentialQuery: credentialQuery, openid4vpConfiguration: openid4vpConfiguration, state: state, onPresentationRequest: onPresentationRequest} = options;
2078
2133
  const dcApiSupported = isDigitalCredentialsApiSupported();
2079
2134
  const openId4VpRedirectUri = deriveOpenId4vpRedirectUri(openid4vpConfiguration);
2080
2135
  const createSessionResult = await createSession({
@@ -2096,6 +2151,30 @@
2096
2151
  }
2097
2152
  const session = createSessionResult.value;
2098
2153
  const {sessionKey: sessionKey, sessionId: sessionId} = session;
2154
+ if (onPresentationRequest) {
2155
+ const presentationRequest = session.type === SessionType.DigitalCredentialsApi ? {
2156
+ type: SessionType.DigitalCredentialsApi,
2157
+ sessionId: sessionId
2158
+ } : {
2159
+ type: SessionType.Openid4vp,
2160
+ sessionId: sessionId,
2161
+ authRequestUri: session.authRequestUri
2162
+ };
2163
+ try {
2164
+ await onPresentationRequest(presentationRequest);
2165
+ } catch (cause) {
2166
+ await abortSession({
2167
+ apiBaseUrl: apiBaseUrl,
2168
+ sessionId: sessionId,
2169
+ sessionKey: sessionKey
2170
+ });
2171
+ return err({
2172
+ type: exports.RequestCredentialsErrorType.OnPresentationRequestFailed,
2173
+ message: RequestCredentialsErrorMessage.OnPresentationRequestFailed,
2174
+ cause: cause
2175
+ });
2176
+ }
2177
+ }
2099
2178
  if (session.type === SessionType.DigitalCredentialsApi) {
2100
2179
  const {request: request, sessionTtl: sessionTtl} = session;
2101
2180
  return await requestCredentialsWithDigitalCredentialsApi({
@@ -2131,7 +2210,8 @@
2131
2210
  sessionUrl: sessionUrl,
2132
2211
  sessionKey: sessionKey,
2133
2212
  sessionId: sessionId,
2134
- state: state
2213
+ state: state,
2214
+ onPresentationRequest: onPresentationRequest
2135
2215
  });
2136
2216
  };
2137
2217
  const deriveOpenId4vpRedirectUri = openid4vpConfiguration => {