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

feat: recovery page when local gateway is unreachable #1125

Merged
merged 42 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b9232a1
Adding recovery options
whizzzkid Dec 23, 2022
3ac7e12
fixing packages
whizzzkid Dec 23, 2022
5103b60
hooking components
whizzzkid Dec 23, 2022
3a53c02
reverting changes
whizzzkid Dec 29, 2022
3256407
adding additional state prop getters
whizzzkid Dec 29, 2022
fb301e0
Adding hookup for local gateway down.
whizzzkid Dec 29, 2022
df31834
Refactor
whizzzkid Dec 29, 2022
a3feda0
Marking recovery page as accessible
whizzzkid Dec 29, 2022
e389d45
unnecessary logging
whizzzkid Dec 29, 2022
133ca93
lint
whizzzkid Dec 29, 2022
cd5f4bf
Fixing UI + encdec URIs
whizzzkid Dec 29, 2022
03c14c6
Making page recover if node is online.
whizzzkid Dec 30, 2022
23412e4
Merge branch 'main' into feat/recover-client
whizzzkid Jan 3, 2023
a438c19
Refactor + Implement Design Change
whizzzkid Jan 5, 2023
0561ea3
Merge branch 'main' into feat/recover-client
whizzzkid Jan 13, 2023
e31cbbf
Update add-on/src/lib/ipfs-request.js
whizzzkid Jan 13, 2023
0561045
Update add-on/_locales/en/messages.json
whizzzkid Jan 13, 2023
4a139d8
Update add-on/src/recovery/recovery.js
whizzzkid Jan 13, 2023
5ce8b18
Update add-on/_locales/en/messages.json
whizzzkid Jan 13, 2023
baad760
Update add-on/_locales/en/messages.json
whizzzkid Jan 13, 2023
4ff97b6
Update add-on/src/recovery/recovery.html
whizzzkid Jan 13, 2023
d3018f4
Update add-on/src/recovery/recovery.js
whizzzkid Jan 13, 2023
41881ec
Update add-on/src/recovery/recovery.js
whizzzkid Jan 13, 2023
8a3db25
moving function to ipfs-path
whizzzkid Jan 13, 2023
ab2d990
unnecessary logging statement
whizzzkid Jan 13, 2023
956863a
fixed logo title not rendering
whizzzkid Jan 13, 2023
885d63f
fix: recovery logic
whizzzkid Jan 13, 2023
7454d62
Refactoring
whizzzkid Jan 13, 2023
5b617fd
fix: styling in firefox
whizzzkid Jan 13, 2023
66485d0
Implementing new UI and copy
whizzzkid Jan 13, 2023
2ea33c2
lint
whizzzkid Jan 13, 2023
4c5642b
feat: Adding regression test
whizzzkid Jan 13, 2023
20f71b6
Merge branch 'main' into feat/recover-client
whizzzkid Jan 18, 2023
414df96
Merge branch 'main' into feat/recover-client
whizzzkid Jan 24, 2023
c6e6dc5
Merge branch 'main' into feat/recover-client
whizzzkid Jan 24, 2023
c7e6956
Merge branch 'main' into feat/recover-client
whizzzkid Jan 31, 2023
ba6e573
feat: :sparkles: Add telemetry for recovery mechanism
whizzzkid Jan 31, 2023
0ca8d4a
Merge branch 'main' into feat/recover-client
whizzzkid Feb 3, 2023
2d8f803
fix(ux): :wastebasket: getting rid of header, companion logo and addi…
whizzzkid Feb 3, 2023
97be393
fix(ux): :lipstick: Fixing CSS
whizzzkid Feb 3, 2023
3e76bf8
fix: :rotating_light: fix lint
whizzzkid Feb 3, 2023
6f53132
style: adjust text and css
lidel Feb 3, 2023
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
32 changes: 32 additions & 0 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -750,5 +750,37 @@
"option_telemetryGroupTracking_description": {
"message": "Tracking description",
"description": "A description for the 'tracking' grouping of metrics we collect (option_telemetryGroupTracking_description)"
},
"recovery_page_title" : {
"message": "Node Offline | IPFS Companion",
lidel marked this conversation as resolved.
Show resolved Hide resolved
"description": "Title of the recovery page (recovery_page_title)"
},
"recovery_page_header" : {
"message": "Companion",
"description": "Main header on the recovery screen (recovery_page_header)"
},
lidel marked this conversation as resolved.
Show resolved Hide resolved
"recovery_page_sub_header": {
"message": "Unable to reach the IPFS node :(",
lidel marked this conversation as resolved.
Show resolved Hide resolved
"description": "Sub-Header on the recovery screen (recovery_page_sub_header)"
},
"recovery_page_message_p1": {
"message": "Make sure IPFS Desktop is running and provides the HTTP Gateway Functionality.",
lidel marked this conversation as resolved.
Show resolved Hide resolved
"description": "Message Para-1 on the recovery screen (recovery_page_message_p1)"
},
"recovery_page_message_p2": {
"message": "Alternatively, you can try to open the requested resource using the Public Gateway configured in IPFS Companion. In doing so you will be delegating trust to that HTTP server and the hash verification will no longer occur on your machine.",
lidel marked this conversation as resolved.
Show resolved Hide resolved
"description": "Message Para-2 on the recovery screen (recovery_page_message_p2)"
},
"recovery_page_button": {
"message": "Continue to the public gateway",
"description": "Button on the recovery screen (recovery_page_button)"
},
"recovery_page_learn_more": {
"message": "Learn more about public gateways",
whizzzkid marked this conversation as resolved.
Show resolved Hide resolved
"description": "Learn more link on the recovery screen (recovery_page_learn_more)"
},
"recovery_page_update_preferences": {
"message": "Update your IPFS Companion preferences",
"description": "Learn more link on the recovery screen (recovery_page_learn_more)"
}
}
5 changes: 4 additions & 1 deletion add-on/manifest.common.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
"icons/png/ipfs-logo-off_38.png",
"icons/png/ipfs-logo-off_128.png",
"icons/ipfs-logo-on.svg",
"icons/ipfs-logo-off.svg"
"icons/ipfs-logo-off.svg",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how this is persisted on CI, locally this gets overwritten.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@whizzzkid This file is a template used for generating final add-on/manifest.json for specific runtime: firefox, chromium, or brave (production brave used chromium, but we need this for local testing).

