From 7808e9d2b6da33e8fea8975b5541e0cd12298b26 Mon Sep 17 00:00:00 2001 From: Aitor <1726644+aaitor@users.noreply.github.com> Date: Mon, 30 Sep 2024 17:46:09 +0200 Subject: [PATCH 1/2] feat: extra attributes on agents registration --- integration/nevermined/DDO.test.ts | 1 + integration/nevermined/SearchAsset.test.ts | 3 +- src/ddo/DDO.ts | 41 ++++++++++++++++++++++ src/ddo/NvmAppMetadata.ts | 2 ++ src/nevermined/api/RegistryBaseApi.ts | 36 ++++++++++++++----- src/types/DDOTypes.ts | 19 ++++++++++ 6 files changed, 92 insertions(+), 10 deletions(-) diff --git a/integration/nevermined/DDO.test.ts b/integration/nevermined/DDO.test.ts index ab39bf6c3..5976707bc 100644 --- a/integration/nevermined/DDO.test.ts +++ b/integration/nevermined/DDO.test.ts @@ -496,6 +496,7 @@ describe('DDO Tests', () => { false, true, 'v1', + 'http://localhost:3001/', ) metadata.main.type = 'agent' const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({ diff --git a/integration/nevermined/SearchAsset.test.ts b/integration/nevermined/SearchAsset.test.ts index a4e1858c7..00143f832 100644 --- a/integration/nevermined/SearchAsset.test.ts +++ b/integration/nevermined/SearchAsset.test.ts @@ -59,6 +59,7 @@ describe('Search Asset', () => { false, true, 'v1', + 'http://localhost/', ) metadata = getMetadata(undefined, 'Test4') agentMetadata.userId = userId @@ -138,7 +139,7 @@ describe('Search Asset', () => { 'dataset', 'TestAsset', ) - assert.equal(ddosWithTextFilter.length, 4) + assert.isAtLeast(ddosWithTextFilter.length, 4) const { results: ddosServices } = await neverminedOffline.search.byType('service') assert.isAtLeast(ddosServices.length, 2) diff --git a/src/ddo/DDO.ts b/src/ddo/DDO.ts index f81d79161..dd235fffe 100644 --- a/src/ddo/DDO.ts +++ b/src/ddo/DDO.ts @@ -1,6 +1,7 @@ import { jsonReplacer } from '../common/helpers' import { DDOConditionNotFoundError, + DDOError, DDOParamNotFoundError, DDOPriceNotFoundError, DDOServiceAlreadyExists, @@ -17,6 +18,7 @@ import { ConditionType, MetaData, MetaDataMain, + NeverminedQueryProtocolEndpoints, NvmConfig, Proof, PublicKey, @@ -27,6 +29,7 @@ import { ServiceNFTAccess, ServiceNFTSales, ServiceType, + WebService, } from '../types/DDOTypes' import { didPrefixed, zeroX } from '../utils/ConversionTypeHelpers' @@ -775,4 +778,42 @@ export class DDO { ): DDO { return DDO.deserialize(DDO.serialize(ddo).replaceAll(paramName, value)) } + + public static parseDDOWebServiceAttributes(webService: WebService, did: string) { + try { + if (webService.implementsQueryProtocol === true || webService.isNeverminedHosted === true) { + if (!webService.serviceHost) { + const errorMessage = `Metadata error: Attribute serviceHost is mandatory when implementsQueryProtocol or isNeverminedHosted` + throw new Error(errorMessage) + } + const serviceHostUrl = new URL(webService.serviceHost) + const serviceHost = serviceHostUrl.origin + + NeverminedQueryProtocolEndpoints.map((endpoint) => { + Object.entries(endpoint).flatMap(([key, value]) => { + const endpointUrl = `${serviceHost}${value}`.replace('{DID}', did) + webService.endpoints?.push({ [key]: endpointUrl }) + }) + }) + + if (webService?.isNeverminedHosted) { + const openApiEndpoint = `${serviceHost}/api/v1/docs-json` + + if (webService.openEndpoints) webService.openEndpoints.push(openApiEndpoint) + else webService.openEndpoints = [openApiEndpoint] + } + + webService.internalAttributes = { + authentication: { + type: 'bearer', + token: '', + }, + headers: [{ Authorization: `Bearer ` }], + } + } + return webService + } catch (error) { + throw new DDOError(`Unable to parse service attributes: ${error.message}`) + } + } } diff --git a/src/ddo/NvmAppMetadata.ts b/src/ddo/NvmAppMetadata.ts index 339fe179e..a4457382c 100644 --- a/src/ddo/NvmAppMetadata.ts +++ b/src/ddo/NvmAppMetadata.ts @@ -126,6 +126,7 @@ export class NvmAppMetadata { isPriceDynamic: boolean = false, isNeverminedHosted: boolean = false, queryProtocol: string | undefined = undefined, + serviceHost: string | undefined = undefined, customData: { [key: string]: any } = {}, nonce: string | number = Math.random(), ): MetaData { @@ -147,6 +148,7 @@ export class NvmAppMetadata { isNeverminedHosted, implementsQueryProtocol: queryProtocol ? true : false, queryProtocolVersion: queryProtocol ? queryProtocol : undefined, + ...(serviceHost && { serviceHost }), }, ...({ nonce } as any), }, diff --git a/src/nevermined/api/RegistryBaseApi.ts b/src/nevermined/api/RegistryBaseApi.ts index 479035009..1d07cfa3f 100644 --- a/src/nevermined/api/RegistryBaseApi.ts +++ b/src/nevermined/api/RegistryBaseApi.ts @@ -3,7 +3,7 @@ import { Instantiable, InstantiableConfig } from '../../Instantiable.abstract' import { generateId } from '../../common/helpers' import { DEFAULT_ENCRYPTION_METHOD, ZeroAddress } from '../../constants/AssetConstants' import { DDO } from '../../ddo/DDO' -import { AssetError } from '../../errors/NeverminedErrors' +import { AssetError, DDOError } from '../../errors/NeverminedErrors' import { DEFAULT_REGISTRATION_ACTIVITY_ID } from '../../keeper/contracts/Provenance' import { AssetAttributes } from '../../models/AssetAttributes' import { AssetPrice } from '../../models/AssetPrice' @@ -158,14 +158,32 @@ export abstract class RegistryBaseApi extends Instantiable { assetAttributes.metadata.main.type === 'assistant' || assetAttributes.metadata.main.type === 'agent' ) { - const encryptedServiceAttributesResponse = await this.nevermined.services.node.encrypt( - ddo.id, - JSON.stringify(assetAttributes.metadata.main.webService.internalAttributes), - new String(assetAttributes.encryptionMethod), - ) - encryptedAttributes = JSON.parse(encryptedServiceAttributesResponse)['hash'] - assetAttributes.metadata.main.webService.encryptedAttributes = encryptedAttributes - assetAttributes.metadata.main.webService.internalAttributes = undefined + try { + const _attr = { ...assetAttributes.metadata.main.webService } + assetAttributes.metadata.main.webService = DDO.parseDDOWebServiceAttributes( + _attr, + ddo.id, + ) + + assetAttributes.metadata.additionalInformation = { + ...assetAttributes.metadata.additionalInformation, + customData: { + openApi: assetAttributes.metadata.main.webService?.openEndpoints[0], + }, + } + + const encryptedServiceAttributesResponse = await this.nevermined.services.node.encrypt( + ddo.id, + JSON.stringify(assetAttributes.metadata.main.webService.internalAttributes), + new String(assetAttributes.encryptionMethod), + ) + encryptedAttributes = JSON.parse(encryptedServiceAttributesResponse)['hash'] + assetAttributes.metadata.main.webService.encryptedAttributes = encryptedAttributes + assetAttributes.metadata.main.webService.internalAttributes = undefined + } catch (error) { + this.logger.log(`Unable to parse service attributes: ${error.message}`) + throw new DDOError(`Unable to parse service attributes: ${error.message}`) + } } } diff --git a/src/types/DDOTypes.ts b/src/types/DDOTypes.ts index a76eb01ad..fc635a621 100644 --- a/src/types/DDOTypes.ts +++ b/src/types/DDOTypes.ts @@ -189,6 +189,17 @@ export interface MetaDataExternalResource { encryption?: 'dtp' | 'dleq' } +export const NeverminedQueryProtocolEndpoints: { [verb: string]: string }[] = [ + { GET: '/api/v1/agents' }, + { POST: '/api/v1/agents/search' }, + { GET: '/api/v1/agents/steps' }, + { GET: '/api/v1/agents/{DID}/tasks/(.*)/steps' }, + { POST: '/api/v1/agents/{DID}/tasks/(.*)/steps' }, + { PUT: '/api/v1/agents/{DID}/tasks/(.*)/step/(.*)' }, + { POST: '/api/v1/agents/{DID}/tasks' }, + { GET: '/api/v1/agents/{DID}/tasks/(.*)' }, +] + /** * Interface describing an asset of type `service` */ @@ -225,6 +236,14 @@ export interface WebService { */ isNeverminedHosted?: boolean + /** + * Host of the service where the service is running. + * This attribute when is given AND the service is hosted by Nevermined or implements the Query Protocol, + * allows to populate automatically the endpoints using the service host. + * Example: https://my-service.com or https://backend.nevermined.app + */ + serviceHost?: string + /** * Flag to indicate if the service implements the Nevermined Query Protocol. * See {@link https://docs.nevermined.io/docs/protocol/query-protocol} From a2f53c1633276aac31d14233f9d8bccbcc950648 Mon Sep 17 00:00:00 2001 From: Aitor <1726644+aaitor@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:21:31 +0200 Subject: [PATCH 2/2] chore: version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 053273a13..f851ddac5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nevermined-io/sdk", - "version": "3.0.35", + "version": "3.0.36", "description": "Javascript SDK for connecting with Nevermined Data Platform ", "main": "./dist/node/sdk.js", "typings": "./dist/node/sdk.d.ts",