Skip to content

Commit

Permalink
feat: precached webui works in offline mode (#782)
Browse files Browse the repository at this point in the history
This adds precache logic for Web UI which is executed when
IPFS API client  (or full node) starts.
The main purpose is to make Web UI load instantly in Brave
and work in offline environments

How does it work?

During the build .tar archives are placed in `add-on/dist/precache`.
This makes them available to fetch over `*-extension://{extension-id}/dist/precache/*` URLs.
Right now we have only one archive: a 22MB file with release version of Web UI.

When IPFS client starts, preache logic is executed:

1. read Web UI CID from Companion's config
2. check if local repo contains mentioned CID.
   - if it is present, finish
   - if missing, continue to the next step
3. precache asynchronously:
   - fetch TAR archive from `*-extension://{extension-id}/dist/precache/*.tar`
   - pass entire directory tree as unpacked streams to ipfs.add
   - confirm produced CID is matching the one from step (1)
  • Loading branch information
lidel committed Oct 17, 2019
1 parent ca26240 commit 0679e3e
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 60 deletions.
11 changes: 7 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ addons:
install:
- npm run ci:install
script:
- npm run ci:build
- npm run ci:test
- npm run ci:lint
- npm run build
- npm run test
- npm run lint
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
notifications:
email: false
email:
if: branch = master
on_success: never
on_failure: always
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ const chromeDefaultOpts = {
Swarm: [
// optional ws-star signaling provides a backup for non-LAN peer discovery
// (this will be removed when autorelay and DHT are stable in js-ipfs)
'/dns4/ws-star1.par.dwebops.pub.com/tcp/443/wss/p2p-websocket-star',
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star'
],
// Delegated Content and Peer Routing: https://github.com/ipfs/js-ipfs/pull/2195
Expand Down
13 changes: 3 additions & 10 deletions add-on/src/lib/ipfs-client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const browser = require('webextension-polyfill')
const external = require('./external')
const embedded = require('./embedded')
const embeddedWithChromeSockets = require('./embedded-chromesockets')
const { webuiCid } = require('../state')
const precache = require('../precache')

let client

Expand Down Expand Up @@ -70,15 +70,8 @@ async function _reloadIpfsClientDependents (instance, opts) {
}
// online only
if (client && instance) {
if (webuiCid && instance.refs) {
// Optimization: preload the root CID to speed up the first time
// Web UI is opened. If embedded js-ipfs is used it will trigger
// remote (always recursive) preload of entire DAG to one of preload nodes.
// This way when embedded node wants to load resource related to webui
// it will get it fast from preload nodes.
log(`preloading webui root at ${webuiCid}`)
instance.refs(webuiCid, { recursive: false })
}
// add important data to local ipfs repo for instant load
precache(instance)
}
}

Expand Down
107 changes: 107 additions & 0 deletions add-on/src/lib/precache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use strict'
/* eslint-env browser, webextensions */
const pull = require('pull-stream/pull')
const drain = require('pull-stream/sinks/drain')
const toStream = require('it-to-stream')
const tar = require('tar-stream')
const CID = require('cids')
const { webuiCid } = require('./state')

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

const PRECACHE_ARCHIVES = [
{ tarPath: '/dist/precache/webui.tar', cid: webuiCid }
]

/**
* Adds important assets such as Web UI to the local js-ipfs-repo.
* This ensures they load instantly, even in offline environments.
*/
module.exports = async (ipfs) => {
for (const { cid, tarPath } of PRECACHE_ARCHIVES) {
if (!await inRepo(ipfs, cid)) {
await importTar(ipfs, tarPath, cid)
} else {
log(`${cid} already in local repo, skipping import`)
}
}
}

async function inRepo (ipfs, cid) {
return new Promise((resolve, reject) => {
let local = false
pull(
ipfs.refs.localPullStream(),
drain(block => {
if (block.ref === cid) {
local = true
return false // abort stream
}
}, () => resolve(local))
)
})
}

async function importTar (ipfs, tarPath, expectedCid) {
const stream = toStream.readable(streamTar(tarPath))
// TODO: HTTP 404 means precache is disabled in the current runtime
// (eg. in Firefox, due to https://github.com/ipfs-shipyard/ipfs-webui/issues/959)
const untarAndAdd = tar.extract()

const files = []

untarAndAdd.on('entry', (header, stream, next) => {
// header is the tar header
// stream is the content body (might be an empty stream)
// call next when you are done with this entry

if (header.type !== 'file') {
// skip non-files
stream.on('end', next)
stream.resume() // drain stream
return
}

files.push(new Promise((resolve, reject) => {
let chunks = []
stream.on('data', data => chunks.push(data))
stream.on('end', () => {
resolve({ path: header.name, content: Buffer.concat(chunks) })
chunks = null
next()
})
}))
})

untarAndAdd.on('finish', async () => {
const { version } = new CID(expectedCid)
const opts = { cidVersion: version, pin: false, preload: false }
const results = await ipfs.add(await Promise.all(files), opts)
const root = results.find(e => e.hash === expectedCid)
if (root) {
log(`${tarPath} successfully precached`, root)
} else {
log.error('imported CID does not match expected one (requires new release with updated package.json)')
}
})

log(`importing ${tarPath} to js-ipfs-repo`)
stream.pipe(untarAndAdd)
}

async function * streamTar (repoPath) {
const response = await fetch(repoPath)
const reader = response.body.getReader()
try {
while (true) {
const { done, value } = await reader.read()
if (done) return
yield value
}
} finally {
// Firefox only? https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/releaseLock
if (typeof reader.releaseLock === 'function') reader.releaseLock()
}
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,23 @@
"build:minimize-dist": "shx rm -rf add-on/dist/lib add-on/dist/contentScripts/ add-on/dist/bundles/ipfsProxyContentScriptPayload.bundle.js",
"build:bundle-all": "cross-env RELEASE_CHANNEL=${RELEASE_CHANNEL:=dev} run-s bundle:chromium bundle:brave:$RELEASE_CHANNEL bundle:firefox:$RELEASE_CHANNEL",
"build:rename-artifacts": "./scripts/rename-artifacts.js",
"precache:clean": "shx rm -rf add-on/dist/precache",
"precache:webui:cid": "shx grep 'const webuiCid' add-on/src/lib/state.js | shx sed \"s/^const webuiCid = '//\" | shx sed \"s/'.*$//\"",
"precache:webui": "shx mkdir -p add-on/dist/precache && ipfs-or-gateway -c $(npm run -s precache:webui:cid) -p add-on/dist/precache/webui.tar --archive",
"bundle": "run-s bundle:*",
"bundle:chromium": "shx cat add-on/manifest.common.json add-on/manifest.chromium.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/chromium && run-s build:rename-artifacts",
"bundle:firefox": "shx cat add-on/manifest.common.json add-on/manifest.firefox.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/firefox/ && run-s build:rename-artifacts",
"bundle:chromium": "run-s precache:webui && shx cat add-on/manifest.common.json add-on/manifest.chromium.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/chromium && run-s build:rename-artifacts",
"bundle:firefox": "run-s precache:clean && shx cat add-on/manifest.common.json add-on/manifest.firefox.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/firefox/ && run-s build:rename-artifacts",
"bundle:firefox:dev": "npm run bundle:firefox",
"bundle:firefox:stable": "npm run bundle:firefox",
"bundle:firefox:beta": "shx cat add-on/manifest.common.json add-on/manifest.firefox.json add-on/manifest.firefox-beta.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/firefox/ && run-s build:rename-artifacts",
"bundle:firefox:beta": "run-s precache:clean && shx cat add-on/manifest.common.json add-on/manifest.firefox.json add-on/manifest.firefox-beta.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/firefox/ && run-s build:rename-artifacts",
"bundle:fennec": "npm run bundle:firefox",
"bundle:fennec:dev": "npm run bundle:firefox:dev",
"bundle:fennec:stable": "npm run bundle:firefox:stable",
"bundle:fennec:beta": "npm run bundle:firefox:beta",
"bundle:brave": "shx cat add-on/manifest.common.json add-on/manifest.chromium.json add-on/manifest.brave.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/brave/ && run-s build:rename-artifacts",
"bundle:brave": "run-s precache:webui && shx cat add-on/manifest.common.json add-on/manifest.chromium.json add-on/manifest.brave.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/brave/ && run-s build:rename-artifacts",
"bundle:brave:dev": "npm run bundle:brave",
"bundle:brave:stable": "npm run bundle:brave",
"bundle:brave:beta": "shx cat add-on/manifest.common.json add-on/manifest.chromium.json add-on/manifest.brave.json add-on/manifest.brave-beta.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/brave/ && run-s build:rename-artifacts",
"bundle:brave:beta": "run-s precache:webui && shx cat add-on/manifest.common.json add-on/manifest.chromium.json add-on/manifest.brave.json add-on/manifest.brave-beta.json | json --deep-merge > add-on/manifest.json && web-ext build -a build/brave/ && run-s build:rename-artifacts",
"watch": "npm-run-all build:copy --parallel watch:*",
"watch:js": "run-p watch:js:*",
"watch:js:webpack": "webpack --watch --progress -d --devtool inline-source-map --config ./webpack.config.js",
Expand Down Expand Up @@ -93,6 +96,7 @@
"get-firefox": "2.2.1",
"husky": "3.0.8",
"ignore-styles": "5.0.1",
"ipfs-or-gateway": "2.1.0",
"json": "9.0.6",
"mem-storage-area": "1.0.3",
"mocha": "6.2.1",
Expand Down Expand Up @@ -138,6 +142,7 @@
"is-fqdn": "1.0.1",
"is-ipfs": "0.6.1",
"is-svg": "4.2.0",
"it-to-stream": "0.1.1",
"lru-cache": "5.1.1",
"merge-options": "1.0.1",
"mime-types": "2.1.24",
Expand All @@ -151,6 +156,7 @@
"pull-file-reader": "1.0.2",
"readable-stream": "3.4.0",
"tachyons": "4.11.1",
"tar-stream": "2.1.0",
"timers-browserify-full": "0.0.1",
"uri-to-multiaddr": "3.0.1",
"webextension-polyfill": "0.5.0",
Expand Down
Loading

0 comments on commit 0679e3e

Please sign in to comment.