@sirosfoundation/dc-api 0.1.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/LICENSE +24 -0
- package/README.md +128 -0
- package/dist/dc-api.bundle.js +106 -0
- package/dist/detect.d.ts +57 -0
- package/dist/detect.js +81 -0
- package/dist/errors.d.ts +19 -0
- package/dist/errors.js +40 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/protocols.d.ts +34 -0
- package/dist/protocols.js +42 -0
- package/dist/request.d.ts +54 -0
- package/dist/request.js +65 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, SIROS Foundation
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @sirosfoundation/dc-api
|
|
2
|
+
|
|
3
|
+
W3C Digital Credentials API utilities for OpenID4VP.
|
|
4
|
+
|
|
5
|
+
Zero-dependency, backend-agnostic library providing:
|
|
6
|
+
|
|
7
|
+
- **Protocol constants** — versioned OpenID4VP protocol identifiers per the W3C DC API spec
|
|
8
|
+
- **Feature detection** — check DC API availability and protocol support
|
|
9
|
+
- **Native DC API invocation** — call `navigator.credentials.get()` with proper parameters
|
|
10
|
+
- **Error helpers** — classify errors and generate user-friendly messages
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @sirosfoundation/dc-api
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A pre-built ESM bundle is also available at `dist/dc-api.bundle.js` for use in
|
|
19
|
+
importmaps or environments without a bundler.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Requesting credentials
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
isDCAPIAvailable,
|
|
28
|
+
getBestProtocol,
|
|
29
|
+
requestCredential,
|
|
30
|
+
isUserCancel,
|
|
31
|
+
} from '@sirosfoundation/dc-api';
|
|
32
|
+
|
|
33
|
+
// 1. Check if DC API + openid4vp is available
|
|
34
|
+
const protocol = getBestProtocol();
|
|
35
|
+
|
|
36
|
+
if (protocol) {
|
|
37
|
+
try {
|
|
38
|
+
// 2. Call the native DC API
|
|
39
|
+
const result = await requestCredential(protocol, {
|
|
40
|
+
request: signedJWT, // JAR for "openid4vp-v1-signed"
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// 3. Submit result.data to your backend
|
|
44
|
+
await submitToBackend(result.data);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (isUserCancel(err)) {
|
|
47
|
+
// User cancelled — show alternative flow
|
|
48
|
+
} else {
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// No DC API support — use QR code / redirect fallback
|
|
54
|
+
showQRCode();
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Protocol constants
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { OID4VP_PROTOCOLS, isOID4VPProtocol } from '@sirosfoundation/dc-api';
|
|
62
|
+
|
|
63
|
+
// Use shared constants instead of hardcoding protocol strings
|
|
64
|
+
const supportedProtocols = [
|
|
65
|
+
OID4VP_PROTOCOLS.UNSIGNED,
|
|
66
|
+
OID4VP_PROTOCOLS.SIGNED,
|
|
67
|
+
OID4VP_PROTOCOLS.MULTISIGNED,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// Type guard for filtering
|
|
71
|
+
const known = requests.filter(r => isOID4VPProtocol(r.protocol));
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API
|
|
75
|
+
|
|
76
|
+
### Protocol Constants
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
OID4VP_PROTOCOLS.UNSIGNED // "openid4vp-v1-unsigned"
|
|
80
|
+
OID4VP_PROTOCOLS.SIGNED // "openid4vp-v1-signed"
|
|
81
|
+
OID4VP_PROTOCOLS.MULTISIGNED // "openid4vp-v1-multisigned"
|
|
82
|
+
OID4VP_PROTOCOLS.LEGACY // "openid4vp"
|
|
83
|
+
|
|
84
|
+
OID4VP_SPEC_PROTOCOLS // [UNSIGNED, SIGNED, MULTISIGNED]
|
|
85
|
+
OID4VP_ALL_PROTOCOLS // [UNSIGNED, SIGNED, MULTISIGNED, LEGACY]
|
|
86
|
+
isOID4VPProtocol(value) // Type guard — true for any known protocol
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Detection
|
|
90
|
+
|
|
91
|
+
| Function | Description |
|
|
92
|
+
|---|---|
|
|
93
|
+
| `isDCAPIAvailable()` | `true` when `typeof DigitalCredential !== "undefined"` |
|
|
94
|
+
| `isProtocolAllowed(protocol)` | Delegates to `DigitalCredential.userAgentAllowsProtocol()` |
|
|
95
|
+
| `getBestProtocol(preference?)` | Returns the first allowed protocol from a preference-ordered list (default: signed > multisigned > unsigned) |
|
|
96
|
+
|
|
97
|
+
### Request
|
|
98
|
+
|
|
99
|
+
| Function | Description |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `requestCredential(protocol, data, options?)` | Calls `navigator.credentials.get({digital: {requests: [{protocol, data}]}})`, returns `{ protocol, data }` |
|
|
102
|
+
|
|
103
|
+
`options.signal` accepts an `AbortSignal` for cancellation.
|
|
104
|
+
|
|
105
|
+
### Error Helpers
|
|
106
|
+
|
|
107
|
+
| Function | Description |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `getUserFriendlyErrorMessage(error)` | Returns a human-readable message for DC API errors |
|
|
110
|
+
| `isUserCancel(error)` | `true` for `NotAllowedError` or `AbortError` |
|
|
111
|
+
| `isProtocolUnsupported(error)` | `true` for `NotSupportedError` |
|
|
112
|
+
| `ERROR_MESSAGES` | Map of DOMException names to user-facing strings |
|
|
113
|
+
|
|
114
|
+
## Design Principles
|
|
115
|
+
|
|
116
|
+
- **Zero dependencies** — no runtime dependencies
|
|
117
|
+
- **Backend-agnostic** — no knowledge of specific verifier or wallet endpoints
|
|
118
|
+
- **Transport-agnostic** — no QR codes, redirects, or SSE; those belong in the consumer
|
|
119
|
+
- **Spec-aligned** — uses the W3C DC API spec's detection and invocation patterns exactly
|
|
120
|
+
|
|
121
|
+
## References
|
|
122
|
+
|
|
123
|
+
- [W3C Digital Credentials API](https://w3c-fedid.github.io/digital-credentials/)
|
|
124
|
+
- [OpenID4VP (DC API profile)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
BSD-2-Clause
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/protocols.ts
|
|
2
|
+
const OID4VP_PROTOCOLS = {
|
|
3
|
+
/** Unsigned request — client_id derived from web-origin */
|
|
4
|
+
UNSIGNED: "openid4vp-v1-unsigned",
|
|
5
|
+
/** Signed request — JAR with single JWS compact serialization */
|
|
6
|
+
SIGNED: "openid4vp-v1-signed",
|
|
7
|
+
/** Multi-signed request — JWS JSON serialization */
|
|
8
|
+
MULTISIGNED: "openid4vp-v1-multisigned",
|
|
9
|
+
/** Legacy protocol string (pre-spec, used by some implementations) */
|
|
10
|
+
LEGACY: "openid4vp"
|
|
11
|
+
};
|
|
12
|
+
const OID4VP_SPEC_PROTOCOLS = [
|
|
13
|
+
OID4VP_PROTOCOLS.UNSIGNED,
|
|
14
|
+
OID4VP_PROTOCOLS.SIGNED,
|
|
15
|
+
OID4VP_PROTOCOLS.MULTISIGNED
|
|
16
|
+
];
|
|
17
|
+
const OID4VP_ALL_PROTOCOLS = [
|
|
18
|
+
...OID4VP_SPEC_PROTOCOLS,
|
|
19
|
+
OID4VP_PROTOCOLS.LEGACY
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
// src/detect.ts
|
|
23
|
+
function isDCAPIAvailable() {
|
|
24
|
+
return typeof DigitalCredential !== "undefined";
|
|
25
|
+
}
|
|
26
|
+
function isProtocolAllowed(protocol) {
|
|
27
|
+
if (typeof DigitalCredential === "undefined") return false;
|
|
28
|
+
if (typeof DigitalCredential.userAgentAllowsProtocol !== "function") return false;
|
|
29
|
+
return DigitalCredential.userAgentAllowsProtocol(protocol);
|
|
30
|
+
}
|
|
31
|
+
const DEFAULT_PREFERENCE = [
|
|
32
|
+
OID4VP_PROTOCOLS.SIGNED,
|
|
33
|
+
OID4VP_PROTOCOLS.MULTISIGNED,
|
|
34
|
+
OID4VP_PROTOCOLS.UNSIGNED
|
|
35
|
+
];
|
|
36
|
+
function getBestProtocol(preference) {
|
|
37
|
+
const candidates = preference ?? DEFAULT_PREFERENCE;
|
|
38
|
+
for (const proto of candidates) {
|
|
39
|
+
if (isProtocolAllowed(proto)) return proto;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/request.ts
|
|
45
|
+
async function requestCredential(protocol, data, options) {
|
|
46
|
+
const dcOptions = {
|
|
47
|
+
digital: {
|
|
48
|
+
requests: [{
|
|
49
|
+
protocol,
|
|
50
|
+
data
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
if (options?.signal) {
|
|
55
|
+
dcOptions.signal = options.signal;
|
|
56
|
+
}
|
|
57
|
+
const credential = await navigator.credentials.get(dcOptions);
|
|
58
|
+
if (!credential) {
|
|
59
|
+
throw new DOMException("No credential received", "NotAllowedError");
|
|
60
|
+
}
|
|
61
|
+
return normalizeCredential(credential, protocol);
|
|
62
|
+
}
|
|
63
|
+
function normalizeCredential(credential, fallbackProtocol) {
|
|
64
|
+
const dc = credential;
|
|
65
|
+
return {
|
|
66
|
+
protocol: dc.protocol ?? fallbackProtocol,
|
|
67
|
+
data: dc.data ?? credential
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/errors.ts
|
|
72
|
+
const ERROR_MESSAGES = {
|
|
73
|
+
NotAllowedError: "You denied the credential request or no wallet is available.",
|
|
74
|
+
NotSupportedError: "Your browser or wallet does not support this credential type.",
|
|
75
|
+
SecurityError: "Security error \u2014 ensure you are on HTTPS.",
|
|
76
|
+
AbortError: "The request was cancelled or timed out.",
|
|
77
|
+
InvalidStateError: "A credential request is already in progress.",
|
|
78
|
+
TypeError: "The credential request data is malformed."
|
|
79
|
+
};
|
|
80
|
+
const DEFAULT_MESSAGE = "An unexpected error occurred. Please try again.";
|
|
81
|
+
function getUserFriendlyErrorMessage(error) {
|
|
82
|
+
if (error instanceof DOMException) {
|
|
83
|
+
return ERROR_MESSAGES[error.name] ?? DEFAULT_MESSAGE;
|
|
84
|
+
}
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
return ERROR_MESSAGES[error.name] ?? DEFAULT_MESSAGE;
|
|
87
|
+
}
|
|
88
|
+
return DEFAULT_MESSAGE;
|
|
89
|
+
}
|
|
90
|
+
function isUserCancel(error) {
|
|
91
|
+
return error instanceof DOMException && (error.name === "NotAllowedError" || error.name === "AbortError");
|
|
92
|
+
}
|
|
93
|
+
function isProtocolUnsupported(error) {
|
|
94
|
+
return error instanceof DOMException && error.name === "NotSupportedError";
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
ERROR_MESSAGES,
|
|
98
|
+
OID4VP_PROTOCOLS,
|
|
99
|
+
getBestProtocol,
|
|
100
|
+
getUserFriendlyErrorMessage,
|
|
101
|
+
isDCAPIAvailable,
|
|
102
|
+
isProtocolAllowed,
|
|
103
|
+
isProtocolUnsupported,
|
|
104
|
+
isUserCancel,
|
|
105
|
+
requestCredential
|
|
106
|
+
};
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DC API feature detection.
|
|
3
|
+
*
|
|
4
|
+
* (a) isDCAPIAvailable() — checks typeof DigitalCredential !== "undefined"
|
|
5
|
+
* Per spec §2.1.
|
|
6
|
+
*
|
|
7
|
+
* (b) isProtocolAllowed(protocol) — calls DigitalCredential.userAgentAllowsProtocol()
|
|
8
|
+
* Per spec §2.2, §7.7.3. Returns false safely for unknown protocols.
|
|
9
|
+
*
|
|
10
|
+
* (c) getBestProtocol(preference?) — finds the best supported protocol
|
|
11
|
+
* from a preference-ordered list.
|
|
12
|
+
*/
|
|
13
|
+
import { type OID4VPProtocol } from './protocols.js';
|
|
14
|
+
/**
|
|
15
|
+
* Check if the W3C Digital Credentials API is available in this browser.
|
|
16
|
+
*
|
|
17
|
+
* Returns true when:
|
|
18
|
+
* - Chrome 141+ (native)
|
|
19
|
+
* - Safari/iOS 26+ (native, but may only support org-iso-mdoc)
|
|
20
|
+
* - Any browser with wallet-companion installed (shims DigitalCredential)
|
|
21
|
+
*/
|
|
22
|
+
export declare function isDCAPIAvailable(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if the browser allows a specific protocol for digital credentials.
|
|
25
|
+
*
|
|
26
|
+
* Uses the static method DigitalCredential.userAgentAllowsProtocol()
|
|
27
|
+
* defined in the DC API spec §7.7.3.
|
|
28
|
+
*
|
|
29
|
+
* Returns false safely when:
|
|
30
|
+
* - DigitalCredential is not defined
|
|
31
|
+
* - userAgentAllowsProtocol is not implemented
|
|
32
|
+
* - The protocol is unknown to the browser
|
|
33
|
+
*
|
|
34
|
+
* NOTE: This method reflects browser capability, NOT wallet availability.
|
|
35
|
+
* A true result means the browser will accept the protocol in a
|
|
36
|
+
* navigator.credentials.get() call, but a wallet must still be present
|
|
37
|
+
* to fulfill the request.
|
|
38
|
+
*/
|
|
39
|
+
export declare function isProtocolAllowed(protocol: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Find the best OpenID4VP protocol supported by this browser.
|
|
42
|
+
*
|
|
43
|
+
* @param preference Ordered list of protocols to try (most preferred first).
|
|
44
|
+
* Defaults to [SIGNED, MULTISIGNED, UNSIGNED].
|
|
45
|
+
* @returns The first allowed protocol, or null if none are supported.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const protocol = getBestProtocol();
|
|
50
|
+
* if (protocol) {
|
|
51
|
+
* const result = await requestCredential(protocol, requestData);
|
|
52
|
+
* } else {
|
|
53
|
+
* // No DC API support for OpenID4VP — use QR/redirect fallback
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function getBestProtocol(preference?: readonly string[]): OID4VPProtocol | null;
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DC API feature detection.
|
|
3
|
+
*
|
|
4
|
+
* (a) isDCAPIAvailable() — checks typeof DigitalCredential !== "undefined"
|
|
5
|
+
* Per spec §2.1.
|
|
6
|
+
*
|
|
7
|
+
* (b) isProtocolAllowed(protocol) — calls DigitalCredential.userAgentAllowsProtocol()
|
|
8
|
+
* Per spec §2.2, §7.7.3. Returns false safely for unknown protocols.
|
|
9
|
+
*
|
|
10
|
+
* (c) getBestProtocol(preference?) — finds the best supported protocol
|
|
11
|
+
* from a preference-ordered list.
|
|
12
|
+
*/
|
|
13
|
+
import { OID4VP_PROTOCOLS } from './protocols.js';
|
|
14
|
+
/**
|
|
15
|
+
* Check if the W3C Digital Credentials API is available in this browser.
|
|
16
|
+
*
|
|
17
|
+
* Returns true when:
|
|
18
|
+
* - Chrome 141+ (native)
|
|
19
|
+
* - Safari/iOS 26+ (native, but may only support org-iso-mdoc)
|
|
20
|
+
* - Any browser with wallet-companion installed (shims DigitalCredential)
|
|
21
|
+
*/
|
|
22
|
+
export function isDCAPIAvailable() {
|
|
23
|
+
return typeof DigitalCredential !== 'undefined';
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if the browser allows a specific protocol for digital credentials.
|
|
27
|
+
*
|
|
28
|
+
* Uses the static method DigitalCredential.userAgentAllowsProtocol()
|
|
29
|
+
* defined in the DC API spec §7.7.3.
|
|
30
|
+
*
|
|
31
|
+
* Returns false safely when:
|
|
32
|
+
* - DigitalCredential is not defined
|
|
33
|
+
* - userAgentAllowsProtocol is not implemented
|
|
34
|
+
* - The protocol is unknown to the browser
|
|
35
|
+
*
|
|
36
|
+
* NOTE: This method reflects browser capability, NOT wallet availability.
|
|
37
|
+
* A true result means the browser will accept the protocol in a
|
|
38
|
+
* navigator.credentials.get() call, but a wallet must still be present
|
|
39
|
+
* to fulfill the request.
|
|
40
|
+
*/
|
|
41
|
+
export function isProtocolAllowed(protocol) {
|
|
42
|
+
if (typeof DigitalCredential === 'undefined')
|
|
43
|
+
return false;
|
|
44
|
+
if (typeof DigitalCredential.userAgentAllowsProtocol !== 'function')
|
|
45
|
+
return false;
|
|
46
|
+
return DigitalCredential.userAgentAllowsProtocol(protocol);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Default protocol preference order.
|
|
50
|
+
* Signed first (most verifiers use JAR), then multisigned, then unsigned.
|
|
51
|
+
*/
|
|
52
|
+
const DEFAULT_PREFERENCE = [
|
|
53
|
+
OID4VP_PROTOCOLS.SIGNED,
|
|
54
|
+
OID4VP_PROTOCOLS.MULTISIGNED,
|
|
55
|
+
OID4VP_PROTOCOLS.UNSIGNED,
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Find the best OpenID4VP protocol supported by this browser.
|
|
59
|
+
*
|
|
60
|
+
* @param preference Ordered list of protocols to try (most preferred first).
|
|
61
|
+
* Defaults to [SIGNED, MULTISIGNED, UNSIGNED].
|
|
62
|
+
* @returns The first allowed protocol, or null if none are supported.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const protocol = getBestProtocol();
|
|
67
|
+
* if (protocol) {
|
|
68
|
+
* const result = await requestCredential(protocol, requestData);
|
|
69
|
+
* } else {
|
|
70
|
+
* // No DC API support for OpenID4VP — use QR/redirect fallback
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function getBestProtocol(preference) {
|
|
75
|
+
const candidates = preference ?? DEFAULT_PREFERENCE;
|
|
76
|
+
for (const proto of candidates) {
|
|
77
|
+
if (isProtocolAllowed(proto))
|
|
78
|
+
return proto;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classification and user-friendly messages for DC API errors.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* User-friendly error messages keyed by DOMException name.
|
|
6
|
+
*/
|
|
7
|
+
export declare const ERROR_MESSAGES: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Get a user-friendly message for a DC API error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getUserFriendlyErrorMessage(error: unknown): string;
|
|
12
|
+
/**
|
|
13
|
+
* Check if the error represents a user cancellation (not a failure).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isUserCancel(error: unknown): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Check if the error means the protocol is not supported by this browser.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isProtocolUnsupported(error: unknown): boolean;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classification and user-friendly messages for DC API errors.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* User-friendly error messages keyed by DOMException name.
|
|
6
|
+
*/
|
|
7
|
+
export const ERROR_MESSAGES = {
|
|
8
|
+
NotAllowedError: 'You denied the credential request or no wallet is available.',
|
|
9
|
+
NotSupportedError: 'Your browser or wallet does not support this credential type.',
|
|
10
|
+
SecurityError: 'Security error — ensure you are on HTTPS.',
|
|
11
|
+
AbortError: 'The request was cancelled or timed out.',
|
|
12
|
+
InvalidStateError: 'A credential request is already in progress.',
|
|
13
|
+
TypeError: 'The credential request data is malformed.',
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_MESSAGE = 'An unexpected error occurred. Please try again.';
|
|
16
|
+
/**
|
|
17
|
+
* Get a user-friendly message for a DC API error.
|
|
18
|
+
*/
|
|
19
|
+
export function getUserFriendlyErrorMessage(error) {
|
|
20
|
+
if (error instanceof DOMException) {
|
|
21
|
+
return ERROR_MESSAGES[error.name] ?? DEFAULT_MESSAGE;
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
return ERROR_MESSAGES[error.name] ?? DEFAULT_MESSAGE;
|
|
25
|
+
}
|
|
26
|
+
return DEFAULT_MESSAGE;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if the error represents a user cancellation (not a failure).
|
|
30
|
+
*/
|
|
31
|
+
export function isUserCancel(error) {
|
|
32
|
+
return (error instanceof DOMException &&
|
|
33
|
+
(error.name === 'NotAllowedError' || error.name === 'AbortError'));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Check if the error means the protocol is not supported by this browser.
|
|
37
|
+
*/
|
|
38
|
+
export function isProtocolUnsupported(error) {
|
|
39
|
+
return error instanceof DOMException && error.name === 'NotSupportedError';
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sirosfoundation/dc-api
|
|
3
|
+
*
|
|
4
|
+
* W3C Digital Credentials API utilities for OpenID4VP.
|
|
5
|
+
*
|
|
6
|
+
* Backend-agnostic library providing:
|
|
7
|
+
* - Protocol constants (versioned OpenID4VP protocol identifiers)
|
|
8
|
+
* - DC API feature detection (API availability + protocol support)
|
|
9
|
+
* - Native DC API invocation with response normalization
|
|
10
|
+
* - Error classification helpers
|
|
11
|
+
*
|
|
12
|
+
* Does NOT include:
|
|
13
|
+
* - Fallback transports (QR, redirect, SSE, polling) — those are verifier-specific
|
|
14
|
+
* - Wallet popup management — that's the wallet-companion's job
|
|
15
|
+
* - Backend endpoint URLs — pass your own
|
|
16
|
+
*
|
|
17
|
+
* References:
|
|
18
|
+
* - W3C Digital Credentials API: https://w3c-fedid.github.io/digital-credentials/
|
|
19
|
+
* - OpenID4VP (DC API profile): https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
|
|
20
|
+
*/
|
|
21
|
+
export { OID4VP_PROTOCOLS, type OID4VPProtocol } from './protocols.js';
|
|
22
|
+
export { isDCAPIAvailable, isProtocolAllowed, getBestProtocol, } from './detect.js';
|
|
23
|
+
export { requestCredential, type DigitalCredentialResponse, type RequestCredentialOptions, } from './request.js';
|
|
24
|
+
export { getUserFriendlyErrorMessage, isUserCancel, isProtocolUnsupported, ERROR_MESSAGES, } from './errors.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sirosfoundation/dc-api
|
|
3
|
+
*
|
|
4
|
+
* W3C Digital Credentials API utilities for OpenID4VP.
|
|
5
|
+
*
|
|
6
|
+
* Backend-agnostic library providing:
|
|
7
|
+
* - Protocol constants (versioned OpenID4VP protocol identifiers)
|
|
8
|
+
* - DC API feature detection (API availability + protocol support)
|
|
9
|
+
* - Native DC API invocation with response normalization
|
|
10
|
+
* - Error classification helpers
|
|
11
|
+
*
|
|
12
|
+
* Does NOT include:
|
|
13
|
+
* - Fallback transports (QR, redirect, SSE, polling) — those are verifier-specific
|
|
14
|
+
* - Wallet popup management — that's the wallet-companion's job
|
|
15
|
+
* - Backend endpoint URLs — pass your own
|
|
16
|
+
*
|
|
17
|
+
* References:
|
|
18
|
+
* - W3C Digital Credentials API: https://w3c-fedid.github.io/digital-credentials/
|
|
19
|
+
* - OpenID4VP (DC API profile): https://openid.net/specs/openid-4-verifiable-presentations-1_0.html
|
|
20
|
+
*/
|
|
21
|
+
export { OID4VP_PROTOCOLS } from './protocols.js';
|
|
22
|
+
export { isDCAPIAvailable, isProtocolAllowed, getBestProtocol, } from './detect.js';
|
|
23
|
+
export { requestCredential, } from './request.js';
|
|
24
|
+
export { getUserFriendlyErrorMessage, isUserCancel, isProtocolUnsupported, ERROR_MESSAGES, } from './errors.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C DigitalCredentialPresentationProtocol enum values for OpenID4VP.
|
|
3
|
+
*
|
|
4
|
+
* Per the DC API spec (§5, §7.8.2), protocol strings are versioned:
|
|
5
|
+
* "openid4vp-v1-unsigned" — unsigned request (client_id from origin)
|
|
6
|
+
* "openid4vp-v1-signed" — signed request (JAR with single signature)
|
|
7
|
+
* "openid4vp-v1-multisigned" — multi-signed request (JWS JSON Serialization)
|
|
8
|
+
*
|
|
9
|
+
* The legacy "openid4vp" string is NOT a valid DC API protocol identifier
|
|
10
|
+
* but is included for compatibility with older implementations.
|
|
11
|
+
*/
|
|
12
|
+
export declare const OID4VP_PROTOCOLS: {
|
|
13
|
+
/** Unsigned request — client_id derived from web-origin */
|
|
14
|
+
readonly UNSIGNED: "openid4vp-v1-unsigned";
|
|
15
|
+
/** Signed request — JAR with single JWS compact serialization */
|
|
16
|
+
readonly SIGNED: "openid4vp-v1-signed";
|
|
17
|
+
/** Multi-signed request — JWS JSON serialization */
|
|
18
|
+
readonly MULTISIGNED: "openid4vp-v1-multisigned";
|
|
19
|
+
/** Legacy protocol string (pre-spec, used by some implementations) */
|
|
20
|
+
readonly LEGACY: "openid4vp";
|
|
21
|
+
};
|
|
22
|
+
export type OID4VPProtocol = (typeof OID4VP_PROTOCOLS)[keyof typeof OID4VP_PROTOCOLS];
|
|
23
|
+
/**
|
|
24
|
+
* All spec-defined OpenID4VP protocol identifiers (excludes legacy).
|
|
25
|
+
*/
|
|
26
|
+
export declare const OID4VP_SPEC_PROTOCOLS: readonly OID4VPProtocol[];
|
|
27
|
+
/**
|
|
28
|
+
* All protocol identifiers including legacy.
|
|
29
|
+
*/
|
|
30
|
+
export declare const OID4VP_ALL_PROTOCOLS: readonly OID4VPProtocol[];
|
|
31
|
+
/**
|
|
32
|
+
* Check if a string is a known OpenID4VP protocol identifier.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isOID4VPProtocol(value: unknown): value is OID4VPProtocol;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3C DigitalCredentialPresentationProtocol enum values for OpenID4VP.
|
|
3
|
+
*
|
|
4
|
+
* Per the DC API spec (§5, §7.8.2), protocol strings are versioned:
|
|
5
|
+
* "openid4vp-v1-unsigned" — unsigned request (client_id from origin)
|
|
6
|
+
* "openid4vp-v1-signed" — signed request (JAR with single signature)
|
|
7
|
+
* "openid4vp-v1-multisigned" — multi-signed request (JWS JSON Serialization)
|
|
8
|
+
*
|
|
9
|
+
* The legacy "openid4vp" string is NOT a valid DC API protocol identifier
|
|
10
|
+
* but is included for compatibility with older implementations.
|
|
11
|
+
*/
|
|
12
|
+
export const OID4VP_PROTOCOLS = {
|
|
13
|
+
/** Unsigned request — client_id derived from web-origin */
|
|
14
|
+
UNSIGNED: 'openid4vp-v1-unsigned',
|
|
15
|
+
/** Signed request — JAR with single JWS compact serialization */
|
|
16
|
+
SIGNED: 'openid4vp-v1-signed',
|
|
17
|
+
/** Multi-signed request — JWS JSON serialization */
|
|
18
|
+
MULTISIGNED: 'openid4vp-v1-multisigned',
|
|
19
|
+
/** Legacy protocol string (pre-spec, used by some implementations) */
|
|
20
|
+
LEGACY: 'openid4vp',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* All spec-defined OpenID4VP protocol identifiers (excludes legacy).
|
|
24
|
+
*/
|
|
25
|
+
export const OID4VP_SPEC_PROTOCOLS = [
|
|
26
|
+
OID4VP_PROTOCOLS.UNSIGNED,
|
|
27
|
+
OID4VP_PROTOCOLS.SIGNED,
|
|
28
|
+
OID4VP_PROTOCOLS.MULTISIGNED,
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* All protocol identifiers including legacy.
|
|
32
|
+
*/
|
|
33
|
+
export const OID4VP_ALL_PROTOCOLS = [
|
|
34
|
+
...OID4VP_SPEC_PROTOCOLS,
|
|
35
|
+
OID4VP_PROTOCOLS.LEGACY,
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Check if a string is a known OpenID4VP protocol identifier.
|
|
39
|
+
*/
|
|
40
|
+
export function isOID4VPProtocol(value) {
|
|
41
|
+
return OID4VP_ALL_PROTOCOLS.includes(value);
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native DC API credential request.
|
|
3
|
+
*
|
|
4
|
+
* Calls navigator.credentials.get() with the DC API options:
|
|
5
|
+
* { digital: { requests: [{ protocol, data }] } }
|
|
6
|
+
*
|
|
7
|
+
* This module is backend-agnostic — it only handles the browser API call
|
|
8
|
+
* and response normalization. Fallback transports (QR, redirect, SSE)
|
|
9
|
+
* are the consumer's responsibility.
|
|
10
|
+
*/
|
|
11
|
+
import type { OID4VPProtocol } from './protocols.js';
|
|
12
|
+
/**
|
|
13
|
+
* Normalized response from a DC API credential request.
|
|
14
|
+
*/
|
|
15
|
+
export interface DigitalCredentialResponse {
|
|
16
|
+
/** The protocol used for this credential exchange */
|
|
17
|
+
protocol: string;
|
|
18
|
+
/** The credential response data (opaque to this library) */
|
|
19
|
+
data: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for requestCredential().
|
|
23
|
+
*/
|
|
24
|
+
export interface RequestCredentialOptions {
|
|
25
|
+
/** AbortSignal for cancellation */
|
|
26
|
+
signal?: AbortSignal;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Request a credential via the native DC API.
|
|
30
|
+
*
|
|
31
|
+
* @param protocol The protocol identifier (e.g. "openid4vp-v1-signed")
|
|
32
|
+
* @param data The request data object. For JAR: { request: "<JWT>" }.
|
|
33
|
+
* For unsigned: the individual OID4VP parameters as an object.
|
|
34
|
+
* @param options Optional AbortSignal for cancellation.
|
|
35
|
+
* @returns A normalized { protocol, data } response.
|
|
36
|
+
* @throws DOMException with name "NotAllowedError" if user cancels
|
|
37
|
+
* @throws DOMException with name "NotSupportedError" if protocol unsupported
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // Signed request (JAR)
|
|
42
|
+
* const result = await requestCredential("openid4vp-v1-signed", {
|
|
43
|
+
* request: signedJWT,
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Unsigned request
|
|
47
|
+
* const result = await requestCredential("openid4vp-v1-unsigned", {
|
|
48
|
+
* response_type: "vp_token",
|
|
49
|
+
* nonce: "abc123",
|
|
50
|
+
* dcql_query: { credentials: [...] },
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function requestCredential(protocol: OID4VPProtocol | string, data: object, options?: RequestCredentialOptions): Promise<DigitalCredentialResponse>;
|
package/dist/request.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native DC API credential request.
|
|
3
|
+
*
|
|
4
|
+
* Calls navigator.credentials.get() with the DC API options:
|
|
5
|
+
* { digital: { requests: [{ protocol, data }] } }
|
|
6
|
+
*
|
|
7
|
+
* This module is backend-agnostic — it only handles the browser API call
|
|
8
|
+
* and response normalization. Fallback transports (QR, redirect, SSE)
|
|
9
|
+
* are the consumer's responsibility.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Request a credential via the native DC API.
|
|
13
|
+
*
|
|
14
|
+
* @param protocol The protocol identifier (e.g. "openid4vp-v1-signed")
|
|
15
|
+
* @param data The request data object. For JAR: { request: "<JWT>" }.
|
|
16
|
+
* For unsigned: the individual OID4VP parameters as an object.
|
|
17
|
+
* @param options Optional AbortSignal for cancellation.
|
|
18
|
+
* @returns A normalized { protocol, data } response.
|
|
19
|
+
* @throws DOMException with name "NotAllowedError" if user cancels
|
|
20
|
+
* @throws DOMException with name "NotSupportedError" if protocol unsupported
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* // Signed request (JAR)
|
|
25
|
+
* const result = await requestCredential("openid4vp-v1-signed", {
|
|
26
|
+
* request: signedJWT,
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Unsigned request
|
|
30
|
+
* const result = await requestCredential("openid4vp-v1-unsigned", {
|
|
31
|
+
* response_type: "vp_token",
|
|
32
|
+
* nonce: "abc123",
|
|
33
|
+
* dcql_query: { credentials: [...] },
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export async function requestCredential(protocol, data, options) {
|
|
38
|
+
const dcOptions = {
|
|
39
|
+
digital: {
|
|
40
|
+
requests: [{
|
|
41
|
+
protocol,
|
|
42
|
+
data,
|
|
43
|
+
}],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
if (options?.signal) {
|
|
47
|
+
dcOptions.signal = options.signal;
|
|
48
|
+
}
|
|
49
|
+
const credential = await navigator.credentials.get(dcOptions);
|
|
50
|
+
if (!credential) {
|
|
51
|
+
throw new DOMException('No credential received', 'NotAllowedError');
|
|
52
|
+
}
|
|
53
|
+
return normalizeCredential(credential, protocol);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Normalize a Credential into our standard response shape.
|
|
57
|
+
*/
|
|
58
|
+
function normalizeCredential(credential, fallbackProtocol) {
|
|
59
|
+
// The DC API returns a DigitalCredential with .protocol and .data
|
|
60
|
+
const dc = credential;
|
|
61
|
+
return {
|
|
62
|
+
protocol: dc.protocol ?? fallbackProtocol,
|
|
63
|
+
data: dc.data ?? credential,
|
|
64
|
+
};
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sirosfoundation/dc-api",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "W3C Digital Credentials API utilities for OpenID4VP — detection, protocol constants, and native DC API invocation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./bundle": {
|
|
14
|
+
"import": "./dist/dc-api.bundle.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc && npm run bundle",
|
|
22
|
+
"bundle": "esbuild src/index.ts --bundle --format=esm --target=es2022 --outfile=dist/dc-api.bundle.js && sed -i 's/^var /const /g' dist/dc-api.bundle.js",
|
|
23
|
+
"prepare": "npm run build",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "tsc --noEmit"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"esbuild": "^0.28.1",
|
|
30
|
+
"typescript": "^5.7.0",
|
|
31
|
+
"vitest": "^3.2.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "BSD-2-Clause",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/sirosfoundation/dc-api.git"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"digital-credentials",
|
|
40
|
+
"dc-api",
|
|
41
|
+
"openid4vp",
|
|
42
|
+
"verifiable-credentials",
|
|
43
|
+
"w3c"
|
|
44
|
+
]
|
|
45
|
+
}
|