@learncard/network-brain-service 3.19.1 → 4.0.1
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 +44 -111
- package/AGENTS.md +52 -0
- package/CHANGELOG.md +120 -0
- package/README.md +9 -2
- package/bootstrapLambda.test.ts +72 -0
- package/bundle.mjs +9 -1
- 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/share-link/abandon.d.ts.map +1 -1
- 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/finalize.d.ts.map +1 -1
- package/dist/accesslayer/share-link/helpers.d.ts +3 -0
- package/dist/accesslayer/share-link/helpers.d.ts.map +1 -1
- package/dist/accesslayer/share-link/recovery.d.ts.map +1 -1
- package/dist/accesslayer/share-link/reserve.d.ts.map +1 -1
- package/dist/accesslayer/share-link/revoke.d.ts.map +1 -1
- package/dist/accesslayer/share-link/types.d.ts +8 -3
- package/dist/accesslayer/share-link/types.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 +5 -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/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/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/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/profile.helpers.d.ts +1 -0
- package/dist/helpers/profile.helpers.d.ts.map +1 -1
- 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/coordinator.d.ts.map +1 -1
- package/dist/helpers/share-link-coordinator/recovery-runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runner.d.ts +1 -0
- package/dist/helpers/share-link-maintenance/runner.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/runtime.d.ts +2 -0
- package/dist/helpers/share-link-maintenance/runtime.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/telemetry.d.ts.map +1 -1
- package/dist/helpers/share-link-maintenance/types.d.ts +4 -1
- package/dist/helpers/share-link-maintenance/types.d.ts.map +1 -1
- package/dist/helpers/share-link-owner-projection.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/production.d.ts +3 -5
- package/dist/helpers/share-link-policy/production.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts +1 -1
- package/dist/helpers/share-link-policy/resolver.d.ts.map +1 -1
- package/dist/helpers/share-link-policy/types.d.ts +8 -2
- package/dist/helpers/share-link-policy/types.d.ts.map +1 -1
- 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/ShareLink.d.ts +2 -0
- package/dist/models/ShareLink.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/share-link-constraints.d.ts.map +1 -1
- package/dist/openapi.d.ts +1 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/publish-metafile.json +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 +146 -3
- package/dist/routes/contracts.d.ts.map +1 -1
- package/dist/routes/credentials.d.ts +34 -0
- 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.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 +55 -0
- package/dist/routes/public-share-links.d.ts.map +1 -1
- package/dist/routes/share-links.d.ts +175 -14
- package/dist/routes/share-links.d.ts.map +1 -1
- 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/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 +30 -3
- 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 -195
- package/lambdaApp.ts +203 -0
- package/package.json +20 -19
- package/project.json +8 -1
- package/scripts/export-openapi.ts +20 -0
- package/scripts/offline-schema-environment.ts +18 -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 +29 -74
- package/shareLinkMaintenanceLambda.ts +13 -44
- 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 +116 -93
- 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 +240 -189
- 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/share-link/abandon.ts +20 -0
- package/src/accesslayer/share-link/attachment.ts +360 -0
- package/src/accesslayer/share-link/finalize.ts +55 -7
- package/src/accesslayer/share-link/helpers.ts +33 -2
- package/src/accesslayer/share-link/recovery.ts +20 -0
- package/src/accesslayer/share-link/reserve.ts +85 -6
- package/src/accesslayer/share-link/revoke.ts +35 -0
- package/src/accesslayer/share-link/types.ts +9 -4
- 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 +1 -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/docker-entry.ts +24 -0
- package/src/helpers/app-manifest.helpers.ts +185 -0
- 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/finalize-inbox.helpers.ts +163 -44
- package/src/helpers/inbox.helpers.ts +51 -66
- package/src/helpers/inline-template.helpers.ts +560 -0
- package/src/helpers/notificationMessages.ts +59 -0
- package/src/helpers/notificationQueue.helpers.ts +23 -5
- package/src/helpers/notifications.helpers.ts +25 -1
- package/src/helpers/profile.helpers.ts +6 -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/coordinator.ts +15 -2
- package/src/helpers/share-link-coordinator/recovery-runner.ts +15 -0
- package/src/helpers/share-link-maintenance/runner.ts +28 -0
- package/src/helpers/share-link-maintenance/runtime.ts +6 -0
- package/src/helpers/share-link-maintenance/telemetry.ts +3 -0
- package/src/helpers/share-link-maintenance/types.ts +2 -0
- package/src/helpers/share-link-owner-projection.ts +2 -0
- package/src/helpers/share-link-policy/production.ts +37 -17
- package/src/helpers/share-link-policy/resolver.ts +16 -11
- package/src/helpers/share-link-policy/types.ts +15 -7
- 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/ShareLink.ts +4 -0
- package/src/models/index.ts +23 -0
- package/src/models/share-link-constraints.ts +5 -0
- package/src/openapi.ts +44 -8
- package/src/routes/app-store.ts +1054 -27
- package/src/routes/boosts.ts +12 -14
- package/src/routes/contracts.ts +382 -262
- package/src/routes/inbox.ts +4 -2
- package/src/routes/index.ts +4 -1
- package/src/routes/profile-manager.ts +19 -0
- package/src/routes/profiles.ts +12 -0
- package/src/routes/public-share-links.ts +169 -0
- package/src/routes/share-links.ts +69 -0
- package/src/routes/workflows.ts +3 -2
- 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/guardian-gated-routes.spec.ts +36 -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 +24 -2
- package/test/openapi.spec.ts +14 -73
- package/test/profiles.spec.ts +30 -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-lifecycle.neo4j.spec.ts +145 -2
- package/test/share-link-maintenance.unit.spec.ts +32 -5
- package/test/share-link-owner-route.unit.spec.ts +70 -2
- package/test/share-link-policy.unit.spec.ts +18 -4
- package/test/share-link-production-policy.unit.spec.ts +58 -9
- package/test/share-link-public-route.unit.spec.ts +279 -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 +13 -0
- package/vitest.inbox-e2e.config.ts +2 -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'
|
|
@@ -149,6 +153,22 @@ const en: LocaleCatalog = {
|
|
|
149
153
|
title: 'App Listing Needs Changes',
|
|
150
154
|
body: '"{displayName}" was not approved. Please review and resubmit.',
|
|
151
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
|
+
},
|
|
152
172
|
consentFlowViewRequest: {
|
|
153
173
|
title: 'AI Insights',
|
|
154
174
|
body: '{name} has requested to view your insights.',
|
|
@@ -304,6 +324,22 @@ const es: LocaleCatalog = {
|
|
|
304
324
|
title: 'La aplicación necesita cambios',
|
|
305
325
|
body: '"{displayName}" no fue aprobada. Revísala y vuelve a enviarla.',
|
|
306
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
|
+
},
|
|
307
343
|
consentFlowViewRequest: {
|
|
308
344
|
title: 'AI Insights',
|
|
309
345
|
body: '{name} ha solicitado ver tus análisis.',
|
|
@@ -459,6 +495,22 @@ const fr: LocaleCatalog = {
|
|
|
459
495
|
title: "L'application nécessite des modifications",
|
|
460
496
|
body: "« {displayName} » n'a pas été approuvée. Veuillez la réviser et la soumettre à nouveau.",
|
|
461
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
|
+
},
|
|
462
514
|
consentFlowViewRequest: {
|
|
463
515
|
title: 'AI Insights',
|
|
464
516
|
body: '{name} a demandé à consulter vos analyses.',
|
|
@@ -614,6 +666,13 @@ const ar: LocaleCatalog = {
|
|
|
614
666
|
title: 'التطبيق يحتاج إلى تعديلات',
|
|
615
667
|
body: 'لم تتم الموافقة على "{displayName}". يرجى مراجعته وإعادة إرساله.',
|
|
616
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} الطلب.' },
|
|
617
676
|
consentFlowViewRequest: {
|
|
618
677
|
title: 'رؤى الذكاء الاصطناعي',
|
|
619
678
|
body: 'طلب {name} الاطلاع على رؤاك.',
|
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authorizeContractNotification } from '@helpers/contract-events.helpers';
|
|
2
|
+
import { ConsentFlowWebhookMetadataValidator, LCNNotificationValidator } from '@learncard/types';
|
|
2
3
|
|
|
3
4
|
import { acknowledgeConnectionPromptNotificationDelivery } from '@helpers/connectionPrompt.helpers';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
PermanentNotificationDeliveryError,
|
|
7
|
+
sendNotification,
|
|
8
|
+
} from '@helpers/notifications.helpers';
|
|
5
9
|
|
|
6
10
|
export const deliverQueuedNotification = async (body: string): Promise<void> => {
|
|
7
11
|
const notification = await LCNNotificationValidator.parseAsync(JSON.parse(body));
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
if (!(await authorizeContractNotification(notification))) return;
|
|
14
|
+
|
|
15
|
+
const isContractEvent = ConsentFlowWebhookMetadataValidator.safeParse(
|
|
16
|
+
notification.data?.metadata
|
|
17
|
+
).success;
|
|
18
|
+
let stored: boolean | undefined;
|
|
19
|
+
try {
|
|
20
|
+
stored = await sendNotification(notification, {
|
|
21
|
+
propagateDirectWebhookTransportErrors: true,
|
|
22
|
+
throwOnPermanentFailure: isContractEvent,
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// Acknowledge terminal contract rejections so SQS can discard its payload too.
|
|
26
|
+
// Other notification types keep their existing worker behavior.
|
|
27
|
+
if (isContractEvent && error instanceof PermanentNotificationDeliveryError) return;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
12
30
|
|
|
13
31
|
if (!stored) throw new Error('Notification was not durably stored');
|
|
14
32
|
|