Skip to content

Commit

Permalink
feat: snapshot link and IPNS path copy options (#937)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcin Rataj <lidel@lidel.org>
  • Loading branch information
jessicaschilling and lidel committed Nov 9, 2020
1 parent ce5a2a6 commit bbb2945
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 49 deletions.
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

0 comments on commit bbb2945

Please sign in to comment.