@learncard/types 5.20.0 → 5.21.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/auth.d.ts +35 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/credential-refresh.d.ts +53 -0
- package/dist/credential-refresh.d.ts.map +1 -1
- package/dist/lcn.d.ts +5225 -2489
- package/dist/lcn.d.ts.map +1 -1
- package/dist/types.cjs.development.cjs +377 -190
- package/dist/types.cjs.development.cjs.map +3 -3
- package/dist/types.cjs.production.min.cjs +23 -23
- package/dist/types.cjs.production.min.cjs.map +4 -4
- package/dist/types.esm.js +1241 -1054
- package/dist/types.esm.js.map +3 -3
- package/package.json +1 -1
- package/src/auth.ts +55 -4
- package/src/credential-refresh.ts +35 -1
- package/src/lcn.ts +246 -3
package/package.json
CHANGED
package/src/auth.ts
CHANGED
|
@@ -101,6 +101,24 @@ export interface PhoneVerificationHandle {
|
|
|
101
101
|
_internal?: unknown;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/** Sign-in features available to the UI, independent of the provider name. */
|
|
105
|
+
export interface SignInCapabilities {
|
|
106
|
+
readonly emailLink: boolean;
|
|
107
|
+
/** Email OTP is verified by the app's server, then exchanged via customToken. */
|
|
108
|
+
readonly emailOtp: boolean;
|
|
109
|
+
readonly phoneOtp: boolean;
|
|
110
|
+
readonly google: boolean;
|
|
111
|
+
readonly apple: boolean;
|
|
112
|
+
readonly social: boolean;
|
|
113
|
+
readonly customToken: boolean;
|
|
114
|
+
readonly deleteAccount: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SocialSignInOptions {
|
|
118
|
+
/** Restore a session using the existing re-authentication interaction. */
|
|
119
|
+
intent?: 'signIn' | 'reauthenticate';
|
|
120
|
+
}
|
|
121
|
+
|
|
104
122
|
/**
|
|
105
123
|
* Abstract sign-in adapter interface.
|
|
106
124
|
*
|
|
@@ -118,6 +136,7 @@ export interface PhoneVerificationHandle {
|
|
|
118
136
|
*/
|
|
119
137
|
export interface SignInAdapter {
|
|
120
138
|
readonly providerType: AuthProviderType;
|
|
139
|
+
readonly capabilities: SignInCapabilities;
|
|
121
140
|
|
|
122
141
|
// --- Auth state ---
|
|
123
142
|
|
|
@@ -140,6 +159,9 @@ export interface SignInAdapter {
|
|
|
140
159
|
|
|
141
160
|
isEmailLink(link: string): boolean;
|
|
142
161
|
|
|
162
|
+
/** Authoritative provider check; isEmailLink remains a synchronous hint. */
|
|
163
|
+
validateEmailLink(link: string): Promise<boolean>;
|
|
164
|
+
|
|
143
165
|
// --- Phone OTP ---
|
|
144
166
|
|
|
145
167
|
/**
|
|
@@ -153,6 +175,17 @@ export interface SignInAdapter {
|
|
|
153
175
|
*/
|
|
154
176
|
confirmPhoneOtp(handle: PhoneVerificationHandle, code: string | number): Promise<AuthUser>;
|
|
155
177
|
|
|
178
|
+
/** Preferred API: confirm the most recent request without exposing SDK state. */
|
|
179
|
+
confirmPhoneOtp(code: string | number): Promise<AuthUser>;
|
|
180
|
+
|
|
181
|
+
/** Fires when a code is ready for entry, on either platform. */
|
|
182
|
+
onPhoneCodeSent(callback: () => void): () => void;
|
|
183
|
+
|
|
184
|
+
/** Auto-retrieved code; call confirmPhoneOtp before starting key derivation. */
|
|
185
|
+
onPhoneVerificationCompleted(callback: (code: string | undefined) => void): () => void;
|
|
186
|
+
|
|
187
|
+
onPhoneVerificationFailed(callback: (error: unknown) => void): () => void;
|
|
188
|
+
|
|
156
189
|
/**
|
|
157
190
|
* Confirm a phone OTP using a native verificationId (Capacitor auto-verify
|
|
158
191
|
* path). Falls back to `confirmPhoneOtp` when not implemented.
|
|
@@ -161,9 +194,9 @@ export interface SignInAdapter {
|
|
|
161
194
|
|
|
162
195
|
// --- OAuth ---
|
|
163
196
|
|
|
164
|
-
signInWithGoogle(): Promise<AuthUser>;
|
|
197
|
+
signInWithGoogle(options?: SocialSignInOptions): Promise<AuthUser>;
|
|
165
198
|
|
|
166
|
-
signInWithApple(): Promise<AuthUser>;
|
|
199
|
+
signInWithApple(options?: SocialSignInOptions): Promise<AuthUser>;
|
|
167
200
|
|
|
168
201
|
/** Check for a pending OAuth redirect result (e.g. Apple on web). */
|
|
169
202
|
checkRedirectResult?(): Promise<AuthUser | null>;
|
|
@@ -179,6 +212,15 @@ export interface SignInAdapter {
|
|
|
179
212
|
|
|
180
213
|
deleteAccount(): Promise<void>;
|
|
181
214
|
|
|
215
|
+
/** Update the signed-in user's display profile, when supported. */
|
|
216
|
+
updateProfile?(profile: {
|
|
217
|
+
displayName?: string | null;
|
|
218
|
+
photoUrl?: string | null;
|
|
219
|
+
}): Promise<void>;
|
|
220
|
+
|
|
221
|
+
/** Limit the auth session to this browser tab on a public computer. */
|
|
222
|
+
setSessionPersistence?(sessionOnly: boolean): Promise<void>;
|
|
223
|
+
|
|
182
224
|
signOut(): Promise<void>;
|
|
183
225
|
|
|
184
226
|
// --- Cleanup ---
|
|
@@ -346,7 +388,13 @@ export interface KeyDerivationStrategy<
|
|
|
346
388
|
fetchServerKeyStatus(token: string, providerType: AuthProviderType): Promise<ServerKeyStatus>;
|
|
347
389
|
|
|
348
390
|
/** Store the remote key component on the server */
|
|
349
|
-
storeAuthShare(
|
|
391
|
+
storeAuthShare(
|
|
392
|
+
token: string,
|
|
393
|
+
providerType: AuthProviderType,
|
|
394
|
+
remoteKey: string,
|
|
395
|
+
did: string,
|
|
396
|
+
didAuthVp?: string
|
|
397
|
+
): Promise<void>;
|
|
350
398
|
|
|
351
399
|
/** Mark migration complete on the server (optional — only needed for migration-capable strategies) */
|
|
352
400
|
markMigrated?(token: string, providerType: AuthProviderType, didAuthVp?: string): Promise<void>;
|
|
@@ -374,7 +422,10 @@ export interface KeyDerivationStrategy<
|
|
|
374
422
|
}): Promise<TRecoverySetupResult>;
|
|
375
423
|
|
|
376
424
|
/** Get configured recovery methods for the authenticated user */
|
|
377
|
-
getAvailableRecoveryMethods?(
|
|
425
|
+
getAvailableRecoveryMethods?(
|
|
426
|
+
token: string,
|
|
427
|
+
providerType: AuthProviderType
|
|
428
|
+
): Promise<RecoveryMethodInfo[]>;
|
|
378
429
|
|
|
379
430
|
// --- Contact method management ---
|
|
380
431
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
2
|
|
|
3
|
-
import { UnsignedVCValidator, VCValidator } from './vc';
|
|
3
|
+
import { CredentialStatusValidator, UnsignedVCValidator, VCValidator } from './vc';
|
|
4
4
|
import { JWEValidator } from './crypto';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -73,6 +73,40 @@ export type AllocateCredentialRefreshResult = z.infer<
|
|
|
73
73
|
typeof AllocateCredentialRefreshResultValidator
|
|
74
74
|
>;
|
|
75
75
|
|
|
76
|
+
// --- Managed issuance receipt (returned to the issuer after a refreshable send) ---
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Issuance metadata returned to the authenticated issuer after a refreshable send
|
|
80
|
+
* (unified send with `refresh: true`, or `sendBoost` with `enableRefresh: true`).
|
|
81
|
+
*
|
|
82
|
+
* Populated from the actual signed version 1 credential — never from the template or
|
|
83
|
+
* allocation alone. This is issuance metadata only: it must not contain credential
|
|
84
|
+
* claims, subject bodies, plaintext VCs, or JWEs. The issuer retains its own
|
|
85
|
+
* template/claims alongside this receipt, and preserves the exact `credentialStatus`
|
|
86
|
+
* descriptor when publishing an update (no new allocation).
|
|
87
|
+
*/
|
|
88
|
+
export const ManagedCredentialRefreshReceiptValidator = z
|
|
89
|
+
.object({
|
|
90
|
+
refreshId: z.string().min(1),
|
|
91
|
+
refreshService: ManagedCredentialRefreshServiceValidator,
|
|
92
|
+
credentialId: z.string().min(1),
|
|
93
|
+
issuerDid: z.string().min(1),
|
|
94
|
+
holderDid: z.string().min(1),
|
|
95
|
+
credentialStatus: CredentialStatusValidator.or(
|
|
96
|
+
CredentialStatusValidator.array()
|
|
97
|
+
).optional(),
|
|
98
|
+
})
|
|
99
|
+
.strip();
|
|
100
|
+
export type ManagedCredentialRefreshReceipt = z.infer<
|
|
101
|
+
typeof ManagedCredentialRefreshReceiptValidator
|
|
102
|
+
>;
|
|
103
|
+
|
|
104
|
+
/** Allocation metadata for deferred Inbox issuance. No holder is invented before claim. */
|
|
105
|
+
export const InboxCredentialRefreshReceiptValidator = ManagedCredentialRefreshReceiptValidator.omit(
|
|
106
|
+
{ holderDid: true }
|
|
107
|
+
).extend({ holderDid: z.string().min(1).optional() });
|
|
108
|
+
export type InboxCredentialRefreshReceipt = z.infer<typeof InboxCredentialRefreshReceiptValidator>;
|
|
109
|
+
|
|
76
110
|
// --- Publication ------------------------------------------------------------
|
|
77
111
|
|
|
78
112
|
export const CredentialRefreshSigningModeValidator = z.enum(['issuer-signed', 'signing-authority']);
|
package/src/lcn.ts
CHANGED
|
@@ -4,6 +4,11 @@ import { z } from 'zod/v4';
|
|
|
4
4
|
import { PaginationResponseValidator } from './mongo';
|
|
5
5
|
import { StringQuery } from './queries';
|
|
6
6
|
import { UnsignedVCValidator, VCValidator, VPValidator } from './vc';
|
|
7
|
+
import {
|
|
8
|
+
ManagedCredentialRefreshReceiptValidator,
|
|
9
|
+
InboxCredentialRefreshReceiptValidator,
|
|
10
|
+
ManagedCredentialRefreshServiceValidator,
|
|
11
|
+
} from './credential-refresh';
|
|
7
12
|
|
|
8
13
|
export const LCNProfileDisplayValidator = z.object({
|
|
9
14
|
backgroundColor: z.string().optional(),
|
|
@@ -466,7 +471,7 @@ export const AutoBoostConfigValidator = z.object({
|
|
|
466
471
|
});
|
|
467
472
|
export type AutoBoostConfig = z.infer<typeof AutoBoostConfigValidator>;
|
|
468
473
|
|
|
469
|
-
const SendBoostTemplateValidator = BoostValidator.partial()
|
|
474
|
+
export const SendBoostTemplateValidator = BoostValidator.partial()
|
|
470
475
|
.omit({ uri: true, claimPermissions: true })
|
|
471
476
|
.extend({
|
|
472
477
|
credential: VCValidator.or(UnsignedVCValidator),
|
|
@@ -538,6 +543,20 @@ export const SendBoostInputValidator = z
|
|
|
538
543
|
),
|
|
539
544
|
templateData: z.record(z.string(), z.unknown()).optional(),
|
|
540
545
|
integrationId: z.string().optional().describe('Integration ID for activity tracking'),
|
|
546
|
+
refresh: z
|
|
547
|
+
.boolean()
|
|
548
|
+
.optional()
|
|
549
|
+
.describe(
|
|
550
|
+
'Request managed credential refresh for this send. Profile/DID recipients use immediate issuance; email/phone recipients use deferred Universal Inbox signing and bind the holder at claim.'
|
|
551
|
+
),
|
|
552
|
+
idempotencyKey: z
|
|
553
|
+
.string()
|
|
554
|
+
.min(1)
|
|
555
|
+
.max(200)
|
|
556
|
+
.optional()
|
|
557
|
+
.describe(
|
|
558
|
+
'Caller-chosen key that makes a managed refresh send (refresh: true) safe to retry as a whole: retries with the same key reuse the same boost, refresh allocation and result. Reusing a key for a different request is rejected. With signedCredential, requires prior tRPC prepareRefreshableSend; direct REST callers omit the key and retry the exact signed credential and templateUri.'
|
|
559
|
+
),
|
|
541
560
|
})
|
|
542
561
|
.refine(data => data.templateUri || data.template || data.signedCredential, {
|
|
543
562
|
message: 'Either templateUri, template, or signedCredential must be provided.',
|
|
@@ -554,11 +573,16 @@ export const SendBoostInputValidator = z
|
|
|
554
573
|
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
555
574
|
path: ['options', 'guardianEmail'],
|
|
556
575
|
}
|
|
557
|
-
)
|
|
576
|
+
)
|
|
577
|
+
.refine(data => !data.idempotencyKey || data.refresh === true, {
|
|
578
|
+
message: 'idempotencyKey is only supported with refresh: true.',
|
|
579
|
+
path: ['idempotencyKey'],
|
|
580
|
+
});
|
|
558
581
|
export type SendBoostInput = z.infer<typeof SendBoostInputValidator>;
|
|
559
582
|
|
|
560
583
|
// Inbox-specific response fields (only present when sent via email/phone)
|
|
561
584
|
export const SendInboxResponseValidator = z.object({
|
|
585
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
562
586
|
issuanceId: z.string(),
|
|
563
587
|
status: z.enum(['PENDING', 'ISSUED', 'EXPIRED', 'DELIVERED', 'CLAIMED']),
|
|
564
588
|
claimUrl: z.string().url().optional().describe('Present when suppressDelivery=true'),
|
|
@@ -576,9 +600,41 @@ export const SendBoostResponseValidator = z.object({
|
|
|
576
600
|
inbox: SendInboxResponseValidator.optional().describe(
|
|
577
601
|
'Present when sent via email/phone (Universal Inbox)'
|
|
578
602
|
),
|
|
603
|
+
refresh: ManagedCredentialRefreshReceiptValidator.optional().describe(
|
|
604
|
+
'Present when managed refresh was requested: issuance metadata the issuer keeps to publish future updates'
|
|
605
|
+
),
|
|
579
606
|
});
|
|
580
607
|
export type SendBoostResponse = z.infer<typeof SendBoostResponseValidator>;
|
|
581
608
|
|
|
609
|
+
export const PrepareRefreshableSendInputValidator = z
|
|
610
|
+
.object({
|
|
611
|
+
recipient: z.string(),
|
|
612
|
+
templateUri: z.string().optional(),
|
|
613
|
+
template: SendBoostTemplateValidator.optional(),
|
|
614
|
+
contractUri: z.string().optional(),
|
|
615
|
+
templateData: z.record(z.string(), z.unknown()).optional(),
|
|
616
|
+
integrationId: z.string().optional(),
|
|
617
|
+
/** Credential ID to allocate for; generated server-side when omitted. */
|
|
618
|
+
credentialId: z.string().min(1).optional(),
|
|
619
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
620
|
+
})
|
|
621
|
+
.refine(data => Boolean(data.templateUri) !== Boolean(data.template), {
|
|
622
|
+
message: 'Provide exactly one of templateUri or template.',
|
|
623
|
+
});
|
|
624
|
+
export type PrepareRefreshableSendInput = z.infer<typeof PrepareRefreshableSendInputValidator>;
|
|
625
|
+
|
|
626
|
+
export const PrepareRefreshableSendResultValidator = z.object({
|
|
627
|
+
boostUri: z.string(),
|
|
628
|
+
credentialId: z.string(),
|
|
629
|
+
refreshId: z.string(),
|
|
630
|
+
refreshService: ManagedCredentialRefreshServiceValidator,
|
|
631
|
+
/** The DID to use as credentialSubject.id (recipient DID, or the profile's did:web). */
|
|
632
|
+
holderDid: z.string(),
|
|
633
|
+
/** Present when this idempotencyKey already completed: return it without signing. */
|
|
634
|
+
completed: SendBoostResponseValidator.optional(),
|
|
635
|
+
});
|
|
636
|
+
export type PrepareRefreshableSendResult = z.infer<typeof PrepareRefreshableSendResultValidator>;
|
|
637
|
+
|
|
582
638
|
// Plugin-level discriminated union (for extensibility)
|
|
583
639
|
export const SendInputValidator = z.discriminatedUnion('type', [SendBoostInputValidator]);
|
|
584
640
|
export type SendInput = z.infer<typeof SendInputValidator>;
|
|
@@ -1074,6 +1130,7 @@ export const LCNNotificationValidator = z.object({
|
|
|
1074
1130
|
export type LCNNotification = z.infer<typeof LCNNotificationValidator>;
|
|
1075
1131
|
|
|
1076
1132
|
export const AUTH_GRANT_AUDIENCE_DOMAIN_PREFIX = 'auth-grant:';
|
|
1133
|
+
export const ACT_AS_HEADER = 'X-LearnCard-Act-As';
|
|
1077
1134
|
|
|
1078
1135
|
export const AuthGrantValidator = z.object({
|
|
1079
1136
|
id: z.string(),
|
|
@@ -1091,6 +1148,7 @@ export const AuthGrantValidator = z.object({
|
|
|
1091
1148
|
},
|
|
1092
1149
|
}),
|
|
1093
1150
|
scope: z.string(),
|
|
1151
|
+
actAs: z.string().optional(),
|
|
1094
1152
|
createdAt: z.iso.datetime({ error: 'createdAt must be a valid ISO 8601 datetime string' }),
|
|
1095
1153
|
expiresAt: z.iso
|
|
1096
1154
|
.datetime({ error: 'expiresAt must be a valid ISO 8601 datetime string' })
|
|
@@ -1217,6 +1275,8 @@ export type CreateContactMethodSessionResponseType = z.infer<
|
|
|
1217
1275
|
|
|
1218
1276
|
// Inbox Credentials
|
|
1219
1277
|
export const InboxCredentialValidator = z.object({
|
|
1278
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
1279
|
+
refreshId: z.string().optional(),
|
|
1220
1280
|
id: z.string(),
|
|
1221
1281
|
credential: z.string().optional(),
|
|
1222
1282
|
isSigned: z.boolean(),
|
|
@@ -1296,6 +1356,13 @@ export const IssueInboxCredentialValidator = z
|
|
|
1296
1356
|
'URI of a boost template to use for issuance. The boost credential will be resolved and used. Mutually exclusive with credential field.'
|
|
1297
1357
|
),
|
|
1298
1358
|
|
|
1359
|
+
refresh: z
|
|
1360
|
+
.boolean()
|
|
1361
|
+
.optional()
|
|
1362
|
+
.describe(
|
|
1363
|
+
'Allocate managed refresh before signing. Requires unsigned content and a registered signing authority; binds the holder on claim.'
|
|
1364
|
+
),
|
|
1365
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
1299
1366
|
// === OPTIONAL FEATURES ===
|
|
1300
1367
|
// Add major, distinct features at the top level.
|
|
1301
1368
|
//consentRequest: ConsentRequestValidator.optional(),
|
|
@@ -1304,6 +1371,13 @@ export const IssueInboxCredentialValidator = z
|
|
|
1304
1371
|
// HOW should this issuance be handled?
|
|
1305
1372
|
configuration: z
|
|
1306
1373
|
.object({
|
|
1374
|
+
guardianEmail: z
|
|
1375
|
+
.string()
|
|
1376
|
+
.email()
|
|
1377
|
+
.optional()
|
|
1378
|
+
.describe(
|
|
1379
|
+
'Require approval from this guardian before the recipient can claim. Must differ from the recipient email.'
|
|
1380
|
+
),
|
|
1307
1381
|
signingAuthority: IssueInboxSigningAuthorityValidator.optional().describe(
|
|
1308
1382
|
'The signing authority to use for the credential. If not provided, the users default signing authority will be used if the credential is not signed.'
|
|
1309
1383
|
),
|
|
@@ -1410,14 +1484,28 @@ export const IssueInboxCredentialValidator = z
|
|
|
1410
1484
|
'Configuration for the credential issuance. If not provided, the default configuration will be used.'
|
|
1411
1485
|
),
|
|
1412
1486
|
})
|
|
1487
|
+
.refine(data => !data.idempotencyKey || data.refresh === true, {
|
|
1488
|
+
message: 'idempotencyKey requires refresh: true.',
|
|
1489
|
+
})
|
|
1413
1490
|
.refine(data => data.credential || data.templateUri, {
|
|
1414
1491
|
message: 'Either credential or templateUri must be provided.',
|
|
1415
1492
|
path: ['credential'],
|
|
1416
|
-
})
|
|
1493
|
+
})
|
|
1494
|
+
.refine(
|
|
1495
|
+
data =>
|
|
1496
|
+
!data.configuration?.guardianEmail ||
|
|
1497
|
+
data.recipient.type !== 'email' ||
|
|
1498
|
+
data.configuration.guardianEmail.toLowerCase() !== data.recipient.value.toLowerCase(),
|
|
1499
|
+
{
|
|
1500
|
+
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
1501
|
+
path: ['configuration', 'guardianEmail'],
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1417
1504
|
|
|
1418
1505
|
export type IssueInboxCredentialType = z.infer<typeof IssueInboxCredentialValidator>;
|
|
1419
1506
|
|
|
1420
1507
|
export const IssueInboxCredentialResponseValidator = z.object({
|
|
1508
|
+
refresh: InboxCredentialRefreshReceiptValidator.optional(),
|
|
1421
1509
|
issuanceId: z.string(),
|
|
1422
1510
|
status: LCNInboxStatusEnumValidator,
|
|
1423
1511
|
recipient: ContactMethodQueryValidator,
|
|
@@ -1425,6 +1513,160 @@ export const IssueInboxCredentialResponseValidator = z.object({
|
|
|
1425
1513
|
recipientDid: z.string().optional(),
|
|
1426
1514
|
});
|
|
1427
1515
|
|
|
1516
|
+
// Do not apply delivery defaults before merging: omitted per-item fields inherit batch defaults.
|
|
1517
|
+
const InboxBatchConfigurationValidator = IssueInboxCredentialValidator.shape.configuration
|
|
1518
|
+
.unwrap()
|
|
1519
|
+
.extend({
|
|
1520
|
+
refresh: IssueInboxCredentialValidator.shape.refresh.describe(
|
|
1521
|
+
'Enable managed refresh by default. An item configuration.refresh overrides this value, including false.'
|
|
1522
|
+
),
|
|
1523
|
+
delivery: IssueInboxCredentialValidator.shape.configuration
|
|
1524
|
+
.unwrap()
|
|
1525
|
+
.shape.delivery.unwrap()
|
|
1526
|
+
.extend({ suppress: z.boolean().optional() })
|
|
1527
|
+
.optional(),
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
const InboxBatchItemConfigurationValidator = InboxBatchConfigurationValidator.extend({
|
|
1531
|
+
guardianEmail: InboxBatchConfigurationValidator.shape.guardianEmail
|
|
1532
|
+
.nullable()
|
|
1533
|
+
.describe(
|
|
1534
|
+
'Require guardian approval, or set null to clear a batch-level guardianEmail for this item.'
|
|
1535
|
+
),
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
export const IssueInboxCredentialBatchItemValidator = z
|
|
1539
|
+
.object({
|
|
1540
|
+
...IssueInboxCredentialValidator.shape,
|
|
1541
|
+
configuration: InboxBatchItemConfigurationValidator.optional(),
|
|
1542
|
+
idempotencyKey: z.string().max(256).optional(),
|
|
1543
|
+
})
|
|
1544
|
+
.refine(data => data.credential || data.templateUri, {
|
|
1545
|
+
message: 'Either credential or templateUri must be provided.',
|
|
1546
|
+
path: ['credential'],
|
|
1547
|
+
})
|
|
1548
|
+
.describe(
|
|
1549
|
+
'One issuance: provide credential or templateUri. Invalid input is rejected at submission with its item index.'
|
|
1550
|
+
);
|
|
1551
|
+
|
|
1552
|
+
export const IssueInboxCredentialBatchValidator = z
|
|
1553
|
+
.object({
|
|
1554
|
+
requestId: z.string().min(1).max(256).optional(),
|
|
1555
|
+
items: z.array(IssueInboxCredentialBatchItemValidator).min(1).max(100),
|
|
1556
|
+
configuration: InboxBatchConfigurationValidator.optional(),
|
|
1557
|
+
})
|
|
1558
|
+
.superRefine((batch, ctx) => {
|
|
1559
|
+
batch.items.forEach((item, index) => {
|
|
1560
|
+
const itemGuardian = item.configuration?.guardianEmail;
|
|
1561
|
+
const guardian =
|
|
1562
|
+
itemGuardian === null
|
|
1563
|
+
? undefined
|
|
1564
|
+
: (itemGuardian ?? batch.configuration?.guardianEmail);
|
|
1565
|
+
if (
|
|
1566
|
+
guardian &&
|
|
1567
|
+
item.recipient.type === 'email' &&
|
|
1568
|
+
guardian.toLowerCase() === item.recipient.value.toLowerCase()
|
|
1569
|
+
) {
|
|
1570
|
+
ctx.addIssue({
|
|
1571
|
+
code: 'custom',
|
|
1572
|
+
path: ['items', index, 'configuration', 'guardianEmail'],
|
|
1573
|
+
message: 'guardianEmail must differ from recipient (self-approval not allowed)',
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
export type IssueInboxCredentialBatch = z.infer<typeof IssueInboxCredentialBatchValidator>;
|
|
1579
|
+
|
|
1580
|
+
export const InboxBatchErrorReasonValidator = z.enum([
|
|
1581
|
+
'DUPLICATE_KEY',
|
|
1582
|
+
'IDEMPOTENCY_MISMATCH',
|
|
1583
|
+
'IN_PROGRESS',
|
|
1584
|
+
'UNCONFIRMED',
|
|
1585
|
+
]);
|
|
1586
|
+
export type InboxBatchErrorReason = z.infer<typeof InboxBatchErrorReasonValidator>;
|
|
1587
|
+
|
|
1588
|
+
export const IssueInboxCredentialBatchItemResultValidator = z.discriminatedUnion('success', [
|
|
1589
|
+
IssueInboxCredentialResponseValidator.extend({
|
|
1590
|
+
success: z.literal(true),
|
|
1591
|
+
index: z.number().int().nonnegative(),
|
|
1592
|
+
deduplicated: z.boolean().optional(),
|
|
1593
|
+
guardianStatus: GuardianStatusValidator.optional(),
|
|
1594
|
+
idempotencyKey: z.string().optional(),
|
|
1595
|
+
}),
|
|
1596
|
+
z.object({
|
|
1597
|
+
success: z.literal(false),
|
|
1598
|
+
index: z.number().int().nonnegative(),
|
|
1599
|
+
idempotencyKey: z.string().optional(),
|
|
1600
|
+
recipient: ContactMethodQueryValidator.optional(),
|
|
1601
|
+
error: z.object({
|
|
1602
|
+
code: z.string(),
|
|
1603
|
+
message: z.string(),
|
|
1604
|
+
reason: InboxBatchErrorReasonValidator.optional(),
|
|
1605
|
+
}),
|
|
1606
|
+
issuanceId: z
|
|
1607
|
+
.string()
|
|
1608
|
+
.optional()
|
|
1609
|
+
.describe(
|
|
1610
|
+
'Present when issuance completed but replay storage could not be confirmed. Reconcile this issuance; do not issue again with a new key.'
|
|
1611
|
+
),
|
|
1612
|
+
claimUrl: z
|
|
1613
|
+
.string()
|
|
1614
|
+
.url()
|
|
1615
|
+
.optional()
|
|
1616
|
+
.describe(
|
|
1617
|
+
'Claim URL of the completed issuance, if available, when replay storage could not be confirmed.'
|
|
1618
|
+
),
|
|
1619
|
+
}),
|
|
1620
|
+
]);
|
|
1621
|
+
export type IssueInboxCredentialBatchItemResult = z.infer<
|
|
1622
|
+
typeof IssueInboxCredentialBatchItemResultValidator
|
|
1623
|
+
>;
|
|
1624
|
+
|
|
1625
|
+
export const IssueInboxCredentialBatchResponseValidator = z.object({
|
|
1626
|
+
results: z.array(IssueInboxCredentialBatchItemResultValidator),
|
|
1627
|
+
summary: z.object({
|
|
1628
|
+
total: z.number(),
|
|
1629
|
+
succeeded: z.number(),
|
|
1630
|
+
failed: z.number(),
|
|
1631
|
+
deduplicated: z.number(),
|
|
1632
|
+
}),
|
|
1633
|
+
});
|
|
1634
|
+
export type IssueInboxCredentialBatchResponse = z.infer<
|
|
1635
|
+
typeof IssueInboxCredentialBatchResponseValidator
|
|
1636
|
+
>;
|
|
1637
|
+
|
|
1638
|
+
/** Submission acknowledges durable storage, not completed credential delivery. */
|
|
1639
|
+
export const InboxBatchReceiptValidator = z.object({
|
|
1640
|
+
batchId: z.string(),
|
|
1641
|
+
status: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1642
|
+
createdAt: z.string(),
|
|
1643
|
+
});
|
|
1644
|
+
export type InboxBatchReceipt = z.infer<typeof InboxBatchReceiptValidator>;
|
|
1645
|
+
|
|
1646
|
+
export const InboxBatchStatusValidator = z.object({
|
|
1647
|
+
batchId: z.string(),
|
|
1648
|
+
createdAt: z.string(),
|
|
1649
|
+
done: z
|
|
1650
|
+
.boolean()
|
|
1651
|
+
.describe(
|
|
1652
|
+
'True when no queued or processing items remain, including unconfirmed outcomes.'
|
|
1653
|
+
),
|
|
1654
|
+
status: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1655
|
+
items: z.array(
|
|
1656
|
+
z.object({
|
|
1657
|
+
index: z.number().int().nonnegative(),
|
|
1658
|
+
state: z.enum(['QUEUED', 'PROCESSING', 'COMPLETED', 'NEEDS_RECONCILIATION']),
|
|
1659
|
+
result: IssueInboxCredentialBatchItemResultValidator.optional(),
|
|
1660
|
+
})
|
|
1661
|
+
),
|
|
1662
|
+
summary: IssueInboxCredentialBatchResponseValidator.shape.summary.extend({
|
|
1663
|
+
completed: z.number(),
|
|
1664
|
+
pending: z.number(),
|
|
1665
|
+
unconfirmed: z.number(),
|
|
1666
|
+
}),
|
|
1667
|
+
});
|
|
1668
|
+
export type InboxBatchStatus = z.infer<typeof InboxBatchStatusValidator>;
|
|
1669
|
+
|
|
1428
1670
|
export type IssueInboxCredentialResponseType = z.infer<
|
|
1429
1671
|
typeof IssueInboxCredentialResponseValidator
|
|
1430
1672
|
>;
|
|
@@ -2245,6 +2487,7 @@ export const CredentialActivityValidator = z.object({
|
|
|
2245
2487
|
eventType: CredentialActivityEventTypeValidator,
|
|
2246
2488
|
timestamp: z.string(),
|
|
2247
2489
|
actorProfileId: z.string().optional(),
|
|
2490
|
+
onBehalfOf: z.string().optional(),
|
|
2248
2491
|
recipientType: CredentialActivityRecipientTypeValidator,
|
|
2249
2492
|
recipientIdentifier: z.string(),
|
|
2250
2493
|
boostUri: z.string().optional(),
|