diff --git a/add-on/src/lib/ipfs-client/embedded-brave.js b/add-on/src/lib/ipfs-client/embedded-brave.js new file mode 100644 index 000000000..31e9cde0c --- /dev/null +++ b/add-on/src/lib/ipfs-client/embedded-brave.js @@ -0,0 +1,151 @@ +'use strict' +/* eslint-env browser, webextensions */ + +// 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 defaultsDeep = require('@nodeutils/defaults-deep') +const Ipfs = require('ipfs') +const HttpApi = require('ipfs/src/http') + +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 + +// 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*' + +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) + } + */ + console.log('[ipfs-companion] Embedded ipfs init') + + const defaultOpts = optionDefaults.ipfsNodeConfig + const userOpts = JSON.parse(opts.ipfsNodeConfig) + const ipfsOpts = defaultsDeep(defaultOpts, userOpts, { start: false }) + + node = new Ipfs(ipfsOpts) + + return new Promise((resolve, reject) => { + 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.on('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() + resolve(node) + } catch (err) { + reject(err) + } + }) + 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) + } + }) + }) +} + +exports.destroy = async function () { + console.log('[ipfs-companion] Embedded ipfs destroy') + + /* + 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) { + console.error(`[ipfs-companion] 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 +} +*/ diff --git a/add-on/src/lib/ipfs-client/embedded.js b/add-on/src/lib/ipfs-client/embedded.js index e2697d85d..d49ef758a 100644 --- a/add-on/src/lib/ipfs-client/embedded.js +++ b/add-on/src/lib/ipfs-client/embedded.js @@ -1,147 +1,45 @@ 'use strict' -/* eslint-env browser, webextensions */ - -// Required by HTTP server -process.hrtime = require('browser-process-hrtime') -const uptimeStart = Date.now() -process.uptime = () => Math.floor((Date.now() - uptimeStart) / 1000) +const defaultsDeep = require('@nodeutils/defaults-deep') const Ipfs = require('ipfs') -const HttpApi = require('ipfs/src/http') - 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 - -// 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*' - -// 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 -} exports.init = function init (opts) { - // BRAVE TESTS FIRST - // TODO: remove after experiments are done - // ======================================= - // [x] start raw http server (http.createServer) - // [x] start raw Hapi server (Hapi.Server) - // [x] return response - // [ ] start js-ipfs with Gateway exposed by embedded Hapi server - // - [x] disabling DHT in libp2p solved `TypeError: this._dht.on is not a function`, - // - [x] API port starts and returns valid response - // - [ ] Gateway port starts, but returns invalid response for things different than QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn - // ======================================= - // 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) - } - // ======================================= - // Resume regular startup console.log('[ipfs-companion] Embedded ipfs init') - // TODO: replace with @nodeutils/defaults-deep - const ipfsOpts = Object.assign(JSON.parse(opts.ipfsNodeConfig || optionDefaults.ipfsNodeConfig), { start: false }) + const defaultOpts = optionDefaults.ipfsNodeConfig + const userOpts = JSON.parse(opts.ipfsNodeConfig) + const ipfsOpts = defaultsDeep(defaultOpts, userOpts, { start: false }) + node = new Ipfs(ipfsOpts) return new Promise((resolve, reject) => { - node.on('start', async () => { - // HttpApi is off in browser context and needs to be started separately - const httpServers = new HttpApi(node, ipfsOpts) - nodeHttpApi = await httpServers.start() - return 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', () => { + node.once('ready', async () => { + node.on('start', () => { + resolve(node) + }) node.on('error', error => { console.error('[ipfs-companion] Something went terribly wrong in embedded js-ipfs!', error) }) - node.start() + try { + await node.start() + } catch (err) { + reject(err) + } }) }) } exports.destroy = async function () { console.log('[ipfs-companion] Embedded ipfs destroy') + if (!node) return - 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) { - console.error(`[ipfs-companion] failed to stop HttpApi`, err) - } - nodeHttpApi = null - } - if (node) { - await node.stop() - node = null - } + await node.stop() + node = null } diff --git a/add-on/src/lib/ipfs-client/index.js b/add-on/src/lib/ipfs-client/index.js index b6a333382..bf4cfb139 100644 --- a/add-on/src/lib/ipfs-client/index.js +++ b/add-on/src/lib/ipfs-client/index.js @@ -1,18 +1,35 @@ 'use strict' +/* eslint-env browser, webextensions */ + const browser = require('webextension-polyfill') const external = require('./external') -const embedded = require('./embedded') +const embeddedJs = require('./embedded') +const embeddedJsBrave = require('./embedded-brave') let client +// TODO: make generic +const hasChromeSocketsForTcp = typeof chrome === 'object' && + typeof chrome.runtime === 'object' && + typeof chrome.runtime.id === 'string' && + typeof chrome.sockets === 'object' && + typeof chrome.sockets.tcpServer === 'object' && + typeof chrome.sockets === 'object' && + typeof chrome.sockets.tcp === 'object' + async function initIpfsClient (opts) { await destroyIpfsClient() - if (opts.ipfsNodeType === 'embedded') { - client = embedded - } else { - client = external + switch (opts.ipfsNodeType) { + case 'embedded': + client = hasChromeSocketsForTcp ? embeddedJsBrave : embeddedJs // TODO: make generic + break + case 'external': + client = external + break + default: + throw new Error(`Unsupported ipfsNodeType: ${opts.ipfsNodeType}`) } const instance = await client.init(opts) diff --git a/add-on/src/lib/runtime-checks.js b/add-on/src/lib/runtime-checks.js index e39ef022a..f164b2b07 100644 --- a/add-on/src/lib/runtime-checks.js +++ b/add-on/src/lib/runtime-checks.js @@ -15,6 +15,16 @@ function getPlatformInfo (browser) { return Promise.resolve() } +function hasChromeSocketsForTcp () { + return typeof chrome === 'object' && + typeof chrome.runtime === 'object' && + typeof chrome.runtime.id === 'string' && + typeof chrome.sockets === 'object' && + typeof chrome.sockets.tcpServer === 'object' && + typeof chrome.sockets === 'object' && + typeof chrome.sockets.tcp === 'object' +} + async function createRuntimeChecks (browser) { // browser const browserInfo = await getBrowserInfo(browser) @@ -24,11 +34,12 @@ async function createRuntimeChecks (browser) { // platform const platformInfo = await getPlatformInfo(browser) const runtimeIsAndroid = platformInfo ? platformInfo.os === 'android' : false - // + const runtimeHasSocketsForTcp = hasChromeSocketsForTcp() return Object.freeze({ browser, isFirefox: runtimeIsFirefox, isAndroid: runtimeIsAndroid, + hasChromeSocketsForTcp: runtimeHasSocketsForTcp, hasNativeProtocolHandler: runtimeHasNativeProtocol }) } diff --git a/package.json b/package.json index 4fe76ff7f..663949d3c 100644 --- a/package.json +++ b/package.json @@ -107,10 +107,11 @@ }, "dependencies": { "@material/switch": "1.0.1", + "@nodeutils/defaults-deep": "1.1.0", "browser-process-hrtime": "1.0.0", "choo": "6.13.1", "chrome-dgram": "3.0.1", - "chrome-net": "3.3.1", + "chrome-net": "https://github.com/lidel/chrome-net/tarball/838ffde4a33721888f74783821e0486dfcc88797/chrome-net.tar.gz", "doc-sniff": "1.0.1", "drag-and-drop-files": "0.0.1", "file-type": "10.9.0", diff --git a/yarn.lock b/yarn.lock index d357d076c..f2580aa68 100644 --- a/yarn.lock +++ b/yarn.lock @@ -747,7 +747,7 @@ dependencies: "@material/feature-targeting" "^0.44.1" -"@nodeutils/defaults-deep@^1.1.0": +"@nodeutils/defaults-deep@1.1.0", "@nodeutils/defaults-deep@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@nodeutils/defaults-deep/-/defaults-deep-1.1.0.tgz#bb1124dc8d7ce0bc5da1d668ace58149258ef20b" integrity sha1-uxEk3I184LxdodZorOWBSSWO8gs= @@ -2639,10 +2639,9 @@ chrome-dgram@3.0.1: inherits "^2.0.1" run-series "^1.1.2" -chrome-net@3.3.1: +"chrome-net@https://github.com/lidel/chrome-net/tarball/838ffde4a33721888f74783821e0486dfcc88797/chrome-net.tar.gz": version "3.3.1" - resolved "https://registry.npmjs.org/chrome-net/-/chrome-net-3.3.1.tgz#b4041edb5b264222e7e3c96101344b2332c86782" - integrity sha512-erjHzaLGOOn1WTDdVtEYxeltKLhWpL3TNAv5ODqgseOCWMmV0GueWUR3by3LH9gr3BKPJRzyqPROJpaOCXz22w== + resolved "https://github.com/lidel/chrome-net/tarball/838ffde4a33721888f74783821e0486dfcc88797/chrome-net.tar.gz#07f9210134741fa40472ef2f3bdf7b325cc8169c" dependencies: inherits "^2.0.1"