@interop/wallet-core 0.1.0 → 0.2.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 +18 -2
- package/dist/display/alignment.d.ts +49 -0
- package/dist/display/alignment.d.ts.map +1 -0
- package/dist/display/alignment.js +114 -0
- package/dist/display/alignment.js.map +1 -0
- package/dist/display/credentialName.d.ts +42 -0
- package/dist/display/credentialName.d.ts.map +1 -0
- package/dist/display/credentialName.js +106 -0
- package/dist/display/credentialName.js.map +1 -0
- package/dist/display/displayFields.d.ts +55 -0
- package/dist/display/displayFields.d.ts.map +1 -0
- package/dist/display/displayFields.js +136 -0
- package/dist/display/displayFields.js.map +1 -0
- package/dist/display/evidence.d.ts +44 -0
- package/dist/display/evidence.d.ts.map +1 -0
- package/dist/display/evidence.js +65 -0
- package/dist/display/evidence.js.map +1 -0
- package/dist/display/image.d.ts +18 -0
- package/dist/display/image.d.ts.map +1 -0
- package/dist/display/image.js +23 -0
- package/dist/display/image.js.map +1 -0
- package/dist/display/index.d.ts +38 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +32 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/issuer.d.ts +97 -0
- package/dist/display/issuer.d.ts.map +1 -0
- package/dist/display/issuer.js +126 -0
- package/dist/display/issuer.js.map +1 -0
- package/dist/display/obv3.d.ts +77 -0
- package/dist/display/obv3.d.ts.map +1 -0
- package/dist/display/obv3.js +127 -0
- package/dist/display/obv3.js.map +1 -0
- package/dist/display/parse.d.ts +62 -0
- package/dist/display/parse.d.ts.map +1 -0
- package/dist/display/parse.js +100 -0
- package/dist/display/parse.js.map +1 -0
- package/dist/display/subject.d.ts +75 -0
- package/dist/display/subject.d.ts.map +1 -0
- package/dist/display/subject.js +122 -0
- package/dist/display/subject.js.map +1 -0
- package/dist/display/text.d.ts +42 -0
- package/dist/display/text.d.ts.map +1 -0
- package/dist/display/text.js +62 -0
- package/dist/display/text.js.map +1 -0
- package/dist/display/types.d.ts +55 -0
- package/dist/display/types.d.ts.map +1 -0
- package/dist/display/types.js +41 -0
- package/dist/display/types.js.map +1 -0
- package/dist/display/validity.d.ts +51 -0
- package/dist/display/validity.d.ts.map +1 -0
- package/dist/display/validity.js +51 -0
- package/dist/display/validity.js.map +1 -0
- package/dist/display/verificationView.d.ts +101 -0
- package/dist/display/verificationView.d.ts.map +1 -0
- package/dist/display/verificationView.js +199 -0
- package/dist/display/verificationView.js.map +1 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/request/classify.d.ts +112 -0
- package/dist/request/classify.d.ts.map +1 -0
- package/dist/request/classify.js +210 -0
- package/dist/request/classify.js.map +1 -0
- package/dist/request/composeVp.d.ts +52 -0
- package/dist/request/composeVp.d.ts.map +1 -0
- package/dist/request/composeVp.js +177 -0
- package/dist/request/composeVp.js.map +1 -0
- package/dist/request/exchangeClient.d.ts +165 -0
- package/dist/request/exchangeClient.d.ts.map +1 -0
- package/dist/request/exchangeClient.js +212 -0
- package/dist/request/exchangeClient.js.map +1 -0
- package/dist/request/index.d.ts +35 -0
- package/dist/request/index.d.ts.map +1 -0
- package/dist/request/index.js +35 -0
- package/dist/request/index.js.map +1 -0
- package/dist/request/interactionUrl.d.ts +53 -0
- package/dist/request/interactionUrl.d.ts.map +1 -0
- package/dist/request/interactionUrl.js +83 -0
- package/dist/request/interactionUrl.js.map +1 -0
- package/dist/request/matching.d.ts +69 -0
- package/dist/request/matching.d.ts.map +1 -0
- package/dist/request/matching.js +183 -0
- package/dist/request/matching.js.map +1 -0
- package/dist/request/parse.d.ts +68 -0
- package/dist/request/parse.d.ts.map +1 -0
- package/dist/request/parse.js +114 -0
- package/dist/request/parse.js.map +1 -0
- package/dist/request/presentationSuite.d.ts +71 -0
- package/dist/request/presentationSuite.d.ts.map +1 -0
- package/dist/request/presentationSuite.js +139 -0
- package/dist/request/presentationSuite.js.map +1 -0
- package/dist/request/processRequest.d.ts +48 -0
- package/dist/request/processRequest.d.ts.map +1 -0
- package/dist/request/processRequest.js +139 -0
- package/dist/request/processRequest.js.map +1 -0
- package/dist/request/types.d.ts +150 -0
- package/dist/request/types.d.ts.map +1 -0
- package/dist/request/types.js +2 -0
- package/dist/request/types.js.map +1 -0
- package/dist/sync/types.d.ts.map +1 -1
- package/package.json +23 -3
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* QueryByExample matching: which stored credentials satisfy a verifier's
|
|
6
|
+
* `QueryByExample` request. Two independent algorithms ship here because the two
|
|
7
|
+
* apps genuinely differ, and each wallet matches only its own local store (no
|
|
8
|
+
* cross-replica agreement is required):
|
|
9
|
+
*
|
|
10
|
+
* - `credentialMatchesVprExampleQuery` / `filterCredentialsByExample` -- DCW's
|
|
11
|
+
* jsonpath-plus deep matcher, which walks the example object and matches any
|
|
12
|
+
* nested field (arrays, nested objects, literals) against the credential.
|
|
13
|
+
* - `vcMatchesFor` / `hasTypedExample` / `requestsCredentialType` --
|
|
14
|
+
* Freewallet's type-and-issuer matcher, which constrains only on the
|
|
15
|
+
* example's `type` (and, when pinned, `issuer`).
|
|
16
|
+
*
|
|
17
|
+
* Both operate on plain `IVerifiableCredential`s; each app maps its own record
|
|
18
|
+
* type down to the credential before calling. Ported from DCW's
|
|
19
|
+
* `app/lib/credentialMatching.ts` and Freewallet's
|
|
20
|
+
* `src/lib/walletRequest/vcMatches.ts`.
|
|
21
|
+
*/
|
|
22
|
+
import { JSONPath } from 'jsonpath-plus';
|
|
23
|
+
import { credentialQueriesOf } from './classify.js';
|
|
24
|
+
import { issuerId, typeArray } from '@interop/data-integrity-core/guards';
|
|
25
|
+
// The loose-field normalizers are owned by data-integrity-core; re-export them
|
|
26
|
+
// here so a matching consumer imports one module.
|
|
27
|
+
export { issuerId, typeArray } from '@interop/data-integrity-core/guards';
|
|
28
|
+
/**
|
|
29
|
+
* Whether a credential matches a QueryByExample `example` object, by the DCW
|
|
30
|
+
* deep-matching algorithm: every key of the example is resolved as a JSONPath
|
|
31
|
+
* against the credential and compared. Array example values require the
|
|
32
|
+
* credential to contain (at least) every listed value; object example values
|
|
33
|
+
* recurse; literal example values compare by strict equality. An empty example
|
|
34
|
+
* matches any credential.
|
|
35
|
+
*
|
|
36
|
+
* @param vprExample {Record<string, unknown>} - The QueryByExample `example`.
|
|
37
|
+
* @param credential {IVerifiableCredential} - The stored credential to test.
|
|
38
|
+
* @param [credentialPath] {string} - JSONPath root into the credential
|
|
39
|
+
* (defaults to `$`); used internally when recursing into nested objects.
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function credentialMatchesVprExampleQuery(vprExample, credential, credentialPath = '$') {
|
|
43
|
+
const matches = [];
|
|
44
|
+
for (const [vprExampleKey, vprExampleValue] of Object.entries(vprExample)) {
|
|
45
|
+
const nextPath = extendPath(credentialPath, vprExampleKey);
|
|
46
|
+
// The result is always dumped into a single-element array.
|
|
47
|
+
const [credentialScope] = JSONPath({ path: nextPath, json: credential });
|
|
48
|
+
if (Array.isArray(vprExampleValue)) {
|
|
49
|
+
// Array query values require that the matching credential contains at
|
|
50
|
+
// least every value specified. This assumes each element is a literal.
|
|
51
|
+
if (!Array.isArray(credentialScope)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (credentialScope.length < vprExampleValue.length) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
matches.push(vprExampleValue.every(exVal => credentialScope.includes(exVal)));
|
|
58
|
+
}
|
|
59
|
+
else if (typeof vprExampleValue === 'object' &&
|
|
60
|
+
vprExampleValue !== null) {
|
|
61
|
+
// Object query values recurse, to handle nested queries.
|
|
62
|
+
matches.push(credentialMatchesVprExampleQuery(vprExampleValue, credential, nextPath));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Literal query values compare directly.
|
|
66
|
+
matches.push(credentialScope === vprExampleValue);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return matches.every(m => m);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Extends a JSONPath by a literal key, escaping any JSONPath-reserved
|
|
73
|
+
* characters in the key (jsonpath-plus escapes a reserved char by prefixing it
|
|
74
|
+
* with a backtick).
|
|
75
|
+
*/
|
|
76
|
+
function extendPath(path, extension) {
|
|
77
|
+
const reserved = /[$@*()[\].:?]/g;
|
|
78
|
+
if (reserved.test(extension)) {
|
|
79
|
+
extension = extension.replace(reserved, match => '`' + match);
|
|
80
|
+
}
|
|
81
|
+
return `${path}.${extension}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Filters credentials to those matching a `QueryByExample`, using the deep
|
|
85
|
+
* matcher. Each of the query's `credentialQuery` details contributes its
|
|
86
|
+
* `example`; a credential is included when it matches any of them. A malformed
|
|
87
|
+
* query with no example matches nothing.
|
|
88
|
+
*
|
|
89
|
+
* @param credentials {IVerifiableCredential[]}
|
|
90
|
+
* @param query {IQueryByExample}
|
|
91
|
+
* @returns {IVerifiableCredential[]}
|
|
92
|
+
*/
|
|
93
|
+
export function filterCredentialsByExample(credentials, query) {
|
|
94
|
+
const examples = credentialQueriesOf(query)
|
|
95
|
+
.map(({ example }) => example)
|
|
96
|
+
.filter((example) => !!example);
|
|
97
|
+
if (examples.length === 0) {
|
|
98
|
+
// Malformed request: no example to match against.
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
return credentials.filter(credential => examples.some(example => credentialMatchesVprExampleQuery(example, credential)));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether a credential matches a single QueryByExample `example` by the
|
|
105
|
+
* type-and-issuer algorithm: every type listed in `example.type` must appear in
|
|
106
|
+
* the credential's `type`, and -- when the example pins an `issuer` -- the
|
|
107
|
+
* credential's issuer must equal it.
|
|
108
|
+
*
|
|
109
|
+
* @param options {object}
|
|
110
|
+
* @param options.credential {IVerifiableCredential}
|
|
111
|
+
* @param options.example {ICredentialQuery['example']}
|
|
112
|
+
* @returns {boolean}
|
|
113
|
+
*/
|
|
114
|
+
function matchesExample({ credential, example }) {
|
|
115
|
+
const wantedTypes = typeArray(example.type);
|
|
116
|
+
const credentialTypes = typeArray(credential.type);
|
|
117
|
+
const typesMatch = wantedTypes.every(type => credentialTypes.includes(type));
|
|
118
|
+
if (!typesMatch) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const wantedIssuer = issuerId(example.issuer);
|
|
122
|
+
if (wantedIssuer) {
|
|
123
|
+
return issuerId(credential.issuer) === wantedIssuer;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The credentials matching any of the given QueryByExample queries by the
|
|
129
|
+
* type-and-issuer algorithm. Only queries whose `example` carries a `type`
|
|
130
|
+
* constrain the result; a query with no example type matches nothing here (the
|
|
131
|
+
* caller keeps its list-all behavior when *no* query specifies a type).
|
|
132
|
+
*
|
|
133
|
+
* @param options {object}
|
|
134
|
+
* @param options.credentials {IVerifiableCredential[]}
|
|
135
|
+
* @param options.queries {IQueryByExample[]}
|
|
136
|
+
* @returns {IVerifiableCredential[]}
|
|
137
|
+
*/
|
|
138
|
+
export function vcMatchesFor({ credentials, queries }) {
|
|
139
|
+
const examples = typedExamplesOf(queries);
|
|
140
|
+
if (examples.length === 0) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return credentials.filter(credential => examples.some(example => matchesExample({ credential, example })));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The example credential shapes pinned by a query set: every `credentialQuery`
|
|
147
|
+
* detail carrying an example `type`. Only these constrain the share list.
|
|
148
|
+
*
|
|
149
|
+
* @param queries {IQueryByExample[]}
|
|
150
|
+
* @returns {Array<ICredentialQuery['example']>}
|
|
151
|
+
*/
|
|
152
|
+
function typedExamplesOf(queries) {
|
|
153
|
+
return queries
|
|
154
|
+
.flatMap(query => credentialQueriesOf(query))
|
|
155
|
+
.map(({ example }) => example)
|
|
156
|
+
.filter((example) => !!example && typeArray(example.type).length > 0);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether any of the QueryByExample queries pins an example `type` (and so
|
|
160
|
+
* should filter the share list). When false, the caller keeps showing all
|
|
161
|
+
* stored credentials.
|
|
162
|
+
*
|
|
163
|
+
* @param queries {IQueryByExample[]}
|
|
164
|
+
* @returns {boolean}
|
|
165
|
+
*/
|
|
166
|
+
export function hasTypedExample(queries) {
|
|
167
|
+
return typedExamplesOf(queries).length > 0;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Whether any typed example in the query set explicitly lists the given
|
|
171
|
+
* credential `type`. Lets the caller distinguish a request that actually asks
|
|
172
|
+
* for a particular type (e.g. a LoginCredential) from a generic, untyped "any
|
|
173
|
+
* VC" request.
|
|
174
|
+
*
|
|
175
|
+
* @param options {object}
|
|
176
|
+
* @param options.queries {IQueryByExample[]}
|
|
177
|
+
* @param options.type {string}
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
export function requestsCredentialType({ queries, type }) {
|
|
181
|
+
return typedExamplesOf(queries).some(example => typeArray(example.type).includes(type));
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=matching.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matching.js","sourceRoot":"","sources":["../../src/request/matching.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parsing of incoming wallet/VC API messages that arrive as JSON text or a
|
|
6
|
+
* deep-link URL (`dccrequest://...?request=<json>`), plus the small
|
|
7
|
+
* query-inspection helpers a caller runs over a parsed VPR body. Ported from
|
|
8
|
+
* DCW's `app/lib/walletRequestApi.ts`; the `query-string` dependency is dropped
|
|
9
|
+
* in favor of the native `URL` / `URLSearchParams`.
|
|
10
|
+
*/
|
|
11
|
+
import type { IVPRQuery, IZcapQuery, WalletApiMessage } from './types.js';
|
|
12
|
+
export { isDIDAuthRequested as isDidAuthRequested } from './classify.js';
|
|
13
|
+
/**
|
|
14
|
+
* Whether a JSON string is a recognized wallet API message: an exchange
|
|
15
|
+
* invitation, a presentation request, a presentation offer, or an issuance
|
|
16
|
+
* request. Malformed JSON is not a message.
|
|
17
|
+
*
|
|
18
|
+
* @param text {string}
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export declare function isWalletApiMessage(text: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Classifies a parsed message object into one of the wallet API message types
|
|
24
|
+
* by its discriminating property. Returns `undefined` for an unrecognized
|
|
25
|
+
* shape.
|
|
26
|
+
*
|
|
27
|
+
* @param options {object}
|
|
28
|
+
* @param options.messageObject {object}
|
|
29
|
+
* @returns {WalletApiMessage | undefined}
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseWalletApiMessage({ messageObject }: {
|
|
32
|
+
messageObject: object;
|
|
33
|
+
}): WalletApiMessage | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Extracts and parses the wallet API message carried in a deep-link URL's
|
|
36
|
+
* `request` query parameter (`dccrequest://...?request=<json>`). Returns
|
|
37
|
+
* `undefined` when the parameter is absent or its value is not valid JSON.
|
|
38
|
+
*
|
|
39
|
+
* @param options {object}
|
|
40
|
+
* @param options.url {string}
|
|
41
|
+
* @returns {Record<string, unknown> | undefined}
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseWalletApiUrl({ url }: {
|
|
44
|
+
url: string;
|
|
45
|
+
}): Record<string, unknown> | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Filters an incoming VCALM query set for capability (zcap) requests, returning
|
|
48
|
+
* only those. Recognizes both the canonical `AuthorizationCapabilityQuery` and
|
|
49
|
+
* the legacy `ZcapQuery` type strings.
|
|
50
|
+
*
|
|
51
|
+
* @param options {object}
|
|
52
|
+
* @param options.queries {IVPRQuery[]}
|
|
53
|
+
* @returns {{ zcapRequests?: IZcapQuery[] }}
|
|
54
|
+
*/
|
|
55
|
+
export declare function zcapsRequested({ queries }: {
|
|
56
|
+
queries: IVPRQuery[];
|
|
57
|
+
}): {
|
|
58
|
+
zcapRequests?: IZcapQuery[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Returns true if the message is a VPR whose only query type is
|
|
62
|
+
* `DIDAuthentication` (i.e. no credential sharing is involved).
|
|
63
|
+
*
|
|
64
|
+
* @param message {WalletApiMessage}
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export declare function isDIDAuthOnlyRequest(message: WalletApiMessage): boolean;
|
|
68
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/request/parse.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;GAMG;AACH,OAAO,KAAK,EAKV,SAAS,EACT,UAAU,EACV,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAInB,OAAO,EAAE,kBAAkB,IAAI,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAExE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAexD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,aAAa,EACd,EAAE;IACD,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,gBAAgB,GAAG,SAAS,CAe/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,GAAG,EACJ,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAqBtC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG;IACrE,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;CAC5B,CASA;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAUvE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The DID-Auth query helper is spelled `isDIDAuthRequested` in `classify.ts`;
|
|
2
|
+
// DCW imported it as `isDidAuthRequested`. Both names resolve to one function.
|
|
3
|
+
export { isDIDAuthRequested as isDidAuthRequested } from './classify.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whether a JSON string is a recognized wallet API message: an exchange
|
|
6
|
+
* invitation, a presentation request, a presentation offer, or an issuance
|
|
7
|
+
* request. Malformed JSON is not a message.
|
|
8
|
+
*
|
|
9
|
+
* @param text {string}
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
export function isWalletApiMessage(text) {
|
|
13
|
+
let messageObject;
|
|
14
|
+
try {
|
|
15
|
+
messageObject = JSON.parse(text);
|
|
16
|
+
}
|
|
17
|
+
catch (_) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return (!!messageObject &&
|
|
21
|
+
typeof messageObject === 'object' &&
|
|
22
|
+
('protocols' in messageObject ||
|
|
23
|
+
'verifiablePresentationRequest' in messageObject ||
|
|
24
|
+
'verifiablePresentation' in messageObject ||
|
|
25
|
+
'issueRequest' in messageObject));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classifies a parsed message object into one of the wallet API message types
|
|
29
|
+
* by its discriminating property. Returns `undefined` for an unrecognized
|
|
30
|
+
* shape.
|
|
31
|
+
*
|
|
32
|
+
* @param options {object}
|
|
33
|
+
* @param options.messageObject {object}
|
|
34
|
+
* @returns {WalletApiMessage | undefined}
|
|
35
|
+
*/
|
|
36
|
+
export function parseWalletApiMessage({ messageObject }) {
|
|
37
|
+
if ('protocols' in messageObject) {
|
|
38
|
+
return messageObject;
|
|
39
|
+
}
|
|
40
|
+
if ('verifiablePresentationRequest' in messageObject) {
|
|
41
|
+
return messageObject;
|
|
42
|
+
}
|
|
43
|
+
if ('verifiablePresentation' in messageObject) {
|
|
44
|
+
return messageObject;
|
|
45
|
+
}
|
|
46
|
+
if ('issueRequest' in messageObject) {
|
|
47
|
+
return messageObject;
|
|
48
|
+
}
|
|
49
|
+
// Message not recognized / not supported, return undefined.
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extracts and parses the wallet API message carried in a deep-link URL's
|
|
54
|
+
* `request` query parameter (`dccrequest://...?request=<json>`). Returns
|
|
55
|
+
* `undefined` when the parameter is absent or its value is not valid JSON.
|
|
56
|
+
*
|
|
57
|
+
* @param options {object}
|
|
58
|
+
* @param options.url {string}
|
|
59
|
+
* @returns {Record<string, unknown> | undefined}
|
|
60
|
+
*/
|
|
61
|
+
export function parseWalletApiUrl({ url }) {
|
|
62
|
+
let messageText;
|
|
63
|
+
try {
|
|
64
|
+
messageText = new URL(url).searchParams.get('request');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
if (messageText === null) {
|
|
70
|
+
// URL does not contain a "request" parameter.
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
// `URLSearchParams.get` has already percent-decoded the value.
|
|
75
|
+
return JSON.parse(messageText);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error(`Error parsing incoming wallet API message: "${messageText}"`, err);
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Filters an incoming VCALM query set for capability (zcap) requests, returning
|
|
84
|
+
* only those. Recognizes both the canonical `AuthorizationCapabilityQuery` and
|
|
85
|
+
* the legacy `ZcapQuery` type strings.
|
|
86
|
+
*
|
|
87
|
+
* @param options {object}
|
|
88
|
+
* @param options.queries {IVPRQuery[]}
|
|
89
|
+
* @returns {{ zcapRequests?: IZcapQuery[] }}
|
|
90
|
+
*/
|
|
91
|
+
export function zcapsRequested({ queries }) {
|
|
92
|
+
const zcapRequests = queries.filter((q) => q.type === 'ZcapQuery' || q.type === 'AuthorizationCapabilityQuery');
|
|
93
|
+
if (zcapRequests.length > 0) {
|
|
94
|
+
return { zcapRequests };
|
|
95
|
+
}
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns true if the message is a VPR whose only query type is
|
|
100
|
+
* `DIDAuthentication` (i.e. no credential sharing is involved).
|
|
101
|
+
*
|
|
102
|
+
* @param message {WalletApiMessage}
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
export function isDIDAuthOnlyRequest(message) {
|
|
106
|
+
if (!('verifiablePresentationRequest' in message)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const { query } = message.verifiablePresentationRequest;
|
|
110
|
+
const queries = Array.isArray(query) ? query : [query];
|
|
111
|
+
return (queries.length > 0 &&
|
|
112
|
+
queries.every(q => !!q && q.type === 'DIDAuthentication'));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/request/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Cryptosuite negotiation and proof-suite construction for signed Verifiable
|
|
6
|
+
* Presentations. `negotiateCryptosuite` decides which cryptosuite the wallet
|
|
7
|
+
* signs a presentation with (honoring a verifier's `acceptedCryptosuites`
|
|
8
|
+
* preference, or inferring VC 2.0 from a QueryByExample); `presentationSuiteFor`
|
|
9
|
+
* builds the matching proof suite and VC data-model version.
|
|
10
|
+
*
|
|
11
|
+
* Ported from Freewallet's `src/lib/walletRequest/presentationSuite.ts` (the
|
|
12
|
+
* superset of DCW's `app/lib/presentationSuite.ts`): it accepts both the VCALM
|
|
13
|
+
* `{ cryptosuite }` object form and the bare cryptosuite-string form verifiers
|
|
14
|
+
* send in practice, and reads `acceptedCryptosuites` from a QueryByExample's
|
|
15
|
+
* individual `credentialQuery` details as well as the query itself.
|
|
16
|
+
*/
|
|
17
|
+
import { DataIntegrityProof } from '@interop/data-integrity-proof';
|
|
18
|
+
import type { ISigner } from '@interop/data-integrity-core';
|
|
19
|
+
import type { IVPRQuery } from './types.js';
|
|
20
|
+
/**
|
|
21
|
+
* VCALM cryptosuite identifier for the modern EdDSA Data Integrity proof. This
|
|
22
|
+
* is what a verifier lists in `acceptedCryptosuites` to ask for a
|
|
23
|
+
* `DataIntegrityProof` (VC 2.0) presentation instead of the legacy default.
|
|
24
|
+
*
|
|
25
|
+
* @see https://www.w3.org/TR/vc-data-integrity/
|
|
26
|
+
*/
|
|
27
|
+
export declare const EDDSA_RDFC_2022 = "eddsa-rdfc-2022";
|
|
28
|
+
/**
|
|
29
|
+
* Decides the cryptosuite the wallet should sign a presentation with, in two
|
|
30
|
+
* tiers:
|
|
31
|
+
*
|
|
32
|
+
* 1. The explicit, spec-sanctioned signal: an `acceptedCryptosuites` preference
|
|
33
|
+
* (allowed on DIDAuthentication and QueryByExample queries, and on a
|
|
34
|
+
* QueryByExample's individual `credentialQuery` details). The verifier's
|
|
35
|
+
* stated order is honored, picking the first listed suite the wallet
|
|
36
|
+
* supports. If the verifier listed suites but none are supported, the wallet
|
|
37
|
+
* falls back to its default rather than overriding their explicit choice with
|
|
38
|
+
* the heuristic below.
|
|
39
|
+
* 2. A fallback heuristic when no `acceptedCryptosuites` is given: if a
|
|
40
|
+
* QueryByExample asks for a VC 2.0 example credential, infer the verifier
|
|
41
|
+
* wants a VC 2.0 `DataIntegrityProof` (eddsa-rdfc-2022) response.
|
|
42
|
+
*
|
|
43
|
+
* Returns `undefined` when neither tier yields a supported suite, signalling
|
|
44
|
+
* that the caller should sign with the wallet default (Ed25519Signature2020,
|
|
45
|
+
* VC 1.0).
|
|
46
|
+
*
|
|
47
|
+
* @param queries {IVPRQuery[]}
|
|
48
|
+
* @returns {string | undefined}
|
|
49
|
+
* @see https://w3c.github.io/vcalm/ -- the `acceptedCryptosuites` query field
|
|
50
|
+
*/
|
|
51
|
+
export declare function negotiateCryptosuite(queries: IVPRQuery[]): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Builds the proof suite and the VC Data Model version to use for a presentation
|
|
54
|
+
* proof. The negotiated `eddsa-rdfc-2022` cryptosuite emits a
|
|
55
|
+
* `DataIntegrityProof` and requires the VC 2.0 context (which defines
|
|
56
|
+
* `challenge`/`domain` only within the `DataIntegrityProof` scope); the default
|
|
57
|
+
* `Ed25519Signature2020` suite uses the VC 1.0 context.
|
|
58
|
+
*
|
|
59
|
+
* @param options {object}
|
|
60
|
+
* @param options.signer {ISigner}
|
|
61
|
+
* @param [options.cryptosuite] {string}
|
|
62
|
+
* @returns {{ suite: DataIntegrityProof, version: number }}
|
|
63
|
+
*/
|
|
64
|
+
export declare function presentationSuiteFor({ signer, cryptosuite }: {
|
|
65
|
+
signer: ISigner;
|
|
66
|
+
cryptosuite?: string;
|
|
67
|
+
}): {
|
|
68
|
+
suite: DataIntegrityProof;
|
|
69
|
+
version: number;
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=presentationSuite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentationSuite.d.ts","sourceRoot":"","sources":["../../src/request/presentationSuite.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AAGlE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAE3D,OAAO,KAAK,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAiEhD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,SAAS,CAkB7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,MAAM,EACN,WAAW,EACZ,EAAE;IACD,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG;IAAE,KAAK,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAQjD"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Cryptosuite negotiation and proof-suite construction for signed Verifiable
|
|
6
|
+
* Presentations. `negotiateCryptosuite` decides which cryptosuite the wallet
|
|
7
|
+
* signs a presentation with (honoring a verifier's `acceptedCryptosuites`
|
|
8
|
+
* preference, or inferring VC 2.0 from a QueryByExample); `presentationSuiteFor`
|
|
9
|
+
* builds the matching proof suite and VC data-model version.
|
|
10
|
+
*
|
|
11
|
+
* Ported from Freewallet's `src/lib/walletRequest/presentationSuite.ts` (the
|
|
12
|
+
* superset of DCW's `app/lib/presentationSuite.ts`): it accepts both the VCALM
|
|
13
|
+
* `{ cryptosuite }` object form and the bare cryptosuite-string form verifiers
|
|
14
|
+
* send in practice, and reads `acceptedCryptosuites` from a QueryByExample's
|
|
15
|
+
* individual `credentialQuery` details as well as the query itself.
|
|
16
|
+
*/
|
|
17
|
+
import { DataIntegrityProof } from '@interop/data-integrity-proof';
|
|
18
|
+
import { Ed25519Signature2020 } from '@interop/ed25519-signature';
|
|
19
|
+
import { eddsaRdfc2022 } from '@interop/ed25519-signature/eddsa-rdfc-2022';
|
|
20
|
+
import { credentialQueriesOf } from './classify.js';
|
|
21
|
+
/**
|
|
22
|
+
* VCALM cryptosuite identifier for the modern EdDSA Data Integrity proof. This
|
|
23
|
+
* is what a verifier lists in `acceptedCryptosuites` to ask for a
|
|
24
|
+
* `DataIntegrityProof` (VC 2.0) presentation instead of the legacy default.
|
|
25
|
+
*
|
|
26
|
+
* @see https://www.w3.org/TR/vc-data-integrity/
|
|
27
|
+
*/
|
|
28
|
+
export const EDDSA_RDFC_2022 = 'eddsa-rdfc-2022';
|
|
29
|
+
/**
|
|
30
|
+
* Cryptosuites this wallet can produce for a presentation proof when a verifier
|
|
31
|
+
* offers a choice via VCALM `acceptedCryptosuites`. `Ed25519Signature2020` is
|
|
32
|
+
* deliberately absent: it is the wallet's default, used as a fallback whenever
|
|
33
|
+
* the verifier expresses no (supported) preference, for backwards compatibility
|
|
34
|
+
* with verifiers that predate cryptosuite negotiation.
|
|
35
|
+
*/
|
|
36
|
+
const SUPPORTED_CRYPTOSUITES = [EDDSA_RDFC_2022];
|
|
37
|
+
/** VC Data Model 2.0 context URL. */
|
|
38
|
+
const CREDENTIALS_CONTEXT_V2_URL = 'https://www.w3.org/ns/credentials/v2';
|
|
39
|
+
/** Returns true if a JSON-LD `@context` value contains the given URL. */
|
|
40
|
+
function contextIncludes(context, url) {
|
|
41
|
+
if (typeof context === 'string') {
|
|
42
|
+
return context === url;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(context)) {
|
|
45
|
+
return context.includes(url);
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Normalizes an `acceptedCryptosuites` list to cryptosuite name strings,
|
|
51
|
+
* accepting both the VCALM `{ cryptosuite }` object form and the bare string
|
|
52
|
+
* form verifiers send in practice.
|
|
53
|
+
*
|
|
54
|
+
* @param [accepted] {IAcceptedCryptosuites}
|
|
55
|
+
* @returns {string[]}
|
|
56
|
+
*/
|
|
57
|
+
function cryptosuiteNames(accepted) {
|
|
58
|
+
if (!Array.isArray(accepted)) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return accepted
|
|
62
|
+
.map(entry => (typeof entry === 'string' ? entry : entry?.cryptosuite))
|
|
63
|
+
.filter((name) => typeof name === 'string');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Every cryptosuite a query offers, in the order the verifier stated them. A
|
|
67
|
+
* `QueryByExample` may carry the preference on the query itself (VCALM) or
|
|
68
|
+
* inside each of its `credentialQuery` details (what verifiers send in
|
|
69
|
+
* practice); both are collected.
|
|
70
|
+
*
|
|
71
|
+
* @param query {IVPRQuery}
|
|
72
|
+
* @returns {string[]}
|
|
73
|
+
*/
|
|
74
|
+
function acceptedCryptosuitesOf(query) {
|
|
75
|
+
const onQuery = 'acceptedCryptosuites' in query
|
|
76
|
+
? cryptosuiteNames(query.acceptedCryptosuites)
|
|
77
|
+
: [];
|
|
78
|
+
if (query.type !== 'QueryByExample') {
|
|
79
|
+
return onQuery;
|
|
80
|
+
}
|
|
81
|
+
const onDetails = credentialQueriesOf(query).flatMap(({ acceptedCryptosuites }) => cryptosuiteNames(acceptedCryptosuites));
|
|
82
|
+
return [...onQuery, ...onDetails];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Decides the cryptosuite the wallet should sign a presentation with, in two
|
|
86
|
+
* tiers:
|
|
87
|
+
*
|
|
88
|
+
* 1. The explicit, spec-sanctioned signal: an `acceptedCryptosuites` preference
|
|
89
|
+
* (allowed on DIDAuthentication and QueryByExample queries, and on a
|
|
90
|
+
* QueryByExample's individual `credentialQuery` details). The verifier's
|
|
91
|
+
* stated order is honored, picking the first listed suite the wallet
|
|
92
|
+
* supports. If the verifier listed suites but none are supported, the wallet
|
|
93
|
+
* falls back to its default rather than overriding their explicit choice with
|
|
94
|
+
* the heuristic below.
|
|
95
|
+
* 2. A fallback heuristic when no `acceptedCryptosuites` is given: if a
|
|
96
|
+
* QueryByExample asks for a VC 2.0 example credential, infer the verifier
|
|
97
|
+
* wants a VC 2.0 `DataIntegrityProof` (eddsa-rdfc-2022) response.
|
|
98
|
+
*
|
|
99
|
+
* Returns `undefined` when neither tier yields a supported suite, signalling
|
|
100
|
+
* that the caller should sign with the wallet default (Ed25519Signature2020,
|
|
101
|
+
* VC 1.0).
|
|
102
|
+
*
|
|
103
|
+
* @param queries {IVPRQuery[]}
|
|
104
|
+
* @returns {string | undefined}
|
|
105
|
+
* @see https://w3c.github.io/vcalm/ -- the `acceptedCryptosuites` query field
|
|
106
|
+
*/
|
|
107
|
+
export function negotiateCryptosuite(queries) {
|
|
108
|
+
const accepted = queries.flatMap(query => acceptedCryptosuitesOf(query));
|
|
109
|
+
if (accepted.length > 0) {
|
|
110
|
+
return accepted.find(cryptosuite => SUPPORTED_CRYPTOSUITES.includes(cryptosuite));
|
|
111
|
+
}
|
|
112
|
+
// Fallback: a QueryByExample requesting a VC 2.0 example credential implies
|
|
113
|
+
// the verifier operates in the VC 2.0 data model, so respond in kind.
|
|
114
|
+
const requestsV2Example = queries.some(query => query.type === 'QueryByExample' &&
|
|
115
|
+
credentialQueriesOf(query).some(({ example }) => contextIncludes(example?.['@context'], CREDENTIALS_CONTEXT_V2_URL)));
|
|
116
|
+
return requestsV2Example ? EDDSA_RDFC_2022 : undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Builds the proof suite and the VC Data Model version to use for a presentation
|
|
120
|
+
* proof. The negotiated `eddsa-rdfc-2022` cryptosuite emits a
|
|
121
|
+
* `DataIntegrityProof` and requires the VC 2.0 context (which defines
|
|
122
|
+
* `challenge`/`domain` only within the `DataIntegrityProof` scope); the default
|
|
123
|
+
* `Ed25519Signature2020` suite uses the VC 1.0 context.
|
|
124
|
+
*
|
|
125
|
+
* @param options {object}
|
|
126
|
+
* @param options.signer {ISigner}
|
|
127
|
+
* @param [options.cryptosuite] {string}
|
|
128
|
+
* @returns {{ suite: DataIntegrityProof, version: number }}
|
|
129
|
+
*/
|
|
130
|
+
export function presentationSuiteFor({ signer, cryptosuite }) {
|
|
131
|
+
if (cryptosuite === EDDSA_RDFC_2022) {
|
|
132
|
+
return {
|
|
133
|
+
suite: new DataIntegrityProof({ signer, cryptosuite: eddsaRdfc2022 }),
|
|
134
|
+
version: 2.0
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { suite: new Ed25519Signature2020({ signer }), version: 1.0 };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=presentationSuite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presentationSuite.js","sourceRoot":"","sources":["../../src/request/presentationSuite.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IVerifiableCredential, IVPRDetails, PresentationSigner, RequestProcessors, WalletResponse } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Domain-binding check (VCALM §3.4.3 advisement): a DID-Auth `domain` MUST match
|
|
4
|
+
* the channel the request arrived on, otherwise a dishonest verifier could relay
|
|
5
|
+
* the challenge from another origin and replay the response.
|
|
6
|
+
*
|
|
7
|
+
* @param options {object}
|
|
8
|
+
* @param options.domain {string} - The `domain` from the request.
|
|
9
|
+
* @param [options.origin] {string} - The channel origin (for CHAPI,
|
|
10
|
+
* `event.credentialRequestOrigin`).
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export declare function domainMatchesOrigin({ domain, origin }: {
|
|
14
|
+
domain: string;
|
|
15
|
+
origin?: string;
|
|
16
|
+
}): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Processes a Verifiable Presentation Request and composes the wallet's
|
|
19
|
+
* response. Assumes the user has already consented and (for VC sharing) picked
|
|
20
|
+
* which credentials to send.
|
|
21
|
+
*
|
|
22
|
+
* @param options {object}
|
|
23
|
+
* @param options.request {IVPRDetails} - The VPR body.
|
|
24
|
+
* @param options.presentationSigner {PresentationSigner} - Authentication signer
|
|
25
|
+
* and holder DID.
|
|
26
|
+
* @param [options.selectedVCs] {IVerifiableCredential[]} - VCs the user chose to
|
|
27
|
+
* share (empty for a DID-Auth-only or zcap-only response).
|
|
28
|
+
* @param [options.credentialRequestOrigin] {string} - Channel origin, used for
|
|
29
|
+
* the domain-binding check and required by the App Connect branch.
|
|
30
|
+
* @param [options.processors] {RequestProcessors} - App-side capability /
|
|
31
|
+
* App Connect processors.
|
|
32
|
+
* @param [options.cryptosuite] {string} - Cryptosuite override; when absent it
|
|
33
|
+
* is negotiated from the request's `acceptedCryptosuites`.
|
|
34
|
+
* @param [options.vocabBaseIri] {string} - Vocabulary base IRI passed through to
|
|
35
|
+
* {@link composeVp} for embedded-grant term definitions.
|
|
36
|
+
* @returns {Promise<WalletResponse>} The response VP (and any granted zcaps), or
|
|
37
|
+
* `{}` when there is nothing to send.
|
|
38
|
+
*/
|
|
39
|
+
export declare function processRequest({ request, presentationSigner, selectedVCs, credentialRequestOrigin, processors, cryptosuite, vocabBaseIri }: {
|
|
40
|
+
request: IVPRDetails;
|
|
41
|
+
presentationSigner: PresentationSigner;
|
|
42
|
+
selectedVCs?: IVerifiableCredential[];
|
|
43
|
+
credentialRequestOrigin?: string;
|
|
44
|
+
processors?: RequestProcessors;
|
|
45
|
+
cryptosuite?: string;
|
|
46
|
+
vocabBaseIri?: string;
|
|
47
|
+
}): Promise<WalletResponse>;
|
|
48
|
+
//# sourceMappingURL=processRequest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processRequest.d.ts","sourceRoot":"","sources":["../../src/request/processRequest.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,qBAAqB,EACrB,WAAW,EAEX,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACf,MAAM,YAAY,CAAA;AAkBnB;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,MAAM,EACP,EAAE;IACD,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAOV;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,cAAc,CAAC,EACnC,OAAO,EACP,kBAAkB,EAClB,WAAgB,EAChB,uBAAuB,EACvB,UAAU,EACV,WAAW,EACX,YAAY,EACb,EAAE;IACD,OAAO,EAAE,WAAW,CAAA;IACpB,kBAAkB,EAAE,kBAAkB,CAAA;IACtC,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAA;IACrC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC,cAAc,CAAC,CA0E1B"}
|