@learncard/network-plugin 3.2.3 → 4.0.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/README.md +18 -123
- package/dist/lcn-plugin.cjs.development.cjs +986 -79
- package/dist/lcn-plugin.cjs.development.cjs.map +3 -3
- package/dist/lcn-plugin.cjs.production.min.cjs +29 -29
- package/dist/lcn-plugin.cjs.production.min.cjs.map +3 -3
- package/dist/lcn-plugin.esm.js +992 -81
- package/dist/lcn-plugin.esm.js.map +3 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/test/holder-send.test.d.ts +2 -0
- package/dist/test/holder-send.test.d.ts.map +1 -0
- package/dist/types.d.ts +95 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/plugin.ts +604 -38
- package/src/test/campaigns.test.ts +87 -1
- package/src/test/holder-send.test.ts +735 -0
- package/src/test/index.test.ts +71 -0
- package/src/test/refresh-send.test.ts +149 -4
- package/src/types.ts +122 -10
package/src/test/index.test.ts
CHANGED
|
@@ -360,6 +360,45 @@ describe('connection prompt methods', () => {
|
|
|
360
360
|
});
|
|
361
361
|
});
|
|
362
362
|
|
|
363
|
+
describe('updateContract patch shapes', () => {
|
|
364
|
+
beforeEach(() => vi.clearAllMocks());
|
|
365
|
+
|
|
366
|
+
it.each(['read', 'write'] as const)(
|
|
367
|
+
'rejects overlapping %s patches without a server mutation',
|
|
368
|
+
async area => {
|
|
369
|
+
const mutate = vi.fn();
|
|
370
|
+
const client = {
|
|
371
|
+
...getMockClient(),
|
|
372
|
+
contracts: { updateConsentFlowContract: { mutate } },
|
|
373
|
+
};
|
|
374
|
+
vi.mocked(getBrainClient).mockResolvedValue(client as never);
|
|
375
|
+
const learnCard = getMockLearnCard();
|
|
376
|
+
const plugin = await getLearnCardNetworkPlugin(
|
|
377
|
+
learnCard,
|
|
378
|
+
'https://network.example/trpc'
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
await expect(
|
|
382
|
+
plugin.methods?.updateContract(learnCard, 'contract:1', {
|
|
383
|
+
[area]: {
|
|
384
|
+
credentials: { categories: { Transcript: { required: true } } },
|
|
385
|
+
personal: {},
|
|
386
|
+
},
|
|
387
|
+
contract: {
|
|
388
|
+
[area]: {
|
|
389
|
+
credentials: { categories: { Certificate: { required: true } } },
|
|
390
|
+
personal: {},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
})
|
|
394
|
+
).rejects.toThrow(
|
|
395
|
+
'Contract read/write updates must use either the top-level or nested contract shape'
|
|
396
|
+
);
|
|
397
|
+
expect(mutate).not.toHaveBeenCalled();
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
363
402
|
describe('credential refresh methods', () => {
|
|
364
403
|
beforeEach(() => {
|
|
365
404
|
vi.clearAllMocks();
|
|
@@ -613,6 +652,38 @@ describe('credential refresh methods', () => {
|
|
|
613
652
|
expect(client.utilities.getDid.query).not.toHaveBeenCalled();
|
|
614
653
|
});
|
|
615
654
|
|
|
655
|
+
it('sendBoost throws instead of signing when the boost is a display-only anchor', async () => {
|
|
656
|
+
const client = getMockClient();
|
|
657
|
+
client.boost.getBoost.query.mockResolvedValue({
|
|
658
|
+
boost: getUnsignedBoostTemplate({ displayOnlyAnchor: true, display: {} }),
|
|
659
|
+
});
|
|
660
|
+
vi.mocked(getBrainClient).mockResolvedValue(client as never);
|
|
661
|
+
const { learnCard } = getMockIssuingLearnCard();
|
|
662
|
+
const plugin = await getLearnCardNetworkPlugin(learnCard, 'https://network.example/trpc');
|
|
663
|
+
|
|
664
|
+
await expect(
|
|
665
|
+
plugin.methods?.sendBoost(learnCard, 'userb', 'did:example:boost:1')
|
|
666
|
+
).rejects.toThrow('display-only anchor');
|
|
667
|
+
|
|
668
|
+
expect(learnCard.invoke.issueCredential).not.toHaveBeenCalled();
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('sendBoost signs normally when the boost has its own display object (not a display-only anchor)', async () => {
|
|
672
|
+
const client = getMockClient();
|
|
673
|
+
client.boost.getBoost.query.mockResolvedValue({
|
|
674
|
+
boost: getUnsignedBoostTemplate({
|
|
675
|
+
display: { backgroundColor: '#fff', backgroundImage: '', displayType: 'card' },
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
vi.mocked(getBrainClient).mockResolvedValue(client as never);
|
|
679
|
+
const { learnCard } = getMockIssuingLearnCard();
|
|
680
|
+
const plugin = await getLearnCardNetworkPlugin(learnCard, 'https://network.example/trpc');
|
|
681
|
+
|
|
682
|
+
await plugin.methods?.sendBoost(learnCard, 'userb', 'did:example:boost:1');
|
|
683
|
+
|
|
684
|
+
expect(learnCard.invoke.issueCredential).toHaveBeenCalled();
|
|
685
|
+
});
|
|
686
|
+
|
|
616
687
|
it('sendBoost receipt preserves the exact credentialStatus descriptor from the signed VC', async () => {
|
|
617
688
|
const client = getMockClient();
|
|
618
689
|
vi.mocked(getBrainClient).mockResolvedValue(client as never);
|
|
@@ -43,6 +43,8 @@ vi.mock('@learncard/types', async importOriginal => ({
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
UnsignedVCValidator: {
|
|
46
|
+
parse: (await importOriginal<typeof import('@learncard/types')>()).UnsignedVCValidator
|
|
47
|
+
.parse,
|
|
46
48
|
spa: async (value: any) => ({
|
|
47
49
|
success: true,
|
|
48
50
|
data: typeof value === 'string' ? JSON.parse(value) : value,
|
|
@@ -174,6 +176,56 @@ const getPlugin = async (learnCard: any, client: any) => {
|
|
|
174
176
|
};
|
|
175
177
|
|
|
176
178
|
describe('unified send with refresh: true (managed branch)', () => {
|
|
179
|
+
it('includes revocation when an old server requires fallback suspension allocation', async () => {
|
|
180
|
+
const client = getMockClient();
|
|
181
|
+
const learnCard = getMockIssuingLearnCard();
|
|
182
|
+
const plugin = await getPlugin(learnCard, client);
|
|
183
|
+
await plugin.methods?.send(learnCard, {
|
|
184
|
+
type: 'boost',
|
|
185
|
+
recipient: 'userb',
|
|
186
|
+
templateUri: 'did:web:network.example:boost:1',
|
|
187
|
+
refresh: true,
|
|
188
|
+
statusPurposes: ['suspension'],
|
|
189
|
+
});
|
|
190
|
+
expect(client.boost.allocateCredentialStatus.mutate).toHaveBeenCalledWith({
|
|
191
|
+
statusPurposes: ['revocation', 'suspension'],
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it.each([false, true])(
|
|
196
|
+
'uses server-bound status without allocating again (array=%s)',
|
|
197
|
+
async arrayShape => {
|
|
198
|
+
const client = getMockClient();
|
|
199
|
+
const learnCard = getMockIssuingLearnCard();
|
|
200
|
+
const entry = SERVER_MANAGED_RESPONSE.refresh.credentialStatus;
|
|
201
|
+
client.boost.prepareRefreshableSend.mutate.mockResolvedValue(
|
|
202
|
+
Object.assign(
|
|
203
|
+
{},
|
|
204
|
+
{
|
|
205
|
+
...defaultPrepared,
|
|
206
|
+
statusEntries: [entry],
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
client.boost.getBoost.query.mockResolvedValue({
|
|
211
|
+
boost: getUnsignedBoostTemplate({ credentialStatus: arrayShape ? [entry] : entry }),
|
|
212
|
+
});
|
|
213
|
+
const plugin = await getPlugin(learnCard, client);
|
|
214
|
+
await plugin.methods?.send(learnCard, {
|
|
215
|
+
type: 'boost',
|
|
216
|
+
recipient: 'userb',
|
|
217
|
+
templateUri: 'did:web:network.example:boost:1',
|
|
218
|
+
refresh: true,
|
|
219
|
+
});
|
|
220
|
+
expect(client.boost.allocateCredentialStatus.mutate).not.toHaveBeenCalled();
|
|
221
|
+
expect(learnCard.invoke.issueCredential).toHaveBeenCalledWith(
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
credentialStatus: arrayShape ? [entry] : entry,
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
177
229
|
beforeEach(() => {
|
|
178
230
|
vi.clearAllMocks();
|
|
179
231
|
});
|
|
@@ -219,9 +271,10 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
219
271
|
// The server runs every guard, creates/reuses the boost and allocates the
|
|
220
272
|
// refresh in one call; the SDK never allocates client-side.
|
|
221
273
|
expect(client.boost.prepareRefreshableSend.mutate).toHaveBeenCalledExactlyOnceWith({
|
|
274
|
+
statusBinding: 'server',
|
|
222
275
|
recipient: 'userb',
|
|
223
276
|
templateUri: 'did:web:network.example:boost:1',
|
|
224
|
-
|
|
277
|
+
display: undefined,
|
|
225
278
|
});
|
|
226
279
|
expect(client.credentialRefresh.allocateCredentialRefresh.mutate).not.toHaveBeenCalled();
|
|
227
280
|
|
|
@@ -249,6 +302,47 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
249
302
|
expect(learnCard.invoke.createDagJwe).not.toHaveBeenCalled();
|
|
250
303
|
});
|
|
251
304
|
|
|
305
|
+
it('throws instead of signing when templateUri points at a display-only anchor', async () => {
|
|
306
|
+
const client = getMockClient();
|
|
307
|
+
const learnCard = getMockIssuingLearnCard();
|
|
308
|
+
client.boost.getBoost.query.mockResolvedValue({
|
|
309
|
+
boost: getUnsignedBoostTemplate({ displayOnlyAnchor: true, display: {} }),
|
|
310
|
+
});
|
|
311
|
+
const plugin = await getPlugin(learnCard, client);
|
|
312
|
+
|
|
313
|
+
await expect(
|
|
314
|
+
plugin.methods?.send(learnCard, {
|
|
315
|
+
type: 'boost',
|
|
316
|
+
recipient: 'userb',
|
|
317
|
+
templateUri: 'did:web:network.example:boost:1',
|
|
318
|
+
refresh: true,
|
|
319
|
+
})
|
|
320
|
+
).rejects.toThrow('display-only anchor');
|
|
321
|
+
|
|
322
|
+
expect(learnCard.invoke.issueCredential).not.toHaveBeenCalled();
|
|
323
|
+
expect(client.boost.prepareRefreshableSend.mutate).not.toHaveBeenCalled();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('signs normally from templateUri when the Boost has its own display object (not a display-only anchor)', async () => {
|
|
327
|
+
const client = getMockClient();
|
|
328
|
+
const learnCard = getMockIssuingLearnCard();
|
|
329
|
+
client.boost.getBoost.query.mockResolvedValue({
|
|
330
|
+
boost: getUnsignedBoostTemplate({
|
|
331
|
+
display: { backgroundColor: '#fff', backgroundImage: '', displayType: 'card' },
|
|
332
|
+
}),
|
|
333
|
+
});
|
|
334
|
+
const plugin = await getPlugin(learnCard, client);
|
|
335
|
+
|
|
336
|
+
await plugin.methods?.send(learnCard, {
|
|
337
|
+
type: 'boost',
|
|
338
|
+
recipient: 'userb',
|
|
339
|
+
templateUri: 'did:web:network.example:boost:1',
|
|
340
|
+
refresh: true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
expect(learnCard.invoke.issueCredential).toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
|
|
252
346
|
it('prepares the inline boost before signing and reuses its prepared URI for delivery', async () => {
|
|
253
347
|
const client = getMockClient();
|
|
254
348
|
const learnCard = getMockIssuingLearnCard();
|
|
@@ -272,8 +366,10 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
272
366
|
const forwarded = client.boost.send.mutate.mock.calls[0]?.[0] as any;
|
|
273
367
|
|
|
274
368
|
expect(client.boost.prepareRefreshableSend.mutate).toHaveBeenCalledExactlyOnceWith({
|
|
369
|
+
statusBinding: 'server',
|
|
275
370
|
recipient: 'userb',
|
|
276
|
-
|
|
371
|
+
templateUri: undefined,
|
|
372
|
+
display: undefined,
|
|
277
373
|
contractUri: 'contract:1',
|
|
278
374
|
credentialId: 'urn:uuid:inline-credential-id',
|
|
279
375
|
});
|
|
@@ -297,6 +393,34 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
297
393
|
expect(client.credentialRefresh.sendRefreshableCredential.mutate).not.toHaveBeenCalled();
|
|
298
394
|
});
|
|
299
395
|
|
|
396
|
+
it('preserves inline external status without requesting server binding or allocating indexes', async () => {
|
|
397
|
+
const client = getMockClient();
|
|
398
|
+
const learnCard = getMockIssuingLearnCard();
|
|
399
|
+
const plugin = await getPlugin(learnCard, client);
|
|
400
|
+
const credentialStatus = {
|
|
401
|
+
...SERVER_MANAGED_RESPONSE.refresh.credentialStatus,
|
|
402
|
+
id: 'https://provider.example/list#94567',
|
|
403
|
+
statusListCredential: 'https://provider.example/list',
|
|
404
|
+
};
|
|
405
|
+
const response = { ...SERVER_MANAGED_RESPONSE, statusManagement: 'external' as const };
|
|
406
|
+
client.boost.send.mutate.mockResolvedValue(response);
|
|
407
|
+
const result = await plugin.methods?.send(learnCard, {
|
|
408
|
+
type: 'boost',
|
|
409
|
+
recipient: 'userb',
|
|
410
|
+
template: { credential: getUnsignedBoostTemplate({ credentialStatus }) },
|
|
411
|
+
refresh: true,
|
|
412
|
+
statusPurposes: ['suspension'],
|
|
413
|
+
});
|
|
414
|
+
expect(client.boost.prepareRefreshableSend.mutate.mock.calls[0]?.[0]).not.toHaveProperty(
|
|
415
|
+
'statusBinding'
|
|
416
|
+
);
|
|
417
|
+
expect(client.boost.allocateCredentialStatus.mutate).not.toHaveBeenCalled();
|
|
418
|
+
expect(learnCard.invoke.issueCredential).toHaveBeenCalledWith(
|
|
419
|
+
expect.objectContaining({ credentialStatus })
|
|
420
|
+
);
|
|
421
|
+
expect(result).toEqual(response);
|
|
422
|
+
});
|
|
423
|
+
|
|
300
424
|
it('signs with the prepared credential ID, refresh service and holder DID', async () => {
|
|
301
425
|
const client = getMockClient();
|
|
302
426
|
const learnCard = getMockIssuingLearnCard();
|
|
@@ -420,7 +544,7 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
420
544
|
expect(forwarded.signedCredential.credentialSubject.id).toEqual(defaultPrepared.holderDid);
|
|
421
545
|
});
|
|
422
546
|
|
|
423
|
-
it('includes
|
|
547
|
+
it('includes public integration metadata but not claim data in preparation', async () => {
|
|
424
548
|
const client = getMockClient();
|
|
425
549
|
const learnCard = getMockIssuingLearnCard();
|
|
426
550
|
const plugin = await getPlugin(learnCard, client);
|
|
@@ -435,11 +559,13 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
435
559
|
});
|
|
436
560
|
expect(client.boost.prepareRefreshableSend.mutate).toHaveBeenCalledWith(
|
|
437
561
|
expect.objectContaining({
|
|
438
|
-
templateData: { grade: 'A' },
|
|
439
562
|
integrationId: 'integration-1',
|
|
440
563
|
idempotencyKey: 'award-1',
|
|
441
564
|
})
|
|
442
565
|
);
|
|
566
|
+
expect(client.boost.prepareRefreshableSend.mutate.mock.calls[0]?.[0]).not.toHaveProperty(
|
|
567
|
+
'templateData'
|
|
568
|
+
);
|
|
443
569
|
});
|
|
444
570
|
|
|
445
571
|
it('delegates unchanged to the server signing-authority path when local signing is unavailable', async () => {
|
|
@@ -463,6 +589,25 @@ describe('unified send with refresh: true (managed branch)', () => {
|
|
|
463
589
|
expect(learnCard.invoke.issueCredential).toBeUndefined();
|
|
464
590
|
});
|
|
465
591
|
|
|
592
|
+
it('forwards statusPurposes to the server signing-authority path when local signing is unavailable', async () => {
|
|
593
|
+
const client = getMockClient();
|
|
594
|
+
const learnCard = getMockIssuingLearnCard();
|
|
595
|
+
delete learnCard.invoke.issueCredential;
|
|
596
|
+
const plugin = await getPlugin(learnCard, client);
|
|
597
|
+
|
|
598
|
+
await plugin.methods?.send(learnCard, {
|
|
599
|
+
type: 'boost',
|
|
600
|
+
recipient: 'userb',
|
|
601
|
+
templateUri: 'did:web:network.example:boost:1',
|
|
602
|
+
refresh: true,
|
|
603
|
+
statusPurposes: ['suspension'],
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
expect(client.boost.send.mutate).toHaveBeenCalledWith(
|
|
607
|
+
expect.objectContaining({ statusPurposes: ['suspension'] })
|
|
608
|
+
);
|
|
609
|
+
});
|
|
610
|
+
|
|
466
611
|
it.each([
|
|
467
612
|
{},
|
|
468
613
|
{ templateUri: 'did:web:network.example:boost:1' },
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import type { CampaignAudienceResult } from '@learncard/types';
|
|
1
2
|
import type { SendContractRequest, ConsentFlowContractRequestForProfile } from '@learncard/types';
|
|
2
3
|
import type { LCNClient } from '@learncard/network-brain-client';
|
|
3
4
|
import type {
|
|
4
5
|
Campaign,
|
|
5
6
|
CampaignAudienceInput,
|
|
7
|
+
AnnouncementPreferences,
|
|
6
8
|
CampaignEvent,
|
|
7
9
|
CampaignStats,
|
|
10
|
+
CampaignDispatchPreview,
|
|
11
|
+
CampaignDispatchOptions,
|
|
8
12
|
CampaignStatus,
|
|
9
13
|
CreateCampaignInput,
|
|
10
14
|
UpdateCampaignInput,
|
|
@@ -14,6 +18,10 @@ import type {
|
|
|
14
18
|
PaginatedCampaigns,
|
|
15
19
|
PaginatedCampaignAudience,
|
|
16
20
|
PublishCredentialSharePreview,
|
|
21
|
+
ConsentFlowContractChanges,
|
|
22
|
+
ConsentFlowContractUpdateResult,
|
|
23
|
+
ConsentFlowContractVersion,
|
|
24
|
+
ConsentFlowConsentStatus,
|
|
17
25
|
} from '@learncard/types';
|
|
18
26
|
import {
|
|
19
27
|
LCNProfile,
|
|
@@ -80,7 +88,6 @@ import {
|
|
|
80
88
|
PaginatedConsentFlowDataForDid,
|
|
81
89
|
PaginatedContractCredentials,
|
|
82
90
|
AutoBoostConfig,
|
|
83
|
-
SendInput,
|
|
84
91
|
SendResponse,
|
|
85
92
|
AuthGrantType,
|
|
86
93
|
AuthGrantQuery,
|
|
@@ -155,7 +162,11 @@ import {
|
|
|
155
162
|
PublishCredentialRefreshInput,
|
|
156
163
|
PublishCredentialRefreshResult,
|
|
157
164
|
GetCredentialRefreshHistoryInput,
|
|
165
|
+
type GetCredentialRefreshReceiptInput,
|
|
166
|
+
type CredentialRefreshReceipt,
|
|
158
167
|
GetCredentialRefreshHistoryResult,
|
|
168
|
+
type SendBoostInput,
|
|
169
|
+
type CredentialRefreshUpdateReason,
|
|
159
170
|
} from '@learncard/types';
|
|
160
171
|
import { LearnCard, Plugin } from '@learncard/core';
|
|
161
172
|
import { ProofOptions } from '@learncard/didkit-plugin';
|
|
@@ -213,6 +224,32 @@ export type SendBoostResultFor<Options> = [EnableRefreshOf<Options>] extends [tr
|
|
|
213
224
|
? string | SendBoostRefreshResult
|
|
214
225
|
: string;
|
|
215
226
|
|
|
227
|
+
/**
|
|
228
|
+
* `send` input extended with the LC-2345 holder-mode `encryption` field. A local
|
|
229
|
+
* intersection rather than a `@learncard/types` edit, because today the field is
|
|
230
|
+
* validated and enforced entirely client-side (rejected for inbox recipients,
|
|
231
|
+
* `signedCredential`, and servers that don't return `recipientKeyIds`).
|
|
232
|
+
*/
|
|
233
|
+
export type SendBoostInputWithEncryption = SendBoostInput & {
|
|
234
|
+
/**
|
|
235
|
+
* `'holder'` requests student-only encryption: the SDK signs and encrypts
|
|
236
|
+
* locally so the server never receives plaintext. Requires `refresh: true` and a
|
|
237
|
+
* profile or DID recipient. Defaults to `'network'`.
|
|
238
|
+
*/
|
|
239
|
+
encryption?: 'network' | 'holder';
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/** Options for {@link LearnCardNetworkPluginMethods.publishHolderEncryptedRefreshable}. */
|
|
243
|
+
export type PublishHolderEncryptedRefreshableOptions = {
|
|
244
|
+
/** Applied locally with the same templating `send` uses; never sent to the server. */
|
|
245
|
+
templateData?: Record<string, unknown>;
|
|
246
|
+
/** Replaces server-inferred materiality: the server cannot compare encrypted content across versions. */
|
|
247
|
+
notify: boolean;
|
|
248
|
+
updateSummary?: string;
|
|
249
|
+
updateReason?: CredentialRefreshUpdateReason;
|
|
250
|
+
idempotencyKey?: string;
|
|
251
|
+
};
|
|
252
|
+
|
|
216
253
|
/** @group LearnCardNetwork Plugin */
|
|
217
254
|
export type LearnCardNetworkPluginDependentMethods = {
|
|
218
255
|
getDidAuthVp: (options?: ProofOptions) => Promise<VP | string>;
|
|
@@ -222,6 +259,8 @@ export type LearnCardNetworkPluginDependentMethods = {
|
|
|
222
259
|
) => Promise<VC>;
|
|
223
260
|
clearDidWebCache?: () => Promise<void>;
|
|
224
261
|
createDagJwe: (cleartext: any, recipients: string[]) => Promise<JWE>;
|
|
262
|
+
/** Encrypts only to explicit recipients, without adding the caller. */
|
|
263
|
+
createDagJweExact?: <T>(cleartext: T, recipients: string[]) => Promise<JWE>;
|
|
225
264
|
decryptDagJwe: (jwe: JWE, jwks: any[]) => Promise<any>;
|
|
226
265
|
resolveDid: (did: string) => Promise<DidDocument>;
|
|
227
266
|
};
|
|
@@ -611,6 +650,17 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
611
650
|
publishCredentialRefresh: (
|
|
612
651
|
input: PublishCredentialRefreshInput
|
|
613
652
|
) => Promise<PublishCredentialRefreshResult>;
|
|
653
|
+
/**
|
|
654
|
+
* Holder-mode (LC-2345) counterpart of `publishCredentialRefresh`: builds and
|
|
655
|
+
* signs a new version locally from `receipt`'s bindings, encrypts it exactly to
|
|
656
|
+
* the current holder + manager recipients, and sends only the ciphertext
|
|
657
|
+
* handoff. Rejects when local signing is unavailable.
|
|
658
|
+
*/
|
|
659
|
+
publishHolderEncryptedRefreshable: (
|
|
660
|
+
receipt: ManagedCredentialRefreshReceipt,
|
|
661
|
+
credential: UnsignedVC,
|
|
662
|
+
options: PublishHolderEncryptedRefreshableOptions
|
|
663
|
+
) => Promise<PublishCredentialRefreshResult>;
|
|
614
664
|
/**
|
|
615
665
|
* Cursor-paginated, metadata-only issuer audit history for a managed refresh.
|
|
616
666
|
* Never includes credential bodies or encrypted payloads.
|
|
@@ -618,6 +668,10 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
618
668
|
getCredentialRefreshHistory: (
|
|
619
669
|
input: GetCredentialRefreshHistoryInput
|
|
620
670
|
) => Promise<GetCredentialRefreshHistoryResult>;
|
|
671
|
+
/** Read the authenticated public binding for a pending managed delivery. */
|
|
672
|
+
getCredentialRefreshReceipt: (
|
|
673
|
+
input: GetCredentialRefreshReceiptInput
|
|
674
|
+
) => Promise<CredentialRefreshReceipt | null>;
|
|
621
675
|
revokeBoostRecipient: (
|
|
622
676
|
boostUri: string,
|
|
623
677
|
recipientProfileId: string,
|
|
@@ -668,10 +722,14 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
668
722
|
) => Promise<{ boostUri: string; challenge: string }>;
|
|
669
723
|
claimBoostWithLink: (boostUri: string, challenge: string) => Promise<string>;
|
|
670
724
|
|
|
671
|
-
send: (input:
|
|
725
|
+
send: (input: SendBoostInputWithEncryption) => Promise<SendResponse>;
|
|
672
726
|
|
|
673
727
|
createContract: (contract: {
|
|
674
728
|
contract: ConsentFlowContract;
|
|
729
|
+
reasonForAccessing?: string;
|
|
730
|
+
needsGuardianConsent?: boolean;
|
|
731
|
+
redirectUrl?: string;
|
|
732
|
+
frontDoorBoostUri?: string;
|
|
675
733
|
name: string;
|
|
676
734
|
subtitle?: string;
|
|
677
735
|
description?: string;
|
|
@@ -681,14 +739,42 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
681
739
|
recipients?: string[];
|
|
682
740
|
autoboosts?: AutoBoostConfig[];
|
|
683
741
|
}) => Promise<string>;
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
742
|
+
/**
|
|
743
|
+
* Updates a live request at the same URI, or previews it without saving.
|
|
744
|
+
* Scope maps merge with the current request. Set an entry to null to remove it;
|
|
745
|
+
* expiresAt: null clears expiry. AutoBoost and writer arrays replace their lists.
|
|
746
|
+
* Pass expectedVersion from the original draft to reject intervening publications.
|
|
747
|
+
* @example
|
|
748
|
+
* await learnCard.invoke.updateContract(uri, {
|
|
749
|
+
* read: { credentials: { categories: {
|
|
750
|
+
* Transcript: { required: true, reason: 'to calculate your GPA' },
|
|
751
|
+
* } } },
|
|
752
|
+
* }, { reason: 'We added transcripts so we can show your GPA on your résumé.' });
|
|
753
|
+
*/
|
|
754
|
+
updateContract: (
|
|
755
|
+
uri: string,
|
|
756
|
+
changes: ConsentFlowContractChanges & NonNullable<ConsentFlowContractChanges['contract']>,
|
|
757
|
+
options?: {
|
|
758
|
+
reason?: string;
|
|
759
|
+
reconsentDeadline?: string;
|
|
760
|
+
dryRun?: boolean;
|
|
761
|
+
expectedVersion?: number;
|
|
762
|
+
}
|
|
763
|
+
) => Promise<ConsentFlowContractUpdateResult>;
|
|
689
764
|
addContractRecipient: (contractUri: string, recipient: string) => Promise<boolean>;
|
|
690
765
|
removeContractRecipient: (contractUri: string, recipient: string) => Promise<boolean>;
|
|
766
|
+
/** Gets the current request, including its version.
|
|
767
|
+
* @example const contract = await learnCard.invoke.getContract(uri);
|
|
768
|
+
*/
|
|
691
769
|
getContract: (uri: string) => Promise<ConsentFlowContractDetails>;
|
|
770
|
+
/** Lists a request's saved versions, newest first.
|
|
771
|
+
* @example const versions = await learnCard.invoke.getContractVersions(uri);
|
|
772
|
+
*/
|
|
773
|
+
getContractVersions: (uri: string) => Promise<ConsentFlowContractVersion[]>;
|
|
774
|
+
/** Checks a user's access state and provides a review link when approval is needed.
|
|
775
|
+
* @example const { needsReconsent, reviewUrl } = await learnCard.invoke.getConsentStatus(uri, profileId);
|
|
776
|
+
*/
|
|
777
|
+
getConsentStatus: (uri: string, profileId: string) => Promise<ConsentFlowConsentStatus>;
|
|
692
778
|
getContracts: (
|
|
693
779
|
options?: Partial<PaginationOptionsType> & { query?: ConsentFlowContractQuery }
|
|
694
780
|
) => Promise<PaginatedConsentFlowContracts>;
|
|
@@ -736,6 +822,13 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
736
822
|
audienceVersion?: number;
|
|
737
823
|
}
|
|
738
824
|
) => Promise<boolean>;
|
|
825
|
+
/** Accepts the reviewed update using the user's selected data.
|
|
826
|
+
* @example await learnCard.invoke.acceptContractUpdate(termsUri, { version: 2, terms, audienceVersion: currentContract.audienceVersion });
|
|
827
|
+
*/
|
|
828
|
+
acceptContractUpdate: (
|
|
829
|
+
termsUri: string,
|
|
830
|
+
update: { version: number; terms: ConsentFlowTerms; audienceVersion?: number }
|
|
831
|
+
) => Promise<boolean>;
|
|
739
832
|
withdrawConsent: (uri: string) => Promise<boolean>;
|
|
740
833
|
getConsentFlowTransactions: (
|
|
741
834
|
uri: string,
|
|
@@ -1130,6 +1223,11 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
1130
1223
|
}>;
|
|
1131
1224
|
|
|
1132
1225
|
isAppStoreAdmin: () => Promise<boolean>;
|
|
1226
|
+
getAnnouncementPreferences: () => Promise<AnnouncementPreferences>;
|
|
1227
|
+
updateAnnouncementPreferences: (
|
|
1228
|
+
updates: Partial<AnnouncementPreferences>
|
|
1229
|
+
) => Promise<AnnouncementPreferences>;
|
|
1230
|
+
unsubscribeFromAnnouncements: (token: string) => Promise<boolean>;
|
|
1133
1231
|
campaignsIsAdmin: () => Promise<boolean>;
|
|
1134
1232
|
campaignsCreate: (input: CreateCampaignInput) => Promise<Campaign>;
|
|
1135
1233
|
campaignsUpdate: (input: UpdateCampaignInput) => Promise<Campaign>;
|
|
@@ -1137,12 +1235,26 @@ export type LearnCardNetworkPluginMethods = {
|
|
|
1137
1235
|
campaignsList: (input?: ListCampaignsInput) => Promise<PaginatedCampaigns>;
|
|
1138
1236
|
campaignsDelete: (campaignId: string) => Promise<boolean>;
|
|
1139
1237
|
campaignsSetStatus: (campaignId: string, status: CampaignStatus) => Promise<Campaign>;
|
|
1140
|
-
campaignsPreviewAudience: (input: CampaignAudienceInput) => Promise<
|
|
1141
|
-
campaignsAddAudience: (input: CampaignAudienceInput) => Promise<
|
|
1238
|
+
campaignsPreviewAudience: (input: CampaignAudienceInput) => Promise<CampaignAudienceResult>;
|
|
1239
|
+
campaignsAddAudience: (input: CampaignAudienceInput) => Promise<CampaignAudienceResult>;
|
|
1142
1240
|
campaignsPageAudience: (input: PageCampaignAudienceInput) => Promise<PaginatedCampaignAudience>;
|
|
1143
1241
|
campaignsGetStats: (campaignId: string) => Promise<CampaignStats>;
|
|
1242
|
+
/** Test selected channels only for the signed-in administrator, regardless of campaign status. */
|
|
1243
|
+
campaignsSendTestToMe: (campaignId: string) => Promise<boolean>;
|
|
1244
|
+
/** Start paced notification delivery for an eligible live campaign. */
|
|
1245
|
+
/** Preview eligible inbox recipients before confirming an audience notification send. */
|
|
1246
|
+
campaignsPreviewDispatch: (campaignId: string) => Promise<CampaignDispatchPreview>;
|
|
1247
|
+
/** Supply the preview token to reject stale or repeated confirmations. */
|
|
1248
|
+
campaignsDispatch: (
|
|
1249
|
+
campaignId: string,
|
|
1250
|
+
options?: CampaignDispatchOptions
|
|
1251
|
+
) => Promise<{ dispatchId: string }>;
|
|
1144
1252
|
getMyCampaigns: () => Promise<MyCampaign[]>;
|
|
1145
|
-
recordCampaignEvent: (
|
|
1253
|
+
recordCampaignEvent: (
|
|
1254
|
+
campaignId: string,
|
|
1255
|
+
event: CampaignEvent,
|
|
1256
|
+
testId?: string
|
|
1257
|
+
) => Promise<boolean>;
|
|
1146
1258
|
enrollInCampaign: (campaignId: string) => Promise<boolean>;
|
|
1147
1259
|
adminUpdateListingStatus: (listingId: string, status: AppListingStatus) => Promise<boolean>;
|
|
1148
1260
|
adminUpdatePromotionLevel: (
|