From a10ce15950b3caf1b4e10fec5d728e8011f8f829 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 8 Jul 2017 21:46:20 +0200 Subject: [PATCH] refactor(browserAction): rewrite update logic This replaces alarm-based updates with message-based signalling based on `browser.runtime.connect` API Chrome does not support Alarm intervals smaller than one minute, so we will replace all of them with this new type of signalling. This is yet another piece of #218 (and #259) efforts. This commit also closes #243 by hiding broken actions in 'incognito' mode. --- add-on/src/lib/common.js | 54 +++++++++++- add-on/src/popup/browser-action.js | 133 +++++++++++++++-------------- 2 files changed, 119 insertions(+), 68 deletions(-) diff --git a/add-on/src/lib/common.js b/add-on/src/lib/common.js index d7d6b74cd..0a0a97fee 100644 --- a/add-on/src/lib/common.js +++ b/add-on/src/lib/common.js @@ -221,20 +221,70 @@ function readDnslinkTxtRecordFromApi (fqdn) { }) } +// PORTS +// =================================================================== +// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/runtime/connect +// Make a connection between different contexts inside the add-on, +// e.g. signalling between browser action popup and background page that works +// in everywhere, even in private contexts (https://github.com/ipfs/ipfs-companion/issues/243) + +const browserActionPortName = 'browser-action-port' +var browserActionPort + +browser.runtime.onConnect.addListener(port => { + // console.log('onConnect', port) + if (port.name === browserActionPortName) { + browserActionPort = port + browserActionPort.onMessage.addListener(handleMessageFromBrowserAction) + browserActionPort.onDisconnect.addListener(() => { browserActionPort = null }) + sendStatusUpdateToBrowserAction() + } +}) + +function handleMessageFromBrowserAction (message) { + // console.log('In background script, received message from browser action', message) + if (message.event === 'notification') { + notify(message.title, message.message) + } +} + +async function sendStatusUpdateToBrowserAction () { + if (browserActionPort) { + const info = { + peerCount: state.peerCount, + currentTab: await browser.tabs.query({active: true, currentWindow: true}).then(tabs => tabs[0]) + } + try { + let v = await ipfs.version() + if (v) { + info.gatewayVersion = v.commit ? v.version + '/' + v.commit : v.version + } + } catch (error) { + info.gatewayVersion = null + } + if (info.currentTab) { + info.ipfsPageActionsContext = isIpfsPageActionsContext(info.currentTab.url) + } + browserActionPort.postMessage({statusUpdate: info}) + } +} + // ALARMS // =================================================================== +// TODO: Remove use of alarms for signal passing, use PORTS instead const ipfsApiStatusUpdateAlarm = 'ipfs-api-status-update' const ipfsRedirectUpdateAlarm = 'ipfs-redirect-update' -function handleAlarm (alarm) { +async function handleAlarm (alarm) { // avoid making expensive updates when IDLE if (alarm.name === ipfsApiStatusUpdateAlarm) { - getSwarmPeerCount() + await getSwarmPeerCount() .then(updatePeerCountState) .then(updateAutomaticModeRedirectState) .then(updateBrowserActionBadge) .then(updateContextMenus) + sendStatusUpdateToBrowserAction() } } diff --git a/add-on/src/popup/browser-action.js b/add-on/src/popup/browser-action.js index aa73fa298..1f98f6a33 100644 --- a/add-on/src/popup/browser-action.js +++ b/add-on/src/popup/browser-action.js @@ -18,6 +18,9 @@ const ipfsIconOn = '../../icons/ipfs-logo-on.svg' const ipfsIconOff = '../../icons/ipfs-logo-off.svg' const offline = 'offline' +var port +var state + function resolv (element) { // lookup DOM if element is just a string ID if (Object.prototype.toString.call(element) === '[object String]') { @@ -49,28 +52,24 @@ function getBackgroundPage () { return browser.runtime.getBackgroundPage() } -function getCurrentTab () { - return browser.tabs.query({active: true, currentWindow: true}).then(tabs => tabs[0]) +function notify (title, message) { + port.postMessage({event: 'notification', title: title, message: message}) } // Ipfs Context Page Actions // =================================================================== async function copyCurrentPublicGwAddress () { - const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - const publicGwAddress = new URL(currentTab.url.replace(bg.state.gwURLString, 'https://ipfs.io/')).toString() + const publicGwAddress = new URL(state.currentTab.url.replace(state.gwURLString, 'https://ipfs.io/')).toString() copyTextToClipboard(publicGwAddress) - bg.notify('notify_copiedPublicURLTitle', publicGwAddress) + notify('notify_copiedPublicURLTitle', publicGwAddress) window.close() } async function copyCurrentCanonicalAddress () { - const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - const rawIpfsAddress = currentTab.url.replace(/^.+(\/ip(f|n)s\/.+)/, '$1') + const rawIpfsAddress = state.currentTab.url.replace(/^.+(\/ip(f|n)s\/.+)/, '$1') copyTextToClipboard(rawIpfsAddress) - bg.notify('notify_copiedCanonicalAddressTitle', rawIpfsAddress) + notify('notify_copiedCanonicalAddressTitle', rawIpfsAddress) window.close() } @@ -89,11 +88,10 @@ async function pinCurrentResource () { deactivatePinButton() try { const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - const currentPath = await resolveToIPFS(new URL(currentTab.url).pathname) + const currentPath = await resolveToIPFS(new URL(state.currentTab.url).pathname) const pinResult = await bg.ipfs.pin.add(currentPath, { recursive: true }) console.log('ipfs.pin.add result', pinResult) - bg.notify('notify_pinnedIpfsResourceTitle', currentPath) + notify('notify_pinnedIpfsResourceTitle', currentPath) } catch (error) { handlePinError('notify_pinErrorTitle', error) } @@ -104,11 +102,10 @@ async function unpinCurrentResource () { deactivatePinButton() try { const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - const currentPath = await resolveToIPFS(new URL(currentTab.url).pathname) + const currentPath = await resolveToIPFS(new URL(state.currentTab.url).pathname) const result = await bg.ipfs.pin.rm(currentPath, {recursive: true}) console.log('ipfs.pin.rm result', result) - bg.notify('notify_unpinnedIpfsResourceTitle', currentPath) + notify('notify_unpinnedIpfsResourceTitle', currentPath) } catch (error) { handlePinError('notify_unpinErrorTitle', error) } @@ -140,8 +137,7 @@ async function handlePinError (errorMessageKey, error) { console.error(browser.i18n.getMessage(errorMessageKey), error) deactivatePinButton() try { - const bg = await getBackgroundPage() - bg.notify(errorMessageKey, error.message) + notify(errorMessageKey, error.message) } catch (error) { console.error('unable to access background page', error) } @@ -159,8 +155,7 @@ async function resolveToIPFS (path) { async function activatePinButton () { try { const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - const currentPath = await resolveToIPFS(new URL(currentTab.url).pathname) + const currentPath = await resolveToIPFS(new URL(state.currentTab.url).pathname) const response = await bg.ipfs.pin.ls(currentPath, {quiet: true}) console.log(`positive ipfs.pin.ls for ${currentPath}: ${JSON.stringify(response)}`) activateUnpinning() @@ -176,21 +171,27 @@ async function activatePinButton () { } async function updatePageActions () { - // console.log('running updatePageActions()') - try { - const bg = await getBackgroundPage() - const currentTab = await getCurrentTab() - if (bg.isIpfsPageActionsContext(currentTab.url)) { - deactivatePinButton() - show(ipfsContextActions) - copyPublicGwAddressButton.onclick = copyCurrentPublicGwAddress - copyIpfsAddressButton.onclick = copyCurrentCanonicalAddress + // IPFS contexts require access to background page + // which is denied in Private Browsing mode + const bg = await getBackgroundPage() + + // Check if current page is an IPFS one + const ipfsContext = bg && state && state.ipfsPageActionsContext + + // There is no point in displaying actions that require API interaction if API is down + const apiIsUp = state && state.peerCount >= 0 + + if (ipfsContext) { + show(ipfsContextActions) + copyPublicGwAddressButton.onclick = copyCurrentPublicGwAddress + copyIpfsAddressButton.onclick = copyCurrentCanonicalAddress + if (apiIsUp) { activatePinButton() } else { - hide(ipfsContextActions) + deactivatePinButton() } - } catch (error) { - console.error(`Error while setting up pageAction: ${error}`) + } else { + hide(ipfsContextActions) } } @@ -223,6 +224,7 @@ openPreferences.onclick = () => { } async function updateBrowserActionPopup () { + updatePageActions() // update redirect status const options = await browser.storage.local.get() try { @@ -247,46 +249,45 @@ async function updateBrowserActionPopup () { set('gateway-address-val', '???') } - try { - const background = await browser.runtime.getBackgroundPage() - if (background.ipfs) { - // update swarm peer count - try { - const peerCount = background.state.peerCount - set('swarm-peers-val', peerCount < 0 ? offline : peerCount) - ipfsIcon.src = peerCount > 0 ? ipfsIconOn : ipfsIconOff - if (peerCount > 0) { // API is online & there are peers - show('quick-upload') - } else { - hide('quick-upload') - } - if (peerCount < 0) { // API is offline - hide('open-webui') - } else { - show('open-webui') - } - } catch (error) { - console.error(`Unable update peer count due to ${error}`) - } - // update gateway version - try { - const v = await background.ipfs.version() - set('gateway-version-val', (v.commit ? v.version + '/' + v.commit : v.version)) - } catch (error) { - set('gateway-version-val', offline) - } + if (state) { + // update swarm peer count + const peerCount = state.peerCount + set('swarm-peers-val', peerCount < 0 ? offline : peerCount) + ipfsIcon.src = peerCount > 0 ? ipfsIconOn : ipfsIconOff + if (peerCount > 0) { // API is online & there are peers + show('quick-upload') + } else { + hide('quick-upload') } - } catch (error) { - console.error(`Error while accessing background page: ${error}`) + if (peerCount < 0) { // API is offline + hide('open-webui') + } else { + show('open-webui') + } + // update gateway version + set('gateway-version-val', state.gatewayVersion ? state.gatewayVersion : offline) } } // hide things that cause ugly reflow if removed later +deactivatePinButton() hide(ipfsContextActions) hide('quick-upload') hide('open-webui') -// listen to any changes and update diagnostics -browser.alarms.onAlarm.addListener(updateBrowserActionPopup) -document.addEventListener('DOMContentLoaded', updatePageActions) -document.addEventListener('DOMContentLoaded', updateBrowserActionPopup) +function onDOMContentLoaded () { + // set up initial layout that will remain if there is no peers + updateBrowserActionPopup() + // initialize connection to the background script which will trigger UI updates + port = browser.runtime.connect({name: 'browser-action-port'}) + port.onMessage.addListener((message) => { + if (message.statusUpdate) { + // console.log('In browser action, received message from background:', message) + state = message.statusUpdate + updateBrowserActionPopup() + } + }) +} + +// init +document.addEventListener('DOMContentLoaded', onDOMContentLoaded)