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

Support chrome.sockets.* APIs #664

Closed
4 tasks done
lidel opened this issue Jan 21, 2019 · 10 comments
Closed
4 tasks done

Support chrome.sockets.* APIs #664

lidel opened this issue Jan 21, 2019 · 10 comments
Assignees
Labels
area/brave Issues related to Brave Browser P1 High: Likely tackled by core team if no one steps up

Comments

@lidel
Copy link
Member

lidel commented Jan 21, 2019

About APIs

Chromium OS exposes chrome.sockets.* for its apps:

We can access those APIs in Brave by adding ipfs-companion (beta+stable) as blessed_extension in brave-core/../_api_features.json (suggested by @bbondy)

See also my notes on bringing IPFS to Brave in 2019: brave/brave-browser#819 (comment)

Potential use

Relevant libraries

Created for and used in webtorrent:

MVP: Exposing HTTP Gateway

Quick notes about first steps:

  1. Create Brave build that exposes chrome.sockets.* APIs to temporary extensions
  2. Detect js-ipfs is running in Brave and enable Gateway by providing a valid multiaddr instead of empty string
  3. Try building ipfs-companion/js-ipfs with chrome-net and chrome-ngram as replacements for Node APIs in browser contexts
  4. Start ipfs-companion in Brave, switch to embedded js-ipfs node
    • did it start and open Gateway on TCP port specified in step 2? is HTTP Gateway working as expected?
      • if yes, then we are lucky and this it! :)
      • if not, then we need to write additional code for exposing Gateway over chrome.sockets.*
  5. Continue in Embedded JS-IPFS in Brave (experiment) #716

cc #312

@lidel lidel added help wanted Seeking public contribution on this issue exp/expert Having worked on the specific codebase is important status/ready Ready to be worked exp/intermediate Prior experience is likely helpful and removed exp/expert Having worked on the specific codebase is important labels Jan 21, 2019
@lidel
Copy link
Member Author

lidel commented Jan 25, 2019

Baby steps :) With some help I was able to confirm APIs are available in ipfs-companion background page after whitelisting extension. Notes below.

How To Develop with Local (Unpacked) Extension (old master)

Click to expand
  1. (prerequisite) Created bundle:brave build script that adds sockets section to extension manifest – this is already in this branch

  2. Built (npm run dev-build) and started Brave (npm start). First build takes 4h+, but following ones are faster.

  3. Enabled "Developer mode" and loaded an unpacked extension with , it got assigned ID paanflafljinkomjoheoobkeigpakegp.

  4. Generated ID shortcut in format expected by Brave..

    $ echo -n paanflafljinkomjoheoobkeigpakegp | openssl sha1 | tr '[:lower:]' '[:upper:]'
    (STDIN)= CEBF34BE446B77F39E2C535566F8C27F9A460B1E
  5. ..and added it to whitelists for sockets.* APIs in two files in src/brave (brave-core):

--- a/common/extensions/api/_api_features.json
+++ b/common/extensions/api/_api_features.json
@@ -61,17 +61,17 @@
   "sockets.tcp": {
     "dependencies": ["manifest:sockets"],
     "contexts": ["blessed_extension"],
-    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"]
+    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423","CEBF34BE446B77F39E2C535566F8C27F9A460B1E"]
   },
   "sockets.tcpServer": {
     "dependencies": ["manifest:sockets"],
     "contexts": ["blessed_extension"],
-    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"]
+    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423","CEBF34BE446B77F39E2C535566F8C27F9A460B1E"]
   },
   "sockets.udp": {
     "dependencies": ["manifest:sockets"],
     "contexts": ["blessed_extension"],
-    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"]
+    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423","CEBF34BE446B77F39E2C535566F8C27F9A460B1E"]
   },
   "braveRewards": {
     "channel": "stable",
diff --git a/common/extensions/api/_manifest_features.json b/common/extensions/api/_manifest_features.json
index fcacdb73..db024427 100644
--- a/common/extensions/api/_manifest_features.json
+++ b/common/extensions/api/_manifest_features.json
@@ -6,6 +6,6 @@
   "sockets": {
     "channel": "stable",
     "extension_types": ["extension", "platform_app"],
-    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423"]
+    "whitelist": ["3D9518A72EB02667A773B69DBA9E72E0F4A37423","CEBF34BE446B77F39E2C535566F8C27F9A460B1E"]
   }
 }
  1. Rebuilt Brave via npm run build
  2. Started it via npm start
  3. Loaded extension again, it got the same ID (paanflafljinkomjoheoobkeigpakegp)
  4. Opened Console for Companion's background page via "Inspect views" on chrome://extensions and confirmed chrome.sockets API is present 🎉

    brave-socket-apis-2019-01-25--16-45-18

