Skip to content

Commit

Permalink
feat: Option to customize Default Public Gateway
Browse files Browse the repository at this point in the history
Now that we can detect IPFS resources on any page,
list of public gateways is no longer needed.

This commit replaces the list with type-safe URL
of a single public gateway.

User-defined public gateway is used in:

- "copy public gateway URL" function
- links made by linkifyDOM experiment
- custom protocol handlers when IPFS API is down
  (fallback gateway)

Closes #284
  • Loading branch information
lidel committed Oct 8, 2017
1 parent 64c498e commit 6bb4ba1
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 48 deletions.
70 changes: 49 additions & 21 deletions add-on/src/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ function initIpfsApi (ipfsApiUrl) {
}

function initStates (options) {
// we store the most used values in optimized form
// to minimize performance impact on overall browsing experience
state.pubGwURL = new URL(options.publicGatewayUrl)
state.pubGwURLString = state.pubGwURL.toString()
state.redirect = options.useCustomGateway
state.apiURL = new URL(options.ipfsApiUrl)
state.apiURLString = state.apiURL.toString()
Expand Down Expand Up @@ -62,24 +66,36 @@ function publicIpfsOrIpnsResource (url) {
return true
}
// /ipns/ requires multiple stages/branches, as it can be FQDN with dnslink or CID
if (window.IsIpfs.ipnsUrl(url)) {
const ipnsRoot = new URL(url).pathname.match(/^\/ipns\/([^/]+)/)[1]
// console.log('=====> IPNS root', ipnsRoot)
// first check if root is a regular CID
if (window.IsIpfs.cid(ipnsRoot)) {
// console.log('=====> IPNS is a valid CID', ipnsRoot)
return true
}
if (isDnslookupSafe(url) && cachedDnslinkLookup(ipnsRoot)) {
// console.log('=====> IPNS for FQDN with valid dnslink: ', ipnsRoot)
return true
}
if (window.IsIpfs.ipnsUrl(url) && validIpnsPath(new URL(url).pathname)) {
return true
}
}
// everything else is not ipfs-related
return false
}

function validIpnsPath (path) {
if (window.IsIpfs.ipnsPath(path)) {
// we may have false-positives here, so we do additional checks below
const ipnsRoot = path.match(/^\/ipns\/([^/]+)/)[1]
// console.log('==> IPNS root', ipnsRoot)
// first check if root is a regular CID
if (window.IsIpfs.cid(ipnsRoot)) {
// console.log('==> IPNS is a valid CID', ipnsRoot)
return true
}
if (isDnslookupPossible() && cachedDnslinkLookup(ipnsRoot)) {
// console.log('==> IPNS for FQDN with valid dnslink: ', ipnsRoot)
return true
}
}
return false
}

function validIpfsOrIpnsPath (path) {
return window.IsIpfs.ipfsPath(path) || validIpnsPath(path)
}

