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: snapshot link and IPNS path copy options #937

Merged
merged 22 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
94e0b7e
Rough in @deedlefake changes from https://github.com/ipfs-shipyard/ip…
jessicaschilling Oct 27, 2020
c2f42b7
Permalink: right position, non-working helperText
jessicaschilling Oct 27, 2020
3b06493
Add IPFS path option, still needs helperText
jessicaschilling Oct 27, 2020
4972ffc
Update context-actions.js
jessicaschilling Oct 27, 2020
b73b49d
Draconian linting
jessicaschilling Oct 27, 2020
022963f
Remove dummy helperText, still needs wiring
jessicaschilling Oct 28, 2020
a1af3f4
Add pre-commit linting via husky
jessicaschilling Oct 28, 2020
357b2e0
Update menu titles, helptext
jessicaschilling Oct 28, 2020
4f04ea6
Resources -> assets
jessicaschilling Oct 28, 2020
3410c5b
Move tools into header: icons still need wiring
jessicaschilling Oct 29, 2020
ac5f929
Header tool buttons: variable icons
jessicaschilling Oct 29, 2020
882dc0c
Button and language tweaks
jessicaschilling Oct 30, 2020
71b2ab8
fix: IPNS snapshot helper text
lidel Oct 30, 2020
8e85277
Revert pin changes, will do in https://github.com/ipfs-shipyard/ipfs-…
jessicaschilling Oct 30, 2020
bbcdac6
message: copy snapshot link
jessicaschilling Oct 30, 2020
ec402b0
Add push effect to header buttons
jessicaschilling Oct 30, 2020
dff4a08
Header icons/buttons: make push slightly less pushy
jessicaschilling Oct 30, 2020
e47f04a
chore: standard 15.x
lidel Oct 30, 2020
d02fb7f
Use isMutable per https://github.com/ipfs-shipyard/ipfs-companion/pul…
jessicaschilling Oct 30, 2020
659d601
Clarify helptext
jessicaschilling Oct 30, 2020
b369846
fix: cache expensive path resolutions
lidel Nov 7, 2020
6cc79cf
fix: always show shareable link
lidel Nov 7, 2020
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
40 changes: 28 additions & 12 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@
"description": "A label tooltip in Node status section of Browser Action pop-up (panel_statusSwarmPeersTitle)"
},
"panel_quickImport": {
"message": "Quick Import/Share…",
"message": "Import",
"description": "A menu item in Browser Action pop-up (panel_quickImport)"
},
"panel_quickImportTooltip": {
"message": "Import files to IPFS and copy a publicly shareable link to your clipboard",
"message": "Import files to IPFS and copy a publicly shareable link to your clipboard.",
"description": "A menu item tooltip in Browser Action pop-up (panel_quickImportTooltip)"
},
"panel_openWebui": {
"message": "Go to My Node",
"message": "My Node",
"description": "A menu item in Browser Action pop-up (panel_openWebui)"
},
"panel_openWebuiTooltip": {
"message": "Open your IPFS node's controls in your browser",
"message": "Open your IPFS node's controls in your browser.",
"description": "A menu item in Browser Action pop-up (panel_openWebuiTooltip)"
},
"panel_openPreferences": {
Expand All @@ -76,11 +76,11 @@
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectEnable)"
},
"panel_activeTabSiteIntegrationsToggle": {
"message": "Enable on $1",
"message": "Enable for $1",
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteIntegrationsToggle)"
},
"panel_activeTabSiteIntegrationsToggleTooltip": {
"message": "Enable/disable all IPFS integrations on $1",
"message": "Enable/disable all IPFS integrations on $1.",
"description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteIntegrationsToggleTooltip)"
},
"panel_pinCurrentIpfsAddress": {
Expand All @@ -91,34 +91,50 @@
"message": "Pin this page's IPFS resources to your node to have a local copy that's available offline and never thrown away.",
"description": "A menu item tooltip in Browser Action pop-up (panel_pinCurrentIpfsAddressTooltip)"
},
"panelCopy_currentIpnsAddress": {
"message": "Copy IPNS Path",
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_currentIpnsAddress)"
},
"panelCopy_currentIpnsAddressTooltip": {
"message": "Use this content path with IPFS tools and gateways to reach the most recently updated version of this tab's content.",
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_currentIpnsAddressTooltip)"
},
"panelCopy_currentIpfsAddress": {
"message": "Copy IPFS Content Path",
"message": "Copy IPFS Path",
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_currentIpfsAddress)"
},
"panelCopy_currentIpfsAddressTooltip": {
"message": "A canonical content path that you can use with IPFS tools and gateways",
"message": "Use this content path with IPFS tools and gateways to reach the content in this tab at this moment in time. This snapshot won't change, even if content changes later.",
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_currentIpfsAddressTooltip)"
},
"panelCopy_copyRawCid": {
"message": "Copy CID",
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_copyRawCid)"
},
"panelCopy_copyRawCidTooltip": {
"message": "The unique IPFS content identifier for this page",
"message": "The unique IPFS content identifier for this tab at this moment in time. If content changes later, the CID will change too.",
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_copyRawCidTooltip)"
},
"panelCopy_copyRawCidNotReadyHint": {
"panelCopy_notReadyHint": {
"message": "(waiting for DAG data)",
"description": "A hint in menu item in Browser Action pop-up to indicate CID is still being resolved (panelCopy_copyRawCidNotReadyHint)"
"description": "A hint in menu item in Browser Action pop-up to indicate value is still being resolved (panelCopy_notReadyHint)"
},
"panel_copyCurrentPublicGwUrl": {
"message": "Copy Shareable Link",
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_copyCurrentPublicGwUrl)"
},
"panel_copyCurrentPublicGwUrlTooltip": {
"message": "This link works for anyone, even if they don't use IPFS",
"message": "A shareable link to this tab that works for anyone, even if they don't use IPFS.",
"description": "A menu item tooltip in Browser Action pop-up (panel_copyCurrentPublicGwUrlTooltip)"
},
"panel_copyCurrentPermalink": {
"message": "Copy Snapshot Link",
"description": "A menu item in Browser Action pop-up (panel_copyCurrentPermalink)"
},
"panel_copyCurrentPermalinkTooltip": {
"message": "A link to a snapshot of this tab at this moment in time; it won't change, even if content changes later. This link works for anyone, even if they don't use IPFS.",
"description": "A menu item tooltip in Browser Action pop-up (panel_copyCurrentPermalinkTooltip)"
},
"panel_contextMenuViewOnGateway": {
"message": "View on Gateway",
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_contextMenuViewOnGateway)"
Expand Down
6 changes: 5 additions & 1 deletion add-on/src/lib/context-menus.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,18 @@ const contextMenuImportToIpfs = 'contextMenu_importToIpfs'
// Add X to IPFS
const contextMenuImportToIpfsSelection = 'contextMenu_importToIpfsSelection'
// Copy X
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpfsAddress'
const contextMenuCopyCidAddress = 'panelCopy_currentIpfsAddress'
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpnsAddress'
const contextMenuCopyRawCid = 'panelCopy_copyRawCid'
const contextMenuCopyAddressAtPublicGw = 'panel_copyCurrentPublicGwUrl'
const contextMenuViewOnGateway = 'panel_contextMenuViewOnGateway'
const contextMenuCopyPermalink = 'panel_copyCurrentPermalink'
module.exports.contextMenuCopyCidAddress = contextMenuCopyCidAddress
module.exports.contextMenuCopyCanonicalAddress = contextMenuCopyCanonicalAddress
module.exports.contextMenuCopyRawCid = contextMenuCopyRawCid
module.exports.contextMenuCopyAddressAtPublicGw = contextMenuCopyAddressAtPublicGw
module.exports.contextMenuViewOnGateway = contextMenuViewOnGateway
module.exports.contextMenuCopyPermalink = contextMenuCopyPermalink

