diff --git a/packages/js/client/src/Web3ApiClient.ts b/packages/js/client/src/Web3ApiClient.ts index 6e4cf11d6c..22dab4569c 100644 --- a/packages/js/client/src/Web3ApiClient.ts +++ b/packages/js/client/src/Web3ApiClient.ts @@ -100,6 +100,8 @@ export class Web3ApiClient implements Client { this._validateConfig(); + this._sanitizeConfig(); + Tracer.setAttribute("config", this._config); } catch (error) { Tracer.recordException(error); @@ -362,9 +364,7 @@ export class Web3ApiClient implements Client { if (freq && (freq.ms || freq.sec || freq.min || freq.hours)) { frequency = (freq.ms ?? 0) + - ((freq.hours ?? 0) * 3600 + - (freq.min ?? 0) * 60 + - (freq.sec ?? 0)) * + ((freq.hours ?? 0) * 3600 + (freq.min ?? 0) * 60 + (freq.sec ?? 0)) * 1000; } else { frequency = 60000; @@ -525,6 +525,64 @@ export class Web3ApiClient implements Client { } } + @Tracer.traceMethod("Web3ApiClient: sanitizeConfig") + private _sanitizeConfig(): void { + this._sanitizeInterfacesAndImplementations(); + } + + // Make sure interface URIs are unique and that all of their implementation URIs are unique + // If not, then merge them + @Tracer.traceMethod("Web3ApiClient: sanitizeInterfacesAndImplementations") + private _sanitizeInterfacesAndImplementations(): void { + const interfaces = this._config.interfaces; + // Interface hash map used to keep track of interfaces with same URI + // A set is used to keep track of unique implementation URIs + const addedInterfacesHashMap = new Map>(); + + for (const interfaceImplementations of interfaces) { + const interfaceUri = interfaceImplementations.interface.uri; + + if (!addedInterfacesHashMap.has(interfaceUri)) { + // If the interface is not added yet then just add it along with its implementations + addedInterfacesHashMap.set( + interfaceUri, + new Set(interfaceImplementations.implementations.map((x) => x.uri)) + ); + } else { + const existingInterfaceImplementations = addedInterfacesHashMap.get( + interfaceUri + ) as Set; + + // Get implementations to add to existing set of implementations + const newImplementationUris = interfaceImplementations.implementations.map( + (x) => x.uri + ); + + // Add new implementations to existing set + newImplementationUris.forEach( + existingInterfaceImplementations.add, + existingInterfaceImplementations + ); + } + } + + // Collection of unique interfaces with implementations merged + const sanitizedInterfaces: InterfaceImplementations[] = []; + + // Go through the unique hash map of interfaces and implementations and add them to the sanitized interfaces + for (const [ + interfaceUri, + implementationSet, + ] of addedInterfacesHashMap.entries()) { + sanitizedInterfaces.push({ + interface: new Uri(interfaceUri), + implementations: [...implementationSet].map((x) => new Uri(x)), + }); + } + + this._config.interfaces = sanitizedInterfaces; + } + @Tracer.traceMethod("Web3ApiClient: validateConfig") private _validateConfig(): void { // Require plugins to use non-interface URIs diff --git a/packages/js/client/src/__tests__/Web3ApiClient.spec.ts b/packages/js/client/src/__tests__/Web3ApiClient.spec.ts index a6eb70c16f..a3dd0f4ccb 100644 --- a/packages/js/client/src/__tests__/Web3ApiClient.spec.ts +++ b/packages/js/client/src/__tests__/Web3ApiClient.spec.ts @@ -2810,7 +2810,10 @@ enum Logger_LogLevel @imported( it("e2e Interface invoke method", async () => { const interfaceUri = "w3://ens/interface.eth"; // Build interface polywrapper - await runCLI({ args: ["build"], cwd: `${GetPathToTestApis()}/interface-invoke/test-interface`}); + await runCLI({ + args: ["build"], + cwd: `${GetPathToTestApis()}/interface-invoke/test-interface`, + }); const implementationApi = await buildAndDeployApi( `${GetPathToTestApis()}/interface-invoke/test-implementation`, @@ -2824,7 +2827,7 @@ enum Logger_LogLevel @imported( { interface: interfaceUri, implementations: [implementationUri], - } + }, ], }); @@ -2864,7 +2867,7 @@ enum Logger_LogLevel @imported( mutationMethod( arg: 1 ) - }` + }`, }); expect(mutation.errors).toBeFalsy(); @@ -2872,4 +2875,68 @@ enum Logger_LogLevel @imported( expect(mutation.data?.mutationMethod).toBe(1); }); + it("merge user-defined interface implementations with each other", async () => { + const interfaceUri = "w3://ens/interface.eth"; + const implementationUri1 = "w3://ens/implementation1.eth"; + const implementationUri2 = "w3://ens/implementation2.eth"; + + const client = new Web3ApiClient({ + interfaces: [ + { + interface: interfaceUri, + implementations: [implementationUri1], + }, + { + interface: interfaceUri, + implementations: [implementationUri2], + }, + ], + }); + + const interfaces = client + .getInterfaces() + .filter((x) => x.interface.uri === interfaceUri); + expect(interfaces.length).toEqual(1); + + const implementationUris = interfaces[0].implementations; + + expect(implementationUris).toEqual([ + new Uri(implementationUri1), + new Uri(implementationUri2), + ]); + }); + + it("merge user-defined interface implementations with defaults", async () => { + const interfaceUri = coreInterfaceUris.uriResolver.uri; + const implementationUri1 = "w3://ens/implementation1.eth"; + const implementationUri2 = "w3://ens/implementation2.eth"; + + const client = new Web3ApiClient({ + interfaces: [ + { + interface: interfaceUri, + implementations: [implementationUri1], + }, + { + interface: interfaceUri, + implementations: [implementationUri2], + }, + ], + }); + + const interfaces = client + .getInterfaces() + .filter((x) => x.interface.uri === interfaceUri); + expect(interfaces.length).toEqual(1); + + const implementationUris = interfaces[0].implementations; + + expect(implementationUris).toEqual([ + new Uri(implementationUri1), + new Uri(implementationUri2), + ...getDefaultClientConfig().interfaces.find( + (x) => x.interface.uri === interfaceUri + )!.implementations, + ]); + }); });