diff --git a/packages/core-interfaces/file-system/.gitignore b/packages/core-interfaces/file-system/.gitignore deleted file mode 100644 index edd2cc9ab1..0000000000 --- a/packages/core-interfaces/file-system/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build -node_modules -wrap \ No newline at end of file diff --git a/packages/core-interfaces/ipfs/README.md b/packages/core-interfaces/ipfs/README.md new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/packages/core-interfaces/ipfs/README.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/packages/core-interfaces/ipfs/package.json b/packages/core-interfaces/ipfs/package.json new file mode 100644 index 0000000000..73378b6023 --- /dev/null +++ b/packages/core-interfaces/ipfs/package.json @@ -0,0 +1,16 @@ +{ + "name": "@polywrap/ipfs-interface", + "description": "Polywrap Ipfs Interface", + "private": true, + "version": "0.0.1-prealpha.89", + "scripts": { + "build": "node ../../../dependencies/node_modules/polywrap/bin/polywrap build", + "lint": "eslint --color -c ../../../.eslintrc.js .", + "test:env:up": "npx polywrap infra up --modules=eth-ens-ipfs", + "test:env:down": "npx polywrap infra down --modules=eth-ens-ipfs", + "deploy": "node ../../../dependencies/node_modules/polywrap/bin/polywrap deploy" + }, + "devDependencies": { + "polywrap": "0.0.1-prealpha.89" + } +} diff --git a/packages/core-interfaces/ipfs/polywrap.deploy.yaml b/packages/core-interfaces/ipfs/polywrap.deploy.yaml new file mode 100644 index 0000000000..b5a9c3dd7d --- /dev/null +++ b/packages/core-interfaces/ipfs/polywrap.deploy.yaml @@ -0,0 +1,5 @@ +format: 0.0.1-prealpha.1 +stages: + ipfs_deploy: + package: ipfs + uri: fs/./build \ No newline at end of file diff --git a/packages/core-interfaces/ipfs/polywrap.yaml b/packages/core-interfaces/ipfs/polywrap.yaml new file mode 100644 index 0000000000..5a25555adb --- /dev/null +++ b/packages/core-interfaces/ipfs/polywrap.yaml @@ -0,0 +1,5 @@ +format: 0.0.1-prealpha.9 +name: IPFS +language: interface +deploy: ./polywrap.deploy.yaml +schema: ./src/schema.graphql \ No newline at end of file diff --git a/packages/core-interfaces/ipfs/src/schema.graphql b/packages/core-interfaces/ipfs/src/schema.graphql new file mode 100644 index 0000000000..faa5fc9c9e --- /dev/null +++ b/packages/core-interfaces/ipfs/src/schema.graphql @@ -0,0 +1,30 @@ +type Module { + cat(cid: String!, options: Options): Bytes! + + resolve(cid: String!, options: Options): ResolveResult + + addFile(data: Bytes!): String! +} + +type ResolveResult { + cid: String! + provider: String! +} + +type Options { + """ + Timeout (in ms) for the operation. + Fallback providers are used if timeout is reached. + """ + timeout: UInt32 + + """ + The IPFS provider to be used + """ + provider: String + + """ + Disable querying providers in parallel when resolving URIs + """ + disableParallelRequests: Boolean +} diff --git a/packages/core-interfaces/logger/.gitignore b/packages/core-interfaces/logger/.gitignore deleted file mode 100644 index e3fbd98336..0000000000 --- a/packages/core-interfaces/logger/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -node_modules diff --git a/packages/core-interfaces/uri-resolver/.gitignore b/packages/core-interfaces/uri-resolver/.gitignore deleted file mode 100644 index e3fbd98336..0000000000 --- a/packages/core-interfaces/uri-resolver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build -node_modules diff --git a/packages/js/client/package.json b/packages/js/client/package.json index 602c9ef4c5..abb53d9119 100644 --- a/packages/js/client/package.json +++ b/packages/js/client/package.json @@ -35,6 +35,7 @@ "@polywrap/tracing-js": "0.0.1-prealpha.89", "@polywrap/uts46-plugin-js": "0.0.1-prealpha.89", "@polywrap/fs-resolver-plugin-js": "0.0.1-prealpha.89", + "@polywrap/ipfs-resolver-plugin-js": "0.0.1-prealpha.89", "graphql": "15.5.0", "js-yaml": "3.14.0", "uuid": "8.3.2" diff --git a/packages/js/client/src/__tests__/core/plugin-wrapper.spec.ts b/packages/js/client/src/__tests__/core/plugin-wrapper.spec.ts index eeda31926a..deea599d46 100644 --- a/packages/js/client/src/__tests__/core/plugin-wrapper.spec.ts +++ b/packages/js/client/src/__tests__/core/plugin-wrapper.spec.ts @@ -18,6 +18,7 @@ const defaultPlugins = [ "wrap://ens/graph-node.polywrap.eth", "wrap://ens/fs.polywrap.eth", "wrap://ens/fs-resolver.polywrap.eth", + "wrap://ens/ipfs-resolver.polywrap.eth", ]; describe("plugin-wrapper", () => { diff --git a/packages/js/client/src/__tests__/core/resolveUri.spec.ts b/packages/js/client/src/__tests__/core/resolveUri.spec.ts index 95b101b533..fca87d5965 100644 --- a/packages/js/client/src/__tests__/core/resolveUri.spec.ts +++ b/packages/js/client/src/__tests__/core/resolveUri.spec.ts @@ -331,7 +331,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); @@ -395,7 +395,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); @@ -522,7 +522,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); @@ -586,7 +586,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); @@ -650,7 +650,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); @@ -730,7 +730,7 @@ describe("resolveUri", () => { result: { uri: ipfsUri, wrapper: true, - implementationUri: new Uri("wrap://ens/ipfs.polywrap.eth"), + implementationUri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), }, }, ]); diff --git a/packages/js/client/src/__tests__/core/sanity.spec.ts b/packages/js/client/src/__tests__/core/sanity.spec.ts index 826d98369a..dc7ad1dd01 100644 --- a/packages/js/client/src/__tests__/core/sanity.spec.ts +++ b/packages/js/client/src/__tests__/core/sanity.spec.ts @@ -26,12 +26,13 @@ describe("sanity", () => { new Uri("wrap://ens/graph-node.polywrap.eth"), new Uri("wrap://ens/fs.polywrap.eth"), new Uri("wrap://ens/fs-resolver.polywrap.eth"), + new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), ]); expect(client.getInterfaces()).toStrictEqual([ { interface: coreInterfaceUris.uriResolver, implementations: [ - new Uri("wrap://ens/ipfs.polywrap.eth"), + new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), new Uri("wrap://ens/ens.polywrap.eth"), new Uri("wrap://ens/fs-resolver.polywrap.eth"), ], diff --git a/packages/js/client/src/default-client-config.ts b/packages/js/client/src/default-client-config.ts index c5d7edb740..6b0fe6415d 100644 --- a/packages/js/client/src/default-client-config.ts +++ b/packages/js/client/src/default-client-config.ts @@ -13,6 +13,7 @@ import { RedirectsResolver, } from "@polywrap/core-js"; import { ipfsPlugin } from "@polywrap/ipfs-plugin-js"; +import { ipfsResolverPlugin } from "@polywrap/ipfs-resolver-plugin-js"; import { ethereumPlugin } from "@polywrap/ethereum-plugin-js"; import { ensPlugin } from "@polywrap/ens-plugin-js"; import { graphNodePlugin } from "@polywrap/graph-node-plugin-js"; @@ -85,12 +86,19 @@ export const getDefaultClientConfig = Tracer.traceFunc( uri: new Uri("wrap://ens/fs-resolver.polywrap.eth"), plugin: fileSystemResolverPlugin({}), }, + { + uri: new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), + plugin: ipfsResolverPlugin({ + provider: defaultIpfsProviders[0], + fallbackProviders: defaultIpfsProviders.slice(1), + }), + }, ], interfaces: [ { interface: coreInterfaceUris.uriResolver, implementations: [ - new Uri("wrap://ens/ipfs.polywrap.eth"), + new Uri("wrap://ens/ipfs-resolver.polywrap.eth"), new Uri("wrap://ens/ens.polywrap.eth"), new Uri("wrap://ens/fs-resolver.polywrap.eth"), ], diff --git a/packages/js/plugins/ipfs/package.json b/packages/js/plugins/ipfs/package.json index 1542aaebb8..f47569cb88 100644 --- a/packages/js/plugins/ipfs/package.json +++ b/packages/js/plugins/ipfs/package.json @@ -23,8 +23,8 @@ "@dorgjelli-test/ipfs-http-client-lite": "0.3.1", "@polywrap/core-js": "0.0.1-prealpha.89", "abort-controller": "3.0.0", - "cids": "^1.1.4", - "is-ipfs": "1.0.3" + "is-ipfs": "1.0.3", + "multiformats": "9.7.0" }, "devDependencies": { "@types/jest": "26.0.8", @@ -34,7 +34,8 @@ "rimraf": "3.0.2", "ts-jest": "26.5.4", "ts-node": "8.10.2", - "typescript": "4.0.7" + "typescript": "4.0.7", + "@polywrap/test-env-js": "0.0.1-prealpha.89" }, "gitHead": "7346adaf5adb7e6bbb70d9247583e995650d390a", "publishConfig": { diff --git a/packages/js/plugins/ipfs/polywrap.plugin.yaml b/packages/js/plugins/ipfs/polywrap.plugin.yaml index 553091d233..beafbe18d8 100644 --- a/packages/js/plugins/ipfs/polywrap.plugin.yaml +++ b/packages/js/plugins/ipfs/polywrap.plugin.yaml @@ -4,5 +4,5 @@ language: plugin/typescript module: ./src/index.ts schema: ./src/schema.graphql import_redirects: - - uri: "ens/uri-resolver.core.polywrap.eth" - schema: ../../../core-interfaces/uri-resolver/src/schema.graphql + - uri: "ens/ipfs.polywrap.eth" + schema: ../../../core-interfaces/ipfs/build/schema.graphql diff --git a/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts b/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts index e9397a2000..f732bc6955 100644 --- a/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts +++ b/packages/js/plugins/ipfs/src/__tests__/e2e.spec.ts @@ -1,5 +1,90 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -// TODO: create client, add plugin, test all the methods -describe("TODO", () => { - it("TODO", () => {}); +import { PolywrapClient } from "@polywrap/client-js"; +import { + initTestEnvironment, + providers, + stopTestEnvironment, +} from "@polywrap/test-env-js"; + +import { ipfsPlugin } from ".."; +import { IpfsClient, IpfsFileInfo } from "../utils/IpfsClient"; +import { Ipfs_Module } from "../wrap"; + +const createIpfsClient = require("@dorgjelli-test/ipfs-http-client-lite"); + +jest.setTimeout(300000); + +describe("IPFS Plugin", () => { + let client: PolywrapClient; + let ipfs: IpfsClient; + + const sampleFileTextContents = "Hello World!"; + let sampleFileIpfsInfo: IpfsFileInfo; + const sampleFileBuffer = Buffer.from(sampleFileTextContents, "utf-8"); + + beforeAll(async () => { + await initTestEnvironment(); + ipfs = createIpfsClient(providers.ipfs); + + client = new PolywrapClient({ + plugins: [ + { + uri: "wrap://ens/ipfs.polywrap.eth", + plugin: ipfsPlugin({ + provider: providers.ipfs, + }), + }, + ], + }); + + let ipfsAddResult = await ipfs.add(sampleFileBuffer); + sampleFileIpfsInfo = ipfsAddResult[0]; + }); + + afterAll(async () => { + await stopTestEnvironment(); + }); + + it("Should cat a file successfully", async () => { + expect(sampleFileIpfsInfo).toBeDefined(); + + let result = await Ipfs_Module.cat( + { cid: sampleFileIpfsInfo.hash.toString() }, + client + ); + + expect(result.error).toBeFalsy(); + + expect(result.data).toEqual(sampleFileBuffer); + }); + + it("Should resolve a file successfully", async () => { + expect(sampleFileIpfsInfo).toBeDefined(); + + let result = await Ipfs_Module.resolve( + { cid: sampleFileIpfsInfo.hash.toString() }, + client + ); + + expect(result.error).toBeFalsy(); + + expect(result.data).toEqual({ + cid: `/ipfs/${sampleFileIpfsInfo.hash.toString()}`, + provider: providers.ipfs, + }); + }); + + it("Should add a file successfully", async () => { + const expectedContents = "A new sample file"; + const contentsBuffer = Buffer.from(expectedContents, "utf-8"); + + let result = await Ipfs_Module.addFile({ data: contentsBuffer }, client); + + expect(result.error).toBeFalsy(); + + expect(result.data).toBeTruthy(); + + const addedFileBuffer = await ipfs.cat(result.data as string); + + expect(contentsBuffer).toEqual(addedFileBuffer); + }); }); diff --git a/packages/js/plugins/ipfs/src/index.ts b/packages/js/plugins/ipfs/src/index.ts index 933709ac77..d0db1d7530 100644 --- a/packages/js/plugins/ipfs/src/index.ts +++ b/packages/js/plugins/ipfs/src/index.ts @@ -1,28 +1,25 @@ import { Module, - Args_catFile, Args_resolve, - Args_tryResolveUri, - Args_getFile, Args_addFile, - Bytes, - Options, - ResolveResult, + Args_cat, + Ipfs_Options, + Ipfs_ResolveResult, Env, - UriResolver_MaybeUriOrManifest, manifest, } from "./wrap"; import { IpfsClient } from "./utils/IpfsClient"; import { execSimple, execFallbacks } from "./utils/exec"; -import { PluginFactory } from "@polywrap/core-js"; +import { Client, PluginFactory } from "@polywrap/core-js"; -// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports -const isIPFS = require("is-ipfs"); // 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 getOptions = (args: Options | undefined | null, env: Env): Options => { +const getOptions = ( + args: Ipfs_Options | undefined | null, + env: Env +): Ipfs_Options => { const options = args || {}; if ( @@ -50,31 +47,20 @@ export class IpfsPlugin extends Module { this._ipfs = createIpfsClient(this.config.provider); } - public static isCID(cid: string): boolean { - return isIPFS.cid(cid) || isIPFS.cidPath(cid) || isIPFS.ipfsPath(cid); - } - - public async cat(cid: string, options?: Options): Promise { + public async cat(args: Args_cat, _client: Client): Promise { return await this._execWithOptions( "cat", (ipfs: IpfsClient, _provider: string, options: unknown) => { - return ipfs.cat(cid, options); + return ipfs.cat(args.cid, options); }, - options + args.options ?? undefined ); } - public async catToString(cid: string, options?: Options): Promise { - const buffer = await this.cat(cid, options); - return buffer.toString("utf-8"); - } - - public async catFile(args: Args_catFile): Promise { - const options = getOptions(args.options, this.env); - return await this.cat(args.cid, options); - } - - public async resolve(args: Args_resolve): Promise { + public async resolve( + args: Args_resolve, + _client: Client + ): Promise { const options = getOptions(args.options, this.env); return await this._execWithOptions( "resolve", @@ -89,74 +75,6 @@ export class IpfsPlugin extends Module { ); } - // uri-resolver.core.polywrap.eth - public async tryResolveUri( - args: Args_tryResolveUri - ): Promise { - if (args.authority !== "ipfs") { - return null; - } - - if (!IpfsPlugin.isCID(args.path)) { - // Not a valid CID - return { manifest: null, uri: null }; - } - - const manifestSearchPatterns = [ - "polywrap.json", - "polywrap.yaml", - "polywrap.yml", - ]; - - let manifest: string | undefined; - - for (const manifestSearchPattern of manifestSearchPatterns) { - try { - manifest = await this.catToString( - `${args.path}/${manifestSearchPattern}`, - { - timeout: 5000, - disableParallelRequests: this.env.disableParallelRequests, - } - ); - } catch (e) { - // TODO: logging - // https://github.com/polywrap/monorepo/issues/33 - } - } - - if (manifest) { - return { uri: null, manifest }; - } else { - // Nothing found - return { uri: null, manifest: null }; - } - } - - public async getFile(args: Args_getFile): Promise { - try { - const result = await this.resolve({ - cid: args.path, - options: { - timeout: 5000, - disableParallelRequests: this.env.disableParallelRequests, - }, - }); - - if (!result) { - return null; - } - - return await this.cat(result.cid, { - provider: result.provider, - timeout: 20000, - disableParallelRequests: true, - }); - } catch (e) { - return null; - } - } - public async addFile(args: Args_addFile): Promise { const result = await this._ipfs.add(new Uint8Array(args.data)); @@ -176,7 +94,7 @@ export class IpfsPlugin extends Module { provider: string, options: unknown ) => Promise, - options?: Options + options?: Ipfs_Options ): Promise { if (!options) { // Default behavior if no options are provided diff --git a/packages/js/plugins/ipfs/src/schema.graphql b/packages/js/plugins/ipfs/src/schema.graphql index 558a2b5803..61085a4a7e 100644 --- a/packages/js/plugins/ipfs/src/schema.graphql +++ b/packages/js/plugins/ipfs/src/schema.graphql @@ -1,4 +1,4 @@ -#import { Module, MaybeUriOrManifest } into UriResolver from "ens/uri-resolver.core.polywrap.eth" +#import { Module } into Ipfs from "ens/ipfs.polywrap.eth" type Env { """ @@ -7,39 +7,5 @@ type Env { disableParallelRequests: Boolean } -type Module implements UriResolver_Module { - catFile( - cid: String! - options: Options - ): Bytes! - - resolve( - cid: String! - options: Options - ): ResolveResult - - addFile(data: Bytes!): String! -} - -type ResolveResult { - cid: String! - provider: String! -} - -type Options { - """ - Timeout (in ms) for the operation. - Fallback providers are used if timeout is reached. - """ - timeout: UInt32 - - """ - The IPFS provider to be used - """ - provider: String - - """ - Disable querying providers in parallel when resolving URIs - """ - disableParallelRequests: Boolean -} +type Module implements Ipfs_Module { +} \ No newline at end of file diff --git a/packages/js/plugins/ipfs/src/utils/IpfsClient.ts b/packages/js/plugins/ipfs/src/utils/IpfsClient.ts index 1f8e827541..80d63207b4 100644 --- a/packages/js/plugins/ipfs/src/utils/IpfsClient.ts +++ b/packages/js/plugins/ipfs/src/utils/IpfsClient.ts @@ -1,15 +1,7 @@ -import CID from "cids"; +import { CID } from "multiformats"; export interface IpfsClient { - add( - data: Uint8Array, - options?: unknown - ): Promise< - { - name: string; - hash: CID; - }[] - >; + add(data: Uint8Array, options?: unknown): Promise; cat(cid: string, options?: unknown): Promise; @@ -20,3 +12,8 @@ export interface IpfsClient { path: string; }>; } + +export type IpfsFileInfo = { + name: string; + hash: CID; +}; diff --git a/packages/js/plugins/uri-resolvers/file-system-resolver/.gitignore b/packages/js/plugins/uri-resolvers/file-system-resolver/.gitignore deleted file mode 100644 index 563ba71918..0000000000 --- a/packages/js/plugins/uri-resolvers/file-system-resolver/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/node_modules -/build \ No newline at end of file diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/README.md b/packages/js/plugins/uri-resolvers/ipfs-resolver/README.md new file mode 100644 index 0000000000..30404ce4c5 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/README.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/jest.config.js b/packages/js/plugins/uri-resolvers/ipfs-resolver/jest.config.js new file mode 100644 index 0000000000..12adcff325 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], + modulePathIgnorePatterns: [], + roots: ["./src/__tests__"], + globals: { + "ts-jest": { + diagnostics: false, + }, + }, +}; diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/package.json b/packages/js/plugins/uri-resolvers/ipfs-resolver/package.json new file mode 100644 index 0000000000..b8cb91a7f2 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/package.json @@ -0,0 +1,44 @@ +{ + "name": "@polywrap/ipfs-resolver-plugin-js", + "description": "Polywrap IPFS Javascript Plugin", + "version": "0.0.1-prealpha.89", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/web3-api/monorepo.git" + }, + "main": "build/index.js", + "files": [ + "build" + ], + "scripts": { + "build": "rimraf ./build && yarn codegen && tsc --project tsconfig.build.json", + "codegen": "node ../../../../../dependencies/node_modules/polywrap/bin/polywrap plugin codegen", + "codegen:patch": "node ../../../../cli/bin/polywrap plugin codegen && rimraf ./src/wrap", + "lint": "eslint --color -c ../../../../../.eslintrc.js src/", + "test": "jest --passWithNoTests --runInBand --verbose", + "test:ci": "yarn codegen && yarn test", + "test:watch": "jest --watch --passWithNoTests --verbose" + }, + "dependencies": { + "@dorgjelli-test/ipfs-http-client-lite": "0.3.1", + "@polywrap/core-js": "0.0.1-prealpha.89", + "abort-controller": "3.0.0", + "is-ipfs": "1.0.3" + }, + "devDependencies": { + "@types/jest": "26.0.8", + "@types/prettier": "2.6.0", + "abort-controller": "3.0.0", + "jest": "26.6.3", + "rimraf": "3.0.2", + "ts-jest": "26.5.4", + "ts-node": "8.10.2", + "typescript": "4.0.7", + "multiformats": "9.7.0" + }, + "gitHead": "7346adaf5adb7e6bbb70d9247583e995650d390a", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/polywrap.plugin.yaml b/packages/js/plugins/uri-resolvers/ipfs-resolver/polywrap.plugin.yaml new file mode 100644 index 0000000000..fbf84d2214 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/polywrap.plugin.yaml @@ -0,0 +1,10 @@ +format: 0.0.1-prealpha.3 +name: IpfsResolver +language: plugin/typescript +module: ./src/index.ts +schema: ./src/schema.graphql +import_redirects: + - uri: "ens/uri-resolver.core.polywrap.eth" + schema: ../../../../core-interfaces/uri-resolver/src/schema.graphql + - uri: "ens/ipfs.polywrap.eth" + schema: ../../../../core-interfaces/ipfs/build/schema.graphql 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 new file mode 100644 index 0000000000..3b11236ee6 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/e2e.spec.ts @@ -0,0 +1,74 @@ +import { PolywrapClient } from "@polywrap/client-js"; +import { GetPathToTestWrappers } from "@polywrap/test-cases"; +import { + initTestEnvironment, + providers, + stopTestEnvironment, + buildAndDeployWrapper, +} 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"; + +jest.setTimeout(300000); + +describe("IPFS Plugin", () => { + let client: PolywrapClient; + let ipfs: IpfsClient; + + let wrapperIpfsCid: string; + + 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: "simple-storage.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({ + provider: providers.ipfs, + }), + }, + ], + }); + }); + + afterAll(async () => { + await stopTestEnvironment(); + }); + + it("Should successfully resolve a deployed wrapper - e2e", async () => { + const wrapperUri = `ipfs/${wrapperIpfsCid}`; + + const resolution = await client.resolveUri(wrapperUri); + + expect(resolution.wrapper).toBeTruthy(); + + const expectedSchema = ( + await ipfs.cat(`${wrapperIpfsCid}/schema.graphql`) + ).toString("utf-8"); + + const schema = await resolution.wrapper?.getSchema(client); + + expect(schema).toEqual(expectedSchema); + }); +}); diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/IpfsClient.ts b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/IpfsClient.ts new file mode 100644 index 0000000000..80d63207b4 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/IpfsClient.ts @@ -0,0 +1,19 @@ +import { CID } from "multiformats"; + +export interface IpfsClient { + add(data: Uint8Array, options?: unknown): Promise; + + cat(cid: string, options?: unknown): Promise; + + resolve( + cid: string, + options?: unknown + ): Promise<{ + path: string; + }>; +} + +export type IpfsFileInfo = { + name: string; + hash: CID; +}; diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/createIpfsClient.ts b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/createIpfsClient.ts new file mode 100644 index 0000000000..d4bccf83d6 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/__tests__/helpers/createIpfsClient.ts @@ -0,0 +1,5 @@ +import { IpfsClient } from "./IpfsClient"; + +export const createIpfsClient: ( + ipfsProvider: string +) => IpfsClient = require("@dorgjelli-test/ipfs-http-client-lite"); diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts new file mode 100644 index 0000000000..9696cffde8 --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/index.ts @@ -0,0 +1,120 @@ +import { + Module, + Args_tryResolveUri, + Args_getFile, + Bytes, + UriResolver_MaybeUriOrManifest, + manifest, + Ipfs_Module, + Client, +} from "./wrap"; + +import { PluginFactory } from "@polywrap/core-js"; + +// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports +const isIPFS = require("is-ipfs"); + +export interface IpfsResolverPluginConfig extends Record { + provider: string; + fallbackProviders?: string[]; +} + +export class IpfsResolverPlugin extends Module { + // uri-resolver.core.web3api.eth + public async tryResolveUri( + args: Args_tryResolveUri, + _client: Client + ): Promise { + if (args.authority !== "ipfs") { + return null; + } + + if (!IpfsResolverPlugin.isCID(args.path)) { + // Not a valid CID + return { manifest: null, uri: null }; + } + + const manifestSearchPatterns = ["polywrap.json"]; + + let manifest: string | undefined; + + for (const manifestSearchPattern of manifestSearchPatterns) { + try { + const manifestResult = await Ipfs_Module.cat( + { + cid: `${args.path}/${manifestSearchPattern}`, + options: { + timeout: 5000, + }, + }, + _client + ); + + if (manifestResult.data) { + manifest = Buffer.from(manifestResult.data).toString("utf-8"); + } else { + throw manifestResult.error; + } + } catch (e) { + // TODO: logging + // https://github.com/web3-api/monorepo/issues/33 + } + } + + return { uri: null, manifest: manifest ?? null }; + } + + public async getFile( + args: Args_getFile, + client: Client + ): Promise { + try { + const resolveResult = await Ipfs_Module.resolve( + { + cid: args.path, + options: { + timeout: 5000, + }, + }, + client + ); + + const result = resolveResult.data; + + if (!result) { + return null; + } + + const catResult = await Ipfs_Module.cat( + { + cid: result.cid, + options: { + provider: result.provider, + timeout: 20000, + disableParallelRequests: true, + }, + }, + client + ); + + return catResult.data ?? null; + } catch (e) { + return null; + } + } + + private static isCID(cid: string): boolean { + return isIPFS.cid(cid) || isIPFS.cidPath(cid) || isIPFS.ipfsPath(cid); + } +} + +export const ipfsResolverPlugin: PluginFactory = ( + opts: IpfsResolverPluginConfig +) => { + return { + factory: () => new IpfsResolverPlugin(opts), + manifest, + }; +}; + +export const plugin = ipfsResolverPlugin; diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql new file mode 100644 index 0000000000..2ba8f5de0f --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/src/schema.graphql @@ -0,0 +1,5 @@ +#import { Module, MaybeUriOrManifest } into UriResolver from "ens/uri-resolver.core.polywrap.eth" +#import { Module } into Ipfs from "ens/ipfs.polywrap.eth" + +type Module implements UriResolver_Module { +} \ No newline at end of file diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.build.json b/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.build.json new file mode 100644 index 0000000000..77aadfdd2f --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./src/**/*.ts" + ], + "exclude": [ + "./src/**/__tests__" + ] +} diff --git a/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.json b/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.json new file mode 100644 index 0000000000..57a0c4369c --- /dev/null +++ b/packages/js/plugins/uri-resolvers/ipfs-resolver/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../../../tsconfig", + "compilerOptions": { + "outDir": "build" + }, + "include": [ + "./src/**/*.ts" + ], + "exclude": [] +} diff --git a/packages/js/react/.gitignore b/packages/js/react/.gitignore deleted file mode 100644 index 41b5d9ec55..0000000000 --- a/packages/js/react/.gitignore +++ /dev/null @@ -1 +0,0 @@ -thread.js \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 920e3d85d0..22fe67aa2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5760,7 +5760,7 @@ cids@^0.7.1: multicodec "^1.0.0" multihashes "~0.4.15" -cids@^1.0.0, cids@^1.1.4: +cids@^1.0.0: version "1.1.9" resolved "https://registry.yarnpkg.com/cids/-/cids-1.1.9.tgz#402c26db5c07059377bcd6fb82f2a24e7f2f4a4f" integrity sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg== @@ -12739,7 +12739,7 @@ multicodec@^3.0.1: uint8arrays "^3.0.0" varint "^6.0.0" -multiformats@^9.4.2: +multiformats@9.7.0, multiformats@^9.4.2: version "9.7.0" resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.7.0.tgz#845799e8df70fbb6b15922500e45cb87cf12f7e5" integrity sha512-uv/tcgwk0yN4DStopnBN4GTgvaAlYdy6KnZpuzEPFOYQd71DYFJjs0MN1ERElAflrZaYyGBWXyGxL5GgrxIx0Q==