diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index 2776ad084..b830e7354 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -83,13 +83,13 @@ "message": "Enable/disable all IPFS integrations on $1.", "description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteIntegrationsToggleTooltip)" }, - "panel_pinCurrentIpfsAddress": { - "message": "Pin IPFS Resource", - "description": "A menu item in Browser Action pop-up (panel_pinCurrentIpfsAddress)" + "panel_importCurrentIpfsAddress": { + "message": "Import to Files at My Node", + "description": "A menu item in Browser Action pop-up (panel_importCurrentIpfsAddress)" }, - "panel_pinCurrentIpfsAddressTooltip": { - "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)" + "panel_importCurrentIpfsAddressTooltip": { + "message": "Import this tab's IPFS resource to your Files to have a persistent local copy that's available offline and never thrown away.", + "description": "A menu item tooltip in Browser Action pop-up (panel_importCurrentIpfsAddressTooltip)" }, "panelCopy_currentIpnsAddress": { "message": "Copy IPNS Path", @@ -195,21 +195,9 @@ "message": "Copied", "description": "A title of system notification (notify_copiedTitle)" }, - "notify_pinnedIpfsResourceTitle": { - "message": "IPFS Resource Pinned", - "description": "A title of system notification (notify_pinnedIpfsResourceTitle)" - }, - "notify_unpinnedIpfsResourceTitle": { - "message": "Removed IPFS Pin", - "description": "A title of system notification (notify_unpinnedIpfsResourceTitle)" - }, - "notify_pinErrorTitle": { - "message": "Error Pinning IPFS Resource", - "description": "A title of system notification (notify_pinErrorTitle)" - }, - "notify_unpinErrorTitle": { - "message": "Error Removing IPFS Pin", - "description": "A title of system notification (notify_unpinErrorTitle)" + "notify_fileCpImportErrorTitle": { + "message": "Error while importing IPFS Resource", + "description": "A title of system notification (notify_fileCpImportErrorTitle)" }, "notify_apiOnlineTitle": { "message": "IPFS API is Online", diff --git a/add-on/manifest.firefox.json b/add-on/manifest.firefox.json index 369b2e45c..d03edeee7 100644 --- a/add-on/manifest.firefox.json +++ b/add-on/manifest.firefox.json @@ -11,13 +11,6 @@ "strict_min_version": "68.0" } }, - "page_action": { - "default_icon": { - "128": "icons/ipfs-logo-off.svg" - }, - "default_title": "__MSG_pageAction_titleNonIpfs__", - "default_popup": "dist/popup/page-action/index.html" - }, "permissions": [ "", "idle", diff --git a/add-on/src/landing-pages/welcome/page.js b/add-on/src/landing-pages/welcome/page.js index 24fe7a341..ce605f83b 100644 --- a/add-on/src/landing-pages/welcome/page.js +++ b/add-on/src/landing-pages/welcome/page.js @@ -100,7 +100,7 @@ const renderInstallSteps = (i18n, isIpfsOnline) => { ` - const optionsUrl = browser.extension.getURL(optionsPage) + const optionsUrl = browser.runtime.getURL(optionsPage) return html`
diff --git a/add-on/src/lib/ipfs-client/brave.js b/add-on/src/lib/ipfs-client/brave.js index dcc57f959..0e27cbf6a 100644 --- a/add-on/src/lib/ipfs-client/brave.js +++ b/add-on/src/lib/ipfs-client/brave.js @@ -255,8 +255,8 @@ async function activationUiCleanup (browser) { }) log('[activation ui cleanup] Brave gateway is up, cleaning up') - const welcomePageUrl = browser.extension.getURL(welcomePage) - const optionsPageUrl = browser.extension.getURL(optionsPage) + const welcomePageUrl = browser.runtime.getURL(welcomePage) + const optionsPageUrl = browser.runtime.getURL(optionsPage) // we are unable to query ipfs:// directly due to reasons mentioned in 'closeIpfsTab' // so we make quick pass over all tabs and check welcome and options while at it. for (const tab of await browser.tabs.query({})) { diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 541c6a435..cf7a9db9e 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -17,7 +17,7 @@ const createDnslinkResolver = require('./dnslink') const { createRequestModifier } = require('./ipfs-request') const { initIpfsClient, destroyIpfsClient } = require('./ipfs-client') const { braveNodeType, useBraveEndpoint, releaseBraveEndpoint } = require('./ipfs-client/brave') -const { createIpfsImportHandler, formatImportDirectory } = require('./ipfs-import') +const { createIpfsImportHandler, formatImportDirectory, browserActionFilesCpImportCurrentTab } = require('./ipfs-import') const createNotifier = require('./notifier') const createCopier = require('./copier') const createInspector = require('./inspector') @@ -234,6 +234,7 @@ module.exports = async function init () { const BrowserActionMessageHandlers = { notification: (message) => notify(message.title, message.message), + [browserActionFilesCpImportCurrentTab]: () => ipfsImportHandler.filesCpImportCurrentTab(browser), [contextMenuViewOnGateway]: inspector.viewOnGateway, [contextMenuCopyCanonicalAddress]: copier.copyCanonicalAddress, [contextMenuCopyCidAddress]: copier.copyCidAddress, diff --git a/add-on/src/lib/ipfs-import.js b/add-on/src/lib/ipfs-import.js index 608c8dca2..a57cbbeee 100644 --- a/add-on/src/lib/ipfs-import.js +++ b/add-on/src/lib/ipfs-import.js @@ -8,9 +8,15 @@ log.error = debug('ipfs-companion:import:error') const browser = require('webextension-polyfill') const { redirectOptOutHint } = require('./ipfs-request') +const { ipfsContentPath } = require('./ipfs-path') + +module.exports.browserActionFilesCpImportCurrentTab = 'browserActionFilesCpImportCurrentTab' module.exports.createIpfsImportHandler = (getState, getIpfs, ipfsPathValidator, runtime, copier) => { - const { resolveToPublicUrl } = ipfsPathValidator + const { + resolveToPublicUrl, + resolveToCid + } = ipfsPathValidator const ipfsImportHandler = { getIpfsPathAndNativeAddress (cid) { const state = getState() @@ -33,9 +39,8 @@ module.exports.createIpfsImportHandler = (getState, getIpfs, ipfsPathValidator, async openFilesAtWebUI (mfsPath) { const state = getState() - await browser.tabs.create({ - url: `${state.webuiRootUrl}#/files${mfsPath}` - }) + const url = `${state.webuiRootUrl}#/files${mfsPath}` + await browser.tabs.create({ url }) }, async copyImportResultsToFiles (results, importDir) { @@ -82,7 +87,41 @@ module.exports.createIpfsImportHandler = (getState, getIpfs, ipfsPathValidator, } } } + }, + + async filesCpImportCurrentTab (browser) { + try { + const { + copyShareLink, + preloadFilesAtPublicGateway, + openFilesAtWebUI + } = this + const currentTab = await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0]) + const importDir = module.exports.formatImportDirectory(getState().importDir) + const ipfs = getIpfs() + const contentPath = ipfsContentPath(currentTab.url, { keepURIParams: false }) + const pathSegments = contentPath.split('/').filter(Boolean) + const namedImport = typeof pathSegments[2] !== 'undefined' + const resolvedCid = await resolveToCid(contentPath) + + // best-effort name based on last path segment, or IPNS root + const name = namedImport + ? pathSegments.slice(-1) + : `${pathSegments[0] === 'ipns' ? pathSegments[1] : 'unnamed'}_${resolvedCid.slice(-4)}` + + // import to mfs + await ipfs.files.mkdir(importDir, { parents: true }) + await ipfs.files.cp(`/ipfs/${resolvedCid}`, `${importDir}${name}`) + await openFilesAtWebUI(importDir) + + // create fake ipfs.add results, so we can reuse code from quick-import feature + const { cid } = await ipfs.files.stat(importDir, { hash: true }) + const results = [{ path: '', cid }, { path: name, cid: resolvedCid }] + await copyShareLink(results) + await preloadFilesAtPublicGateway(results) + } catch (e) { log.error('unexpected error during filesCpImportCurrentTab', e) } } + } return ipfsImportHandler } diff --git a/add-on/src/lib/ipfs-proxy/request-access.js b/add-on/src/lib/ipfs-proxy/request-access.js index c8ba8d3df..b85bc50a3 100644 --- a/add-on/src/lib/ipfs-proxy/request-access.js +++ b/add-on/src/lib/ipfs-proxy/request-access.js @@ -19,7 +19,7 @@ function createRequestAccess (browser, screen) { // TODO: cleanup so below stub is not needed permissions = Array.isArray(permissions) ? permissions : [permissions] - const url = browser.extension.getURL(opts.dialogPath || DIALOG_PATH) + const url = browser.runtime.getURL(opts.dialogPath || DIALOG_PATH) let dialogTabId diff --git a/add-on/src/lib/notifier.js b/add-on/src/lib/notifier.js index 09014d2f8..e813fd0a1 100644 --- a/add-on/src/lib/notifier.js +++ b/add-on/src/lib/notifier.js @@ -20,7 +20,7 @@ function createNotifier (getState) { try { return await browser.notifications.create({ type: 'basic', - iconUrl: browser.extension.getURL('icons/ipfs-logo-on.svg'), + iconUrl: browser.runtime.getURL('icons/ipfs-logo-on.svg'), title: title, message: message }) diff --git a/add-on/src/options/forms/experiments-form.js b/add-on/src/options/forms/experiments-form.js index 9c37d3cf4..521d66898 100644 --- a/add-on/src/options/forms/experiments-form.js +++ b/add-on/src/options/forms/experiments-form.js @@ -108,7 +108,7 @@ function experimentsForm ({ ${browser.i18n.getMessage('option_ipfsProxy_description')}

${ipfsProxy ? html` - + ${browser.i18n.getMessage('option_ipfsProxy_link_manage_permissions')} ` : html`${browser.i18n.getMessage('option_ipfsProxy_link_manage_permissions')}`} diff --git a/add-on/src/popup/browser-action/context-actions.js b/add-on/src/popup/browser-action/context-actions.js index 7450ca028..503ece61e 100644 --- a/add-on/src/popup/browser-action/context-actions.js +++ b/add-on/src/popup/browser-action/context-actions.js @@ -6,6 +6,7 @@ const html = require('choo/html') const navItem = require('./nav-item') const navHeader = require('./nav-header') const { sameGateway } = require('../../lib/ipfs-path') +const { formatImportDirectory } = require('../../lib/ipfs-import') const { contextMenuViewOnGateway, contextMenuCopyAddressAtPublicGw, @@ -35,26 +36,22 @@ function contextActions ({ currentTabPermalink = notReady, ipfsNodeType, isIpfsContext, - isPinning, - isUnPinning, - isPinned, isIpfsOnline, isApiAvailable, onToggleSiteIntegrations, onViewOnGateway, onCopy, - onPin, - onUnPin + importDir, + onFilesCpImport }) { const activeCidResolver = active && isIpfsOnline && isApiAvailable && currentTabCid - const activePinControls = active && isIpfsOnline && isApiAvailable + const activeFilesCpImport = active && isIpfsOnline && isApiAvailable && !ipfsNodeType.startsWith('embedded') const isMutable = currentTabContentPath.startsWith('/ipns/') const activeViewOnGateway = (currentTab) => { if (!currentTab) return false const { url } = currentTab return !(url.startsWith('ip') || sameGateway(url, gwURLString) || sameGateway(url, pubGwURLString)) } - const renderIpfsContextItems = () => { if (!isIpfsContext) return return html`

@@ -100,11 +97,11 @@ function contextActions ({ onClick: () => onCopy(contextMenuCopyRawCid) })} ${navItem({ - text: browser.i18n.getMessage('panel_pinCurrentIpfsAddress'), - title: browser.i18n.getMessage('panel_pinCurrentIpfsAddressTooltip'), - disabled: !activePinControls, - switchValue: (isPinned || isPinning) && !isUnPinning, - onClick: isPinned ? onUnPin : onPin + text: browser.i18n.getMessage('panel_importCurrentIpfsAddress'), + title: browser.i18n.getMessage('panel_importCurrentIpfsAddressTooltip'), + helperText: formatImportDirectory(importDir), + disabled: !activeFilesCpImport, + onClick: onFilesCpImport })}
` diff --git a/add-on/src/popup/browser-action/page.js b/add-on/src/popup/browser-action/page.js index 4b8a46f89..fac5f81d2 100644 --- a/add-on/src/popup/browser-action/page.js +++ b/add-on/src/popup/browser-action/page.js @@ -12,8 +12,7 @@ const tools = require('./tools') module.exports = function browserActionPage (state, emit) { const onViewOnGateway = () => emit('viewOnGateway') const onCopy = (copyAction) => emit('copy', copyAction) - const onPin = () => emit('pin') - const onUnPin = () => emit('unPin') + const onFilesCpImport = () => emit('filesCpImport') const onQuickImport = () => emit('quickImport') const onOpenWebUi = () => emit('openWebUi', '/') @@ -25,7 +24,7 @@ module.exports = function browserActionPage (state, emit) { const onToggleActive = () => emit('toggleActive') const headerProps = Object.assign({ onToggleActive, onOpenPrefs, onOpenReleaseNotes, onOpenWelcomePage }, state) - const activeTabActionsProps = Object.assign({ onViewOnGateway, onToggleSiteIntegrations, onCopy, onPin, onUnPin }, state) + const activeTabActionsProps = Object.assign({ onViewOnGateway, onToggleSiteIntegrations, onCopy, onFilesCpImport }, state) const opsProps = Object.assign({ onQuickImport, onOpenWebUi, onToggleGlobalRedirect }, state) return html` diff --git a/add-on/src/popup/browser-action/store.js b/add-on/src/popup/browser-action/store.js index fe7bf9c9b..b019a88c3 100644 --- a/add-on/src/popup/browser-action/store.js +++ b/add-on/src/popup/browser-action/store.js @@ -3,8 +3,8 @@ const browser = require('webextension-polyfill') const isIPFS = require('is-ipfs') -const all = require('it-all') -const { trimHashAndSearch, ipfsContentPath } = require('../../lib/ipfs-path') +const { browserActionFilesCpImportCurrentTab } = require('../../lib/ipfs-import') +const { ipfsContentPath } = require('../../lib/ipfs-path') const { welcomePage, optionsPage } = require('../../lib/constants') const { contextMenuViewOnGateway, contextMenuCopyAddressAtPublicGw, contextMenuCopyPermalink, contextMenuCopyRawCid, contextMenuCopyCanonicalAddress, contextMenuCopyCidAddress } = require('../../lib/context-menus') @@ -17,9 +17,6 @@ module.exports = (state, emitter) => { // UI contexts isIpfsContext: false, // Active Tab represents IPFS resource isRedirectContext: false, // Active Tab or its subresources could be redirected - isPinning: false, - isUnPinning: false, - isPinned: false, // IPFS details ipfsNodeType: 'external', isIpfsOnline: false, @@ -51,12 +48,6 @@ module.exports = (state, emitter) => { console.log('In browser action, received message from background:', message) await updateBrowserActionState(status) emitter.emit('render') - if (status.isIpfsContext) { - // calculating pageActions states is expensive (especially pin-related checks) - // we update them in separate step to keep UI snappy - await updatePageActionsState(status) - emitter.emit('render') - } } }) // fix for https://github.com/ipfs-shipyard/ipfs-companion/issues/318 @@ -92,53 +83,13 @@ module.exports = (state, emitter) => { window.close() }) - emitter.on('pin', async function pinCurrentResource () { - state.isPinning = true - emitter.emit('render') - - try { - const ipfs = await getIpfsApi() - const currentPath = await resolveToPinPath(ipfs, state.currentTab.url) - const pinResult = await ipfs.pin.add(currentPath, { recursive: true }) - console.log('ipfs.pin.add result', pinResult) - state.isPinned = true - notify('notify_pinnedIpfsResourceTitle', currentPath) - } catch (error) { - handlePinError('notify_pinErrorTitle', error) - } - state.isPinning = false - emitter.emit('render') - }) - - emitter.on('unPin', async function unPinCurrentResource () { - state.isUnPinning = true - emitter.emit('render') - - try { - const ipfs = await getIpfsApi() - const currentPath = await resolveToPinPath(ipfs, state.currentTab.url) - const result = await ipfs.pin.rm(currentPath, { recursive: true }) - state.isPinned = false - console.log('ipfs.pin.rm result', result) - notify('notify_unpinnedIpfsResourceTitle', currentPath) - } catch (error) { - handlePinError('notify_unpinErrorTitle', error) - } - state.isUnPinning = false - emitter.emit('render') + emitter.on('filesCpImport', () => { + port.postMessage({ event: browserActionFilesCpImportCurrentTab }) + window.close() }) - async function handlePinError (errorMessageKey, error) { - console.error(browser.i18n.getMessage(errorMessageKey), error) - try { - notify(errorMessageKey, error.message) - } catch (notifyError) { - console.error('Unable to notify user about pin-related error', notifyError) - } - } - emitter.on('quickImport', () => { - browser.tabs.create({ url: browser.extension.getURL('dist/popup/quick-import.html') }) + browser.tabs.create({ url: browser.runtime.getURL('dist/popup/quick-import.html') }) window.close() }) @@ -188,7 +139,7 @@ module.exports = (state, emitter) => { .catch((err) => { console.error('runtime.openOptionsPage() failed, opening options page in tab instead.', err) // brave: fallback to opening options page as a tab. - browser.tabs.create({ url: browser.extension.getURL(optionsPage) }) + browser.tabs.create({ url: browser.runtime.getURL(optionsPage) }) }) }) @@ -267,28 +218,6 @@ module.exports = (state, emitter) => { emitter.emit('render') }) - async function updatePageActionsState (status) { - // browser.pageAction-specific items that can be rendered earlier (snappy UI) - requestAnimationFrame(async () => { - const tabId = state.currentTab ? { tabId: state.currentTab.id } : null - if (browser.pageAction && tabId && await browser.pageAction.isShown(tabId)) { - // Get title stored on page load so that valid transport is displayed - // even if user toggles between public/custom gateway after the load - state.pageActionTitle = await browser.pageAction.getTitle(tabId) - emitter.emit('render') - } - }) - - if (state.isIpfsContext) { - // IPFS contexts require access to ipfs API object from background page - // Note: access to background page will be denied in Private Browsing mode - const ipfs = await getIpfsApi() - // There is no point in displaying actions that require API interaction if API is down - const apiIsUp = ipfs && status && status.peerCount >= 0 - if (apiIsUp) await updatePinnedState(ipfs, status) - } - } - async function updateBrowserActionState (status) { if (status) { // Copy all attributes @@ -314,50 +243,8 @@ module.exports = (state, emitter) => { state.isRedirectContext = false } } - - async function updatePinnedState (ipfs, status) { - // skip update if there is an ongoing pin or unpin - if (state.isPinning || state.isUnPinning) return - try { - const currentPath = await resolveToPinPath(ipfs, status.currentTab.url) - const response = await all(ipfs.pin.ls({ paths: [currentPath], type: 'recursive' })) - console.log(`positive ipfs.pin.ls for ${currentPath}: ${JSON.stringify(response)}`) - state.isPinned = true - } catch (error) { - if (/is not pinned/.test(error.message)) { - console.log(`negative ipfs.pin.ls: ${error} (${JSON.stringify(error)})`) - } else { - console.error(`unexpected result of ipfs.pin.ls: ${error} (${JSON.stringify(error)})`) - } - state.isPinned = false - } - } - - function notify (title, message) { - // console.log('Sending notification (' + title + '): ' + message + ')') - return port.postMessage({ event: 'notification', title: title, message: message }) - } } function getBackgroundPage () { return browser.runtime.getBackgroundPage() } - -async function getIpfsApi () { - const bg = await getBackgroundPage() - return (bg && bg.ipfsCompanion) ? bg.ipfsCompanion.ipfs : null -} - -async function getIpfsPathValidator () { - const bg = await getBackgroundPage() - return (bg && bg.ipfsCompanion) ? bg.ipfsCompanion.ipfsPathValidator : null -} - -async function resolveToPinPath (ipfs, url) { - // Prior issues: - // https://github.com/ipfs-shipyard/ipfs-companion/issues/567 - // https://github.com/ipfs/ipfs-companion/issues/303 - const pathValidator = await getIpfsPathValidator() - const pinPath = trimHashAndSearch(await pathValidator.resolveToImmutableIpfsPath(url)) - return pinPath -} diff --git a/add-on/src/popup/page-action/header.js b/add-on/src/popup/page-action/header.js deleted file mode 100644 index a660a3c92..000000000 --- a/add-on/src/popup/page-action/header.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict' -/* eslint-env browser, webextensions */ - -const html = require('choo/html') -const logo = require('../logo') - -module.exports = function header ({ isIpfsContext, pageActionTitle }) { - if (!isIpfsContext) return null - return html` -
-

- ${logo({ - size: 20, - path: '../../../icons', - ipfsNodeType: 'external', - isIpfsOnline: true, - heartbeat: false - })} ${pageActionTitle || '…'} -

-
- ` -} diff --git a/add-on/src/popup/page-action/index.html b/add-on/src/popup/page-action/index.html deleted file mode 100644 index 2b259d567..000000000 --- a/add-on/src/popup/page-action/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - -
- - - - diff --git a/add-on/src/popup/page-action/index.js b/add-on/src/popup/page-action/index.js deleted file mode 100644 index 9108badbe..000000000 --- a/add-on/src/popup/page-action/index.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict' -/* eslint-env browser, webextensions */ - -const choo = require('choo') -const pageActionPage = require('./page') -const browserActionStore = require('../browser-action/store') - -const app = choo() - -// Reuse store to setup state defaults and event listeners for mutations -app.use(browserActionStore) - -// Register our single route -app.route('*', pageActionPage) - -// Start the application and render it to the given querySelector -app.mount('#root') diff --git a/add-on/src/popup/page-action/page.js b/add-on/src/popup/page-action/page.js deleted file mode 100644 index 4c668dae2..000000000 --- a/add-on/src/popup/page-action/page.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict' -/* eslint-env browser, webextensions */ - -const html = require('choo/html') -const header = require('./header') -const { contextActions } = require('../browser-action/context-actions') - -// Render the page-action page: -// Passed current app `state` from the store and `emit`, a function to create -// events, allowing views to signal back to the store that something happened. -module.exports = function pageActionPage (state, emit) { - const onViewOnGateway = () => emit('viewOnGateway') - const onCopy = (copyAction) => emit('copy', copyAction) - const onPin = () => emit('pin') - const onUnPin = () => emit('unPin') - const onToggleSiteIntegrations = () => emit('toggleSiteIntegrations') - - const contextActionsProps = Object.assign({ onViewOnGateway, onCopy, onPin, onUnPin, onToggleSiteIntegrations }, state) - - // Instant init: page-action is shown only in ipfsContext - contextActionsProps.isIpfsContext = true - - return html` -
- ${header(contextActionsProps)} - ${contextActions(contextActionsProps)} -
- ` -} diff --git a/webpack.config.js b/webpack.config.js index 9f04721b9..668d67ae3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -131,7 +131,6 @@ const uiConfig = merge(commonConfig, { name: 'ui', entry: { browserAction: './add-on/src/popup/browser-action/index.js', - pageAction: './add-on/src/popup/page-action/index.js', importPage: './add-on/src/popup/quick-import.js', optionsPage: './add-on/src/options/options.js', // TODO: remove or fix (window.ipfs) proxyAclManagerPage: './add-on/src/pages/proxy-acl/index.js',