Skip to content

Commit 0679e3e

Browse files
authored
feat: precached webui works in offline mode (#782)
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)
1 parent ca26240 commit 0679e3e

File tree

6 files changed

+260
-60
lines changed

6 files changed

+260
-60
lines changed

.travis.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ addons:
1919
install:
2020
- npm run ci:install
2121
script:
22-
- npm run ci:build
23-
- npm run ci:test
24-
- npm run ci:lint
22+
- npm run build
23+
- npm run test
24+
- npm run lint
2525
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
2626
notifications:
27-
email: false
27+
email:
28+
if: branch = master
29+
on_success: never
30+
on_failure: always

add-on/src/lib/ipfs-client/embedded-chromesockets/config.js

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ const chromeDefaultOpts = {
2525
Swarm: [
2626
// optional ws-star signaling provides a backup for non-LAN peer discovery
2727
// (this will be removed when autorelay and DHT are stable in js-ipfs)
28-
'/dns4/ws-star1.par.dwebops.pub.com/tcp/443/wss/p2p-websocket-star',
2928
'/dns4/ws-star.discovery.libp2p.io/tcp/443/wss/p2p-websocket-star'
3029
],
3130
// Delegated Content and Peer Routing: https://github.com/ipfs/js-ipfs/pull/2195

add-on/src/lib/ipfs-client/index.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const browser = require('webextension-polyfill')
1010
const external = require('./external')
1111
const embedded = require('./embedded')
1212
const embeddedWithChromeSockets = require('./embedded-chromesockets')
13-
const { webuiCid } = require('../state')
13+
const precache = require('../precache')
1414

1515
let client
1616

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

add-on/src/lib/precache.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use strict'
2+
/* eslint-env browser, webextensions */
3+
const pull = require('pull-stream/pull')
4+
const drain = require('pull-stream/sinks/drain')
5+
const toStream = require('it-to-stream')
6+
const tar = require('tar-stream')
7+
const CID = require('cids')
8+
const { webuiCid } = require('./state')
9+
10+
const debug = require('debug')
11+
const log = debug('ipfs-companion:precache')
12+
log.error = debug('ipfs-companion:precache:error')
13+
14+
const PRECACHE_ARCHIVES = [
15+
{ tarPath: '/dist/precache/webui.tar', cid: webuiCid }
16+
]
17+
18+
/**
19+
* Adds important assets such as Web UI to the local js-ipfs-repo.
20+
* This ensures they load instantly, even in offline environments.
21+
*/
22+
module.exports = async (ipfs) => {
23+
for (const { cid, tarPath } of PRECACHE_ARCHIVES) {
24+
if (!await inRepo(ipfs, cid)) {
25+
await importTar(ipfs, tarPath, cid)
26+
} else {
27+
log(`${cid} already in local repo, skipping import`)
28+
}
29+
}
30+
}
31+
32+
async function inRepo (ipfs, cid) {
33+
return new Promise((resolve, reject) => {
34+
let local = false
35+
pull(
36+
ipfs.refs.localPullStream(),
37+
drain(block => {
38+
if (block.ref === cid) {
39+
local = true
40+
return false // abort stream
41+
}
42+
}, () => resolve(local))
43+
)
44+
})
45+
}
46+
47+
async function importTar (ipfs, tarPath, expectedCid) {
48+
const stream = toStream.readable(streamTar(tarPath))
49+
// TODO: HTTP 404 means precache is disabled in the current runtime
50+
// (eg. in Firefox, due to https://github.com/ipfs-shipyard/ipfs-webui/issues/959)
51+
const untarAndAdd = tar.extract()
52+
53+
const files = []
54+
55+
untarAndAdd.on('entry', (header, stream, next) => {
56+
// header is the tar header
57+
// stream is the content body (might be an empty stream)
58+
// call next when you are done with this entry
59+
60+
if (header.type !== 'file') {
61+
// skip non-files
62+
stream.on('end', next)
63+
stream.resume() // drain stream
64+
return
65+
}
66+
67+
files.push(new Promise((resolve, reject) => {
68+
let chunks = []
69+
stream.on('data', data => chunks.push(data))
70+
stream.on('end', () => {
71+
resolve({ path: header.name, content: Buffer.concat(chunks) })
72+
chunks = null
73+
next()
74+
})
75+
}))
76+
})
77+
78+
untarAndAdd.on('finish', async () => {
79+
const { version } = new CID(expectedCid)
80+
const opts = { cidVersion: version, pin: false, preload: false }
81+
const results = await ipfs.add(await Promise.all(files), opts)
82+
const root = results.find(e => e.hash === expectedCid)
83+
if (root) {
84+
log(`${tarPath} successfully precached`, root)
85+
} else {
86+
log.error('imported CID does not match expected one (requires new release with updated package.json)')
87+
}
88+
})
89+
90+
log(`importing ${tarPath} to js-ipfs-repo`)
91+
stream.pipe(untarAndAdd)
92+
}
93+
94+
async function * streamTar (repoPath) {
95+
const response = await fetch(repoPath)
96+
const reader = response.body.getReader()
97+
try {
98+
while (true) {
99+
const { done, value } = await reader.read()
100+
if (done) return
101+
yield value
102+
}
103+
} finally {
104+
// Firefox only? https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/releaseLock
105+
if (typeof reader.releaseLock === 'function') reader.releaseLock()
106+
}
107+
}

package.json

+11-5
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,23 @@
2828
"build:minimize-dist": "shx rm -rf add-on/dist/lib add-on/dist/contentScripts/ add-on/dist/bundles/ipfsProxyContentScriptPayload.bundle.js",
2929
"build:bundle-all": "cross-env RELEASE_CHANNEL=${RELEASE_CHANNEL:=dev} run-s bundle:chromium bundle:brave:$RELEASE_CHANNEL bundle:firefox:$RELEASE_CHANNEL",
3030
"build:rename-artifacts": "./scripts/rename-artifacts.js",
31+
"precache:clean": "shx rm -rf add-on/dist/precache",
32+
"precache:webui:cid": "shx grep 'const webuiCid' add-on/src/lib/state.js | shx sed \"s/^const webuiCid = '//\" | shx sed \"s/'.*$//\"",
33+
"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",
3134
"bundle": "run-s bundle:*",
32-
"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",
33-
"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",
35+
"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",
36+
"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",
3437
"bundle:firefox:dev": "npm run bundle:firefox",
3538
"bundle:firefox:stable": "npm run bundle:firefox",
36-
"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",
39+
"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",
3740
"bundle:fennec": "npm run bundle:firefox",
3841
"bundle:fennec:dev": "npm run bundle:firefox:dev",
3942
"bundle:fennec:stable": "npm run bundle:firefox:stable",
4043
"bundle:fennec:beta": "npm run bundle:firefox:beta",
41-
"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",
44+
"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",
4245
"bundle:brave:dev": "npm run bundle:brave",
4346
"bundle:brave:stable": "npm run bundle:brave",
44-
"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",
47+
"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",
4548
"watch": "npm-run-all build:copy --parallel watch:*",
4649
"watch:js": "run-p watch:js:*",
4750
"watch:js:webpack": "webpack --watch --progress -d --devtool inline-source-map --config ./webpack.config.js",
@@ -93,6 +96,7 @@
9396
"get-firefox": "2.2.1",
9497
"husky": "3.0.8",
9598
"ignore-styles": "5.0.1",
99+
"ipfs-or-gateway": "2.1.0",
96100
"json": "9.0.6",
97101
"mem-storage-area": "1.0.3",
98102
"mocha": "6.2.1",
@@ -138,6 +142,7 @@
138142
"is-fqdn": "1.0.1",
139143
"is-ipfs": "0.6.1",
140144
"is-svg": "4.2.0",
145+
"it-to-stream": "0.1.1",
141146
"lru-cache": "5.1.1",
142147
"merge-options": "1.0.1",
143148
"mime-types": "2.1.24",
@@ -151,6 +156,7 @@
151156
"pull-file-reader": "1.0.2",
152157
"readable-stream": "3.4.0",
153158
"tachyons": "4.11.1",
159+
"tar-stream": "2.1.0",
154160
"timers-browserify-full": "0.0.1",
155161
"uri-to-multiaddr": "3.0.1",
156162
"webextension-polyfill": "0.5.0",

0 commit comments

Comments
 (0)