How To Develop with Local (Unpacked) Extension (ipfs branch of Brave)

  1. Switch extension to feat/brave-build-with-chrome-sockets branch and run npm run build bundle:brave build script to produce extension manifest with sockets section.

  2. Switch Brave to ipfs branch, build (npm run build) and start Brave (npm start). First build takes 4h+, but following ones are faster.

  3. Enabled "Developer mode" and loaded an unpacked extension.

  4. Opened Console for Companion's background page via "Inspect views" on chrome://extensions and confirmed chrome.sockets API is present 🎉

Next

Override js-ipfs config to make it start Gateway and set up ipfs-companion build to see if use of chrome-net and chrome-ngram is enough to get it working.

@lidel
Copy link
Member Author

lidel commented Jan 28, 2019

First checkpoint, http backed by http-node and chrome-net works from ipfs-companion:

let http = require('http')
let server = 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')                                                                                                                                                      
})                                                                                                                                                                                                                                          
server.listen(9091, '127.0.0.1')

ipfs-companion-brave-http-smoke-test-success-2019-01-28--15-32-02

Next

Getting js-ipfs working may take more than this, as http-node was created from Node 6.x and modern js-ipfs throws ipfs-inactive/js-ipfs-http-client#840.

@lidel
Copy link
Member Author

lidel commented Feb 27, 2019

Second checkpoint: hapi v18 (HTTP server used by js-ipfs) works.

I took js-ipfs 0.35.0-pre.0 for a spin.
It ships with hapi.js v18, HTTP server used for exposing Gateway.

Note: see changes in feat/brave-build-with-chrome-sockets branch

  • start raw http server (http.createServer)
  • start raw hapi server (Hapi.Server) (screenshot below)
    • after some troubleshooting I managed to start raw Hapi server but got internal error for all responses
  • return valid response from hapi server running in browser extension
    • got it to work by replacing url with iso-url during webpack build

Demo: hapi v18 (HTTP server) running in ipfs-companion

hapi-over-chrome-sockets
TypeError: this._dht.on is not a function comes from libp2p, see Next below

Next

  • start embedded js-ipfs with Gateway exposed by embedded Hapi server
    • right now new Ipfs(..) fails due to TypeError: this._dht.on is not a function, which is quite surprisingly not related to HTTP server (which should just work). It seems libp2p does not disable DHT fully when we polyfill net/dgram using chrome.sockets, which results in TypeError around js-libp2p/src/index.js#L404
  • start js-ipfs-http-client

@drbh
Copy link

drbh commented Feb 27, 2019

@lidel great progress! How'd you get the Hapi route response to not throw an error? When I try to get a response from :9092/ I get a 500 and the console says TypeError: Uncaught error: Cannot read property '_replied' of null

Aside from that, what are the next steps for handling the roadblocks you noted above?

If we completely disable DHT - should the embedded js-ipfs Gateway run?

@lidel
Copy link
Member Author

lidel commented Feb 27, 2019

@drbh I believe _replied error went away when I switched to new hapi/js-ipfs. Try:

  • fetching recent updates from feat/brave-build-with-chrome-sockets branch
  • rebulding Companion via npm run dev-build bundle:brave

