@learncard/network-plugin 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/plugin.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  getCredentialStatusArray,
31
31
  injectManagedRefreshService as injectSharedManagedRefreshService,
32
32
  isVC2Format,
33
+ isEncrypted,
33
34
  resolveStorageReadResult,
34
35
  } from '@learncard/helpers';
35
36
  import Mustache from 'mustache';
@@ -544,6 +545,8 @@ const isActAsDenial = (error: unknown): boolean => {
544
545
  }
545
546
  return false;
546
547
  };
548
+ const getInboxFailureReason = (reason: unknown): string =>
549
+ reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason);
547
550
 
548
551
  /**
549
552
  * @group Plugins
@@ -1147,6 +1150,73 @@ export async function getLearnCardNetworkPlugin(
1147
1150
  return client.profile.invalidateInvite.mutate({ challenge });
1148
1151
  },
1149
1152
 
1153
+ createShareLink: async (_learnCard, input) => {
1154
+ await ensureUser();
1155
+
1156
+ return client.shareLinks.create.mutate(input);
1157
+ },
1158
+ putShareLinkAttachmentChunk: async (_learnCard, input) => {
1159
+ await ensureUser();
1160
+ return client.shareLinks.putAttachmentChunk.mutate(input);
1161
+ },
1162
+ deleteShareLinkAttachmentChunks: async (_learnCard, input) => {
1163
+ await ensureUser();
1164
+ return client.shareLinks.deleteAttachmentChunks.mutate(input);
1165
+ },
1166
+ getShareLinkAttachmentChunk: async (_learnCard, input) =>
1167
+ client.publicShareLinks.attachmentChunk.mutate(input),
1168
+ updateShareLink: async (_learnCard, input) => {
1169
+ await ensureUser();
1170
+
1171
+ return client.shareLinks.update.mutate(input);
1172
+ },
1173
+ revokeShareLink: async (_learnCard, input) => {
1174
+ await ensureUser();
1175
+
1176
+ return client.shareLinks.revoke.mutate(input);
1177
+ },
1178
+ getShareLink: async (_learnCard, id) => {
1179
+ await ensureUser();
1180
+
1181
+ return client.shareLinks.get.query({ id });
1182
+ },
1183
+ getShareLinkOperationStatus: async (_learnCard, input) => {
1184
+ await ensureUser();
1185
+
1186
+ return client.shareLinks.getOperationStatus.query(input);
1187
+ },
1188
+ retryShareLinkOperation: async (_learnCard, input) => {
1189
+ await ensureUser();
1190
+
1191
+ return client.shareLinks.retry.mutate(input);
1192
+ },
1193
+ getShareLinkRecovery: async (_learnCard, id) => {
1194
+ await ensureUser();
1195
+
1196
+ return client.shareLinks.getRecovery.query({ id });
1197
+ },
1198
+ getShareLinkOwnerContent: async (_learnCard, id) => {
1199
+ await ensureUser();
1200
+
1201
+ return client.shareLinks.getContent.query({ id });
1202
+ },
1203
+ listShareLinks: async (_learnCard, input) => {
1204
+ await ensureUser();
1205
+
1206
+ return client.shareLinks.list.query(input);
1207
+ },
1208
+
1209
+ // Anonymous public methods: deliberately NO `ensureUser()`. A viewer
1210
+ // has no account and must not need one.
1211
+ // POST keeps an optional passcode in the request body and out of URLs,
1212
+ // access logs, browser history and referrers.
1213
+ resolveShareLink: async (_learnCard, id, passcode) =>
1214
+ client.publicShareLinks.resolve.mutate({ id, passcode }),
1215
+ getShareLinkContent: async (_learnCard, id, passcode) =>
1216
+ client.publicShareLinks.content.mutate({ id, passcode }),
1217
+ acknowledgeShareLinkView: async (_learnCard, receipt) =>
1218
+ client.publicShareLinks.acknowledgeView.mutate({ receipt }),
1219
+
1150
1220
  blockProfile: async (_learnCard, profileId) => {
1151
1221
  await ensureUser();
1152
1222
 
@@ -1291,13 +1361,19 @@ export async function getLearnCardNetworkPlugin(
1291
1361
  return client.credential.deleteCredential.mutate({ uri });
1292
1362
  },
1293
1363
 
1294
- sendPresentation: async (_learnCard, profileId, vp, encrypt = true) => {
1364
+ sendPresentation: async (_learnCard, profileId, vp, metadataOrEncrypt, encrypt) => {
1295
1365
  await ensureUser();
1296
1366
 
1297
- if (!encrypt) {
1367
+ const metadata =
1368
+ typeof metadataOrEncrypt === 'object' ? metadataOrEncrypt : undefined;
1369
+ const shouldEncrypt =
1370
+ typeof metadataOrEncrypt === 'boolean' ? metadataOrEncrypt : (encrypt ?? true);
1371
+
1372
+ if (!shouldEncrypt) {
1298
1373
  return client.presentation.sendPresentation.mutate({
1299
1374
  profileId,
1300
1375
  presentation: vp,
1376
+ metadata,
1301
1377
  });
1302
1378
  }
1303
1379
 
@@ -1311,7 +1387,11 @@ export async function getLearnCardNetworkPlugin(
1311
1387
  target.did,
1312
1388
  ]);
1313
1389
 
1314
- return client.presentation.sendPresentation.mutate({ profileId, presentation });
1390
+ return client.presentation.sendPresentation.mutate({
1391
+ profileId,
1392
+ presentation,
1393
+ metadata,
1394
+ });
1315
1395
  },
1316
1396
  acceptPresentation: async (_learnCard, uri) => {
1317
1397
  await ensureUser();
@@ -2286,6 +2366,14 @@ export async function getLearnCardNetworkPlugin(
2286
2366
  });
2287
2367
  },
2288
2368
 
2369
+ addContractRecipient: async (_learnCard, contractUri, recipient) => {
2370
+ await ensureUser();
2371
+ return client.contracts.addContractRecipient.mutate({ contractUri, recipient });
2372
+ },
2373
+ removeContractRecipient: async (_learnCard, contractUri, recipient) => {
2374
+ await ensureUser();
2375
+ return client.contracts.removeContractRecipient.mutate({ contractUri, recipient });
2376
+ },
2289
2377
  getContract: async (_learnCard, uri) => {
2290
2378
  return client.contracts.getConsentFlowContract.query({ uri });
2291
2379
  },
@@ -2340,7 +2428,7 @@ export async function getLearnCardNetworkPlugin(
2340
2428
  consentToContract: async (
2341
2429
  _learnCard,
2342
2430
  contractUri,
2343
- { terms, expiresAt, oneTime },
2431
+ { terms, expiresAt, oneTime, audienceVersion, expectedRequestId },
2344
2432
  recipientToken
2345
2433
  ) => {
2346
2434
  await ensureUser();
@@ -2350,6 +2438,8 @@ export async function getLearnCardNetworkPlugin(
2350
2438
  terms,
2351
2439
  expiresAt,
2352
2440
  oneTime,
2441
+ audienceVersion,
2442
+ expectedRequestId,
2353
2443
  recipientToken, // for SmartResume
2354
2444
  });
2355
2445
  },
@@ -2360,7 +2450,11 @@ export async function getLearnCardNetworkPlugin(
2360
2450
  return client.contracts.getConsentedContracts.query(options);
2361
2451
  },
2362
2452
 
2363
- updateContractTerms: async (_learnCard, uri, { terms, expiresAt, oneTime }) => {
2453
+ updateContractTerms: async (
2454
+ _learnCard,
2455
+ uri,
2456
+ { terms, expiresAt, oneTime, audienceVersion }
2457
+ ) => {
2364
2458
  await ensureUser();
2365
2459
 
2366
2460
  return client.contracts.updateConsentedContractTerms.mutate({
@@ -2368,6 +2462,7 @@ export async function getLearnCardNetworkPlugin(
2368
2462
  terms,
2369
2463
  expiresAt,
2370
2464
  oneTime,
2465
+ audienceVersion,
2371
2466
  });
2372
2467
  },
2373
2468
 
@@ -2407,12 +2502,18 @@ export async function getLearnCardNetworkPlugin(
2407
2502
  return client.contracts.verifyConsent.query({ uri, profileId });
2408
2503
  },
2409
2504
 
2410
- syncCredentialsToContract: async (_learnCard, termsUri, categories) => {
2505
+ syncCredentialsToContract: async (
2506
+ _learnCard,
2507
+ termsUri,
2508
+ categories,
2509
+ audienceVersion
2510
+ ) => {
2411
2511
  await ensureUser();
2412
2512
 
2413
2513
  return client.contracts.syncCredentialsToContract.mutate({
2414
2514
  termsUri,
2415
2515
  categories,
2516
+ audienceVersion,
2416
2517
  });
2417
2518
  },
2418
2519
 
@@ -2424,6 +2525,14 @@ export async function getLearnCardNetworkPlugin(
2424
2525
  });
2425
2526
  },
2426
2527
 
2528
+ sendContractRequest: async (_learnCard, request) => {
2529
+ await ensureUser();
2530
+ return client.contracts.sendContractRequest.mutate(request);
2531
+ },
2532
+ denyContractRequest: async (_learnCard, contractUri) => {
2533
+ await ensureUser();
2534
+ return client.contracts.denyContractRequest.mutate({ contractUri });
2535
+ },
2427
2536
  sendAiInsightsContractRequest: async (
2428
2537
  _learnCard,
2429
2538
  contractUri,
@@ -2683,10 +2792,34 @@ export async function getLearnCardNetworkPlugin(
2683
2792
 
2684
2793
  return client.inbox.getInboxCredential.query({ credentialId: id });
2685
2794
  },
2686
- finalizeInboxCredentials: async _learnCard => {
2795
+ finalizeInboxCredentials: async learnCard => {
2687
2796
  await ensureUser();
2688
-
2689
- return client.inbox.finalize.mutate();
2797
+ const result = await client.inbox.finalize.mutate();
2798
+ const results = await Promise.allSettled(
2799
+ result.deliveries.map(async delivery => ({
2800
+ ...delivery,
2801
+ credential: VCValidator.parse(
2802
+ isEncrypted(delivery.credential)
2803
+ ? await learnCard.invoke.decryptDagJwe(delivery.credential, [
2804
+ learnCard.id.keypair(),
2805
+ ])
2806
+ : delivery.credential
2807
+ ),
2808
+ }))
2809
+ );
2810
+ const deliveries = results.flatMap((delivery, index) => {
2811
+ if (delivery.status === 'fulfilled') return [delivery.value];
2812
+ console.warn(
2813
+ `Failed to finalize inbox delivery ${result.deliveries[index].id}: ${getInboxFailureReason(delivery.reason)}`
2814
+ );
2815
+ return [];
2816
+ });
2817
+ return {
2818
+ ...result,
2819
+ deliveries,
2820
+ verifiableCredentials: deliveries.map(delivery => delivery.credential),
2821
+ errors: result.errors + results.length - deliveries.length,
2822
+ };
2690
2823
  },
2691
2824
  recoverInboxCredentials: async (learnCard, options = {}) => {
2692
2825
  const result = await client.inbox.getMyInboxDeliveries.query(options);
@@ -2700,9 +2833,13 @@ export async function getLearnCardNetworkPlugin(
2700
2833
  ),
2701
2834
  }))
2702
2835
  );
2703
- const records = results.flatMap(record =>
2704
- record.status === 'fulfilled' ? [record.value] : []
2705
- );
2836
+ const records = results.flatMap((record, index) => {
2837
+ if (record.status === 'fulfilled') return [record.value];
2838
+ console.warn(
2839
+ `Failed to recover inbox delivery ${result.records[index].id}: ${getInboxFailureReason(record.reason)}`
2840
+ );
2841
+ return [];
2842
+ });
2706
2843
  return { ...result, records, failed: results.length - records.length };
2707
2844
  },
2708
2845
  sendGuardianApprovalEmail: async (_learnCard, options) => {
@@ -3017,6 +3154,24 @@ export async function getLearnCardNetworkPlugin(
3017
3154
  return client.appStore.unsubmitForReview.mutate({ listingId });
3018
3155
  },
3019
3156
 
3157
+ submitAppStoreListingUpdate: async (_learnCard, listingId) => {
3158
+ await ensureUser();
3159
+
3160
+ return client.appStore.submitListingUpdate.mutate({ listingId });
3161
+ },
3162
+
3163
+ withdrawAppStoreListingUpdate: async (_learnCard, listingId) => {
3164
+ await ensureUser();
3165
+
3166
+ return client.appStore.withdrawListingUpdate.mutate({ listingId });
3167
+ },
3168
+
3169
+ discardAppStoreListingUpdate: async (_learnCard, listingId) => {
3170
+ await ensureUser();
3171
+
3172
+ return client.appStore.discardListingUpdate.mutate({ listingId });
3173
+ },
3174
+
3020
3175
  getListingsForIntegration: async (_learnCard, integrationId, options = {}) => {
3021
3176
  await ensureUser();
3022
3177
 
@@ -3032,6 +3187,39 @@ export async function getLearnCardNetworkPlugin(
3032
3187
  return client.appStore.countListingsForIntegration.query({ integrationId });
3033
3188
  },
3034
3189
 
3190
+ submitAppManifest: async (_learnCard, integrationId, manifest) => {
3191
+ await ensureUser();
3192
+ return client.appStore.submitAppManifest.mutate({ integrationId, manifest });
3193
+ },
3194
+
3195
+ getManifestVersions: async (_learnCard, integrationId, options) => {
3196
+ await ensureUser();
3197
+ return client.appStore.getManifestVersions.query({ integrationId, ...options });
3198
+ },
3199
+
3200
+ getManifestVersion: async (_learnCard, integrationId, version) => {
3201
+ await ensureUser();
3202
+ return client.appStore.getManifestVersion.query({ integrationId, version });
3203
+ },
3204
+
3205
+ getManifestDiff: async (_learnCard, integrationId, toVersion, fromVersion) => {
3206
+ await ensureUser();
3207
+ return client.appStore.getManifestDiff.query({
3208
+ integrationId,
3209
+ toVersion,
3210
+ fromVersion,
3211
+ });
3212
+ },
3213
+
3214
+ applyManifestVersion: async (_learnCard, integrationId, version, listingId) => {
3215
+ await ensureUser();
3216
+ return client.appStore.applyManifestVersion.mutate({
3217
+ integrationId,
3218
+ version,
3219
+ listingId,
3220
+ });
3221
+ },
3222
+
3035
3223
  browseAppStore: async (_learnCard, options) => {
3036
3224
  return client.appStore.browseListedApps.query(options);
3037
3225
  },
@@ -3111,6 +3299,12 @@ export async function getLearnCardNetworkPlugin(
3111
3299
  return client.appStore.adminGetAllListings.query(options);
3112
3300
  },
3113
3301
 
3302
+ adminReviewListingUpdate: async (_learnCard, listingId, approve) => {
3303
+ await ensureUser();
3304
+
3305
+ return client.appStore.adminReviewListingUpdate.mutate({ listingId, approve });
3306
+ },
3307
+
3114
3308
  // App Store Boost Management
3115
3309
  addBoostToApp: async (_learnCard, listingId, boostUri, templateAlias) => {
3116
3310
  await ensureUser();
@@ -0,0 +1,61 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { generateLearnCard } from '@learncard/core';
3
+ import { getDidKitPlugin } from '@learncard/didkit-plugin';
4
+ import { getDidKeyPlugin } from '@learncard/didkey-plugin';
5
+ import { getVCPlugin } from '@learncard/vc-plugin';
6
+ import { getClient as getBrainClient } from '@learncard/network-brain-client';
7
+ import { vi, expect, it } from 'vitest';
8
+ import { getLearnCardNetworkPlugin } from '../';
9
+ import { SAMPLE_VCS } from './mocks/sample-vcs';
10
+
11
+ vi.mock('@learncard/network-brain-client', () => ({
12
+ getClient: vi.fn(),
13
+ getApiTokenClient: vi.fn(),
14
+ }));
15
+
16
+ it('preserves valid siblings through finalization and recovery despite wrong-key and credential-validation failures', async () => {
17
+ const didkit = readFile(
18
+ require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
19
+ );
20
+ const makeCard = async (seed: string) => {
21
+ const card = await (await generateLearnCard()).addPlugin(await getDidKitPlugin(didkit));
22
+ const identity = await card.addPlugin(await getDidKeyPlugin(card, seed, 'key'));
23
+ return identity.addPlugin(await getVCPlugin(identity));
24
+ };
25
+ const holder = await makeCard('a'.repeat(64));
26
+ const otherHolder = await makeCard('b'.repeat(64));
27
+ const credential = SAMPLE_VCS.VALID_BOOST;
28
+ const wrongHolderJwe = await holder.invoke.createDagJwe(credential, [otherHolder.id.did()]);
29
+ const validJwe = await holder.invoke.createDagJwe(credential, [holder.id.did()]);
30
+ const invalidCredentialJwe = await holder.invoke.createDagJwe({}, [holder.id.did()]);
31
+ const deliveries = [
32
+ { id: 'wrong-holder', credential: wrongHolderJwe },
33
+ { id: 'invalid-credential', credential: invalidCredentialJwe },
34
+ { id: 'valid-sibling', credential: validJwe },
35
+ ];
36
+ const client = {
37
+ profile: { getProfile: { query: vi.fn().mockResolvedValue({ did: holder.id.did() }) } },
38
+ inbox: {
39
+ finalize: {
40
+ mutate: vi
41
+ .fn()
42
+ .mockResolvedValue({ processed: 3, claimed: 3, errors: 0, deliveries }),
43
+ },
44
+ getMyInboxDeliveries: {
45
+ query: vi.fn().mockResolvedValue({ records: deliveries, hasMore: false }),
46
+ },
47
+ },
48
+ };
49
+ vi.mocked(getBrainClient).mockResolvedValue(client as never);
50
+ const plugin = await getLearnCardNetworkPlugin(holder, 'https://network.example');
51
+ const network = await holder.addPlugin(plugin);
52
+
53
+ const result = await network.invoke.finalizeInboxCredentials();
54
+ expect(result.deliveries).toEqual([{ id: 'valid-sibling', credential }]);
55
+ expect(result.verifiableCredentials).toEqual([credential]);
56
+ expect(result.errors).toBe(2);
57
+
58
+ const recovery = await network.invoke.recoverInboxCredentials({});
59
+ expect(recovery.records).toEqual([{ id: 'valid-sibling', credential }]);
60
+ expect(recovery.failed).toBe(2);
61
+ });
@@ -757,36 +757,3 @@ describe.skip('LearnCard Network Plugin', () => {
757
757
  });
758
758
  });
759
759
  });
760
-
761
- it('recovers valid deliveries despite decryption and validation failures on the page', async () => {
762
- const client = {
763
- ...getMockClient(),
764
- inbox: {
765
- getMyInboxDeliveries: {
766
- query: vi.fn().mockResolvedValue({
767
- records: [
768
- { id: 'bad-decrypt', credential: 'broken' },
769
- { id: 'bad-vc', credential: {} },
770
- { id: 'good', credential: { type: ['VerifiableCredential'] } },
771
- ],
772
- hasMore: true,
773
- cursor: 'good',
774
- }),
775
- },
776
- },
777
- };
778
- vi.mocked(getBrainClient).mockResolvedValue(client as any);
779
- const wallet = getMockLearnCard();
780
- wallet.id.keypair = () => ({ publicKey: 'key' });
781
- wallet.invoke.decryptDagJwe = vi.fn(async (credential: any) => {
782
- if (credential === 'broken') throw new Error('Cannot decrypt');
783
- return credential;
784
- });
785
- const plugin = await getLearnCardNetworkPlugin(wallet, 'https://network.example');
786
- expect(await plugin.methods?.recoverInboxCredentials(wallet, {})).toEqual({
787
- records: [{ id: 'good', credential: { type: ['VerifiableCredential'] } }],
788
- failed: 2,
789
- hasMore: true,
790
- cursor: 'good',
791
- });
792
- });