// menu items that are enabled only when API is online
const apiMenuItems = new Set()
Expand Down
12 changes: 12 additions & 0 deletions add-on/src/lib/copier.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ function createCopier (notify, ipfsPathValidator) {
await copyTextToClipboard(ipfsPath, notify)
},

async copyCidAddress (context, contextType) {
const url = await findValueForContext(context, contextType)
const ipfsPath = await ipfsPathValidator.resolveToImmutableIpfsPath(url)
await copyTextToClipboard(ipfsPath, notify)
},

async copyRawCid (context, contextType) {
const url = await findValueForContext(context, contextType)
try {
Expand All @@ -68,6 +74,12 @@ function createCopier (notify, ipfsPathValidator) {
const url = await findValueForContext(context, contextType)
const publicUrl = ipfsPathValidator.resolveToPublicUrl(url)
await copyTextToClipboard(publicUrl, notify)
},

async copyPermalink (context, contextType) {
const url = await findValueForContext(context, contextType)
const permalink = await ipfsPathValidator.resolveToPermalink(url)
await copyTextToClipboard(permalink, notify)
}
}
}
Expand Down
25 changes: 18 additions & 7 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const createNotifier = require('./notifier')
const createCopier = require('./copier')
const createInspector = require('./inspector')
const { createRuntimeChecks } = require('./runtime-checks')
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway } = require('./context-menus')
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway, contextMenuCopyPermalink, contextMenuCopyCidAddress } = require('./context-menus')
const createIpfsProxy = require('./ipfs-proxy')
const { registerSubdomainProxy } = require('./http-proxy')
const { showPendingLandingPages } = require('./on-installed')
Expand Down Expand Up @@ -217,7 +217,7 @@ module.exports = async function init () {

// Cache for async URL2CID resolution used by browser action
// (resolution happens off-band so UI render is not blocked with sometimes expensive DHT traversal)
const url2cidCache = new LRU({ max: 10, maxAge: 1000 * 30 })
const resolveCache = new LRU({ max: 10, maxAge: 1000 * 30 })

var browserActionPort

Expand All @@ -235,8 +235,10 @@ module.exports = async function init () {
notification: (message) => notify(message.title, message.message),
[contextMenuViewOnGateway]: inspector.viewOnGateway,
[contextMenuCopyCanonicalAddress]: copier.copyCanonicalAddress,
[contextMenuCopyCidAddress]: copier.copyCidAddress,
[contextMenuCopyRawCid]: copier.copyRawCid,
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw,
[contextMenuCopyPermalink]: copier.copyPermalink
}

function handleMessageFromBrowserAction (message) {
Expand Down Expand Up @@ -279,13 +281,22 @@ module.exports = async function init () {
if (info.isIpfsContext) {
info.currentTabPublicUrl = ipfsPathValidator.resolveToPublicUrl(url)
info.currentTabContentPath = ipfsPathValidator.resolveToIpfsPath(url)
if (!url2cidCache.has(url)) {
// run async resolution in the next event loop
if (resolveCache.has(url)) {
const [immutableIpfsPath, permalink, cid] = resolveCache.get(url)
info.currentTabImmutablePath = immutableIpfsPath
info.currentTabPermalink = permalink
info.currentTabCid = cid
} else {
// run async resolution in the next event loop so it does not block the UI
setImmediate(async () => {
url2cidCache.set(url, await ipfsPathValidator.resolveToCid(url))
resolveCache.set(url, [
await ipfsPathValidator.resolveToImmutableIpfsPath(url),
await ipfsPathValidator.resolveToPermalink(url),
await ipfsPathValidator.resolveToCid(url)
])
await sendStatusUpdateToBrowserAction()
})
}
info.currentTabCid = url2cidCache.get(url)
}
info.currentDnslinkFqdn = dnslinkResolver.findDNSLinkHostname(url)
info.currentFqdn = info.currentDnslinkFqdn || new URL(url).hostname
Expand Down
16 changes: 15 additions & 1 deletion add-on/src/lib/ipfs-path.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const isIPFS = require('is-ipfs')
const isFQDN = require('is-fqdn')

// For how long more expensive lookups (DAG traversal etc) should be cached
const RESULT_TTL_MS = 30 * 1000
const RESULT_TTL_MS = 300000 // 5 minutes

// Turns URL or URIencoded path into a content path
function ipfsContentPath (urlOrPath, opts) {
Expand Down Expand Up @@ -254,6 +254,20 @@ function createIpfsPathValidator (getState, getIpfs, dnslinkResolver) {
return null
},

// Resolve URL or path to HTTP URL with CID:
// - IPFS paths are attached to HTTP Gateway root
// - URL of DNSLinked websties are resolved to CIDs
// The purpose of this resolver is to always return a meaningful, publicly
// accessible URL that can be accessed without the need of an IPFS client
// and that never changes.
async resolveToPermalink (urlOrPath, optionalGatewayUrl) {
const input = urlOrPath
const ipfsPath = await this.resolveToImmutableIpfsPath(input)
const gateway = optionalGatewayUrl || getState().pubGwURLString
if (ipfsPath) return pathAtHttpGateway(ipfsPath, gateway)
return input.startsWith('http') ? input : null
},

// Resolve URL or path to IPFS Path:
// - The path can be /ipfs/ or /ipns/
// - Keeps pathname + ?search + #hash from original URL
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/browser-action.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

.header-icon:active {
color: #edf0f4;
transform: translateY(4px);
transform: translateY(2px);
}
.header-icon[disabled],
.header-icon[disabled]:active {
Expand Down
51 changes: 37 additions & 14 deletions add-on/src/popup/browser-action/context-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ const { sameGateway } = require('../../lib/ipfs-path')
const {
contextMenuViewOnGateway,
contextMenuCopyAddressAtPublicGw,
contextMenuCopyPermalink,
contextMenuCopyRawCid,
contextMenuCopyCanonicalAddress
contextMenuCopyCanonicalAddress,
contextMenuCopyCidAddress
} = require('../../lib/context-menus')

const notReady = browser.i18n.getMessage('panelCopy_notReadyHint')

// Context Actions are displayed in Browser Action and Page Action (FF only)
function contextActions ({
active,
Expand All @@ -24,9 +28,11 @@ function contextActions ({
currentFqdn,
currentDnslinkFqdn,
currentTabIntegrationsOptOut,
currentTabContentPath,
currentTabCid,
currentTabPublicUrl,
currentTabContentPath = notReady,
currentTabImmutablePath = notReady,
currentTabCid = notReady,
currentTabPublicUrl = notReady,
currentTabPermalink = notReady,
ipfsNodeType,
isIpfsContext,
isPinning,
Expand All @@ -42,6 +48,7 @@ function contextActions ({
}) {
const activeCidResolver = active && isIpfsOnline && isApiAvailable && currentTabCid
const activePinControls = active && isIpfsOnline && isApiAvailable
const isMutable = currentTabContentPath.startsWith('/ipns/')
const activeViewOnGateway = (currentTab) => {
if (!currentTab) return false
const { url } = currentTab
Expand All @@ -52,27 +59,43 @@ function contextActions ({
if (!isIpfsContext) return
return html`<div>
${activeViewOnGateway(currentTab)
? navItem({
text: browser.i18n.getMessage(contextMenuViewOnGateway),
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
})
: null}
? navItem({
text: browser.i18n.getMessage(contextMenuViewOnGateway),
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
})
: null}
${navItem({
text: browser.i18n.getMessage(contextMenuCopyAddressAtPublicGw),
title: browser.i18n.getMessage('panel_copyCurrentPublicGwUrlTooltip'),
helperText: currentTabPublicUrl,
onClick: () => onCopy(contextMenuCopyAddressAtPublicGw)
})}
${isMutable
? navItem({
text: browser.i18n.getMessage(contextMenuCopyPermalink),
title: browser.i18n.getMessage('panel_copyCurrentPermalinkTooltip'),
helperText: currentTabPermalink,
onClick: () => onCopy(contextMenuCopyPermalink)
})
: ''}
${isMutable
? navItem({
text: browser.i18n.getMessage(contextMenuCopyCanonicalAddress),
title: browser.i18n.getMessage('panelCopy_currentIpnsAddressTooltip'),
helperText: currentTabContentPath,
onClick: () => onCopy(contextMenuCopyCanonicalAddress)
})
: ''}
${navItem({
text: browser.i18n.getMessage(contextMenuCopyCanonicalAddress),
text: browser.i18n.getMessage(contextMenuCopyCidAddress),
title: browser.i18n.getMessage('panelCopy_currentIpfsAddressTooltip'),
helperText: currentTabContentPath,
onClick: () => onCopy(contextMenuCopyCanonicalAddress)
helperText: currentTabImmutablePath,
onClick: () => onCopy(contextMenuCopyCidAddress)
})}
${navItem({
text: browser.i18n.getMessage(contextMenuCopyRawCid),
title: browser.i18n.getMessage('panelCopy_copyRawCidTooltip'),
helperText: (currentTabCid || browser.i18n.getMessage('panelCopy_copyRawCidNotReadyHint')),
helperText: currentTabCid,
disabled: !activeCidResolver,
onClick: () => onCopy(contextMenuCopyRawCid)
})}
Expand Down Expand Up @@ -114,7 +137,7 @@ function activeTabActions (state) {
const showActiveTabSection = (state.isRedirectContext) || state.isIpfsContext
if (!showActiveTabSection) return
return html`
<div class="mv1">
<div class="mb1">
${navHeader('panel_activeTabSectionHeader')}
<div class="fade-in pv0">
${contextActions(state)} </div>
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/gateway-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = function gatewayStatus ({
}) {
const api = ipfsApiUrl && ipfsNodeType === 'embedded' ? 'js-ipfs' : ipfsApiUrl
return html`
<ul class="fade-in list mv0 pv2 ph3 white">
<ul class="fade-in list mv0 pt2 ph3 white">
${statusEntry({
label: 'panel_statusSwarmPeers',
labelLegend: 'panel_statusSwarmPeersTitle',
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const gatewayStatus = require('./gateway-status')
module.exports = function header (props) {
const { ipfsNodeType, active, onToggleActive, onOpenPrefs, onOpenReleaseNotes, isIpfsOnline, onOpenWelcomePage, showUpdateIndicator } = props
return html`
<div class="br2 br--top ba bw1 b--white ipfs-gradient-0">
<div>
<div class="pt3 pr3 pb2 pl3 no-user-select flex justify-between items-center">
<div class="inline-flex items-center">
<div
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/nav-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const html = require('choo/html')

function navHeader (label) {
return html`
<div class="no-select w-100 outline-0--focus tl ph3 pt3 pb1 o-40 f6 bt b--silver">
<div class="no-select w-100 outline-0--focus tl ph3 pt2 mt1 pb1 o-40 f6">
${browser.i18n.getMessage(label)}
</div>
`
Expand Down
6 changes: 4 additions & 2 deletions add-on/src/popup/browser-action/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ module.exports = function browserActionPage (state, emit) {

return html`
<div class="sans-serif" style="text-rendering: optimizeLegibility;">
${header(headerProps)}
${tools(opsProps)}
<div class="ba bw1 b--white ipfs-gradient-0">
${header(headerProps)}
${tools(opsProps)}
</div>
${activeTabActions(activeTabActionsProps)}
</div>
`
Expand Down
Loading