Skip to content

Commit bbb2945

Browse files
feat: snapshot link and IPNS path copy options (#937)
Co-authored-by: Marcin Rataj <[email protected]>
1 parent ce5a2a6 commit bbb2945

15 files changed

+175
-49
lines changed

add-on/_locales/en/messages.json

+28-12
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@
5252
"description": "A label tooltip in Node status section of Browser Action pop-up (panel_statusSwarmPeersTitle)"
5353
},
5454
"panel_quickImport": {
55-
"message": "Quick Import/Share…",
55+
"message": "Import",
5656
"description": "A menu item in Browser Action pop-up (panel_quickImport)"
5757
},
5858
"panel_quickImportTooltip": {
59-
"message": "Import files to IPFS and copy a publicly shareable link to your clipboard",
59+
"message": "Import files to IPFS and copy a publicly shareable link to your clipboard.",
6060
"description": "A menu item tooltip in Browser Action pop-up (panel_quickImportTooltip)"
6161
},
6262
"panel_openWebui": {
63-
"message": "Go to My Node",
63+
"message": "My Node",
6464
"description": "A menu item in Browser Action pop-up (panel_openWebui)"
6565
},
6666
"panel_openWebuiTooltip": {
67-
"message": "Open your IPFS node's controls in your browser",
67+
"message": "Open your IPFS node's controls in your browser.",
6868
"description": "A menu item in Browser Action pop-up (panel_openWebuiTooltip)"
6969
},
7070
"panel_openPreferences": {
@@ -76,11 +76,11 @@
7676
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectEnable)"
7777
},
7878
"panel_activeTabSiteIntegrationsToggle": {
79-
"message": "Enable on $1",
79+
"message": "Enable for $1",
8080
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteIntegrationsToggle)"
8181
},
8282
"panel_activeTabSiteIntegrationsToggleTooltip": {
83-
"message": "Enable/disable all IPFS integrations on $1",
83+
"message": "Enable/disable all IPFS integrations on $1.",
8484
"description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteIntegrationsToggleTooltip)"
8585
},
8686
"panel_pinCurrentIpfsAddress": {
@@ -91,34 +91,50 @@
9191
"message": "Pin this page's IPFS resources to your node to have a local copy that's available offline and never thrown away.",
9292
"description": "A menu item tooltip in Browser Action pop-up (panel_pinCurrentIpfsAddressTooltip)"
9393
},
94+
"panelCopy_currentIpnsAddress": {
95+
"message": "Copy IPNS Path",
96+
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_currentIpnsAddress)"
97+
},
98+
"panelCopy_currentIpnsAddressTooltip": {
99+
"message": "Use this content path with IPFS tools and gateways to reach the most recently updated version of this tab's content.",
100+
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_currentIpnsAddressTooltip)"
101+
},
94102
"panelCopy_currentIpfsAddress": {
95-
"message": "Copy IPFS Content Path",
103+
"message": "Copy IPFS Path",
96104
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_currentIpfsAddress)"
97105
},
98106
"panelCopy_currentIpfsAddressTooltip": {
99-
"message": "A canonical content path that you can use with IPFS tools and gateways",
107+
"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.",
100108
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_currentIpfsAddressTooltip)"
101109
},
102110
"panelCopy_copyRawCid": {
103111
"message": "Copy CID",
104112
"description": "A menu item in Browser Action pop-up and right-click context menu (panelCopy_copyRawCid)"
105113
},
106114
"panelCopy_copyRawCidTooltip": {
107-
"message": "The unique IPFS content identifier for this page",
115+
"message": "The unique IPFS content identifier for this tab at this moment in time. If content changes later, the CID will change too.",
108116
"description": "A menu item tooltip in Browser Action pop-up (panelCopy_copyRawCidTooltip)"
109117
},
110-
"panelCopy_copyRawCidNotReadyHint": {
118+
"panelCopy_notReadyHint": {
111119
"message": "(waiting for DAG data)",
112-
"description": "A hint in menu item in Browser Action pop-up to indicate CID is still being resolved (panelCopy_copyRawCidNotReadyHint)"
120+
"description": "A hint in menu item in Browser Action pop-up to indicate value is still being resolved (panelCopy_notReadyHint)"
113121
},
114122
"panel_copyCurrentPublicGwUrl": {
115123
"message": "Copy Shareable Link",
116124
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_copyCurrentPublicGwUrl)"
117125
},
118126
"panel_copyCurrentPublicGwUrlTooltip": {
119-
"message": "This link works for anyone, even if they don't use IPFS",
127+
"message": "A shareable link to this tab that works for anyone, even if they don't use IPFS.",
120128
"description": "A menu item tooltip in Browser Action pop-up (panel_copyCurrentPublicGwUrlTooltip)"
121129
},
130+
"panel_copyCurrentPermalink": {
131+
"message": "Copy Snapshot Link",
132+
"description": "A menu item in Browser Action pop-up (panel_copyCurrentPermalink)"
133+
},
134+
"panel_copyCurrentPermalinkTooltip": {
135+
"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.",
136+
"description": "A menu item tooltip in Browser Action pop-up (panel_copyCurrentPermalinkTooltip)"
137+
},
122138
"panel_contextMenuViewOnGateway": {
123139
"message": "View on Gateway",
124140
"description": "A menu item in Browser Action pop-up and right-click context menu (panel_contextMenuViewOnGateway)"

add-on/src/lib/context-menus.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,18 @@ const contextMenuImportToIpfs = 'contextMenu_importToIpfs'
5555
// Add X to IPFS
5656
const contextMenuImportToIpfsSelection = 'contextMenu_importToIpfsSelection'
5757
// Copy X
58-
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpfsAddress'
58+
const contextMenuCopyCidAddress = 'panelCopy_currentIpfsAddress'
59+
const contextMenuCopyCanonicalAddress = 'panelCopy_currentIpnsAddress'
5960
const contextMenuCopyRawCid = 'panelCopy_copyRawCid'
6061
const contextMenuCopyAddressAtPublicGw = 'panel_copyCurrentPublicGwUrl'
6162
const contextMenuViewOnGateway = 'panel_contextMenuViewOnGateway'
63+
const contextMenuCopyPermalink = 'panel_copyCurrentPermalink'
64+
module.exports.contextMenuCopyCidAddress = contextMenuCopyCidAddress
6265
module.exports.contextMenuCopyCanonicalAddress = contextMenuCopyCanonicalAddress
6366
module.exports.contextMenuCopyRawCid = contextMenuCopyRawCid
6467
module.exports.contextMenuCopyAddressAtPublicGw = contextMenuCopyAddressAtPublicGw
6568
module.exports.contextMenuViewOnGateway = contextMenuViewOnGateway
69+
module.exports.contextMenuCopyPermalink = contextMenuCopyPermalink
6670

6771
// menu items that are enabled only when API is online
6872
const apiMenuItems = new Set()

add-on/src/lib/copier.js

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ function createCopier (notify, ipfsPathValidator) {
4343
await copyTextToClipboard(ipfsPath, notify)
4444
},
4545

46+
async copyCidAddress (context, contextType) {
47+
const url = await findValueForContext(context, contextType)
48+
const ipfsPath = await ipfsPathValidator.resolveToImmutableIpfsPath(url)
49+
await copyTextToClipboard(ipfsPath, notify)
50+
},
51+
4652
async copyRawCid (context, contextType) {
4753
const url = await findValueForContext(context, contextType)
4854
try {
@@ -68,6 +74,12 @@ function createCopier (notify, ipfsPathValidator) {
6874
const url = await findValueForContext(context, contextType)
6975
const publicUrl = ipfsPathValidator.resolveToPublicUrl(url)
7076
await copyTextToClipboard(publicUrl, notify)
77+
},
78+
79+
async copyPermalink (context, contextType) {
80+
const url = await findValueForContext(context, contextType)
81+
const permalink = await ipfsPathValidator.resolveToPermalink(url)
82+
await copyTextToClipboard(permalink, notify)
7183
}
7284
}
7385
}

add-on/src/lib/ipfs-companion.js

+18-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const createNotifier = require('./notifier')
2121
const createCopier = require('./copier')
2222
const createInspector = require('./inspector')
2323
const { createRuntimeChecks } = require('./runtime-checks')
24-
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway } = require('./context-menus')
24+
const { createContextMenus, findValueForContext, contextMenuCopyAddressAtPublicGw, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuViewOnGateway, contextMenuCopyPermalink, contextMenuCopyCidAddress } = require('./context-menus')
2525
const createIpfsProxy = require('./ipfs-proxy')
2626
const { registerSubdomainProxy } = require('./http-proxy')
2727
const { showPendingLandingPages } = require('./on-installed')
@@ -217,7 +217,7 @@ module.exports = async function init () {
217217

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

222222
var browserActionPort
223223

@@ -235,8 +235,10 @@ module.exports = async function init () {
235235
notification: (message) => notify(message.title, message.message),
236236
[contextMenuViewOnGateway]: inspector.viewOnGateway,
237237
[contextMenuCopyCanonicalAddress]: copier.copyCanonicalAddress,
238+
[contextMenuCopyCidAddress]: copier.copyCidAddress,
238239
[contextMenuCopyRawCid]: copier.copyRawCid,
239-
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw
240+
[contextMenuCopyAddressAtPublicGw]: copier.copyAddressAtPublicGw,
241+
[contextMenuCopyPermalink]: copier.copyPermalink
240242
}
241243

242244
function handleMessageFromBrowserAction (message) {
@@ -279,13 +281,22 @@ module.exports = async function init () {
279281
if (info.isIpfsContext) {
280282
info.currentTabPublicUrl = ipfsPathValidator.resolveToPublicUrl(url)
281283
info.currentTabContentPath = ipfsPathValidator.resolveToIpfsPath(url)
282-
if (!url2cidCache.has(url)) {
283-
// run async resolution in the next event loop
284+
if (resolveCache.has(url)) {
285+
const [immutableIpfsPath, permalink, cid] = resolveCache.get(url)
286+
info.currentTabImmutablePath = immutableIpfsPath
287+
info.currentTabPermalink = permalink
288+
info.currentTabCid = cid
289+
} else {
290+
// run async resolution in the next event loop so it does not block the UI
284291
setImmediate(async () => {
285-
url2cidCache.set(url, await ipfsPathValidator.resolveToCid(url))
292+
resolveCache.set(url, [
293+
await ipfsPathValidator.resolveToImmutableIpfsPath(url),
294+
await ipfsPathValidator.resolveToPermalink(url),
295+
await ipfsPathValidator.resolveToCid(url)
296+
])
297+
await sendStatusUpdateToBrowserAction()
286298
})
287299
}
288-
info.currentTabCid = url2cidCache.get(url)
289300
}
290301
info.currentDnslinkFqdn = dnslinkResolver.findDNSLinkHostname(url)
291302
info.currentFqdn = info.currentDnslinkFqdn || new URL(url).hostname

add-on/src/lib/ipfs-path.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const isIPFS = require('is-ipfs')
66
const isFQDN = require('is-fqdn')
77

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

1111
// Turns URL or URIencoded path into a content path
1212
function ipfsContentPath (urlOrPath, opts) {
@@ -254,6 +254,20 @@ function createIpfsPathValidator (getState, getIpfs, dnslinkResolver) {
254254
return null
255255
},
256256

257+
// Resolve URL or path to HTTP URL with CID:
258+
// - IPFS paths are attached to HTTP Gateway root
259+
// - URL of DNSLinked websties are resolved to CIDs
260+
// The purpose of this resolver is to always return a meaningful, publicly
261+
// accessible URL that can be accessed without the need of an IPFS client
262+
// and that never changes.
263+
async resolveToPermalink (urlOrPath, optionalGatewayUrl) {
264+
const input = urlOrPath
265+
const ipfsPath = await this.resolveToImmutableIpfsPath(input)
266+
const gateway = optionalGatewayUrl || getState().pubGwURLString
267+
if (ipfsPath) return pathAtHttpGateway(ipfsPath, gateway)
268+
return input.startsWith('http') ? input : null
269+
},
270+
257271
// Resolve URL or path to IPFS Path:
258272
// - The path can be /ipfs/ or /ipns/
259273
// - Keeps pathname + ?search + #hash from original URL

add-on/src/popup/browser-action/browser-action.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
.header-icon:active {
1111
color: #edf0f4;
12-
transform: translateY(4px);
12+
transform: translateY(2px);
1313
}
1414
.header-icon[disabled],
1515
.header-icon[disabled]:active {

add-on/src/popup/browser-action/context-actions.js

+37-14
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ const { sameGateway } = require('../../lib/ipfs-path')
99
const {
1010
contextMenuViewOnGateway,
1111
contextMenuCopyAddressAtPublicGw,
12+
contextMenuCopyPermalink,
1213
contextMenuCopyRawCid,
13-
contextMenuCopyCanonicalAddress
14+
contextMenuCopyCanonicalAddress,
15+
contextMenuCopyCidAddress
1416
} = require('../../lib/context-menus')
1517

18+
const notReady = browser.i18n.getMessage('panelCopy_notReadyHint')
19+
1620
// Context Actions are displayed in Browser Action and Page Action (FF only)
1721
function contextActions ({
1822
active,
@@ -24,9 +28,11 @@ function contextActions ({
2428
currentFqdn,
2529
currentDnslinkFqdn,
2630
currentTabIntegrationsOptOut,
27-
currentTabContentPath,
28-
currentTabCid,
29-
currentTabPublicUrl,
31+
currentTabContentPath = notReady,
32+
currentTabImmutablePath = notReady,
33+
currentTabCid = notReady,
34+
currentTabPublicUrl = notReady,
35+
currentTabPermalink = notReady,
3036
ipfsNodeType,
3137
isIpfsContext,
3238
isPinning,
@@ -42,6 +48,7 @@ function contextActions ({
4248
}) {
4349
const activeCidResolver = active && isIpfsOnline && isApiAvailable && currentTabCid
4450
const activePinControls = active && isIpfsOnline && isApiAvailable
51+
const isMutable = currentTabContentPath.startsWith('/ipns/')
4552
const activeViewOnGateway = (currentTab) => {
4653
if (!currentTab) return false
4754
const { url } = currentTab
@@ -52,27 +59,43 @@ function contextActions ({
5259
if (!isIpfsContext) return
5360
return html`<div>
5461
${activeViewOnGateway(currentTab)
55-
? navItem({
56-
text: browser.i18n.getMessage(contextMenuViewOnGateway),
57-
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
58-
})
59-
: null}
62+
? navItem({
63+
text: browser.i18n.getMessage(contextMenuViewOnGateway),
64+
onClick: () => onViewOnGateway(contextMenuViewOnGateway)
65+
})
66+
: null}
6067
${navItem({
6168
text: browser.i18n.getMessage(contextMenuCopyAddressAtPublicGw),
6269
title: browser.i18n.getMessage('panel_copyCurrentPublicGwUrlTooltip'),
6370
helperText: currentTabPublicUrl,
6471
onClick: () => onCopy(contextMenuCopyAddressAtPublicGw)
6572
})}
73+
${isMutable
74+
? navItem({
75+
text: browser.i18n.getMessage(contextMenuCopyPermalink),
76+
title: browser.i18n.getMessage('panel_copyCurrentPermalinkTooltip'),
77+
helperText: currentTabPermalink,
78+
onClick: () => onCopy(contextMenuCopyPermalink)
79+
})
80+
: ''}
81+
${isMutable
82+
? navItem({
83+
text: browser.i18n.getMessage(contextMenuCopyCanonicalAddress),
84+
title: browser.i18n.getMessage('panelCopy_currentIpnsAddressTooltip'),
85+
helperText: currentTabContentPath,
86+
onClick: () => onCopy(contextMenuCopyCanonicalAddress)
87+
})
88+
: ''}
6689
${navItem({
67-
text: browser.i18n.getMessage(contextMenuCopyCanonicalAddress),
90+
text: browser.i18n.getMessage(contextMenuCopyCidAddress),
6891
title: browser.i18n.getMessage('panelCopy_currentIpfsAddressTooltip'),
69-
helperText: currentTabContentPath,
70-
onClick: () => onCopy(contextMenuCopyCanonicalAddress)
92+
helperText: currentTabImmutablePath,
93+
onClick: () => onCopy(contextMenuCopyCidAddress)
7194
})}
7295
${navItem({
7396
text: browser.i18n.getMessage(contextMenuCopyRawCid),
7497
title: browser.i18n.getMessage('panelCopy_copyRawCidTooltip'),
75-
helperText: (currentTabCid || browser.i18n.getMessage('panelCopy_copyRawCidNotReadyHint')),
98+
helperText: currentTabCid,
7699
disabled: !activeCidResolver,
77100
onClick: () => onCopy(contextMenuCopyRawCid)
78101
})}
@@ -114,7 +137,7 @@ function activeTabActions (state) {
114137
const showActiveTabSection = (state.isRedirectContext) || state.isIpfsContext
115138
if (!showActiveTabSection) return
116139
return html`
117-
<div class="mv1">
140+
<div class="mb1">
118141
${navHeader('panel_activeTabSectionHeader')}
119142
<div class="fade-in pv0">
120143
${contextActions(state)} </div>

add-on/src/popup/browser-action/gateway-status.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ module.exports = function gatewayStatus ({
2626
}) {
2727
const api = ipfsApiUrl && ipfsNodeType === 'embedded' ? 'js-ipfs' : ipfsApiUrl
2828
return html`
29-
<ul class="fade-in list mv0 pv2 ph3 white">
29+
<ul class="fade-in list mv0 pt2 ph3 white">
3030
${statusEntry({
3131
label: 'panel_statusSwarmPeers',
3232
labelLegend: 'panel_statusSwarmPeersTitle',

add-on/src/popup/browser-action/header.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const gatewayStatus = require('./gateway-status')
1212
module.exports = function header (props) {
1313
const { ipfsNodeType, active, onToggleActive, onOpenPrefs, onOpenReleaseNotes, isIpfsOnline, onOpenWelcomePage, showUpdateIndicator } = props
1414
return html`
15-
<div class="br2 br--top ba bw1 b--white ipfs-gradient-0">
15+
<div>
1616
<div class="pt3 pr3 pb2 pl3 no-user-select flex justify-between items-center">
1717
<div class="inline-flex items-center">
1818
<div

add-on/src/popup/browser-action/nav-header.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const html = require('choo/html')
66

77
function navHeader (label) {
88
return html`
9-
<div class="no-select w-100 outline-0--focus tl ph3 pt3 pb1 o-40 f6 bt b--silver">
9+
<div class="no-select w-100 outline-0--focus tl ph3 pt2 mt1 pb1 o-40 f6">
1010
${browser.i18n.getMessage(label)}
1111
</div>
1212
`

add-on/src/popup/browser-action/page.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ module.exports = function browserActionPage (state, emit) {
3030

3131
return html`
3232
<div class="sans-serif" style="text-rendering: optimizeLegibility;">
33-
${header(headerProps)}
34-
${tools(opsProps)}
33+
<div class="ba bw1 b--white ipfs-gradient-0">
34+
${header(headerProps)}
35+
${tools(opsProps)}
36+
</div>
3537
${activeTabActions(activeTabActionsProps)}
3638
</div>
3739
`

0 commit comments

Comments
 (0)