@learncard/network-plugin 3.0.1 → 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.
@@ -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
- });
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { SendContractRequest, ConsentFlowContractRequestForProfile } from '@learncard/types';
1
2
  import type { LCNClient } from '@learncard/network-brain-client';
2
3
  import {
3
4
  LCNProfile,
@@ -22,6 +23,10 @@ import {
22
23
  ShareLinkOwnerRecoveryOutput,
23
24
  ShareLinkPublicState,
24
25
  ShareLinkPublicContentView,
26
+ PutShareLinkAttachmentChunkInput,
27
+ DeleteShareLinkAttachmentChunksInput,
28
+ GetShareLinkAttachmentChunkInput,
29
+ ShareLinkAttachmentChunkOutput,
25
30
  AcknowledgeViewOutput,
26
31
  Boost,
27
32
  BoostQuery,
@@ -113,6 +118,9 @@ import {
113
118
  AppStoreListing,
114
119
  AppStoreListingCreateType,
115
120
  AppStoreListingUpdateType,
121
+ AppManifest,
122
+ AppManifestVersion,
123
+ AppManifestDiff,
116
124
  AppListingStatus,
117
125
  PromotionLevel,
118
126
  PaginatedAppStoreListings,
@@ -309,6 +317,13 @@ export type LearnCardNetworkPluginMethods = {
309
317
  * no caller-supplied owner/namespace/object authority is accepted.
310
318
  */
311
319
  createShareLink: (input: CreateShareLinkInput) => Promise<ShareLinkOwnerCommitOutput>;
320
+ putShareLinkAttachmentChunk: (input: PutShareLinkAttachmentChunkInput) => Promise<{ ok: true }>;
321
+ deleteShareLinkAttachmentChunks: (
322
+ input: DeleteShareLinkAttachmentChunksInput
323
+ ) => Promise<{ ok: boolean }>;
324
+ getShareLinkAttachmentChunk: (
325
+ input: GetShareLinkAttachmentChunkInput
326
+ ) => Promise<ShareLinkAttachmentChunkOutput>;
312
327
  updateShareLink: (input: UpdateShareLinkInput) => Promise<ShareLinkOwnerCommitOutput>;
313
328
  revokeShareLink: (input: {
314
329
  id: string;
@@ -639,6 +654,7 @@ export type LearnCardNetworkPluginMethods = {
639
654
  image?: string;
640
655
  expiresAt?: string;
641
656
  writers?: string[];
657
+ recipients?: string[];
642
658
  autoboosts?: AutoBoostConfig[];
643
659
  }) => Promise<string>;
644
660
  addAutoBoostsToContract: (
@@ -646,6 +662,8 @@ export type LearnCardNetworkPluginMethods = {
646
662
  autoboosts: AutoBoostConfig[]
647
663
  ) => Promise<boolean>;
648
664
  removeAutoBoostsFromContract: (contractUri: string, boostUris: string[]) => Promise<boolean>;
665
+ addContractRecipient: (contractUri: string, recipient: string) => Promise<boolean>;
666
+ removeContractRecipient: (contractUri: string, recipient: string) => Promise<boolean>;
649
667
  getContract: (uri: string) => Promise<ConsentFlowContractDetails>;
650
668
  getContracts: (
651
669
  options?: Partial<PaginationOptionsType> & { query?: ConsentFlowContractQuery }
@@ -676,6 +694,9 @@ export type LearnCardNetworkPluginMethods = {
676
694
  terms: ConsentFlowTerms;
677
695
  expiresAt?: string;
678
696
  oneTime?: boolean;
697
+ audienceVersion?: number;
698
+ /** Bind acceptance to the pending referral the learner reviewed. */
699
+ expectedRequestId?: string;
679
700
  },
680
701
  recipientToken?: string
681
702
  ) => Promise<{ termsUri: string; redirectUrl?: string }>;
@@ -688,6 +709,7 @@ export type LearnCardNetworkPluginMethods = {
688
709
  terms: ConsentFlowTerms;
689
710
  expiresAt?: string;
690
711
  oneTime?: boolean;
712
+ audienceVersion?: number;
691
713
  }
692
714
  ) => Promise<boolean>;
693
715
  withdrawConsent: (uri: string) => Promise<boolean>;
@@ -708,7 +730,8 @@ export type LearnCardNetworkPluginMethods = {
708
730
 
709
731
  syncCredentialsToContract: (
710
732
  termsUri: string,
711
- categories: Record<string, string[]>
733
+ categories: Record<string, string[]>,
734
+ audienceVersion?: number
712
735
  ) => Promise<boolean>;
713
736
 
714
737
  deleteCredentialFromAllContracts: (deletedUris: string[]) => Promise<{
@@ -716,6 +739,11 @@ export type LearnCardNetworkPluginMethods = {
716
739
  removedSharedUris: number;
717
740
  }>;
718
741
 
742
+ /** Sends one attributed request; identical pending retries are idempotent. */
743
+ sendContractRequest: (request: SendContractRequest) => Promise<boolean>;
744
+ /** Target-only denial preserves the request and its referral reference. */
745
+ denyContractRequest: (contractUri: string) => Promise<boolean>;
746
+
719
747
  sendAiInsightsContractRequest: (
720
748
  contractUri: string,
721
749
  targetProfileId: string,
@@ -728,31 +756,25 @@ export type LearnCardNetworkPluginMethods = {
728
756
  childProfileId?: string
729
757
  ) => Promise<boolean>;
730
758
 
731
- getContractSentRequests: (contractUri: string) => Promise<
732
- {
733
- profile: LCNProfile;
734
- status: 'pending' | 'accepted' | 'denied' | null;
735
- readStatus?: 'unseen' | 'seen' | null;
736
- }[]
737
- >;
759
+ getContractSentRequests: (
760
+ contractUri: string
761
+ ) => Promise<ConsentFlowContractRequestForProfile[]>;
738
762
 
739
763
  getRequestStatusForProfile: (
740
764
  targetProfileId: string,
741
- contractId?: string | undefined,
742
- contractUri?: string | undefined
743
- ) => Promise<{
744
- profile: LCNProfile;
745
- status: 'pending' | 'accepted' | 'denied' | null;
746
- readStatus?: 'unseen' | 'seen' | null;
747
- } | null>;
765
+ contractId?: string,
766
+ contractUri?: string
767
+ ) => Promise<ConsentFlowContractRequestForProfile | null>;
748
768
 
749
769
  getAllContractRequestsForProfile: (targetProfileId: string) => Promise<
750
- {
751
- contract: ConsentFlowContract & { uri: string };
752
- profile: LCNProfile;
753
- status: 'pending' | 'accepted' | 'denied' | null;
754
- readStatus?: 'unseen' | 'seen' | null;
755
- }[]
770
+ (ConsentFlowContractRequestForProfile & {
771
+ contract: ConsentFlowContract & {
772
+ uri: string;
773
+ name?: string;
774
+ image?: string;
775
+ description?: string;
776
+ };
777
+ })[]
756
778
  >;
757
779
 
758
780
  getSharedInsightsRequestsForProfile: (targetProfileId: string) => Promise<
@@ -993,12 +1015,63 @@ export type LearnCardNetworkPluginMethods = {
993
1015
  deleteAppStoreListing: (listingId: string) => Promise<boolean>;
994
1016
  submitAppStoreListingForReview: (listingId: string) => Promise<boolean>;
995
1017
  unsubmitAppStoreListing: (listingId: string) => Promise<boolean>;
1018
+ /** Sends held changes to a live app for review; the live listing is unchanged until approval. */
1019
+ submitAppStoreListingUpdate: (listingId: string) => Promise<boolean>;
1020
+ withdrawAppStoreListingUpdate: (listingId: string) => Promise<boolean>;
1021
+ discardAppStoreListingUpdate: (listingId: string) => Promise<boolean>;
996
1022
  getListingsForIntegration: (
997
1023
  integrationId: string,
998
1024
  options?: Partial<PaginationOptionsType>
999
1025
  ) => Promise<PaginatedAppStoreListings>;
1000
1026
  countListingsForIntegration: (integrationId: string) => Promise<number>;
1001
1027
 
1028
+ submitAppManifest: (
1029
+ integrationId: string,
1030
+ manifest: AppManifest
1031
+ ) => Promise<{
1032
+ version: number;
1033
+ manifestHash: string;
1034
+ diff: AppManifestDiff | null;
1035
+ noop: boolean;
1036
+ }>;
1037
+ getManifestVersions: (
1038
+ integrationId: string,
1039
+ options?: { limit?: number; cursor?: string }
1040
+ ) => Promise<{
1041
+ hasMore: boolean;
1042
+ cursor?: string;
1043
+ records: Pick<
1044
+ AppManifestVersion,
1045
+ 'id' | 'version' | 'manifestHash' | 'status' | 'createdAt' | 'activatedAt'
1046
+ >[];
1047
+ }>;
1048
+ getManifestVersion: (
1049
+ integrationId: string,
1050
+ version: number
1051
+ ) => Promise<AppManifestVersion | undefined>;
1052
+ getManifestDiff: (
1053
+ integrationId: string,
1054
+ toVersion: number,
1055
+ fromVersion?: number
1056
+ ) => Promise<AppManifestDiff>;
1057
+ applyManifestVersion: (
1058
+ integrationId: string,
1059
+ version: number,
1060
+ listingId?: string
1061
+ ) => Promise<{
1062
+ applied: boolean;
1063
+ /** True when the listing is live and the new version is waiting for review. */
1064
+ pendingReview?: boolean;
1065
+ version: number;
1066
+ reconciled: {
1067
+ templatesUpserted: number;
1068
+ templatesSkipped: number;
1069
+ contractsUpserted: number;
1070
+ contractsSkipped: number;
1071
+ signingAuthorityEnsured: boolean;
1072
+ };
1073
+ }>;
1074
+
1002
1075
  browseAppStore: (options?: {
1003
1076
  limit?: number;
1004
1077
  cursor?: string;
@@ -1042,7 +1115,9 @@ export type LearnCardNetworkPluginMethods = {
1042
1115
  limit?: number;
1043
1116
  cursor?: string;
1044
1117
  status?: AppListingStatus;
1118
+ pendingUpdatesOnly?: boolean;
1045
1119
  }) => Promise<PaginatedAppStoreListings>;
1120
+ adminReviewListingUpdate: (listingId: string, approve: boolean) => Promise<boolean>;
1046
1121
 
1047
1122
  // App Store Boost Management
1048
1123
  addBoostToApp: (listingId: string, boostUri: string, templateAlias: string) => Promise<boolean>;