Skip to content

Commit

Permalink
Merge pull request #717 from nevermined-io/feat/query_protocol
Browse files Browse the repository at this point in the history
feat: extra attributes on agents registration
  • Loading branch information
aaitor authored Oct 1, 2024
2 parents e8c0c38 + e188bff commit e40871a
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 11 deletions.
1 change: 1 addition & 0 deletions integration/nevermined/DDO.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ describe('DDO Tests', () => {
false,
true,
'v1',
'http://localhost:3001/',
)
metadata.main.type = 'agent'
const nftAttributes = NFTAttributes.getCreditsSubscriptionInstance({
Expand Down
3 changes: 2 additions & 1 deletion integration/nevermined/SearchAsset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('Search Asset', () => {
false,
true,
'v1',
'http://localhost/',
)
metadata = getMetadata(undefined, 'Test4')
agentMetadata.userId = userId
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
41 changes: 41 additions & 0 deletions src/ddo/DDO.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { jsonReplacer } from '../common/helpers'
import {
DDOConditionNotFoundError,
DDOError,
DDOParamNotFoundError,
DDOPriceNotFoundError,
DDOServiceAlreadyExists,
Expand All @@ -17,6 +18,7 @@ import {
ConditionType,
MetaData,
MetaDataMain,
NeverminedQueryProtocolEndpoints,
NvmConfig,
Proof,
PublicKey,
Expand All @@ -27,6 +29,7 @@ import {
ServiceNFTAccess,
ServiceNFTSales,
ServiceType,
WebService,
} from '../types/DDOTypes'
import { didPrefixed, zeroX } from '../utils/ConversionTypeHelpers'

Expand Down Expand Up @@ -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}`)
}
}
}
2 changes: 2 additions & 0 deletions src/ddo/NvmAppMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -147,6 +148,7 @@ export class NvmAppMetadata {
isNeverminedHosted,
implementsQueryProtocol: queryProtocol ? true : false,
queryProtocolVersion: queryProtocol ? queryProtocol : undefined,
...(serviceHost && { serviceHost }),
},
...({ nonce } as any),
},
Expand Down
36 changes: 27 additions & 9 deletions src/nevermined/api/RegistryBaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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}`)
}
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/types/DDOTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
*/
Expand Down Expand Up @@ -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}
Expand Down

0 comments on commit e40871a

Please sign in to comment.