Generation happens in ci/update-manifest.sh (triggered during build with some env vars). It was way more complex in the past when we had beta channels, we could simplify build pipeline these days, but did not bother since it works fine.

"dist/recovery/recovery.css",
"dist/recovery/recovery.html",
"dist/recovery/recovery.js"
lidel marked this conversation as resolved.
Show resolved Hide resolved
],
"content_security_policy": "script-src 'self'; object-src 'self'; frame-src 'self';",
"default_locale": "en"
Expand Down
18 changes: 13 additions & 5 deletions add-on/src/landing-pages/welcome/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,24 @@ export default function createWelcomePage (i18n) {
/* ========================================================
Render functions for the left side
======================================================== */

const renderCompanionLogo = (i18n, isIpfsOnline) => {
export const renderLogo = (isIpfsOnline, logoSize = 128) => {
const logoPath = '../../../icons'
const logoSize = 128

return html`
${logo({ path: logoPath, size: logoSize, isIpfsOnline })}
`
}

export const renderCompanionLogo = (i18n, isIpfsOnline, showTitle = true) => {
const stateUnknown = isIpfsOnline === null

return html`
<div class="mt4 mb2 flex flex-column justify-center items-center transition-all ${stateUnknown && 'state-unknown'}">
${logo({ path: logoPath, size: logoSize, isIpfsOnline })}
<p class="montserrat mt3 mb0 f2">${i18n.getMessage('page_landingWelcome_logo_title')}</p>
${renderLogo(isIpfsOnline)}
${showTitle
? html`<p class="montserrat mt3 mb0 f2">${i18n.getMessage('page_landingWelcome_logo_title')}</p>`
: ''
}
</div>
`
}
Expand Down
1 change: 1 addition & 0 deletions add-on/src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

export const welcomePage = '/dist/landing-pages/welcome/index.html'
export const optionsPage = '/dist/options/options.html'
export const recoveryPagePath = '/dist/recovery/recovery.html'
export const tickMs = 250 // no CPU spike, but still responsive enough
3 changes: 1 addition & 2 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import LRU from 'lru-cache'
import all from 'it-all'
import { optionDefaults, storeMissingOptions, migrateOptions, guiURLString, safeURL } from './options.js'
import { initState, offlinePeerCount } from './state.js'
import { createIpfsPathValidator, sameGateway, safeHostname } from './ipfs-path.js'
import { createIpfsPathValidator, dropSlash, sameGateway, safeHostname } from './ipfs-path.js'
import createDnslinkResolver from './dnslink.js'
import { createRequestModifier } from './ipfs-request.js'
import { initIpfsClient, destroyIpfsClient, reloadIpfsClientOfflinePages } from './ipfs-client/index.js'
Expand Down Expand Up @@ -223,7 +223,6 @@ export default async function init () {

async function sendStatusUpdateToBrowserAction () {
if (!browserActionPort) return
const dropSlash = url => url.replace(/\/$/, '')
const currentTab = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0])
const { version } = browser.runtime.getManifest()
const info = {
Expand Down
2 changes: 2 additions & 0 deletions add-on/src/lib/ipfs-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import isFQDN from 'is-fqdn'
// For how long more expensive lookups (DAG traversal etc) should be cached
const RESULT_TTL_MS = 300000 // 5 minutes

export const dropSlash = url => url.replace(/\/$/, '')

// Turns URL or URIencoded path into a content path
export function ipfsContentPath (urlOrPath, opts) {
opts = opts || {}
Expand Down
11 changes: 10 additions & 1 deletion add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import debug from 'debug'
import LRU from 'lru-cache'
import isIPFS from 'is-ipfs'
import isFQDN from 'is-fqdn'
import { pathAtHttpGateway, sameGateway, ipfsUri } from './ipfs-path.js'
import { dropSlash, ipfsUri, pathAtHttpGateway, sameGateway } from './ipfs-path.js'
import { safeURL } from './options.js'
import { braveNodeType } from './ipfs-client/brave.js'
import { recoveryPagePath } from './constants.js'

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

Expand Down Expand Up @@ -140,6 +142,13 @@ export function createRequestModifier (getState, dnslinkResolver, ipfsPathValida
const state = getState()
if (!state.active) return

// When local IPFS node is unreachable , show recovery page where user can redirect
// to public gateway.
if (!state.nodeActive && request.type === 'main_frame' && sameGateway(request.url, state.gwURL)) {
const publicUri = ipfsPathValidator.resolveToPublicUrl(request.url, state.pubGwURLString)
return { redirectUrl: `${dropSlash(runtimeRoot)}${recoveryPagePath}#${encodeURIComponent(publicUri)}` }
}

// When Subdomain Proxy is enabled we normalize address bar requests made
// to the local gateway and replace raw IP with 'localhost' hostname to
// take advantage of subdomain redirect provided by go-ipfs >= 0.5
Expand Down
7 changes: 5 additions & 2 deletions add-on/src/lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ export function initState (options, overrides) {
return false
}
}
// TODO state.connected ~= state.peerCount > 0
// TODO state.nodeActive ~= API is online,eg. state.peerCount > offlinePeerCount
// TODO refactor this into a class. It's getting too big and messy.
Object.defineProperty(state, 'nodeActive', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if things defined via Object.defineProperty will survive postMessage serialization present in places where we use browser.runtime.connect ?

I vaguely remember that we did not do getters like this in the past because it did not survive serialization (maybe browser fixed it these days?). That is why we read peerCount field directly (without getters) and compare it to offlinePeerCount const.

By adding these getters here, we are having two ways of doing online check in the codebase, which over time leads to errors.

My suggestion is to remove these Object.defineProperty to keep this PR scope small, and if you want to add them, do that in separate PR that also refactors all places which act on peerCount (if possible, but I feel it may not be worth the work).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it does: https://github.com/ipfs/ipfs-companion/pull/1125/files#diff-cf4a768af4d2baab2a1c0a5862f50af6239b155754f4a9ea90b43e465f115d88R148

I need this to validate if we need to recover the client. However I didn't want to touch too much of this, I'm just trying to follow the adjacent code pattern. I created #1129 to tackle this again in the future.

// TODO: make quick fetch to confirm it works?
get: function () { return this.peerCount !== offlinePeerCount }
})
Object.defineProperty(state, 'localGwAvailable', {
// TODO: make quick fetch to confirm it works?
get: function () { return this.ipfsNodeType !== 'embedded' }
Expand Down
61 changes: 61 additions & 0 deletions add-on/src/recovery/recovery.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@import url('~tachyons/css/tachyons.css');
@import url('~ipfs-css/ipfs.css');

#left-col {
background-image: url('../../images/stars.png'), linear-gradient(to bottom, #041727 0%, #043b55 100%);
background-size: 100%;
background-repeat: repeat;
}

a:hover {
text-decoration: underline;
}

/*
https://github.com/tachyons-css/tachyons-queries
Tachyons: $point == large
*/
@media (min-width: 60em) {
#left-col {
position: fixed;
top: 0;
right: 55%;
width: 45%;
background-image: url('../../images/stars.png'), linear-gradient(to bottom, #041727 0%, #043b55 100%);
background-size: 100%;
background-repeat: repeat;
}

#right-col {
margin-left: 57%;
margin-right: 7%;
}
}

@media (max-height: 800px) {
#left-col img {
width: 98px !important;
height: 98px !important;
}

#left-col svg {
width: 60px;
}
}

header {
z-index: 1;
border-color: #69c4cd;
text-transform: uppercase;
}

#header-logo {
height: 50px;
width: 117.5px;
}

.recovery-root {
width: 100%;
height: 100%;
text-align: left;
}
26 changes: 26 additions & 0 deletions add-on/src/recovery/recovery.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
lidel marked this conversation as resolved.
Show resolved Hide resolved
<html>
<head>
<title>IPFS Node is Offline</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlo89/56ZQ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUjDu1lo89/6mhTP+zrVP/nplD/5+aRK8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHNiIS6Wjz3/ubFY/761W/+vp1D/urRZ/8vDZf/GvmH/nplD/1BNIm8AAAAAAAAAAAAAAAAAAAAAAAAAAJaPPf+knEj/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf+tpk7/nplD/wAAAAAAAAAAAAAAAJaPPf+2rVX/vrVb/761W/++tVv/vrVb/6+nUP+6tFn/y8Nl/8vDZf/Lw2X/y8Nl/8G6Xv+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/761W/+vp1D/urRZ/8vDZf/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/vrVb/761W/++tVv/r6dQ/7q0Wf/Lw2X/y8Nl/8vDZf/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf++tVv/vrVb/761W/++tVv/vbRa/5aPPf+emUP/y8Nl/8vDZf/Lw2X/y8Nl/8vDZf+emUP/AAAAAAAAAACWjz3/vrVb/761W/++tVv/vrVb/5qTQP+inkb/op5G/6KdRv/Lw2X/y8Nl/8vDZf/Lw2X/nplD/wAAAAAAAAAAlo89/761W/++tVv/sqlS/56ZQ//LxWb/0Mlp/9DJaf/Kw2X/oJtE/7+3XP/Lw2X/y8Nl/56ZQ/8AAAAAAAAAAJaPPf+9tFr/mJE+/7GsUv/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+xrFL/nplD/8vDZf+emUP/AAAAAAAAAACWjz3/op5G/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+inkb/nplD/wAAAAAAAAAAAAAAAKKeRv+3slb/0cpq/9HKav/Rymr/0cpq/9HKav/Rymr/0cpq/9HKav+1sFX/op5G/wAAAAAAAAAAAAAAAAAAAAAAAAAAop5GUKKeRv/Nxmf/0cpq/9HKav/Rymr/0cpq/83GZ/+inkb/op5GSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G16KeRv/LxWb/y8Vm/6KeRv+inkaPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAop5G/6KeRtcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/n8AAPgfAADwDwAAwAMAAIABAACAAQAAgAEAAIABAACAAQAAgAEAAIABAACAAQAAwAMAAPAPAAD4HwAA/n8AAA==" />
<link rel="stylesheet" href="/dist/bundles/uiCommons.css">
<link rel="stylesheet" href="/dist/bundles/recoveryPage.css">
</head>
<body class="navy bg-white sans-serif">
<app class="flex flex-column transition-all vh-100">
<header class="flex-l items-center pa3 bg-navy bb bw3 border-aqua tc tl-l justify-between">
<div class="flex-none v-mid">
<svg alt="IPFS" id="header-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 553 235.3"><path d="M239 63h17.8v105H239V63zm35.6 0h36.3c7.9 0 14.5.9 19.6 2.6s9.2 4.1 12.1 7.1a24.45 24.45 0 016.2 10.2 40.75 40.75 0 011.8 12.1 45.69 45.69 0 01-1.8 12.9 26.58 26.58 0 01-6.2 10.8 30.59 30.59 0 01-12.1 7.3c-5.1 1.8-11.5 2.7-19.3 2.7h-19.1V168h-17.5V63zm36.2 51a38.37 38.37 0 0011.1-1.3 16.3 16.3 0 006.8-3.7 13.34 13.34 0 003.5-5.8 29.75 29.75 0 001-7.6 25.68 25.68 0 00-1-7.7 12 12 0 00-3.6-5.5 17.15 17.15 0 00-6.9-3.4 41.58 41.58 0 00-10.9-1.2h-18.5V114h18.5zm119.9-51v15.3h-49.2V108h46.3v15.4h-46.3V168h-17.8V63h67zm26.2 72.9c.8 6.9 3.3 11.9 7.4 15s10.4 4.7 18.6 4.7a32.61 32.61 0 0010.1-1.3 20.52 20.52 0 006.6-3.5 12 12 0 003.5-5.2 19.08 19.08 0 001-6.4 16.14 16.14 0 00-.7-4.9 12.87 12.87 0 00-2.6-4.5 16.59 16.59 0 00-5.1-3.6 35 35 0 00-8.2-2.4l-13.4-2.5a89.76 89.76 0 01-14.1-3.7 33.51 33.51 0 01-10.4-5.8 22.28 22.28 0 01-6.3-8.8 34.1 34.1 0 01-2.1-12.7 26 26 0 0111.3-22.4 36.35 36.35 0 0112.6-5.6 65.89 65.89 0 0115.8-1.8c7.2 0 13.3.8 18.2 2.5a34.46 34.46 0 0111.9 6.5 28.21 28.21 0 016.9 9.3 42.1 42.1 0 013.2 11l-16.8 2.6c-1.4-5.9-3.7-10.2-7.1-13.1s-8.7-4.3-16.1-4.3a43.9 43.9 0 00-10.5 1.1 19.47 19.47 0 00-6.8 3.1 11.63 11.63 0 00-3.7 4.6 14.08 14.08 0 00-1.1 5.4c0 4.6 1.2 8 3.7 10.3s6.9 4 13.2 5.3l14.5 2.8c11.1 2.1 19.2 5.6 24.4 10.5s7.8 12.1 7.8 21.4a31.37 31.37 0 01-2.4 12.3 25.27 25.27 0 01-7.4 9.8 36.58 36.58 0 01-12.4 6.6 56 56 0 01-17.3 2.4c-13.4 0-24-2.8-31.6-8.5s-11.9-14.4-12.6-26.2h18z" fill="#fff"/><path fill="#469ea2" d="M30.3 164l84 48.5 84-48.5V67l-84-48.5-84 48.5v97z"/><path d="M105.7 30.1l-61 35.2a18.19 18.19 0 010 3.3l60.9 35.2a14.55 14.55 0 0117.3 0l60.9-35.2a18.19 18.19 0 010-3.3L123 30.1a14.55 14.55 0 01-17.3 0zm84 48.2l-61 35.6a14.73 14.73 0 01-8.6 15l.1 70a15.57 15.57 0 012.8 1.6l60.9-35.2a14.73 14.73 0 018.6-15V79.9a20 20 0 01-2.8-1.6zm-150.8.4a15.57 15.57 0 01-2.8 1.6v70.4a14.38 14.38 0 018.6 15l60.9 35.2a15.57 15.57 0 012.8-1.6v-70.4a14.38 14.38 0 01-8.6-15L38.9 78.7z" fill="#6acad1"/><path fill="#469ea2" d="M114.3 29l75.1 43.4v86.7l-75.1 43.4-75.1-43.4V72.3L114.3 29m0-10.3l-84 48.5v97l84 48.5 84-48.5v-97l-84-48.5z"/><path fill="#469ea2" d="M114.9 132h-1.2A15.66 15.66 0 0198 116.3v-1.2a15.66 15.66 0 0115.7-15.7h1.2a15.66 15.66 0 0115.7 15.7v1.2a15.66 15.66 0 01-15.7 15.7zm0 64.5h-1.2a15.65 15.65 0 00-13.7 8l14.3 8.2 14.3-8.2a15.65 15.65 0 00-13.7-8zm83.5-48.5h-.6a15.66 15.66 0 00-15.7 15.7v1.2a15.13 15.13 0 002 7.6l14.3-8.3V148zm-14.3-89a15.4 15.4 0 00-2 7.6v1.2a15.66 15.66 0 0015.7 15.7h.6V67.2L184.1 59zm-69.8-40.3L100 26.9a15.73 15.73 0 0013.7 8.1h1.2a15.65 15.65 0 0013.7-8l-14.3-8.3zM44.6 58.9l-14.3 8.3v16.3h.6a15.66 15.66 0 0015.7-15.7v-1.2a16.63 16.63 0 00-2-7.7zM30.9 148h-.6v16.2l14.3 8.3a15.4 15.4 0 002-7.6v-1.2A15.66 15.66 0 0030.9 148z"/><path d="M114.3 213.2v-97.1l-84-48.5v97.1z" fill-opacity=".15" fill="#083b54"/><path d="M198.4 163.8v-97l-84 48.5v97.1z" fill-opacity=".05" fill="#083b54"/></svg>
</div>
<h1 id="header-text" class="pt2 pt0-l ma0 f3 fw2 montserrat aqua ttu flex-none no-select"></h1>
</header>
<main class="bg-white pv3 pa3 pa4-l flex-grow-1">
<div id="root"></div>
</main>
<script src="/dist/bundles/uiCommons.bundle.js"></script>
<script src="/dist/bundles/recoveryPage.bundle.js"></script>
</app>
</body>
</html>
78 changes: 78 additions & 0 deletions add-on/src/recovery/recovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use strict'
/* eslint-env browser, webextensions */

import choo from 'choo'
import html from 'choo/html/index.js'
import icon from 'ipfs-css/icons/stroke_attention.svg'
import browser, { i18n, runtime } from 'webextension-polyfill'
import { renderCompanionLogo } from '../landing-pages/welcome/page.js'
import createWelcomePageStore from '../landing-pages/welcome/store.js'
import { optionsPage } from '../lib/constants.js'
import './recovery.css'

const app = choo()

const learnMoreLink = html`<a class="no-underline" href="https://docs.ipfs.tech/how-to/companion-node-types/" target="_blank" rel="noopener noreferrer">${i18n.getMessage('recovery_page_learn_more')}</a>`
lidel marked this conversation as resolved.
Show resolved Hide resolved

const optionsPageLink = html`<a class="no-underline" id="learn-more" href="${optionsPage}" target="_blank" rel="noopener noreferrer">${i18n.getMessage('recovery_page_update_preferences')}</a>`
lidel marked this conversation as resolved.
Show resolved Hide resolved

// TODO (whizzzkid): refactor base store to be more generic.
app.use(createWelcomePageStore(i18n, runtime))
// Register our single route
app.route('*', (state) => {
browser.runtime.sendMessage({ telemetry: { trackView: 'recovery' } })
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SgtPooki added view for recovery pages.

const { hash } = window.location
const { href: publicURI } = new URL(decodeURIComponent(hash.slice(1)))
const { version } = browser.runtime.getManifest()

if (!publicURI) {
return
}

const openURLFromHash = () => {
try {
console.log('Opening URL from hash:', publicURI)
window.location.replace(publicURI)
} catch (err) {
console.error('Failed to open URL from hash:', err)
}
}

// if the IPFS node is online, open the URL from the hash, this will redirect to the local node.
if (state.isIpfsOnline) {
openURLFromHash()
return
}

return html`<div class="flex flex-column flex-row-l">
<div id="left-col" class="min-vh-100 flex flex-column justify-center items-center bg-navy white">
${renderCompanionLogo(i18n, false)}
<p class="montserrat mt3 mb0 f3">${version}</p>
</div>

<div id="right-col" class="pt4 w-100 flex flex-column justify-around items-center">
<img src="${icon}" class="w4 h4" />
<h1 class="f3 fw6">${i18n.getMessage('recovery_page_sub_header')}</h1>
<p class="f3 fw5">${i18n.getMessage('recovery_page_message_p1')}</p>
<p class="f4 fw4">${i18n.getMessage('recovery_page_message_p2')}</p>
<p class="f4 fw4 w-100"><span class="b-ns">Public URL:</span> <a href="${publicURI}" rel="noopener noreferrer" target="_blank">${publicURI}</a></p>
<button
class="fade-in ba bw1 b--teal bg-teal snow f7 ph2 pv3 br4 ma1 pointer"
onclick=${openURLFromHash}
href="${publicURI}"
>
<span class="f5 fw6">${i18n.getMessage('recovery_page_button')}</span>
</button>
<p class="f5 fw2 pt6">
${learnMoreLink} | ${optionsPageLink}
</span>
</div>
</div>`
})

// Start the application and render it to the given querySelector
app.mount('#root')

// Set page title and header translation
document.getElementById('header-text').innerText = i18n.getMessage('recovery_page_header')
document.title = i18n.getMessage('recovery_page_title')
19 changes: 19 additions & 0 deletions test/functional/lib/ipfs-request-gateway-redirect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('modifyRequest.onBeforeRequest:', function () {
global.URL = URL
global.browser = browser
browser.runtime.id = 'testid'
browser.runtime.getURL.returns('chrome-extension://testid/')
})

beforeEach(async function () {
Expand Down Expand Up @@ -425,6 +426,24 @@ describe('modifyRequest.onBeforeRequest:', function () {
})
})

describe('Recovers Page if node is unreachable', function () {
beforeEach(function () {
global.browser = browser
state.ipfsNodeType = 'external'
state.redirect = true
state.peerCount = -1
state.gwURLString = 'http://localhost:8080'
state.gwURL = new URL('http://localhost:8080')
state.pubGwURLString = 'https://ipfs.io'
state.pubGwURL = new URL('https://ipfs.io')
})
it('should present recovery page if node is offline', function () {
expect(state.nodeActive).to.be.equal(false)
const request = url2request('https://localhost:8080/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR/foo/bar')
expect(modifyRequest.onBeforeRequest(request).redirectUrl).to.equal('chrome-extension://testid/dist/recovery/recovery.html#https%3A%2F%2Fipfs.io%2Fipfs%2FQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR%2Ffoo%2Fbar')
})
})

after(function () {
delete global.URL
delete global.browser
Expand Down
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const uiConfig = merge(commonConfig, {
browserAction: './add-on/src/popup/browser-action/index.js',
importPage: './add-on/src/popup/quick-import.js',
optionsPage: './add-on/src/options/options.js',
recoveryPage: './add-on/src/recovery/recovery.js',
welcomePage: './add-on/src/landing-pages/welcome/index.js'
},
optimization: {
Expand Down