As for remaining roadblocks:

  • this._dht.on is not a function – I tried to disable DHT via config passed to new Ipfs(), but the error remained – not sure why. It is something I will be looking next (need to understand code around js-libp2p/src/index.js#L404 first)
  • res.req.getHeaders is not a function – this is a low priority (we want to get js-ipfs online first), but my current guess is this will probably require update of http-node (http api shim which is 3 years old)

@lidel
Copy link
Member Author

lidel commented Mar 13, 2019

Changes in feat/brave-build-with-chrome-sockets branch:

  • Synchronized codebase with latest master/v2.7.5.753
  • After some debugging I managed to fix this._dht.on is not a function by disabling DHT in options.libp2p.config (instead of options.config):
{
  config: {
    Addresses: {
      Swarm: [],
      API: '/ip4/127.0.0.1/tcp/5002',
      Gateway: '/ip4/127.0.0.1/tcp/9090'
    }
  },
  libp2p: {
    config: {
      dht: {
        enabled: false
      }
    }
  }
}
  • Switched to js-ipfs 0.35.0-rc.0
    • Starts fine, but does not start HTTP server with API/Gateway
      This is because HTTP endpoints are initialized only by jsipfs daemon called from command-line.
      • We need to refactor src/cli/commands/daemon.js and make it possible to start it programmatically in browser environment (WIP), but it seems we resolved all polyfill-related blockers for now.

lidel added a commit to lidel/js-ipfs that referenced this issue Mar 19, 2019
In the past API was exposed via HTTP Server only when jsipfs daemon was run
from the commandline, so src/http/index.js was also responsible for
orchestration that is not related to HTTP itself.

This refactor moves code that is not related to HTTP Servers into
standalone-daemon.js, which is easier to reason about, and unlocks use
of HttpApi in contexts other than commandline jsipfs daemon,
such as Firefox with libdweb or Chromium-based web browser with chrome.sockets APIs.

Refs.
ipfs/ipfs-companion#664

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
lidel added a commit to lidel/js-ipfs that referenced this issue Mar 19, 2019
In the past API was exposed via HTTP Server only when jsipfs daemon was run
from the commandline, so src/http/index.js was also responsible for
orchestration that is not related to HTTP itself.

This refactor moves code that is not related to HTTP Servers into
standalone-daemon.js, which is easier to reason about, and unlocks use
of HttpApi in contexts other than commandline jsipfs daemon,
such as Firefox with libdweb or Chromium-based web browser with chrome.sockets APIs.

Refs.
ipfs/ipfs-companion#664

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
@lidel lidel added P1 High: Likely tackled by core team if no one steps up and removed exp/intermediate Prior experience is likely helpful help wanted Seeking public contribution on this issue labels Mar 19, 2019
@lidel
Copy link
Member Author

lidel commented Mar 19, 2019

Changes in feat/brave-build-with-chrome-sockets branch:

After some refactoring in ipfs/js-ipfs#1950, pinojs/pino#612 and some ah-hoc polyfills, I am stoked to present..

Embedded js-ipfs with self-hosted HTTP API and Gateway! 🎉

Gateway: dir listing API: /api/v0/id
working-dir-listing-2019-03-20--00-07-32 working-api-port-2019-03-19--22-41-20

\o/

Known Issues

  • If you have an old js-ipfs config without API and Gateway ports, go to Preferences of the extension, scroll to the bottom and click on 2019-03-20--00-28-21

  • Gateway: only directory listing works ATM, fetching raw file data returns some JSON artifacts instead (probably need to tweak some polyfils related to buffers/streams)

  • "power button" from browser action menu does not stop the js-ipfs, it hangs in "stopping state" (need to look into this)

  • Switching to "external" ipfs node does not work (but I believe it will be an easy fix)

@lidel lidel pinned this issue Mar 19, 2019
mcollina pushed a commit to pinojs/pino that referenced this issue Mar 20, 2019
This changes browser.js to expose symbols and serializer methods used
by hapi-pino.

This is extremely niche use case, but some people want to run Hapi.js
(HTTP server) in WebExtension context using chrome.sockets APIs.

Ref. ipfs/ipfs-companion#664
@lidel
Copy link
Member Author

lidel commented Apr 3, 2019

Changes in feat/brave-build-with-chrome-sockets branch:

Solved the problem with fetching raw file data

Hapi's stream detection via stream instanceof Stream does not work correctly in browser context, especially when different polyfils are used and mixed together.

I created a patch to detect browserified streams in Chrome App environment, which replaces instanceof check with feature-detection. This enables Hapi to consume stream-like objects, as long they match proper contract.

tl;dr loading data from embedded gateway works:

2019-04-02--20-43-46

Fixed streaming of compressed payload

There is a WIP js-ipfs patch to do proper streaming and content-type sniffing. It simplifies code responsible for streaming response and makes the streaming actually work by telling the payload compression stream to flush its content on every read(). IIUC previous version was buffering entire thing in Hapi's compressor memory, so the payload was returned when entire thing was fetched.

Known Issues

  • If you have an old js-ipfs config without API and Gateway ports, go to Preferences of the extension, scroll to the bottom and click on 2019-03-20--00-28-21
  • "power button" from browser action menu does not stop the js-ipfs, it hangs in "stopping state" (need to look into this)
  • Switching to "external" ipfs node does not work (but I believe it will be an easy fix)
  • Uncaught Error: Cannot call write after a stream was destroyed on refreshing already loaded payload
    • I confirmed it does not occur when we remove content-type sniffing based on buffer-peek-stream, there is a race-condition around its use
  • content-type sniffing needs refinement, current version was ported from libdweb experiment
  • Artifact created by npm run dev-build bundle:generic is not compatible with regular Chrome yet. We need to switch to chrome.sockets feature-detection at runtime, so it can be installed from regular Chrome Store.

Next

  • Solve known issues
  • Cleanup patches and create upstream PRs
  • Update Companion UI to be aware of embedded Gateway
  • Merge and publish to Beta Channel

@lidel
Copy link
Member Author

lidel commented Apr 10, 2019

Changes in feat/brave-build-with-chrome-sockets branch:

Companion UI is now aware of embedded Gateway

Embedded node is now the default if chrome.sockets are present.
Both main menu and onboarding pages use it and all features are enabled:

brave-1-2019-04-10--14-51-33

Implemented feature-detection for chrome.sockets

We can now run the same code in Chrome and Brave, and more advanced embedded node will be enabled automatically when Brave runtime is detected.

This work included:

  • Patched chrome-net: lidel/chrome-net@838ffde

    chrome-net did not check if chrome.sockets.* actually exist. This caused problems with extensions built for Chromium-based browsers that tried to maintain the single codebase and do feature-detection at runtime.

  • Feature detection for embedded js-ipfs:

    If chrome.sockets is available, embedded-brave.js will be used instead of regular embedded.js

  • Refactored initial boot logic on clean install

    When extension started for the very first time, the node got restarted multiple times due to config updates/migrations happening in lib/options.js.

    I simplified boot process to the point no restarts are triggered by config updates on the first run. JS on landing page is also tweaked to properly receive notification about new peers being available. Tests for relevant code paths are updated.

  • Added new ipfsNodeType named embedded:chromesockets

    For now we do naive check if chrome.sockets.tcp* APIs are available and switch to ipfsNodeType=embedded:chromesockets if true.
    User can customize configuration of embedded js-ipfs node via Preferences screen where embedded:chromesockets supersedes regular embedded on Brave:

    2019-04-10-153104_1081x718_scrot
    API and Gateway ports used by extension will follow configuration of embedded js-ipfs.

Made it play nice with go-ipfs and js-ipfs daemons

  • Switching to External node works again, user can switch between embedded (default in Brave) and external IPFS nodes

  • Notify user about socket errors:

    If port is already taken, a notification "port already in use" is displayed.
    We will most likely improve this soon (eg. by finding the next free port) but is good enough for now.

  • Use custom ports by default

    Embedded js-ipfs with chrome.sockets now listens on custom ports, removing the need of changing configuration if someone is already running go-ipfs or js-ipfs.

Web UI works!

Web UI can be opened from Companion's main menu and works as expected:

2019-04-10-145206_1081x718_scrot

2019-04-10-145231_1081x718_scrot

Sidenote: ipfs-webui connects to embedded js-ipfs over HTTP API, but we could make it work via window.ipfs by blessing Web UI's CID to work over it without scoping/sandboxing.

Known Issues

Key blockers in bold

  • Content discovery is sometimes extremely slow when preload nodes are under load and slow to respond
  • We may mitigate this by enabling relay and other existing discovery methods such as webrtc or stardust
  • This will just go away when we add p2p transport that can interop with go nodes
  • DNSLink websites (e.g. /ipns/docs.ipfs.io/) are not supported by js-ipfs yet (tracked in Resolving DNSLink Paths: /ipns/<fqdn> js-ipfs#1918)
  • DNS lookups switched to ipfs.io/api/v0/dns for DNS resolution until DNSLink support lands in js-ipfs and we get better understanding how to operate chrome.sockets.udp* API.
  • HAMT-shareded directories, eg. /ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/wiki/ do not work with js-ipfs (tracked in HTTP Gateway fails to load sharded website js-ipfs#1963)
  • ... in directory listing points at current directory instead of its parent
  • HTTP Range requests do not seem to be supported by js-ipfs
  • Broken bandwidth graph in Web UI

Next

lidel added a commit to lidel/js-ipfs that referenced this issue Apr 11, 2019
In the past API was exposed via HTTP Server only when jsipfs daemon was run
from the commandline, so src/http/index.js was also responsible for
orchestration that is not related to HTTP itself.

This refactor moves code that is not related to HTTP Servers into
standalone-daemon.js, which is easier to reason about, and unlocks use
of HttpApi in contexts other than commandline jsipfs daemon,
such as Firefox with libdweb or Chromium-based web browser with chrome.sockets APIs.

Refs.
ipfs/ipfs-companion#664

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
alanshaw pushed a commit to ipfs/js-ipfs that referenced this issue Apr 12, 2019
* refactor: decouple HttpApi from cli/commands/daemon

In the past API was exposed via HTTP Server only when jsipfs daemon was run
from the commandline, so src/http/index.js was also responsible for
orchestration that is not related to HTTP itself.

This refactor moves code that is not related to HTTP Servers into
standalone-daemon.js, which is easier to reason about, and unlocks use
of HttpApi in contexts other than commandline jsipfs daemon,
such as Firefox with libdweb or Chromium-based web browser with chrome.sockets APIs.

Refs.
ipfs/ipfs-companion#664

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>

* fix: print HTTP listeners only when run as daemon

This changes behavior in web browser. Instead of printing to
console.log, it uses proper debug-based logger.

Old behavior in terminal (when run via `jsipfs daemon`) does not change.

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>

* test: use StandaloneDaemon in test/http-api,gateway

This replaces durect use of HttpApi with StandaloneDaemon, restoring all
existing tests to operational state.

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>

* refactor: rename StandaloneDaemon to Daemon

License: MIT
Signed-off-by: Marcin Rataj <lidel@lidel.org>
@lidel lidel unpinned this issue Apr 22, 2019
@lidel
Copy link
Member Author

lidel commented Apr 22, 2019

Feature-detection for chrome.sockets works as expected, landed in v2.8.1.778 (Beta) 🚀

Remaining work around supercharging Embedded JS-IPFS in Brave 🦁 is tracked in #716

@lidel lidel closed this as completed Apr 22, 2019
@ghost ghost removed the status/in-progress In progress label Apr 22, 2019
@lidel lidel added the area/brave Issues related to Brave Browser label Apr 23, 2019
lidel added a commit to lidel/hapi that referenced this issue Apr 26, 2019
Detection via `stream instanceof Stream` does not work correctly in browser context,
especially when different polyfils are used and mixed together.

This change replaces instanceof check with feature-detection of stream-like objects.
It enables Hapi to be used in Chrome Apps (browser environments with additional chrome.sockets APIs)

Real life example:
ipfs/ipfs-companion#664
lidel added a commit to lidel/hapi that referenced this issue Sep 11, 2019
Detection via `stream instanceof Stream` does not work correctly in browser context,
especially when different polyfils are used and mixed together.

This change replaces instanceof check with feature-detection of stream-like objects.
It enables Hapi to be used in Chrome Apps (browser environments with additional chrome.sockets APIs)

Real life example:
ipfs/ipfs-companion#664
lidel added a commit to lidel/hapi that referenced this issue Sep 12, 2019
Detection via `stream instanceof Stream` does not work correctly in browser context,
especially when different polyfils are used and mixed together.

This change replaces instanceof check with feature-detection of stream-like objects.
It enables Hapi to be used in Chrome Apps (browser environments with additional chrome.sockets APIs)

Real life example:
ipfs/ipfs-companion#664
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/brave Issues related to Brave Browser P1 High: Likely tackled by core team if no one steps up
Projects
None yet
Development

No branches or pull requests

2 participants