/ electron / start-ipfs.js
start-ipfs.js
  1  import isDev from 'electron-is-dev';
  2  import path from 'path';
  3  import { spawn } from 'child_process';
  4  import fs from 'fs-extra';
  5  import ps from 'node:process';
  6  import proxyServer from './proxy-server.js';
  7  import tcpPortUsed from 'tcp-port-used';
  8  import EnvPaths from 'env-paths';
  9  import { fileURLToPath } from 'url';
 10  const dirname = path.join(path.dirname(fileURLToPath(import.meta.url)));
 11  const envPaths = EnvPaths('plebbit', { suffix: false });
 12  
 13  // use this custom function instead of spawnSync for better logging
 14  // also spawnSync might have been causing crash on start on windows
 15  const spawnAsync = (...args) =>
 16    new Promise((resolve, reject) => {
 17      const spawedProcess = spawn(...args);
 18      spawedProcess.on('exit', (exitCode, signal) => {
 19        if (exitCode === 0) resolve();
 20        else reject(Error(`spawnAsync process '${spawedProcess.pid}' exited with code '${exitCode}' signal '${signal}'`));
 21      });
 22      spawedProcess.stderr.on('data', (data) => console.error(data.toString()));
 23      spawedProcess.stdin.on('data', (data) => console.log(data.toString()));
 24      spawedProcess.stdout.on('data', (data) => console.log(data.toString()));
 25      spawedProcess.on('error', (data) => console.error(data.toString()));
 26    });
 27  
 28  const startIpfs = async () => {
 29    const ipfsFileName = process.platform == 'win32' ? 'ipfs.exe' : 'ipfs';
 30    let ipfsPath = path.join(process.resourcesPath, 'bin', ipfsFileName);
 31    let ipfsDataPath = path.join(envPaths.data, 'ipfs');
 32  
 33    // test launching the ipfs binary in dev mode
 34    // they must be downloaded first using `yarn electron:build`
 35    if (isDev) {
 36      let binFolderName = 'win';
 37      if (process.platform === 'linux') {
 38        binFolderName = 'linux';
 39      }
 40      if (process.platform === 'darwin') {
 41        binFolderName = 'mac';
 42      }
 43      ipfsPath = path.join(dirname, '..', 'bin', binFolderName, ipfsFileName);
 44      ipfsDataPath = path.join(dirname, '..', '.plebbit', 'ipfs');
 45    }
 46  
 47    if (!fs.existsSync(ipfsPath)) {
 48      throw Error(`ipfs binary '${ipfsPath}' doesn't exist`);
 49    }
 50  
 51    console.log({ ipfsPath, ipfsDataPath });
 52  
 53    fs.ensureDirSync(ipfsDataPath);
 54    const env = { IPFS_PATH: ipfsDataPath };
 55    // init ipfs client on first launch
 56    try {
 57      await spawnAsync(ipfsPath, ['init'], { env, hideWindows: true });
 58    } catch (e) {}
 59  
 60    // make sure repo is migrated
 61    try {
 62      await spawnAsync(ipfsPath, ['repo', 'migrate'], { env, hideWindows: true });
 63    } catch (e) {}
 64  
 65    // dont use 8080 port because it's too common
 66    await spawnAsync(ipfsPath, ['config', '--json', 'Addresses.Gateway', '"/ip4/127.0.0.1/tcp/6473"'], {
 67      env,
 68      hideWindows: true,
 69    });
 70  
 71    // use different port with proxy for debugging during env
 72    let apiAddress = '/ip4/127.0.0.1/tcp/5001';
 73    if (isDev) {
 74      apiAddress = apiAddress.replace('5001', '5002');
 75      proxyServer.start({ proxyPort: 5001, targetPort: 5002 });
 76    }
 77    await spawnAsync(ipfsPath, ['config', 'Addresses.API', apiAddress], { env, hideWindows: true });
 78  
 79    await new Promise((resolve, reject) => {
 80      const ipfsProcess = spawn(ipfsPath, ['daemon', '--migrate', '--enable-pubsub-experiment', '--enable-namesys-pubsub'], { env, hideWindows: true });
 81      console.log(`ipfs daemon process started with pid ${ipfsProcess.pid}`);
 82      let lastError;
 83      ipfsProcess.stderr.on('data', (data) => {
 84        lastError = data.toString();
 85        console.error(data.toString());
 86      });
 87      ipfsProcess.stdin.on('data', (data) => console.log(data.toString()));
 88      ipfsProcess.stdout.on('data', (data) => console.log(data.toString()));
 89      ipfsProcess.on('error', (data) => console.error(data.toString()));
 90      ipfsProcess.on('exit', () => {
 91        console.error(`ipfs process with pid ${ipfsProcess.pid} exited`);
 92        reject(Error(lastError));
 93      });
 94      process.on('exit', () => {
 95        try {
 96          ps.kill(ipfsProcess.pid);
 97        } catch (e) {
 98          console.log(e);
 99        }
100        try {
101          // sometimes ipfs doesnt exit unless we kill pid +1
102          ps.kill(ipfsProcess.pid + 1);
103        } catch (e) {
104          console.log(e);
105        }
106      });
107    });
108  };
109  
110  const DefaultExport = {};
111  
112  let pendingStart = false;
113  const start = async () => {
114    if (pendingStart) {
115      return;
116    }
117    pendingStart = true;
118    try {
119      const started = await tcpPortUsed.check(5001, '127.0.0.1');
120      if (started) {
121        return;
122      }
123      await startIpfs();
124    } catch (e) {
125      console.log('failed starting ipfs', e);
126      try {
127        // try to run exported onError callback, can be undefined
128        DefaultExport.onError(e)?.catch?.(console.log);
129      } catch (e) {}
130    }
131    pendingStart = false;
132  };
133  
134  // retry starting ipfs every 1 second,
135  // in case it was started by another client that shut down and shut down ipfs with it
136  start();
137  setInterval(() => {
138    start();
139  }, 1000);
140  
141  DefaultExport.start = start;
142  export default DefaultExport;