// eslint-disable-next-line no-unused-vars import axios, { AxiosResponse } from 'axios' import { BzzNode as Bzz } from '@erebos/bzz-node' export interface Imported { content: string; cleanUrl: string; type: string; } interface PreviouslyHandledImports { [filePath: string]: Imported } interface Handler { type: string; match(url: string): any; handle(match: any): any; } interface HandlerResponse { content: any; cleanUrl: string } export class RemixURLResolver { private previouslyHandled: PreviouslyHandledImports gistAccessToken: string protocol: string constructor (gistToken?: string, protocol = 'http:') { this.previouslyHandled = {} this.setGistToken(gistToken, protocol) } async setGistToken (gistToken?: string, protocol = 'http:') { this.gistAccessToken = gistToken || '' this.protocol = protocol } /** * Handle an import statement based on github * @param root The root of the github import statement * @param filePath path of the file in github */ async handleGithubCall (root: string, filePath: string): Promise { const regex = filePath.match(/blob\/([^/]+)\/(.*)/) let reference = 'master' if (regex) { // if we have /blob/master/+path we extract the branch name "master" and add it as a parameter to the github api // the ref can be branch name, tag, commit id reference = regex[1] filePath = filePath.replace(`blob/${reference}/`, '') } // eslint-disable-next-line no-useless-catch try { const req = `https://raw.githubusercontent.com/${root}/${reference}/${filePath}` const response: AxiosResponse = await axios.get(req, { transformResponse: [] }) return { content: response.data, cleanUrl: root + '/' + filePath } } catch (e) { throw e } } /** * Handle an import statement based on http * @param url The url of the import statement * @param cleanUrl */ async handleHttp (url: string, cleanUrl: string): Promise { // eslint-disable-next-line no-useless-catch try { const response: AxiosResponse = await axios.get(url, { transformResponse: [] }) return { content: response.data, cleanUrl } } catch (e) { throw e } } /** * Handle an import statement based on https * @param url The url of the import statement * @param cleanUrl */ async handleHttps (url: string, cleanUrl: string): Promise { // eslint-disable-next-line no-useless-catch try { const response: AxiosResponse = await axios.get(url, { transformResponse: [] }) return { content: response.data, cleanUrl } } catch (e) { throw e } } async handleSwarm (url: string, cleanUrl: string): Promise { // eslint-disable-next-line no-useless-catch try { const bzz = new Bzz({ url: this.protocol + '//swarm-gateways.net' }) const url = bzz.getDownloadURL(cleanUrl, { mode: 'raw' }) const response: AxiosResponse = await axios.get(url, { transformResponse: [] }) return { content: response.data, cleanUrl } } catch (e) { throw e } } /** * Handle an import statement based on IPFS * @param url The url of the IPFS import statement */ async handleIPFS (url: string): Promise { // replace ipfs:// with /ipfs/ url = url.replace(/^ipfs:\/\/?/, 'ipfs/') // eslint-disable-next-line no-useless-catch try { const req = 'https://jqgt.remixproject.org/' + url // If you don't find greeter.sol on ipfs gateway use local // const req = 'http://localhost:8080/' + url const response: AxiosResponse = await axios.get(req, { transformResponse: [] }) return { content: response.data, cleanUrl: url.replace('ipfs/', '') } } catch (e) { throw e } } /** * Handle an import statement based on NPM * @param url The url of the NPM import statement */ async handleNpmImport (url: string): Promise { // eslint-disable-next-line no-useless-catch try { const req = 'https://unpkg.com/' + url const response: AxiosResponse = await axios.get(req, { transformResponse: [] }) return { content: response.data, cleanUrl: url } } catch (e) { throw e } } getHandlers (): Handler[] { return [ { type: 'github', match: (url) => { return /^(https?:\/\/)?(www.)?github.com\/([^/]*\/[^/]*)\/(.*)/.exec(url) }, handle: (match) => this.handleGithubCall(match[3], match[4]) }, { type: 'http', match: (url) => { return /^(http?:\/\/?(.*))$/.exec(url) }, handle: (match) => this.handleHttp(match[1], match[2]) }, { type: 'https', match: (url) => { return /^(https?:\/\/?(.*))$/.exec(url) }, handle: (match) => this.handleHttps(match[1], match[2]) }, { type: 'swarm', match: (url) => { return /^(bzz-raw?:\/\/?(.*))$/.exec(url) }, handle: (match) => this.handleSwarm(match[1], match[2]) }, { type: 'ipfs', match: (url) => { return /^(ipfs:\/\/?.+)/.exec(url) }, handle: (match) => this.handleIPFS(match[1]) }, { type: 'npm', match: (url) => { return /^[^/][^\n"?:*<>|]*$/g.exec(url) }, // match a typical relative path handle: (match) => this.handleNpmImport(match[0]) } ] } public async resolve (filePath: string, customHandlers?: Handler[]): Promise { let imported: Imported = this.previouslyHandled[filePath] if (imported) { return imported } const builtinHandlers: Handler[] = this.getHandlers() const handlers: Handler[] = customHandlers ? [...builtinHandlers, ...customHandlers] : [...builtinHandlers] const matchedHandler = handlers.filter(handler => handler.match(filePath)) const handler: Handler = matchedHandler[0] const match = handler.match(filePath) const { content, cleanUrl } = await handler.handle(match) imported = { content, cleanUrl: cleanUrl || filePath, type: handler.type } this.previouslyHandled[filePath] = imported return imported } }