diff --git a/add-on/_locales/en/messages.json b/add-on/_locales/en/messages.json index f186046dc..6e94caaf0 100644 --- a/add-on/_locales/en/messages.json +++ b/add-on/_locales/en/messages.json @@ -277,15 +277,19 @@ }, "option_ipfsNodeType_external_description": { "message": "Set to \"External\" to connect to a local node using the HTTP API.", - "description": "An option description on the Preferences screen (option_ipfsNodeType_description)" + "description": "An option description on the Preferences screen (option_ipfsNodeType_external_description)" }, "option_ipfsNodeType_embedded_description": { "message": "Set to \"Embedded\" to run a js-ipfs node directly in your browser. (Click \"Read more\" to learn about the limitations of this experimental feature.)", - "description": "An option description on the Preferences screen (option_ipfsNodeType_description)" + "description": "An option description on the Preferences screen (option_ipfsNodeType_embedded_description)" }, "option_ipfsNodeType_embedded_chromesockets_description": { - "message": "Embedded with Chrome Sockets: run js-ipfs node in your browser with access to chrome.sockets APIs (details under the link below)", - "description": "An option description on the Preferences screen (option_ipfsNodeType_description)" + "message": "\"Embedded + chrome.sockets\" is no longer supported by Chromium. If you are using this option, please migrate to a different node type ASAP.", + "description": "An option description on the Preferences screen (option_ipfsNodeType_embedded_chromesockets_description)" + }, + "option_ipfsNodeType_brave_description": { + "message": "Set to \"Provided by Brave\" to leverage the Brave browser's native IPFS support.", + "description": "An option description on the Preferences screen (option_ipfsNodeType_brave_description)" }, "option_ipfsNodeConfig_title": { "message": "IPFS Node Config", @@ -307,6 +311,18 @@ "message": "Embedded + chrome.sockets", "description": "An option on the Preferences screen (option_ipfsNodeType_embedded_chromesockets)" }, + "option_ipfsNodeType_brave": { + "message": "Provided by Brave", + "description": "An option on the Preferences screen (option_ipfsNodeType_brave)" + }, + "option_hint_url": { + "message": "Enter URL without any sub-path", + "description": "An option description on the Preferences screen (option_hint_url)" + }, + "option_hint_readonly": { + "message": "This value is read-only", + "description": "An option description on the Preferences screen (option_hint_readonly)" + }, "option_header_gateways": { "message": "Gateways", "description": "A section header on the Preferences screen (option_header_gateways)" @@ -435,6 +451,10 @@ "message": "experimental", "description": "Warning label added to experimental options on the Preferences screen (option_experimental)" }, + "option_deprecated": { + "message": "deprecated", + "description": "Warning label added to deprecated options on the Preferences screen (option_deprecated)" + }, "option_experiments_warning": { "message": "Warning! These experimental features are works in progress and are subject to changes in availability and functionality.", "description": "Warning about Experiments section on the Preferences screen (option_experiments_warning)" @@ -711,6 +731,14 @@ "message": "IPFS is not running", "description": "Install steps title (page_landingWelcome_installSteps_notRunning_title)" }, + "page_landingWelcome_installSteps_brave_title": { + "message": "Brave users", + "description": "Install steps title (page_landingWelcome_installSteps_brave_title)" + }, + "page_landingWelcome_installSteps_brave_install": { + "message": "You can run IPFS directly in your browser — no need for IPFS Desktop or the command line. Open <0>Companion Preferences and set the IPFS node type to “Provided by Brave”.", + "description": "Install steps copy (page_landingWelcome_installSteps_brave_install)" + }, "page_landingWelcome_installSteps_desktop_title": { "message": "IPFS Desktop users", "description": "Install steps title (page_landingWelcome_installSteps_desktop_title)" diff --git a/add-on/src/landing-pages/welcome/page.js b/add-on/src/landing-pages/welcome/page.js index 7d4db4bc8..fb25187b2 100644 --- a/add-on/src/landing-pages/welcome/page.js +++ b/add-on/src/landing-pages/welcome/page.js @@ -1,9 +1,13 @@ 'use strict' +const browser = require('webextension-polyfill') const html = require('choo/html') const logo = require('../../popup/logo') const { renderTranslatedLinks, renderTranslatedSpans } = require('../../utils/i18n') +// Brave detection +const { brave } = require('../../../src/lib/ipfs-client/brave') + // Assets const libp2pLogo = '../../../images/libp2p.svg' const multiformatsLogo = '../../../images/multiformats.svg' @@ -95,12 +99,18 @@ const renderInstallSteps = (i18n, isIpfsOnline) => { ` + const optionsUrl = browser.extension.getURL('dist/options/options.html') return html`
${nodeOffSvg()}

${i18n.getMessage('page_landingWelcome_installSteps_notRunning_title')}

+ ${brave + ? html` +

${i18n.getMessage('page_landingWelcome_installSteps_brave_title')}

+

${renderTranslatedLinks('page_landingWelcome_installSteps_brave_install', [optionsUrl], `target="_blank" class="${anchorClass}"`)}

` + : null}

${i18n.getMessage('page_landingWelcome_installSteps_desktop_title')}

${renderTranslatedLinks('page_landingWelcome_installSteps_desktop_install', ['https://github.com/ipfs-shipyard/ipfs-desktop#ipfs-desktop'], `target="_blank" class="${anchorClass}"`)}

${i18n.getMessage('page_landingWelcome_installSteps_cli_title')}

diff --git a/add-on/src/lib/ipfs-client/brave.js b/add-on/src/lib/ipfs-client/brave.js new file mode 100644 index 000000000..1651118d8 --- /dev/null +++ b/add-on/src/lib/ipfs-client/brave.js @@ -0,0 +1,229 @@ +'use strict' +/* eslint-env browser, webextensions */ + +const debug = require('debug') +const log = debug('ipfs-companion:client:brave') +log.error = debug('ipfs-companion:client:brave:error') + +const external = require('./external') +const toUri = require('multiaddr-to-uri') +const pWaitFor = require('p-wait-for') + +// increased interval to decrease impact of IPFS service process spawns +const waitFor = (f, t) => pWaitFor(f, { interval: 250, timeout: t || Infinity }) + +exports.init = async function (browser, opts) { + log('ensuring Brave Settings are correct') + const { brave } = exports + await initBraveSettings(browser, brave) + log('delegating API client init to "external" backend pointed at node managed by Brave') + return external.init(browser, opts) +} + +exports.destroy = async function (browser) { + log('shuting down node managed by Brave') + const { brave } = exports + const method = await brave.getResolveMethodType() + if (method === 'local') { + // shut down local node when this backend is not active + log('waiting for brave.shutdown() to finish') + await waitFor(() => brave.shutdown()) + log('brave.shutdown() done') + } + log('delegating API client destroy to "external" backend pointed at node managed by Brave') + return external.destroy(browser) +} + +// ---------------- Brave-specifics ------------------- + +// ipfs:// URI that will be used for triggering the "Enable IPFS" dropbar in Brave +const braveIpfsUriTrigger = 'ipfs://bafkreigxbf77se2an2u6hmg2kxxbhmenetc7dzvkd3rl4m2orlobjvqcqq' + +// Settings screen in Brave where user can manage IPFS support +const braveSettingsPage = 'brave://settings/extensions' + +// Diagnostic page for manually starting/stopping Brave's node +// const braveIpfsDiagnosticPage = 'brave://ipfs' + +// ipfsNodeType for this backend +exports.braveNodeType = 'external:brave' + +// wrapper for chrome.ipfs.* that gets us closer to ergonomics of promise-based browser.* +exports.brave = hasBraveChromeIpfs() + ? Object.freeze({ + // This is the main check - returns true only in Brave and only when + // feature flag is enabled brave://flags and can be used for high level UI + // decisions such as showing custom node type on Preferences + getIPFSEnabled: async () => + Boolean(await promisifyBraveCheck(chrome.ipfs.getIPFSEnabled)), + + // Obtains a string representation of the resolve method + // method is one of the following strings: + // "ask" uses a gateway but also prompts them to install a local node + // "gateway" uses a gateway but also prompts them to install a local node + // "local" uses a gateway but also prompts them to install a local node + // "disabled" disabled by the user + // "undefined" everything else (IPFS feature flag is not enabled, error etc) + getResolveMethodType: async () => + String(await promisifyBraveCheck(chrome.ipfs.getResolveMethodType)), + + // Obtains the config contents of the local IPFS node + // Returns undefined if missing for any reason + getConfig: async () => + await promisifyBraveCheck(chrome.ipfs.getConfig), + + // Returns true if binary is present + getExecutableAvailable: async () => + Boolean(await promisifyBraveCheck(chrome.ipfs.getExecutableAvailable)), + + // Attempts to start the daemon and returns true if finished + launch: async () => + Boolean(await promisifyBraveCheck(chrome.ipfs.launch)), + + // Attempts to stop the daemon and returns true if finished + shutdown: async () => + Boolean(await promisifyBraveCheck(chrome.ipfs.shutdown)) + }) + : undefined + +// Detect chrome.ipfs.* APIs provided by Brave to IPFS Companion +function hasBraveChromeIpfs () { + return typeof chrome === 'object' && + typeof chrome.ipfs === 'object' && + typeof chrome.ipfs.getIPFSEnabled === 'function' && + typeof chrome.ipfs.getResolveMethodType === 'function' && + typeof chrome.ipfs.launch === 'function' && + typeof chrome.ipfs.shutdown === 'function' && + typeof chrome.ipfs.getExecutableAvailable === 'function' && + typeof chrome.ipfs.getConfig === 'function' +} + +// Reads value via chrome.ipfs and returns it. +// Never throws: missing/error is returned as undefined. +const promisifyBraveCheck = (fn) => { + return new Promise((resolve, reject) => { + try { + if (fn === chrome.ipfs.getConfig) { + fn((ok, config) => { + if (ok && config) return resolve(JSON.parse(config)) + return resolve(undefined) + }) + } + fn(val => resolve(val)) + } catch (e) { + log.error('unexpected error during promisifyBraveCheck', e) + reject(e) + } + }) +} + +// We preserve original "external" config, so user can switch between +// nodes provided by Brave and IPFS Desktop without the need for +// manually editing the address of IPFS API endpoint. + +exports.useBraveEndpoint = async function (browser) { + const { brave } = exports + const braveConfig = await brave.getConfig() + if (typeof braveConfig === 'undefined') { + log.error('useBraveEndpoint: IPFS_PATH/config is missing, unable to use endpoint from Brave at this time, will try later') + return + } + + const { + externalNodeConfig: oldExternalNodeConfig, + customGatewayUrl: oldGatewayUrl, + ipfsApiUrl: oldApiUrl + } = (await browser.storage.local.get(['customGatewayUrl', 'ipfsApiUrl', 'externalNodeConfig'])) + const braveApiUrl = addrs2url(braveConfig.Addresses.API) + const braveGatewayUrl = addrs2url(braveConfig.Addresses.Gateway) + + if (braveApiUrl === oldApiUrl && braveGatewayUrl === oldGatewayUrl) { + log('useBraveEndpoint: ok') + return + } + + log(`useBraveEndpoint: setting api=${braveApiUrl}, gw=${braveGatewayUrl} (before: api=${oldApiUrl}, gw=${oldGatewayUrl})`) + await browser.storage.local.set({ + ipfsApiUrl: braveApiUrl, + customGatewayUrl: braveGatewayUrl, + externalNodeConfig: oldExternalNodeConfig || [oldGatewayUrl, oldApiUrl] + }) +} + +exports.releaseBraveEndpoint = async function (browser) { + const [oldGatewayUrl, oldApiUrl] = (await browser.storage.local.get('externalNodeConfig')).externalNodeConfig + log(`releaseBraveEndpoint: restoring api=${oldApiUrl}, gw=${oldGatewayUrl}`) + await browser.storage.local.set({ + ipfsApiUrl: oldApiUrl, + customGatewayUrl: oldGatewayUrl, + externalNodeConfig: null + }) +} + +// Addresses in go-ipfs config can be a String or array of strings with multiaddrs +function addrs2url (addr) { + if (Array.isArray(addr)) { + addr = addr[0] + } + return toUri(addr, { assumeHttp: true }) +} + +async function initBraveSettings (browser, brave) { + let showState = () => {} + let tabId + let method = await brave.getResolveMethodType() + log(`brave.resolveMethodType is '${method}'`) + + if (method === 'ask') { + // Trigger the dropbar with "Enable IPFS" button by opening ipfs:// URI in a new tab. + // The trigger is a HTML page with some text to make onboarding easier. + tabId = (await browser.tabs.create({ url: braveIpfsUriTrigger })).id + + // Reuse the tab for state updates (or create a new one if user closes it) + // Caveat: we inject JS as we can't use tab.update during the init of local gateway + // because Brave will try to use it and fail as it is not ready yet :-)) + showState = async (s) => { + try { + await browser.tabs.executeScript(tabId, { code: `window.location.hash = '#${s}';` }) + } catch (e) { // noop, just log, don't break if user closed the tab etc + log.error('error while showState', e) + } + } + showState('ask') + + // IPFS Companion is unable to change Brave settings, + // all we can do is to poll chrome.ipfs.* and detect when user made a decision + log('waiting for user to make a decision how IPFS resources should be resolved') + await waitFor(async () => { + method = await brave.getResolveMethodType() + return method && method !== 'ask' + }) + log(`user set resolveMethodType to '${method}'`) + + if (method === 'local') { + log('waiting while Brave downloads IPFS executable..') + showState('download') + await waitFor(() => brave.getExecutableAvailable()) + + log('waiting while Brave creates repo and config via ipfs init..') + await showState('init') + await waitFor(async () => typeof (await brave.getConfig()) !== 'undefined') + } + } + + if (method !== 'local') { + await showState('ask') + await browser.tabs.create({ url: braveSettingsPage }) + throw new Error('"Method to resolve IPFS resources" in Brave settings should be "Local node"') + } + + // ensure local node is started + log('waiting while brave.launch() starts ipfs daemon..') + await showState('start') + await waitFor(() => brave.launch()) + log('brave.launch() finished') + await showState('done') + + // ensure Companion uses the endpoint provided by Brave + await exports.useBraveEndpoint(browser) +} diff --git a/add-on/src/lib/ipfs-client/embedded-chromesockets/index.js b/add-on/src/lib/ipfs-client/embedded-chromesockets/index.js index 03ca59f8a..8ae472b7a 100644 --- a/add-on/src/lib/ipfs-client/embedded-chromesockets/index.js +++ b/add-on/src/lib/ipfs-client/embedded-chromesockets/index.js @@ -23,7 +23,7 @@ const { buildConfig, syncConfig } = require('./config') let node let nodeHttpApi -exports.init = async function init (opts) { +exports.init = async function init (browser, opts) { log('init embedded:chromesockets') const ipfsOpts = await buildConfig(opts, log) @@ -40,7 +40,7 @@ exports.init = async function init (opts) { return node } -exports.destroy = async function () { +exports.destroy = async function (browser) { log('destroy: embedded:chromesockets') if (nodeHttpApi) { diff --git a/add-on/src/lib/ipfs-client/embedded.js b/add-on/src/lib/ipfs-client/embedded.js index cf786f47d..b8b45caf3 100644 --- a/add-on/src/lib/ipfs-client/embedded.js +++ b/add-on/src/lib/ipfs-client/embedded.js @@ -10,7 +10,7 @@ const { optionDefaults } = require('../options') let node = null -exports.init = async function init (opts) { +exports.init = async function init (browser, opts) { log('init') const defaultOpts = JSON.parse(optionDefaults.ipfsNodeConfig) const userOpts = JSON.parse(opts.ipfsNodeConfig) @@ -35,7 +35,7 @@ exports.init = async function init (opts) { return node } -exports.destroy = async function () { +exports.destroy = async function (browser) { log('destroy') if (!node) return diff --git a/add-on/src/lib/ipfs-client/external.js b/add-on/src/lib/ipfs-client/external.js index f52d94854..7571cba34 100644 --- a/add-on/src/lib/ipfs-client/external.js +++ b/add-on/src/lib/ipfs-client/external.js @@ -7,7 +7,7 @@ log.error = debug('ipfs-companion:client:external:error') const httpClient = require('ipfs-http-client') -exports.init = async function (opts) { +exports.init = async function (browser, opts) { log(`init with IPFS API at ${opts.apiURLString}`) const clientConfig = opts.apiURLString // https://github.com/ipfs/js-ipfs/tree/master/packages/ipfs-http-client#importing-the-module-and-usage @@ -15,6 +15,6 @@ exports.init = async function (opts) { return api } -exports.destroy = async function () { +exports.destroy = async function (browser) { log('destroy') } diff --git a/add-on/src/lib/ipfs-client/index.js b/add-on/src/lib/ipfs-client/index.js index 59359456a..c5d0d90cf 100644 --- a/add-on/src/lib/ipfs-client/index.js +++ b/add-on/src/lib/ipfs-client/index.js @@ -6,16 +6,16 @@ const debug = require('debug') const log = debug('ipfs-companion:client') log.error = debug('ipfs-companion:client:error') -const browser = require('webextension-polyfill') const external = require('./external') const embedded = require('./embedded') +const brave = require('./brave') const embeddedWithChromeSockets = require('./embedded-chromesockets') const { precache } = require('../precache') // ensure single client at all times, and no overlap between init and destroy let client -async function initIpfsClient (opts) { +async function initIpfsClient (browser, opts) { log('init ipfs client') if (client) return // await destroyIpfsClient() let backend @@ -26,25 +26,27 @@ async function initIpfsClient (opts) { case 'embedded:chromesockets': backend = embeddedWithChromeSockets break + case 'external:brave': + backend = brave + break case 'external': backend = external break default: throw new Error(`Unsupported ipfsNodeType: ${opts.ipfsNodeType}`) } - const instance = await backend.init(opts) - easeApiChanges(instance) - _reloadIpfsClientDependents(instance, opts) // async (API is present) + const instance = await backend.init(browser, opts) + _reloadIpfsClientDependents(browser, instance, opts) // async (API is present) client = backend return instance } -async function destroyIpfsClient () { +async function destroyIpfsClient (browser) { log('destroy ipfs client') if (!client) return try { - await client.destroy() - await _reloadIpfsClientDependents() // sync (API stopped working) + await client.destroy(browser) + await _reloadIpfsClientDependents(browser) // sync (API stopped working) } finally { client = null } @@ -56,7 +58,7 @@ function _isWebuiTab (url) { return bundled || ipns } -async function _reloadIpfsClientDependents (instance, opts) { +async function _reloadIpfsClientDependents (browser, instance, opts) { // online || offline if (browser.tabs && browser.tabs.query) { const tabs = await browser.tabs.query({}) @@ -77,11 +79,5 @@ async function _reloadIpfsClientDependents (instance, opts) { } } -// This enables use of dependencies without worrying if they already migrated to the new API. -function easeApiChanges (ipfs) { - // no-op: used in past, not used atm - // if (!ipfs) return -} - exports.initIpfsClient = initIpfsClient exports.destroyIpfsClient = destroyIpfsClient diff --git a/add-on/src/lib/ipfs-companion.js b/add-on/src/lib/ipfs-companion.js index 217dd7e51..952238344 100644 --- a/add-on/src/lib/ipfs-companion.js +++ b/add-on/src/lib/ipfs-companion.js @@ -16,6 +16,7 @@ const { createIpfsPathValidator, sameGateway } = require('./ipfs-path') 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 createNotifier = require('./notifier') const createCopier = require('./copier') @@ -59,7 +60,7 @@ module.exports = async function init () { if (state.active) { // It's ok for this to fail, node might be unavailable or mis-configured try { - ipfs = await initIpfsClient(state) + ipfs = await initIpfsClient(browser, state) } catch (err) { console.error('[ipfs-companion] Failed to init IPFS client', err) notify( @@ -619,9 +620,13 @@ module.exports = async function init () { shouldStopIpfsClient = !state.active break case 'ipfsNodeType': - // Switching between External and Embeedded HTTP Gateway in Brave is tricky. - // For now we remove user confusion by persisting and restoring the External config. - // TODO: refactor as a part of https://github.com/ipfs-shipyard/ipfs-companion/issues/491 + if (change.oldValue !== braveNodeType && change.newValue === braveNodeType) { + useBraveEndpoint(browser) + } else if (change.oldValue === braveNodeType && change.newValue !== braveNodeType) { + releaseBraveEndpoint(browser) + } + + // TODO: remove when go-ipfs in Brave ships if (change.oldValue === 'external' && change.newValue === 'embedded:chromesockets') { const oldGatewayUrl = (await browser.storage.local.get('customGatewayUrl')).customGatewayUrl const oldApiUrl = (await browser.storage.local.get('ipfsApiUrl')).ipfsApiUrl @@ -705,7 +710,7 @@ module.exports = async function init () { if ((state.active && shouldRestartIpfsClient) || shouldStopIpfsClient) { try { log('stoping ipfs client due to config changes', changes) - await destroyIpfsClient() + await destroyIpfsClient(browser) } catch (err) { console.error('[ipfs-companion] Failed to destroy IPFS client', err) notify('notify_stopIpfsNodeErrorTitle', err.message) @@ -717,7 +722,7 @@ module.exports = async function init () { try { log('starting ipfs client with the new config') - ipfs = await initIpfsClient(state) + ipfs = await initIpfsClient(browser, state) } catch (err) { console.error('[ipfs-companion] Failed to init IPFS client', err) notify( @@ -792,7 +797,7 @@ module.exports = async function init () { } if (ipfs) { - await destroyIpfsClient() + await destroyIpfsClient(browser) ipfs = null } } diff --git a/add-on/src/lib/options.js b/add-on/src/lib/options.js index 454c31c34..89c11a7c0 100644 --- a/add-on/src/lib/options.js +++ b/add-on/src/lib/options.js @@ -2,10 +2,9 @@ const isIP = require('is-ip') const isFQDN = require('is-fqdn') -const { hasChromeSocketsForTcp } = require('./runtime-checks') -// TODO: enable by default when embedded node is performant enough -const DEFAULT_TO_EMBEDDED_GATEWAY = false && hasChromeSocketsForTcp() +// TODO: remove code related to chrome.sockets +const DEFAULT_TO_EMBEDDED_GATEWAY = false exports.optionDefaults = Object.freeze({ active: true, // global ON/OFF switch, overrides everything else @@ -188,17 +187,6 @@ exports.migrateOptions = async (storage, debug) => { await storage.remove('dnslink') } - // ~ v2.8.x + Brave - // Upgrade js-ipfs to js-ipfs + chrome.sockets - const { ipfsNodeType } = await storage.get('ipfsNodeType') - if (ipfsNodeType === 'embedded' && hasChromeSocketsForTcp()) { - // migrating ipfsNodeType to embedded:chromesockets - await storage.set({ - ipfsNodeType: 'embedded:chromesockets', - ipfsNodeConfig: buildDefaultIpfsNodeConfig() - }) - } - // ~ v2.9.x: migrating noRedirectHostnames → noIntegrationsHostnames // https://github.com/ipfs-shipyard/ipfs-companion/pull/830 const { noRedirectHostnames } = await storage.get('noRedirectHostnames') @@ -247,4 +235,6 @@ exports.migrateOptions = async (storage, debug) => { await storage.set({ displayReleaseNotes: false }) } } + + // TODO: detect chrome.ipfs and warn chromesockets users about deprecation } diff --git a/add-on/src/lib/runtime-checks.js b/add-on/src/lib/runtime-checks.js index 7f0092ea0..aabb3dcb1 100644 --- a/add-on/src/lib/runtime-checks.js +++ b/add-on/src/lib/runtime-checks.js @@ -1,6 +1,10 @@ 'use strict' /* eslint-env browser, webextensions */ +const { brave } = require('./ipfs-client/brave') + +// this is our kitchen sink for runtime detection + function getBrowserInfo (browser) { // browser.runtime.getBrowserInfo is not available in Chromium-based browsers if (browser && browser.runtime && browser.runtime.getBrowserInfo) { @@ -17,6 +21,7 @@ function getPlatformInfo (browser) { } function hasChromeSocketsForTcp () { + // experimental Chrome Apps APIs safelisted by Brave as an experiment (superseded by chrome.ipfs.*) return typeof chrome === 'object' && typeof chrome.runtime === 'object' && typeof chrome.runtime.id === 'string' && @@ -30,15 +35,16 @@ async function createRuntimeChecks (browser) { // browser const { name, version } = await getBrowserInfo(browser) const isFirefox = name && (name.includes('Firefox') || name.includes('Fennec')) - const hasNativeProtocolHandler = !!(browser && browser.protocol && browser.protocol.registerStringProtocol) + const hasNativeProtocolHandler = !!(browser && browser.protocol && browser.protocol.registerStringProtocol) // TODO: chrome.ipfs support // platform const platformInfo = await getPlatformInfo(browser) const isAndroid = platformInfo ? platformInfo.os === 'android' : false return Object.freeze({ browser, + brave, // easy Boolean(runtime.brave) isFirefox, isAndroid, - isBrave: hasChromeSocketsForTcp(), // TODO: make it more robust + isBrave: hasChromeSocketsForTcp(), // TODO: deprecated, remove chrome sockets, use brave instead requiresXHRCORSfix: !!(isFirefox && version && version.startsWith('68')), hasChromeSocketsForTcp: hasChromeSocketsForTcp(), hasNativeProtocolHandler diff --git a/add-on/src/options/forms/api-form.js b/add-on/src/options/forms/api-form.js index c8e32e7fc..b948dbfcc 100644 --- a/add-on/src/options/forms/api-form.js +++ b/add-on/src/options/forms/api-form.js @@ -4,12 +4,15 @@ const browser = require('webextension-polyfill') const html = require('choo/html') const { guiURLString } = require('../../lib/options') +const { braveNodeType } = require('../../lib/ipfs-client/brave') const switchToggle = require('../../pages/components/switch-toggle') -function apiForm ({ ipfsApiUrl, ipfsApiPollMs, automaticMode, onOptionChange }) { +function apiForm ({ ipfsNodeType, ipfsApiUrl, ipfsApiPollMs, automaticMode, onOptionChange }) { const onIpfsApiUrlChange = onOptionChange('ipfsApiUrl', (url) => guiURLString(url, { useLocalhostName: false })) const onIpfsApiPollMsChange = onOptionChange('ipfsApiPollMs') const onAutomaticModeChange = onOptionChange('automaticMode') + const apiAddresEditable = ipfsNodeType === 'external' + const braveClass = ipfsNodeType === braveNodeType ? 'brave' : '' return html`
@@ -23,15 +26,16 @@ function apiForm ({ ipfsApiUrl, ipfsApiPollMs, automaticMode, onOptionChange })
diff --git a/add-on/src/options/forms/gateways-form.js b/add-on/src/options/forms/gateways-form.js index 01b568502..66e69ed17 100644 --- a/add-on/src/options/forms/gateways-form.js +++ b/add-on/src/options/forms/gateways-form.js @@ -5,6 +5,7 @@ const browser = require('webextension-polyfill') const html = require('choo/html') const switchToggle = require('../../pages/components/switch-toggle') const { guiURLString, hostTextToArray, hostArrayToText } = require('../../lib/options') +const { braveNodeType } = require('../../lib/ipfs-client/brave') // Warn about mixed content issues when changing the gateway // to something other than HTTP or localhost @@ -31,7 +32,8 @@ function gatewaysForm ({ const onEnabledOnChange = onOptionChange('enabledOn', hostTextToArray) const mixedContentWarning = !secureContextUrl.test(customGatewayUrl) const supportRedirectToCustomGateway = ipfsNodeType !== 'embedded' - const allowChangeOfCustomGateway = ipfsNodeType !== 'embedded:chromesockets' + const allowChangeOfCustomGateway = ipfsNodeType === 'external' + const braveClass = ipfsNodeType === braveNodeType ? 'brave' : '' return html` @@ -52,7 +54,7 @@ function gatewaysForm ({ required pattern="^https?://[^/]+/?$" spellcheck="false" - title="Enter URL without any sub-path" + title="${browser.i18n.getMessage('option_hint_url')}" onchange=${onPublicGatewayUrlChange} value=${publicGatewayUrl} />
@@ -76,11 +78,11 @@ function gatewaysForm ({ required pattern="^https?://[^/]+/?$" spellcheck="false" - title="Enter URL without any sub-path" + title="${browser.i18n.getMessage('option_hint_url')}" onchange=${onPublicSubdomainGatewayUrlChange} value=${publicSubdomainGatewayUrl} /> - ${supportRedirectToCustomGateway && allowChangeOfCustomGateway + ${supportRedirectToCustomGateway ? html`
diff --git a/add-on/src/options/forms/ipfs-node-form.js b/add-on/src/options/forms/ipfs-node-form.js index 37d5dbf6f..41e64c167 100644 --- a/add-on/src/options/forms/ipfs-node-form.js +++ b/add-on/src/options/forms/ipfs-node-form.js @@ -4,11 +4,13 @@ const browser = require('webextension-polyfill') const html = require('choo/html') const { hasChromeSocketsForTcp } = require('../../lib/runtime-checks') +const { braveNodeType } = require('../../lib/ipfs-client/brave') -function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) { +function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange, withNodeFromBrave }) { const onIpfsNodeTypeChange = onOptionChange('ipfsNodeType') const onIpfsNodeConfigChange = onOptionChange('ipfsNodeConfig') - const withChromeSockets = hasChromeSocketsForTcp() + const withChromeSockets = hasChromeSocketsForTcp() // TODO: remove chrome sockets + const braveClass = ipfsNodeType === braveNodeType ? 'brave' : '' return html`
@@ -19,6 +21,7 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {
${browser.i18n.getMessage('option_ipfsNodeType_title')}

${browser.i18n.getMessage('option_ipfsNodeType_external_description')}

+ ${withNodeFromBrave ? html`

${browser.i18n.getMessage('option_ipfsNodeType_brave_description')}

` : null}

${browser.i18n.getMessage(withChromeSockets ? 'option_ipfsNodeType_embedded_chromesockets_description' : 'option_ipfsNodeType_embedded_description')}

${browser.i18n.getMessage('option_legend_readMore')} @@ -26,17 +29,24 @@ function ipfsNodeForm ({ ipfsNodeType, ipfsNodeConfig, onOptionChange }) {

- + ${withNodeFromBrave + ? html`` + : null} ${withChromeSockets ? html`` : html`