Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature-detection of chrome.sockets #711

Merged
merged 21 commits into from
Apr 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e3cb1de
wip: add chrome-net & chrome-dgram
lidel Jan 28, 2019
4f9b383
wip: working raw http server, wip hapi from js-ipfs
lidel Jan 28, 2019
6ffb723
wip: working standalone hapi v18, wip one from js-ipfs
lidel Feb 27, 2019
cf1147a
Merge 'master' into feat/brave-build-with-chrome-sockets
lidel Mar 13, 2019
870a4cb
fix: this._dht.on is not a function
lidel Mar 13, 2019
f4ba231
Merge 'master' into feat/brave-build-with-chrome-sockets
lidel Mar 18, 2019
db006df
feat: add bundle:brave:beta script
lidel Mar 18, 2019
1b15a29
feat(brave): self-hosted API and Gateway ports
lidel Mar 19, 2019
e4a82e1
chore: enable verbose logs of HTTP server in js-ipfs
lidel Mar 20, 2019
6c90904
chore(brave): support reproducible beta build
lidel Mar 26, 2019
84fee2a
Merge v2.8.0 into feat/brave-build-with-chrome-sockets
lidel Apr 1, 2019
04e842d
feat(brave): streaming compressed payload from gw
lidel Apr 3, 2019
b441d27
fix: feature-detection for chrome.sockets
lidel Apr 4, 2019
9abb740
Merge tag 'v2.8.1' into feat/brave-build-with-chrome-sockets
lidel Apr 9, 2019
5c0b495
feat: add ipfsNodeType embedded:chromesockets
lidel Apr 9, 2019
2b63636
fix: cleanup boot on clean install
lidel Apr 10, 2019
5a96d77
test: runtime-checks isBrave, hasChromeSocketsForTcp
lidel Apr 11, 2019
a0978b8
fix(brave): robust ipfs.cat + content-type sniff
lidel Apr 12, 2019
295c3fc
feat(brave): peer discovery with ws-star
lidel Apr 12, 2019
72c34f4
fix: default to External node
lidel Apr 15, 2019
2cc5084
docs: add 'Embedded + chrome.sockets' to node-types.md
lidel Apr 16, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,12 +227,16 @@
"message": "Embedded (experimental): run js-ipfs node in your browser (use only for development, read about its limitations under the link below)",
"description": "An option description on the Preferences screen (option_ipfsNodeType_description)"
},
"option_ipfsNodeType_embedded_chromesockets_description": {
"message": "Embedded with Chrome Sockets (experimental): run js-ipfs node in your browser with access to chrome.sockets APIs (details under the link below)",
"description": "An option description on the Preferences screen (option_ipfsNodeType_description)"
},
"option_ipfsNodeConfig_title": {
"message": "IPFS Node Config",
"description": "An option title on the Preferences screen (option_ipfsNodeConfig_title)"
},
"option_ipfsNodeConfig_description": {
"message": "Configuration for the embedded IPFS node. Must be valid JSON.",
"message": "Additional configuration for the embedded IPFS node (arrays will be merged). Must be valid JSON.",
"description": "An option description on the Preferences screen (option_ipfsNodeConfig_description)"
},
"option_ipfsNodeType_external": {
Expand All @@ -243,6 +247,10 @@
"message": "Embedded",
"description": "An option on the Preferences screen (option_ipfsNodeType_embedded)"
},
"option_ipfsNodeType_embedded_chromesockets": {
"message": "Embedded + chrome.sockets",
"description": "An option on the Preferences screen (option_ipfsNodeType_embedded_chromesockets)"
},
"option_header_gateways": {
"message": "Gateways",
"description": "A section header on the Preferences screen (option_header_gateways)"
Expand Down
3 changes: 3 additions & 0 deletions add-on/manifest.brave-beta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAshQhe/y/j0adZqOFes0nqgRgjDNx4eX6oNrESKeKbpUjH9eSN+lCYqBM3PT+BhPxo+sj/aVgCYsddiIbO43Bq/LsQLBFd+kD1I4qZSN4pJAX9AdsbMmXR9XV0W/O9zlyqkXAfxV13Hwmy+e6IH3p59ytQbpcuLnyipspQ4VXZprLkiWdvPMdifT9wgf5gmD30S1n7uaNrKCu8yZk/Lz5Z+KjoxRdk7X7FJYW+hoUGKb6Ld3Q99iLeKPIvcTjK6/xNHXTbaZfRYbfI8i/mSaxetGxSo7/XkMB8VvAiUkZ4gVSp786oMciQVwK2UyVFXw9pJhGD+O4ozcNk0PSq8aE7QIDAQAB"
}
6 changes: 6 additions & 0 deletions add-on/src/background/background.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
'use strict'
/* eslint-env browser, webextensions */