function redirectToCustomGateway (requestUrl) {
const url = new URL(requestUrl)
url.protocol = state.gwURL.protocol
Expand Down Expand Up @@ -139,7 +155,7 @@ function onBeforeRequest (request) {
return redirectToCustomGateway(request.url)
}
// Look for dnslink in TXT records of visited sites
if (state.dnslink && isDnslookupSafe(request.url)) {
if (state.dnslink && isDnslookupSafeForURL(request.url)) {
return dnslinkLookupAndOptionalRedirect(request.url)
}
}
Expand All @@ -154,8 +170,8 @@ function webPlusProtocolRequest (request) {
return request.url.startsWith(webPlusProtocolHandler)
}

function pathAtPublicGw (path) {
return new URL(`https://ipfs.io${path}`).toString()
function urlAtPublicGw (path) {
return new URL(`${state.pubGwURLString}${path}`).toString().replace(/([^:]\/)\/+/g, '$1')
}

function normalizedWebPlusRequest (request) {
Expand All @@ -165,7 +181,7 @@ function normalizedWebPlusRequest (request) {
path = path.replace(/^\/web\+ipfs:\/\//i, '/ipfs/') // web+ipfs://Qm → /ipfs/Qm
path = path.replace(/^\/web\+ipns:\/\//i, '/ipns/') // web+ipns://Qm → /ipns/Qm
if (oldPath !== path && window.IsIpfs.path(path)) {
return { redirectUrl: pathAtPublicGw(path) }
return { redirectUrl: urlAtPublicGw(path) }
}
return null
}
Expand Down Expand Up @@ -194,15 +210,21 @@ function normalizedUnhandledIpfsProtocol (request) {
if (window.IsIpfs.path(path)) {
// replace search query with fake request to the public gateway
// (will be redirected later, if needed)
return { redirectUrl: pathAtPublicGw(path) }
return { redirectUrl: urlAtPublicGw(path) }
}
}

// DNSLINK
// ===================================================================

function isDnslookupSafe (requestUrl) {
return state.peerCount > 0 &&
function isDnslookupPossible () {
// DNS lookups require IPFS API to be up
// and have a confirmed connection to the internet
return state.peerCount > 0
}

function isDnslookupSafeForURL (requestUrl) {
return isDnslookupPossible() &&
requestUrl.startsWith('http') &&
!requestUrl.startsWith(state.apiURLString) &&
!requestUrl.startsWith(state.gwURLString)
Expand Down Expand Up @@ -280,8 +302,10 @@ function readDnslinkFromTxtRecord (fqdn) {

function onRuntimeMessage (request, sender) {
// console.log((sender.tab ? 'Message from a content script:' + sender.tab.url : 'Message from the extension'), request)
if (request.isIpfsPath) {
return Promise.resolve({isIpfsPath: window.IsIpfs.path(request.isIpfsPath)})
if (request.pubGwUrlForIpfsOrIpnsPath) {
const path = request.pubGwUrlForIpfsOrIpnsPath
const result = validIpfsOrIpnsPath(path) ? urlAtPublicGw(path) : null
return Promise.resolve({pubGwUrlForIpfsOrIpnsPath: result})
}
}

Expand Down Expand Up @@ -317,6 +341,7 @@ async function sendStatusUpdateToBrowserAction () {
const info = {
peerCount: state.peerCount,
gwURLString: state.gwURLString,
pubGwURLString: state.pubGwURLString,
currentTab: await browser.tabs.query({active: true, currentWindow: true}).then(tabs => tabs[0])
}
try {
Expand Down Expand Up @@ -640,6 +665,9 @@ function onStorageChange (changes, area) { // eslint-disable-line no-unused-vars
} else if (key === 'customGatewayUrl') {
state.gwURL = new URL(change.newValue)
state.gwURLString = state.gwURL.toString()
} else if (key === 'publicGatewayUrl') {
state.pubGwURL = new URL(change.newValue)
state.pubGwURLString = state.pubGwURL.toString()
} else if (key === 'useCustomGateway') {
state.redirect = change.newValue
} else if (key === 'linkify') {
Expand Down
13 changes: 4 additions & 9 deletions add-on/src/lib/linkifyDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* plain text with IPFS addresses with clickable links.
* Loosely based on https://github.com/mdn/webextensions-examples/blob/master/emoji-substitution/substitute.js
* Note that this is a quick&dirty PoC and may slow down browsing experience.
* Test page: http://bit.ly/2fgkF4E
* Test page: http://bit.ly/2yuknPI
* TODO: measure & improve performance
*/

Expand Down Expand Up @@ -130,16 +130,11 @@
}
try {
// Callback wrapped in promise -- Chrome compatibility
const checkResult = await browser.runtime.sendMessage({isIpfsPath: path})
if (checkResult.isIpfsPath) {
// TODO: use customizable public gateway
window.ipfsLinkifyValidationCache.set(path, 'https://ipfs.io' + path)
} else {
window.ipfsLinkifyValidationCache.set(path, null)
}
const checkResult = await browser.runtime.sendMessage({pubGwUrlForIpfsOrIpnsPath: path})
window.ipfsLinkifyValidationCache.set(path, checkResult.pubGwUrlForIpfsOrIpnsPath)
} catch (error) {
window.ipfsLinkifyValidationCache.set(path, null)
console.error('isIpfsPath.error for ' + path, error)
console.error('pubGwUrlForIpfsOrIpnsPath.error for ' + path, error)
}
return window.ipfsLinkifyValidationCache.get(path)
}
Expand Down
4 changes: 1 addition & 3 deletions add-on/src/lib/option-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-env browser, webextensions */

const optionDefaults = Object.freeze({ // eslint-disable-line no-unused-vars
publicGateways: 'ipfs.io gateway.ipfs.io ipfs.pics global.upload',
publicGatewayUrl: 'https://ipfs.io',
useCustomGateway: true,
automaticMode: true,
linkify: false,
Expand All @@ -12,6 +12,4 @@ const optionDefaults = Object.freeze({ // eslint-disable-line no-unused-vars
customGatewayUrl: 'http://127.0.0.1:8080',
ipfsApiUrl: 'http://127.0.0.1:5001',
ipfsApiPollMs: 3000
// TODO:
// defaultToFsProtocol
})
24 changes: 12 additions & 12 deletions add-on/src/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,14 @@
<form>
<fieldset>
<legend>Gateways</legend>
<div>
<label for="publicGateways">
<dl>
<dt>Public Gateways</dt>
<dd>List of hostnames known to expose IPFS under <code>/ipfs/</code></dd>
</dl>
</label>
<textarea id="publicGateways" rows="4" required autocomplete="off" spellcheck="false" placeholder="List of hostnames separated with spaces or newlines"></textarea>
</div>
<div>
<label for="customGatewayUrl">
<dl>
<dt>Custom Gateway</dt>
<dt>Custom Local Gateway</dt>
<dd>URL of preferred HTTP2IPFS&nbsp;Gateway</dd>
</dl>
</label>
<input type="url" id="customGatewayUrl" inputmode="url" required pattern="https?://.+"/>
<input type="url" id="customGatewayUrl" inputmode="url" required pattern="https?://[^/].+"/>
</div>
<div>
<label for="useCustomGateway">
Expand All @@ -100,6 +91,15 @@
</label>
<input type="checkbox" id="useCustomGateway" />
</div>
<div>
<label for="publicGatewayUrl">
<dl>
<dt>Default Public Gateway</dt>
<dd>Fallback URL used when Custom Gateway is not available and for copying shareable links</dd>
</dl>
</label>
<input type="url" id="publicGatewayUrl" inputmode="url" required pattern="https?://[^/]+"/>
</div>
</fieldset>
</form>
<form>
Expand All @@ -112,7 +112,7 @@
<dd>Hint: this is where <code>/webui</code> lives</dd>
</dl>
</label>
<input type="url" id="ipfsApiUrl" inputmode="url" required pattern="https?://.+"/>
<input type="url" id="ipfsApiUrl" inputmode="url" required pattern="https?://[^/]+"/>
</div>
<div>
<label for="ipfsApiPollMs">
Expand Down
4 changes: 2 additions & 2 deletions add-on/src/options/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function saveOption (name) {
change[name] = element.value
break
case 'textarea':
// normalize input into a string of hostnames separated by a single space
// normalize input into a string of entries separated by a single space
change[name] = element.value.replace(/[\r\n,;]+/g, ' ').replace(/ +(?= )/g, '').trim()
break
case 'checkbox':
Expand All @@ -39,7 +39,7 @@ async function readOption (name) {
break
case 'textarea':
element.onfocusout = () => saveOption(name)
// display every hostname in a new line
// display every entry in a new line
element.value = oldValue.trim().split(' ').join('\n')
break
case 'number':
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function notify (title, message) {
// ===================================================================

async function copyCurrentPublicGwAddress () {
const publicGwAddress = new URL(state.currentTab.url.replace(state.gwURLString, 'https://ipfs.io/')).toString()
const publicGwAddress = new URL(state.currentTab.url.replace(state.gwURLString, state.pubGwURLString)).toString()
copyTextToClipboard(publicGwAddress)
notify('notify_copiedPublicURLTitle', publicGwAddress)
window.close()
Expand Down

0 comments on commit 6bb4ba1

Please sign in to comment.