diff --git a/packages/cli/src/lib/helpers/client.ts b/packages/cli/src/lib/helpers/client.ts index a8b6a4009e..1e42c8389f 100644 --- a/packages/cli/src/lib/helpers/client.ts +++ b/packages/cli/src/lib/helpers/client.ts @@ -1,4 +1,4 @@ -import { PluginRegistration } from "@polywrap/core-js"; +import { PluginRegistration, Env } from "@polywrap/core-js"; import { ensResolverPlugin } from "@polywrap/ens-resolver-plugin-js"; import { ethereumPlugin, @@ -18,6 +18,8 @@ interface SimpleClientConfig { export function getSimpleClient(config: SimpleClientConfig): PolywrapClient { const { ensAddress, ethProvider, ipfsProvider } = config; const plugins: PluginRegistration[] = []; + const envs: Env[] = []; + if (ensAddress) { plugins.push({ uri: "wrap://ens/ens-resolver.polywrap.eth", @@ -45,10 +47,15 @@ export function getSimpleClient(config: SimpleClientConfig): PolywrapClient { if (ipfsProvider) { plugins.push({ uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ + plugin: ipfsPlugin({}), + }); + + envs.push({ + uri: "wrap://ens/ipfs.polywrap.eth", + env: { provider: ipfsProvider, fallbackProviders: defaultIpfsProviders, - }), + }, }); } return new PolywrapClient({ plugins }); diff --git a/packages/cli/src/lib/test-env/client-config.ts b/packages/cli/src/lib/test-env/client-config.ts index 651db94a2f..5e968f1eb3 100644 --- a/packages/cli/src/lib/test-env/client-config.ts +++ b/packages/cli/src/lib/test-env/client-config.ts @@ -10,6 +10,7 @@ import { } from "@polywrap/ethereum-plugin-js"; import { ipfsPlugin } from "@polywrap/ipfs-plugin-js"; import { ensAddresses } from "@polywrap/test-env-js"; +import { Env } from "@polywrap/core-js"; export async function getTestEnvClientConfig(): Promise< Partial @@ -41,10 +42,7 @@ export async function getTestEnvClientConfig(): Promise< }, { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ - provider: ipfsProvider, - fallbackProviders: defaultIpfsProviders, - }), + plugin: ipfsPlugin({}), }, { uri: "wrap://ens/ens-resolver.polywrap.eth", @@ -56,7 +54,18 @@ export async function getTestEnvClientConfig(): Promise< }, ]; + const envs: Env[] = [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: ipfsProvider, + fallbackProviders: defaultIpfsProviders, + }, + }, + ]; + return { plugins, + envs, }; } diff --git a/packages/interfaces/ipfs/src/schema.graphql b/packages/interfaces/ipfs/src/schema.graphql index faa5fc9c9e..f54a6268f3 100644 --- a/packages/interfaces/ipfs/src/schema.graphql +++ b/packages/interfaces/ipfs/src/schema.graphql @@ -23,6 +23,11 @@ type Options { """ provider: String + """ + Fallback IPFS providers + """ + fallbackProviders: [String!] + """ Disable querying providers in parallel when resolving URIs """ diff --git a/packages/js/client-config-builder/src/bundles/default-client-config.ts b/packages/js/client-config-builder/src/bundles/default-client-config.ts index 76c9839ef3..c4e196d3ec 100644 --- a/packages/js/client-config-builder/src/bundles/default-client-config.ts +++ b/packages/js/client-config-builder/src/bundles/default-client-config.ts @@ -32,6 +32,13 @@ export const getDefaultClientConfig = ( provider: "https://api.thegraph.com", }, }, + { + uri: new Uri("wrap://ens/ipfs.polywrap.eth"), + env: { + provider: defaultIpfsProviders[0], + fallbackProviders: defaultIpfsProviders.slice(1), + }, + }, ], redirects: [ { @@ -51,10 +58,7 @@ export const getDefaultClientConfig = ( // IPFS is required for downloading Polywrap packages { uri: new Uri("wrap://ens/ipfs.polywrap.eth"), - plugin: ipfsPlugin({ - provider: defaultIpfsProviders[0], - fallbackProviders: defaultIpfsProviders.slice(1), - }), + plugin: ipfsPlugin({}), }, // ENS is required for resolving domain to IPFS hashes { diff --git a/packages/js/client/scripts/extractPluginConfigs.ts b/packages/js/client/scripts/extractPluginConfigs.ts index 5a3ea8c861..6c1be1447d 100644 --- a/packages/js/client/scripts/extractPluginConfigs.ts +++ b/packages/js/client/scripts/extractPluginConfigs.ts @@ -23,11 +23,11 @@ const plugins: PluginConfigSource[] = [ name: "Ipfs", module: "@polywrap/ipfs-plugin-js", uri: "wrap://ens/ipfs.polywrap.eth", - config: "IpfsPluginConfig", + config: "NoConfig", files: [ { name: "build/index.d.ts", - interfaces: ["IpfsPluginConfig"], + types: ["NoConfig"] }, ], }, diff --git a/packages/js/client/src/__tests__/utils/getClientWithEnsAndIpfs.ts b/packages/js/client/src/__tests__/utils/getClientWithEnsAndIpfs.ts index bac1a7d00d..55446a45a3 100644 --- a/packages/js/client/src/__tests__/utils/getClientWithEnsAndIpfs.ts +++ b/packages/js/client/src/__tests__/utils/getClientWithEnsAndIpfs.ts @@ -16,7 +16,7 @@ export const getClientWithEnsAndIpfs = async ( return createPolywrapClient( { ethereum: { connections }, - ipfs: { provider: providers.ipfs }, + ipfs: {}, ens: { addresses: { testnet: ensAddresses.ensAddress, diff --git a/packages/js/client/src/pluginConfigs/Ipfs.ts b/packages/js/client/src/pluginConfigs/Ipfs.ts index 94df81f0cb..acf0f28c06 100644 --- a/packages/js/client/src/pluginConfigs/Ipfs.ts +++ b/packages/js/client/src/pluginConfigs/Ipfs.ts @@ -5,7 +5,4 @@ /// Types generated from @polywrap/ipfs-plugin-js build files: /// build/index.d.ts -export interface IpfsPluginConfig { - provider: string; - fallbackProviders?: string[]; -} +export type NoConfig = Record; diff --git a/packages/js/client/src/pluginConfigs/index.ts b/packages/js/client/src/pluginConfigs/index.ts index e4887885fa..d6ee66e563 100644 --- a/packages/js/client/src/pluginConfigs/index.ts +++ b/packages/js/client/src/pluginConfigs/index.ts @@ -2,12 +2,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable prettier/prettier */ -import { IpfsPluginConfig } from "./Ipfs"; +import { NoConfig } from "./Ipfs"; import { EthereumPluginConfig } from "./Ethereum"; import { EnsResolverPluginConfig } from "./Ens"; interface PluginConfigs { - ipfs?: IpfsPluginConfig; + ipfs?: NoConfig; ethereum?: EthereumPluginConfig; ens?: EnsResolverPluginConfig; } diff --git a/packages/js/plugins/ethereum/src/__tests__/e2e.spec.ts b/packages/js/plugins/ethereum/src/__tests__/e2e.spec.ts index 1841fd48fb..9c9d079d04 100644 --- a/packages/js/plugins/ethereum/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/ethereum/src/__tests__/e2e.spec.ts @@ -63,6 +63,15 @@ describe("Ethereum Plugin", () => { }); client = new PolywrapClient({ + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: providers.ipfs, + fallbackProviders: defaultIpfsProviders, + }, + }, + ], plugins: [ { uri: "wrap://ens/ethereum.polywrap.eth", @@ -70,10 +79,7 @@ describe("Ethereum Plugin", () => { }, { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ - provider: providers.ipfs, - fallbackProviders: defaultIpfsProviders, - }), + plugin: ipfsPlugin({}), }, { uri: "wrap://ens/ens-resolver.polywrap.eth", diff --git a/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts b/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts index f732bc6955..8cbb88e4f5 100644 --- a/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts @@ -1,3 +1,4 @@ +import { InvokeResult } from "@polywrap/core-js"; import { PolywrapClient } from "@polywrap/client-js"; import { initTestEnvironment, @@ -29,9 +30,15 @@ describe("IPFS Plugin", () => { plugins: [ { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ + plugin: ipfsPlugin({}), + }, + ], + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { provider: providers.ipfs, - }), + }, }, ], }); @@ -87,4 +94,166 @@ describe("IPFS Plugin", () => { expect(contentsBuffer).toEqual(addedFileBuffer); }); + + it("Should timeout within a specified amount of time - env and options", async () => { + const createRacePromise = ( + timeout: number + ): Promise> => { + return new Promise>((resolve) => + setTimeout(() => { + resolve({ + data: Uint8Array.from([1, 2, 3, 4]), + error: undefined, + }); + }, timeout) + ); + }; + + const altClient = new PolywrapClient({ + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({}), + }, + ], + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: providers.ipfs, + timeout: 1000, + }, + }, + ], + }); + + const nonExistentFileCid = "Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + const catPromise = Ipfs_Module.cat({ cid: nonExistentFileCid }, altClient); + + let racePromise = createRacePromise(1100); + + const result = await Promise.race([catPromise, racePromise]); + + expect(result).toBeTruthy(); + expect(result.data).toBeFalsy(); + expect(result.error).toBeTruthy(); + expect(result.error?.stack).toMatch("Timeout has been reached"); + expect(result.error?.stack).toMatch("Timeout: 1000"); + + const catPromiseWithTimeoutOverride = Ipfs_Module.cat( + { + cid: nonExistentFileCid, + options: { timeout: 500 }, + }, + altClient + ); + + racePromise = createRacePromise(600); + + const resultForOverride = await Promise.race([ + catPromiseWithTimeoutOverride, + racePromise, + ]); + + expect(resultForOverride).toBeTruthy(); + expect(resultForOverride.data).toBeFalsy(); + expect(resultForOverride.error).toBeTruthy(); + expect(resultForOverride.error?.stack).toMatch("Timeout has been reached"); + expect(resultForOverride.error?.stack).toMatch("Timeout: 500"); + }); + + it("Should use provider from method options", async () => { + const clientWithBadProvider = new PolywrapClient({ + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({}), + }, + ], + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: "this-provider-doesnt-exist", + }, + }, + ], + }); + + const catResult = await Ipfs_Module.cat( + { + cid: sampleFileIpfsInfo.hash.toString(), + options: { provider: providers.ipfs }, + }, + clientWithBadProvider + ); + + expect(catResult.error).toBeFalsy(); + expect(catResult.data).toEqual(sampleFileBuffer); + + const resolveResult = await Ipfs_Module.resolve( + { + cid: sampleFileIpfsInfo.hash.toString(), + options: { provider: providers.ipfs }, + }, + clientWithBadProvider + ); + + expect(resolveResult.error).toBeFalsy(); + expect(resolveResult.data).toEqual({ + cid: `/ipfs/${sampleFileIpfsInfo.hash.toString()}`, + provider: providers.ipfs, + }); + }); + + it("Should use fallback provider from method options", async () => { + const clientWithBadProvider = new PolywrapClient({ + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({}), + }, + ], + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: "this-provider-doesnt-exist", + }, + }, + ], + }); + + const catResult = await Ipfs_Module.cat( + { + cid: sampleFileIpfsInfo.hash.toString(), + options: { + provider: "this-provider-also-doesnt-exist", + fallbackProviders: [providers.ipfs], + }, + }, + clientWithBadProvider + ); + + expect(catResult.error).toBeFalsy(); + expect(catResult.data).toEqual(sampleFileBuffer); + + const resolveResult = await Ipfs_Module.resolve( + { + cid: sampleFileIpfsInfo.hash.toString(), + options: { + provider: "this-provider-also-doesnt-exist", + fallbackProviders: [providers.ipfs], + }, + }, + clientWithBadProvider + ); + + expect(resolveResult.error).toBeFalsy(); + expect(resolveResult.data).toEqual({ + cid: `/ipfs/${sampleFileIpfsInfo.hash.toString()}`, + provider: providers.ipfs, + }); + }); }); diff --git a/packages/js/plugins/ipfs/src/index.ts b/packages/js/plugins/ipfs/src/index.ts index d0db1d7530..18aba44ddb 100644 --- a/packages/js/plugins/ipfs/src/index.ts +++ b/packages/js/plugins/ipfs/src/index.ts @@ -3,10 +3,10 @@ import { Args_resolve, Args_addFile, Args_cat, - Ipfs_Options, Ipfs_ResolveResult, - Env, + Ipfs_Options, manifest, + Env, } from "./wrap"; import { IpfsClient } from "./utils/IpfsClient"; import { execSimple, execFallbacks } from "./utils/exec"; @@ -16,44 +16,48 @@ import { Client, PluginFactory } from "@polywrap/core-js"; // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports, @typescript-eslint/naming-convention const createIpfsClient = require("@dorgjelli-test/ipfs-http-client-lite"); +const isNullOrUndefined = (arg: unknown) => { + return arg === undefined || arg === null; +}; + const getOptions = ( args: Ipfs_Options | undefined | null, env: Env ): Ipfs_Options => { const options = args || {}; - if ( - options.disableParallelRequests === undefined || - options.disableParallelRequests === null - ) { + if (isNullOrUndefined(options.disableParallelRequests)) { options.disableParallelRequests = env.disableParallelRequests; } - return options; -}; - -export interface IpfsPluginConfig { - provider: string; - fallbackProviders?: string[]; -} + if (isNullOrUndefined(options.timeout)) { + // Default to a 5000ms timeout when none is provided + options.timeout = env.timeout ?? 5000; + } -export class IpfsPlugin extends Module { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: initialized within setProvider - private _ipfs: IpfsClient; + if (isNullOrUndefined(options.provider)) { + options.provider = env.provider; + } - constructor(config: IpfsPluginConfig) { - super(config); - this._ipfs = createIpfsClient(this.config.provider); + if (isNullOrUndefined(options.fallbackProviders)) { + options.fallbackProviders = env.fallbackProviders; } + return options; +}; + +export type NoConfig = Record; + +export class IpfsPlugin extends Module { public async cat(args: Args_cat, _client: Client): Promise { + const options = getOptions(args.options, this.env); + return await this._execWithOptions( "cat", (ipfs: IpfsClient, _provider: string, options: unknown) => { return ipfs.cat(args.cid, options); }, - args.options ?? undefined + options ); } @@ -62,6 +66,7 @@ export class IpfsPlugin extends Module { _client: Client ): Promise { const options = getOptions(args.options, this.env); + return await this._execWithOptions( "resolve", async (ipfs: IpfsClient, provider: string, options: unknown) => { @@ -76,15 +81,23 @@ export class IpfsPlugin extends Module { } public async addFile(args: Args_addFile): Promise { - const result = await this._ipfs.add(new Uint8Array(args.data)); + const options = getOptions(null, this.env); - if (result.length === 0) { - throw Error( - `IpfsPlugin:add failed to add contents. Result of length 0 returned.` - ); - } + return await this._execWithOptions( + "add", + async (ipfs: IpfsClient, provider: string, options: unknown) => { + const result = await ipfs.add(new Uint8Array(args.data), options); - return result[0].hash.toString(); + if (result.length === 0) { + throw Error( + `IpfsPlugin:add failed to add contents. Result of length 0 returned.` + ); + } + + return result[0].hash.toString(); + }, + options + ); } private async _execWithOptions( @@ -96,11 +109,13 @@ export class IpfsPlugin extends Module { ) => Promise, options?: Ipfs_Options ): Promise { + const defaultIpfsClient = createIpfsClient(this.env.provider); + if (!options) { // Default behavior if no options are provided return await execSimple( operation, - this._ipfs, + defaultIpfsClient, this.config.provider, 0, func @@ -109,12 +124,9 @@ export class IpfsPlugin extends Module { const timeout = options.timeout || 0; - let providers = [ - this.config.provider, - ...(this.config.fallbackProviders || []), - ]; - let ipfs = this._ipfs; - let defaultProvider = this.config.provider; + let providers = [this.env.provider, ...(this.env.fallbackProviders || [])]; + let ipfs = defaultIpfsClient; + let defaultProvider = this.env.provider; // Use the provider default override specified if (options.provider) { @@ -123,6 +135,15 @@ export class IpfsPlugin extends Module { defaultProvider = options.provider; } + // insert fallback providers before the env providers and fallbacks + if (options.fallbackProviders) { + providers = [ + providers[0], + ...options.fallbackProviders, + ...providers.slice(1), + ]; + } + return await execFallbacks( operation, ipfs, @@ -137,9 +158,7 @@ export class IpfsPlugin extends Module { } } -export const ipfsPlugin: PluginFactory = ( - config: IpfsPluginConfig -) => { +export const ipfsPlugin: PluginFactory = (config: NoConfig) => { return { factory: () => new IpfsPlugin(config), manifest, diff --git a/packages/js/plugins/ipfs/src/schema.graphql b/packages/js/plugins/ipfs/src/schema.graphql index 61085a4a7e..b2146cd4d5 100644 --- a/packages/js/plugins/ipfs/src/schema.graphql +++ b/packages/js/plugins/ipfs/src/schema.graphql @@ -4,7 +4,19 @@ type Env { """ Disable querying providers in parallel when resolving URIs """ - disableParallelRequests: Boolean + disableParallelRequests: Boolean, + """ + Global timeout for IPFS actions + """ + timeout: UInt32, + """ + Default provider + """ + provider: String!, + """ + Fallback providers + """ + fallbackProviders: [String!] } type Module implements Ipfs_Module { diff --git a/packages/js/plugins/uri-resolvers/ens-resolver/src/__tests__/e2e.spec.ts b/packages/js/plugins/uri-resolvers/ens-resolver/src/__tests__/e2e.spec.ts index 06807c4690..846c8ff369 100644 --- a/packages/js/plugins/uri-resolvers/ens-resolver/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/uri-resolvers/ens-resolver/src/__tests__/e2e.spec.ts @@ -6,7 +6,7 @@ import { ensAddresses, initTestEnvironment, providers, - stopTestEnvironment + stopTestEnvironment, } from "@polywrap/test-env-js"; import { ensResolverPlugin } from ".."; @@ -28,19 +28,25 @@ describe("ENS Resolver Plugin", () => { wrapperAbsPath: wrapperAbsPath, ipfsProvider: providers.ipfs, ethereumProvider: providers.ethereum, - ensName: "cool.wrapper.eth" + ensName: "cool.wrapper.eth", }); wrapperEnsDomain = ensDomain; client = new PolywrapClient({ - plugins: [ + envs: [ { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ + env: { provider: providers.ipfs, fallbackProviders: defaultIpfsProviders - }) + } + } + ], + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({}), }, { uri: "wrap://ens/ethereum.polywrap.eth", @@ -59,11 +65,11 @@ describe("ENS Resolver Plugin", () => { uri: "wrap://ens/ens-resolver.polywrap.eth", plugin: ensResolverPlugin({ addresses: { - testnet: ensAddresses.ensAddress - } - }) - } - ] + testnet: ensAddresses.ensAddress, + }, + }), + }, + ], }); }); diff --git a/packages/js/plugins/uri-resolvers/file-system-resolver/src/__tests__/e2e.spec.ts b/packages/js/plugins/uri-resolvers/file-system-resolver/src/__tests__/e2e.spec.ts index 9d9ed70fe4..98cf2150d0 100644 --- a/packages/js/plugins/uri-resolvers/file-system-resolver/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/uri-resolvers/file-system-resolver/src/__tests__/e2e.spec.ts @@ -27,17 +27,23 @@ describe("Filesystem plugin", () => { await initTestEnvironment(); const config: Partial = { + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: providers.ipfs, + fallbackProviders: defaultIpfsProviders, + }, + }, + ], plugins: [ { uri: "wrap://ens/fs-resolver.polywrap.eth", - plugin: fileSystemResolverPlugin({ }), + plugin: fileSystemResolverPlugin({}), }, { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ - provider: providers.ipfs, - fallbackProviders: defaultIpfsProviders, - }), + plugin: ipfsPlugin({}), }, { uri: "wrap://ens/ens-resolver.polywrap.eth", diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/e2e.spec.ts b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/e2e.spec.ts index c99820f19e..60f9b95659 100644 --- a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/e2e.spec.ts @@ -1,43 +1,66 @@ import { PolywrapClient } from "@polywrap/client-js"; import { GetPathToTestWrappers } from "@polywrap/test-cases"; -import { buildAndDeployWrapper, initTestEnvironment, providers, stopTestEnvironment } from "@polywrap/test-env-js"; +import { + buildAndDeployWrapper, + initTestEnvironment, + providers, + stopTestEnvironment, +} from "@polywrap/test-env-js"; import { ipfsResolverPlugin } from ".."; import { ipfsPlugin } from "@polywrap/ipfs-plugin-js"; +import { IpfsClient } from "./helpers/IpfsClient"; +import { createIpfsClient } from "./helpers/createIpfsClient"; +import { InvokeResult } from "@polywrap/core-js"; jest.setTimeout(300000); describe("IPFS Plugin", () => { - let client: PolywrapClient; + let ipfsResolverUri = "wrap://ens/ipfs-resolver.polywrap.eth"; + let ipfs: IpfsClient; let wrapperIpfsCid: string; + const getClientConfigWithIpfsResolverEnv = (env: Record) => { + return { + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({}), + }, + { + uri: ipfsResolverUri, + plugin: ipfsResolverPlugin({}), + }, + ], + envs: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: providers.ipfs, + }, + }, + { + uri: "wrap://ens/ipfs-resolver.polywrap.eth", + env: env, + }, + ], + }; + }; + beforeAll(async () => { await initTestEnvironment(); + ipfs = createIpfsClient(providers.ipfs); + let { ipfsCid } = await buildAndDeployWrapper({ wrapperAbsPath: `${GetPathToTestWrappers()}/wasm-as/simple-storage`, ipfsProvider: providers.ipfs, ethereumProvider: providers.ethereum, - ensName: "cool.wrapper.eth" + ensName: "cool.wrapper.eth", }); wrapperIpfsCid = ipfsCid; - - client = new PolywrapClient({ - plugins: [ - { - uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ - provider: providers.ipfs - }) - }, - { - uri: "wrap://ens/ipfs-uri-resolver.polywrap.eth", - plugin: ipfsResolverPlugin({}) - } - ] - }); }); afterAll(async () => { @@ -45,6 +68,8 @@ describe("IPFS Plugin", () => { }); it("Should successfully resolve a deployed wrapper - e2e", async () => { + const client = new PolywrapClient(getClientConfigWithIpfsResolverEnv({})); + const wrapperUri = `ipfs/${wrapperIpfsCid}`; const result = await client.tryResolveUri({ uri: wrapperUri }); @@ -61,4 +86,122 @@ describe("IPFS Plugin", () => { expect(manifest?.name).toBe("SimpleStorage"); }); + + const createRacePromise = ( + timeout: number + ): Promise> => { + return new Promise>((resolve) => + setTimeout(() => { + resolve({ + data: Uint8Array.from([1, 2, 3, 4]), + error: undefined, + }); + }, timeout) + ); + }; + + it("Should properly timeout - getFile", async () => { + const runGetFileTimeoutTestWithEnv = async ( + env: Record, + timeout: number + ) => { + const nonExistentFileCid = "Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const client = new PolywrapClient(getClientConfigWithIpfsResolverEnv(env)); + + const getFilePromise = client.invoke({ + uri: ipfsResolverUri, + method: "getFile", + args: { + path: nonExistentFileCid, + }, + }); + + const fasterRacePromise = createRacePromise(timeout - 100); + const slowerRacePromise = createRacePromise(timeout + 100); + + const fasterRaceResult = await Promise.race([ + fasterRacePromise, + getFilePromise, + ]); + const slowerRaceResult = await Promise.race([ + getFilePromise, + slowerRacePromise, + ]); + + expect(fasterRaceResult.data).toStrictEqual((await fasterRacePromise).data); + expect(slowerRaceResult.data).toStrictEqual((await getFilePromise).data); + }; + + const timeout = 1000; + + await runGetFileTimeoutTestWithEnv( + { + timeouts: { + getFile: timeout, + checkIfExists: timeout, + tryResolveUri: timeout, + }, + }, + timeout + ); + + await runGetFileTimeoutTestWithEnv( + { + timeouts: { + getFile: timeout, + checkIfExists: timeout, + tryResolveUri: timeout, + }, + skipCheckIfExists: true + }, + timeout + ); + }); + + it("Should properly timeout - tryResolveUri", async () => { + const runTryResolveUriTimeoutTestWithEnv = async ( + env: Record, + timeout: number + ) => { + const nonExistentFileCid = "Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const client = new PolywrapClient(getClientConfigWithIpfsResolverEnv(env)); + + const getFilePromise = client.invoke({ + uri: ipfsResolverUri, + method: "tryResolveUri", + args: { + authority: "ipfs", + path: nonExistentFileCid, + }, + }); + + const fasterRacePromise = createRacePromise(timeout - 100); + const slowerRacePromise = createRacePromise(timeout + 100); + + const fasterRaceResult = await Promise.race([ + fasterRacePromise, + getFilePromise, + ]); + const slowerRaceResult = await Promise.race([ + getFilePromise, + slowerRacePromise, + ]); + + expect(fasterRaceResult.data).toStrictEqual((await fasterRacePromise).data); + expect(slowerRaceResult.data).toStrictEqual((await getFilePromise).data); + }; + + const timeout = 1000; + + await runTryResolveUriTimeoutTestWithEnv( + { + timeouts: { + getFile: timeout, + checkIfExists: timeout, + tryResolveUri: timeout, + }, + }, + timeout + ); + }); }); diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts index 0a1d7a2ec1..5daa7bf78d 100644 --- a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts @@ -40,7 +40,8 @@ export class IpfsResolverPlugin extends Module { { cid: `${args.path}/${manifestSearchPattern}`, options: { - timeout: 5000, + timeout: this.env.timeouts?.tryResolveUri, + disableParallelRequests: this.env.disableParallelRequests, }, }, _client @@ -64,29 +65,36 @@ export class IpfsResolverPlugin extends Module { client: Client ): Promise { try { - const resolveResult = await Ipfs_Module.resolve( - { - cid: args.path, - options: { - timeout: 5000, + let provider: string | undefined = undefined; + + if (!this.env.skipCheckIfExists) { + const resolveResult = await Ipfs_Module.resolve( + { + cid: args.path, + options: { + timeout: this.env.timeouts?.checkIfExists, + disableParallelRequests: this.env.disableParallelRequests, + }, }, - }, - client - ); + client + ); - const result = resolveResult.data; + const result = resolveResult.data; - if (!result) { - return null; + if (!result) { + return null; + } + + provider = result.provider; } const catResult = await Ipfs_Module.cat( { - cid: result.cid, + cid: args.path, options: { - provider: result.provider, - timeout: 20000, - disableParallelRequests: true, + provider: provider, + timeout: this.env.timeouts?.getFile, + disableParallelRequests: this.env.disableParallelRequests, }, }, client diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql index 2ba8f5de0f..10024abd4a 100644 --- a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql @@ -1,5 +1,26 @@ #import { Module, MaybeUriOrManifest } into UriResolver from "ens/uri-resolver.core.polywrap.eth" #import { Module } into Ipfs from "ens/ipfs.polywrap.eth" +type Timeouts { + getFile: UInt32, + checkIfExists: UInt32, + tryResolveUri: UInt32, +} + +type Env { + """ + Determines whether the plugin should try to resolve a file (check for its existence) or immediately get the file + """ + skipCheckIfExists: Boolean, + """ + Timeouts for methods + """ + timeouts: Timeouts, + """ + Disable parallel requests + """ + disableParallelRequests: Boolean +} + type Module implements UriResolver_Module { } \ No newline at end of file diff --git a/packages/js/react/src/__tests__/app/SimpleStorage.tsx b/packages/js/react/src/__tests__/app/SimpleStorage.tsx index 12d7eaa503..5698884977 100644 --- a/packages/js/react/src/__tests__/app/SimpleStorage.tsx +++ b/packages/js/react/src/__tests__/app/SimpleStorage.tsx @@ -2,6 +2,7 @@ import { usePolywrapQuery, PolywrapProvider, usePolywrapClient, createPolywrapPr import { PluginRegistration } from "@polywrap/client-js"; // eslint-disable-next-line import/no-extraneous-dependencies import React from "react"; +import { Env } from "@polywrap/core-js"; const SimpleStorage = ({ uri }: { uri: string }) => { const { execute: deployContract, data: deployData } = usePolywrapQuery<{ @@ -75,14 +76,16 @@ const SimpleStorage = ({ uri }: { uri: string }) => { const CustomProvider = createPolywrapProvider("custom"); export const SimpleStorageContainer = ({ + envs, plugins, ensUri, }: { + envs: Env[] plugins: PluginRegistration[]; ensUri: string; }) => ( - + diff --git a/packages/js/react/src/__tests__/plugins.ts b/packages/js/react/src/__tests__/config.ts similarity index 78% rename from packages/js/react/src/__tests__/plugins.ts rename to packages/js/react/src/__tests__/config.ts index 63b69c44f7..224ac9932f 100644 --- a/packages/js/react/src/__tests__/plugins.ts +++ b/packages/js/react/src/__tests__/config.ts @@ -1,13 +1,12 @@ -import { PluginRegistration } from "@polywrap/core-js"; -import { defaultIpfsProviders } from "@polywrap/client-config-builder-js"; +import { Env, PluginRegistration } from "@polywrap/core-js"; import { plugin as ensResolverPlugin } from "@polywrap/ens-resolver-plugin-js"; import { Connection, Connections, plugin as ethereumPlugin } from "@polywrap/ethereum-plugin-js"; import { plugin as ipfsPlugin } from "@polywrap/ipfs-plugin-js"; +import { defaultIpfsProviders } from "@polywrap/client-config-builder-js"; export function createPlugins( ensAddress: string, - ethereumProvider: string, - ipfsProvider: string + ethereumProvider: string ): PluginRegistration[] { return [ { @@ -22,10 +21,7 @@ export function createPlugins( }, { uri: "wrap://ens/ipfs.polywrap.eth", - plugin: ipfsPlugin({ - provider: ipfsProvider, - fallbackProviders: defaultIpfsProviders, - }), + plugin: ipfsPlugin({}), }, { uri: "wrap://ens/ens-resolver.polywrap.eth", @@ -36,4 +32,16 @@ export function createPlugins( }), }, ]; +} + +export function createEnvs(ipfsProvider: string): Env[] { + return [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + env: { + provider: ipfsProvider, + fallbackProviders: defaultIpfsProviders, + } + } + ]; } \ No newline at end of file diff --git a/packages/js/react/src/__tests__/integration.spec.tsx b/packages/js/react/src/__tests__/integration.spec.tsx index 976175f753..bbf4349363 100644 --- a/packages/js/react/src/__tests__/integration.spec.tsx +++ b/packages/js/react/src/__tests__/integration.spec.tsx @@ -1,6 +1,6 @@ import { createPolywrapProvider } from ".."; import { SimpleStorageContainer } from "./app/SimpleStorage"; -import { createPlugins } from "./plugins"; +import { createEnvs, createPlugins } from "./config"; import { initTestEnvironment, @@ -10,7 +10,7 @@ import { providers } from "@polywrap/test-env-js"; import { GetPathToTestWrappers } from "@polywrap/test-cases"; -import { PluginRegistration } from "@polywrap/core-js"; +import { Env, PluginRegistration } from "@polywrap/core-js"; // eslint-disable-next-line import/no-extraneous-dependencies import React from "react"; @@ -18,6 +18,7 @@ import { render, fireEvent, screen, waitFor } from "@testing-library/react"; jest.setTimeout(360000); describe("Polywrap React Integration", () => { + let envs: Env[]; let plugins: PluginRegistration[]; let ensUri: string; let wrapper: { @@ -28,7 +29,9 @@ describe("Polywrap React Integration", () => { beforeAll(async () => { await initTestEnvironment(); - plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum, providers.ipfs); + envs = createEnvs(providers.ipfs); + + plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum); wrapper = await buildAndDeployWrapper({ wrapperAbsPath: `${GetPathToTestWrappers()}/wasm-as/simple-storage`, @@ -44,7 +47,7 @@ describe("Polywrap React Integration", () => { }); it("Deploys, read and write on Smart Contract ", async () => { - render(); + render(); fireEvent.click(screen.getByText("Deploy")); await waitFor(() => screen.getByText(/0x/), { timeout: 30000 }); diff --git a/packages/js/react/src/__tests__/usePolywrapClient.spec.tsx b/packages/js/react/src/__tests__/usePolywrapClient.spec.tsx index baa7359632..212f2a554a 100644 --- a/packages/js/react/src/__tests__/usePolywrapClient.spec.tsx +++ b/packages/js/react/src/__tests__/usePolywrapClient.spec.tsx @@ -4,9 +4,9 @@ import { createPolywrapProvider, usePolywrapClient } from ".."; -import { createPlugins } from "./plugins"; +import { createPlugins, createEnvs } from "./config"; -import { PluginRegistration } from "@polywrap/core-js"; +import { Env, PluginRegistration } from "@polywrap/core-js"; import { ensAddresses, providers, @@ -23,17 +23,21 @@ import { jest.setTimeout(360000); describe("usePolywrapClient hook", () => { + let envs: Env[]; let plugins: PluginRegistration[]; let WrapperProvider: RenderHookOptions; beforeAll(async () => { await initTestEnvironment(); - plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum, providers.ipfs); + envs = createEnvs(providers.ipfs); + + plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum); WrapperProvider = { wrapper: PolywrapProvider, initialProps: { + envs, plugins, }, }; diff --git a/packages/js/react/src/__tests__/usePolywrapInvoke.spec.tsx b/packages/js/react/src/__tests__/usePolywrapInvoke.spec.tsx index 2f2783fa7e..95529d4d2b 100644 --- a/packages/js/react/src/__tests__/usePolywrapInvoke.spec.tsx +++ b/packages/js/react/src/__tests__/usePolywrapInvoke.spec.tsx @@ -4,9 +4,9 @@ import { createPolywrapProvider, } from ".."; import { UsePolywrapInvokeProps } from "../invoke"; -import { createPlugins } from "./plugins"; +import { createPlugins, createEnvs } from "./config"; -import { PluginRegistration } from "@polywrap/core-js"; +import { Env, PluginRegistration } from "@polywrap/core-js"; import { initTestEnvironment, stopTestEnvironment, @@ -28,6 +28,7 @@ jest.setTimeout(360000); describe("usePolywrapInvoke hook", () => { let uri: string; let envUri: string; + let envs: Env[]; let plugins: PluginRegistration[]; let WrapperProvider: RenderHookOptions; @@ -48,10 +49,12 @@ describe("usePolywrapInvoke hook", () => { uri = `ens/testnet/${ensDomain}`; envUri = `ens/testnet/${envEnsDomain}`; - plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum, providers.ipfs); + envs = createEnvs(providers.ipfs); + plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum); WrapperProvider = { wrapper: PolywrapProvider, initialProps: { + envs, plugins, }, }; diff --git a/packages/js/react/src/__tests__/usePolywrapQuery.spec.tsx b/packages/js/react/src/__tests__/usePolywrapQuery.spec.tsx index a555bfc8d8..479388c973 100644 --- a/packages/js/react/src/__tests__/usePolywrapQuery.spec.tsx +++ b/packages/js/react/src/__tests__/usePolywrapQuery.spec.tsx @@ -6,9 +6,9 @@ import { import { UsePolywrapQueryProps } from "../query" -import { createPlugins } from "./plugins"; +import { createPlugins, createEnvs } from "./config"; -import { PluginRegistration } from "@polywrap/core-js"; +import { Env, PluginRegistration } from "@polywrap/core-js"; import { initTestEnvironment, stopTestEnvironment, @@ -30,6 +30,7 @@ jest.setTimeout(360000); describe("usePolywrapQuery hook", () => { let uri: string; let envUri: string; + let envs: Env[]; let plugins: PluginRegistration[]; let WrapperProvider: RenderHookOptions; @@ -50,10 +51,12 @@ describe("usePolywrapQuery hook", () => { uri = `ens/testnet/${ensDomain}`; envUri = `ens/testnet/${envEnsDomain}`; - plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum, providers.ipfs); + envs = createEnvs(providers.ipfs); + plugins = createPlugins(ensAddresses.ensAddress, providers.ethereum); WrapperProvider = { wrapper: PolywrapProvider, initialProps: { + envs, plugins, }, };