-
Notifications
You must be signed in to change notification settings - Fork 331
feat: leverage IPFS node provided by Brave #956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| '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 | ||
| // TODO: replace the image with a page that asks user to ' "Enable IPFS" in | ||
| // Brave if you want to use this node type.' | ||
| const braveIpfsUriTrigger = 'ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi' | ||
|
|
||
| // 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 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 | ||
| await browser.tabs.create({ url: braveIpfsUriTrigger }) | ||
| // IPFS Companion is unable to change Brave settings, | ||
| // all we can do is to poll chrome.ipfs.* and detect when user made a decision | ||
| // TODO: nudge user to click on 'Enable IPFS' ? | ||
| 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') { | ||
| // TODO: tell user what is happening by updating tab with braveIpfsUriTrigger? | ||
| log('waiting while Brave downloads IPFS executable..') | ||
| await waitFor(() => brave.getExecutableAvailable()) | ||
|
|
||
| log('waiting while Brave creates repo and config via ipfs init..') | ||
| await waitFor(async () => typeof (await brave.getConfig()) !== 'undefined') | ||
| } | ||
| } | ||
|
|
||
| if (method !== 'local') { | ||
| 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 waitFor(() => brave.launch()) | ||
| log('brave.launch() finished') | ||
|
|
||
| // ensure Companion uses the endpoint provided by Brave | ||
| await exports.useBraveEndpoint(browser) | ||
|
|
||
| // TODO: close the trigger tab | ||
| // TODO: switch to 'welcome' tab is its open | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.