@learncard/network-plugin 2.13.4 → 2.13.6
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 +12 -7
- package/dist/lcn-plugin.cjs.development.cjs +4336 -2459
- package/dist/lcn-plugin.cjs.development.cjs.map +4 -4
- package/dist/lcn-plugin.cjs.production.min.cjs +56 -14
- package/dist/lcn-plugin.cjs.production.min.cjs.map +4 -4
- package/dist/lcn-plugin.esm.js +4336 -2459
- package/dist/lcn-plugin.esm.js.map +4 -4
- package/package.json +64 -62
- package/src/index.ts +2 -0
- package/src/plugin.ts +2747 -0
- package/src/test/index.test.ts +89 -0
- package/src/test/mocks/sample-vcs.ts +579 -0
- package/src/types.ts +908 -0
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,2747 @@
|
|
|
1
|
+
import { getClient, getApiTokenClient } from '@learncard/network-brain-client';
|
|
2
|
+
import {
|
|
3
|
+
JWEValidator,
|
|
4
|
+
LCNProfile,
|
|
5
|
+
LCNVisibleProfile,
|
|
6
|
+
UnsignedVCValidator,
|
|
7
|
+
VCValidator,
|
|
8
|
+
VPValidator,
|
|
9
|
+
Profile,
|
|
10
|
+
ConsentFlowContractValidator,
|
|
11
|
+
ConsentFlowTermsValidator,
|
|
12
|
+
JWE,
|
|
13
|
+
UnsignedVC,
|
|
14
|
+
VC,
|
|
15
|
+
BitstringCredentialStatusEntry,
|
|
16
|
+
BitstringCredentialStatusPurpose,
|
|
17
|
+
StoredCredentialEnvelope,
|
|
18
|
+
StoredCredentialEnvelopeValidator,
|
|
19
|
+
isStoredCredentialEnvelope,
|
|
20
|
+
} from '@learncard/types';
|
|
21
|
+
import { LearnCard } from '@learncard/core';
|
|
22
|
+
import { VerifyExtension } from '@learncard/vc-plugin';
|
|
23
|
+
import {
|
|
24
|
+
getCredentialStatusArray,
|
|
25
|
+
isVC2Format,
|
|
26
|
+
resolveStorageReadResult,
|
|
27
|
+
} from '@learncard/helpers';
|
|
28
|
+
import Mustache from 'mustache';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
LearnCardNetworkPluginDependentMethods,
|
|
32
|
+
LearnCardNetworkPlugin,
|
|
33
|
+
VerifyBoostPlugin,
|
|
34
|
+
TrustedBoostRegistryEntry,
|
|
35
|
+
} from './types';
|
|
36
|
+
|
|
37
|
+
const uint8ArrayToBase64Url = (bytes: Uint8Array): string => {
|
|
38
|
+
let binary = '';
|
|
39
|
+
for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]!);
|
|
40
|
+
const base64 =
|
|
41
|
+
typeof btoa === 'function'
|
|
42
|
+
? btoa(binary)
|
|
43
|
+
: Buffer.from(binary, 'binary').toString('base64');
|
|
44
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type WireEnvelope = StoredCredentialEnvelope & { data: string };
|
|
48
|
+
|
|
49
|
+
const normalizeEnvelopeForTransport = <T>(
|
|
50
|
+
value: T
|
|
51
|
+
): Exclude<T, StoredCredentialEnvelope> | WireEnvelope => {
|
|
52
|
+
if (!isStoredCredentialEnvelope(value)) {
|
|
53
|
+
return value as Exclude<T, StoredCredentialEnvelope>;
|
|
54
|
+
}
|
|
55
|
+
if (typeof value.data === 'string') return value as WireEnvelope;
|
|
56
|
+
return { ...value, data: uint8ArrayToBase64Url(value.data) };
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/* -------------------------------------------------------------------------- *
|
|
60
|
+
* Boost template rendering helpers
|
|
61
|
+
* -------------------------------------------------------------------------- *
|
|
62
|
+
*
|
|
63
|
+
* A "boost" is stored on the server as a JSON string that may contain Mustache
|
|
64
|
+
* variables (e.g. `{{recipient_name}}`, `{{#evidence}}...{{/evidence}}`). When
|
|
65
|
+
* we send a boost to a recipient, we substitute those variables with values
|
|
66
|
+
* from `templateData` BEFORE the JSON is parsed back into a credential object.
|
|
67
|
+
*
|
|
68
|
+
* Because substitution happens against a raw JSON string, any user-supplied
|
|
69
|
+
* value (names, titles, descriptions, etc.) must be escaped so that control
|
|
70
|
+
* characters do not corrupt the JSON. The helpers below implement that
|
|
71
|
+
* escape-then-render pipeline, plus a small evidence-merging step for template
|
|
72
|
+
* data that carries an `evidence` field.
|
|
73
|
+
*
|
|
74
|
+
* The same behavior is implemented server-side in
|
|
75
|
+
* `brain-service/src/helpers/template.helpers.ts`. If you change the escaping
|
|
76
|
+
* rules here, mirror the change there (and vice versa) so that client-side
|
|
77
|
+
* and server-side rendering produce identical output.
|
|
78
|
+
* -------------------------------------------------------------------------- */
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Escapes a single scalar value so it can be safely inlined inside a JSON
|
|
82
|
+
* string literal.
|
|
83
|
+
*
|
|
84
|
+
* The order of replacements matters: backslashes MUST be escaped first,
|
|
85
|
+
* otherwise subsequent replacements (which themselves introduce backslashes)
|
|
86
|
+
* would be double-escaped and produce invalid JSON.
|
|
87
|
+
*
|
|
88
|
+
* Null / undefined coerce to an empty string so Mustache renders nothing
|
|
89
|
+
* rather than the literal text "null" / "undefined" inside the JSON.
|
|
90
|
+
*
|
|
91
|
+
* @param value - Any scalar value to be rendered into a JSON string position
|
|
92
|
+
* @returns A string whose characters are valid inside a JSON string literal
|
|
93
|
+
*/
|
|
94
|
+
const escapeJsonStringValue = (value: unknown): string => {
|
|
95
|
+
if (value === null || value === undefined) return '';
|
|
96
|
+
|
|
97
|
+
return String(value)
|
|
98
|
+
.replace(/\\/g, '\\\\') // backslash — must run first
|
|
99
|
+
.replace(/"/g, '\\"') // double quote — would close the JSON string
|
|
100
|
+
.replace(/\n/g, '\\n') // newline
|
|
101
|
+
.replace(/\r/g, '\\r') // carriage return
|
|
102
|
+
.replace(/\t/g, '\\t') // tab
|
|
103
|
+
.replace(/\f/g, '\\f') // form feed
|
|
104
|
+
.replace(/[\b]/g, '\\b'); // backspace (character class to dodge \b word-boundary)
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Recursively walks a template-data value and escapes every string leaf for
|
|
109
|
+
* JSON safety, while preserving the original array / object shape.
|
|
110
|
+
*
|
|
111
|
+
* Keeping the structure intact is important because Mustache sections such as
|
|
112
|
+
* `{{#evidence}}...{{/evidence}}` iterate over arrays and read properties off
|
|
113
|
+
* the objects inside them — we cannot simply flatten everything to strings.
|
|
114
|
+
*
|
|
115
|
+
* Non-string primitives (numbers, booleans) are passed through untouched so
|
|
116
|
+
* that the template author can decide whether to render them quoted or bare.
|
|
117
|
+
*/
|
|
118
|
+
const prepareTemplateValue = (value: unknown): unknown => {
|
|
119
|
+
if (value === null || value === undefined) return '';
|
|
120
|
+
if (Array.isArray(value)) return value.map(prepareTemplateValue);
|
|
121
|
+
if (typeof value === 'object') {
|
|
122
|
+
return Object.fromEntries(
|
|
123
|
+
Object.entries(value as Record<string, unknown>).map(([key, nestedValue]) => [
|
|
124
|
+
key,
|
|
125
|
+
prepareTemplateValue(nestedValue),
|
|
126
|
+
])
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (typeof value === 'string') return escapeJsonStringValue(value);
|
|
130
|
+
|
|
131
|
+
return value;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Top-level wrapper around {@link prepareTemplateValue} that returns a new
|
|
136
|
+
* object with every leaf string escaped for JSON safety.
|
|
137
|
+
*
|
|
138
|
+
* Only the direct top-level entries are enumerated here; deeper recursion is
|
|
139
|
+
* delegated to {@link prepareTemplateValue}.
|
|
140
|
+
*/
|
|
141
|
+
const prepareTemplateData = (templateData: Record<string, unknown>): Record<string, unknown> => {
|
|
142
|
+
const prepared: Record<string, unknown> = {};
|
|
143
|
+
|
|
144
|
+
for (const [key, value] of Object.entries(templateData)) {
|
|
145
|
+
prepared[key] = prepareTemplateValue(value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return prepared;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Renders a Mustache-templated JSON string using `templateData`, with two
|
|
153
|
+
* LearnCard-specific adjustments on top of stock Mustache:
|
|
154
|
+
*
|
|
155
|
+
* 1. `templateData` is pre-escaped via {@link prepareTemplateData} so that
|
|
156
|
+
* user-supplied strings do not break the surrounding JSON.
|
|
157
|
+
* 2. Plain `{{var}}` tags are rewritten to triple-mustache `{{{var}}}` to
|
|
158
|
+
* disable Mustache's built-in HTML escaping. We want our own JSON escaping
|
|
159
|
+
* (from step 1), not `&` / `"` HTML entities.
|
|
160
|
+
*
|
|
161
|
+
* Section / comment / partial / unescaped tags — anything starting with
|
|
162
|
+
* `#`, `^`, `/`, `!`, `>`, `&`, or `=` — are left alone; Mustache already
|
|
163
|
+
* handles those correctly.
|
|
164
|
+
*
|
|
165
|
+
* @param jsonString - The raw boost JSON string containing `{{variables}}`
|
|
166
|
+
* @param templateData - Values to substitute, keyed by variable name
|
|
167
|
+
* @returns The rendered JSON string, ready to be `JSON.parse`'d
|
|
168
|
+
*/
|
|
169
|
+
const renderTemplateJson = (jsonString: string, templateData: Record<string, unknown>): string => {
|
|
170
|
+
const preparedData = prepareTemplateData(templateData);
|
|
171
|
+
|
|
172
|
+
// Rewrite `{{var}}` → `{{{var}}}` so Mustache skips its HTML escaping.
|
|
173
|
+
// Anything with a control sigil (#, ^, /, !, >, &, =) is a section /
|
|
174
|
+
// comment / partial / unescaped tag and must be passed through verbatim.
|
|
175
|
+
const unescapedTemplate = jsonString.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (match, tag) => {
|
|
176
|
+
const trimmedTag = tag.trim();
|
|
177
|
+
|
|
178
|
+
return /^[#^/!>&=]/.test(trimmedTag) ? match : `{{{${trimmedTag}}}}`;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return Mustache.render(unescapedTemplate, preparedData);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns `true` if the boost template itself already references `evidence`
|
|
186
|
+
* as a Mustache variable / section (e.g. `{{evidence}}`, `{{#evidence}}`,
|
|
187
|
+
* `{{evidence.id}}`).
|
|
188
|
+
*
|
|
189
|
+
* The trailing `[.\s}]|$` boundary prevents false positives on tokens that
|
|
190
|
+
* merely START with the word `evidence`, such as `{{evidenceList}}` or
|
|
191
|
+
* `{{evidenceItem.foo}}` — those should NOT count as the author handling
|
|
192
|
+
* evidence themselves.
|
|
193
|
+
*
|
|
194
|
+
* When this returns `true` the caller should skip the auto-append step in
|
|
195
|
+
* {@link appendTemplateEvidence} to avoid duplicating entries (the template
|
|
196
|
+
* author is already placing evidence wherever they want it in the output).
|
|
197
|
+
*/
|
|
198
|
+
const hasDynamicEvidenceTemplate = (jsonString: string): boolean => {
|
|
199
|
+
return /\{\{\s*[#^/]?\s*evidence(?:[.\s}]|$)/.test(jsonString);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Merges any `evidence` entries supplied in `templateData` onto the boost's
|
|
204
|
+
* top-level `evidence` array.
|
|
205
|
+
*
|
|
206
|
+
* This exists because not every boost template opts into rendering evidence
|
|
207
|
+
* via a `{{evidence}}` section. For those templates we still want evidence
|
|
208
|
+
* supplied at send-time (e.g. a recipient-specific media attachment) to land
|
|
209
|
+
* on the final credential — so we append it as a post-render step.
|
|
210
|
+
*
|
|
211
|
+
* Behavior:
|
|
212
|
+
* - Skipped entirely when `allowAutoAppend` is `false`. The caller typically
|
|
213
|
+
* derives that flag from {@link hasDynamicEvidenceTemplate} — if the
|
|
214
|
+
* template renders evidence itself, we must NOT also append it here, or the
|
|
215
|
+
* recipient would see the same evidence twice.
|
|
216
|
+
* - Accepts either a single evidence object or an array of them under
|
|
217
|
+
* `templateData.evidence` and normalizes to an array.
|
|
218
|
+
* - Strips the internal `last` marker used by upstream list-rendering code;
|
|
219
|
+
* that field is a rendering hint and should never land on the credential.
|
|
220
|
+
* - Preserves any evidence already present on the boost and appends the new
|
|
221
|
+
* entries after it.
|
|
222
|
+
*
|
|
223
|
+
* @param boost - The parsed credential object produced by `JSON.parse`
|
|
224
|
+
* @param templateData - The same template data used for rendering; may contain an `evidence` field
|
|
225
|
+
* @param allowAutoAppend - Set to `false` when the template already renders evidence itself
|
|
226
|
+
* @returns A new boost object (or the original reference when nothing changed)
|
|
227
|
+
*/
|
|
228
|
+
const appendTemplateEvidence = (
|
|
229
|
+
boost: any,
|
|
230
|
+
templateData?: Record<string, unknown>,
|
|
231
|
+
allowAutoAppend: boolean = true
|
|
232
|
+
) => {
|
|
233
|
+
if (!allowAutoAppend) return boost;
|
|
234
|
+
|
|
235
|
+
const templateEvidence = templateData?.evidence;
|
|
236
|
+
|
|
237
|
+
if (!templateEvidence) return boost;
|
|
238
|
+
|
|
239
|
+
// Accept either a single evidence object or an array; drop the `last`
|
|
240
|
+
// marker that upstream rendering code uses for iteration hints.
|
|
241
|
+
const normalizedTemplateEvidence = (
|
|
242
|
+
Array.isArray(templateEvidence) ? templateEvidence : [templateEvidence]
|
|
243
|
+
).map(evidenceItem => {
|
|
244
|
+
if (evidenceItem && typeof evidenceItem === 'object' && 'last' in evidenceItem) {
|
|
245
|
+
const { last, ...rest } = evidenceItem as Record<string, unknown>;
|
|
246
|
+
|
|
247
|
+
return rest;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return evidenceItem;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (normalizedTemplateEvidence.length === 0) return boost;
|
|
254
|
+
|
|
255
|
+
// Preserve any evidence the template already baked in, normalizing a
|
|
256
|
+
// single-object shape to an array so we can spread it.
|
|
257
|
+
const existingEvidence = boost.evidence
|
|
258
|
+
? Array.isArray(boost.evidence)
|
|
259
|
+
? boost.evidence
|
|
260
|
+
: [boost.evidence]
|
|
261
|
+
: [];
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
...boost,
|
|
265
|
+
evidence: [...existingEvidence, ...normalizedTemplateEvidence],
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const hasDid = (profile: LCNProfile | LCNVisibleProfile | undefined): profile is LCNProfile => {
|
|
270
|
+
return (
|
|
271
|
+
!!profile && 'did' in profile && typeof profile.did === 'string' && profile.did.length > 0
|
|
272
|
+
);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const appendNetworkCredentialStatus = async (
|
|
276
|
+
client: any,
|
|
277
|
+
credential: UnsignedVC,
|
|
278
|
+
statusPurposes: BitstringCredentialStatusPurpose[] = ['revocation']
|
|
279
|
+
): Promise<UnsignedVC> => {
|
|
280
|
+
if (!isVC2Format(credential)) return credential;
|
|
281
|
+
|
|
282
|
+
const existingStatuses = getCredentialStatusArray(credential);
|
|
283
|
+
const missingPurposes = statusPurposes.filter(statusPurpose => {
|
|
284
|
+
return !existingStatuses.some(
|
|
285
|
+
status =>
|
|
286
|
+
status.type === 'BitstringStatusListEntry' && status.statusPurpose === statusPurpose
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (missingPurposes.length === 0) return credential;
|
|
291
|
+
|
|
292
|
+
const entries = (await client.boost.allocateCredentialStatus.mutate({
|
|
293
|
+
statusPurposes: missingPurposes,
|
|
294
|
+
})) as BitstringCredentialStatusEntry[];
|
|
295
|
+
|
|
296
|
+
const credentialStatuses = [...existingStatuses, ...entries];
|
|
297
|
+
credential.credentialStatus = (
|
|
298
|
+
credentialStatuses.length === 1 ? credentialStatuses[0] : credentialStatuses
|
|
299
|
+
) as UnsignedVC['credentialStatus'];
|
|
300
|
+
|
|
301
|
+
return credential;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const issueCredentialWithNetworkStatus = async (
|
|
305
|
+
learnCard: LearnCard<any, any, LearnCardNetworkPluginDependentMethods>,
|
|
306
|
+
client: any,
|
|
307
|
+
credential: UnsignedVC,
|
|
308
|
+
statusPurposes?: BitstringCredentialStatusPurpose[]
|
|
309
|
+
): Promise<VC> => {
|
|
310
|
+
return learnCard.invoke.issueCredential(
|
|
311
|
+
await appendNetworkCredentialStatus(client, credential, statusPurposes)
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export * from './types';
|
|
316
|
+
|
|
317
|
+
export type GuardianApprovalGetter = () => string | undefined | Promise<string | undefined>;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @group Plugins
|
|
321
|
+
*/
|
|
322
|
+
export async function getLearnCardNetworkPlugin(
|
|
323
|
+
learnCard: LearnCard<any, 'id', LearnCardNetworkPluginDependentMethods>,
|
|
324
|
+
url: string,
|
|
325
|
+
apiTokenOrOptions?: {
|
|
326
|
+
guardianApprovalGetter?: GuardianApprovalGetter;
|
|
327
|
+
extraHeaders?: Record<string, string>;
|
|
328
|
+
}
|
|
329
|
+
): Promise<LearnCardNetworkPlugin>;
|
|
330
|
+
export async function getLearnCardNetworkPlugin(
|
|
331
|
+
learnCard: LearnCard<any, any, LearnCardNetworkPluginDependentMethods>,
|
|
332
|
+
url: string,
|
|
333
|
+
apiToken: string,
|
|
334
|
+
options?: {
|
|
335
|
+
guardianApprovalGetter?: GuardianApprovalGetter;
|
|
336
|
+
extraHeaders?: Record<string, string>;
|
|
337
|
+
}
|
|
338
|
+
): Promise<LearnCardNetworkPlugin>;
|
|
339
|
+
export async function getLearnCardNetworkPlugin(
|
|
340
|
+
learnCard: LearnCard<any, any, LearnCardNetworkPluginDependentMethods>,
|
|
341
|
+
url: string,
|
|
342
|
+
apiTokenOrOptions?:
|
|
343
|
+
| string
|
|
344
|
+
| {
|
|
345
|
+
guardianApprovalGetter?: GuardianApprovalGetter;
|
|
346
|
+
extraHeaders?: Record<string, string>;
|
|
347
|
+
},
|
|
348
|
+
options?: {
|
|
349
|
+
guardianApprovalGetter?: GuardianApprovalGetter;
|
|
350
|
+
extraHeaders?: Record<string, string>;
|
|
351
|
+
}
|
|
352
|
+
): Promise<LearnCardNetworkPlugin> {
|
|
353
|
+
const apiToken = typeof apiTokenOrOptions === 'string' ? apiTokenOrOptions : undefined;
|
|
354
|
+
const guardianApprovalGetter =
|
|
355
|
+
(typeof apiTokenOrOptions === 'object'
|
|
356
|
+
? apiTokenOrOptions?.guardianApprovalGetter
|
|
357
|
+
: undefined) ?? options?.guardianApprovalGetter;
|
|
358
|
+
|
|
359
|
+
const extraHeaders =
|
|
360
|
+
(typeof apiTokenOrOptions === 'object' ? apiTokenOrOptions?.extraHeaders : undefined) ??
|
|
361
|
+
options?.extraHeaders;
|
|
362
|
+
// Initialize DID safely: in API-key mode there may be no local ID plane provider
|
|
363
|
+
let did = '';
|
|
364
|
+
try {
|
|
365
|
+
const idPlane = (learnCard as any)?.id;
|
|
366
|
+
if (idPlane && typeof idPlane.did === 'function') {
|
|
367
|
+
did = idPlane.did();
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
learnCard?.debug?.('LCN: no local DID available at init; will fetch from profile');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
learnCard?.debug?.('Adding LearnCardNetwork Plugin');
|
|
374
|
+
const client = apiToken
|
|
375
|
+
? await getApiTokenClient(url, apiToken, guardianApprovalGetter, extraHeaders)
|
|
376
|
+
: await getClient(
|
|
377
|
+
url,
|
|
378
|
+
async challenge => {
|
|
379
|
+
const jwt = await learnCard.invoke.getDidAuthVp({
|
|
380
|
+
proofFormat: 'jwt',
|
|
381
|
+
challenge,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (typeof jwt !== 'string') throw new Error('Error getting DID-Auth-JWT!');
|
|
385
|
+
|
|
386
|
+
return jwt;
|
|
387
|
+
},
|
|
388
|
+
guardianApprovalGetter,
|
|
389
|
+
extraHeaders
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
let userData: LCNProfile | undefined;
|
|
393
|
+
|
|
394
|
+
learnCard?.debug?.('LCN: initial getProfile query starting', { apiToken: !!apiToken });
|
|
395
|
+
const initialQuery = client.profile.getProfile
|
|
396
|
+
.query()
|
|
397
|
+
.then((result: LCNProfile | undefined) => {
|
|
398
|
+
userData = result;
|
|
399
|
+
|
|
400
|
+
if (userData?.did) did = userData.did;
|
|
401
|
+
|
|
402
|
+
learnCard?.debug?.('LCN: initial getProfile success', { did });
|
|
403
|
+
return result;
|
|
404
|
+
})
|
|
405
|
+
.catch((error: any) => {
|
|
406
|
+
// Non-fatal: API-key tokens may not have profiles:read; avoid unhandled rejection
|
|
407
|
+
learnCard?.debug?.('LCN: getProfile failed (non-fatal)', error);
|
|
408
|
+
return undefined;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Ensure profile context is loaded (if available) before enforcing user presence.
|
|
412
|
+
// Prevents races where methods run before initial profile query resolves.
|
|
413
|
+
const ensureUser = async (): Promise<LCNProfile> => {
|
|
414
|
+
try {
|
|
415
|
+
await initialQuery;
|
|
416
|
+
} catch {
|
|
417
|
+
// ignore; initialQuery errors are non-fatal in API-key mode
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Retry once in API-token mode in case the initial query raced with method calls
|
|
421
|
+
if (!userData && apiToken) {
|
|
422
|
+
try {
|
|
423
|
+
learnCard?.debug?.('LCN ensureUser: retrying getProfile with apiToken');
|
|
424
|
+
const res = await client.profile.getProfile.query();
|
|
425
|
+
userData = res;
|
|
426
|
+
if (userData?.did) did = userData.did;
|
|
427
|
+
learnCard?.debug?.('LCN ensureUser: retry success', { did });
|
|
428
|
+
} catch (e) {
|
|
429
|
+
learnCard?.debug?.('LCN ensureUser: retry failed', e);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
434
|
+
|
|
435
|
+
return userData;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// Prefer authenticated boost retrieval for issuance flows.
|
|
439
|
+
// Falls back to storage resolve for backwards compatibility.
|
|
440
|
+
const getBoostTemplateForIssuance = async (_learnCard: any, boostUri: string) => {
|
|
441
|
+
try {
|
|
442
|
+
const boostRecord = await client.boost.getBoost.query({ uri: boostUri });
|
|
443
|
+
return boostRecord.boost;
|
|
444
|
+
} catch (error) {
|
|
445
|
+
learnCard?.debug?.(
|
|
446
|
+
'LCN: getBoost failed for template retrieval, falling back to resolveFromLCN',
|
|
447
|
+
error
|
|
448
|
+
);
|
|
449
|
+
return _learnCard.invoke.resolveFromLCN(boostUri);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const getInboxEndpointForDid = async (
|
|
454
|
+
_learnCard: any,
|
|
455
|
+
recipientDid: string
|
|
456
|
+
): Promise<string> => {
|
|
457
|
+
const serviceUrl = new URL(url);
|
|
458
|
+
const serviceDomain = `${serviceUrl.hostname}${
|
|
459
|
+
serviceUrl.port ? `%3A${serviceUrl.port}` : ''
|
|
460
|
+
}`;
|
|
461
|
+
const localServiceDid = `did:web:${serviceDomain}`;
|
|
462
|
+
|
|
463
|
+
const getInferredServiceDid = (did: string): string | null => {
|
|
464
|
+
if (!did.startsWith('did:web:')) return null;
|
|
465
|
+
|
|
466
|
+
const parts = did.split(':');
|
|
467
|
+
if (parts.length < 3) return null;
|
|
468
|
+
|
|
469
|
+
const pathMarkerIndex = parts.findIndex((part, index) => {
|
|
470
|
+
if (index < 3) return false;
|
|
471
|
+
return part === 'users' || part === 'app' || part === 'manager';
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
if (pathMarkerIndex === -1) {
|
|
475
|
+
return did;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return `did:web:${parts.slice(2, pathMarkerIndex).join(':')}`;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const getInboxService = (didDoc: {
|
|
482
|
+
service?: Array<{
|
|
483
|
+
type: string | string[];
|
|
484
|
+
serviceEndpoint?: string;
|
|
485
|
+
serviceDid?: string;
|
|
486
|
+
}>;
|
|
487
|
+
}) =>
|
|
488
|
+
didDoc.service?.find(service => {
|
|
489
|
+
const type = Array.isArray(service.type) ? service.type[0] : service.type;
|
|
490
|
+
return type === 'UniversalInboxService' || type === 'LearnCardInboxService';
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const inferredServiceDid = getInferredServiceDid(recipientDid);
|
|
494
|
+
|
|
495
|
+
if (inferredServiceDid === localServiceDid) {
|
|
496
|
+
return `${serviceUrl.origin}/api/inbox/receive`;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const didDoc = await _learnCard.invoke.resolveDid(recipientDid);
|
|
500
|
+
const inboxService = getInboxService(didDoc);
|
|
501
|
+
|
|
502
|
+
if ((inboxService?.serviceDid || inferredServiceDid) === localServiceDid) {
|
|
503
|
+
return `${serviceUrl.origin}/api/inbox/receive`;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (!inboxService?.serviceEndpoint) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
`Recipient DID ${recipientDid} does not have a UniversalInboxService endpoint in its DID document`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return inboxService.serviceEndpoint;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
name: 'LearnCard Network',
|
|
517
|
+
displayName: 'LearnCard Network',
|
|
518
|
+
description: 'LearnCard Network Integration',
|
|
519
|
+
id: {
|
|
520
|
+
did: (_learnCard, method) => {
|
|
521
|
+
if (!method || method === 'web') return did;
|
|
522
|
+
|
|
523
|
+
return (learnCard as any).id?.did?.(method);
|
|
524
|
+
},
|
|
525
|
+
keypair: (_learnCard, algorithm) => (learnCard as any).id?.keypair?.(algorithm),
|
|
526
|
+
},
|
|
527
|
+
read: {
|
|
528
|
+
get: async (_learnCard, vcUri) => {
|
|
529
|
+
_learnCard.debug?.("learnCard.read['LearnCard Network'].get");
|
|
530
|
+
|
|
531
|
+
if (!vcUri) return undefined;
|
|
532
|
+
|
|
533
|
+
const parts = vcUri.split(':');
|
|
534
|
+
|
|
535
|
+
if (parts.length !== 5) return undefined;
|
|
536
|
+
|
|
537
|
+
const [lc, method] = parts as [string, string, string, string, string];
|
|
538
|
+
|
|
539
|
+
if (lc !== 'lc' || method !== 'network') return undefined;
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
let result = await client.storage.resolve.query({ uri: vcUri });
|
|
543
|
+
|
|
544
|
+
if ('ciphertext' in result) {
|
|
545
|
+
try {
|
|
546
|
+
const kp = _learnCard.id.keypair();
|
|
547
|
+
result = await _learnCard.invoke.decryptDagJwe(result as JWE, [kp]);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
_learnCard.debug?.(
|
|
550
|
+
'LCN read.get: skipping decryption (no keypair?)',
|
|
551
|
+
e
|
|
552
|
+
);
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const parsed = await VCValidator.or(VPValidator)
|
|
558
|
+
.or(StoredCredentialEnvelopeValidator)
|
|
559
|
+
.parseAsync(result);
|
|
560
|
+
return resolveStorageReadResult(parsed);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
_learnCard.debug?.(error);
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
store: {
|
|
568
|
+
upload: async (_learnCard, credential) => {
|
|
569
|
+
_learnCard.debug?.("learnCard.store['LearnCard Network'].upload");
|
|
570
|
+
|
|
571
|
+
return client.storage.store.mutate({
|
|
572
|
+
item: normalizeEnvelopeForTransport(credential),
|
|
573
|
+
});
|
|
574
|
+
},
|
|
575
|
+
uploadEncrypted: async (
|
|
576
|
+
_learnCard,
|
|
577
|
+
credential,
|
|
578
|
+
{ recipients = [] } = { recipients: [] }
|
|
579
|
+
) => {
|
|
580
|
+
_learnCard.debug?.("learnCard.store['LearnCard Network'].upload");
|
|
581
|
+
await ensureUser();
|
|
582
|
+
|
|
583
|
+
// Use known DID if available (from init or loaded profile)
|
|
584
|
+
const selfDid = did || userData?.did || '';
|
|
585
|
+
const recipientsList = [selfDid, ...recipients].filter(Boolean);
|
|
586
|
+
|
|
587
|
+
if (recipientsList.length === 0) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
'No recipients specified for encrypted upload; provide at least one DID'
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const jwe = await _learnCard.invoke.createDagJwe(
|
|
594
|
+
normalizeEnvelopeForTransport(credential),
|
|
595
|
+
recipientsList
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
return client.storage.store.mutate({ item: jwe });
|
|
599
|
+
},
|
|
600
|
+
delete: async (_learnCard, uri) => {
|
|
601
|
+
await ensureUser();
|
|
602
|
+
|
|
603
|
+
return client.credential.deleteCredential.mutate({ uri });
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
methods: {
|
|
607
|
+
createProfile: async (_learnCard, profile) => {
|
|
608
|
+
try {
|
|
609
|
+
await ensureUser();
|
|
610
|
+
} catch {
|
|
611
|
+
// ignore; This is to prevent a race condition but we are expecting the profile
|
|
612
|
+
// to not exist yet here.
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (userData) throw new Error('Account already exists!');
|
|
616
|
+
|
|
617
|
+
const newDid = await client.profile.createProfile.mutate(profile);
|
|
618
|
+
|
|
619
|
+
userData = await client.profile.getProfile.query();
|
|
620
|
+
did = newDid;
|
|
621
|
+
|
|
622
|
+
return newDid;
|
|
623
|
+
},
|
|
624
|
+
createServiceProfile: async (_learnCard, profile) => {
|
|
625
|
+
try {
|
|
626
|
+
await ensureUser();
|
|
627
|
+
} catch {
|
|
628
|
+
// ignore; This is to prevent a race condition but we are expecting the profile
|
|
629
|
+
// to not exist yet here.
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const newDid = await client.profile.createServiceProfile.mutate(profile);
|
|
633
|
+
|
|
634
|
+
userData = await client.profile.getProfile.query();
|
|
635
|
+
did = newDid;
|
|
636
|
+
|
|
637
|
+
return newDid;
|
|
638
|
+
},
|
|
639
|
+
createManagedProfile: async (_learnCard, profile) => {
|
|
640
|
+
const newDid = await client.profileManager.createManagedProfile.mutate(profile);
|
|
641
|
+
|
|
642
|
+
return newDid;
|
|
643
|
+
},
|
|
644
|
+
createProfileManager: async (_learnCard, profile) => {
|
|
645
|
+
await ensureUser();
|
|
646
|
+
|
|
647
|
+
const newDid = await client.profileManager.createProfileManager.mutate(profile);
|
|
648
|
+
|
|
649
|
+
return newDid;
|
|
650
|
+
},
|
|
651
|
+
createChildProfileManager: async (_learnCard, parentUri, profile) => {
|
|
652
|
+
await ensureUser();
|
|
653
|
+
|
|
654
|
+
const newDid = await client.profileManager.createChildProfileManager.mutate({
|
|
655
|
+
parentUri,
|
|
656
|
+
profile,
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
return newDid;
|
|
660
|
+
},
|
|
661
|
+
createManagedServiceProfile: async (_learnCard, profile) => {
|
|
662
|
+
await ensureUser();
|
|
663
|
+
|
|
664
|
+
const newDid = await client.profile.createManagedServiceProfile.mutate(profile);
|
|
665
|
+
|
|
666
|
+
return newDid;
|
|
667
|
+
},
|
|
668
|
+
getAvailableProfiles: async (_learnCard, options = {}) => {
|
|
669
|
+
await ensureUser();
|
|
670
|
+
|
|
671
|
+
return client.profile.getAvailableProfiles.query(options);
|
|
672
|
+
},
|
|
673
|
+
getManagedProfiles: async (_learnCard, options = {}) => {
|
|
674
|
+
return client.profileManager.getManagedProfiles.query(options);
|
|
675
|
+
},
|
|
676
|
+
claimPendingGuardianLinks: async () => {
|
|
677
|
+
await ensureUser();
|
|
678
|
+
return client.inbox.claimPendingGuardianLinks.mutate({});
|
|
679
|
+
},
|
|
680
|
+
getMyManagedChildren: async () => {
|
|
681
|
+
await ensureUser();
|
|
682
|
+
return client.profileManager.getMyManagedChildren.query();
|
|
683
|
+
},
|
|
684
|
+
getMyGuardians: async () => {
|
|
685
|
+
await ensureUser();
|
|
686
|
+
return client.profileManager.getMyGuardians.query();
|
|
687
|
+
},
|
|
688
|
+
removeManagesRelationship: async (_learnCard, profileId) => {
|
|
689
|
+
await ensureUser();
|
|
690
|
+
return client.profileManager.removeManagesRelationship.mutate({ profileId });
|
|
691
|
+
},
|
|
692
|
+
getManagedServiceProfiles: async (_learnCard, options = {}) => {
|
|
693
|
+
await ensureUser();
|
|
694
|
+
|
|
695
|
+
return client.profile.getManagedServiceProfiles.query(options);
|
|
696
|
+
},
|
|
697
|
+
updateProfile: async (_learnCard, profile) => {
|
|
698
|
+
await ensureUser();
|
|
699
|
+
|
|
700
|
+
const result = await client.profile.updateProfile.mutate(profile);
|
|
701
|
+
|
|
702
|
+
if (result) {
|
|
703
|
+
userData = await client.profile.getProfile.query();
|
|
704
|
+
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return false;
|
|
709
|
+
},
|
|
710
|
+
updateProfileManagerProfile: async (_learnCard, manager) => {
|
|
711
|
+
return client.profileManager.updateProfileManager.mutate(manager);
|
|
712
|
+
},
|
|
713
|
+
deleteProfile: async () => {
|
|
714
|
+
if (!userData) throw new Error('Account does not exist!');
|
|
715
|
+
|
|
716
|
+
const result = await client.profile.deleteProfile.mutate();
|
|
717
|
+
|
|
718
|
+
if (result) {
|
|
719
|
+
userData = undefined;
|
|
720
|
+
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return false;
|
|
725
|
+
},
|
|
726
|
+
getProfile: async (_learnCard, profileId) => {
|
|
727
|
+
try {
|
|
728
|
+
await ensureUser();
|
|
729
|
+
} catch {}
|
|
730
|
+
|
|
731
|
+
// If no profileId is provided, return whatever we have cached locally.
|
|
732
|
+
if (!profileId) return userData;
|
|
733
|
+
|
|
734
|
+
return client.profile.getOtherProfile.query({ profileId });
|
|
735
|
+
},
|
|
736
|
+
getProfileManagerProfile: async (_learnCard, id) => {
|
|
737
|
+
if (!id) return client.profileManager.getProfileManager.query();
|
|
738
|
+
|
|
739
|
+
return client.profileManager.getOtherProfileManager.query({ id });
|
|
740
|
+
},
|
|
741
|
+
searchProfiles: async (
|
|
742
|
+
_learnCard,
|
|
743
|
+
input = '',
|
|
744
|
+
{
|
|
745
|
+
limit = 25,
|
|
746
|
+
includeSelf = false,
|
|
747
|
+
includeConnectionStatus = false,
|
|
748
|
+
includeServiceProfiles = false,
|
|
749
|
+
} = {}
|
|
750
|
+
) => {
|
|
751
|
+
return client.profile.searchProfiles.query({
|
|
752
|
+
input,
|
|
753
|
+
limit,
|
|
754
|
+
includeSelf,
|
|
755
|
+
includeConnectionStatus,
|
|
756
|
+
includeServiceProfiles,
|
|
757
|
+
});
|
|
758
|
+
},
|
|
759
|
+
connectWith: async (_learnCard, profileId) => {
|
|
760
|
+
await ensureUser();
|
|
761
|
+
|
|
762
|
+
return client.profile.connectWith.mutate({ profileId });
|
|
763
|
+
},
|
|
764
|
+
connectWithExpiredInvite: async (_learnCard, profileId) => {
|
|
765
|
+
await ensureUser();
|
|
766
|
+
|
|
767
|
+
return client.profile.connectWithExpiredInvite.mutate({ profileId });
|
|
768
|
+
},
|
|
769
|
+
cancelConnectionRequest: async (_learnCard, profileId) => {
|
|
770
|
+
await ensureUser();
|
|
771
|
+
|
|
772
|
+
return client.profile.cancelConnectionRequest.mutate({ profileId });
|
|
773
|
+
},
|
|
774
|
+
connectWithInvite: async (_learnCard, profileId, challenge) => {
|
|
775
|
+
await ensureUser();
|
|
776
|
+
|
|
777
|
+
return client.profile.connectWithInvite.mutate({ profileId, challenge });
|
|
778
|
+
},
|
|
779
|
+
disconnectWith: async (_learnCard, profileId) => {
|
|
780
|
+
await ensureUser();
|
|
781
|
+
|
|
782
|
+
return client.profile.disconnectWith.mutate({ profileId });
|
|
783
|
+
},
|
|
784
|
+
acceptConnectionRequest: async (_learnCard, profileId) => {
|
|
785
|
+
await ensureUser();
|
|
786
|
+
|
|
787
|
+
return client.profile.acceptConnectionRequest.mutate({ profileId });
|
|
788
|
+
},
|
|
789
|
+
getConnections: async _learnCard => {
|
|
790
|
+
console.warn(
|
|
791
|
+
'The getConnections method is deprecated! Please use getPaginatedConnections instead!'
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
await ensureUser();
|
|
795
|
+
|
|
796
|
+
return client.profile.connections.query();
|
|
797
|
+
},
|
|
798
|
+
getPaginatedConnections: async (_learnCard, options) => {
|
|
799
|
+
await ensureUser();
|
|
800
|
+
|
|
801
|
+
return client.profile.paginatedConnections.query(options);
|
|
802
|
+
},
|
|
803
|
+
getPendingConnections: async _learnCard => {
|
|
804
|
+
console.warn(
|
|
805
|
+
'The getPendingConnections method is deprecated! Please use getPaginatedPendingConnections instead!'
|
|
806
|
+
);
|
|
807
|
+
|
|
808
|
+
await ensureUser();
|
|
809
|
+
|
|
810
|
+
return client.profile.pendingConnections.query();
|
|
811
|
+
},
|
|
812
|
+
getPaginatedPendingConnections: async (_learnCard, options) => {
|
|
813
|
+
await ensureUser();
|
|
814
|
+
|
|
815
|
+
return client.profile.paginatedPendingConnections.query(options);
|
|
816
|
+
},
|
|
817
|
+
getConnectionRequests: async _learnCard => {
|
|
818
|
+
console.warn(
|
|
819
|
+
'The getConnectionRequests method is deprecated! Please use getPaginatedConnectionRequests instead!'
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
await ensureUser();
|
|
823
|
+
|
|
824
|
+
return client.profile.connectionRequests.query();
|
|
825
|
+
},
|
|
826
|
+
getPaginatedConnectionRequests: async (_learnCard, options) => {
|
|
827
|
+
await ensureUser();
|
|
828
|
+
|
|
829
|
+
return client.profile.paginatedConnectionRequests.query(options);
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
generateInvite: async (_learnCard, challenge, expiration, maxUses) => {
|
|
833
|
+
await ensureUser();
|
|
834
|
+
|
|
835
|
+
return client.profile.generateInvite.mutate({ challenge, expiration, maxUses });
|
|
836
|
+
},
|
|
837
|
+
|
|
838
|
+
listInvites: async () => {
|
|
839
|
+
await ensureUser();
|
|
840
|
+
|
|
841
|
+
return client.profile.listInvites.query();
|
|
842
|
+
},
|
|
843
|
+
|
|
844
|
+
invalidateInvite: async (_learnCard, challenge) => {
|
|
845
|
+
await ensureUser();
|
|
846
|
+
|
|
847
|
+
return client.profile.invalidateInvite.mutate({ challenge });
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
blockProfile: async (_learnCard, profileId) => {
|
|
851
|
+
await ensureUser();
|
|
852
|
+
|
|
853
|
+
return client.profile.blockProfile.mutate({ profileId });
|
|
854
|
+
},
|
|
855
|
+
unblockProfile: async (_learnCard, profileId) => {
|
|
856
|
+
await ensureUser();
|
|
857
|
+
|
|
858
|
+
return client.profile.unblockProfile.mutate({ profileId });
|
|
859
|
+
},
|
|
860
|
+
getBlockedProfiles: async () => {
|
|
861
|
+
await ensureUser();
|
|
862
|
+
|
|
863
|
+
return client.profile.blocked.query();
|
|
864
|
+
},
|
|
865
|
+
|
|
866
|
+
sendCredential: async (_learnCard, profileId, vc, metadataOrEncrypt, encrypt) => {
|
|
867
|
+
await ensureUser();
|
|
868
|
+
|
|
869
|
+
if (profileId.startsWith('did:web:')) {
|
|
870
|
+
const inboxEndpoint = await getInboxEndpointForDid(_learnCard, profileId);
|
|
871
|
+
|
|
872
|
+
// Handle metadata parameter
|
|
873
|
+
let metadata: Record<string, unknown> | undefined;
|
|
874
|
+
if (typeof metadataOrEncrypt === 'object') {
|
|
875
|
+
metadata = metadataOrEncrypt;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const myProfile = await client.profile.getProfile.query();
|
|
879
|
+
const issuerDid = myProfile?.did || (await client.utilities.getDid.query());
|
|
880
|
+
const issuerDisplayName = myProfile?.displayName || 'Unknown Issuer';
|
|
881
|
+
const signedCredential = await issueCredentialWithNetworkStatus(
|
|
882
|
+
_learnCard,
|
|
883
|
+
client,
|
|
884
|
+
vc
|
|
885
|
+
);
|
|
886
|
+
const didAuthJwt = await _learnCard.invoke.getDidAuthVp({
|
|
887
|
+
proofFormat: 'jwt',
|
|
888
|
+
challenge: `inbox-federation-${crypto.randomUUID()}`,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
let receiveUrl = inboxEndpoint;
|
|
892
|
+
|
|
893
|
+
if (receiveUrl.includes('localhost')) {
|
|
894
|
+
receiveUrl = receiveUrl.replace('https://', 'http://');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const response = await fetch(receiveUrl, {
|
|
898
|
+
method: 'POST',
|
|
899
|
+
headers: {
|
|
900
|
+
'Content-Type': 'application/json',
|
|
901
|
+
'Authorization': `Bearer ${didAuthJwt}`,
|
|
902
|
+
},
|
|
903
|
+
body: JSON.stringify({
|
|
904
|
+
recipientDid: profileId,
|
|
905
|
+
credential: signedCredential,
|
|
906
|
+
issuerDid,
|
|
907
|
+
issuerDisplayName,
|
|
908
|
+
configuration: {
|
|
909
|
+
...metadata,
|
|
910
|
+
federatedFrom: issuerDid,
|
|
911
|
+
},
|
|
912
|
+
}),
|
|
913
|
+
signal: AbortSignal.timeout(30000),
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
if (!response.ok) {
|
|
917
|
+
const error = await response.text();
|
|
918
|
+
throw new Error(`Federation failed: ${error}`);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const result = await response.json();
|
|
922
|
+
return result.issuanceId;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Handle backward compatibility: if metadataOrEncrypt is a boolean, it's the encrypt flag
|
|
926
|
+
let metadata: Record<string, unknown> | undefined;
|
|
927
|
+
let shouldEncrypt = true;
|
|
928
|
+
|
|
929
|
+
if (typeof metadataOrEncrypt === 'boolean') {
|
|
930
|
+
shouldEncrypt = metadataOrEncrypt;
|
|
931
|
+
} else if (metadataOrEncrypt !== undefined) {
|
|
932
|
+
metadata = metadataOrEncrypt;
|
|
933
|
+
shouldEncrypt = encrypt ?? true;
|
|
934
|
+
} else {
|
|
935
|
+
shouldEncrypt = encrypt ?? true;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (!shouldEncrypt) {
|
|
939
|
+
return client.credential.sendCredential.mutate({
|
|
940
|
+
profileId,
|
|
941
|
+
credential: vc,
|
|
942
|
+
metadata,
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const target = await _learnCard.invoke.getProfile(profileId);
|
|
947
|
+
|
|
948
|
+
if (!target) throw new Error('Could not find target account');
|
|
949
|
+
if (!hasDid(target)) throw new Error('Could not find target DID');
|
|
950
|
+
|
|
951
|
+
const credential = await _learnCard.invoke.createDagJwe(vc, [
|
|
952
|
+
_learnCard.id.did(),
|
|
953
|
+
target.did,
|
|
954
|
+
]);
|
|
955
|
+
|
|
956
|
+
return client.credential.sendCredential.mutate({
|
|
957
|
+
profileId,
|
|
958
|
+
credential,
|
|
959
|
+
metadata,
|
|
960
|
+
});
|
|
961
|
+
},
|
|
962
|
+
acceptCredential: async (_learnCard, uri, options) => {
|
|
963
|
+
await ensureUser();
|
|
964
|
+
|
|
965
|
+
return client.credential.acceptCredential.mutate({ uri, options });
|
|
966
|
+
},
|
|
967
|
+
getReceivedCredentials: async (_learnCard, from) => {
|
|
968
|
+
await ensureUser();
|
|
969
|
+
|
|
970
|
+
return client.credential.receivedCredentials.query({ from });
|
|
971
|
+
},
|
|
972
|
+
getRevokedCredentials: async () => {
|
|
973
|
+
await ensureUser();
|
|
974
|
+
|
|
975
|
+
return client.credential.getRevokedCredentials.query();
|
|
976
|
+
},
|
|
977
|
+
getSentCredentials: async (_learnCard, to) => {
|
|
978
|
+
await ensureUser();
|
|
979
|
+
|
|
980
|
+
return client.credential.sentCredentials.query({ to });
|
|
981
|
+
},
|
|
982
|
+
getIncomingCredentials: async (_learnCard, from) => {
|
|
983
|
+
await ensureUser();
|
|
984
|
+
|
|
985
|
+
return client.credential.incomingCredentials.query({ from });
|
|
986
|
+
},
|
|
987
|
+
deleteCredential: async (_learnCard, uri) => {
|
|
988
|
+
await ensureUser();
|
|
989
|
+
|
|
990
|
+
return client.credential.deleteCredential.mutate({ uri });
|
|
991
|
+
},
|
|
992
|
+
|
|
993
|
+
sendPresentation: async (_learnCard, profileId, vp, encrypt = true) => {
|
|
994
|
+
await ensureUser();
|
|
995
|
+
|
|
996
|
+
if (!encrypt) {
|
|
997
|
+
return client.presentation.sendPresentation.mutate({
|
|
998
|
+
profileId,
|
|
999
|
+
presentation: vp,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const target = await _learnCard.invoke.getProfile(profileId);
|
|
1004
|
+
|
|
1005
|
+
if (!target) throw new Error('Could not find target account');
|
|
1006
|
+
if (!hasDid(target)) throw new Error('Could not find target DID');
|
|
1007
|
+
|
|
1008
|
+
const presentation = await _learnCard.invoke.createDagJwe(vp, [
|
|
1009
|
+
_learnCard.id.did(),
|
|
1010
|
+
target.did,
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
return client.presentation.sendPresentation.mutate({ profileId, presentation });
|
|
1014
|
+
},
|
|
1015
|
+
acceptPresentation: async (_learnCard, uri) => {
|
|
1016
|
+
await ensureUser();
|
|
1017
|
+
|
|
1018
|
+
return client.presentation.acceptPresentation.mutate({ uri });
|
|
1019
|
+
},
|
|
1020
|
+
getReceivedPresentations: async (_learnCard, from) => {
|
|
1021
|
+
await ensureUser();
|
|
1022
|
+
|
|
1023
|
+
return client.presentation.receivedPresentations.query({ from });
|
|
1024
|
+
},
|
|
1025
|
+
getSentPresentations: async (_learnCard, to) => {
|
|
1026
|
+
await ensureUser();
|
|
1027
|
+
|
|
1028
|
+
return client.presentation.sentPresentations.query({ to });
|
|
1029
|
+
},
|
|
1030
|
+
getIncomingPresentations: async (_learnCard, from) => {
|
|
1031
|
+
await ensureUser();
|
|
1032
|
+
|
|
1033
|
+
return client.presentation.incomingPresentations.query({ from });
|
|
1034
|
+
},
|
|
1035
|
+
deletePresentation: async (_learnCard, uri) => {
|
|
1036
|
+
await ensureUser();
|
|
1037
|
+
|
|
1038
|
+
return client.presentation.deletePresentation.mutate({ uri });
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
createBoost: async (_learnCard, credential, metadata) => {
|
|
1042
|
+
await ensureUser();
|
|
1043
|
+
const { skills, ...rest } = metadata ?? {};
|
|
1044
|
+
|
|
1045
|
+
const payload: any = { credential, ...rest };
|
|
1046
|
+
if (Array.isArray(skills) && skills.length > 0) payload.skills = skills;
|
|
1047
|
+
|
|
1048
|
+
return client.boost.createBoost.mutate(payload);
|
|
1049
|
+
},
|
|
1050
|
+
createChildBoost: async (_learnCard, parentUri, credential, metadata) => {
|
|
1051
|
+
await ensureUser();
|
|
1052
|
+
const { skills, ...restMetadata } = metadata || {};
|
|
1053
|
+
|
|
1054
|
+
const payload: any = { parentUri, boost: { credential, ...restMetadata } };
|
|
1055
|
+
if (Array.isArray(skills) && skills.length > 0) payload.skills = skills;
|
|
1056
|
+
|
|
1057
|
+
return client.boost.createChildBoost.mutate(payload);
|
|
1058
|
+
},
|
|
1059
|
+
getBoost: async (_learnCard, uri) => {
|
|
1060
|
+
await ensureUser();
|
|
1061
|
+
|
|
1062
|
+
return client.boost.getBoost.query({ uri });
|
|
1063
|
+
},
|
|
1064
|
+
getBoostSkills: async (_learnCard, uri) => {
|
|
1065
|
+
await ensureUser();
|
|
1066
|
+
|
|
1067
|
+
return client.boost.getBoostSkills.query({ uri });
|
|
1068
|
+
},
|
|
1069
|
+
getBoostFrameworks: async (_learnCard, uri, options = {}) => {
|
|
1070
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1071
|
+
|
|
1072
|
+
const { limit, cursor, query } = options ?? {};
|
|
1073
|
+
|
|
1074
|
+
return client.boost.getBoostFrameworks.query({ uri, limit, cursor, query });
|
|
1075
|
+
},
|
|
1076
|
+
getSkillsAvailableForBoost: async (_learnCard, uri) => {
|
|
1077
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1078
|
+
|
|
1079
|
+
return client.boost.getSkillsAvailableForBoost.query({ uri });
|
|
1080
|
+
},
|
|
1081
|
+
searchSkillsAvailableForBoost: async (_learnCard, uri, query, options = {}) => {
|
|
1082
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1083
|
+
|
|
1084
|
+
return client.boost.searchSkillsAvailableForBoost.query({ uri, query, ...options });
|
|
1085
|
+
},
|
|
1086
|
+
getBoosts: async (_learnCard, query) => {
|
|
1087
|
+
console.warn(
|
|
1088
|
+
'The getBoosts method is deprecated! Please use getPaginatedBoosts instead!'
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
await ensureUser();
|
|
1092
|
+
|
|
1093
|
+
return client.boost.getBoosts.query({ query });
|
|
1094
|
+
},
|
|
1095
|
+
countBoosts: async (_learnCard, query) => {
|
|
1096
|
+
await ensureUser();
|
|
1097
|
+
|
|
1098
|
+
return client.boost.countBoosts.query({ query });
|
|
1099
|
+
},
|
|
1100
|
+
getPaginatedBoosts: async (_learnCard, options) => {
|
|
1101
|
+
await ensureUser();
|
|
1102
|
+
|
|
1103
|
+
return client.boost.getPaginatedBoosts.query(options);
|
|
1104
|
+
},
|
|
1105
|
+
getBoostChildren: async (_learnCard, uri, options) => {
|
|
1106
|
+
await ensureUser();
|
|
1107
|
+
|
|
1108
|
+
return client.boost.getBoostChildren.query({ uri, ...options });
|
|
1109
|
+
},
|
|
1110
|
+
countBoostChildren: async (_learnCard, uri, options) => {
|
|
1111
|
+
await ensureUser();
|
|
1112
|
+
|
|
1113
|
+
return client.boost.countBoostChildren.query({ uri, ...options });
|
|
1114
|
+
},
|
|
1115
|
+
getBoostSiblings: async (_learnCard, uri, options) => {
|
|
1116
|
+
await ensureUser();
|
|
1117
|
+
|
|
1118
|
+
return client.boost.getBoostSiblings.query({ uri, ...options });
|
|
1119
|
+
},
|
|
1120
|
+
countBoostSiblings: async (_learnCard, uri, options) => {
|
|
1121
|
+
await ensureUser();
|
|
1122
|
+
|
|
1123
|
+
return client.boost.countBoostSiblings.query({ uri, ...options });
|
|
1124
|
+
},
|
|
1125
|
+
getFamilialBoosts: async (_learnCard, uri, options) => {
|
|
1126
|
+
await ensureUser();
|
|
1127
|
+
|
|
1128
|
+
return client.boost.getFamilialBoosts.query({ uri, ...options });
|
|
1129
|
+
},
|
|
1130
|
+
countFamilialBoosts: async (_learnCard, uri, options) => {
|
|
1131
|
+
await ensureUser();
|
|
1132
|
+
|
|
1133
|
+
return client.boost.countFamilialBoosts.query({ uri, ...options });
|
|
1134
|
+
},
|
|
1135
|
+
getBoostParents: async (_learnCard, uri, options) => {
|
|
1136
|
+
await ensureUser();
|
|
1137
|
+
|
|
1138
|
+
return client.boost.getBoostParents.query({ uri, ...options });
|
|
1139
|
+
},
|
|
1140
|
+
countBoostParents: async (_learnCard, uri, options) => {
|
|
1141
|
+
await ensureUser();
|
|
1142
|
+
|
|
1143
|
+
return client.boost.countBoostParents.query({ uri, ...options });
|
|
1144
|
+
},
|
|
1145
|
+
makeBoostParent: async (_learnCard, uris) => {
|
|
1146
|
+
await ensureUser();
|
|
1147
|
+
|
|
1148
|
+
return client.boost.makeBoostParent.mutate(uris);
|
|
1149
|
+
},
|
|
1150
|
+
removeBoostParent: async (_learnCard, uris) => {
|
|
1151
|
+
await ensureUser();
|
|
1152
|
+
|
|
1153
|
+
return client.boost.removeBoostParent.mutate(uris);
|
|
1154
|
+
},
|
|
1155
|
+
getBoostRecipients: async (
|
|
1156
|
+
_learnCard,
|
|
1157
|
+
uri,
|
|
1158
|
+
limit = 25,
|
|
1159
|
+
skip = undefined,
|
|
1160
|
+
includeUnacceptedBoosts = true
|
|
1161
|
+
) => {
|
|
1162
|
+
console.warn(
|
|
1163
|
+
'The getBoostRecipients method is deprecated! Please use getPaginatedBoostRecipients instead!'
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
await ensureUser();
|
|
1167
|
+
|
|
1168
|
+
return client.boost.getBoostRecipients.query({
|
|
1169
|
+
uri,
|
|
1170
|
+
limit,
|
|
1171
|
+
skip,
|
|
1172
|
+
includeUnacceptedBoosts,
|
|
1173
|
+
});
|
|
1174
|
+
},
|
|
1175
|
+
getPaginatedBoostRecipients: async (
|
|
1176
|
+
_learnCard,
|
|
1177
|
+
uri,
|
|
1178
|
+
limit = 25,
|
|
1179
|
+
cursor = undefined,
|
|
1180
|
+
includeUnacceptedBoosts = true,
|
|
1181
|
+
query
|
|
1182
|
+
) => {
|
|
1183
|
+
await ensureUser();
|
|
1184
|
+
|
|
1185
|
+
return client.boost.getPaginatedBoostRecipients.query({
|
|
1186
|
+
uri,
|
|
1187
|
+
limit,
|
|
1188
|
+
cursor,
|
|
1189
|
+
includeUnacceptedBoosts,
|
|
1190
|
+
query,
|
|
1191
|
+
});
|
|
1192
|
+
},
|
|
1193
|
+
getPaginatedBoostRecipientsWithChildren: async (
|
|
1194
|
+
_learnCard,
|
|
1195
|
+
uri,
|
|
1196
|
+
limit = 25,
|
|
1197
|
+
cursor = undefined,
|
|
1198
|
+
includeUnacceptedBoosts = true,
|
|
1199
|
+
boostQuery,
|
|
1200
|
+
profileQuery,
|
|
1201
|
+
numberOfGenerations = 1
|
|
1202
|
+
) => {
|
|
1203
|
+
await ensureUser();
|
|
1204
|
+
|
|
1205
|
+
return client.boost.getPaginatedBoostRecipientsWithChildren.query({
|
|
1206
|
+
uri,
|
|
1207
|
+
limit,
|
|
1208
|
+
cursor,
|
|
1209
|
+
includeUnacceptedBoosts,
|
|
1210
|
+
boostQuery,
|
|
1211
|
+
profileQuery,
|
|
1212
|
+
numberOfGenerations,
|
|
1213
|
+
});
|
|
1214
|
+
},
|
|
1215
|
+
countBoostRecipientsWithChildren: async (
|
|
1216
|
+
_learnCard,
|
|
1217
|
+
uri,
|
|
1218
|
+
includeUnacceptedBoosts = true,
|
|
1219
|
+
boostQuery,
|
|
1220
|
+
profileQuery,
|
|
1221
|
+
numberOfGenerations = 1
|
|
1222
|
+
) => {
|
|
1223
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1224
|
+
|
|
1225
|
+
return client.boost.getBoostRecipientsWithChildrenCount.query({
|
|
1226
|
+
uri,
|
|
1227
|
+
includeUnacceptedBoosts,
|
|
1228
|
+
boostQuery,
|
|
1229
|
+
profileQuery,
|
|
1230
|
+
numberOfGenerations,
|
|
1231
|
+
});
|
|
1232
|
+
},
|
|
1233
|
+
countBoostRecipients: async (_learnCard, uri, includeUnacceptedBoosts = true) => {
|
|
1234
|
+
await ensureUser();
|
|
1235
|
+
|
|
1236
|
+
return client.boost.getBoostRecipientCount.query({ uri, includeUnacceptedBoosts });
|
|
1237
|
+
},
|
|
1238
|
+
getConnectedBoostRecipients: async (
|
|
1239
|
+
_learnCard,
|
|
1240
|
+
uri,
|
|
1241
|
+
{ limit = 25, cursor = undefined, includeUnacceptedBoosts = true, query } = {}
|
|
1242
|
+
) => {
|
|
1243
|
+
await ensureUser();
|
|
1244
|
+
|
|
1245
|
+
return client.boost.getConnectedBoostRecipients.query({
|
|
1246
|
+
uri,
|
|
1247
|
+
limit,
|
|
1248
|
+
cursor,
|
|
1249
|
+
includeUnacceptedBoosts,
|
|
1250
|
+
query,
|
|
1251
|
+
});
|
|
1252
|
+
},
|
|
1253
|
+
countConnectedBoostRecipients: async (
|
|
1254
|
+
_learnCard,
|
|
1255
|
+
uri,
|
|
1256
|
+
includeUnacceptedBoosts = true
|
|
1257
|
+
) => {
|
|
1258
|
+
await ensureUser();
|
|
1259
|
+
|
|
1260
|
+
return client.boost.getConnectedBoostRecipientCount.query({
|
|
1261
|
+
uri,
|
|
1262
|
+
includeUnacceptedBoosts,
|
|
1263
|
+
});
|
|
1264
|
+
},
|
|
1265
|
+
getBoostChildrenProfileManagers: async (_learnCard, uri, options) => {
|
|
1266
|
+
await ensureUser();
|
|
1267
|
+
|
|
1268
|
+
return client.boost.getChildrenProfileManagers.query({ uri, ...options });
|
|
1269
|
+
},
|
|
1270
|
+
updateBoost: async (_learnCard, uri, updates, credential) => {
|
|
1271
|
+
await ensureUser();
|
|
1272
|
+
|
|
1273
|
+
// Allow passing skills similar to createBoost; send them at top-level, not inside updates
|
|
1274
|
+
const { skills, ...restUpdates } = (updates as any) ?? {};
|
|
1275
|
+
|
|
1276
|
+
const payload: any = {
|
|
1277
|
+
uri,
|
|
1278
|
+
updates: { ...(credential && { credential }), ...restUpdates },
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
if (Array.isArray(skills)) payload.skills = skills;
|
|
1282
|
+
|
|
1283
|
+
return client.boost.updateBoost.mutate(payload);
|
|
1284
|
+
},
|
|
1285
|
+
attachFrameworkToBoost: async (_learnCard, boostUri, frameworkId) => {
|
|
1286
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1287
|
+
|
|
1288
|
+
return client.boost.attachFrameworkToBoost.mutate({ boostUri, frameworkId });
|
|
1289
|
+
},
|
|
1290
|
+
detachFrameworkFromBoost: async (_learnCard, boostUri, frameworkId) => {
|
|
1291
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1292
|
+
|
|
1293
|
+
return client.boost.detachFrameworkFromBoost.mutate({ boostUri, frameworkId });
|
|
1294
|
+
},
|
|
1295
|
+
alignBoostSkills: async (_learnCard, boostUri, skills) => {
|
|
1296
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
1297
|
+
|
|
1298
|
+
return client.boost.alignBoostSkills.mutate({ boostUri, skills } as any);
|
|
1299
|
+
},
|
|
1300
|
+
getBoostAdmins: async (_learnCard, uri, options = {}) => {
|
|
1301
|
+
await ensureUser();
|
|
1302
|
+
|
|
1303
|
+
return client.boost.getBoostAdmins.query({ uri, ...options });
|
|
1304
|
+
},
|
|
1305
|
+
getBoostPermissions: async (_learnCard, uri, profileId) => {
|
|
1306
|
+
await ensureUser();
|
|
1307
|
+
|
|
1308
|
+
if (!profileId) return client.boost.getBoostPermissions.query({ uri });
|
|
1309
|
+
|
|
1310
|
+
return client.boost.getOtherBoostPermissions.query({ uri, profileId });
|
|
1311
|
+
},
|
|
1312
|
+
updateBoostPermissions: async (_learnCard, uri, updates, profileId) => {
|
|
1313
|
+
await ensureUser();
|
|
1314
|
+
|
|
1315
|
+
if (!profileId) return client.boost.updateBoostPermissions.query({ uri, updates });
|
|
1316
|
+
|
|
1317
|
+
const result = await client.boost.updateOtherBoostPermissions.query({
|
|
1318
|
+
uri,
|
|
1319
|
+
profileId,
|
|
1320
|
+
updates,
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
1324
|
+
|
|
1325
|
+
return result;
|
|
1326
|
+
},
|
|
1327
|
+
addBoostAdmin: async (_learnCard, uri, profileId) => {
|
|
1328
|
+
await ensureUser();
|
|
1329
|
+
|
|
1330
|
+
const result = await client.boost.addBoostAdmin.mutate({ uri, profileId });
|
|
1331
|
+
|
|
1332
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
1333
|
+
|
|
1334
|
+
return result;
|
|
1335
|
+
},
|
|
1336
|
+
removeBoostAdmin: async (_learnCard, uri, profileId) => {
|
|
1337
|
+
await ensureUser();
|
|
1338
|
+
|
|
1339
|
+
const result = await client.boost.removeBoostAdmin.mutate({ uri, profileId });
|
|
1340
|
+
|
|
1341
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
1342
|
+
|
|
1343
|
+
return result;
|
|
1344
|
+
},
|
|
1345
|
+
allocateCredentialStatus: async (_learnCard, options = {}) => {
|
|
1346
|
+
await ensureUser();
|
|
1347
|
+
|
|
1348
|
+
return client.boost.allocateCredentialStatus.mutate(options);
|
|
1349
|
+
},
|
|
1350
|
+
revokeBoostRecipient: async (
|
|
1351
|
+
_learnCard,
|
|
1352
|
+
boostUri,
|
|
1353
|
+
recipientProfileId,
|
|
1354
|
+
credentialUri
|
|
1355
|
+
) => {
|
|
1356
|
+
await ensureUser();
|
|
1357
|
+
|
|
1358
|
+
const input = { boostUri, recipientProfileId };
|
|
1359
|
+
if (credentialUri !== undefined) Object.assign(input, { credentialUri });
|
|
1360
|
+
|
|
1361
|
+
return client.boost.revokeBoostRecipient.mutate(input);
|
|
1362
|
+
},
|
|
1363
|
+
suspendBoostRecipient: async (
|
|
1364
|
+
_learnCard,
|
|
1365
|
+
boostUri,
|
|
1366
|
+
recipientProfileId,
|
|
1367
|
+
credentialUri
|
|
1368
|
+
) => {
|
|
1369
|
+
await ensureUser();
|
|
1370
|
+
|
|
1371
|
+
const input = { boostUri, recipientProfileId };
|
|
1372
|
+
if (credentialUri !== undefined) Object.assign(input, { credentialUri });
|
|
1373
|
+
|
|
1374
|
+
return client.boost.suspendBoostRecipient.mutate(input);
|
|
1375
|
+
},
|
|
1376
|
+
unsuspendBoostRecipient: async (
|
|
1377
|
+
_learnCard,
|
|
1378
|
+
boostUri,
|
|
1379
|
+
recipientProfileId,
|
|
1380
|
+
credentialUri
|
|
1381
|
+
) => {
|
|
1382
|
+
await ensureUser();
|
|
1383
|
+
|
|
1384
|
+
const input = { boostUri, recipientProfileId };
|
|
1385
|
+
if (credentialUri !== undefined) Object.assign(input, { credentialUri });
|
|
1386
|
+
|
|
1387
|
+
return client.boost.unsuspendBoostRecipient.mutate(input);
|
|
1388
|
+
},
|
|
1389
|
+
deleteBoost: async (_learnCard, uri) => {
|
|
1390
|
+
await ensureUser();
|
|
1391
|
+
|
|
1392
|
+
return client.boost.deleteBoost.mutate({ uri });
|
|
1393
|
+
},
|
|
1394
|
+
sendBoost: async (
|
|
1395
|
+
_learnCard,
|
|
1396
|
+
profileId,
|
|
1397
|
+
boostUri,
|
|
1398
|
+
options = { encrypt: true, skipNotification: false, templateData: {} }
|
|
1399
|
+
) => {
|
|
1400
|
+
await ensureUser();
|
|
1401
|
+
|
|
1402
|
+
const result = await getBoostTemplateForIssuance(_learnCard, boostUri);
|
|
1403
|
+
const data = await UnsignedVCValidator.spa(result);
|
|
1404
|
+
|
|
1405
|
+
if (!data.success) throw new Error('Did not get a valid boost from URI');
|
|
1406
|
+
|
|
1407
|
+
const targetProfile = await _learnCard.invoke.getProfile(profileId);
|
|
1408
|
+
|
|
1409
|
+
if (!targetProfile) throw new Error('Target profile not found');
|
|
1410
|
+
if (!hasDid(targetProfile)) throw new Error('Target profile has no DID');
|
|
1411
|
+
|
|
1412
|
+
let boost = data.data;
|
|
1413
|
+
|
|
1414
|
+
if (isVC2Format(boost)) {
|
|
1415
|
+
boost.validFrom = new Date().toISOString();
|
|
1416
|
+
} else {
|
|
1417
|
+
boost.issuanceDate = new Date().toISOString();
|
|
1418
|
+
}
|
|
1419
|
+
boost.issuer = _learnCard.id.did();
|
|
1420
|
+
|
|
1421
|
+
if (Array.isArray(boost.credentialSubject)) {
|
|
1422
|
+
boost.credentialSubject = boost.credentialSubject.map(subject => ({
|
|
1423
|
+
...subject,
|
|
1424
|
+
id: targetProfile.did,
|
|
1425
|
+
}));
|
|
1426
|
+
} else {
|
|
1427
|
+
boost.credentialSubject.id = targetProfile.did;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Embed the boostURI into the boost credential for verification purposes.
|
|
1431
|
+
if (boost?.type?.includes('BoostCredential')) boost.boostId = boostUri;
|
|
1432
|
+
|
|
1433
|
+
// Apply templateData if provided (Mustache templating)
|
|
1434
|
+
if (
|
|
1435
|
+
typeof options === 'object' &&
|
|
1436
|
+
options.templateData &&
|
|
1437
|
+
Object.keys(options.templateData).length > 0
|
|
1438
|
+
) {
|
|
1439
|
+
const boostString = JSON.stringify(boost);
|
|
1440
|
+
const allowAutoAppendEvidence = !hasDynamicEvidenceTemplate(boostString);
|
|
1441
|
+
|
|
1442
|
+
try {
|
|
1443
|
+
const rendered = renderTemplateJson(boostString, options.templateData);
|
|
1444
|
+
boost = JSON.parse(rendered);
|
|
1445
|
+
boost = appendTemplateEvidence(
|
|
1446
|
+
boost,
|
|
1447
|
+
options.templateData,
|
|
1448
|
+
allowAutoAppendEvidence
|
|
1449
|
+
);
|
|
1450
|
+
} catch (error) {
|
|
1451
|
+
throw new Error(
|
|
1452
|
+
`Template substitution failed: ${
|
|
1453
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
1454
|
+
}. ` +
|
|
1455
|
+
`Please check your templateData variables and ensure the rendered output is valid JSON.`
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (typeof options === 'object' && options.overideFn) {
|
|
1461
|
+
boost = options.overideFn(boost);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const statusPurposes =
|
|
1465
|
+
typeof options === 'object' ? options.statusPurposes : undefined;
|
|
1466
|
+
const vc = await issueCredentialWithNetworkStatus(
|
|
1467
|
+
_learnCard,
|
|
1468
|
+
client,
|
|
1469
|
+
boost,
|
|
1470
|
+
statusPurposes
|
|
1471
|
+
);
|
|
1472
|
+
|
|
1473
|
+
// options is allowed to be a boolean to maintain backwards compatibility
|
|
1474
|
+
if ((typeof options === 'object' && !options.encrypt) || !options) {
|
|
1475
|
+
return client.boost.sendBoost.mutate({
|
|
1476
|
+
profileId,
|
|
1477
|
+
uri: boostUri,
|
|
1478
|
+
credential: vc,
|
|
1479
|
+
options: {
|
|
1480
|
+
skipNotification:
|
|
1481
|
+
typeof options === 'object' && options.skipNotification,
|
|
1482
|
+
},
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const lcnDid = await client.utilities.getDid.query();
|
|
1487
|
+
|
|
1488
|
+
const credential = await _learnCard.invoke.createDagJwe(vc, [
|
|
1489
|
+
_learnCard.id.did(),
|
|
1490
|
+
targetProfile.did,
|
|
1491
|
+
lcnDid,
|
|
1492
|
+
]);
|
|
1493
|
+
|
|
1494
|
+
return client.boost.sendBoost.mutate({
|
|
1495
|
+
profileId,
|
|
1496
|
+
uri: boostUri,
|
|
1497
|
+
credential,
|
|
1498
|
+
options: {
|
|
1499
|
+
skipNotification: typeof options === 'object' && options.skipNotification,
|
|
1500
|
+
},
|
|
1501
|
+
});
|
|
1502
|
+
},
|
|
1503
|
+
|
|
1504
|
+
registerSigningAuthority: async (_learnCard, endpoint, name, _did) => {
|
|
1505
|
+
await ensureUser();
|
|
1506
|
+
|
|
1507
|
+
return client.profile.registerSigningAuthority.mutate({
|
|
1508
|
+
endpoint,
|
|
1509
|
+
name,
|
|
1510
|
+
did: _did,
|
|
1511
|
+
});
|
|
1512
|
+
},
|
|
1513
|
+
getRegisteredSigningAuthorities: async _learnCard => {
|
|
1514
|
+
await ensureUser();
|
|
1515
|
+
|
|
1516
|
+
return client.profile.signingAuthorities.query();
|
|
1517
|
+
},
|
|
1518
|
+
getRegisteredSigningAuthority: async (_learnCard, endpoint, name) => {
|
|
1519
|
+
await ensureUser();
|
|
1520
|
+
|
|
1521
|
+
return client.profile.signingAuthority.query({ endpoint, name });
|
|
1522
|
+
},
|
|
1523
|
+
setPrimaryRegisteredSigningAuthority: async (_learnCard, endpoint, name) => {
|
|
1524
|
+
await ensureUser();
|
|
1525
|
+
|
|
1526
|
+
return client.profile.setPrimarySigningAuthority.mutate({ endpoint, name });
|
|
1527
|
+
},
|
|
1528
|
+
getPrimaryRegisteredSigningAuthority: async _learnCard => {
|
|
1529
|
+
await ensureUser();
|
|
1530
|
+
|
|
1531
|
+
return client.profile.primarySigningAuthority.query();
|
|
1532
|
+
},
|
|
1533
|
+
|
|
1534
|
+
generateClaimLink: async (_learnCard, boostUri, claimLinkSA, options, challenge) => {
|
|
1535
|
+
await ensureUser();
|
|
1536
|
+
|
|
1537
|
+
return client.boost.generateClaimLink.mutate({
|
|
1538
|
+
boostUri,
|
|
1539
|
+
claimLinkSA,
|
|
1540
|
+
options,
|
|
1541
|
+
challenge,
|
|
1542
|
+
});
|
|
1543
|
+
},
|
|
1544
|
+
|
|
1545
|
+
claimBoostWithLink: async (_learnCard, boostUri, challenge) => {
|
|
1546
|
+
await ensureUser();
|
|
1547
|
+
|
|
1548
|
+
return client.boost.claimBoostWithLink.mutate({ boostUri, challenge });
|
|
1549
|
+
},
|
|
1550
|
+
|
|
1551
|
+
send: async (_learnCard, input) => {
|
|
1552
|
+
await ensureUser();
|
|
1553
|
+
|
|
1554
|
+
if (input.type === 'boost') {
|
|
1555
|
+
const recipient = input.recipient;
|
|
1556
|
+
const isDid = recipient.startsWith('did:');
|
|
1557
|
+
const isEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient);
|
|
1558
|
+
const isPhone = /^\+?[\d\s-]{10,}$/.test(recipient.replace(/[\s-]/g, ''));
|
|
1559
|
+
const serviceUrl = new URL(url);
|
|
1560
|
+
const serviceDomain = `${serviceUrl.hostname}${
|
|
1561
|
+
serviceUrl.port ? `%3A${serviceUrl.port}` : ''
|
|
1562
|
+
}`;
|
|
1563
|
+
const recipientDomain = recipient.startsWith('did:web:')
|
|
1564
|
+
? recipient.split(':')[2]
|
|
1565
|
+
: undefined;
|
|
1566
|
+
const isRemoteDidWebRecipient =
|
|
1567
|
+
recipient.startsWith('did:web:') && recipientDomain !== serviceDomain;
|
|
1568
|
+
|
|
1569
|
+
if (isRemoteDidWebRecipient && input.templateUri) {
|
|
1570
|
+
const boostRecord = await _learnCard.invoke.getBoost(input.templateUri);
|
|
1571
|
+
let boost = boostRecord.boost;
|
|
1572
|
+
const boostString = JSON.stringify(boost);
|
|
1573
|
+
const allowAutoAppendEvidence = !hasDynamicEvidenceTemplate(boostString);
|
|
1574
|
+
|
|
1575
|
+
if (input.templateData && Object.keys(input.templateData).length > 0) {
|
|
1576
|
+
const rendered = renderTemplateJson(boostString, input.templateData);
|
|
1577
|
+
boost = JSON.parse(rendered);
|
|
1578
|
+
boost = appendTemplateEvidence(
|
|
1579
|
+
boost,
|
|
1580
|
+
input.templateData,
|
|
1581
|
+
allowAutoAppendEvidence
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if (isVC2Format(boost)) {
|
|
1586
|
+
boost.validFrom = new Date().toISOString();
|
|
1587
|
+
} else {
|
|
1588
|
+
boost.issuanceDate = new Date().toISOString();
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
boost.issuer = _learnCard.id.did();
|
|
1592
|
+
|
|
1593
|
+
if (Array.isArray(boost.credentialSubject)) {
|
|
1594
|
+
boost.credentialSubject = boost.credentialSubject.map(subject => ({
|
|
1595
|
+
...subject,
|
|
1596
|
+
id: recipient,
|
|
1597
|
+
}));
|
|
1598
|
+
} else {
|
|
1599
|
+
boost.credentialSubject = {
|
|
1600
|
+
...boost.credentialSubject,
|
|
1601
|
+
id: recipient,
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
if (boost.type?.includes('BoostCredential')) {
|
|
1606
|
+
boost.boostId = input.templateUri;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
const signedCredential = await issueCredentialWithNetworkStatus(
|
|
1610
|
+
_learnCard,
|
|
1611
|
+
client,
|
|
1612
|
+
boost
|
|
1613
|
+
);
|
|
1614
|
+
const credentialUri = await _learnCard.invoke.sendCredential(
|
|
1615
|
+
recipient,
|
|
1616
|
+
signedCredential,
|
|
1617
|
+
{
|
|
1618
|
+
boostUri: input.templateUri,
|
|
1619
|
+
templateData: input.templateData,
|
|
1620
|
+
integrationId: input.integrationId,
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
|
|
1624
|
+
return {
|
|
1625
|
+
type: 'boost' as const,
|
|
1626
|
+
credentialUri,
|
|
1627
|
+
uri: input.templateUri,
|
|
1628
|
+
activityId: '',
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
const canIssueLocally = 'issueCredential' in _learnCard.invoke;
|
|
1633
|
+
const isDirectRecipient = isDid || (!isEmail && !isPhone);
|
|
1634
|
+
|
|
1635
|
+
if (canIssueLocally && isDirectRecipient && input.templateUri) {
|
|
1636
|
+
const result = await getBoostTemplateForIssuance(
|
|
1637
|
+
_learnCard,
|
|
1638
|
+
input.templateUri
|
|
1639
|
+
);
|
|
1640
|
+
const data = await UnsignedVCValidator.spa(result);
|
|
1641
|
+
|
|
1642
|
+
if (data.success) {
|
|
1643
|
+
let targetDid: string;
|
|
1644
|
+
|
|
1645
|
+
if (isDid) {
|
|
1646
|
+
targetDid = recipient;
|
|
1647
|
+
} else {
|
|
1648
|
+
const targetProfile = await _learnCard.invoke.getProfile(recipient);
|
|
1649
|
+
|
|
1650
|
+
if (!hasDid(targetProfile)) return client.boost.send.mutate(input);
|
|
1651
|
+
|
|
1652
|
+
targetDid = targetProfile.did;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
let boost = data.data;
|
|
1656
|
+
const boostString = JSON.stringify(boost);
|
|
1657
|
+
const allowAutoAppendEvidence =
|
|
1658
|
+
!hasDynamicEvidenceTemplate(boostString);
|
|
1659
|
+
|
|
1660
|
+
// Apply templateData if provided
|
|
1661
|
+
if (input.templateData && Object.keys(input.templateData).length > 0) {
|
|
1662
|
+
try {
|
|
1663
|
+
const rendered = renderTemplateJson(
|
|
1664
|
+
boostString,
|
|
1665
|
+
input.templateData
|
|
1666
|
+
);
|
|
1667
|
+
boost = JSON.parse(rendered);
|
|
1668
|
+
boost = appendTemplateEvidence(
|
|
1669
|
+
boost,
|
|
1670
|
+
input.templateData,
|
|
1671
|
+
allowAutoAppendEvidence
|
|
1672
|
+
);
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
throw new Error(
|
|
1675
|
+
`Failed to apply template data: ${
|
|
1676
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
1677
|
+
}`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
if (isVC2Format(boost)) {
|
|
1683
|
+
boost.validFrom = new Date().toISOString();
|
|
1684
|
+
} else {
|
|
1685
|
+
boost.issuanceDate = new Date().toISOString();
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
boost.issuer = _learnCard.id.did();
|
|
1689
|
+
|
|
1690
|
+
if (Array.isArray(boost.credentialSubject)) {
|
|
1691
|
+
boost.credentialSubject = boost.credentialSubject.map(subject => ({
|
|
1692
|
+
...subject,
|
|
1693
|
+
id: targetDid,
|
|
1694
|
+
}));
|
|
1695
|
+
} else {
|
|
1696
|
+
boost.credentialSubject = {
|
|
1697
|
+
...boost.credentialSubject,
|
|
1698
|
+
id: targetDid,
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
if (boost?.type?.includes('BoostCredential'))
|
|
1703
|
+
boost.boostId = input.templateUri;
|
|
1704
|
+
|
|
1705
|
+
const signedCredential = await issueCredentialWithNetworkStatus(
|
|
1706
|
+
_learnCard,
|
|
1707
|
+
client,
|
|
1708
|
+
boost
|
|
1709
|
+
);
|
|
1710
|
+
|
|
1711
|
+
if (isDid && recipient.startsWith('did:web:')) {
|
|
1712
|
+
const credentialUri = await _learnCard.invoke.sendCredential(
|
|
1713
|
+
recipient,
|
|
1714
|
+
signedCredential,
|
|
1715
|
+
{
|
|
1716
|
+
boostUri: input.templateUri,
|
|
1717
|
+
templateData: input.templateData,
|
|
1718
|
+
integrationId: input.integrationId,
|
|
1719
|
+
}
|
|
1720
|
+
);
|
|
1721
|
+
|
|
1722
|
+
return {
|
|
1723
|
+
type: 'boost' as const,
|
|
1724
|
+
credentialUri,
|
|
1725
|
+
uri: input.templateUri,
|
|
1726
|
+
activityId: '',
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
return client.boost.send.mutate({
|
|
1731
|
+
...input,
|
|
1732
|
+
signedCredential,
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// Let the brain service handle all routing (email/phone → inbox, profileId/DID → direct)
|
|
1739
|
+
return client.boost.send.mutate(input);
|
|
1740
|
+
},
|
|
1741
|
+
|
|
1742
|
+
createContract: async (_learnCard, contract) => {
|
|
1743
|
+
await ensureUser();
|
|
1744
|
+
|
|
1745
|
+
return client.contracts.createConsentFlowContract.mutate(contract);
|
|
1746
|
+
},
|
|
1747
|
+
|
|
1748
|
+
addAutoBoostsToContract: async (_learnCard, contractUri, autoboosts) => {
|
|
1749
|
+
await ensureUser();
|
|
1750
|
+
|
|
1751
|
+
return client.contracts.addAutoBoostsToContract.mutate({ contractUri, autoboosts });
|
|
1752
|
+
},
|
|
1753
|
+
|
|
1754
|
+
removeAutoBoostsFromContract: async (_learnCard, contractUri, boostUris) => {
|
|
1755
|
+
await ensureUser();
|
|
1756
|
+
|
|
1757
|
+
return client.contracts.removeAutoBoostsFromContract.mutate({
|
|
1758
|
+
contractUri,
|
|
1759
|
+
boostUris,
|
|
1760
|
+
});
|
|
1761
|
+
},
|
|
1762
|
+
|
|
1763
|
+
getContract: async (_learnCard, uri) => {
|
|
1764
|
+
return client.contracts.getConsentFlowContract.query({ uri });
|
|
1765
|
+
},
|
|
1766
|
+
|
|
1767
|
+
getContracts: async (_learnCard, options) => {
|
|
1768
|
+
await ensureUser();
|
|
1769
|
+
|
|
1770
|
+
return client.contracts.getConsentFlowContracts.query(options);
|
|
1771
|
+
},
|
|
1772
|
+
|
|
1773
|
+
deleteContract: async (_learnCard, uri) => {
|
|
1774
|
+
await ensureUser();
|
|
1775
|
+
|
|
1776
|
+
return client.contracts.deleteConsentFlowContract.mutate({ uri });
|
|
1777
|
+
},
|
|
1778
|
+
|
|
1779
|
+
getConsentFlowData: async (_learnCard, uri, options = {}) => {
|
|
1780
|
+
await ensureUser();
|
|
1781
|
+
|
|
1782
|
+
return client.contracts.getConsentedDataForContract.query({ uri, ...options });
|
|
1783
|
+
},
|
|
1784
|
+
|
|
1785
|
+
getConsentFlowDataForDid: async (_learnCard, did, options = {}) => {
|
|
1786
|
+
await ensureUser();
|
|
1787
|
+
|
|
1788
|
+
return client.contracts.getConsentedDataForDid.query({ did, ...options });
|
|
1789
|
+
},
|
|
1790
|
+
|
|
1791
|
+
getAllConsentFlowData: async (_learnCard, query = {}, options = {}) => {
|
|
1792
|
+
await ensureUser();
|
|
1793
|
+
|
|
1794
|
+
return client.contracts.getConsentedData.query({ query, ...options });
|
|
1795
|
+
},
|
|
1796
|
+
|
|
1797
|
+
writeCredentialToContract: async (
|
|
1798
|
+
_learnCard,
|
|
1799
|
+
did,
|
|
1800
|
+
contractUri,
|
|
1801
|
+
credential,
|
|
1802
|
+
boostUri
|
|
1803
|
+
) => {
|
|
1804
|
+
await ensureUser();
|
|
1805
|
+
|
|
1806
|
+
return client.contracts.writeCredentialToContract.mutate({
|
|
1807
|
+
did,
|
|
1808
|
+
contractUri,
|
|
1809
|
+
credential,
|
|
1810
|
+
boostUri,
|
|
1811
|
+
});
|
|
1812
|
+
},
|
|
1813
|
+
|
|
1814
|
+
consentToContract: async (
|
|
1815
|
+
_learnCard,
|
|
1816
|
+
contractUri,
|
|
1817
|
+
{ terms, expiresAt, oneTime },
|
|
1818
|
+
recipientToken
|
|
1819
|
+
) => {
|
|
1820
|
+
await ensureUser();
|
|
1821
|
+
|
|
1822
|
+
return client.contracts.consentToContract.mutate({
|
|
1823
|
+
contractUri,
|
|
1824
|
+
terms,
|
|
1825
|
+
expiresAt,
|
|
1826
|
+
oneTime,
|
|
1827
|
+
recipientToken, // for SmartResume
|
|
1828
|
+
});
|
|
1829
|
+
},
|
|
1830
|
+
|
|
1831
|
+
getConsentedContracts: async (_learnCard, options) => {
|
|
1832
|
+
await ensureUser();
|
|
1833
|
+
|
|
1834
|
+
return client.contracts.getConsentedContracts.query(options);
|
|
1835
|
+
},
|
|
1836
|
+
|
|
1837
|
+
updateContractTerms: async (_learnCard, uri, { terms, expiresAt, oneTime }) => {
|
|
1838
|
+
await ensureUser();
|
|
1839
|
+
|
|
1840
|
+
return client.contracts.updateConsentedContractTerms.mutate({
|
|
1841
|
+
uri,
|
|
1842
|
+
terms,
|
|
1843
|
+
expiresAt,
|
|
1844
|
+
oneTime,
|
|
1845
|
+
});
|
|
1846
|
+
},
|
|
1847
|
+
|
|
1848
|
+
withdrawConsent: async (_learnCard, uri) => {
|
|
1849
|
+
await ensureUser();
|
|
1850
|
+
|
|
1851
|
+
return client.contracts.withdrawConsent.mutate({ uri });
|
|
1852
|
+
},
|
|
1853
|
+
|
|
1854
|
+
getConsentFlowTransactions: async (_learnCard, uri, options) => {
|
|
1855
|
+
await ensureUser();
|
|
1856
|
+
|
|
1857
|
+
return client.contracts.getTermsTransactionHistory.query({ uri, ...options });
|
|
1858
|
+
},
|
|
1859
|
+
|
|
1860
|
+
getHolderExportMetadata: async _learnCard => {
|
|
1861
|
+
await ensureUser();
|
|
1862
|
+
|
|
1863
|
+
return client.credential.getHolderExportMetadata.query({});
|
|
1864
|
+
},
|
|
1865
|
+
|
|
1866
|
+
getCredentialsForContract: async (_learnCard, termsUri, options = {}) => {
|
|
1867
|
+
await ensureUser();
|
|
1868
|
+
|
|
1869
|
+
return client.contracts.getCredentialsForContract.query({ termsUri, ...options });
|
|
1870
|
+
},
|
|
1871
|
+
|
|
1872
|
+
getConsentFlowCredentials: async (_learnCard, options = {}) => {
|
|
1873
|
+
await ensureUser();
|
|
1874
|
+
|
|
1875
|
+
return client.contracts.getAllCredentialsForTerms.query(options);
|
|
1876
|
+
},
|
|
1877
|
+
|
|
1878
|
+
verifyConsent: async (_learnCard, uri, profileId) => {
|
|
1879
|
+
await ensureUser();
|
|
1880
|
+
|
|
1881
|
+
return client.contracts.verifyConsent.query({ uri, profileId });
|
|
1882
|
+
},
|
|
1883
|
+
|
|
1884
|
+
syncCredentialsToContract: async (_learnCard, termsUri, categories) => {
|
|
1885
|
+
await ensureUser();
|
|
1886
|
+
|
|
1887
|
+
return client.contracts.syncCredentialsToContract.mutate({
|
|
1888
|
+
termsUri,
|
|
1889
|
+
categories,
|
|
1890
|
+
});
|
|
1891
|
+
},
|
|
1892
|
+
|
|
1893
|
+
deleteCredentialFromAllContracts: async (_learnCard, deletedUris) => {
|
|
1894
|
+
await ensureUser();
|
|
1895
|
+
|
|
1896
|
+
return client.contracts.deleteCredentialFromAllContracts.mutate({
|
|
1897
|
+
deletedUris,
|
|
1898
|
+
});
|
|
1899
|
+
},
|
|
1900
|
+
|
|
1901
|
+
sendAiInsightsContractRequest: async (
|
|
1902
|
+
_learnCard,
|
|
1903
|
+
contractUri,
|
|
1904
|
+
targetProfileId,
|
|
1905
|
+
shareLink
|
|
1906
|
+
) => {
|
|
1907
|
+
await ensureUser();
|
|
1908
|
+
|
|
1909
|
+
return client.contracts.sendAiInsightsContractRequest.mutate({
|
|
1910
|
+
contractUri,
|
|
1911
|
+
targetProfileId,
|
|
1912
|
+
shareLink,
|
|
1913
|
+
});
|
|
1914
|
+
},
|
|
1915
|
+
|
|
1916
|
+
sendAiInsightShareRequest: async (
|
|
1917
|
+
_learnCard,
|
|
1918
|
+
targetProfileId,
|
|
1919
|
+
shareLink,
|
|
1920
|
+
childProfileId
|
|
1921
|
+
) => {
|
|
1922
|
+
await ensureUser();
|
|
1923
|
+
|
|
1924
|
+
return client.contracts.sendAiInsightShareRequest.mutate({
|
|
1925
|
+
targetProfileId,
|
|
1926
|
+
shareLink,
|
|
1927
|
+
childProfileId,
|
|
1928
|
+
});
|
|
1929
|
+
},
|
|
1930
|
+
|
|
1931
|
+
getContractSentRequests: async (_learnCard, contractUri) => {
|
|
1932
|
+
await ensureUser();
|
|
1933
|
+
|
|
1934
|
+
return client.contracts.getContractSentRequests.query({
|
|
1935
|
+
contractUri,
|
|
1936
|
+
});
|
|
1937
|
+
},
|
|
1938
|
+
|
|
1939
|
+
getRequestStatusForProfile: async (
|
|
1940
|
+
_learnCard,
|
|
1941
|
+
targetProfileId,
|
|
1942
|
+
contractId,
|
|
1943
|
+
contractUri
|
|
1944
|
+
) => {
|
|
1945
|
+
await ensureUser();
|
|
1946
|
+
|
|
1947
|
+
return client.contracts.getRequestStatusForProfile.query({
|
|
1948
|
+
targetProfileId,
|
|
1949
|
+
contractId: contractId ?? null,
|
|
1950
|
+
contractUri: contractUri ?? null,
|
|
1951
|
+
});
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1954
|
+
getAllContractRequestsForProfile: async (_learnCard, targetProfileId) => {
|
|
1955
|
+
await ensureUser();
|
|
1956
|
+
|
|
1957
|
+
return client.contracts.getAllContractRequestsForProfile.query({
|
|
1958
|
+
targetProfileId,
|
|
1959
|
+
});
|
|
1960
|
+
},
|
|
1961
|
+
|
|
1962
|
+
getSharedInsightsRequestsForProfile: async (_learnCard, targetProfileId) => {
|
|
1963
|
+
await ensureUser();
|
|
1964
|
+
|
|
1965
|
+
return client.contracts.getSharedInsightsRequestsForProfile.query({
|
|
1966
|
+
targetProfileId,
|
|
1967
|
+
});
|
|
1968
|
+
},
|
|
1969
|
+
|
|
1970
|
+
forwardContractRequestToProfile: async (
|
|
1971
|
+
_learnCard,
|
|
1972
|
+
parentProfileId,
|
|
1973
|
+
targetProfileId,
|
|
1974
|
+
contractUri
|
|
1975
|
+
) => {
|
|
1976
|
+
await ensureUser();
|
|
1977
|
+
|
|
1978
|
+
return client.contracts.forwardContractRequestToProfile.mutate({
|
|
1979
|
+
parentProfileId,
|
|
1980
|
+
targetProfileId,
|
|
1981
|
+
contractUri,
|
|
1982
|
+
});
|
|
1983
|
+
},
|
|
1984
|
+
|
|
1985
|
+
markContractRequestAsSeen: async (_learnCard, contractUri, targetProfileId) => {
|
|
1986
|
+
await ensureUser();
|
|
1987
|
+
|
|
1988
|
+
return client.contracts.markContractRequestAsSeen.mutate({
|
|
1989
|
+
contractUri,
|
|
1990
|
+
targetProfileId,
|
|
1991
|
+
});
|
|
1992
|
+
},
|
|
1993
|
+
|
|
1994
|
+
cancelContractRequest: async (_learnCard, contractUri, targetProfileId) => {
|
|
1995
|
+
await ensureUser();
|
|
1996
|
+
|
|
1997
|
+
return client.contracts.cancelContractRequest.mutate({
|
|
1998
|
+
contractUri,
|
|
1999
|
+
targetProfileId,
|
|
2000
|
+
});
|
|
2001
|
+
},
|
|
2002
|
+
|
|
2003
|
+
addDidMetadata: async (_learnCard, metadata) => {
|
|
2004
|
+
await ensureUser();
|
|
2005
|
+
|
|
2006
|
+
const result = await client.didMetadata.addDidMetadata.mutate(metadata);
|
|
2007
|
+
|
|
2008
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
2009
|
+
|
|
2010
|
+
return result;
|
|
2011
|
+
},
|
|
2012
|
+
|
|
2013
|
+
getDidMetadata: async (_learnCard, id) => {
|
|
2014
|
+
await ensureUser();
|
|
2015
|
+
|
|
2016
|
+
return client.didMetadata.getDidMetadata.query({ id });
|
|
2017
|
+
},
|
|
2018
|
+
|
|
2019
|
+
getMyDidMetadata: async () => {
|
|
2020
|
+
await ensureUser();
|
|
2021
|
+
|
|
2022
|
+
return client.didMetadata.getMyDidMetadata.query();
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
updateDidMetadata: async (_learnCard, id, updates) => {
|
|
2026
|
+
await ensureUser();
|
|
2027
|
+
|
|
2028
|
+
const result = await client.didMetadata.updateDidMetadata.mutate({ id, updates });
|
|
2029
|
+
|
|
2030
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
2031
|
+
|
|
2032
|
+
return result;
|
|
2033
|
+
},
|
|
2034
|
+
|
|
2035
|
+
deleteDidMetadata: async (_learnCard, id) => {
|
|
2036
|
+
await ensureUser();
|
|
2037
|
+
|
|
2038
|
+
const result = await client.didMetadata.deleteDidMetadata.mutate({ id });
|
|
2039
|
+
|
|
2040
|
+
if (result) await _learnCard.invoke.clearDidWebCache?.();
|
|
2041
|
+
|
|
2042
|
+
return result;
|
|
2043
|
+
},
|
|
2044
|
+
|
|
2045
|
+
createClaimHook: async (_learnCard, hook) => {
|
|
2046
|
+
await ensureUser();
|
|
2047
|
+
|
|
2048
|
+
return client.claimHook.createClaimHook.mutate({ hook });
|
|
2049
|
+
},
|
|
2050
|
+
|
|
2051
|
+
getClaimHooksForBoost: async (_learnCard, options) => {
|
|
2052
|
+
await ensureUser();
|
|
2053
|
+
|
|
2054
|
+
return client.claimHook.getClaimHooksForBoost.query(options);
|
|
2055
|
+
},
|
|
2056
|
+
|
|
2057
|
+
deleteClaimHook: async (_learnCard, id) => {
|
|
2058
|
+
await ensureUser();
|
|
2059
|
+
|
|
2060
|
+
return client.claimHook.deleteClaimHook.mutate({ id });
|
|
2061
|
+
},
|
|
2062
|
+
|
|
2063
|
+
addAuthGrant: async (_learnCard, authGrant) => {
|
|
2064
|
+
await ensureUser();
|
|
2065
|
+
|
|
2066
|
+
return client.authGrants.addAuthGrant.mutate(authGrant);
|
|
2067
|
+
},
|
|
2068
|
+
revokeAuthGrant: async (_learnCard, id) => {
|
|
2069
|
+
await ensureUser();
|
|
2070
|
+
|
|
2071
|
+
return client.authGrants.revokeAuthGrant.mutate({ id });
|
|
2072
|
+
},
|
|
2073
|
+
deleteAuthGrant: async (_learnCard, id) => {
|
|
2074
|
+
await ensureUser();
|
|
2075
|
+
|
|
2076
|
+
return client.authGrants.deleteAuthGrant.mutate({ id });
|
|
2077
|
+
},
|
|
2078
|
+
updateAuthGrant: async (_learnCard, id, updates) => {
|
|
2079
|
+
await ensureUser();
|
|
2080
|
+
|
|
2081
|
+
return client.authGrants.updateAuthGrant.mutate({ id, updates });
|
|
2082
|
+
},
|
|
2083
|
+
getAuthGrant: async (_learnCard, id) => {
|
|
2084
|
+
await ensureUser();
|
|
2085
|
+
|
|
2086
|
+
return client.authGrants.getAuthGrant.query({ id });
|
|
2087
|
+
},
|
|
2088
|
+
getAuthGrants: async (_learnCard, options) => {
|
|
2089
|
+
await ensureUser();
|
|
2090
|
+
|
|
2091
|
+
return client.authGrants.getAuthGrants.query(options);
|
|
2092
|
+
},
|
|
2093
|
+
getAPITokenForAuthGrant: async (_learnCard, id) => {
|
|
2094
|
+
await ensureUser();
|
|
2095
|
+
|
|
2096
|
+
const authGrant = await client.authGrants.getAuthGrant.query({ id });
|
|
2097
|
+
if (!authGrant) throw new Error('Auth grant not found');
|
|
2098
|
+
if (authGrant.status !== 'active') throw new Error('Auth grant is not active');
|
|
2099
|
+
if (!authGrant.challenge) throw new Error('Auth grant has no challenge');
|
|
2100
|
+
if (!authGrant.scope) throw new Error('Auth grant has no scope');
|
|
2101
|
+
if (authGrant.expiresAt && new Date(authGrant.expiresAt) < new Date())
|
|
2102
|
+
throw new Error('Auth grant is expired');
|
|
2103
|
+
|
|
2104
|
+
const apiToken = (await _learnCard.invoke.getDidAuthVp({
|
|
2105
|
+
challenge: authGrant.challenge,
|
|
2106
|
+
proofFormat: 'jwt',
|
|
2107
|
+
})) as string;
|
|
2108
|
+
|
|
2109
|
+
if (!apiToken) throw new Error('Failed to get API Token for auth grant');
|
|
2110
|
+
|
|
2111
|
+
return apiToken;
|
|
2112
|
+
},
|
|
2113
|
+
|
|
2114
|
+
sendCredentialViaInbox: async (_learnCard, issueInboxCredential) => {
|
|
2115
|
+
await ensureUser();
|
|
2116
|
+
|
|
2117
|
+
return client.inbox.issue.mutate(issueInboxCredential);
|
|
2118
|
+
},
|
|
2119
|
+
getMySentInboxCredentials: async (_learnCard, options) => {
|
|
2120
|
+
await ensureUser();
|
|
2121
|
+
|
|
2122
|
+
return client.inbox.getMyIssuedCredentials.query(options);
|
|
2123
|
+
},
|
|
2124
|
+
getInboxCredential: async (_learnCard, id) => {
|
|
2125
|
+
await ensureUser();
|
|
2126
|
+
|
|
2127
|
+
return client.inbox.getInboxCredential.query({ credentialId: id });
|
|
2128
|
+
},
|
|
2129
|
+
finalizeInboxCredentials: async _learnCard => {
|
|
2130
|
+
await ensureUser();
|
|
2131
|
+
|
|
2132
|
+
return client.inbox.finalize.mutate();
|
|
2133
|
+
},
|
|
2134
|
+
sendGuardianApprovalEmail: async (_learnCard, options) => {
|
|
2135
|
+
await ensureUser();
|
|
2136
|
+
|
|
2137
|
+
return client.inbox.sendGuardianApprovalEmail.mutate(options);
|
|
2138
|
+
},
|
|
2139
|
+
approveGuardianRequest: async (_learnCard, token) => {
|
|
2140
|
+
// Open route; no auth required
|
|
2141
|
+
return client.inbox.approveGuardianRequest.mutate({ token });
|
|
2142
|
+
},
|
|
2143
|
+
approveGuardianRequestByPath: async (_learnCard, token) => {
|
|
2144
|
+
// Open route; no auth required
|
|
2145
|
+
return client.inbox.approveGuardianRequestByPath.query({ token });
|
|
2146
|
+
},
|
|
2147
|
+
getGuardianPendingCredential: async (_learnCard, token) => {
|
|
2148
|
+
// Open route; no auth required
|
|
2149
|
+
return client.inbox.getGuardianPendingCredential.query({ token });
|
|
2150
|
+
},
|
|
2151
|
+
sendGuardianChallenge: async (_learnCard, token) => {
|
|
2152
|
+
// Open route; no auth required
|
|
2153
|
+
return client.inbox.sendGuardianChallenge.mutate({ token });
|
|
2154
|
+
},
|
|
2155
|
+
approveGuardianCredential: async (_learnCard, token, otpCode) => {
|
|
2156
|
+
// Open route; no auth required
|
|
2157
|
+
return client.inbox.approveGuardianCredential.mutate({ token, otpCode });
|
|
2158
|
+
},
|
|
2159
|
+
rejectGuardianCredential: async (_learnCard, token, otpCode) => {
|
|
2160
|
+
// Open route; no auth required
|
|
2161
|
+
return client.inbox.rejectGuardianCredential.mutate({ token, otpCode });
|
|
2162
|
+
},
|
|
2163
|
+
approveGuardianCredentialInApp: async (_learnCard, inboxCredentialId) => {
|
|
2164
|
+
await ensureUser();
|
|
2165
|
+
return client.inbox.approveGuardianCredentialInApp.mutate({ inboxCredentialId });
|
|
2166
|
+
},
|
|
2167
|
+
rejectGuardianCredentialInApp: async (_learnCard, inboxCredentialId) => {
|
|
2168
|
+
await ensureUser();
|
|
2169
|
+
return client.inbox.rejectGuardianCredentialInApp.mutate({ inboxCredentialId });
|
|
2170
|
+
},
|
|
2171
|
+
addContactMethod: async (_learnCard, contactMethod) => {
|
|
2172
|
+
await ensureUser();
|
|
2173
|
+
|
|
2174
|
+
return client.contactMethods.addContactMethod.mutate(contactMethod);
|
|
2175
|
+
},
|
|
2176
|
+
getMyContactMethods: async _learnCard => {
|
|
2177
|
+
await ensureUser();
|
|
2178
|
+
|
|
2179
|
+
return client.contactMethods.getMyContactMethods.query();
|
|
2180
|
+
},
|
|
2181
|
+
setPrimaryContactMethod: async (_learnCard, contactMethodId) => {
|
|
2182
|
+
await ensureUser();
|
|
2183
|
+
|
|
2184
|
+
return client.contactMethods.setPrimaryContactMethod.mutate({ contactMethodId });
|
|
2185
|
+
},
|
|
2186
|
+
verifyContactMethod: async (_learnCard, token) => {
|
|
2187
|
+
await ensureUser();
|
|
2188
|
+
|
|
2189
|
+
return client.contactMethods.verifyContactMethod.mutate({ token });
|
|
2190
|
+
},
|
|
2191
|
+
verifyContactMethodWithCredential: async (_learnCard, proofOfLoginJwt) => {
|
|
2192
|
+
await ensureUser();
|
|
2193
|
+
|
|
2194
|
+
return client.contactMethods.verifyWithCredential.mutate({ proofOfLoginJwt });
|
|
2195
|
+
},
|
|
2196
|
+
removeContactMethod: async (_learnCard, id) => {
|
|
2197
|
+
await ensureUser();
|
|
2198
|
+
|
|
2199
|
+
return client.contactMethods.removeContactMethod.mutate({ id });
|
|
2200
|
+
},
|
|
2201
|
+
|
|
2202
|
+
// Skills & Skill Frameworks
|
|
2203
|
+
syncFrameworkSkills: async (_learnCard, input) => {
|
|
2204
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2205
|
+
return client.skills.syncFrameworkSkills.mutate(input);
|
|
2206
|
+
},
|
|
2207
|
+
listSkillTags: async (_learnCard, frameworkId, skillId) => {
|
|
2208
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2209
|
+
return client.skills.listSkillTags.query({ frameworkId, id: skillId } as any);
|
|
2210
|
+
},
|
|
2211
|
+
addSkillTag: async (_learnCard, frameworkId, skillId, tag) => {
|
|
2212
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2213
|
+
return client.skills.addSkillTag.mutate({ frameworkId, id: skillId, tag } as any);
|
|
2214
|
+
},
|
|
2215
|
+
removeSkillTag: async (_learnCard, frameworkId, skillId, slug) => {
|
|
2216
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2217
|
+
return client.skills.removeSkillTag.mutate({
|
|
2218
|
+
frameworkId,
|
|
2219
|
+
id: skillId,
|
|
2220
|
+
slug,
|
|
2221
|
+
} as any);
|
|
2222
|
+
},
|
|
2223
|
+
createManagedSkillFramework: async (_learnCard, input) => {
|
|
2224
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2225
|
+
return client.skillFrameworks.createManaged.mutate(input);
|
|
2226
|
+
},
|
|
2227
|
+
createManagedSkillFrameworks: async (_learnCard, input) => {
|
|
2228
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2229
|
+
return client.skillFrameworks.createManagedBatch.mutate(input);
|
|
2230
|
+
},
|
|
2231
|
+
createSkillFramework: async (_learnCard, input) => {
|
|
2232
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2233
|
+
return client.skillFrameworks.create.mutate(input);
|
|
2234
|
+
},
|
|
2235
|
+
listMySkillFrameworks: async _learnCard => {
|
|
2236
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2237
|
+
return client.skillFrameworks.listMine.query();
|
|
2238
|
+
},
|
|
2239
|
+
getAllAvailableFrameworks: async (_learnCard, options = {}) => {
|
|
2240
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2241
|
+
return client.skillFrameworks.getAllAvailableFrameworks.query(options);
|
|
2242
|
+
},
|
|
2243
|
+
getSkillFrameworkById: async (_learnCard, id, options = {}) => {
|
|
2244
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2245
|
+
return client.skillFrameworks.getById.query({ id, ...options });
|
|
2246
|
+
},
|
|
2247
|
+
getBoostsThatUseFramework: async (_learnCard, frameworkId, options = {}) => {
|
|
2248
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2249
|
+
return client.skillFrameworks.getBoostsThatUseFramework.query({
|
|
2250
|
+
id: frameworkId,
|
|
2251
|
+
...options,
|
|
2252
|
+
});
|
|
2253
|
+
},
|
|
2254
|
+
countBoostsThatUseFramework: async (_learnCard, frameworkId, options = {}) => {
|
|
2255
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2256
|
+
return client.skillFrameworks.countBoostsThatUseFramework.query({
|
|
2257
|
+
id: frameworkId,
|
|
2258
|
+
...options,
|
|
2259
|
+
});
|
|
2260
|
+
},
|
|
2261
|
+
getFrameworkSkillTree: async (_learnCard, frameworkId, options = {}) => {
|
|
2262
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2263
|
+
|
|
2264
|
+
return client.skills.getFrameworkSkillTree.query({ id: frameworkId, ...options });
|
|
2265
|
+
},
|
|
2266
|
+
getSkillChildren: async (_learnCard, frameworkId, skillId, options = {}) => {
|
|
2267
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2268
|
+
|
|
2269
|
+
return client.skills.getSkillChildrenTree.query({
|
|
2270
|
+
frameworkId,
|
|
2271
|
+
id: skillId,
|
|
2272
|
+
...options,
|
|
2273
|
+
});
|
|
2274
|
+
},
|
|
2275
|
+
searchFrameworkSkills: async (_learnCard, frameworkId, query, options = {}) => {
|
|
2276
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2277
|
+
|
|
2278
|
+
return client.skills.searchFrameworkSkills.query({
|
|
2279
|
+
id: frameworkId,
|
|
2280
|
+
query,
|
|
2281
|
+
...options,
|
|
2282
|
+
});
|
|
2283
|
+
},
|
|
2284
|
+
semanticSearchSkills: async (_learnCard, input) => {
|
|
2285
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2286
|
+
|
|
2287
|
+
return client.skills.semanticSearchSkills.query(input);
|
|
2288
|
+
},
|
|
2289
|
+
updateSkillFramework: async (_learnCard, input) => {
|
|
2290
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2291
|
+
return client.skillFrameworks.update.mutate(input);
|
|
2292
|
+
},
|
|
2293
|
+
deleteSkillFramework: async (_learnCard, id) => {
|
|
2294
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2295
|
+
return client.skillFrameworks.delete.mutate({ id });
|
|
2296
|
+
},
|
|
2297
|
+
replaceSkillFrameworkSkills: async (_learnCard, input) => {
|
|
2298
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2299
|
+
return client.skillFrameworks.replaceSkills.mutate(input);
|
|
2300
|
+
},
|
|
2301
|
+
countSkills: async (_learnCard, input) => {
|
|
2302
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2303
|
+
return client.skills.countSkills.query(input);
|
|
2304
|
+
},
|
|
2305
|
+
getFullSkillTree: async (_learnCard, input) => {
|
|
2306
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2307
|
+
return client.skills.getFullSkillTree.query(input);
|
|
2308
|
+
},
|
|
2309
|
+
getSkillPath: async (_learnCard, input) => {
|
|
2310
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2311
|
+
return client.skills.getSkillPath.query(input);
|
|
2312
|
+
},
|
|
2313
|
+
getSkillFrameworkAdmins: async (_learnCard, frameworkId) => {
|
|
2314
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2315
|
+
return client.skillFrameworks.listFrameworkAdmins.query({ frameworkId });
|
|
2316
|
+
},
|
|
2317
|
+
addSkillFrameworkAdmin: async (_learnCard, frameworkId, profileId) => {
|
|
2318
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2319
|
+
return client.skillFrameworks.addFrameworkAdmin.mutate({ frameworkId, profileId });
|
|
2320
|
+
},
|
|
2321
|
+
removeSkillFrameworkAdmin: async (_learnCard, frameworkId, profileId) => {
|
|
2322
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2323
|
+
return client.skillFrameworks.removeFrameworkAdmin.mutate({
|
|
2324
|
+
frameworkId,
|
|
2325
|
+
profileId,
|
|
2326
|
+
});
|
|
2327
|
+
},
|
|
2328
|
+
getSkill: async (_learnCard, frameworkId, skillId) => {
|
|
2329
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2330
|
+
return client.skills.getSkill.query({ frameworkId, id: skillId });
|
|
2331
|
+
},
|
|
2332
|
+
createSkill: async (_learnCard, input) => {
|
|
2333
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2334
|
+
return client.skills.create.mutate(input);
|
|
2335
|
+
},
|
|
2336
|
+
createSkills: async (_learnCard, input) => {
|
|
2337
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2338
|
+
return client.skills.createMany.mutate(input);
|
|
2339
|
+
},
|
|
2340
|
+
updateSkill: async (_learnCard, input) => {
|
|
2341
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2342
|
+
return client.skills.update.mutate(input);
|
|
2343
|
+
},
|
|
2344
|
+
deleteSkill: async (_learnCard, input) => {
|
|
2345
|
+
if (!userData) throw new Error('Please make an account first!');
|
|
2346
|
+
return client.skills.delete.mutate(input);
|
|
2347
|
+
},
|
|
2348
|
+
|
|
2349
|
+
// Integrations
|
|
2350
|
+
addIntegration: async (_learnCard, integration) => {
|
|
2351
|
+
await ensureUser();
|
|
2352
|
+
|
|
2353
|
+
return client.integrations.addIntegration.mutate(integration);
|
|
2354
|
+
},
|
|
2355
|
+
getIntegration: async (_learnCard, id) => {
|
|
2356
|
+
await ensureUser();
|
|
2357
|
+
|
|
2358
|
+
return client.integrations.getIntegration.query({ id });
|
|
2359
|
+
},
|
|
2360
|
+
getIntegrations: async (_learnCard, options = {}) => {
|
|
2361
|
+
await ensureUser();
|
|
2362
|
+
|
|
2363
|
+
return client.integrations.getIntegrations.query(options);
|
|
2364
|
+
},
|
|
2365
|
+
countIntegrations: async (_learnCard, options = {}) => {
|
|
2366
|
+
await ensureUser();
|
|
2367
|
+
|
|
2368
|
+
return client.integrations.countIntegrations.query(options);
|
|
2369
|
+
},
|
|
2370
|
+
updateIntegration: async (_learnCard, id, updates) => {
|
|
2371
|
+
await ensureUser();
|
|
2372
|
+
|
|
2373
|
+
return client.integrations.updateIntegration.mutate({ id, updates });
|
|
2374
|
+
},
|
|
2375
|
+
deleteIntegration: async (_learnCard, id) => {
|
|
2376
|
+
await ensureUser();
|
|
2377
|
+
|
|
2378
|
+
return client.integrations.deleteIntegration.mutate({ id });
|
|
2379
|
+
},
|
|
2380
|
+
|
|
2381
|
+
// App Store
|
|
2382
|
+
createAppStoreListing: async (_learnCard, integrationId, listing) => {
|
|
2383
|
+
await ensureUser();
|
|
2384
|
+
|
|
2385
|
+
return client.appStore.createListing.mutate({ integrationId, listing });
|
|
2386
|
+
},
|
|
2387
|
+
associateListingWithSigningAuthority: async (
|
|
2388
|
+
_learnCard,
|
|
2389
|
+
listingId,
|
|
2390
|
+
endpoint,
|
|
2391
|
+
name,
|
|
2392
|
+
did,
|
|
2393
|
+
isPrimary
|
|
2394
|
+
) => {
|
|
2395
|
+
await ensureUser();
|
|
2396
|
+
|
|
2397
|
+
return client.appStore.associateListingWithSigningAuthority.mutate({
|
|
2398
|
+
listingId,
|
|
2399
|
+
endpoint,
|
|
2400
|
+
name,
|
|
2401
|
+
did,
|
|
2402
|
+
isPrimary,
|
|
2403
|
+
});
|
|
2404
|
+
},
|
|
2405
|
+
getListingSigningAuthority: async (_learnCard, listingId) => {
|
|
2406
|
+
await ensureUser();
|
|
2407
|
+
|
|
2408
|
+
return client.appStore.getListingSigningAuthority.query({ listingId });
|
|
2409
|
+
},
|
|
2410
|
+
getIntegrationForListing: async (_learnCard, listingId) => {
|
|
2411
|
+
await ensureUser();
|
|
2412
|
+
|
|
2413
|
+
return client.appStore.getIntegrationForListing.query({ listingId });
|
|
2414
|
+
},
|
|
2415
|
+
|
|
2416
|
+
getAppStoreListing: async (_learnCard, listingId) => {
|
|
2417
|
+
await ensureUser();
|
|
2418
|
+
|
|
2419
|
+
return client.appStore.getListing.query({ listingId });
|
|
2420
|
+
},
|
|
2421
|
+
|
|
2422
|
+
updateAppStoreListing: async (_learnCard, listingId, updates) => {
|
|
2423
|
+
await ensureUser();
|
|
2424
|
+
|
|
2425
|
+
return client.appStore.updateListing.mutate({ listingId, updates });
|
|
2426
|
+
},
|
|
2427
|
+
|
|
2428
|
+
deleteAppStoreListing: async (_learnCard, listingId) => {
|
|
2429
|
+
await ensureUser();
|
|
2430
|
+
|
|
2431
|
+
return client.appStore.deleteListing.mutate({ listingId });
|
|
2432
|
+
},
|
|
2433
|
+
|
|
2434
|
+
submitAppStoreListingForReview: async (_learnCard, listingId) => {
|
|
2435
|
+
await ensureUser();
|
|
2436
|
+
|
|
2437
|
+
return client.appStore.submitForReview.mutate({ listingId });
|
|
2438
|
+
},
|
|
2439
|
+
|
|
2440
|
+
unsubmitAppStoreListing: async (_learnCard, listingId) => {
|
|
2441
|
+
await ensureUser();
|
|
2442
|
+
|
|
2443
|
+
return client.appStore.unsubmitForReview.mutate({ listingId });
|
|
2444
|
+
},
|
|
2445
|
+
|
|
2446
|
+
getListingsForIntegration: async (_learnCard, integrationId, options = {}) => {
|
|
2447
|
+
await ensureUser();
|
|
2448
|
+
|
|
2449
|
+
return client.appStore.getListingsForIntegration.query({
|
|
2450
|
+
integrationId,
|
|
2451
|
+
...options,
|
|
2452
|
+
});
|
|
2453
|
+
},
|
|
2454
|
+
|
|
2455
|
+
countListingsForIntegration: async (_learnCard, integrationId) => {
|
|
2456
|
+
await ensureUser();
|
|
2457
|
+
|
|
2458
|
+
return client.appStore.countListingsForIntegration.query({ integrationId });
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2461
|
+
browseAppStore: async (_learnCard, options) => {
|
|
2462
|
+
return client.appStore.browseListedApps.query(options);
|
|
2463
|
+
},
|
|
2464
|
+
|
|
2465
|
+
getPublicAppStoreListing: async (_learnCard, listingId) => {
|
|
2466
|
+
return client.appStore.getPublicListing.query({ listingId });
|
|
2467
|
+
},
|
|
2468
|
+
|
|
2469
|
+
getPublicAppStoreListingBySlug: async (_learnCard, slug) => {
|
|
2470
|
+
return client.appStore.getPublicListingBySlug.query({ slug });
|
|
2471
|
+
},
|
|
2472
|
+
|
|
2473
|
+
getAppStoreListingInstallCount: async (_learnCard, listingId) => {
|
|
2474
|
+
return client.appStore.getListingInstallCount.query({ listingId });
|
|
2475
|
+
},
|
|
2476
|
+
|
|
2477
|
+
installApp: async (_learnCard, listingId) => {
|
|
2478
|
+
await ensureUser();
|
|
2479
|
+
|
|
2480
|
+
return client.appStore.installApp.mutate({ listingId });
|
|
2481
|
+
},
|
|
2482
|
+
|
|
2483
|
+
uninstallApp: async (_learnCard, listingId) => {
|
|
2484
|
+
await ensureUser();
|
|
2485
|
+
|
|
2486
|
+
return client.appStore.uninstallApp.mutate({ listingId });
|
|
2487
|
+
},
|
|
2488
|
+
|
|
2489
|
+
getInstalledApps: async (_learnCard, options = {}) => {
|
|
2490
|
+
await ensureUser();
|
|
2491
|
+
|
|
2492
|
+
return client.appStore.getInstalledApps.query(options);
|
|
2493
|
+
},
|
|
2494
|
+
|
|
2495
|
+
countInstalledApps: async _learnCard => {
|
|
2496
|
+
await ensureUser();
|
|
2497
|
+
|
|
2498
|
+
return client.appStore.countInstalledApps.query();
|
|
2499
|
+
},
|
|
2500
|
+
|
|
2501
|
+
isAppInstalled: async (_learnCard, listingId) => {
|
|
2502
|
+
await ensureUser();
|
|
2503
|
+
|
|
2504
|
+
return client.appStore.isAppInstalled.query({ listingId });
|
|
2505
|
+
},
|
|
2506
|
+
|
|
2507
|
+
getMyCredentialsFromApp: async (_learnCard, listingId, options = {}) => {
|
|
2508
|
+
await ensureUser();
|
|
2509
|
+
|
|
2510
|
+
return client.appStore.getMyCredentialsFromApp.query({ listingId, ...options });
|
|
2511
|
+
},
|
|
2512
|
+
|
|
2513
|
+
isAppStoreAdmin: async _learnCard => {
|
|
2514
|
+
await ensureUser();
|
|
2515
|
+
|
|
2516
|
+
return client.appStore.isAdmin.query();
|
|
2517
|
+
},
|
|
2518
|
+
|
|
2519
|
+
adminUpdateListingStatus: async (_learnCard, listingId, status) => {
|
|
2520
|
+
await ensureUser();
|
|
2521
|
+
|
|
2522
|
+
return client.appStore.adminUpdateListingStatus.mutate({ listingId, status });
|
|
2523
|
+
},
|
|
2524
|
+
|
|
2525
|
+
adminUpdatePromotionLevel: async (_learnCard, listingId, promotionLevel) => {
|
|
2526
|
+
await ensureUser();
|
|
2527
|
+
|
|
2528
|
+
return client.appStore.adminUpdatePromotionLevel.mutate({
|
|
2529
|
+
listingId,
|
|
2530
|
+
promotionLevel,
|
|
2531
|
+
});
|
|
2532
|
+
},
|
|
2533
|
+
|
|
2534
|
+
adminGetAllListings: async (_learnCard, options) => {
|
|
2535
|
+
await ensureUser();
|
|
2536
|
+
|
|
2537
|
+
return client.appStore.adminGetAllListings.query(options);
|
|
2538
|
+
},
|
|
2539
|
+
|
|
2540
|
+
// App Store Boost Management
|
|
2541
|
+
addBoostToApp: async (_learnCard, listingId, boostUri, templateAlias) => {
|
|
2542
|
+
await ensureUser();
|
|
2543
|
+
|
|
2544
|
+
return client.appStore.addBoostToListing.mutate({
|
|
2545
|
+
listingId,
|
|
2546
|
+
boostUri,
|
|
2547
|
+
templateAlias,
|
|
2548
|
+
});
|
|
2549
|
+
},
|
|
2550
|
+
|
|
2551
|
+
associateBoostWithListing: async (_learnCard, listingId, boostUri, templateAlias) => {
|
|
2552
|
+
await ensureUser();
|
|
2553
|
+
|
|
2554
|
+
return client.appStore.addBoostToListing.mutate({
|
|
2555
|
+
listingId,
|
|
2556
|
+
boostUri,
|
|
2557
|
+
templateAlias,
|
|
2558
|
+
});
|
|
2559
|
+
},
|
|
2560
|
+
|
|
2561
|
+
removeBoostFromApp: async (_learnCard, listingId, templateAlias) => {
|
|
2562
|
+
await ensureUser();
|
|
2563
|
+
|
|
2564
|
+
return client.appStore.removeBoostFromListing.mutate({ listingId, templateAlias });
|
|
2565
|
+
},
|
|
2566
|
+
|
|
2567
|
+
getAppBoosts: async (_learnCard, listingId) => {
|
|
2568
|
+
await ensureUser();
|
|
2569
|
+
|
|
2570
|
+
return client.appStore.getBoostsForListing.query({ listingId });
|
|
2571
|
+
},
|
|
2572
|
+
|
|
2573
|
+
// Generic App Event
|
|
2574
|
+
sendAppEvent: async (_learnCard, listingId, event) => {
|
|
2575
|
+
await ensureUser();
|
|
2576
|
+
|
|
2577
|
+
return client.appStore.appEvent.mutate({ listingId, event });
|
|
2578
|
+
},
|
|
2579
|
+
|
|
2580
|
+
resolveFromLCN: async (_learnCard, uri) => {
|
|
2581
|
+
const result = await client.storage.resolve.query({ uri });
|
|
2582
|
+
|
|
2583
|
+
return UnsignedVCValidator.or(VCValidator)
|
|
2584
|
+
.or(VPValidator)
|
|
2585
|
+
.or(JWEValidator)
|
|
2586
|
+
.or(ConsentFlowContractValidator)
|
|
2587
|
+
.or(ConsentFlowTermsValidator)
|
|
2588
|
+
.parseAsync(result);
|
|
2589
|
+
},
|
|
2590
|
+
|
|
2591
|
+
getLCNClient: () => client,
|
|
2592
|
+
|
|
2593
|
+
// Activity
|
|
2594
|
+
getMyActivities: async (_learnCard, options = {}) => {
|
|
2595
|
+
await ensureUser();
|
|
2596
|
+
|
|
2597
|
+
return client.activity.getMyActivities.query(options);
|
|
2598
|
+
},
|
|
2599
|
+
|
|
2600
|
+
getActivityStats: async (_learnCard, options = {}) => {
|
|
2601
|
+
await ensureUser();
|
|
2602
|
+
|
|
2603
|
+
return client.activity.getActivityStats.query(options);
|
|
2604
|
+
},
|
|
2605
|
+
|
|
2606
|
+
getActivity: async (_learnCard, options) => {
|
|
2607
|
+
await ensureUser();
|
|
2608
|
+
|
|
2609
|
+
return client.activity.getActivity.query(options);
|
|
2610
|
+
},
|
|
2611
|
+
|
|
2612
|
+
getActivityChain: async (_learnCard, options) => {
|
|
2613
|
+
await ensureUser();
|
|
2614
|
+
|
|
2615
|
+
return client.activity.getActivityChain.query(options);
|
|
2616
|
+
},
|
|
2617
|
+
|
|
2618
|
+
isServiceTrusted: async (_learnCard, serviceDid) => {
|
|
2619
|
+
const trustedServices = await client.federation.getTrustedServices.query({});
|
|
2620
|
+
return trustedServices.some(s => s.did === serviceDid);
|
|
2621
|
+
},
|
|
2622
|
+
|
|
2623
|
+
getTrustedServices: async () => {
|
|
2624
|
+
return client.federation.getTrustedServices.query({});
|
|
2625
|
+
},
|
|
2626
|
+
},
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
/**
|
|
2631
|
+
* @group Plugins
|
|
2632
|
+
*/
|
|
2633
|
+
export const getVerifyBoostPlugin = async (
|
|
2634
|
+
learnCard: LearnCard<any, any, VerifyExtension>,
|
|
2635
|
+
trustedBoostRegistryUrl?: string
|
|
2636
|
+
): Promise<VerifyBoostPlugin> => {
|
|
2637
|
+
const DEFAULT_TRUSTED_BOOST_REGISTRY: TrustedBoostRegistryEntry[] = [
|
|
2638
|
+
{
|
|
2639
|
+
id: 'LearnCard Network',
|
|
2640
|
+
url: 'https://network.learncard.com',
|
|
2641
|
+
did: 'did:web:network.learncard.com',
|
|
2642
|
+
},
|
|
2643
|
+
];
|
|
2644
|
+
|
|
2645
|
+
let boostRegistry: TrustedBoostRegistryEntry[];
|
|
2646
|
+
|
|
2647
|
+
if (trustedBoostRegistryUrl) {
|
|
2648
|
+
const res = await fetch(trustedBoostRegistryUrl);
|
|
2649
|
+
if (res.status === 200) {
|
|
2650
|
+
boostRegistry = JSON.parse(await res.text());
|
|
2651
|
+
} else {
|
|
2652
|
+
boostRegistry = DEFAULT_TRUSTED_BOOST_REGISTRY;
|
|
2653
|
+
}
|
|
2654
|
+
} else {
|
|
2655
|
+
boostRegistry = DEFAULT_TRUSTED_BOOST_REGISTRY;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
const getIssuerDID = (issuer: string | Profile | undefined): string | undefined => {
|
|
2659
|
+
if (!issuer) return;
|
|
2660
|
+
if (typeof issuer === 'string') {
|
|
2661
|
+
return issuer;
|
|
2662
|
+
} else if ('id' in issuer && typeof issuer.id === 'string') {
|
|
2663
|
+
return issuer.id;
|
|
2664
|
+
} else {
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
const getTrustedBoostVerifier = (
|
|
2669
|
+
issuer: string | Profile | undefined
|
|
2670
|
+
): TrustedBoostRegistryEntry | undefined => {
|
|
2671
|
+
const issuerDID = getIssuerDID(issuer);
|
|
2672
|
+
if (!issuerDID) return;
|
|
2673
|
+
return boostRegistry.find(o => o.did === issuerDID);
|
|
2674
|
+
};
|
|
2675
|
+
return {
|
|
2676
|
+
name: 'VerifyBoost',
|
|
2677
|
+
displayName: 'Verify Boost Extension',
|
|
2678
|
+
description: 'Adds a check for validating Boost Credentials.',
|
|
2679
|
+
methods: {
|
|
2680
|
+
verifyCredential: async (_learnCard, credential, options) => {
|
|
2681
|
+
const verificationCheck = await learnCard.invoke.verifyCredential(
|
|
2682
|
+
credential,
|
|
2683
|
+
options
|
|
2684
|
+
);
|
|
2685
|
+
const boostCredential = credential?.boostCredential;
|
|
2686
|
+
try {
|
|
2687
|
+
if (boostCredential) {
|
|
2688
|
+
const verifyBoostCredential = await learnCard.invoke.verifyCredential(
|
|
2689
|
+
boostCredential
|
|
2690
|
+
);
|
|
2691
|
+
const boostCredentialErrors = verifyBoostCredential.errors ?? [];
|
|
2692
|
+
if (verifyBoostCredential.status?.length) {
|
|
2693
|
+
verificationCheck.status = [
|
|
2694
|
+
...(verificationCheck.status ?? []),
|
|
2695
|
+
...verifyBoostCredential.status,
|
|
2696
|
+
];
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
if (!boostCredential?.boostId && !credential?.boostId) {
|
|
2700
|
+
verificationCheck.warnings.push(
|
|
2701
|
+
'Boost Authenticity could not be verified: Boost ID metadata is missing.'
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
if (boostCredentialErrors.length > 0) {
|
|
2706
|
+
if (
|
|
2707
|
+
boostCredentialErrors.some(error =>
|
|
2708
|
+
/revoked|suspend|status/i.test(error)
|
|
2709
|
+
)
|
|
2710
|
+
) {
|
|
2711
|
+
verificationCheck.checks = verificationCheck.checks.filter(
|
|
2712
|
+
check => check !== 'status'
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
verificationCheck.errors = [
|
|
2717
|
+
...boostCredentialErrors,
|
|
2718
|
+
...(verificationCheck.errors || []),
|
|
2719
|
+
'Boost Credential could not be verified.',
|
|
2720
|
+
];
|
|
2721
|
+
} else if (boostCredential?.boostId !== credential?.boostId) {
|
|
2722
|
+
verificationCheck.errors.push(
|
|
2723
|
+
'Boost Authenticity could not be verified: Boost ID metadata is mismatched.'
|
|
2724
|
+
);
|
|
2725
|
+
} else {
|
|
2726
|
+
const trustedBoostIssuer = getTrustedBoostVerifier(credential?.issuer);
|
|
2727
|
+
if (trustedBoostIssuer) {
|
|
2728
|
+
verificationCheck.checks.push(
|
|
2729
|
+
`Boost is Authentic. Verified by ${trustedBoostIssuer.id}.`
|
|
2730
|
+
);
|
|
2731
|
+
} else {
|
|
2732
|
+
verificationCheck.warnings.push(
|
|
2733
|
+
`Boost Authenticity could not be verified. Issuer is outside of trust network: ${getIssuerDID(
|
|
2734
|
+
credential?.issuer
|
|
2735
|
+
)}`
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
} catch {
|
|
2741
|
+
verificationCheck.errors.push('Boost authenticity could not be verified.');
|
|
2742
|
+
}
|
|
2743
|
+
return verificationCheck;
|
|
2744
|
+
},
|
|
2745
|
+
},
|
|
2746
|
+
};
|
|
2747
|
+
};
|