@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/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 `&amp;` / `&quot;` 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
+ };