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.gistAccessToken = gistToken ? 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 { let param = '?' param += this.gistAccessToken ? 'access_token=' + this.gistAccessToken : '' 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] param += '&ref=' + reference filePath = filePath.replace(`blob/${reference}/`, '') } //eslint-disable-next-line no-useless-catch try { const req: string = `https://raw.githubusercontent.com/${root}/${reference}/${filePath}` const response: AxiosResponse = await axios.get(req) 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) 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) 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) 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://ipfsgw.komputing.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) return { content: response.data, cleanUrl: url } } 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) 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 ? cleanUrl : filePath, type: handler.type } this.previouslyHandled[filePath] = imported return imported } }