Skip to content

Commit

Permalink
PoC dnslink support
Browse files Browse the repository at this point in the history
- based on functionality from legacy-sdk branch (see issue #44)
- DNS lookups are stored in LRU Cache
- does not redirect on initial cache miss (room for improvement)
- disabled by default (in current form degrades browsing performance)
  • Loading branch information
lidel committed Jan 10, 2017
1 parent c585ccc commit d46bf4d
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 14 deletions.
6 changes: 6 additions & 0 deletions add-on/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
"icons": {
"48": "icons/ipfs-logo-on.svg"
},
"applications": {
"gecko": {
"id": "ipfs-firefox-addon@lidel.org",
"strict_min_version": "50.0"
}
},

"permissions": [
"<all_urls>",
Expand Down
1 change: 1 addition & 0 deletions add-on/src/background/background.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
<meta charset="utf-8">
<script src="../lib/npm/is-ipfs.min.js"></script>
<script src="../lib/npm/ipfs-api.min.js"></script>
<script src="../lib/npm/lru.js"></script>
<script src="../lib/common.js"></script>
<script src="background.js"></script>
132 changes: 123 additions & 9 deletions add-on/src/lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ function initIpfsApi (ipfsApiUrl) {

function initStates (options) {
state.redirect = options.useCustomGateway
state.apiURLString = options.ipfsApiUrl
state.apiURL = new URL(state.apiURLString)
state.gwURLString = options.customGatewayUrl
state.gwURL = new URL(state.gwURLString)
state.automaticMode = options.automaticMode
state.dnslink = options.dnslink
state.dnslinkCache = /* global LRUMap */ new LRUMap(1000)
getSwarmPeerCount()
.then(updatePeerCountState)
.then(updateBrowserActionBadge)
Expand Down Expand Up @@ -70,17 +74,122 @@ function publicIpfsResource (url) {
return window.IsIpfs.url(url) && !url.startsWith(state.gwURLString)
}

function redirectToCustomGateway (request) {
const url = new URL(request.url)
url.protocol = state.gwURL.protocol
url.host = state.gwURL.host
url.port = state.gwURL.port
return { redirectUrl: url.toString() }
}

function onBeforeRequest (request) {
if (state.redirect && publicIpfsResource(request.url)) {
const newUrl = new URL(request.url)
newUrl.protocol = state.gwURL.protocol
newUrl.host = state.gwURL.host
newUrl.port = state.gwURL.port
console.log('redirecting: ' + request.url + ' to ' + newUrl.toString())
return { redirectUrl: newUrl.toString() }
if (state.redirect) {
// IPFS resources
if (publicIpfsResource(request.url)) {
return redirectToCustomGateway(request)
}
// Look for dnslink in TXT records of visited sites
if (isDnslookupEnabled(request)) {
return dnslinkLookup(request)
}
}
}

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

function isDnslookupEnabled (request) {
return state.dnslink &&
state.peerCount > 0 &&
request.url.startsWith('http') &&
!request.url.startsWith(state.apiURLString) &&
!request.url.startsWith(state.gwURLString)
}

function dnslinkLookup (request) {
// TODO: benchmark and improve performance
const requestUrl = new URL(request.url)
const fqdn = requestUrl.hostname
let dnslink = state.dnslinkCache.get(fqdn)
if (typeof dnslink === 'undefined') {
// fetching fresh dnslink is expensive, so we switch to async
console.log('dnslink cache miss for: ' + fqdn)
/* According to https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/onBeforeRequest
* "From Firefox 52 onwards, instead of returning BlockingResponse, the listener can return a Promise
* which is resolved with a BlockingResponse. This enables the listener to process the request asynchronously."
*
* Seems that this does not work yet, and even tho promise is executed, request is not blocked but resolves to regular URL.
* TODO: This should be revisited after Firefox 52 is released. If does not work by then, we need to fill a bug.
*/
return asyncDnslookupResponse(fqdn, requestUrl)
}
if (dnslink) {
console.log('SYNC resolving to Cached dnslink redirect:' + fqdn)
return redirectToDnslinkPath(requestUrl, dnslink)
}
}

function asyncDnslookupResponse (fqdn, requestUrl) {
return readDnslinkTxtRecordFromApi(fqdn)
.then(dnslink => {
if (dnslink) {
state.dnslinkCache.set(fqdn, dnslink)
console.log('ASYNC Resolved dnslink for:' + fqdn + ' is: ' + dnslink)
return redirectToDnslinkPath(requestUrl, dnslink)
} else {
state.dnslinkCache.set(fqdn, false)
console.log('ASYNC NO dnslink for:' + fqdn)
return {}
}
})
.catch((error) => {
console.error(`ASYNC Error in asyncDnslookupResponse for '${fqdn}': ${error}`)
console.error(error)
return {}
})
}

function redirectToDnslinkPath (url, dnslink) {
url.protocol = state.gwURL.protocol
url.host = state.gwURL.host
url.port = state.gwURL.port
url.pathname = dnslink + url.pathname
return { redirectUrl: url.toString() }
}

function readDnslinkTxtRecordFromApi (fqdn) {
// js-ipfs-api does not provide method for fetching this
// TODO: revisit after https://github.com/ipfs/js-ipfs-api/issues/501 is addressed
return new Promise((resolve, reject) => {
const apiCall = state.apiURLString + '/api/v0/dns/' + fqdn
const xhr = new XMLHttpRequest() // older XHR API us used because window.fetch appends Origin which causes error 403 in go-ipfs
xhr.open('GET', apiCall)
xhr.setRequestHeader('Accept', 'application/json')
xhr.onload = function () {
if (this.status === 200) {
const dnslink = JSON.parse(xhr.responseText).Path
resolve(dnslink)
} else if (this.status === 500) {
// go-ipfs returns 500 if host has no dnslink
// TODO: find/fill an upstream bug to make this more intuitive
resolve(false)
} else {
reject({
status: this.status,
statusText: xhr.statusText
})
}
}
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
})
}
xhr.send()
})
}

// ALARMS
// ===================================================================

Expand Down Expand Up @@ -179,8 +288,8 @@ function updateBrowserActionBadge () {

function setBrowserActionBadge (text, color, icon) {
return Promise.all([
browser.browserAction.setBadgeText({text: text}),
browser.browserAction.setBadgeBackgroundColor({color: color}),
browser.browserAction.setBadgeText({text: text}),
browser.browserAction.setIcon({path: icon})
])
}
Expand All @@ -192,6 +301,7 @@ const optionDefaults = Object.freeze({
publicGateways: 'ipfs.io gateway.ipfs.io ipfs.pics global.upload',
useCustomGateway: true,
automaticMode: true,
dnslink: false,
customGatewayUrl: 'http://127.0.0.1:8080',
ipfsApiUrl: 'http://127.0.0.1:5001'
})
Expand Down Expand Up @@ -240,7 +350,9 @@ function onStorageChange (changes, area) { // eslint-disable-line no-unused-vars
// debug info
// console.info(`Storage key "${key}" in namespace "${area}" changed. Old value was "${change.oldValue}", new value is "${change.newValue}".`)
if (key === 'ipfsApiUrl') {
ipfs = initIpfsApi(change.newValue)
state.apiURLString = change.newValue
state.apiURL = new URL(state.apiURLString)
ipfs = initIpfsApi(state.apiURLString)
browser.alarms.create(ipfsApiStatusUpdateAlarm, {})
} else if (key === 'customGatewayUrl') {
state.gwURLString = change.newValue
Expand All @@ -250,6 +362,8 @@ function onStorageChange (changes, area) { // eslint-disable-line no-unused-vars
browser.alarms.create(ipfsRedirectUpdateAlarm, {})
} else if (key === 'automaticMode') {
state.automaticMode = change.newValue
} else if (key === 'dnslink') {
state.dnslink = change.newValue
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions add-on/src/options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

<p><label>Automatic Mode <small>(Redirect only when IPFS API is Online and node has peers)</small> <input type="checkbox" id="automaticMode" /></label></p>

<p><label>Enable <code>dnslink</code> lookups for every website <small>(experimental, degrades browser performance)</small> <input type="checkbox" id="dnslink" /></label></p>

<p><label>Public Gateways Hosts <input type="text" id="publicGateways" size="40" require placeholder="List of hostnames separated with spaces"/></label></p>

<p><label>Custom Gateway URL <input type="url" id="customGatewayUrl" size="40" required pattern="https?://.+"/></label></p>
Expand Down
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"start": "run-s clean build test firefox",
"clean": "shx rm -f add-on/src/lib/npm/*.js build/*.zip",
"build": "run-s clean build:*",
"build:copy-is-ipfs-lib": "shx cp node_modules/is-ipfs/dist/index.min.js add-on/src/lib/npm/is-ipfs.min.js",
"build:copy-ipfs-api-lib": "shx cp node_modules/ipfs-api/dist/index.min.js add-on/src/lib/npm/ipfs-api.min.js",
"build:copy-is-ipfs-lib": "cp node_modules/is-ipfs/dist/index.min.js add-on/src/lib/npm/is-ipfs.min.js",
"build:copy-ipfs-api-lib": "cp node_modules/ipfs-api/dist/index.min.js add-on/src/lib/npm/ipfs-api.min.js",
"build:copy-lru-map-lib": "cp node_modules/lru_map/lru.js add-on/src/lib/npm/lru.js",
"build:bundle-extension": "web-ext build -s add-on/ -a build/",
"test": "run-s test:*",
"test:unit": "cross-env NODE_ENV=test karma start",
Expand All @@ -29,7 +30,7 @@
"pre-commit": "git:commit",
"standard": {
"ignore": [
"add-on/lib/npm/**"
"add-on/src/lib/npm/**"
]
},
"devDependencies": {
Expand Down Expand Up @@ -61,6 +62,7 @@
},
"dependencies": {
"ipfs-api": "12.1.2",
"is-ipfs": "0.2.1"
"is-ipfs": "0.2.1",
"lru_map": "0.3.2"
}
}
2 changes: 1 addition & 1 deletion test/unit/00-init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('init.js', function () {
describe('init()', function () {
beforeEach(() => {
browser.storage.local.get.returns(Promise.resolve(optionDefaults))
//sandbox.stub(window, 'smokeTestLibs')
// sandbox.stub(window, 'smokeTestLibs')
})
it('should query local storage for options with hardcoded defaults for fallback', done => {
init()
Expand Down

0 comments on commit d46bf4d

Please sign in to comment.