diff --git a/src/index.js b/src/index.js index 60d3bc473..e28ec66fe 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ -import { app, dialog, shell } from 'electron' -import { store, createDaemon } from './utils' +import { app, dialog } from 'electron' +import { store, createDaemon, showErrorMessage } from './utils' import startupMenubar from './menubar' import registerHooks from './hooks' @@ -14,65 +14,6 @@ if (!app.requestSingleInstanceLock()) { process.exit(1) } -const issueTemplate = (e) => `Please describe what you were doing when this error happened. - -**Specifications** - -- **OS**: ${process.platform} -- **IPFS Desktop Version**: ${app.getVersion()} -- **Electron Version**: ${process.versions.electron} -- **Chrome Version**: ${process.versions.chrome} - -**Error** - -\`\`\` -${e.stack} -\`\`\` -` - -function handleError (e) { - const option = dialog.showMessageBox({ - type: 'error', - title: 'IPFS Desktop has shutdown', - message: 'IPFS Desktop has shutdown because of an error. You can restart the app or report the error to the developers, which requires a GitHub account.', - buttons: [ - 'Close', - 'Report the error to the developers', - 'Restart the app' - ], - cancelId: 0 - }) - - if (option === 1) { - shell.openExternal(`https://github.com/ipfs-shipyard/ipfs-desktop/issues/new?body=${encodeURI(issueTemplate(e))}`) - } else if (option === 2) { - app.relaunch() - } - - app.exit(1) -} - -async function setupConnection () { - let config = store.get('config') - let updateCfg = false - - if (config === null) { - config = { type: 'go' } - updateCfg = true - } - - const ipfsd = await createDaemon(config) - - // createDaemon has changed the config object, - // but didn't add the repo variable. - if (updateCfg) { - config.path = ipfsd.repoPath - store.set('config', config) - } - - return ipfsd -} - async function run () { try { await app.whenReady() @@ -81,10 +22,20 @@ async function run () { app.exit(1) } + let config = store.get('ipfsConfig') + try { // Initial context object let ctx = { - ipfsd: await setupConnection() + ipfsd: await createDaemon(config) + } + + /// Update the path if it was blank previously. + // This way we use the default path when it is + // not set. + if (config.path === '') { + config.path = ctx.ipfsd.repoPath + store.set('ipfsConfig', config) } // Initialize windows. These can add properties to context @@ -92,12 +43,12 @@ async function run () { // Register hooks await registerHooks(ctx) - - if (!store.get('seenWelcome')) { - // TODO: open WebUI on Welcome screen - } } catch (e) { - handleError(e) + if (e.message === 'exit') { + app.exit(1) + } else { + showErrorMessage(e) + } } } diff --git a/src/utils/daemon.js b/src/utils/daemon.js index ef2f62409..077e63d72 100644 --- a/src/utils/daemon.js +++ b/src/utils/daemon.js @@ -1,23 +1,53 @@ import IPFSFactory from 'ipfsd-ctl' import logger from './logger' +import { showConnFailureErrorMessage } from './errors' +import { join } from 'path' +import fs from 'fs-extra' +import { spawnSync } from 'child_process' +import findExecutable from 'ipfsd-ctl/src/utils/find-ipfs-executable' -export default async function createDaemon (opts) { - opts.type = opts.type || 'go' - opts.path = opts.path || '' - opts.flags = opts.flags || ['--migrate=true', '--routing=dhtclient'] - opts.keysize = opts.keysize || 4096 +function repoFsck (path) { + const exec = findExecutable('go', join(__dirname, '..')) + spawnSync(exec, ['repo', 'fsck'], { + env: { + ...process.env, + IPFS_PATH: path + } + }) +} - if (opts.type !== 'go') { - throw new Error(`${opts.type} connection is not supported yet`) +async function configure (ipfsd) { + const cfgFile = join(ipfsd.repoPath, 'config') + const cfg = await fs.readJSON(cfgFile) + + let origins = [] + try { + origins = cfg.API.HTTPHeaders['Access-Control-Allow-Origin'] + } catch (e) { + logger.warn(e) } - const factory = IPFSFactory.create({ type: opts.type }) + if (!Array.isArray(origins)) { + origins = [] + } + + if (!origins.includes('webui://-')) origins.push('webui://-') + if (!origins.includes('https://webui.ipfs.io')) origins.push('https://webui.ipfs.io') + + cfg.API.HTTPHeaders['Access-Control-Allow-Origin'] = origins + cfg.API.HTTPHeaders['Access-Control-Allow-Methods'] = ['PUT', 'GET', 'POST'] + + await fs.writeJSON(cfgFile, cfg) +} + +async function spawn ({ type, path, keysize }) { + const factory = IPFSFactory.create({ type: type }) - const ipfsd = await new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { factory.spawn({ disposable: false, defaultAddrs: true, - repoPath: opts.path + repoPath: path }, (e, ipfsd) => { if (e) return reject(e) if (ipfsd.initialized) { @@ -25,39 +55,50 @@ export default async function createDaemon (opts) { } ipfsd.init({ - directory: opts.path, - keysize: opts.keysize + directory: path, + keysize: keysize }, e => { if (e) return reject(e) resolve(ipfsd) }) }) }) +} - if (!ipfsd.started) { - await new Promise((resolve, reject) => { - ipfsd.start(opts.flags, err => { - if (err) { - return reject(err) - } +async function start (ipfsd, { flags }) { + await new Promise((resolve, reject) => { + ipfsd.start(flags, err => { + if (err) { + return reject(err) + } - resolve() - }) + resolve() }) + }) +} + +export default async function (opts) { + const ipfsd = await spawn(opts) + await configure(ipfsd) + + if (!ipfsd.started) { + await start(ipfsd, opts) } - let origins = [] try { - origins = await ipfsd.api.config.get('API.HTTPHeaders.Access-Control-Allow-Origin') + await ipfsd.api.id() } catch (e) { - logger.warn(e) - } + if (!e.message.includes('ECONNREFUSED')) { + throw e + } - if (!origins.includes('webui://-')) origins.push('webui://-') - if (!origins.includes('https://webui.ipfs.io')) origins.push('https://webui.ipfs.io') + if (!showConnFailureErrorMessage(ipfsd.repoPath, ipfsd.apiAddr)) { + throw new Error('exit') + } - await ipfsd.api.config.set('API.HTTPHeaders.Access-Control-Allow-Origin', origins) - await ipfsd.api.config.set('API.HTTPHeaders.Access-Control-Allow-Methods', ['PUT', 'GET', 'POST']) + repoFsck(ipfsd.repoPath) + await start(ipfsd, opts) + } return ipfsd } diff --git a/src/utils/errors.js b/src/utils/errors.js new file mode 100644 index 000000000..8cf130f3e --- /dev/null +++ b/src/utils/errors.js @@ -0,0 +1,54 @@ +import { app, dialog, shell } from 'electron' + +const issueTemplate = (e) => `Please describe what you were doing when this error happened. + +**Specifications** + +- **OS**: ${process.platform} +- **IPFS Desktop Version**: ${app.getVersion()} +- **Electron Version**: ${process.versions.electron} +- **Chrome Version**: ${process.versions.chrome} + +**Error** + +\`\`\` +${e.stack} +\`\`\` +` + +export function showErrorMessage (e) { + const option = dialog.showMessageBox({ + type: 'error', + title: 'IPFS Desktop has shutdown', + message: 'IPFS Desktop has shutdown because of an error. You can restart the app or report the error to the developers, which requires a GitHub account.', + buttons: [ + 'Close', + 'Report the error to the developers', + 'Restart the app' + ], + cancelId: 0 + }) + + if (option === 1) { + shell.openExternal(`https://github.com/ipfs-shipyard/ipfs-desktop/issues/new?body=${encodeURI(issueTemplate(e))}`) + } else if (option === 2) { + app.relaunch() + } + + app.exit(1) +} + +export function showConnFailureErrorMessage (path, addr) { + const option = dialog.showMessageBox({ + type: 'warning', + title: 'IPFS Desktop', + message: `IPFS Desktop failed to connect to an existing ipfs api at ${addr}. This can happen if you run 'ipfs daemon' manually and it has not shutdown cleanly. Would you like to try running 'ipfs repo fsck' to remove the lock and api files from ${path} and try again?`, + buttons: [ + 'No, just quit', + 'Yes, run "ipfs repo fsck"' + ], + cancelId: 0 + }) + + return option === 1 +} diff --git a/src/utils/index.js b/src/utils/index.js index f9042054b..0c349e943 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,5 +1,6 @@ import createDaemon from './daemon' import logo from './logo' +import { showConnFailureErrorMessage, showErrorMessage } from './errors' import store from './store' import logger from './logger' import i18n from './i18n' @@ -9,5 +10,7 @@ export { logo, store, logger, - i18n + i18n, + showErrorMessage, + showConnFailureErrorMessage } diff --git a/src/utils/store.js b/src/utils/store.js index caeb17f54..95bef5856 100644 --- a/src/utils/store.js +++ b/src/utils/store.js @@ -2,11 +2,18 @@ import Store from 'electron-store' const store = new Store() -if (store.get('version') !== 4) { +if (store.get('version') !== 5) { store.clear() - store.set('seenWelcome', false) - store.set('config', null) - store.set('version', 4) + + // default config + store.set('ipfsConfig', { + type: 'go', + path: '', + flags: ['--migrate=true', '--routing=dhtclient'], + keysize: 2048 + }) + + store.set('version', 5) } export default store