settings.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. const { app } = require('electron');
  2. const path = require('path');
  3. const fs = require('fs');
  4. const packageJson = require('../package.json');
  5. const _ = require('./utils/underscore');
  6. const lodash = require('lodash');
  7. import {
  8. syncBuildConfig,
  9. syncFlags,
  10. setSwarmEnableOnStart
  11. } from './core/settings/actions';
  12. import logger from './utils/logger';
  13. const settingsLog = logger.create('Settings');
  14. let instance = null;
  15. class Settings {
  16. constructor() {
  17. if (!instance) {
  18. instance = this;
  19. }
  20. return instance;
  21. }
  22. init() {
  23. const logLevel = { logLevel: argv.loglevel };
  24. const logFolder = { logFolder: path.join(this.userDataPath, 'logs') };
  25. const loggerOptions = Object.assign(argv, logLevel, logFolder);
  26. logger.setup(loggerOptions);
  27. store.dispatch(syncFlags(argv));
  28. // If -v flag provided, log the Mist version and exit
  29. if (argv.version) {
  30. settingsLog.info(`Mist v${this.appVersion}`);
  31. process.exit(0);
  32. }
  33. // Some Linux installations require this setting:
  34. if (argv.ignoreGpuBlacklist) {
  35. app.commandLine.appendSwitch('ignore-gpu-blacklist', 'true');
  36. store.dispatch({ type: '[MAIN]:IGNORE_GPU_BLACKLIST:SET' });
  37. }
  38. if (this.inAutoTestMode) {
  39. settingsLog.info('AUTOMATED TESTING');
  40. store.dispatch({ type: '[MAIN]:TEST_MODE:SET' });
  41. }
  42. settingsLog.info(`Running in production mode: ${this.inProductionMode}`);
  43. if (this.rpcMode === 'http') {
  44. settingsLog.warn(
  45. 'Connecting to a node via HTTP instead of ipcMain. This is less secure!!!!'.toUpperCase()
  46. );
  47. }
  48. store.dispatch(syncBuildConfig('appVersion', packageJson.version));
  49. store.dispatch(syncBuildConfig('rpcMode', this.rpcMode));
  50. store.dispatch(syncBuildConfig('productionMode', this.inProductionMode));
  51. store.dispatch(syncBuildConfig('uiMode', this.uiMode));
  52. }
  53. // @returns "Application Support/Mist" in production mode
  54. // @returns "Application Support/Electron" in development mode
  55. get userDataPath() {
  56. return app.getPath('userData');
  57. }
  58. get dbFilePath() {
  59. const dbFileName = this.inAutoTestMode ? 'mist.test.lokidb' : 'mist.lokidb';
  60. return path.join(this.userDataPath, dbFileName);
  61. }
  62. get appDataPath() {
  63. // Application Support/
  64. return app.getPath('appData');
  65. }
  66. get userHomePath() {
  67. return app.getPath('home');
  68. }
  69. get cli() {
  70. return argv;
  71. }
  72. get appVersion() {
  73. return packageJson.version;
  74. }
  75. get appName() {
  76. return this.uiMode === 'mist' ? 'Mist' : 'Ethereum Wallet';
  77. }
  78. get appLicense() {
  79. return packageJson.license;
  80. }
  81. get uiMode() {
  82. return _.isString(argv.mode) ? argv.mode.toLowerCase() : argv.mode;
  83. }
  84. get inProductionMode() {
  85. return defaultConfig.production;
  86. }
  87. get inAutoTestMode() {
  88. return !!process.env.TEST_MODE;
  89. }
  90. get swarmURL() {
  91. return argv.swarmurl;
  92. }
  93. get gethPath() {
  94. return argv.gethpath;
  95. }
  96. get ethPath() {
  97. return argv.ethpath;
  98. }
  99. get rpcMode() {
  100. if (argv.rpc && argv.rpc.indexOf('http') === 0) return 'http';
  101. if (argv.rpc && argv.rpc.indexOf('ws:') === 0) {
  102. settingsLog.warn(
  103. 'Websockets are not yet supported by Mist, using default IPC connection'
  104. );
  105. argv.rpc = null;
  106. return 'ipc';
  107. } else return 'ipc';
  108. }
  109. get rpcConnectConfig() {
  110. if (this.rpcMode === 'ipc') {
  111. return {
  112. path: this.rpcIpcPath
  113. };
  114. }
  115. return {
  116. hostPort: this.rpcHttpPath
  117. };
  118. }
  119. get rpcHttpPath() {
  120. return this.rpcMode === 'http' ? argv.rpc : null;
  121. }
  122. get rpcIpcPath() {
  123. let ipcPath = this.rpcMode === 'ipc' ? argv.rpc : null;
  124. if (ipcPath) {
  125. return ipcPath;
  126. }
  127. ipcPath = this.userHomePath;
  128. if (process.platform === 'darwin') {
  129. ipcPath += '/Library/Ethereum/geth.ipc';
  130. } else if (
  131. process.platform === 'freebsd' ||
  132. process.platform === 'linux' ||
  133. process.platform === 'sunos'
  134. ) {
  135. ipcPath += '/.ethereum/geth.ipc';
  136. } else if (process.platform === 'win32') {
  137. ipcPath = '\\\\.\\pipe\\geth.ipc';
  138. }
  139. settingsLog.debug(`IPC path: ${ipcPath}`);
  140. return ipcPath;
  141. }
  142. get nodeType() {
  143. return argv.node;
  144. }
  145. get network() {
  146. return argv.network;
  147. }
  148. get syncmode() {
  149. return argv.syncmode;
  150. }
  151. get nodeOptions() {
  152. return argv.nodeOptions;
  153. }
  154. get language() {
  155. return this.loadConfig('ui.i18n');
  156. }
  157. set language(langCode) {
  158. this.saveConfig('ui.i18n', langCode);
  159. }
  160. get enableSwarmOnStart() {
  161. if (global.mode === 'wallet') {
  162. return false;
  163. }
  164. if (argv.swarm) {
  165. return true;
  166. }
  167. const enableOnStart = this.loadConfig('swarm.enableOnStart');
  168. // Sync to redux
  169. if (enableOnStart) {
  170. store.dispatch(setSwarmEnableOnStart());
  171. }
  172. return enableOnStart;
  173. }
  174. set enableSwarmOnStart(bool) {
  175. this.saveConfig('swarm.enableOnStart', bool);
  176. }
  177. get skiptimesynccheck() {
  178. return argv.skiptimesynccheck;
  179. }
  180. initConfig() {
  181. global.config.insert({
  182. ui: {
  183. i18n: i18n.getBestMatchedLangCode(app.getLocale())
  184. },
  185. swarm: {
  186. enableOnStart: argv.swarm
  187. }
  188. });
  189. }
  190. saveConfig(key, value) {
  191. let obj = global.config.get(1);
  192. if (!obj) {
  193. this.initConfig();
  194. obj = global.config.get(1);
  195. }
  196. if (lodash.get(obj, key) !== value) {
  197. lodash.set(obj, key, value);
  198. global.config.update(obj);
  199. settingsLog.debug(`Settings: saveConfig('${key}', '${value}')`);
  200. settingsLog.trace(global.config.data);
  201. }
  202. }
  203. loadConfig(key) {
  204. const obj = global.config.get(1);
  205. if (!obj) {
  206. this.initConfig();
  207. return this.loadConfig(key);
  208. }
  209. settingsLog.trace(
  210. `Settings: loadConfig('${key}') = '${lodash.get(obj, key)}'`
  211. );
  212. return lodash.get(obj, key);
  213. }
  214. loadUserData(thisPath) {
  215. const fullPath = this.constructUserDataPath(thisPath);
  216. settingsLog.trace('Load user data', fullPath);
  217. // check if the file exists
  218. try {
  219. fs.accessSync(fullPath, fs.R_OK);
  220. } catch (err) {
  221. return null;
  222. }
  223. // try to read it
  224. try {
  225. const data = fs.readFileSync(fullPath, { encoding: 'utf8' });
  226. settingsLog.debug(`Reading "${data}" from ${fullPath}`);
  227. return data;
  228. } catch (err) {
  229. settingsLog.warn(`File not readable: ${fullPath}`, err);
  230. }
  231. return null;
  232. }
  233. saveUserData(thisPath, data) {
  234. if (!data) {
  235. // return so we dont write null, or other invalid data
  236. return;
  237. }
  238. const fullPath = this.constructUserDataPath(thisPath);
  239. try {
  240. settingsLog.debug(`Saving "${data}" to ${fullPath}`);
  241. fs.writeFileSync(fullPath, data, { encoding: 'utf8' });
  242. } catch (err) {
  243. settingsLog.warn(`Unable to write to ${fullPath}`, err);
  244. }
  245. }
  246. constructUserDataPath(filePath) {
  247. return path.join(this.userDataPath, filePath);
  248. }
  249. }
  250. module.exports = new Settings();
  251. /* ==========================
  252. Command line argument parsing
  253. ============================= */
  254. // Load config
  255. const defaultConfig = {
  256. mode: 'mist',
  257. production: false
  258. };
  259. try {
  260. _.extend(defaultConfig, require('../config.json'));
  261. } catch (error) {
  262. settingsLog.error(error);
  263. }
  264. const argv = require('yargs')
  265. .usage('Usage: $0 [Mist options] [Node options]')
  266. .option({
  267. mode: {
  268. alias: 'm',
  269. demand: false,
  270. default: defaultConfig.mode,
  271. describe: 'App UI mode: wallet, mist.',
  272. requiresArg: true,
  273. nargs: 1,
  274. type: 'string',
  275. group: 'Mist options:'
  276. },
  277. node: {
  278. demand: false,
  279. default: null,
  280. describe: 'Node to use: geth, eth',
  281. requiresArg: true,
  282. nargs: 1,
  283. type: 'string',
  284. group: 'Mist options:'
  285. },
  286. network: {
  287. demand: false,
  288. default: null,
  289. describe: 'Network to connect to: main, test',
  290. requiresArg: true,
  291. nargs: 1,
  292. type: 'string',
  293. group: 'Mist options:'
  294. },
  295. rpc: {
  296. demand: false,
  297. describe:
  298. 'Path to node IPC socket file OR HTTP RPC hostport (if IPC socket file then --node-ipcpath will be set with this value).',
  299. requiresArg: true,
  300. nargs: 1,
  301. type: 'string',
  302. group: 'Mist options:'
  303. },
  304. swarm: {
  305. describe: 'Enable Swarm on start.',
  306. requiresArg: false,
  307. nargs: 0,
  308. type: 'boolean',
  309. group: 'Mist options:'
  310. },
  311. swarmurl: {
  312. demand: false,
  313. default: 'http://localhost:8500',
  314. describe:
  315. 'URL serving the Swarm HTTP API. If null, Mist will open a local node.',
  316. requiresArg: true,
  317. nargs: 1,
  318. type: 'string',
  319. group: 'Mist options:'
  320. },
  321. gethpath: {
  322. demand: false,
  323. describe: 'Path to Geth executable to use instead of default.',
  324. requiresArg: true,
  325. nargs: 1,
  326. type: 'string',
  327. group: 'Mist options:'
  328. },
  329. ethpath: {
  330. demand: false,
  331. describe: 'Path to Eth executable to use instead of default.',
  332. requiresArg: true,
  333. nargs: 1,
  334. type: 'string',
  335. group: 'Mist options:'
  336. },
  337. 'ignore-gpu-blacklist': {
  338. demand: false,
  339. describe: 'Ignores GPU blacklist (needed for some Linux installations).',
  340. requiresArg: false,
  341. nargs: 0,
  342. type: 'boolean',
  343. group: 'Mist options:'
  344. },
  345. 'reset-tabs': {
  346. demand: false,
  347. describe: 'Reset Mist tabs to their default settings.',
  348. requiresArg: false,
  349. nargs: 0,
  350. type: 'boolean',
  351. group: 'Mist options:'
  352. },
  353. loglevel: {
  354. demand: false,
  355. default: 'info',
  356. describe:
  357. 'Minimum logging threshold: info, debug, error, trace (shows all logs, including possible passwords over IPC!).',
  358. requiresArg: true,
  359. nargs: 1,
  360. type: 'string',
  361. group: 'Mist options:'
  362. },
  363. syncmode: {
  364. demand: false,
  365. requiresArg: true,
  366. describe: 'Geth synchronization mode: [fast|light|full|nosync]',
  367. nargs: 1,
  368. type: 'string',
  369. group: 'Mist options:'
  370. },
  371. version: {
  372. alias: 'v',
  373. demand: false,
  374. requiresArg: false,
  375. nargs: 0,
  376. describe: 'Display Mist version.',
  377. group: 'Mist options:',
  378. type: 'boolean'
  379. },
  380. skiptimesynccheck: {
  381. demand: false,
  382. requiresArg: false,
  383. nargs: 0,
  384. describe:
  385. 'Disable checks for the presence of automatic time sync on your OS.',
  386. group: 'Mist options:',
  387. type: 'boolean'
  388. },
  389. '': {
  390. describe:
  391. 'To pass options to the underlying node (e.g. Geth) use the --node- prefix, e.g. --node-datadir',
  392. group: 'Node options:'
  393. }
  394. })
  395. .help('h')
  396. .alias('h', 'help')
  397. .parse(process.argv.slice(1));
  398. argv.nodeOptions = [];
  399. for (const optIdx in argv) {
  400. if (optIdx.indexOf('node-') === 0) {
  401. argv.nodeOptions.push(`--${optIdx.substr(5)}`);
  402. if (argv[optIdx] !== true) {
  403. argv.nodeOptions.push(argv[optIdx]);
  404. }
  405. }
  406. }
  407. // some options are shared
  408. if (argv.ipcpath) {
  409. argv.nodeOptions.push('--ipcpath', argv.ipcpath);
  410. }
  411. if (argv.nodeOptions && argv.nodeOptions.syncmode) {
  412. argv.push('--syncmode', argv.nodeOptions.syncmode);
  413. }