Skip to content
This repository has been archived by the owner on Jun 16, 2020. It is now read-only.

IPFS Integration #1

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package-lock.json
yarn.lock

# Logs
logs
*.log
Expand Down Expand Up @@ -54,6 +57,8 @@ Brave.tar.bz2
app/extensions/gen
app/extensions/brave/gen
app/extensions/torrent/gen
app/extensions/ipfs/gen

*.pfx
buildConfig.js

Expand Down
24 changes: 18 additions & 6 deletions app/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,13 +415,17 @@ module.exports.init = () => {
return object
}

let loadExtension = (extensionId, extensionPath, manifest = {}, manifestLocation = 'unpacked') => {
if (extensionId === config.PDFJSExtensionId) {
const loadExtension = (extensionId, extensionPath, manifest = {}, manifestLocation = 'unpacked') => {
if (extensionId === config.PDFJSExtensionId ||
extensionId === config.ipfsExtensionId) {
manifestLocation = 'component'
}
if (!extensionInfo.isLoaded(extensionId) && !extensionInfo.isLoading(extensionId)) {
if (!extensionInfo.isLoaded(extensionId) &&
!extensionInfo.isLoading(extensionId)) {
extensionInfo.setState(extensionId, extensionStates.LOADING)
if (extensionId === config.braveExtensionId || extensionId === config.torrentExtensionId || extensionId === config.syncExtensionId) {
if (extensionId === config.braveExtensionId ||
extensionId === config.torrentExtensionId ||
extensionId === config.syncExtensionId) {
session.defaultSession.extensions.load(extensionPath, manifest, manifestLocation)
return
}
Expand All @@ -430,6 +434,7 @@ module.exports.init = () => {
// just a safety net.
fs.exists(path.join(extensionPath, 'manifest.json'), (exists) => {
if (exists) {
console.log('Loading:', extensionId, manifestLocation)
session.defaultSession.extensions.load(extensionPath, manifest, manifestLocation)
} else {
// This is an error condition, but we can recover.
Expand All @@ -454,18 +459,21 @@ module.exports.init = () => {
const extensions = extensionState.getExtensions(appStore.getState())
const extensionPath = extensions.getIn([extensionId, 'filePath'])
if (extensionPath) {
// Otheriwse just install it
// Otherwise just install it
loadExtension(extensionId, extensionPath)
}
}
}

// Manually install the braveExtension and torrentExtension
// Manually install the braveExtension, torrentExtension and ipfsExtension

// braveExtension
extensionInfo.setState(config.braveExtensionId, extensionStates.REGISTERED)
loadExtension(config.braveExtensionId, getExtensionsPath('brave'), generateBraveManifest(), 'component')
extensionInfo.setState(config.syncExtensionId, extensionStates.REGISTERED)
loadExtension(config.syncExtensionId, getExtensionsPath('brave'), generateSyncManifest(), 'unpacked')

// torrentExtension
if (getSetting(settings.TORRENT_VIEWER_ENABLED)) {
extensionInfo.setState(config.torrentExtensionId, extensionStates.REGISTERED)
loadExtension(config.torrentExtensionId, getExtensionsPath('torrent'), generateTorrentManifest(), 'component')
Expand All @@ -474,6 +482,10 @@ module.exports.init = () => {
extensionActions.extensionDisabled(config.torrentExtensionId)
}

// ipfsExtension
extensionInfo.setState(config.ipfsExtensionId, extensionStates.REGISTERED)
loadExtension(config.ipfsExtensionId, getExtensionsPath('ipfs'))

let registerComponents = (diff) => {
if (getSetting(settings.PDFJS_ENABLED)) {
registerComponent(config.PDFJSExtensionId, config.PDFJSExtensionPublicKey)
Expand Down
35 changes: 34 additions & 1 deletion app/extensions/brave/index-dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,40 @@
<!-- TODO: Don't allow img-src *, needed for favicons -->
<!-- TODO: Refactor away all unsafe-inline content -->
<!-- TODO: Replace suggestqueries.google.com and ac.duckduckgo.com and other search engines with a single config search engine -->
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; form-action http://localhost:*; script-src 'self' http://localhost:*; connect-src 'self' https://s3.amazonaws.com/adblock-data/ https://s3.amazonaws.com/safe-browsing-data/ https://s3.amazonaws.com/tracking-protection-data/ https://s3.amazonaws.com/https-everywhere-data/ http://localhost:* ws://localhost:* https://suggestqueries.google.com https://ac.duckduckgo.com https://completion.amazon.com https://search.yahoo.com https://api.bing.com https://www.startpage.com https://infogalactic.com https://api.qwant.com https://ac.ecosia.org https://searx.me https://www.findx.com https://brave-download.global.ssl.fastly.net https://brave-laptop-updates.global.ssl.fastly.net https://brave-download.global.ssl.fastly.net https://laptop-updates-pre.brave.com https://brave-laptop-updates-pre.brave.com; style-src 'unsafe-inline'; font-src 'self' http://localhost:*; img-src 'self' * data: file: chrome-extension:; object-src 'self'; plugin-types application/browser-plugin">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
form-action http://localhost:*;
script-src
'self'
http://localhost:*;
connect-src
'self'
https://s3.amazonaws.com/adblock-data/
https://s3.amazonaws.com/safe-browsing-data/
https://s3.amazonaws.com/tracking-protection-data/
https://s3.amazonaws.com/https-everywhere-data/
http://localhost:* ws://localhost:*
https://suggestqueries.google.com
https://ac.duckduckgo.com
https://completion.amazon.com
https://search.yahoo.com
https://api.bing.com
https://www.startpage.com
https://infogalactic.com
https://api.qwant.com
https://ac.ecosia.org
https://searx.me
https://www.findx.com
https://brave-download.global.ssl.fastly.net
https://brave-laptop-updates.global.ssl.fastly.net
https://brave-download.global.ssl.fastly.net
https://laptop-updates-pre.brave.com
https://brave-laptop-updates-pre.brave.com;
style-src 'unsafe-inline';
font-src 'self' http://localhost:*;
img-src 'self' * data: file: chrome-extension:;
object-src 'self';
plugin-types application/browser-plugin">
<meta name="referrer" content="no-referrer">
<title>Brave</title>
<script src="ext/l20n.min.js"></script>
Expand Down
1 change: 1 addition & 0 deletions app/extensions/ipfs/_locales/en/app.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ipfs=Distributed Web
1 change: 1 addition & 0 deletions app/extensions/ipfs/_locales/en/messages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions app/extensions/ipfs/ext/l20n.min.js

Large diffs are not rendered by default.

Binary file added app/extensions/ipfs/img/favicon.ico
Binary file not shown.
Binary file added app/extensions/ipfs/img/ipfs-128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/extensions/ipfs/img/ipfs-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/extensions/ipfs/img/ipfs-48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/extensions/ipfs/img/ipfs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions app/extensions/ipfs/ipfs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="availableLanguages" content="">
<meta name="defaultLanguage" content="en-US">
<meta http-equiv="Content-Security-Policy" content="
default-src 'none';
form-action http://localhost:*;
script-src 'self' 'unsafe-eval';
img-src * data: file: chrome-extension:;
style-src 'self' 'unsafe-inline'; font-src 'self';
connect-src 'self' wss://* ;
object-src 'self';
plugin-types application/browser-plugin"
/>
<meta name="referrer" content="no-referrer">
<title>IPFS</title>
<script src="js/ipfs.min.js"></script>
<script src="js/main.js"></script>
<script src="js/some-other.js"></script>
</head>
<body>
<h1>IPFS</h1>
</body>
</html>
227 changes: 227 additions & 0 deletions app/extensions/ipfs/js/ipfs.min.js

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions app/extensions/ipfs/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* global Ipfs */

chrome.protocol.registerStringProtocol('ipfs', handler)

const node = new Ipfs({
config: {
Addresses: {
Swarm: []
}
}
})

// TODO: bring back once brave supports registerURIHandlers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diasdavid do you have any background info on why registerURIHandlers is needed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because an IPFS hash is, in reality, a URI and not a URL, we should be able to do ipfs:QmHash and not ipfs://QmHash.

This is still an ongoing proposal, read more at:

// chrome.protocol.registerStringProtocol('dweb', handler)

/*
* request is an object with:
* - method (e.g GET)
* - referrer (always empty for now)
* - url (full ipfs://<path>)
* reply is a functin that takes one argument which is the response to the request
*/
function handler (request, reply) {
if (!node.isOnline()) {
node.once('ready', () => handler(request, reply))
}

const path = request.url.split('ipfs://')[1]

// TODO check if it is valid IPFS Path
// callback('hi there!' + test()) // eslint-disable-line

// TODO here I need to check if it is a directory or a file:
// if file load it
// if directory fetch it and look for an index.html
// if index.html load that
// if no index.html, load the directory listing just like the gateway

node.files.cat(path, (err, stream) => {
if (err) {
// TODO create a nice error page
return reply('err: ' + err.message)
}

// TODO replace this by something like BL
let buf = ''

stream.on('data', (data) => {
buf += data.toString()
})

stream.on('end', () => reply(buf))
})
}
3 changes: 3 additions & 0 deletions app/extensions/ipfs/js/some-other.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
function test () {
return 'yeeah'
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a test to make sure I could in fact run a function from another file.

18 changes: 18 additions & 0 deletions app/extensions/ipfs/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"manifest_version": 2,
"name": "IPFS",
"version": "0.1.0",
"default_locale": "en",
"description": "InterPlanetary FileSystem",
"icons": {
"128": "img/ipfs-128.png",
"48": "img/ipfs-48.png",
"16": "img/ipfs-16.png"
},
"offline_enabled": true,
"background": {
"page": "ipfs.html",
"persistent": true
},
"content_security_policy": "script-src 'self' 'unsafe-eval'"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the csp you have defined in the page above can be defined here instead

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the CSP is just for the IPFS extension, it should be defined here instead of in index-dev.html. i would still encourage refactoring such that unsafe-eval isn't needed.

Copy link
Collaborator Author

@daviddias daviddias Sep 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bridiver when an extension has an index.html page, the extension loader ignores this CSP and applies the one in index.html. So yes, you are right, the index.html one is the only one necessary. My test yielded a false positive, it actually needs to have this in the manifest too.

@diracdeltas agreed that we should not have the need for unsafe-eval. We are working on it :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diasdavid I think the latest version of js-ipfs-api is ready to be tested without evals :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we just use js-ipfs. It seems that there still something. Need to test this more:

image

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is definitely still a protobuf thing

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we missed two modules

npm ls protocol-buffers
ipfs@0.25.4 /Users/dignifiedquire/opensource/ipfs/js-ipfs
├─┬ libp2p-floodsub@0.11.0
│ └─┬ libp2p-crypto@0.9.4
│   └── protocol-buffers@3.2.1  deduped
└─┬ libp2p-kad-dht@0.5.0
  └── protocol-buffers@3.2.1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 So it is evident. Nice, I was worried that it was npm false deduping. Thanks for catching that!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Woot 🚀 No more unsafe-eval!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 🎉 🎉

}
1 change: 1 addition & 0 deletions docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ AppStore
'advanced.send-usage-statistics': boolean, // true or undefined if usage reports should be sent
'advanced.smooth-scroll-enabled': boolean, // false if smooth scrolling should be explicitly disabled
'advanced.torrent-viewer-enabled': boolean, // whether to render magnet links in the browser
'advanced.ipfs-enabled': boolean, // whether to use IPFS to render ipfs:// or dweb: links in the browser
'bookmarks.toolbar.show': boolean, // true if the bookmakrs toolbar should be shown
'bookmarks.toolbar.showFavicon': boolean, // true if bookmark favicons should be shown on the bookmarks toolbar
'bookmarks.toolbar.showOnlyFavicon': boolean, // true if only favicons should be shown on the bookmarks toolbar
Expand Down
1 change: 1 addition & 0 deletions js/about/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,7 @@ class AboutPreferences extends React.Component {
settings.LANGUAGE,
settings.PDFJS_ENABLED,
settings.TORRENT_VIEWER_ENABLED,
settings.IPFS_ENABLED,
settings.SMOOTH_SCROLL_ENABLED,
settings.SEND_CRASH_REPORTS,
settings.UPDATE_TO_PREVIEW_RELEASES
Expand Down
7 changes: 6 additions & 1 deletion js/constants/appConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ module.exports = {
COOKIEBLOCK: 'cookieblock', // block 3p cookies and referer
COOKIEBLOCK_ALL: 'cookieblockAll', // block all cookies and referer
SITEHACK: 'siteHacks',
WEBTORRENT: 'webtorrent'
WEBTORRENT: 'webtorrent',
IPFS: 'ipfs'
// ... other optional resource files are identified by uuid such as for regional adblock
},
cookieblock: {
Expand Down Expand Up @@ -95,6 +96,9 @@ module.exports = {
webtorrent: {
enabled: true
},
ipfs: {
enabled: true
},
adInsertion: {
enabled: false,
url: adHost
Expand Down Expand Up @@ -207,6 +211,7 @@ module.exports = {
'advanced.default-zoom-level': null,
'advanced.pdfjs-enabled': true,
'advanced.torrent-viewer-enabled': true,
'advanced.ipfs-enabled': true,
'advanced.smooth-scroll-enabled': false,
'advanced.send-crash-reports': true,
'advanced.send-usage-statistics': false,
Expand Down
1 change: 1 addition & 0 deletions js/constants/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = {
widevineComponentPublicKey: 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmhe+02cLPPAViaevk/fzODKUnb/ysaAeD8lpE9pwirV6GYOm+naTo7xPOCh8ujcR6Ryi1nPTq2GTG0CyqdDyOsZ1aRLuMZ5QqX3dJ9jXklS0LqGfosoIpGexfwggbiLvQOo9Q+IWTrAO620KAzYU0U6MV272TJLSmZPUEFY6IGQIDAQAB',
braveExtensionId: 'mnojpmjdmbbfmejpflffifhffcmidifd',
torrentExtensionId: 'fmdpfempfmekjkcfdehndghogpnpjeno',
ipfsExtensionId: 'interplanetaryfilesystemuniverse',
syncExtensionId: 'cjnmeadmgmiihncdidmfiabhenbggfjm',
// PDFJS
// Parent repo: https://github.com/diracdeltas/pdf.js
Expand Down
1 change: 1 addition & 0 deletions js/constants/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const settings = {
HARDWARE_ACCELERATION_ENABLED: 'advanced.hardware-acceleration-enabled',
PDFJS_ENABLED: 'advanced.pdfjs-enabled',
TORRENT_VIEWER_ENABLED: 'advanced.torrent-viewer-enabled',
IPFS_ENABLED: 'advanced.ipfs-enabled',
DEFAULT_ZOOM_LEVEL: 'advanced.default-zoom-level',
SMOOTH_SCROLL_ENABLED: 'advanced.smooth-scroll-enabled',
SEND_CRASH_REPORTS: 'advanced.send-crash-reports',
Expand Down
53 changes: 53 additions & 0 deletions js/lib/appUrlUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ module.exports.getTorrentExtUrl = function (relativeUrl) {
return 'chrome-extension://' + config.torrentExtensionId + '/' + relativeUrl
}

/**
* Gets the URL of a page hosted by the ipfsExtension
* Returns 'chrome-extension://<...>'
*/
module.exports.getIpfsExtUrl = function (relativeUrl) {
if (relativeUrl === undefined) {
relativeUrl = ''
}

return 'chrome-extension://' + config.ipfsExtensionId + '/' + relativeUrl
}

module.exports.getExtensionsPath = function (extensionDir) {
return (process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test')
// the path is different for release builds because extensions are not in the asar file
Expand Down Expand Up @@ -210,6 +222,47 @@ module.exports.isTargetMagnetUrl = function (input) {
return !!module.exports.getSourceMagnetUrl(input)
}

/**
* Obtains the target URL associated with a IPFS path
* Returns null if the input is not a IPFS path
* Example: getTargetIpfsPath('ipfs:/...') -> 'chrome-extension://<...>.html#/ipfs/...'
*/
module.exports.getTargetIpfsPath = function (input) {
if (!input.startsWith('ipfs://') &&
!input.startsWith('dweb:')) {
return null
}
const url = module.exports.getIpfsExtUrl('ipfs.html')
return [url, input].join('#')
}

/**
* Obtains the source IPFS Path associated with a target URL
* Returns null if the input is not the local URL for a IPFS Path
* Example: getSourceIPFSPath('chrome-extension://<...>.html#/ipfs/...') -> 'ipfs:/:...'
*/
module.exports.getSourceIPFSPath = function (input) {
if (getBaseUrl(input) !== module.exports.getIpfsExtUrl('ipfs.html')) return null
const url = decodeURIComponent(getHash(input))
return url
}

/**
* Checks if the input looks like a magnet: URL
* Example: isSourceIpfsPath('ipfs:/..') -> true
*/
module.exports.isSourceIpfsPath = function (input) {
return !!module.exports.getTargetIpfsPath(input)
}

/*
* Checks if the input looks like the local URL for a IPFS Path
* Example: isSourceIpfsPath('chrome-extension://<...>.html#/ipfs/') -> true
*/
module.exports.isTargetIPFSPath = function (input) {
return !!module.exports.getSourceIPFSPath(input)
}

/**
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be no longer necessary as we now capture the ipfs:// protocol to load the extension and not redirect certain urls to the IPFS app as before.

* Determines whether a string is a valid URL. Based on node-urlutil.js.
* @param {string} input
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"watch-all": "npm run watch & npm run watch-test",
"watch-test": "cross-env NODE_ENV=test webpack --watch",
"webpack": "webpack",
"win-renpm": "powershell ./tools/windows/re-npm.ps1"
"win-renpm": "powershell ./tools/windows/re-npm.ps1",
"ipfs": "wget https://unpkg.com/ipfs/dist/index.min.js -O app/extensions/ipfs/js/ipfs.min.js"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this step to make it simpler to reproduce my local set up.

},
"repository": "brave/browser-laptop",
"author": {
Expand Down Expand Up @@ -148,6 +149,7 @@
"electron-chromedriver": "^1.7.1",
"electron-packager": "brave/electron-packager",
"electron-prebuilt": "brave/electron-prebuilt",
"electron-rebuild": "^1.5.11",
"empty-port": "0.0.2",
"enzyme": "^2.9.1",
"flow-bin": "^0.22.1",
Expand Down