From 534b1aa0f5d905b95c9087cf58f3fa17fcbb3101 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 28 Jan 2022 17:40:00 +0100 Subject: [PATCH 01/25] wip: ask when opening external protocol License: MIT Signed-off-by: Henrique Dias --- assets/locales/en.json | 2 + src/dialogs/prompt/index.js | 42 ++++++++++- src/dialogs/prompt/template.js | 10 ++- src/download-cid.js | 10 ++- src/protocol-handlers.js | 133 ++++++++++++++++++++++++++++++--- src/tray.js | 5 +- 6 files changed, 181 insertions(+), 21 deletions(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index 8cc7cbe53..94eb39e30 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -33,6 +33,7 @@ "yes": "Yes", "no": "No", "close": "Close", + "continue": "Continue", "ok": "OK", "cancel": "Cancel", "enable": "Enable", @@ -200,6 +201,7 @@ "appPreferences": "App Preferences", "launchOnStartup": "Launch at Login", "openWebUIAtLaunch": "Open Web UI at Launch", + "askWhenOpeningIpfsURIs": "Ask When Opening IPFS URIs", "pubsub": "Enable PubSub", "namesysPubsub": "Enable IPNS over PubSub", "automaticGC": "Automatic Garbage Collection", diff --git a/src/dialogs/prompt/index.js b/src/dialogs/prompt/index.js index bde6e10b5..a7892c065 100644 --- a/src/dialogs/prompt/index.js +++ b/src/dialogs/prompt/index.js @@ -5,14 +5,52 @@ const dock = require('../../utils/dock') const makePage = require('./template') const { getBackgroundColor } = require('./styles') -function generatePage ({ message, defaultValue = '', buttons }, id) { +function makeButtons (buttons) { buttons = buttons.map((txt, i) => ``) if (IS_MAC) { buttons.reverse() } - const page = makePage({ message, defaultValue, buttons, id }) + return buttons.join('\n') +} + +function makeInputs (inputs) { + return inputs.map(({ type, name, label, defaultValue, labels = {} }) => { + let str = '
' + + switch (type) { + case 'checkbox': + str += '
' + str += `` + str += `` + str += '
' + break + case 'radio': + str += '
' + for (const key in labels) { + str += '
' + str += `` + str += `` + str += '
' + } + str += '
' + + break + case 'text': + str += `` + } + + str += '
' + return str + }).join('\n') +} + +function generatePage ({ message, inputs, defaultValue = '', buttons }, id) { + buttons = makeButtons(buttons) + inputs = makeInputs(inputs) + + const page = makePage({ message, inputs, defaultValue, buttons, id }) return `data:text/html;base64,${Buffer.from(page, 'utf8').toString('base64')}` } diff --git a/src/dialogs/prompt/template.js b/src/dialogs/prompt/template.js index 12dfc6b87..3c89fc00c 100644 --- a/src/dialogs/prompt/template.js +++ b/src/dialogs/prompt/template.js @@ -1,14 +1,16 @@ const { styles } = require('./styles') -module.exports = ({ message, defaultValue, buttons, id }) => (` +module.exports = ({ message, inputs, buttons, id }) => (`

${message}

- -
${buttons.join('\n')}
+
+ ${inputs} +
${buttons}
+
+ + ` + + return `data:text/html;base64,${Buffer.from(html, 'utf8').toString('base64')}` +} + +module.exports = { + getPromptEncodedHtml +} diff --git a/src/dialogs/prompt/index.js b/src/dialogs/prompt/index.js index a37e6c755..24146b1f2 100644 --- a/src/dialogs/prompt/index.js +++ b/src/dialogs/prompt/index.js @@ -1,67 +1,34 @@ const { BrowserWindow, ipcMain } = require('electron') const crypto = require('crypto') -const { IS_MAC } = require('../../common/consts') + const dock = require('../../utils/dock') -const makePage = require('./template') const { getBackgroundColor } = require('./styles') - -function getButtonComponentsHtml (buttons) { - buttons = buttons.map((txt, i) => ``) - - if (IS_MAC) { - buttons.reverse() - } - - return buttons.join('\n') -} - -function getInputComponentsHtml (inputs) { - return inputs.map(({ type, name, label, defaultValue, labels = {} }) => { - let str = '
' - - switch (type) { - case 'checkbox': - str += '
' - str += `` - str += `` - str += '
' - break - case 'radio': - str += '
' - for (const key in labels) { - str += '
' - str += `` - str += `` - str += '
' - } - str += '
' - - break - case 'text': - str += `` - } - - str += '
' - return str - }).join('\n') -} - -function generatePage ({ message, inputs, defaultValue = '', buttons }, id) { - buttons = getButtonComponentsHtml(buttons) - inputs = getInputComponentsHtml(inputs) - - const page = makePage({ message, inputs, defaultValue, buttons, id }) - return `data:text/html;base64,${Buffer.from(page, 'utf8').toString('base64')}` -} - -module.exports = async function showPrompt (options) { - options = Object.assign({}, { +const { getPromptEncodedHtml } = require('./html') + +/** + * @typedef PromptConfiguration + * @type {object} + * @property {string} title + * @property {string} message + * @property {InputConfiguration[]} inputs + * @property {string[]} buttons + * @property {object} window + */ + +/** + * Displays a prompt to the user according to the given configuration. + * + * @param {PromptConfiguration} config + * @returns + */ +module.exports = async function showPrompt (config) { + config = Object.assign({}, { window: {}, showDock: true - }, options) + }, config) const window = new BrowserWindow({ - title: options.title, + title: config.title, show: false, width: 350, height: 330, @@ -74,7 +41,7 @@ module.exports = async function showPrompt (options) { nodeIntegration: true, contextIsolation: false }, - ...options.window + ...config.window }) // Generate random id @@ -83,20 +50,20 @@ module.exports = async function showPrompt (options) { return new Promise(resolve => { ipcMain.once(id, (_, data) => { window.destroy() - if (options.showDock) dock.hide() + if (config.showDock) dock.hide() resolve(data) }) window.on('close', () => { - if (options.showDock) dock.hide() + if (config.showDock) dock.hide() resolve({ input: '', button: null }) }) window.once('ready-to-show', () => { - if (options.showDock) dock.show() + if (config.showDock) dock.show() window.show() }) - window.loadURL(generatePage(options, id)) + window.loadURL(getPromptEncodedHtml(config, id)) }) } diff --git a/src/dialogs/prompt/template.js b/src/dialogs/prompt/template.js deleted file mode 100644 index 37a5c5b63..000000000 --- a/src/dialogs/prompt/template.js +++ /dev/null @@ -1,44 +0,0 @@ -const { styles } = require('./styles') - -const fs = require('fs-extra') -const path = require('path') - -const ipfsLogoPath = path.join(__dirname, '../../../assets/icons/ipfs.svg') -const ipfsLogo = 'data:image/svg+xml;base64,' + fs.readFileSync(ipfsLogoPath).toString('base64') - -module.exports = ({ message, inputs, buttons, id }) => (` - - - - - - -

${message}

-
- ${inputs} -
${buttons}
-
- - - -`) From 24ecde42af38181fdb66acb5c2e7aa57c3a149fe Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 10 May 2022 14:24:57 +0200 Subject: [PATCH 16/25] refactor: separate protocol handler url tools --- src/protocol-handlers.js | 253 --------------------------------- src/protocol-handlers/index.js | 134 +++++++++++++++++ src/protocol-handlers/urls.js | 147 +++++++++++++++++++ 3 files changed, 281 insertions(+), 253 deletions(-) delete mode 100644 src/protocol-handlers.js create mode 100644 src/protocol-handlers/index.js create mode 100644 src/protocol-handlers/urls.js diff --git a/src/protocol-handlers.js b/src/protocol-handlers.js deleted file mode 100644 index f1dddebad..000000000 --- a/src/protocol-handlers.js +++ /dev/null @@ -1,253 +0,0 @@ -const { app, shell, ipcMain } = require('electron') -const toUri = require('multiaddr-to-uri') -const { CID } = require('multiformats/cid') -const i18n = require('i18next') -const fetch = require('node-fetch') -const createToggler = require('./utils/create-toggler') -const store = require('./common/store') -const { showPrompt } = require('./dialogs') - -const CONFIG_KEY = 'askWhenOpeningIpfsURIs' -const CONFIG_KEY_ACTION = 'openIpfsURIsAction' - -const ACTION_OPTIONS = { - OPEN_IN_BROWSER: 'openInBrowser', - OPEN_IN_IPFS_DESKTOP: 'openInIpfsDesktop' - // BROWSER_PUBLIC_GATEWAY: 'browserPublicGateway', - // BROWSER_LOCAL_GATEWAY: 'browserLocalGateway', - // FILES_SCREEN: 'filesScreen' - // EXPLORE_SCREEN: 'exploreScreen' -} - -const DEFAULT_ACTION = ACTION_OPTIONS.OPEN_IN_BROWSER -const DEFAULT_GATEWAY = 'https://dweb.link' - -const LOCAL_HOSTNAMES = ['127.0.0.1', '[::1]', '0.0.0.0', '[::]'] - -async function getAction () { - const ask = store.get(CONFIG_KEY, true) - if (!ask) { - return store.get(CONFIG_KEY_ACTION, DEFAULT_ACTION) - } - - const { button, input } = await showPrompt({ - title: i18n.t('protocolHandlerDialog.title'), - message: i18n.t('protocolHandlerDialog.message'), - inputs: [ - { - type: 'radio', - name: 'action', - defaultValue: DEFAULT_ACTION, - labels: { - [ACTION_OPTIONS.OPEN_IN_BROWSER]: i18n.t('protocolHandlerDialog.openInBrowser'), - [ACTION_OPTIONS.OPEN_IN_IPFS_DESKTOP]: i18n.t('protocolHandlerDialog.openInIpfsDesktop') - // [ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY]: i18n.t('protocolHandlerDialog.browserPublicGateway'), - // [ACTION_OPTIONS.BROWSER_LOCAL_GATEWAY]: i18n.t('protocolHandlerDialog.browserLocalGateway'), - // [ACTION_OPTIONS.FILES_SCREEN]: i18n.t('protocolHandlerDialog.filesScreen') - // [ACTION_OPTIONS.EXPLORE_SCREEN]: i18n.t('protocolHandlerDialog.exploreScreen') - } - }, - { - type: 'checkbox', - name: 'remember', - defaultValue: 'checked', - label: i18n.t('protocolHandlerDialog.rememberThisChoice') - } - ], - buttons: [ - i18n.t('continue'), - i18n.t('cancel') - ], - window: { - width: 500, - height: 218 - } - }) - - if (button !== 0) { - return - } - - const action = input.action || DEFAULT_ACTION - - if (input.remember === 'on') { - store.set(CONFIG_KEY, false) - store.set(CONFIG_KEY_ACTION, action) - ipcMain.emit('configUpdated') - } - - return action -} - -async function getPublicGateway (ctx) { - if (!ctx.webui) { - // Best effort. If the Web UI window wasn't created yet, we just return the default - // gateway. - return DEFAULT_GATEWAY - } - - return await ctx.webui - .webContents - .executeJavaScript('JSON.parse(localStorage.getItem("ipfsPublicGateway")) || "https://dweb.link"', true) -} - -async function getPrivateGateway (ctx) { - const ipfsd = ctx.getIpfsd ? await ctx.getIpfsd(true) : null - if (!ipfsd || !ipfsd.api) { - return DEFAULT_GATEWAY - } - - let gateway = await ipfsd.api.config.get('Addresses.Gateway') - if (Array.isArray(gateway)) { - if (gateway.length >= 1) { - gateway = gateway[0] - } else { - return DEFAULT_GATEWAY - } - } - - return toUri(gateway) -} - -const checkIfGatewayUrlIsAccessible = async (url) => { - try { - const { status } = await fetch( - `${url}/ipfs/bafkqae2xmvwgg33nmuqhi3zajfiemuzahiwss` - ) - return status === 200 - } catch (e) { - return false - } -} - -// Separate test is necessary to see if subdomain mode is possible, -// because some browser+OS combinations won't resolve them: -// https://github.com/ipfs/go-ipfs/issues/7527 -const checkIfSubdomainGatewayUrlIsAccessible = async (url) => { - try { - url = new URL(url) - url.hostname = `bafkqae2xmvwgg33nmuqhi3zajfiemuzahiwss.ipfs.${url.hostname}` - const { status } = await fetch(url.toString()) - return status === 200 - } catch (e) { - return false - } -} - -function getPathAndProtocol (url) { - let protocol = null - let hostname = null - let path = '/' - - if (url.startsWith('ipfs://')) { - protocol = 'ipfs' - hostname = url.slice(7) - } else if (url.startsWith('ipns://')) { - protocol = 'ipns' - hostname = url.slice(7) - } else if (url.startsWith('dweb:/ipfs/')) { - protocol = 'ipfs' - hostname = url.slice(11) - } else if (url.startsWith('dweb:/ipns/')) { - protocol = 'ipns' - hostname = url.slice(11) - } else { - return null - } - - if (hostname.includes('/')) { - const [first, ...rest] = hostname.split('/') - hostname = first - path = '/' + rest.join('/') - } - - return { protocol, hostname, path } -} - -async function getGatewayUrl (ctx, { protocol, hostname, path }) { - const publicGateway = await getPublicGateway(ctx) - const privateGateway = await getPrivateGateway(ctx) - - const gw = new URL(privateGateway) - if (LOCAL_HOSTNAMES.includes(gw.hostname)) { - gw.hostname = 'localhost' - const localUrl = gw.toString().replace(/\/+$/, '') // no trailing slashes - if (await checkIfSubdomainGatewayUrlIsAccessible(localUrl)) { - if (protocol === 'ipns') { - hostname = hostname.replaceAll('.', '-') - gw.hostname = `${hostname}.ipns.localhost` - } else { - const cid = CID.parse(hostname) - gw.hostname = `${cid.toV1().toString()}.ipfs.localhost` - } - - gw.pathname = path - return gw.toString().replace(/\/+$/, '') - } - } - - if (await checkIfGatewayUrlIsAccessible(privateGateway)) { - return `${privateGateway}/${protocol}/${hostname}${path}` - } - - return `${publicGateway}/${protocol}/${hostname}${path}` -} - -async function parseUrl (url, ctx) { - const parsed = getPathAndProtocol(url) - if (!parsed) { - return false - } - - const action = await getAction() - - if (action === ACTION_OPTIONS.OPEN_IN_BROWSER) { - const url = await getGatewayUrl(ctx, parsed) - shell.openExternal(url) - return true - } - - if (action === ACTION_OPTIONS.OPEN_IN_IPFS_DESKTOP) { - ctx.launchWebUI(`/${parsed.protocol}/${parsed.hostname}${parsed.path}`, { focus: true }) - return true - } - - return false -} - -async function argvHandler (argv, ctx) { - let handled = false - - for (const arg of argv) { - if (await parseUrl(arg, ctx)) { - handled = true - } - } - - return handled -} - -module.exports = async function (ctx) { - // By default, ask. We need to change this to ensure the - // tray option shows a 'tick'. - if (store.get(CONFIG_KEY, null) === null) { - store.set(CONFIG_KEY, true) - ipcMain.emit('configUpdated') - } - - createToggler(CONFIG_KEY, () => true) - - // Handle if the app started running now, and a link - // was sent to be handled. - argvHandler(process.argv, ctx) - - // Handle URLs in macOS - app.on('open-url', (event, url) => { - event.preventDefault() - parseUrl(url, ctx) - }) -} - -module.exports.argvHandler = argvHandler - -module.exports.CONFIG_KEY = CONFIG_KEY diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js new file mode 100644 index 000000000..3a905919f --- /dev/null +++ b/src/protocol-handlers/index.js @@ -0,0 +1,134 @@ +const { app, shell, ipcMain } = require('electron') +const i18n = require('i18next') +const createToggler = require('../utils/create-toggler') +const store = require('../common/store') +const { showPrompt } = require('../dialogs') +const { parseUrl, getGatewayUrl } = require('./urls') + +const CONFIG_KEY = 'askWhenOpeningIpfsURIs' +const CONFIG_KEY_ACTION = 'openIpfsURIsAction' + +const ACTION_OPTIONS = { + OPEN_IN_BROWSER: 'openInBrowser', + OPEN_IN_IPFS_DESKTOP: 'openInIpfsDesktop' + // BROWSER_PUBLIC_GATEWAY: 'browserPublicGateway', + // BROWSER_LOCAL_GATEWAY: 'browserLocalGateway', + // FILES_SCREEN: 'filesScreen' + // EXPLORE_SCREEN: 'exploreScreen' +} + +const DEFAULT_ACTION = ACTION_OPTIONS.OPEN_IN_BROWSER + +async function getAction () { + const ask = store.get(CONFIG_KEY, true) + if (!ask) { + return store.get(CONFIG_KEY_ACTION, DEFAULT_ACTION) + } + + const { button, input } = await showPrompt({ + title: i18n.t('protocolHandlerDialog.title'), + message: i18n.t('protocolHandlerDialog.message'), + inputs: [ + { + type: 'radio', + name: 'action', + defaultValue: DEFAULT_ACTION, + labels: { + [ACTION_OPTIONS.OPEN_IN_BROWSER]: i18n.t('protocolHandlerDialog.openInBrowser'), + [ACTION_OPTIONS.OPEN_IN_IPFS_DESKTOP]: i18n.t('protocolHandlerDialog.openInIpfsDesktop') + // [ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY]: i18n.t('protocolHandlerDialog.browserPublicGateway'), + // [ACTION_OPTIONS.BROWSER_LOCAL_GATEWAY]: i18n.t('protocolHandlerDialog.browserLocalGateway'), + // [ACTION_OPTIONS.FILES_SCREEN]: i18n.t('protocolHandlerDialog.filesScreen') + // [ACTION_OPTIONS.EXPLORE_SCREEN]: i18n.t('protocolHandlerDialog.exploreScreen') + } + }, + { + type: 'checkbox', + name: 'remember', + defaultValue: 'checked', + label: i18n.t('protocolHandlerDialog.rememberThisChoice') + } + ], + buttons: [ + i18n.t('continue'), + i18n.t('cancel') + ], + window: { + width: 500, + height: 218 + } + }) + + if (button !== 0) { + return + } + + const action = input.action || DEFAULT_ACTION + + if (input.remember === 'on') { + store.set(CONFIG_KEY, false) + store.set(CONFIG_KEY_ACTION, action) + ipcMain.emit('configUpdated') + } + + return action +} + +async function handleUrl (url, ctx) { + const parsed = parseUrl(url) + if (!parsed) { + return false + } + + const action = await getAction() + + if (action === ACTION_OPTIONS.OPEN_IN_BROWSER) { + const url = await getGatewayUrl(ctx, parsed) + shell.openExternal(url) + return true + } + + if (action === ACTION_OPTIONS.OPEN_IN_IPFS_DESKTOP) { + ctx.launchWebUI(`/${parsed.protocol}/${parsed.hostname}${parsed.path}`, { focus: true }) + return true + } + + return false +} + +async function argvHandler (argv, ctx) { + let handled = false + + for (const arg of argv) { + if (await handleUrl(arg, ctx)) { + handled = true + } + } + + return handled +} + +module.exports = async function (ctx) { + // By default, ask. We need to change this to ensure the + // tray option shows a 'tick'. + if (store.get(CONFIG_KEY, null) === null) { + store.set(CONFIG_KEY, true) + ipcMain.emit('configUpdated') + } + + createToggler(CONFIG_KEY, () => true) + + // Handle if the app started running now, and a link + // was sent to be handled. + argvHandler(process.argv, ctx) + + // Handle URLs in macOS + app.on('open-url', (event, url) => { + event.preventDefault() + parseUrl(url, ctx) + }) +} + +module.exports.argvHandler = argvHandler + +module.exports.CONFIG_KEY = CONFIG_KEY diff --git a/src/protocol-handlers/urls.js b/src/protocol-handlers/urls.js new file mode 100644 index 000000000..9d3fc9aca --- /dev/null +++ b/src/protocol-handlers/urls.js @@ -0,0 +1,147 @@ +const toUri = require('multiaddr-to-uri') +const { CID } = require('multiformats/cid') +const fetch = require('node-fetch') + +const DEFAULT_GATEWAY = 'https://dweb.link' +const LOCAL_HOSTNAMES = ['127.0.0.1', '[::1]', '0.0.0.0', '[::]'] + +/** + * @typedef ParsedUrl + * @type {object} + * @property {string} protocol + * @property {string} hostname + * @property {string} path + */ + +/** + * Parses an IPFS/IPNS/dWeb URL to be handled by IPFS Desktop. + * + * @param {string} url + * @returns {ParsedUrl} + */ +function parseUrl (url) { + let protocol = null + let hostname = null + let path = '/' + + if (url.startsWith('ipfs://')) { + protocol = 'ipfs' + hostname = url.slice(7) + } else if (url.startsWith('ipns://')) { + protocol = 'ipns' + hostname = url.slice(7) + } else if (url.startsWith('dweb:/ipfs/')) { + protocol = 'ipfs' + hostname = url.slice(11) + } else if (url.startsWith('dweb:/ipns/')) { + protocol = 'ipns' + hostname = url.slice(11) + } else { + return null + } + + if (hostname.includes('/')) { + const [first, ...rest] = hostname.split('/') + hostname = first + path = '/' + rest.join('/') + } + + return { protocol, hostname, path } +} + +async function getPublicGatewayUrl (ctx) { + if (!ctx.webui) { + // Best effort. If the Web UI window wasn't created yet, we just return the default + // gateway. + return DEFAULT_GATEWAY + } + + return await ctx.webui + .webContents + .executeJavaScript('JSON.parse(localStorage.getItem("ipfsPublicGateway")) || "https://dweb.link"', true) +} + +async function getPrivateGatewayUrl (ctx) { + const ipfsd = ctx.getIpfsd ? await ctx.getIpfsd(true) : null + if (!ipfsd || !ipfsd.api) { + return DEFAULT_GATEWAY + } + + let gateway = await ipfsd.api.config.get('Addresses.Gateway') + if (Array.isArray(gateway)) { + if (gateway.length >= 1) { + gateway = gateway[0] + } else { + return DEFAULT_GATEWAY + } + } + + return toUri(gateway) +} + +const checkIfGatewayUrlIsAccessible = async (url) => { + try { + const { status } = await fetch( + `${url}/ipfs/bafkqae2xmvwgg33nmuqhi3zajfiemuzahiwss` + ) + return status === 200 + } catch (e) { + return false + } +} + +// Separate test is necessary to see if subdomain mode is possible, +// because some browser+OS combinations won't resolve them: +// https://github.com/ipfs/go-ipfs/issues/7527 +const checkIfSubdomainGatewayUrlIsAccessible = async (url) => { + try { + url = new URL(url) + url.hostname = `bafkqae2xmvwgg33nmuqhi3zajfiemuzahiwss.ipfs.${url.hostname}` + const { status } = await fetch(url.toString()) + return status === 200 + } catch (e) { + return false + } +} + +/** + * Get the gateway URL. Logic borrowed from Web UI. + * Please check https://github.com/ipfs/ipfs-webui/pull/1591. + * + * @param {object} ctx + * @param {ParsedUrl} parsedUrl + * @returns + */ +async function getGatewayUrl (ctx, { protocol, hostname, path }) { + const publicGateway = await getPublicGatewayUrl(ctx) + const privateGateway = await getPrivateGatewayUrl(ctx) + + const gw = new URL(privateGateway) + if (LOCAL_HOSTNAMES.includes(gw.hostname)) { + gw.hostname = 'localhost' + const localUrl = gw.toString().replace(/\/+$/, '') // no trailing slashes + if (await checkIfSubdomainGatewayUrlIsAccessible(localUrl)) { + if (protocol === 'ipns') { + hostname = hostname.replaceAll('.', '-') + gw.hostname = `${hostname}.ipns.localhost` + } else { + const cid = CID.parse(hostname) + gw.hostname = `${cid.toV1().toString()}.ipfs.localhost` + } + + gw.pathname = path + return gw.toString().replace(/\/+$/, '') + } + } + + if (await checkIfGatewayUrlIsAccessible(privateGateway)) { + return `${privateGateway}/${protocol}/${hostname}${path}` + } + + return `${publicGateway}/${protocol}/${hostname}${path}` +} + +module.exports = { + parseUrl, + getGatewayUrl +} From c0290bb22a838834a2446bda4ddbf9e2f59d215c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 10 May 2022 14:33:40 +0200 Subject: [PATCH 17/25] revert changes to index and wait on protocol hanlers --- src/i18n.js | 8 +++++++- src/index.js | 6 ++---- src/protocol-handlers/index.js | 4 ++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/i18n.js b/src/i18n.js index 69a31d65a..28eb6ee8a 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -34,4 +34,10 @@ const setupI18n = async () => { }) } -module.exports = setupI18n() +let result = null + +module.exports = async () => { + if (result) return result + result = await setupI18n() + return result +} diff --git a/src/index.js b/src/index.js index d89817fa0..63ca45a4d 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,7 @@ if (process.env.NODE_ENV === 'test') { const fixPath = require('fix-path') const { criticalErrorDialog } = require('./dialogs') const logger = require('./common/logger') -const i18nReady = require('./i18n') +const setupI18n = require('./i18n') const setupProtocolHandlers = require('./protocol-handlers') const setupNpmOnIpfs = require('./npm-on-ipfs') const setupDaemon = require('./daemon') @@ -46,8 +46,6 @@ if (!app.requestSingleInstanceLock()) { const ctx = {} app.on('will-finish-launching', async () => { - await app.whenReady() - await i18nReady // Ensure i18n is ready for the dialog. await setupProtocolHandlers(ctx) }) @@ -74,8 +72,8 @@ async function run () { } try { - await i18nReady await setupAnalytics(ctx) // ctx.countlyDeviceId + await setupI18n(ctx) await setupAppMenu(ctx) await setupWebUI(ctx) // ctx.webui, launchWebUI diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js index 3a905919f..4a131571b 100644 --- a/src/protocol-handlers/index.js +++ b/src/protocol-handlers/index.js @@ -3,6 +3,7 @@ const i18n = require('i18next') const createToggler = require('../utils/create-toggler') const store = require('../common/store') const { showPrompt } = require('../dialogs') +const setupI18n = require('../i18n') const { parseUrl, getGatewayUrl } = require('./urls') const CONFIG_KEY = 'askWhenOpeningIpfsURIs' @@ -109,6 +110,9 @@ async function argvHandler (argv, ctx) { } module.exports = async function (ctx) { + await app.whenReady() + await setupI18n(ctx) // Ensure i18n is ready for the dialog. + // By default, ask. We need to change this to ensure the // tray option shows a 'tick'. if (store.get(CONFIG_KEY, null) === null) { From 9dcf458be1945ecff79fc7d65d770780d2e0e348 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 10 May 2022 14:34:26 +0200 Subject: [PATCH 18/25] revert index.js --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 63ca45a4d..2e1aa8021 100644 --- a/src/index.js +++ b/src/index.js @@ -11,8 +11,8 @@ if (process.env.NODE_ENV === 'test') { const fixPath = require('fix-path') const { criticalErrorDialog } = require('./dialogs') const logger = require('./common/logger') -const setupI18n = require('./i18n') const setupProtocolHandlers = require('./protocol-handlers') +const setupI18n = require('./i18n') const setupNpmOnIpfs = require('./npm-on-ipfs') const setupDaemon = require('./daemon') const setupWebUI = require('./webui') @@ -45,8 +45,8 @@ if (!app.requestSingleInstanceLock()) { const ctx = {} -app.on('will-finish-launching', async () => { - await setupProtocolHandlers(ctx) +app.on('will-finish-launching', () => { + setupProtocolHandlers(ctx) }) function handleError (err) { From 91f255e4aeb9c0f83c4722e3edfe2aa0d9784b9f Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 15 Jul 2022 13:21:41 +0200 Subject: [PATCH 19/25] refactor: remove comments --- src/protocol-handlers/index.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js index 4a131571b..eb6342238 100644 --- a/src/protocol-handlers/index.js +++ b/src/protocol-handlers/index.js @@ -12,10 +12,6 @@ const CONFIG_KEY_ACTION = 'openIpfsURIsAction' const ACTION_OPTIONS = { OPEN_IN_BROWSER: 'openInBrowser', OPEN_IN_IPFS_DESKTOP: 'openInIpfsDesktop' - // BROWSER_PUBLIC_GATEWAY: 'browserPublicGateway', - // BROWSER_LOCAL_GATEWAY: 'browserLocalGateway', - // FILES_SCREEN: 'filesScreen' - // EXPLORE_SCREEN: 'exploreScreen' } const DEFAULT_ACTION = ACTION_OPTIONS.OPEN_IN_BROWSER @@ -37,10 +33,6 @@ async function getAction () { labels: { [ACTION_OPTIONS.OPEN_IN_BROWSER]: i18n.t('protocolHandlerDialog.openInBrowser'), [ACTION_OPTIONS.OPEN_IN_IPFS_DESKTOP]: i18n.t('protocolHandlerDialog.openInIpfsDesktop') - // [ACTION_OPTIONS.BROWSER_PUBLIC_GATEWAY]: i18n.t('protocolHandlerDialog.browserPublicGateway'), - // [ACTION_OPTIONS.BROWSER_LOCAL_GATEWAY]: i18n.t('protocolHandlerDialog.browserLocalGateway'), - // [ACTION_OPTIONS.FILES_SCREEN]: i18n.t('protocolHandlerDialog.filesScreen') - // [ACTION_OPTIONS.EXPLORE_SCREEN]: i18n.t('protocolHandlerDialog.exploreScreen') } }, { From e7cd90bd62c563eeb409c68fef8276b6c080442c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 15 Jul 2022 13:25:41 +0200 Subject: [PATCH 20/25] refactor: rename ask askWhenOpeningUri --- src/protocol-handlers/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js index eb6342238..d2f718f67 100644 --- a/src/protocol-handlers/index.js +++ b/src/protocol-handlers/index.js @@ -17,8 +17,8 @@ const ACTION_OPTIONS = { const DEFAULT_ACTION = ACTION_OPTIONS.OPEN_IN_BROWSER async function getAction () { - const ask = store.get(CONFIG_KEY, true) - if (!ask) { + const askWhenOpeningUri = store.get(CONFIG_KEY, true) + if (!askWhenOpeningUri) { return store.get(CONFIG_KEY_ACTION, DEFAULT_ACTION) } From c4752d790030a9188e84bd7ec9876900a7cda49c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 15 Jul 2022 13:27:26 +0200 Subject: [PATCH 21/25] refactor: add comment and be more explicit --- src/protocol-handlers/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js index d2f718f67..5b6c53c24 100644 --- a/src/protocol-handlers/index.js +++ b/src/protocol-handlers/index.js @@ -52,7 +52,8 @@ async function getAction () { } }) - if (button !== 0) { + if (button === 1) { + // User canceled. return } From a861ffb8e7b3d0993690aaed15f15c6af74263ed Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 15 Jul 2022 13:36:11 +0200 Subject: [PATCH 22/25] refactor: add comment, cleanupo --- src/protocol-handlers/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/protocol-handlers/index.js b/src/protocol-handlers/index.js index 5b6c53c24..5e21bbf15 100644 --- a/src/protocol-handlers/index.js +++ b/src/protocol-handlers/index.js @@ -57,9 +57,8 @@ async function getAction () { return } - const action = input.action || DEFAULT_ACTION - - if (input.remember === 'on') { + const { remember, action } = input + if (remember === 'on') { store.set(CONFIG_KEY, false) store.set(CONFIG_KEY_ACTION, action) ipcMain.emit('configUpdated') @@ -68,6 +67,9 @@ async function getAction () { return action } +/** + * @returns {Promise} whether or not the URL was handled. + */ async function handleUrl (url, ctx) { const parsed = parseUrl(url) if (!parsed) { From 4d54b5a93b1ea251c9865e989dedaed48070c65a Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 15 Jul 2022 13:39:00 +0200 Subject: [PATCH 23/25] fix: add null to return types --- src/protocol-handlers/urls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/protocol-handlers/urls.js b/src/protocol-handlers/urls.js index 9d3fc9aca..dfc49e530 100644 --- a/src/protocol-handlers/urls.js +++ b/src/protocol-handlers/urls.js @@ -17,7 +17,7 @@ const LOCAL_HOSTNAMES = ['127.0.0.1', '[::1]', '0.0.0.0', '[::]'] * Parses an IPFS/IPNS/dWeb URL to be handled by IPFS Desktop. * * @param {string} url - * @returns {ParsedUrl} + * @returns {ParsedUrl|null} */ function parseUrl (url) { let protocol = null From 18e8b14563c5bc9baedda196093e49f81b31a81c Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 20 Jul 2022 10:05:07 +0200 Subject: [PATCH 24/25] fix: wrap JSON.parse around try catch --- src/protocol-handlers/urls.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/protocol-handlers/urls.js b/src/protocol-handlers/urls.js index dfc49e530..0bb2b1f20 100644 --- a/src/protocol-handlers/urls.js +++ b/src/protocol-handlers/urls.js @@ -56,9 +56,17 @@ async function getPublicGatewayUrl (ctx) { return DEFAULT_GATEWAY } - return await ctx.webui - .webContents - .executeJavaScript('JSON.parse(localStorage.getItem("ipfsPublicGateway")) || "https://dweb.link"', true) + /** @type {string|null} */ + let publicGatewayUrl + + try { + publicGatewayUrl = await ctx.webui.webContents + .executeJavaScript('JSON.parse(localStorage.getItem("ipfsPublicGateway"))') || 'https://dweb.link' + } catch (_) { + publicGatewayUrl = 'https://dweb.link' + } + + return publicGatewayUrl } async function getPrivateGatewayUrl (ctx) { From 270d60d8b994f4c047dd1463af8b0dfecea264e1 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Tue, 2 Aug 2022 13:59:08 +0200 Subject: [PATCH 25/25] Update assets/locales/en.json Co-authored-by: Marcin Rataj --- assets/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/locales/en.json b/assets/locales/en.json index b059aed93..66c26b35f 100644 --- a/assets/locales/en.json +++ b/assets/locales/en.json @@ -215,7 +215,7 @@ }, "protocolHandlerDialog": { "title": "Opening IPFS address", - "message": "How would you like IPFS Desktop to open IPFS addresses?", + "message": "How would you like IPFS Desktop to open ipfs:// and ipns:// addresses?", "openInBrowser": "Open in my default browser", "openInIpfsDesktop": "Open in IPFS Desktop", "rememberThisChoice": "Remember this choice for all IPFS addresses"