// Enable some debug output from js-ipfs
// (borrowed from https://github.com/ipfs-shipyard/ipfs-companion/pull/557)
// to include everything (mplex, libp2p, mss): localStorage.debug = '*'
localStorage.debug = 'jsipfs*,ipfs*,-*:mfs*,-*:ipns*,-ipfs:preload*'

const browser = require('webextension-polyfill')
const createIpfsCompanion = require('../lib/ipfs-companion')
const { onInstalled } = require('../lib/on-installed')
Expand Down
32 changes: 14 additions & 18 deletions add-on/src/landing-pages/welcome/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,22 @@ function createWelcomePageStore (i18n, runtime) {
return function welcomePageStore (state, emitter) {
state.isIpfsOnline = null
state.peerCount = null

const port = runtime.connect({ name: 'browser-action-port' })

const onMessage = (message) => {
if (message.statusUpdate) {
const peerCount = message.statusUpdate.peerCount
const isIpfsOnline = peerCount > -1

if (isIpfsOnline !== state.isIpfsOnline || peerCount !== state.peerCount) {
state.isIpfsOnline = isIpfsOnline
state.peerCount = peerCount
emitter.emit('render')
}
}
}

port.onMessage.addListener(onMessage)

let port
emitter.on('DOMContentLoaded', async () => {
emitter.emit('render')
port = runtime.connect({ name: 'browser-action-port' })
port.onMessage.addListener(async (message) => {
console.log('port.onMessage', message)
if (message.statusUpdate) {
const peerCount = message.statusUpdate.peerCount
const isIpfsOnline = peerCount > -1
if (isIpfsOnline !== state.isIpfsOnline || peerCount !== state.peerCount) {
state.isIpfsOnline = isIpfsOnline
state.peerCount = peerCount
emitter.emit('render')
}
}
})
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion add-on/src/lib/dnslink.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ module.exports = function createDnslinkResolver (getState) {
readDnslinkFromTxtRecord (fqdn) {
const state = getState()
let apiProvider
if (state.ipfsNodeType === 'external' && state.peerCount !== offlinePeerCount) {
// TODO: fix DNS resolver for ipfsNodeType='embedded:chromesockets', for now use ipfs.io
if (!state.ipfsNodeType.startsWith('embedded') && state.peerCount !== offlinePeerCount) {
apiProvider = state.apiURLString
} else {
// fallback to resolver at public gateway
Expand Down
191 changes: 191 additions & 0 deletions add-on/src/lib/ipfs-client/embedded-chromesockets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
'use strict'
/* eslint-env browser, webextensions */
const browser = require('webextension-polyfill')
const debug = require('debug')

// Polyfills required by embedded HTTP server
const uptimeStart = Date.now()
process.uptime = () => Math.floor((Date.now() - uptimeStart) / 1000)
process.hrtime = require('browser-process-hrtime')

const mergeOptions = require('merge-options')
const Ipfs = require('ipfs')
const HttpApi = require('ipfs/src/http')
const multiaddr = require('multiaddr')
const maToUri = require('multiaddr-to-uri')

const { optionDefaults } = require('../options')

// js-ipfs with embedded hapi HTTP server
let node = null
let nodeHttpApi = null

// additional servers for smoke-tests
// let httpServer = null
// let hapiServer = null

const log = debug('ipfs-companion:client:embedded')
log.error = debug('ipfs-companion:client:embedded:error')

exports.init = function init (opts) {
/*
// TEST RAW require('http') SERVER
if (!httpServer) {
httpServer = startRawHttpServer(9091)
}
// TEST require('hapi') HTTP SERVER (same as in js-ipfs)
if (!hapiServer) {
hapiServer = startRawHapiServer(9092)
}
*/
log('init embedded:chromesockets')

const defaultOpts = JSON.parse(optionDefaults.ipfsNodeConfig)

defaultOpts.libp2p = {
config: {
dht: {
// TODO: check if below is needed after js-ipfs is released with DHT disabled
enabled: false
}
}
}

const userOpts = JSON.parse(opts.ipfsNodeConfig)
const ipfsOpts = mergeOptions.call({ concatArrays: true }, defaultOpts, userOpts, { start: false })
log('creating js-ipfs with opts: ', ipfsOpts)
node = new Ipfs(ipfsOpts)

return new Promise((resolve, reject) => {
node.once('error', (error) => {
log.error('something went terribly wrong during startup of js-ipfs!', error)
reject(error)
})
node.once('ready', async () => {
node.once('start', async () => {
// HttpApi is off in browser context and needs to be started separately
try {
const httpServers = new HttpApi(node, ipfsOpts)
nodeHttpApi = await httpServers.start()
await updateConfigWithHttpEndpoints(node, opts)
resolve(node)
} catch (err) {
reject(err)
}
})
try {
node.on('error', error => {
log.error('something went terribly wrong in embedded js-ipfs!', error)
})
await node.start()
} catch (err) {
reject(err)
}
})
})
}

const multiaddr2httpUrl = (ma) => maToUri(ma.includes('/http') ? ma : multiaddr(ma).encapsulate('/http'))

// Update internal configuration to HTTP Endpoints from js-ipfs instance
async function updateConfigWithHttpEndpoints (ipfs, opts) {
const localConfig = await browser.storage.local.get('ipfsNodeConfig')
if (localConfig && localConfig.ipfsNodeConfig) {
const gwMa = await ipfs.config.get('Addresses.Gateway')
const apiMa = await ipfs.config.get('Addresses.API')
const httpGateway = multiaddr2httpUrl(gwMa)
const httpApi = multiaddr2httpUrl(apiMa)
log(`updating extension configuration to Gateway=${httpGateway} and API=${httpApi}`)
// update ports in JSON configuration for embedded js-ipfs
const ipfsNodeConfig = JSON.parse(localConfig.ipfsNodeConfig)
ipfsNodeConfig.config.Addresses.Gateway = gwMa
ipfsNodeConfig.config.Addresses.API = apiMa
const configChanges = {
customGatewayUrl: httpGateway,
ipfsApiUrl: httpApi,
ipfsNodeConfig: JSON.stringify(ipfsNodeConfig, null, 2)
}
// update current runtime config (in place, effective without restart)
Object.assign(opts, configChanges)
// update user config in storage (effective on next run)
await browser.storage.local.set(configChanges)
}
}

exports.destroy = async function () {
log('destroy: embedded:chromesockets')

/*
if (httpServer) {
httpServer.close()
httpServer = null
}
if (hapiServer) {
try {
await hapiServer.stop({ timeout: 1000 })
} catch (err) {
if (err) {
console.error(`[ipfs-companion] failed to stop hapi`, err)
} else {
console.log('[ipfs-companion] hapi server stopped')
}
}
hapiServer = null
}
*/

if (nodeHttpApi) {
try {
await nodeHttpApi.stop()
} catch (err) {
log.error('failed to stop HttpApi', err)
}
nodeHttpApi = null
}
if (node) {
await node.stop()
node = null
}
}

/*
// Quick smoke-test to confirm require('http') works for MVP
function startRawHttpServer (port) {
const http = require('http') // courtesy of chrome-net
const httpServer = http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Hello from ipfs-companion exposing HTTP via chrome.sockets in Brave :-)\n')
})
httpServer.listen(port, '127.0.0.1')
console.log(`[ipfs-companion] require('http') HTTP server on http://127.0.0.1:${port}`)
return httpServer
}

function startRawHapiServer (port) {
let options = {
host: '127.0.0.1',
port,
debug: {
log: ['*'],
request: ['*']
}
}
const initHapi = async () => {
// hapi v18 (js-ipfs >=v0.35.0-pre.0)
const Hapi = require('hapi') // courtesy of js-ipfs
const hapiServer = new Hapi.Server(options)
await hapiServer.route({
method: 'GET',
path: '/',
handler: (request, h) => {
console.log('[ipfs-companion] hapiServer processing request', request)
return 'Hello from ipfs-companion+Hapi.js exposing HTTP via chrome.sockets in Brave :-)'
}
})
await hapiServer.start()
console.log(`[ipfs-companion] require('hapi') HTTP server running at: ${hapiServer.info.uri}`)
}
initHapi()
return hapiServer
}
*/
31 changes: 22 additions & 9 deletions add-on/src/lib/ipfs-client/embedded.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const mergeOptions = require('merge-options')
const Ipfs = require('ipfs')
const { optionDefaults } = require('../options')

Expand All @@ -8,18 +9,30 @@ let node = null
exports.init = function init (opts) {
console.log('[ipfs-companion] Embedded ipfs init')

node = new Ipfs(
JSON.parse(opts.ipfsNodeConfig || optionDefaults.ipfsNodeConfig)
)
const defaultOpts = JSON.parse(optionDefaults.ipfsNodeConfig)
const userOpts = JSON.parse(opts.ipfsNodeConfig)
const ipfsOpts = mergeOptions.call({ concatArrays: true }, defaultOpts, userOpts, { start: false })

if (node.isOnline()) {
return Promise.resolve(node)
}
node = new Ipfs(ipfsOpts)

return new Promise((resolve, reject) => {
// TODO: replace error listener after a 'ready' event.
node.once('error', (err) => reject(err))
node.once('ready', () => resolve(node))
node.once('error', (error) => {
console.error('[ipfs-companion] Something went terribly wrong during startup of js-ipfs!', error)
reject(error)
})
node.once('ready', async () => {
node.once('start', () => {
resolve(node)
})
node.on('error', error => {
console.error('[ipfs-companion] Something went terribly wrong in embedded js-ipfs!', error)
})
try {
await node.start()
} catch (err) {
reject(err)
}
})
})
}

Expand Down
27 changes: 21 additions & 6 deletions add-on/src/lib/ipfs-client/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
'use strict'

/* eslint-env browser, webextensions */

const debug = require('debug')
const log = debug('ipfs-companion:client')
log.error = debug('ipfs-companion:client:error')

const browser = require('webextension-polyfill')
const external = require('./external')
const embedded = require('./embedded')
const embeddedWithChromeSockets = require('./embedded-chromesockets')

let client

async function initIpfsClient (opts) {
log('init ipfs client')
await destroyIpfsClient()

if (opts.ipfsNodeType === 'embedded') {
client = embedded
} else {
client = external
switch (opts.ipfsNodeType) {
case 'embedded':
client = embedded
break
case 'embedded:chromesockets':
client = embeddedWithChromeSockets
break
case 'external':
client = external
break
default:
throw new Error(`Unsupported ipfsNodeType: ${opts.ipfsNodeType}`)
}

const instance = await client.init(opts)
Expand Down Expand Up @@ -46,7 +61,7 @@ async function _reloadIpfsClientDependents () {
// detect bundled webui in any of open tabs
if (_isWebuiTab(tab.url)) {
browser.tabs.reload(tab.id)
console.log('[ipfs-companion] reloading bundled webui')
log('reloading bundled webui')
}
})
}
Expand Down
Loading