@learncard/network-brain-service 3.19.0 → 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/.env.example +47 -82
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +147 -0
- package/README.md +51 -2
- package/bootstrapLambda.test.ts +72 -0
- package/config/config.dev.json +29 -0
- package/config/config.example.json +29 -0
- package/config/config.json +1 -0
- package/config/config.production.json +29 -0
- package/config/config.scouts.dev.json +24 -0
- package/config/config.scouts.production.json +24 -0
- package/contractEventsLambda.ts +18 -0
- package/contractEventsLambdaApp.ts +19 -0
- package/didWebLambda.ts +13 -18
- package/didWebLambdaApp.ts +22 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts +10 -0
- package/dist/accesslayer/app-manifest-version/create.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts +11 -0
- package/dist/accesslayer/app-manifest-version/read.d.ts.map +1 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts +7 -0
- package/dist/accesslayer/app-manifest-version/update.d.ts.map +1 -0
- package/dist/accesslayer/app-store-listing/read.d.ts +2 -1
- package/dist/accesslayer/app-store-listing/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts +2 -0
- package/dist/accesslayer/app-store-listing/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/app-store-listing/update.d.ts +1 -0
- package/dist/accesslayer/app-store-listing/update.d.ts.map +1 -1
- package/dist/accesslayer/boost/relationships/create.d.ts +2 -1
- package/dist/accesslayer/boost/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts +9 -1
- package/dist/accesslayer/consentflowcontract/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/read.d.ts +34 -3
- package/dist/accesslayer/consentflowcontract/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts +4 -1
- package/dist/accesslayer/consentflowcontract/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/delete.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts +3 -2
- package/dist/accesslayer/consentflowcontract/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/recipients.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts +12 -0
- package/dist/accesslayer/consentflowcontract/relationships/smartResume.d.ts.map +1 -0
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts +8 -5
- package/dist/accesslayer/consentflowcontract/relationships/update.d.ts.map +1 -1
- package/dist/accesslayer/credential/create.d.ts +2 -1
- package/dist/accesslayer/credential/create.d.ts.map +1 -1
- package/dist/accesslayer/credential/relationships/create.d.ts +4 -3
- package/dist/accesslayer/credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/inbox-credential/update.d.ts +2 -1
- package/dist/accesslayer/inbox-credential/update.d.ts.map +1 -1
- package/dist/accesslayer/presentation/read.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/create.d.ts +2 -2
- package/dist/accesslayer/presentation/relationships/create.d.ts.map +1 -1
- package/dist/accesslayer/presentation/relationships/read.d.ts.map +1 -1
- package/dist/accesslayer/share-link/abandon.d.ts +3 -0
- package/dist/accesslayer/share-link/abandon.d.ts.map +1 -0
- package/dist/accesslayer/share-link/attachment.d.ts +37 -0
- package/dist/accesslayer/share-link/attachment.d.ts.map +1 -0
- package/dist/accesslayer/share-link/cleanup.d.ts +6 -0
- package/dist/accesslayer/share-link/cleanup.d.ts.map +1 -0
- package/dist/accesslayer/share-link/errors.d.ts +8 -0
- package/dist/accesslayer/share-link/errors.d.ts.map +1 -0
- package/dist/accesslayer/share-link/finalize.d.ts +3 -0
- package/dist/accesslayer/share-link/finalize.d.ts.map +1 -0
- package/dist/accesslayer/share-link/helpers.d.ts +59 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -0
- package/dist/accesslayer/share-link/index.d.ts +12 -0
- package/dist/accesslayer/share-link/index.d.ts.map +1 -0
- package/dist/accesslayer/share-link/list.d.ts +5 -0
- package/dist/accesslayer/share-link/list.d.ts.map +1 -0
- package/dist/accesslayer/share-link/read.d.ts +5 -0
- package/dist/accesslayer/share-link/read.d.ts.map +1 -0
- package/dist/accesslayer/share-link/receipt.d.ts +11 -0
- package/dist/accesslayer/share-link/receipt.d.ts.map +1 -0
- package/dist/accesslayer/share-link/recovery.d.ts +6 -0
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -0
- package/dist/accesslayer/share-link/reserve.d.ts +4 -0
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -0
- package/dist/accesslayer/share-link/revoke.d.ts +3 -0
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts +18 -0
- package/dist/accesslayer/share-link/transaction-execution.d.ts.map +1 -0
- package/dist/accesslayer/share-link/transaction.d.ts +15 -0
- package/dist/accesslayer/share-link/transaction.d.ts.map +1 -0
- package/dist/accesslayer/share-link/types.d.ts +375 -0
- package/dist/accesslayer/share-link/types.d.ts.map +1 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/cache/app-store.caches.d.ts +49 -0
- package/dist/cache/app-store.caches.d.ts.map +1 -1
- package/dist/config/applyDockerStageConfig.d.ts +2 -0
- package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
- package/dist/config/environment.d.ts +14 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
- package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
- package/dist/config/stageConfig.d.ts +112 -0
- package/dist/config/stageConfig.d.ts.map +1 -0
- package/dist/config/stageConfig.test.d.ts +2 -0
- package/dist/config/stageConfig.test.d.ts.map +1 -0
- package/dist/constants/auth-grant.d.ts +2 -0
- package/dist/constants/auth-grant.d.ts.map +1 -1
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/docker-entry.d.ts +1 -1
- package/dist/docker-entry.d.ts.map +1 -1
- package/dist/helpers/app-manifest.helpers.d.ts +5 -0
- package/dist/helpers/app-manifest.helpers.d.ts.map +1 -0
- package/dist/helpers/boost.helpers.d.ts.map +1 -1
- package/dist/helpers/claim-hooks.helpers.d.ts +2 -1
- package/dist/helpers/claim-hooks.helpers.d.ts.map +1 -1
- package/dist/helpers/consent-referral.helpers.d.ts +2 -0
- package/dist/helpers/consent-referral.helpers.d.ts.map +1 -0
- package/dist/helpers/consent-scopes.helpers.d.ts +7 -0
- package/dist/helpers/consent-scopes.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts +3 -0
- package/dist/helpers/contract-event-maintenance.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-events.helpers.d.ts +25 -0
- package/dist/helpers/contract-events.helpers.d.ts.map +1 -0
- package/dist/helpers/contract-requests.helpers.d.ts +19 -0
- package/dist/helpers/contract-requests.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.d.ts +3 -0
- package/dist/helpers/credentialSubject.helpers.d.ts.map +1 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts +2 -0
- package/dist/helpers/credentialSubject.helpers.test.d.ts.map +1 -0
- package/dist/helpers/finalize-inbox.helpers.d.ts +3 -3
- package/dist/helpers/finalize-inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inbox.helpers.d.ts.map +1 -1
- package/dist/helpers/inline-template.helpers.d.ts +45 -0
- package/dist/helpers/inline-template.helpers.d.ts.map +1 -0
- package/dist/helpers/notificationMessages.d.ts +1 -1
- package/dist/helpers/notificationMessages.d.ts.map +1 -1
- package/dist/helpers/notificationQueue.helpers.d.ts.map +1 -1
- package/dist/helpers/notifications.helpers.d.ts +6 -0
- package/dist/helpers/notifications.helpers.d.ts.map +1 -1
- package/dist/helpers/presentation-metadata.d.ts +4 -0
- package/dist/helpers/presentation-metadata.d.ts.map +1 -0
- package/dist/helpers/presentation.helpers.d.ts +1 -1
- package/dist/helpers/presentation.helpers.d.ts.map +1 -1
- package/dist/helpers/profile.helpers.d.ts +2 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- package/dist/helpers/share-content-client/adapters.d.ts +10 -0
- package/dist/helpers/share-content-client/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-client/canonical.d.ts +7 -0
- package/dist/helpers/share-content-client/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-client/client.d.ts +5 -0
- package/dist/helpers/share-content-client/client.d.ts.map +1 -0
- package/dist/helpers/share-content-client/config.d.ts +11 -0
- package/dist/helpers/share-content-client/config.d.ts.map +1 -0
- package/dist/helpers/share-content-client/index.d.ts +6 -0
- package/dist/helpers/share-content-client/index.d.ts.map +1 -0
- package/dist/helpers/share-content-client/responses.d.ts +16 -0
- package/dist/helpers/share-content-client/responses.d.ts.map +1 -0
- package/dist/helpers/share-content-client/types.d.ts +145 -0
- package/dist/helpers/share-content-client/types.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment-access.d.ts +4 -0
- package/dist/helpers/share-link-attachment-access.d.ts.map +1 -0
- package/dist/helpers/share-link-attachment.d.ts +18 -0
- package/dist/helpers/share-link-attachment.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/attachment-stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/budget-helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts +3 -0
- package/dist/helpers/share-link-coordinator/cleanup-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/coordinator.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/index.d.ts +5 -0
- package/dist/helpers/share-link-coordinator/index.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts +9 -0
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts +11 -0
- package/dist/helpers/share-link-coordinator/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts +4 -0
- package/dist/helpers/share-link-coordinator/stat.d.ts.map +1 -0
- package/dist/helpers/share-link-coordinator/types.d.ts +160 -0
- package/dist/helpers/share-link-coordinator/types.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts +5 -0
- package/dist/helpers/share-link-lifecycle/index.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts +4 -0
- package/dist/helpers/share-link-lifecycle/lease.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts +21 -0
- package/dist/helpers/share-link-lifecycle/recovery.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts +9 -0
- package/dist/helpers/share-link-lifecycle/request-hash.helpers.d.ts.map +1 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts +14 -0
- package/dist/helpers/share-link-lifecycle/types.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts +12 -0
- package/dist/helpers/share-link-maintenance/budget.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/config.d.ts +48 -0
- package/dist/helpers/share-link-maintenance/config.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/index.d.ts +8 -0
- package/dist/helpers/share-link-maintenance/index.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts +20 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts +38 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts +18 -0
- package/dist/helpers/share-link-maintenance/scheduler.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts +5 -0
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -0
- package/dist/helpers/share-link-maintenance/types.d.ts +56 -0
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/config.d.ts +16 -0
- package/dist/helpers/share-link-owner/config.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts +26 -0
- package/dist/helpers/share-link-owner/diagnostics.d.ts.map +1 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts +2 -0
- package/dist/helpers/share-link-owner/lazy-initializer.d.ts.map +1 -0
- package/dist/helpers/share-link-owner-projection.d.ts +4 -0
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts +2 -0
- package/dist/helpers/share-link-passcode-abuse.d.ts.map +1 -0
- package/dist/helpers/share-link-passcode.d.ts +6 -0
- package/dist/helpers/share-link-passcode.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/index.d.ts +4 -0
- package/dist/helpers/share-link-policy/index.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/production.d.ts +11 -0
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/resolver.d.ts +6 -0
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -0
- package/dist/helpers/share-link-policy/types.d.ts +22 -0
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -0
- package/dist/helpers/share-link-public/config.d.ts +13 -0
- package/dist/helpers/share-link-public/config.d.ts.map +1 -0
- package/dist/helpers/share-link-view-notification.d.ts +2 -0
- package/dist/helpers/share-link-view-notification.d.ts.map +1 -0
- package/dist/helpers/signingAuthority.helpers.d.ts.map +1 -1
- package/dist/helpers/smartResume.helpers.d.ts +3 -0
- package/dist/helpers/smartResume.helpers.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/AppManifestVersion.d.ts +20 -0
- package/dist/models/AppManifestVersion.d.ts.map +1 -0
- package/dist/models/AppStoreListing.d.ts +13 -0
- package/dist/models/AppStoreListing.d.ts.map +1 -1
- package/dist/models/ConsentFlowContract.d.ts +13 -2
- package/dist/models/ConsentFlowContract.d.ts.map +1 -1
- package/dist/models/ConsentFlowTerms.d.ts.map +1 -1
- package/dist/models/ConsentFlowTransaction.d.ts.map +1 -1
- package/dist/models/Integration.d.ts +2 -0
- package/dist/models/Integration.d.ts.map +1 -1
- package/dist/models/Presentation.d.ts +2 -0
- package/dist/models/Presentation.d.ts.map +1 -1
- package/dist/models/Profile.d.ts +2 -0
- package/dist/models/Profile.d.ts.map +1 -1
- package/dist/models/ShareLink.d.ts +46 -0
- package/dist/models/ShareLink.d.ts.map +1 -0
- package/dist/models/ShareViewReceipt.d.ts +22 -0
- package/dist/models/ShareViewReceipt.d.ts.map +1 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts +3 -0
- package/dist/models/share-link-constraints.d.ts.map +1 -0
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/public-share-link-http.d.ts +4 -0
- package/dist/public-share-link-http.d.ts.map +1 -0
- package/dist/routes/app-store.d.ts +619 -0
- package/dist/routes/app-store.d.ts.map +1 -1
- package/dist/routes/boosts.d.ts +2 -2
- package/dist/routes/boosts.d.ts.map +1 -1
- package/dist/routes/contracts.d.ts +148 -5
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +35 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/inbox.d.ts +52 -2
- package/dist/routes/inbox.d.ts.map +1 -1
- package/dist/routes/index.d.ts +153 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/presentations.d.ts +1 -0
- package/dist/routes/presentations.d.ts.map +1 -1
- package/dist/routes/profile-manager.d.ts.map +1 -1
- package/dist/routes/profiles.d.ts.map +1 -1
- package/dist/routes/public-share-links.d.ts +285 -0
- package/dist/routes/public-share-links.d.ts.map +1 -0
- package/dist/routes/share-links.d.ts +1005 -0
- package/dist/routes/share-links.d.ts.map +1 -0
- package/dist/routes/test.d.ts +5 -0
- package/dist/routes/test.d.ts.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/services/delivery/adapters/postmark.adapter.d.ts.map +1 -1
- package/dist/types/app-manifest.d.ts +339 -0
- package/dist/types/app-manifest.d.ts.map +1 -0
- package/dist/types/app-store-listing.d.ts +34 -0
- package/dist/types/app-store-listing.d.ts.map +1 -1
- package/dist/types/consentflowcontract.d.ts +31 -4
- package/dist/types/consentflowcontract.d.ts.map +1 -1
- package/dist/types/credential-refresh.d.ts +1 -1
- package/esbuildPlugins.cjs +1 -0
- package/lambda.ts +49 -192
- package/lambdaApp.ts +203 -0
- package/package.json +25 -17
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -0
- package/scripts/seed-demo-persona.ts +425 -0
- package/scripts/verify-python-contracts.ts +72 -0
- package/serverless-local.yml +3 -6
- package/serverless.function-env.cjs +50 -0
- package/serverless.local.yml +0 -1
- package/serverless.yml +44 -55
- package/shareLinkMaintenanceLambda.ts +18 -0
- package/shareLinkMaintenanceLambdaApp.ts +50 -0
- package/src/accesslayer/app-manifest-version/create.ts +57 -0
- package/src/accesslayer/app-manifest-version/read.ts +129 -0
- package/src/accesslayer/app-manifest-version/update.ts +61 -0
- package/src/accesslayer/app-store-listing/read.ts +6 -0
- package/src/accesslayer/app-store-listing/relationships/create.ts +20 -0
- package/src/accesslayer/app-store-listing/relationships/read.ts +24 -1
- package/src/accesslayer/app-store-listing/update.ts +15 -0
- package/src/accesslayer/boost/relationships/create.ts +8 -2
- package/src/accesslayer/consentflowcontract/create.ts +81 -8
- package/src/accesslayer/consentflowcontract/read.ts +84 -10
- package/src/accesslayer/consentflowcontract/relationships/create.ts +125 -109
- package/src/accesslayer/consentflowcontract/relationships/delete.ts +1 -0
- package/src/accesslayer/consentflowcontract/relationships/read.ts +184 -108
- package/src/accesslayer/consentflowcontract/relationships/recipients.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/smartResume.ts +125 -0
- package/src/accesslayer/consentflowcontract/relationships/update.ts +255 -217
- package/src/accesslayer/credential/create.test.ts +2 -115
- package/src/accesslayer/credential/create.ts +11 -6
- package/src/accesslayer/credential/relationships/create.ts +17 -10
- package/src/accesslayer/inbox-credential/relationships/create.ts +70 -48
- package/src/accesslayer/inbox-credential/update.ts +4 -2
- package/src/accesslayer/presentation/read.ts +57 -25
- package/src/accesslayer/presentation/relationships/create.ts +54 -11
- package/src/accesslayer/presentation/relationships/read.ts +21 -6
- package/src/accesslayer/share-link/abandon.ts +120 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/cleanup.ts +271 -0
- package/src/accesslayer/share-link/errors.ts +32 -0
- package/src/accesslayer/share-link/finalize.ts +313 -0
- package/src/accesslayer/share-link/helpers.ts +450 -0
- package/src/accesslayer/share-link/index.ts +11 -0
- package/src/accesslayer/share-link/list.ts +162 -0
- package/src/accesslayer/share-link/read.ts +107 -0
- package/src/accesslayer/share-link/receipt.ts +548 -0
- package/src/accesslayer/share-link/recovery.ts +565 -0
- package/src/accesslayer/share-link/reserve.ts +763 -0
- package/src/accesslayer/share-link/revoke.ts +232 -0
- package/src/accesslayer/share-link/transaction-execution.ts +72 -0
- package/src/accesslayer/share-link/transaction.ts +126 -0
- package/src/accesslayer/share-link/types.ts +606 -0
- package/src/app.ts +6 -0
- package/src/cache/app-store.caches.ts +35 -0
- package/src/config/applyDockerStageConfig.ts +6 -0
- package/src/config/environment.test.ts +17 -12
- package/src/config/environment.ts +15 -0
- package/src/config/serverlessFunctionEnv.test.ts +105 -0
- package/src/config/stageConfig.test.ts +25 -0
- package/src/config/stageConfig.ts +24 -0
- package/src/constants/auth-grant.ts +5 -0
- package/src/docker-entry.ts +81 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- package/src/helpers/boost.helpers.ts +4 -31
- package/src/helpers/claim-hooks.helpers.ts +20 -7
- package/src/helpers/consent-referral.helpers.ts +6 -0
- package/src/helpers/consent-scopes.helpers.ts +58 -0
- package/src/helpers/contract-event-maintenance.helpers.ts +43 -0
- package/src/helpers/contract-events.helpers.ts +540 -0
- package/src/helpers/contract-requests.helpers.ts +211 -0
- package/src/helpers/credentialSubject.helpers.test.ts +114 -0
- package/src/helpers/credentialSubject.helpers.ts +40 -0
- package/src/helpers/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +58 -70
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +77 -1
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/presentation-metadata.ts +41 -0
- package/src/helpers/presentation.helpers.ts +27 -20
- package/src/helpers/profile.helpers.ts +13 -0
- package/src/helpers/share-content-client/adapters.ts +55 -0
- package/src/helpers/share-content-client/canonical.ts +25 -0
- package/src/helpers/share-content-client/client.ts +440 -0
- package/src/helpers/share-content-client/config.ts +189 -0
- package/src/helpers/share-content-client/index.ts +27 -0
- package/src/helpers/share-content-client/responses.ts +273 -0
- package/src/helpers/share-content-client/types.ts +253 -0
- package/src/helpers/share-link-attachment-access.ts +87 -0
- package/src/helpers/share-link-attachment.ts +115 -0
- package/src/helpers/share-link-coordinator/attachment-stat.ts +41 -0
- package/src/helpers/share-link-coordinator/budget-helpers.ts +53 -0
- package/src/helpers/share-link-coordinator/cleanup-runner.ts +286 -0
- package/src/helpers/share-link-coordinator/coordinator.ts +727 -0
- package/src/helpers/share-link-coordinator/index.ts +8 -0
- package/src/helpers/share-link-coordinator/recovery-runner.ts +443 -0
- package/src/helpers/share-link-coordinator/runtime.ts +88 -0
- package/src/helpers/share-link-coordinator/stat.ts +30 -0
- package/src/helpers/share-link-coordinator/types.ts +353 -0
- package/src/helpers/share-link-lifecycle/index.ts +4 -0
- package/src/helpers/share-link-lifecycle/lease.helpers.ts +35 -0
- package/src/helpers/share-link-lifecycle/recovery.helpers.ts +173 -0
- package/src/helpers/share-link-lifecycle/request-hash.helpers.ts +130 -0
- package/src/helpers/share-link-lifecycle/types.ts +58 -0
- package/src/helpers/share-link-maintenance/budget.ts +63 -0
- package/src/helpers/share-link-maintenance/config.ts +312 -0
- package/src/helpers/share-link-maintenance/index.ts +24 -0
- package/src/helpers/share-link-maintenance/runner.ts +251 -0
- package/src/helpers/share-link-maintenance/runtime.ts +340 -0
- package/src/helpers/share-link-maintenance/scheduler.ts +126 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +73 -0
- package/src/helpers/share-link-maintenance/types.ts +91 -0
- package/src/helpers/share-link-owner/config.ts +48 -0
- package/src/helpers/share-link-owner/diagnostics.ts +89 -0
- package/src/helpers/share-link-owner/lazy-initializer.ts +29 -0
- package/src/helpers/share-link-owner-projection.ts +54 -0
- package/src/helpers/share-link-passcode-abuse.ts +36 -0
- package/src/helpers/share-link-passcode.ts +47 -0
- package/src/helpers/share-link-policy/index.ts +7 -0
- package/src/helpers/share-link-policy/production.ts +108 -0
- package/src/helpers/share-link-policy/resolver.ts +113 -0
- package/src/helpers/share-link-policy/types.ts +56 -0
- package/src/helpers/share-link-public/config.ts +41 -0
- package/src/helpers/share-link-view-notification.ts +18 -0
- package/src/helpers/signingAuthority.helpers.ts +23 -14
- package/src/helpers/smartResume.helpers.ts +123 -0
- package/src/models/AppManifestVersion.ts +43 -0
- package/src/models/AppStoreListing.ts +32 -0
- package/src/models/ConsentFlowContract.ts +34 -3
- package/src/models/ConsentFlowTerms.ts +10 -0
- package/src/models/ConsentFlowTransaction.ts +3 -0
- package/src/models/Integration.ts +2 -0
- package/src/models/Presentation.ts +2 -2
- package/src/models/Profile.ts +3 -2
- package/src/models/ShareLink.ts +134 -0
- package/src/models/ShareViewReceipt.ts +69 -0
- package/src/models/index.ts +25 -0
- package/src/models/share-link-constraints.ts +98 -0
- package/src/openapi.ts +44 -8
- package/src/public-share-link-http.ts +49 -0
- package/src/routes/app-store.ts +1056 -38
- package/src/routes/boosts.ts +14 -25
- package/src/routes/contracts.ts +384 -273
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +142 -44
- package/src/routes/presentations.ts +19 -3
- package/src/routes/profile-manager.ts +29 -2
- package/src/routes/profiles.ts +29 -6
- package/src/routes/public-share-links.ts +983 -0
- package/src/routes/share-links.ts +709 -0
- package/src/routes/workflows.ts +3 -2
- package/src/services/delivery/adapters/postmark.adapter.ts +4 -1
- package/src/types/app-manifest.ts +28 -0
- package/src/types/app-store-listing.ts +13 -0
- package/src/types/consentflowcontract.ts +25 -4
- package/test/app-manifest.helpers.spec.ts +170 -0
- package/test/app-manifest.routes.spec.ts +299 -0
- package/test/app-store-listing-updates.spec.ts +236 -0
- package/test/app-store-listing.spec.ts +65 -3
- package/test/consent-data-boundaries.spec.ts +352 -0
- package/test/consent-recipients.spec.ts +513 -0
- package/test/consent-scopes.helpers.spec.ts +67 -0
- package/test/consentflow.spec.ts +63 -2
- package/test/contract-requests.spec.ts +1588 -0
- package/test/credential-storage.neo4j.spec.ts +122 -0
- package/test/dids.spec.ts +10 -1
- package/test/guardian-gated-routes.spec.ts +49 -0
- package/test/helpers/share-link-fixtures.ts +94 -0
- package/test/inbox-security.e2e.ts +26 -10
- package/test/inbox.spec.ts +184 -8
- package/test/inline-template.helpers.spec.ts +49 -0
- package/test/managed-recipient-inbox.spec.ts +152 -0
- package/test/notificationMessages.spec.ts +13 -0
- package/test/openapi.spec.ts +14 -53
- package/test/presentations.spec.ts +54 -6
- package/test/profiles.spec.ts +97 -1
- package/test/share-content-client.contract.spec.ts +365 -0
- package/test/share-content-client.unit.spec.ts +462 -0
- package/test/share-link-abuse.unit.spec.ts +65 -0
- package/test/share-link-attachment.neo4j.spec.ts +262 -0
- package/test/share-link-attachment.unit.spec.ts +244 -0
- package/test/share-link-coordinator.neo4j.spec.ts +684 -0
- package/test/share-link-coordinator.unit.spec.ts +990 -0
- package/test/share-link-dependency-diagnostics.unit.spec.ts +127 -0
- package/test/share-link-lifecycle.neo4j.spec.ts +1411 -0
- package/test/share-link-lifecycle.unit.spec.ts +205 -0
- package/test/share-link-list-cursor.unit.spec.ts +86 -0
- package/test/share-link-list.neo4j.spec.ts +324 -0
- package/test/share-link-maintenance.neo4j.spec.ts +355 -0
- package/test/share-link-maintenance.unit.spec.ts +1140 -0
- package/test/share-link-owner-policy.unit.spec.ts +210 -0
- package/test/share-link-owner-route.unit.spec.ts +820 -0
- package/test/share-link-owner-runtime.unit.spec.ts +102 -0
- package/test/share-link-passcode.unit.spec.ts +29 -0
- package/test/share-link-policy.unit.spec.ts +149 -0
- package/test/share-link-presentation-metadata.unit.spec.ts +49 -0
- package/test/share-link-production-policy.unit.spec.ts +126 -0
- package/test/share-link-public-config.unit.spec.ts +64 -0
- package/test/share-link-public-http-adapter.unit.spec.ts +315 -0
- package/test/share-link-public-route.unit.spec.ts +1079 -0
- package/test/share-link-receipt.neo4j.spec.ts +943 -0
- package/test/share-link-recovery.neo4j.spec.ts +935 -0
- package/test/share-link-recovery.unit.spec.ts +726 -0
- package/test/share-link-service-policy.neo4j.spec.ts +403 -0
- package/test/signing-authority-encryption.spec.ts +75 -0
- package/test/smart-resume-consent.spec.ts +233 -0
- package/test/smart-resume-upload.spec.ts +65 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -0
- package/vitest.config.ts +14 -0
- package/vitest.inbox-e2e.config.ts +2 -0
- package/vitest.share-links-neo4j.config.ts +19 -0
- package/vitest.share-links-unit.config.ts +28 -0
- package/workerLambda.test.ts +100 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { TRPCError } from '@trpc/server';
|
|
5
|
+
import type { UnsignedVC } from '@learncard/types';
|
|
6
|
+
import type { InlineCredentialTemplate, VariableManifest } from '@learncard/partner-connect-core';
|
|
7
|
+
import {
|
|
8
|
+
canonicalJsonString,
|
|
9
|
+
compileInlineTemplate,
|
|
10
|
+
validateInlineTemplate,
|
|
11
|
+
validateTemplateData,
|
|
12
|
+
} from '@learncard/partner-connect-core';
|
|
13
|
+
|
|
14
|
+
import { getBoostUri } from '@helpers/boost.helpers';
|
|
15
|
+
import { getAppDidWeb, getDidWeb } from '@helpers/did.helpers';
|
|
16
|
+
import { getDidWebLearnCard, getLearnCard } from '@helpers/learnCard.helpers';
|
|
17
|
+
import { inflateObject } from '@helpers/objects.helpers';
|
|
18
|
+
import { createBoostForListing } from '@accesslayer/boost/create';
|
|
19
|
+
import { updateBoost } from '@accesslayer/boost/update';
|
|
20
|
+
import { removeBoostFromListing } from '@accesslayer/app-store-listing/relationships/delete';
|
|
21
|
+
import {
|
|
22
|
+
associateBoostWithListing,
|
|
23
|
+
associateListingWithSigningAuthority,
|
|
24
|
+
} from '@accesslayer/app-store-listing/relationships/create';
|
|
25
|
+
import { getBoostForListingByTemplateAlias } from '@accesslayer/app-store-listing/relationships/read';
|
|
26
|
+
import { upsertSigningAuthority } from '@accesslayer/signing-authority/create';
|
|
27
|
+
import { createUseSigningAuthorityRelationship } from '@accesslayer/signing-authority/relationships/create';
|
|
28
|
+
import {
|
|
29
|
+
getPrimarySigningAuthorityForListing,
|
|
30
|
+
getPrimarySigningAuthorityForIntegration,
|
|
31
|
+
getPrimarySigningAuthorityForUser,
|
|
32
|
+
} from '@accesslayer/signing-authority/relationships/read';
|
|
33
|
+
import {
|
|
34
|
+
invalidateBoostForListingCache,
|
|
35
|
+
invalidateListingSigningAuthorityCache,
|
|
36
|
+
} from '@cache/app-store.caches';
|
|
37
|
+
import { deleteDidDocForProfile } from '@cache/did-docs';
|
|
38
|
+
import { neogma } from '@instance';
|
|
39
|
+
import { environment } from '@environment';
|
|
40
|
+
import { Boost, type BoostInstance } from '@models';
|
|
41
|
+
import type { AppStoreListingType } from 'types/app-store-listing';
|
|
42
|
+
import type { IntegrationType } from 'types/integration';
|
|
43
|
+
import type { ProfileType, SigningAuthorityForUserType } from 'types/profile';
|
|
44
|
+
|
|
45
|
+
type InlineTemplateBoostMeta = {
|
|
46
|
+
integrationId?: string;
|
|
47
|
+
templateAlias?: string;
|
|
48
|
+
inlineTemplateHash?: string;
|
|
49
|
+
inlineTemplateVersion?: number;
|
|
50
|
+
inlineVariableManifest?: string;
|
|
51
|
+
source?: string;
|
|
52
|
+
supersededAt?: string;
|
|
53
|
+
supersededBy?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type UpsertInlineTemplateBoostResult = {
|
|
57
|
+
boost: BoostInstance;
|
|
58
|
+
boostUri: string;
|
|
59
|
+
templateVersion: number;
|
|
60
|
+
manifest: VariableManifest;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const INLINE_TEMPLATE_SOURCE = 'partner-connect-inline';
|
|
64
|
+
const DEFAULT_SIGNING_AUTHORITY_ENDPOINT =
|
|
65
|
+
environment.SIGNING_AUTHORITY_ENDPOINT ?? 'http://localhost:5100/api';
|
|
66
|
+
const MAX_SIGNING_AUTHORITY_NAME_LENGTH = 15;
|
|
67
|
+
const SIGNING_AUTHORITY_PREFIX = 'app-';
|
|
68
|
+
|
|
69
|
+
const getBoostMeta = (boost: BoostInstance): InlineTemplateBoostMeta => {
|
|
70
|
+
const inflated = inflateObject<Record<string, unknown>>(
|
|
71
|
+
boost.getDataValues() as Record<string, unknown>
|
|
72
|
+
);
|
|
73
|
+
return (inflated.meta as InlineTemplateBoostMeta | undefined) ?? {};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const readInlineTemplateBoostMeta = async (
|
|
77
|
+
boostOrId: BoostInstance | string
|
|
78
|
+
): Promise<InlineTemplateBoostMeta> => {
|
|
79
|
+
const boostId = typeof boostOrId === 'string' ? boostOrId : boostOrId.id;
|
|
80
|
+
const inMemoryMeta = typeof boostOrId === 'string' ? undefined : getBoostMeta(boostOrId);
|
|
81
|
+
|
|
82
|
+
if (
|
|
83
|
+
inMemoryMeta?.inlineTemplateHash ||
|
|
84
|
+
typeof inMemoryMeta?.inlineTemplateVersion === 'number' ||
|
|
85
|
+
inMemoryMeta?.templateAlias
|
|
86
|
+
) {
|
|
87
|
+
return inMemoryMeta;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = await neogma.queryRunner.run(
|
|
91
|
+
`MATCH (boost:Boost {id: $boostId})
|
|
92
|
+
RETURN boost.\`meta.integrationId\` AS integrationId,
|
|
93
|
+
boost.\`meta.templateAlias\` AS templateAlias,
|
|
94
|
+
boost.\`meta.inlineTemplateHash\` AS inlineTemplateHash,
|
|
95
|
+
boost.\`meta.inlineTemplateVersion\` AS inlineTemplateVersion,
|
|
96
|
+
boost.\`meta.inlineVariableManifest\` AS inlineVariableManifest,
|
|
97
|
+
boost.\`meta.source\` AS source,
|
|
98
|
+
boost.\`meta.supersededAt\` AS supersededAt,
|
|
99
|
+
boost.\`meta.supersededBy\` AS supersededBy`,
|
|
100
|
+
{ boostId }
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const record = result.records[0];
|
|
104
|
+
|
|
105
|
+
if (!record) {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const rawVersion = record.get('inlineTemplateVersion') as { toNumber?: () => number } | number;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
integrationId: record.get('integrationId') ?? undefined,
|
|
113
|
+
templateAlias: record.get('templateAlias') ?? undefined,
|
|
114
|
+
inlineTemplateHash: record.get('inlineTemplateHash') ?? undefined,
|
|
115
|
+
inlineTemplateVersion:
|
|
116
|
+
typeof rawVersion === 'number' ? rawVersion : rawVersion?.toNumber?.(),
|
|
117
|
+
inlineVariableManifest: record.get('inlineVariableManifest') ?? undefined,
|
|
118
|
+
source: record.get('source') ?? undefined,
|
|
119
|
+
supersededAt: record.get('supersededAt') ?? undefined,
|
|
120
|
+
supersededBy: record.get('supersededBy') ?? undefined,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const joinTemplateErrors = (errors: Array<{ message: string }>): string => {
|
|
125
|
+
return errors.map(error => error.message).join('; ');
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const getInlineTemplateVersion = (boost: BoostInstance): number | undefined => {
|
|
129
|
+
const version = getBoostMeta(boost).inlineTemplateVersion;
|
|
130
|
+
return typeof version === 'number' && Number.isFinite(version) ? version : undefined;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const getInlineTemplateHashForBoost = (boost: BoostInstance): string | undefined => {
|
|
134
|
+
const inlineTemplateHash = getBoostMeta(boost).inlineTemplateHash;
|
|
135
|
+
return typeof inlineTemplateHash === 'string' ? inlineTemplateHash : undefined;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const isInlineTemplateBoostVersionMatch = (
|
|
139
|
+
boost: BoostInstance,
|
|
140
|
+
templateHash: string,
|
|
141
|
+
templateVersion: number
|
|
142
|
+
): boolean => {
|
|
143
|
+
return (
|
|
144
|
+
getInlineTemplateHashForBoost(boost) === templateHash &&
|
|
145
|
+
getInlineTemplateVersion(boost) === templateVersion
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const computeInlineTemplateHash = (
|
|
150
|
+
template: InlineCredentialTemplate
|
|
151
|
+
): {
|
|
152
|
+
credentialTemplateJson: string;
|
|
153
|
+
variableManifest: VariableManifest;
|
|
154
|
+
category: string;
|
|
155
|
+
walletSkills?: unknown;
|
|
156
|
+
contentHash: string;
|
|
157
|
+
} => {
|
|
158
|
+
const compiled = compileInlineTemplate(template);
|
|
159
|
+
const contentHash = crypto
|
|
160
|
+
.createHash('sha256')
|
|
161
|
+
.update(
|
|
162
|
+
canonicalJsonString({
|
|
163
|
+
credentialTemplateJson: compiled.credentialTemplateJson,
|
|
164
|
+
category: compiled.category,
|
|
165
|
+
walletSkills: compiled.walletSkills,
|
|
166
|
+
})
|
|
167
|
+
)
|
|
168
|
+
.digest('hex');
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
...compiled,
|
|
172
|
+
contentHash,
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const validateInlineTemplateOrThrow = (template: InlineCredentialTemplate): void => {
|
|
177
|
+
const errors = validateInlineTemplate(template);
|
|
178
|
+
|
|
179
|
+
if (errors.length === 0) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
throw new TRPCError({
|
|
184
|
+
code: 'BAD_REQUEST',
|
|
185
|
+
message: `TEMPLATE_INVALID: ${joinTemplateErrors(errors)}`,
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const validateInlineTemplateDataOrThrow = (
|
|
190
|
+
manifest: VariableManifest,
|
|
191
|
+
templateData: Record<string, unknown> | undefined
|
|
192
|
+
): void => {
|
|
193
|
+
const errors = validateTemplateData(manifest, templateData);
|
|
194
|
+
|
|
195
|
+
if (errors.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new TRPCError({
|
|
200
|
+
code: 'BAD_REQUEST',
|
|
201
|
+
message: `TEMPLATE_DATA_INVALID: ${joinTemplateErrors(errors)}`,
|
|
202
|
+
});
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const ensureListingSigningAuthorityRelationship = async (
|
|
206
|
+
listing: AppStoreListingType,
|
|
207
|
+
signingAuthority: SigningAuthorityForUserType
|
|
208
|
+
): Promise<void> => {
|
|
209
|
+
const listingSigningAuthority = await getPrimarySigningAuthorityForListing(listing);
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
listingSigningAuthority &&
|
|
213
|
+
listingSigningAuthority.signingAuthority.endpoint ===
|
|
214
|
+
signingAuthority.signingAuthority.endpoint &&
|
|
215
|
+
listingSigningAuthority.relationship.name === signingAuthority.relationship.name &&
|
|
216
|
+
listingSigningAuthority.relationship.did === signingAuthority.relationship.did
|
|
217
|
+
) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await associateListingWithSigningAuthority(
|
|
222
|
+
listing.listing_id,
|
|
223
|
+
signingAuthority.signingAuthority.endpoint,
|
|
224
|
+
{
|
|
225
|
+
name: signingAuthority.relationship.name,
|
|
226
|
+
did: signingAuthority.relationship.did,
|
|
227
|
+
isPrimary: true,
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
invalidateListingSigningAuthorityCache(listing.listing_id);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const buildManagedSigningAuthorityName = (listing: AppStoreListingType): string => {
|
|
235
|
+
const base = (listing.slug || listing.listing_id).toLowerCase().replace(/[^a-z0-9-]/g, '');
|
|
236
|
+
const trimmed = base.slice(
|
|
237
|
+
0,
|
|
238
|
+
MAX_SIGNING_AUTHORITY_NAME_LENGTH - SIGNING_AUTHORITY_PREFIX.length
|
|
239
|
+
);
|
|
240
|
+
return `${SIGNING_AUTHORITY_PREFIX}${trimmed || 'app'}`.slice(
|
|
241
|
+
0,
|
|
242
|
+
MAX_SIGNING_AUTHORITY_NAME_LENGTH
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const ManagedSigningAuthorityResponseValidator = z.object({ did: z.string().min(1) });
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Gets or creates the app's signing authority through LCA API, which owns key generation
|
|
250
|
+
* and encrypts the seed at rest. Brain authenticates as a trusted service DID.
|
|
251
|
+
*/
|
|
252
|
+
const provisionManagedSigningAuthority = async (
|
|
253
|
+
endpoint: string,
|
|
254
|
+
ownerDid: string,
|
|
255
|
+
name: string
|
|
256
|
+
): Promise<string> => {
|
|
257
|
+
// Tests mock signing-authority issuance, so derive a stable DID without calling LCA API.
|
|
258
|
+
if (environment.NODE_ENV === 'test') {
|
|
259
|
+
return (await getLearnCard('e'.repeat(64))).id.did();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const learnCard = await getDidWebLearnCard();
|
|
264
|
+
const didJwt = await learnCard.invoke.getDidAuthVp({ proofFormat: 'jwt' });
|
|
265
|
+
|
|
266
|
+
const response = await fetch(`${endpoint}/signing-authority/managed`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: {
|
|
269
|
+
'Content-Type': 'application/json',
|
|
270
|
+
'Authorization': `Bearer ${didJwt}`,
|
|
271
|
+
},
|
|
272
|
+
body: JSON.stringify({ ownerDid, name }),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
throw new Error(`LCA API responded with ${response.status}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return ManagedSigningAuthorityResponseValidator.parse(await response.json()).did;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
282
|
+
throw new TRPCError({
|
|
283
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
284
|
+
message: `Unable to auto-provision app signing authority: ${message}`,
|
|
285
|
+
cause: error,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const ensureManagedSigningAuthorityForListing = async (
|
|
291
|
+
listing: AppStoreListingType,
|
|
292
|
+
integration: IntegrationType,
|
|
293
|
+
integrationOwner: ProfileType,
|
|
294
|
+
domain: string
|
|
295
|
+
): Promise<SigningAuthorityForUserType> => {
|
|
296
|
+
const existingListingSa = await getPrimarySigningAuthorityForListing(listing);
|
|
297
|
+
if (existingListingSa) {
|
|
298
|
+
return existingListingSa;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const existingIntegrationSa = await getPrimarySigningAuthorityForIntegration(integration.id);
|
|
302
|
+
if (existingIntegrationSa) {
|
|
303
|
+
await ensureListingSigningAuthorityRelationship(listing, existingIntegrationSa);
|
|
304
|
+
return existingIntegrationSa;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const existingOwnerSa = await getPrimarySigningAuthorityForUser(integrationOwner);
|
|
308
|
+
if (existingOwnerSa) {
|
|
309
|
+
await ensureListingSigningAuthorityRelationship(listing, existingOwnerSa);
|
|
310
|
+
return existingOwnerSa;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const ownerDid = listing.slug
|
|
314
|
+
? getAppDidWeb(domain, listing.slug)
|
|
315
|
+
: getDidWeb(domain, integrationOwner.profileId);
|
|
316
|
+
const name = buildManagedSigningAuthorityName(listing);
|
|
317
|
+
const did = await provisionManagedSigningAuthority(
|
|
318
|
+
DEFAULT_SIGNING_AUTHORITY_ENDPOINT,
|
|
319
|
+
ownerDid,
|
|
320
|
+
name
|
|
321
|
+
);
|
|
322
|
+
const signingAuthority = await upsertSigningAuthority(DEFAULT_SIGNING_AUTHORITY_ENDPOINT);
|
|
323
|
+
|
|
324
|
+
await createUseSigningAuthorityRelationship(
|
|
325
|
+
integrationOwner,
|
|
326
|
+
signingAuthority,
|
|
327
|
+
name,
|
|
328
|
+
did,
|
|
329
|
+
true
|
|
330
|
+
);
|
|
331
|
+
await associateListingWithSigningAuthority(
|
|
332
|
+
listing.listing_id,
|
|
333
|
+
DEFAULT_SIGNING_AUTHORITY_ENDPOINT,
|
|
334
|
+
{
|
|
335
|
+
name,
|
|
336
|
+
did,
|
|
337
|
+
isPrimary: true,
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
invalidateListingSigningAuthorityCache(listing.listing_id);
|
|
341
|
+
await deleteDidDocForProfile(integrationOwner.profileId);
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
signingAuthority: { endpoint: DEFAULT_SIGNING_AUTHORITY_ENDPOINT },
|
|
345
|
+
relationship: { name, did, isPrimary: true },
|
|
346
|
+
} as SigningAuthorityForUserType;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const getReusableInlineTemplateBoost = async ({
|
|
350
|
+
integrationId,
|
|
351
|
+
templateAlias,
|
|
352
|
+
contentHash,
|
|
353
|
+
templateVersion,
|
|
354
|
+
}: {
|
|
355
|
+
integrationId: string;
|
|
356
|
+
templateAlias: string;
|
|
357
|
+
contentHash: string;
|
|
358
|
+
templateVersion: number;
|
|
359
|
+
}): Promise<BoostInstance | null> => {
|
|
360
|
+
const result = await neogma.queryRunner.run(
|
|
361
|
+
`MATCH (boost:Boost)
|
|
362
|
+
WHERE boost.\`meta.integrationId\` = $integrationId
|
|
363
|
+
AND boost.\`meta.templateAlias\` = $templateAlias
|
|
364
|
+
AND boost.\`meta.inlineTemplateHash\` = $contentHash
|
|
365
|
+
AND boost.\`meta.inlineTemplateVersion\` = $templateVersion
|
|
366
|
+
RETURN boost.id AS id
|
|
367
|
+
ORDER BY boost.id DESC
|
|
368
|
+
LIMIT 1`,
|
|
369
|
+
{
|
|
370
|
+
integrationId,
|
|
371
|
+
templateAlias,
|
|
372
|
+
contentHash,
|
|
373
|
+
templateVersion,
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const id = result.records[0]?.get('id');
|
|
378
|
+
|
|
379
|
+
if (typeof id !== 'string') {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Boost.findOne({ where: { id } });
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export const upsertInlineTemplateBoostForListing = async ({
|
|
387
|
+
listing,
|
|
388
|
+
integration,
|
|
389
|
+
templateAlias,
|
|
390
|
+
template,
|
|
391
|
+
domain,
|
|
392
|
+
desiredVersion,
|
|
393
|
+
}: {
|
|
394
|
+
listing: AppStoreListingType;
|
|
395
|
+
integration: IntegrationType;
|
|
396
|
+
templateAlias: string;
|
|
397
|
+
template: InlineCredentialTemplate;
|
|
398
|
+
domain: string;
|
|
399
|
+
desiredVersion?: number;
|
|
400
|
+
}): Promise<UpsertInlineTemplateBoostResult> => {
|
|
401
|
+
validateInlineTemplateOrThrow(template);
|
|
402
|
+
|
|
403
|
+
const { credentialTemplateJson, variableManifest, category, walletSkills, contentHash } =
|
|
404
|
+
computeInlineTemplateHash(template);
|
|
405
|
+
const existing = await getBoostForListingByTemplateAlias(
|
|
406
|
+
listing.listing_id,
|
|
407
|
+
templateAlias,
|
|
408
|
+
domain
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
if (existing) {
|
|
412
|
+
const existingMeta = await readInlineTemplateBoostMeta(existing.boost);
|
|
413
|
+
const existingVersion =
|
|
414
|
+
typeof existingMeta.inlineTemplateVersion === 'number'
|
|
415
|
+
? existingMeta.inlineTemplateVersion
|
|
416
|
+
: 1;
|
|
417
|
+
const targetVersion = desiredVersion ?? existingVersion;
|
|
418
|
+
|
|
419
|
+
if (existingMeta.inlineTemplateHash === contentHash && existingVersion === targetVersion) {
|
|
420
|
+
return {
|
|
421
|
+
boost: existing.boost,
|
|
422
|
+
boostUri: existing.boostUri,
|
|
423
|
+
templateVersion: targetVersion,
|
|
424
|
+
manifest: variableManifest,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const reusableBoost = await getReusableInlineTemplateBoost({
|
|
429
|
+
integrationId: integration.id,
|
|
430
|
+
templateAlias,
|
|
431
|
+
contentHash,
|
|
432
|
+
templateVersion: targetVersion,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
if (reusableBoost) {
|
|
436
|
+
const reusableBoostUri = getBoostUri(reusableBoost.id, domain);
|
|
437
|
+
|
|
438
|
+
await updateBoost(existing.boost, {
|
|
439
|
+
meta: {
|
|
440
|
+
supersededAt: new Date().toISOString(),
|
|
441
|
+
supersededBy: reusableBoostUri,
|
|
442
|
+
},
|
|
443
|
+
});
|
|
444
|
+
await removeBoostFromListing(listing.listing_id, templateAlias);
|
|
445
|
+
await associateBoostWithListing(listing.listing_id, reusableBoostUri, templateAlias);
|
|
446
|
+
invalidateBoostForListingCache(listing.listing_id, templateAlias, domain);
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
boost: reusableBoost,
|
|
450
|
+
boostUri: reusableBoostUri,
|
|
451
|
+
templateVersion: targetVersion,
|
|
452
|
+
manifest: variableManifest,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const nextVersion = desiredVersion ?? existingVersion + 1;
|
|
457
|
+
|
|
458
|
+
const newBoost = await createBoostForListing(
|
|
459
|
+
parseInlineTemplateJson(credentialTemplateJson) as UnsignedVC,
|
|
460
|
+
listing,
|
|
461
|
+
{
|
|
462
|
+
status: 'LIVE',
|
|
463
|
+
category,
|
|
464
|
+
meta: {
|
|
465
|
+
integrationId: integration.id,
|
|
466
|
+
templateAlias,
|
|
467
|
+
inlineTemplateHash: contentHash,
|
|
468
|
+
inlineTemplateVersion: nextVersion,
|
|
469
|
+
inlineVariableManifest: JSON.stringify(variableManifest),
|
|
470
|
+
inlineWalletSkills: walletSkills ? JSON.stringify(walletSkills) : undefined,
|
|
471
|
+
source: INLINE_TEMPLATE_SOURCE,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
domain
|
|
475
|
+
);
|
|
476
|
+
const newBoostUri = getBoostUri(newBoost.id, domain);
|
|
477
|
+
|
|
478
|
+
await updateBoost(existing.boost, {
|
|
479
|
+
meta: {
|
|
480
|
+
supersededAt: new Date().toISOString(),
|
|
481
|
+
supersededBy: newBoostUri,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
await removeBoostFromListing(listing.listing_id, templateAlias);
|
|
485
|
+
await associateBoostWithListing(listing.listing_id, newBoostUri, templateAlias);
|
|
486
|
+
invalidateBoostForListingCache(listing.listing_id, templateAlias, domain);
|
|
487
|
+
|
|
488
|
+
return {
|
|
489
|
+
boost: newBoost,
|
|
490
|
+
boostUri: newBoostUri,
|
|
491
|
+
templateVersion: nextVersion,
|
|
492
|
+
manifest: variableManifest,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const initialVersion = desiredVersion ?? 1;
|
|
497
|
+
|
|
498
|
+
const reusableBoost = await getReusableInlineTemplateBoost({
|
|
499
|
+
integrationId: integration.id,
|
|
500
|
+
templateAlias,
|
|
501
|
+
contentHash,
|
|
502
|
+
templateVersion: initialVersion,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (reusableBoost) {
|
|
506
|
+
const reusableBoostUri = getBoostUri(reusableBoost.id, domain);
|
|
507
|
+
|
|
508
|
+
await associateBoostWithListing(listing.listing_id, reusableBoostUri, templateAlias);
|
|
509
|
+
invalidateBoostForListingCache(listing.listing_id, templateAlias, domain);
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
boost: reusableBoost,
|
|
513
|
+
boostUri: reusableBoostUri,
|
|
514
|
+
templateVersion: initialVersion,
|
|
515
|
+
manifest: variableManifest,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const boost = await createBoostForListing(
|
|
520
|
+
parseInlineTemplateJson(credentialTemplateJson) as UnsignedVC,
|
|
521
|
+
listing,
|
|
522
|
+
{
|
|
523
|
+
status: 'LIVE',
|
|
524
|
+
category,
|
|
525
|
+
meta: {
|
|
526
|
+
integrationId: integration.id,
|
|
527
|
+
templateAlias,
|
|
528
|
+
inlineTemplateHash: contentHash,
|
|
529
|
+
inlineTemplateVersion: initialVersion,
|
|
530
|
+
inlineVariableManifest: JSON.stringify(variableManifest),
|
|
531
|
+
inlineWalletSkills: walletSkills ? JSON.stringify(walletSkills) : undefined,
|
|
532
|
+
source: INLINE_TEMPLATE_SOURCE,
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
domain
|
|
536
|
+
);
|
|
537
|
+
const boostUri = getBoostUri(boost.id, domain);
|
|
538
|
+
|
|
539
|
+
await associateBoostWithListing(listing.listing_id, boostUri, templateAlias);
|
|
540
|
+
invalidateBoostForListingCache(listing.listing_id, templateAlias, domain);
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
boost,
|
|
544
|
+
boostUri,
|
|
545
|
+
templateVersion: initialVersion,
|
|
546
|
+
manifest: variableManifest,
|
|
547
|
+
};
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const parseInlineTemplateJson = (templateJson: string): Record<string, unknown> => {
|
|
551
|
+
try {
|
|
552
|
+
return JSON.parse(templateJson) as Record<string, unknown>;
|
|
553
|
+
} catch (error) {
|
|
554
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
555
|
+
throw new TRPCError({
|
|
556
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
557
|
+
message: `Compiled inline template produced invalid JSON: ${message}`,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
};
|
|
@@ -38,6 +38,10 @@ export type NotificationMessageKey =
|
|
|
38
38
|
| 'appListingWithdrawn'
|
|
39
39
|
| 'appListingApproved'
|
|
40
40
|
| 'appListingRejected'
|
|
41
|
+
| 'contractRequestReceived'
|
|
42
|
+
| 'contractRequestAccepted'
|
|
43
|
+
| 'contractRequestDenied'
|
|
44
|
+
| 'contractRequestCancelled'
|
|
41
45
|
| 'consentFlowViewRequest'
|
|
42
46
|
| 'consentFlowInvite'
|
|
43
47
|
| 'consentFlowShare'
|
|
@@ -57,7 +61,8 @@ export type NotificationMessageKey =
|
|
|
57
61
|
| 'credentialRestoredUnnamed'
|
|
58
62
|
// --- Managed credential refresh (LC-2136) — generic copy only: never name the
|
|
59
63
|
// credential, the holder's claims, or implementation terms (refresh/sync/etc.) ---
|
|
60
|
-
| 'credentialRefreshed'
|
|
64
|
+
| 'credentialRefreshed'
|
|
65
|
+
| 'shareViewed';
|
|
61
66
|
|
|
62
67
|
type MessageTemplate = { title: string; body: string };
|
|
63
68
|
type LocaleCatalog = Record<NotificationMessageKey, MessageTemplate>;
|
|
@@ -148,6 +153,22 @@ const en: LocaleCatalog = {
|
|
|
148
153
|
title: 'App Listing Needs Changes',
|
|
149
154
|
body: '"{displayName}" was not approved. Please review and resubmit.',
|
|
150
155
|
},
|
|
156
|
+
contractRequestReceived: {
|
|
157
|
+
title: 'Connection request',
|
|
158
|
+
body: '{referrer} would like to connect you with {contractOwner}.',
|
|
159
|
+
},
|
|
160
|
+
contractRequestAccepted: {
|
|
161
|
+
title: 'Connection request accepted',
|
|
162
|
+
body: '{name} accepted the request.',
|
|
163
|
+
},
|
|
164
|
+
contractRequestDenied: {
|
|
165
|
+
title: 'Connection request declined',
|
|
166
|
+
body: '{name} declined the request.',
|
|
167
|
+
},
|
|
168
|
+
contractRequestCancelled: {
|
|
169
|
+
title: 'Connection request cancelled',
|
|
170
|
+
body: '{name} cancelled the request.',
|
|
171
|
+
},
|
|
151
172
|
consentFlowViewRequest: {
|
|
152
173
|
title: 'AI Insights',
|
|
153
174
|
body: '{name} has requested to view your insights.',
|
|
@@ -216,6 +237,10 @@ const en: LocaleCatalog = {
|
|
|
216
237
|
title: 'Credential updated',
|
|
217
238
|
body: '{from} updated one of your credentials.',
|
|
218
239
|
},
|
|
240
|
+
shareViewed: {
|
|
241
|
+
title: 'Share viewed',
|
|
242
|
+
body: 'Your share “{title} ({count})” was viewed.',
|
|
243
|
+
},
|
|
219
244
|
};
|
|
220
245
|
|
|
221
246
|
const es: LocaleCatalog = {
|
|
@@ -299,6 +324,22 @@ const es: LocaleCatalog = {
|
|
|
299
324
|
title: 'La aplicación necesita cambios',
|
|
300
325
|
body: '"{displayName}" no fue aprobada. Revísala y vuelve a enviarla.',
|
|
301
326
|
},
|
|
327
|
+
contractRequestReceived: {
|
|
328
|
+
title: 'Solicitud de conexión',
|
|
329
|
+
body: '{referrer} quiere conectarte con {contractOwner}.',
|
|
330
|
+
},
|
|
331
|
+
contractRequestAccepted: {
|
|
332
|
+
title: 'Solicitud de conexión aceptada',
|
|
333
|
+
body: '{name} aceptó la solicitud.',
|
|
334
|
+
},
|
|
335
|
+
contractRequestDenied: {
|
|
336
|
+
title: 'Solicitud de conexión rechazada',
|
|
337
|
+
body: '{name} rechazó la solicitud.',
|
|
338
|
+
},
|
|
339
|
+
contractRequestCancelled: {
|
|
340
|
+
title: 'Solicitud de conexión cancelada',
|
|
341
|
+
body: '{name} canceló la solicitud.',
|
|
342
|
+
},
|
|
302
343
|
consentFlowViewRequest: {
|
|
303
344
|
title: 'AI Insights',
|
|
304
345
|
body: '{name} ha solicitado ver tus análisis.',
|
|
@@ -367,6 +408,10 @@ const es: LocaleCatalog = {
|
|
|
367
408
|
title: 'Credencial actualizada',
|
|
368
409
|
body: '{from} actualizó una de tus credenciales.',
|
|
369
410
|
},
|
|
411
|
+
shareViewed: {
|
|
412
|
+
title: 'Enlace visto',
|
|
413
|
+
body: 'Se vio tu enlace «{title} ({count})».',
|
|
414
|
+
},
|
|
370
415
|
};
|
|
371
416
|
|
|
372
417
|
const fr: LocaleCatalog = {
|
|
@@ -450,6 +495,22 @@ const fr: LocaleCatalog = {
|
|
|
450
495
|
title: "L'application nécessite des modifications",
|
|
451
496
|
body: "« {displayName} » n'a pas été approuvée. Veuillez la réviser et la soumettre à nouveau.",
|
|
452
497
|
},
|
|
498
|
+
contractRequestReceived: {
|
|
499
|
+
title: 'Demande de connexion',
|
|
500
|
+
body: '{referrer} souhaite vous mettre en relation avec {contractOwner}.',
|
|
501
|
+
},
|
|
502
|
+
contractRequestAccepted: {
|
|
503
|
+
title: 'Demande de connexion acceptée',
|
|
504
|
+
body: '{name} a accepté la demande.',
|
|
505
|
+
},
|
|
506
|
+
contractRequestDenied: {
|
|
507
|
+
title: 'Demande de connexion refusée',
|
|
508
|
+
body: '{name} a refusé la demande.',
|
|
509
|
+
},
|
|
510
|
+
contractRequestCancelled: {
|
|
511
|
+
title: 'Demande de connexion annulée',
|
|
512
|
+
body: '{name} a annulé la demande.',
|
|
513
|
+
},
|
|
453
514
|
consentFlowViewRequest: {
|
|
454
515
|
title: 'AI Insights',
|
|
455
516
|
body: '{name} a demandé à consulter vos analyses.',
|
|
@@ -518,6 +579,10 @@ const fr: LocaleCatalog = {
|
|
|
518
579
|
title: 'Titre mis à jour',
|
|
519
580
|
body: "{from} a mis à jour l'un de vos titres.",
|
|
520
581
|
},
|
|
582
|
+
shareViewed: {
|
|
583
|
+
title: 'Partage consulté',
|
|
584
|
+
body: 'Votre partage « {title} ({count}) » a été consulté.',
|
|
585
|
+
},
|
|
521
586
|
};
|
|
522
587
|
|
|
523
588
|
const ar: LocaleCatalog = {
|
|
@@ -601,6 +666,13 @@ const ar: LocaleCatalog = {
|
|
|
601
666
|
title: 'التطبيق يحتاج إلى تعديلات',
|
|
602
667
|
body: 'لم تتم الموافقة على "{displayName}". يرجى مراجعته وإعادة إرساله.',
|
|
603
668
|
},
|
|
669
|
+
contractRequestReceived: {
|
|
670
|
+
title: 'طلب اتصال',
|
|
671
|
+
body: 'يريد {referrer} توصيلك بـ {contractOwner}.',
|
|
672
|
+
},
|
|
673
|
+
contractRequestAccepted: { title: 'تم قبول طلب الاتصال', body: 'قبل {name} الطلب.' },
|
|
674
|
+
contractRequestDenied: { title: 'تم رفض طلب الاتصال', body: 'رفض {name} الطلب.' },
|
|
675
|
+
contractRequestCancelled: { title: 'تم إلغاء طلب الاتصال', body: 'ألغى {name} الطلب.' },
|
|
604
676
|
consentFlowViewRequest: {
|
|
605
677
|
title: 'رؤى الذكاء الاصطناعي',
|
|
606
678
|
body: 'طلب {name} الاطلاع على رؤاك.',
|
|
@@ -669,6 +741,10 @@ const ar: LocaleCatalog = {
|
|
|
669
741
|
title: 'تم تحديث الشهادة',
|
|
670
742
|
body: 'قام {from} بتحديث إحدى شهاداتك.',
|
|
671
743
|
},
|
|
744
|
+
shareViewed: {
|
|
745
|
+
title: 'تمت مشاهدة المشاركة',
|
|
746
|
+
body: 'تمت مشاهدة مشاركتك «{title} ({count})».',
|
|
747
|
+
},
|
|
672
748
|
};
|
|
673
749
|
|
|
674
750
|
const CATALOGS: Record<NotificationLocale, LocaleCatalog> = { en, es, fr, ar };
|