diff options
Diffstat (limited to 'app/scripts/lib/ens-ipfs')
-rw-r--r-- | app/scripts/lib/ens-ipfs/contracts/registrar.js | 1 | ||||
-rw-r--r-- | app/scripts/lib/ens-ipfs/contracts/resolver.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/ens-ipfs/resolver.js | 54 | ||||
-rw-r--r-- | app/scripts/lib/ens-ipfs/setup.js | 63 |
4 files changed, 120 insertions, 0 deletions
diff --git a/app/scripts/lib/ens-ipfs/contracts/registrar.js b/app/scripts/lib/ens-ipfs/contracts/registrar.js new file mode 100644 index 000000000..99ca24458 --- /dev/null +++ b/app/scripts/lib/ens-ipfs/contracts/registrar.js @@ -0,0 +1 @@ +module.exports = [{'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'resolver', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'label', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setSubnodeOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'ttl', 'type': 'uint64'}], 'name': 'setTTL', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'ttl', 'outputs': [{'name': '', 'type': 'uint64'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'resolver', 'type': 'address'}], 'name': 'setResolver', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'owner', 'type': 'address'}], 'name': 'setOwner', 'outputs': [], 'payable': false, 'type': 'function'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'label', 'type': 'bytes32'}, {'indexed': false, 'name': 'owner', 'type': 'address'}], 'name': 'NewOwner', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'resolver', 'type': 'address'}], 'name': 'NewResolver', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'ttl', 'type': 'uint64'}], 'name': 'NewTTL', 'type': 'event'}] diff --git a/app/scripts/lib/ens-ipfs/contracts/resolver.js b/app/scripts/lib/ens-ipfs/contracts/resolver.js new file mode 100644 index 000000000..1bf3f90ce --- /dev/null +++ b/app/scripts/lib/ens-ipfs/contracts/resolver.js @@ -0,0 +1,2 @@ +module.exports = +[{'constant': true, 'inputs': [{'name': 'interfaceID', 'type': 'bytes4'}], 'name': 'supportsInterface', 'outputs': [{'name': '', 'type': 'bool'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentTypes', 'type': 'uint256'}], 'name': 'ABI', 'outputs': [{'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'name': 'setPubkey', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'content', 'outputs': [{'name': 'ret', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'addr', 'outputs': [{'name': 'ret', 'type': 'address'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'contentType', 'type': 'uint256'}, {'name': 'data', 'type': 'bytes'}], 'name': 'setABI', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'name', 'outputs': [{'name': 'ret', 'type': 'string'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'name', 'type': 'string'}], 'name': 'setName', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'hash', 'type': 'bytes32'}], 'name': 'setContent', 'outputs': [], 'payable': false, 'type': 'function'}, {'constant': true, 'inputs': [{'name': 'node', 'type': 'bytes32'}], 'name': 'pubkey', 'outputs': [{'name': 'x', 'type': 'bytes32'}, {'name': 'y', 'type': 'bytes32'}], 'payable': false, 'type': 'function'}, {'constant': false, 'inputs': [{'name': 'node', 'type': 'bytes32'}, {'name': 'addr', 'type': 'address'}], 'name': 'setAddr', 'outputs': [], 'payable': false, 'type': 'function'}, {'inputs': [{'name': 'ensAddr', 'type': 'address'}], 'payable': false, 'type': 'constructor'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'a', 'type': 'address'}], 'name': 'AddrChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'hash', 'type': 'bytes32'}], 'name': 'ContentChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'name', 'type': 'string'}], 'name': 'NameChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': true, 'name': 'contentType', 'type': 'uint256'}], 'name': 'ABIChanged', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': 'node', 'type': 'bytes32'}, {'indexed': false, 'name': 'x', 'type': 'bytes32'}, {'indexed': false, 'name': 'y', 'type': 'bytes32'}], 'name': 'PubkeyChanged', 'type': 'event'}] diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js new file mode 100644 index 000000000..fe2dc1134 --- /dev/null +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -0,0 +1,54 @@ +const namehash = require('eth-ens-namehash') +const multihash = require('multihashes') +const Eth = require('ethjs-query') +const EthContract = require('ethjs-contract') +const registrarAbi = require('./contracts/registrar') +const resolverAbi = require('./contracts/resolver') + +module.exports = resolveEnsToIpfsContentId + + +async function resolveEnsToIpfsContentId ({ provider, name }) { + const eth = new Eth(provider) + const hash = namehash.hash(name) + const contract = new EthContract(eth) + // lookup registrar + const chainId = Number.parseInt(await eth.net_version(), 10) + const registrarAddress = getRegistrarForChainId(chainId) + if (!registrarAddress) { + throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`) + } + const Registrar = contract(registrarAbi).at(registrarAddress) + // lookup resolver + const resolverLookupResult = await Registrar.resolver(hash) + const resolverAddress = resolverLookupResult[0] + if (hexValueIsEmpty(resolverAddress)) { + throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`) + } + const Resolver = contract(resolverAbi).at(resolverAddress) + // lookup content id + const contentLookupResult = await Resolver.content(hash) + const contentHash = contentLookupResult[0] + if (hexValueIsEmpty(contentHash)) { + throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`) + } + const nonPrefixedHex = contentHash.slice(2) + const buffer = multihash.fromHexString(nonPrefixedHex) + const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256')) + return contentId +} + +function hexValueIsEmpty(value) { + return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value) +} + +function getRegistrarForChainId (chainId) { + switch (chainId) { + // mainnet + case 1: + return '0x314159265dd8dbb310642f98f50c066173c1259b' + // ropsten + case 3: + return '0x112234455c3a32fd11230c42e7bccd4a84e02010' + } +} diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js new file mode 100644 index 000000000..45eb1ce14 --- /dev/null +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -0,0 +1,63 @@ +const urlUtil = require('url') +const extension = require('extensionizer') +const resolveEnsToIpfsContentId = require('./resolver.js') + +const supportedTopLevelDomains = ['eth'] + +module.exports = setupEnsIpfsResolver + +function setupEnsIpfsResolver({ provider }) { + + // install listener + const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`) + extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns }) + + // return api object + return { + // uninstall listener + remove () { + extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail) + }, + } + + async function webRequestDidFail (details) { + const { tabId, url } = details + // ignore requests that are not associated with tabs + if (tabId === -1) return + // parse ens name + const urlData = urlUtil.parse(url) + const { hostname: name, path, search } = urlData + const domainParts = name.split('.') + const topLevelDomain = domainParts[domainParts.length - 1] + // if unsupported TLD, abort + if (!supportedTopLevelDomains.includes(topLevelDomain)) return + // otherwise attempt resolve + attemptResolve({ tabId, name, path, search }) + } + + async function attemptResolve({ tabId, name, path, search }) { + extension.tabs.update(tabId, { url: `loading.html` }) + try { + const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name }) + let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}` + try { + // check if ipfs gateway has result + const response = await fetch(url, { method: 'HEAD' }) + // if failure, redirect to 404 page + if (response.status !== 200) { + extension.tabs.update(tabId, { url: '404.html' }) + return + } + // otherwise redirect to the correct page + extension.tabs.update(tabId, { url }) + } catch (err) { + console.warn(err) + // if HEAD fetch failed, redirect so user can see relevant error page + extension.tabs.update(tabId, { url }) + } + } catch (err) { + console.warn(err) + extension.tabs.update(tabId, { url: `error.html?name=${name}` }) + } + } +} |