@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.
- package/dist/lcn-plugin.cjs.development.cjs +452 -50
- package/dist/lcn-plugin.cjs.development.cjs.map +3 -3
- package/dist/lcn-plugin.cjs.production.min.cjs +7 -7
- package/dist/lcn-plugin.cjs.production.min.cjs.map +3 -3
- package/dist/lcn-plugin.esm.js +453 -50
- package/dist/lcn-plugin.esm.js.map +3 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/test/inbox-finalize-crypto.test.d.ts +2 -0
- package/dist/test/inbox-finalize-crypto.test.d.ts.map +1 -0
- package/dist/types.d.ts +63 -17
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/plugin.ts +136 -9
- package/src/test/inbox-finalize-crypto.test.ts +61 -0
- package/src/test/index.test.ts +0 -33
- package/src/types.ts +96 -21
|
@@ -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
|
+
});
|
package/src/test/index.test.ts
CHANGED
|
@@ -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: (
|
|
732
|
-
|
|
733
|
-
|
|
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
|
|
742
|
-
contractUri?: string
|
|
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 & {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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>;
|