diff --git a/app/actions/DaemonActions.js b/app/actions/DaemonActions.js
index b1b1e5631a..c47dca1e66 100644
--- a/app/actions/DaemonActions.js
+++ b/app/actions/DaemonActions.js
@@ -89,7 +89,10 @@ export const checkDecreditonVersion = () => (dispatch, getState) => {
}
})
.catch(function (error) {
- console.log("Unable to check latest decrediton release version.", error);
+ console.error(
+ "Unable to check latest decrediton release version.",
+ error
+ );
});
};
@@ -260,7 +263,7 @@ export const shutdownApp = () => async (dispatch, getState) => {
return dispatch(finalShutdown());
} catch (error) {
const openOrder =
- error.indexOf("cannot log out with active orders", 0) > -1;
+ String(error).indexOf("cannot log out with active orders", 0) > -1;
dispatch({ type: DEX_LOGOUT_FAILED, error, openOrder });
dispatch(showCantCloseModal());
}
diff --git a/app/actions/DexActions.js b/app/actions/DexActions.js
index 8808dc6cc3..b66ceb6b39 100644
--- a/app/actions/DexActions.js
+++ b/app/actions/DexActions.js
@@ -9,8 +9,8 @@ import { EXTERNALREQUEST_DEX } from "main_dev/externalRequests";
import * as configConstants from "constants/config";
import { makeRandomString } from "helpers";
-const sendSync = (...args) => {
- const res = ipcRenderer.sendSync(...args);
+const invoke = async (...args) => {
+ const res = await ipcRenderer.invoke(...args);
if (res instanceof Error) {
throw res;
}
@@ -56,7 +56,7 @@ export const DEX_STARTUP_ATTEMPT = "DEX_STARTUP_ATTEMPT";
export const DEX_STARTUP_FAILED = "DEX_STARTUP_FAILED";
export const DEX_STARTUP_SUCCESS = "DEX_STARTUP_SUCCESS";
-export const startDex = () => (dispatch, getState) => {
+export const startDex = () => async (dispatch, getState) => {
dispatch({ type: DEX_STARTUP_ATTEMPT });
const isTestnet = sel.isTestNet(getState());
const {
@@ -65,7 +65,7 @@ export const startDex = () => (dispatch, getState) => {
const walletPath = getWalletPath(isTestnet, walletName);
try {
- const res = sendSync("start-dex", walletPath, isTestnet);
+ const res = await invoke("start-dex", walletPath, isTestnet);
dispatch({ type: DEX_STARTUP_SUCCESS, serverAddress: res });
dispatch(dexCheckInit());
} catch (error) {
@@ -78,10 +78,10 @@ export const DEX_CHECKINIT_ATTEMPT = "DEX_CHECKINIT_ATTEMPT";
export const DEX_CHECKINIT_FAILED = "DEX_CHECKINIT_FAILED";
export const DEX_CHECKINIT_SUCCESS = "DEX_CHECKINIT_SUCCESS";
-export const dexCheckInit = () => (dispatch) => {
+export const dexCheckInit = () => async (dispatch) => {
dispatch({ type: DEX_CHECKINIT_ATTEMPT });
try {
- const res = sendSync("check-init-dex");
+ const res = await invoke("check-init-dex");
dispatch({ type: DEX_CHECKINIT_SUCCESS, res });
} catch (error) {
dispatch({ type: DEX_CHECKINIT_FAILED, error });
@@ -96,7 +96,7 @@ export const stopDex = () => (dispatch, getState) => {
return;
}
- ipcRenderer.send("stop-dex");
+ invoke("stop-dex");
dispatch({ type: DEX_STOPPED });
};
@@ -104,14 +104,14 @@ export const DEX_INIT_ATTEMPT = "DEX_INIT_ATTEMPT";
export const DEX_INIT_SUCCESS = "DEX_INIT_SUCCESS";
export const DEX_INIT_FAILED = "DEX_INIT_FAILED";
-export const initDex = (passphrase) => (dispatch, getState) => {
+export const initDex = (passphrase) => async (dispatch, getState) => {
dispatch({ type: DEX_INIT_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_INIT_FAILED, error: "Dex isn't active" });
return;
}
try {
- sendSync("init-dex", passphrase);
+ await invoke("init-dex", passphrase);
dispatch({ type: DEX_INIT_SUCCESS });
// Request current user information
dispatch(userDex());
@@ -125,14 +125,14 @@ export const DEX_LOGIN_ATTEMPT = "DEX_LOGIN_ATTEMPT";
export const DEX_LOGIN_SUCCESS = "DEX_LOGIN_SUCCESS";
export const DEX_LOGIN_FAILED = "DEX_LOGIN_FAILED";
-export const loginDex = (passphrase) => (dispatch, getState) => {
+export const loginDex = (passphrase) => async (dispatch, getState) => {
dispatch({ type: DEX_LOGIN_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_LOGIN_FAILED, error: "Dex isn't active" });
return;
}
try {
- sendSync("login-dex", passphrase);
+ await invoke("login-dex", passphrase);
dispatch({ type: DEX_LOGIN_SUCCESS });
// Request current user information
dispatch(userDex());
@@ -146,24 +146,17 @@ export const DEX_LOGOUT_ATTEMPT = "DEX_LOGOUT_ATTEMPT";
export const DEX_LOGOUT_SUCCESS = "DEX_LOGOUT_SUCCESS";
export const DEX_LOGOUT_FAILED = "DEX_LOGOUT_FAILED";
-export const logoutDex = () =>
- new Promise((resolve, reject) => {
- try {
- sendSync("logout-dex");
- return resolve(true);
- } catch (error) {
- return reject(error);
- }
- });
+export const logoutDex = () => invoke("logout-dex");
export const DEX_CREATEWALLET_ATTEMPT = "DEX_CREATEWALLET_ATTEMPT";
export const DEX_CREATEWALLET_SUCCESS = "DEX_CREATEWALLET_SUCCESS";
export const DEX_CREATEWALLET_FAILED = "DEX_CREATEWALLET_FAILED";
-export const createWalletDex = (passphrase, appPassphrase, accountName) => (
- dispatch,
- getState
-) => {
+export const createWalletDex = (
+ passphrase,
+ appPassphrase,
+ accountName
+) => async (dispatch, getState) => {
dispatch({ type: DEX_CREATEWALLET_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_CREATEWALLET_FAILED, error: "Dex isn't active" });
@@ -180,7 +173,7 @@ export const createWalletDex = (passphrase, appPassphrase, accountName) => (
const rpclisten = rpcCreds.rpcListen;
const rpccert = rpcCreds.rpcCert;
const assetID = 42;
- sendSync(
+ await invoke(
"create-wallet-dex",
assetID,
passphrase,
@@ -208,7 +201,7 @@ export const btcCreateWalletDex = (
passphrase,
appPassphrase,
btcWalletName
-) => (dispatch, getState) => {
+) => async (dispatch, getState) => {
dispatch({ type: BTC_CREATEWALLET_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: BTC_CREATEWALLET_FAILED, error: "Dex isn't active" });
@@ -226,7 +219,7 @@ export const btcCreateWalletDex = (
? btcConfig.test.rpcbind + ":" + btcConfig.test.rpcport
: btcConfig.rpcbind + ":" + btcConfig.rpcport;
const assetID = 0;
- sendSync(
+ await invoke(
"create-wallet-dex",
assetID,
passphrase,
@@ -257,14 +250,14 @@ export const DEX_USER_ATTEMPT = "DEX_USER_ATTEMPT";
export const DEX_USER_SUCCESS = "DEX_USER_SUCCESS";
export const DEX_USER_FAILED = "DEX_USER_FAILED";
-export const userDex = () => (dispatch, getState) => {
+export const userDex = () => async (dispatch, getState) => {
dispatch({ type: DEX_USER_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_USER_FAILED, error: "Dex isn't active" });
return;
}
try {
- const user = sendSync("user-dex");
+ const user = await invoke("user-dex");
dispatch({ type: DEX_USER_SUCCESS, user });
} catch (error) {
dispatch({ type: DEX_USER_FAILED, error });
@@ -276,14 +269,14 @@ export const DEX_GETCONFIG_ATTEMPT = "DEX_GETCONFIG_ATTEMPT";
export const DEX_GETCONFIG_SUCCESS = "DEX_GETCONFIG_SUCCESS";
export const DEX_GETCONFIG_FAILED = "DEX_GETCONFIG_FAILED";
-export const getConfigDex = (addr) => (dispatch, getState) => {
+export const getConfigDex = (addr) => async (dispatch, getState) => {
dispatch({ type: DEX_GETCONFIG_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_GETCONFIG_FAILED, error: "Dex isn't active" });
return;
}
try {
- const config = sendSync("get-config-dex", addr);
+ const config = await invoke("get-config-dex", addr);
dispatch({ type: DEX_GETCONFIG_SUCCESS, config, addr });
} catch (error) {
dispatch({ type: DEX_GETCONFIG_FAILED, error });
@@ -295,7 +288,7 @@ export const DEX_REGISTER_ATTEMPT = "DEX_REGISTER_ATTEMPT";
export const DEX_REGISTER_SUCCESS = "DEX_REGISTER_SUCCESS";
export const DEX_REGISTER_FAILED = "DEX_REGISTER_FAILED";
-export const registerDex = (appPass) => (dispatch, getState) => {
+export const registerDex = (appPass) => async (dispatch, getState) => {
dispatch({ type: DEX_REGISTER_ATTEMPT });
if (!sel.dexActive(getState())) {
dispatch({ type: DEX_REGISTER_FAILED, error: "Dex isn't acteive" });
@@ -309,7 +302,7 @@ export const registerDex = (appPass) => (dispatch, getState) => {
}
const fee = config.feeAsset.amount;
try {
- sendSync("register-dex", appPass, addr, fee);
+ await invoke("register-dex", appPass, addr, fee);
dispatch({ type: DEX_REGISTER_SUCCESS });
// Request current user information
dispatch(userDex());
@@ -331,7 +324,7 @@ export const DEX_LAUNCH_WINDOW_ATTEMPT = "DEX_LAUNCH_WINDOW_ATTEMPT";
export const DEX_LAUNCH_WINDOW_SUCCESS = "DEX_LAUNCH_WINDOW_SUCCESS";
export const DEX_LAUNCH_WINDOW_FAILED = "DEX_LAUNCH_WINDOW_FAILED";
-export const launchDexWindow = () => (dispatch, getState) => {
+export const launchDexWindow = () => async (dispatch, getState) => {
const {
dex: { dexServerAddress }
} = getState();
@@ -342,7 +335,7 @@ export const launchDexWindow = () => (dispatch, getState) => {
}
try {
const serverAddress = dexServerAddress;
- sendSync("launch-dex-window", serverAddress);
+ await invoke("launch-dex-window", serverAddress);
dispatch({ type: DEX_LAUNCH_WINDOW_SUCCESS });
// Request current user information
dispatch(userDex());
@@ -360,10 +353,10 @@ export const CHECK_BTC_CONFIG_SUCCESS_UPDATE_NEEDED =
export const CHECK_BTC_CONFIG_SUCCESS_NEED_INSTALL =
"CHECK_BTC_CONFIG_SUCCESS_NEED_INSTALL";
-export const checkBTCConfig = () => (dispatch, getState) => {
+export const checkBTCConfig = () => async (dispatch, getState) => {
dispatch({ type: CHECK_BTC_CONFIG_ATTEMPT });
try {
- const res = sendSync("check-btc-config");
+ const res = await invoke("check-btc-config");
if (
res.rpcuser &&
res.rpcpassword &&
@@ -397,7 +390,7 @@ export const UPDATE_BTC_CONFIG_ATTEMPT = "UPDATE_BTC_CONFIG_ATTEMPT";
export const UPDATE_BTC_CONFIG_SUCCESS = "UPDATE_BTC_CONFIG_SUCCESS";
export const UPDATE_BTC_CONFIG_FAILED = "UPDATE_BTC_CONFIG_FAILED";
-export const updateBTCConfig = () => (dispatch, getState) => {
+export const updateBTCConfig = () => async (dispatch, getState) => {
dispatch({ type: UPDATE_BTC_CONFIG_ATTEMPT });
try {
const rpcuser = makeRandomString(12);
@@ -405,7 +398,7 @@ export const updateBTCConfig = () => (dispatch, getState) => {
const rpcbind = "127.0.0.1";
const rpcport = sel.isTestNet(getState()) ? "18332" : "8332";
const testnet = sel.isTestNet(getState());
- const res = sendSync(
+ const res = await invoke(
"update-btc-config",
rpcuser,
rpcpassword,
diff --git a/app/actions/LNActions.js b/app/actions/LNActions.js
index 93fd53bd93..fd7cbe9ba8 100644
--- a/app/actions/LNActions.js
+++ b/app/actions/LNActions.js
@@ -6,7 +6,8 @@ import { getWalletCfg } from "../config";
import { getWalletPath } from "main_dev/paths";
import { getNextAccountAttempt } from "./ControlActions";
import * as cfgConstants from "constants/config";
-import { isString, isNumber } from "lodash";
+import { isNumber } from "lodash";
+import { invoke } from "helpers/electronRenderer";
export const CLOSETYPE_COOPERATIVE_CLOSE = 0;
export const CLOSETYPE_LOCAL_FORCE_CLOSE = 1;
@@ -97,7 +98,7 @@ export const startDcrlnd = (
stage: LNWALLET_STARTUPSTAGE_STARTDCRLND,
type: LNWALLET_STARTUP_CHANGEDSTAGE
});
- const res = ipcRenderer.sendSync(
+ const res = await invoke(
"start-dcrlnd",
lnAccount,
walletPort,
@@ -106,9 +107,6 @@ export const startDcrlnd = (
isTestnet,
autopilotEnabled
);
- if (isString(res) || res instanceof Error) {
- throw res;
- }
dcrlndCreds = res;
} catch (error) {
dispatch({ type: LNWALLET_STARTUP_FAILED });
@@ -120,7 +118,7 @@ export const startDcrlnd = (
// dcrlnd is already running so if some error occurs we need to shut it down.
const cleanup = () => {
// Force dcrlnd to stop.
- ipcRenderer.send("stop-dcrlnd");
+ invoke("stop-dcrlnd");
dispatch({ type: LNWALLET_STARTUP_FAILED });
if (creating) {
@@ -223,7 +221,7 @@ export const stopDcrlnd = () => (dispatch, getState) => {
return;
}
- ipcRenderer.send("stop-dcrlnd");
+ invoke("stop-dcrlnd");
dispatch({ type: LNWALLET_DCRLND_STOPPED });
};
@@ -240,7 +238,7 @@ export const checkLnWallet = () => async (dispatch) => {
}
// Check whether the app knows of a previously running dcrlnd instance.
- const creds = ipcRenderer.sendSync("dcrlnd-creds");
+ const creds = await invoke("dcrlnd-creds");
if (!creds) {
return;
}
diff --git a/app/actions/StatisticsActions.js b/app/actions/StatisticsActions.js
index b26362c551..45026aefe0 100644
--- a/app/actions/StatisticsActions.js
+++ b/app/actions/StatisticsActions.js
@@ -1,6 +1,6 @@
import * as wallet from "wallet";
import * as sel from "selectors";
-import fs from "fs";
+import { fs } from "wallet-preload-shim";
import { isNumber, isNil, isUndefined } from "lodash";
import { endOfDay, formatLocalISODate, isSameDate } from "helpers";
import {
@@ -380,7 +380,7 @@ export const exportStatToCSV = (opts) => (dispatch, getState) => {
const seriesNames = allSeries.map((s) => s.name);
const headerLine = csvLine(["time", ...seriesNames]);
- fd = fs.openSync(csvFilename, "w", 0o600);
+ fd = fs.openWritable(csvFilename);
fs.writeSync(fd, headerLine);
fs.writeSync(fd, ln);
} catch (err) {
diff --git a/app/components/views/GetStartedPage/hooks.js b/app/components/views/GetStartedPage/hooks.js
index c0960faeb3..e5584715ca 100644
--- a/app/components/views/GetStartedPage/hooks.js
+++ b/app/components/views/GetStartedPage/hooks.js
@@ -226,16 +226,24 @@ export const useGetStarted = () => {
});
const getError = useCallback((serviceError) => {
if (!serviceError) return;
+
// We can return errors in the form of react component, which are objects.
// So we handle them first.
if (React.isValidElement(serviceError)) {
return serviceError;
}
- // If the errors is an object but not a react component, we strigfy it so we can
- // render.
+
+ // If the error is an instance of the Error class, extract the message.
+ if (serviceError instanceof Error) {
+ return serviceError.message;
+ }
+
+ // If the error is an object but not a react component, we stringify it so
+ // we can render it.
if (isObject(serviceError)) {
return JSON.stringify(serviceError);
}
+
return serviceError;
}, []);
const error = useMemo(
diff --git a/app/components/views/LNPage/WalletTab/WalletTab.jsx b/app/components/views/LNPage/WalletTab/WalletTab.jsx
index ef986870a5..d9afff09a2 100644
--- a/app/components/views/LNPage/WalletTab/WalletTab.jsx
+++ b/app/components/views/LNPage/WalletTab/WalletTab.jsx
@@ -3,7 +3,6 @@ import { FormattedMessage as T } from "react-intl";
import { DescriptionHeader } from "layout";
import { Subtitle } from "shared";
import { InfoDocModalButton } from "buttons";
-import { ConfirmModal } from "modals";
import BalanceHeader from "./BalanceHeader/BalanceHeader";
import BackupInfoHeader from "./BackupInfoHeader/BackupInfoHeader";
import BackupInfoDetails from "./BackupInfoDetails/BackupInfoDetails";
@@ -27,11 +26,8 @@ const WalletTab = () => {
info,
scbPath,
scbUpdatedTime,
- confirmFileOverwrite,
onBackup,
- onVerifyBackup,
- onCancelFileOverwrite,
- onConfirmFileOverwrite
+ onVerifyBackup
} = useWalletTab();
const { confirmedBalance, unconfirmedBalance, totalBalance } = walletBalances;
@@ -71,28 +67,6 @@ const WalletTab = () => {
onVerifyBackup={onVerifyBackup}
/>
-
- }
- modalContent={
- <>
- {confirmFileOverwrite}
- }}
- />
- >
- }
- />
>
);
};
diff --git a/app/components/views/LNPage/WalletTab/hooks.js b/app/components/views/LNPage/WalletTab/hooks.js
index 252028f608..048e181abb 100644
--- a/app/components/views/LNPage/WalletTab/hooks.js
+++ b/app/components/views/LNPage/WalletTab/hooks.js
@@ -1,6 +1,5 @@
import { ipcRenderer } from "electron";
-import fs from "fs";
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
import { useLNPage } from "../hooks";
export function useWalletTab() {
@@ -14,38 +13,16 @@ export function useWalletTab() {
scbUpdatedTime
} = useLNPage();
- const [confirmFileOverwrite, setConfirmFileOverwrite] = useState(null);
-
useEffect(() => {
setTimeout(() => updateWalletBalances(), 1000);
}, [updateWalletBalances]);
- const onConfirmFileOverwrite = async () => {
- const filePath = confirmFileOverwrite;
- if (!filePath) {
- return;
- }
- setConfirmFileOverwrite(null);
- await exportBackup(filePath);
- };
-
- const onCancelFileOverwrite = () => {
- setConfirmFileOverwrite(null);
- };
-
const onBackup = async () => {
- setConfirmFileOverwrite(null);
const { filePath } = await ipcRenderer.invoke("show-save-dialog");
if (!filePath) {
return;
}
- // If this file already exists, show the confirmation modal.
- if (fs.existsSync(filePath)) {
- setConfirmFileOverwrite(filePath);
- return;
- }
-
await exportBackup(filePath);
};
@@ -64,10 +41,7 @@ export function useWalletTab() {
info,
scbPath,
scbUpdatedTime,
- confirmFileOverwrite,
onBackup,
- onVerifyBackup,
- onCancelFileOverwrite,
- onConfirmFileOverwrite
+ onVerifyBackup
};
}
diff --git a/app/helpers/electronRenderer.js b/app/helpers/electronRenderer.js
new file mode 100644
index 0000000000..5eb8a0ec29
--- /dev/null
+++ b/app/helpers/electronRenderer.js
@@ -0,0 +1,9 @@
+import { ipcRenderer } from "electron";
+
+export const invoke = async (...args) => {
+ const res = await ipcRenderer.invoke(...args);
+ if (res instanceof Error) {
+ throw res;
+ }
+ return res;
+};
diff --git a/app/i18n/extracted/static/dialogs.json b/app/i18n/extracted/static/dialogs.json
new file mode 100644
index 0000000000..3664a8e823
--- /dev/null
+++ b/app/i18n/extracted/static/dialogs.json
@@ -0,0 +1,14 @@
+[
+ {
+ "id": "dialogs.confirmFileOverwrite",
+ "defaultMessage": "Overwrite contents of file {filename}?"
+ },
+ {
+ "id": "dialogs.yesButton",
+ "defaultMessage": "Yes"
+ },
+ {
+ "id": "dialogs.cancelButton",
+ "defaultMessage": "Cancel"
+ }
+]
diff --git a/app/i18n/extracted/static/index.js b/app/i18n/extracted/static/index.js
index 9d0fdd8b5a..98c46cfa6a 100644
--- a/app/i18n/extracted/static/index.js
+++ b/app/i18n/extracted/static/index.js
@@ -1,5 +1,5 @@
// Add any new files to this array
-const staticFilesData = [require("./menus.json")];
+const staticFilesData = [require("./menus.json"), require("./dialogs.json")];
// staticDefaults are the flattened messages for all statically-defined
// messages (all messages in this directory).
diff --git a/app/main.development.js b/app/main.development.js
index 914d2a6b41..93a3af8c90 100644
--- a/app/main.development.js
+++ b/app/main.development.js
@@ -118,7 +118,7 @@ import {
// setPath as decrediton
app.setPath("userData", getAppDataDirectory());
-app.allowRendererProcessReuse = false;
+app.allowRendererProcessReuse = true;
// See if we can communicate with the dexc lib.
const dexPingRes = __pingDex("__pong");
@@ -281,6 +281,28 @@ const installExtensions = async () => {
const { ipcMain } = require("electron");
+// handleEvent listens on the given channel for ipcRenderer.invoke() calls and
+// returns the result of the given function or an Error instance if the function
+// failed.
+const handleEvent = (channel, fn) => {
+ ipcMain.handle(channel, async (...args) => {
+ try {
+ return await fn(...args);
+ } catch (error) {
+ if (error instanceof Error) {
+ return error;
+ } else {
+ return new Error(err);
+ }
+ }
+ });
+};
+
+// handle is the same as handleEvent but pops off the first arg ("event" object)
+// before calling fn.
+const handle = (channel, fn) =>
+ handleEvent(channel, (event, ...args) => fn(...args));
+
ipcMain.on("reload-allowed-external-request", (event) => {
reloadAllowedExternalRequests();
event.returnValue = true;
@@ -310,14 +332,13 @@ ipcMain.on("get-available-wallets", (event, network) => {
event.returnValue = getAvailableWallets(network);
});
-ipcMain.on("start-daemon", async (event, params, testnet) => {
- const startedValues = await startDaemon(params, testnet, reactIPC);
- event.sender.send("start-daemon-response", startedValues);
-});
+handle("start-daemon", (params, testnet) =>
+ startDaemon(params, testnet, reactIPC)
+);
-ipcMain.on("connect-daemon", (event, { rpcCreds }) => {
- event.returnValue = connectRpcDaemon(mainWindow, rpcCreds);
-});
+handle("connect-daemon", ({ rpcCreds }) =>
+ connectRpcDaemon(mainWindow, rpcCreds)
+);
ipcMain.on("delete-daemon", (event, appData, testnet) => {
event.returnValue = deleteDaemon(appData, testnet);
@@ -344,9 +365,9 @@ ipcMain.on("stop-wallet", (event) => {
event.returnValue = stopWallet();
});
-ipcMain.on("start-wallet", (event, walletPath, testnet, rpcCreds) => {
+handle("start-wallet", (walletPath, testnet, rpcCreds) => {
const { rpcUser, rpcPass, rpcListen, rpcCert } = rpcCreds;
- event.returnValue = startWallet(
+ return startWallet(
mainWindow,
daemonIsAdvanced,
testnet,
@@ -359,177 +380,31 @@ ipcMain.on("start-wallet", (event, walletPath, testnet, rpcCreds) => {
);
});
-ipcMain.on(
- "start-dcrlnd",
- async (
- event,
- walletAccount,
- walletPort,
- rpcCreds,
- walletPath,
- testnet,
- autopilotEnabled
- ) => {
- try {
- event.returnValue = await startDcrlnd(
- walletAccount,
- walletPort,
- rpcCreds,
- walletPath,
- testnet,
- autopilotEnabled
- );
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
- }
-);
+handle("start-dcrlnd", startDcrlnd);
-ipcMain.on("stop-dcrlnd", async (event) => {
- event.returnValue = await stopDcrlnd();
-});
+handle("stop-dcrlnd", stopDcrlnd);
-ipcMain.on("check-init-dex", async (event) => {
- try {
- event.returnValue = await checkInitDex();
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("check-init-dex", checkInitDex);
-ipcMain.on("init-dex", async (event, passphrase) => {
- try {
- event.returnValue = await initDex(passphrase);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("init-dex", initDex);
-ipcMain.on("login-dex", async (event, passphrase) => {
- try {
- event.returnValue = await loginDex(passphrase);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("login-dex", loginDex);
-ipcMain.on("logout-dex", async (event) => {
- try {
- event.returnValue = await logoutDex();
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("logout-dex", logoutDex);
-ipcMain.on(
- "create-wallet-dex",
- async (
- event,
- assetID,
- passphrase,
- appPassphrase,
- account,
- rpcuser,
- rpcpass,
- rpclisten,
- rpccert
- ) => {
- try {
- event.returnValue = await createWalletDex(
- assetID,
- passphrase,
- appPassphrase,
- account,
- rpcuser,
- rpcpass,
- rpclisten,
- rpccert
- );
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
- }
-);
+handle("create-wallet-dex", createWalletDex);
-ipcMain.on("get-config-dex", async (event, addr) => {
- try {
- event.returnValue = await getConfigDex(addr);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("get-config-dex", getConfigDex);
-ipcMain.on("register-dex", async (event, appPass, addr, fee) => {
- try {
- event.returnValue = await registerDex(appPass, addr, fee);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("register-dex", registerDex);
-ipcMain.on("user-dex", async (event) => {
- try {
- event.returnValue = await userDex();
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("user-dex", userDex);
-ipcMain.on("start-dex", async (event, walletPath, testnet) => {
- try {
- event.returnValue = await startDex(walletPath, testnet);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("start-dex", startDex);
-ipcMain.on("stop-dex", async (event) => {
- event.returnValue = await stopDex();
-});
+handle("stop-dex", stopDex);
-ipcMain.on("launch-dex-window", async (event, serverAddress) => {
- event.returnValue = await createDexWindow(serverAddress);
-});
+handle("launch-dex-window", createDexWindow);
function createDexWindow(serverAddress) {
const child = new BrowserWindow({
@@ -543,88 +418,23 @@ function createDexWindow(serverAddress) {
});
}
-ipcMain.on("check-btc-config", async (event) => {
- try {
- event.returnValue = await getCurrentBitcoinConfig();
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("check-btc-config", getCurrentBitcoinConfig);
-ipcMain.on(
- "update-btc-config",
- async (event, rpcuser, rpcpassword, rpcbind, rpcport, testnet) => {
- try {
- event.returnValue = await updateDefaultBitcoinConfig(
- rpcuser,
- rpcpassword,
- rpcbind,
- rpcport,
- testnet
- );
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
- }
-);
+handle("update-btc-config", updateDefaultBitcoinConfig);
-ipcMain.on("dcrlnd-creds", (event) => {
- if (GetDcrlndPID() && GetDcrlndPID() !== -1) {
- event.returnValue = GetDcrlndCreds();
- } else {
- event.returnValue = null;
- }
-});
+handle("dcrlnd-creds", () => (GetDcrlndPID() !== -1 ? GetDcrlndCreds() : null));
-ipcMain.on("ln-scb-info", (event, walletPath, testnet) => {
- try {
- event.returnValue = lnScbInfo(walletPath, testnet);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("ln-scb-info", lnScbInfo);
-ipcMain.on("ln-remove-dir", (event, walletName, testnet) => {
- try {
- event.returnValue = removeDcrlnd(walletName, testnet);
- } catch (error) {
- if (!(error instanceof Error)) {
- event.returnValue = new Error(error);
- } else {
- event.returnValue = error;
- }
- }
-});
+handle("ln-remove-dir", removeDcrlnd);
-ipcMain.on("check-daemon", () => {
- getBlockChainInfo();
-});
+handle("check-daemon", getBlockChainInfo);
-ipcMain.on("daemon-getinfo", () => {
- getDaemonInfo();
-});
+handle("daemon-getinfo", getDaemonInfo);
-ipcMain.on("clean-shutdown", async function (event) {
- const stopped = await cleanShutdown(
- mainWindow,
- app,
- GetDcrdPID(),
- GetDcrwPID()
- );
- event.sender.send("clean-shutdown-finished", stopped);
-});
+handle("clean-shutdown", () =>
+ cleanShutdown(mainWindow, app, GetDcrdPID(), GetDcrwPID())
+);
let reactIPC;
ipcMain.on("register-for-errors", function (event) {
@@ -762,6 +572,26 @@ ipcMain.handle("show-open-dialog", async (event, opts) => {
return await dialog.showOpenDialog(allowedOpts);
});
+ipcMain.on("confirm-file-overwrite", (event, filename) => {
+ const cfgLocale = globalCfg.get(LOCALE);
+ const locale = locales.find((value) => value.key === cfgLocale);
+ const msgTemplate = locale.messages["dialogs.confirmFileOverwrite"];
+ const msg = msgTemplate.replace("{filename}", filename);
+ const yesBtn = locale.messages["dialogs.yesButton"];
+ const cancelBtn = locale.messages["dialogs.cancelButton"];
+ const buttons = [cancelBtn, yesBtn];
+
+ const opts = {
+ message: msg,
+ type: "question",
+ buttons: buttons,
+ defaultId: buttons.indexOf(cancelBtn),
+ cancelId: buttons.indexOf(cancelBtn)
+ };
+ const res = dialog.showMessageBoxSync(mainWindow, opts);
+ event.returnValue = res === buttons.indexOf(yesBtn);
+});
+
function setMenuLocale(locale) {
//Removes previous listeners of "context-menu" event.
mainWindow.webContents._events["context-menu"] = [];
@@ -839,6 +669,8 @@ app.on("ready", async () => {
}
let url = `file://${__dirname}/dist/app.html`;
+ const path = require("path"); // eslint-disable-line
+ const preloadPath = path.resolve(__dirname, "dist", "wallet-preload.js");
if (process.env.NODE_ENV === "development") {
// Load from the webpack dev server with hot module replacement.
const port = process.env.PORT || 3000;
@@ -860,7 +692,8 @@ app.on("ready", async () => {
devTools: true,
contextIsolation: false,
webSecurity: false,
- enableRemoteModule
+ enableRemoteModule,
+ preload: preloadPath
},
icon: __dirname + "/icon.png"
};
diff --git a/app/main_dev/ipc.js b/app/main_dev/ipc.js
index 5ab49e6e53..72ccf7d940 100644
--- a/app/main_dev/ipc.js
+++ b/app/main_dev/ipc.js
@@ -119,11 +119,10 @@ export const startDaemon = async (params, testnet, reactIPC) => {
}
const appdata = params && params.appdata;
- const started = await launchDCRD(reactIPC, testnet, appdata);
- return started;
+ return await launchDCRD(reactIPC, testnet, appdata);
} catch (err) {
logger.log("error", "error launching dcrd: " + err);
- return { err };
+ throw err;
}
};
@@ -159,7 +158,7 @@ export const removeWallet = (testnet, walletPath) => {
}
};
-export const startWallet = (
+export const startWallet = async (
mainWindow,
daemonIsAdvanced,
testnet,
@@ -172,8 +171,7 @@ export const startWallet = (
) => {
if (GetDcrwPID()) {
logger.log("info", "dcrwallet already started " + GetDcrwPID());
- mainWindow.webContents.send("dcrwallet-port", GetDcrwPort());
- return GetDcrwPID();
+ return { pid: GetDcrwPID(), port: GetDcrwPort() };
}
initWalletCfg(testnet, walletPath);
checkNoLegacyWalletConfig(
@@ -182,7 +180,7 @@ export const startWallet = (
rpcUser && rpcPass && rpcHost && rpcListen
);
try {
- return launchDCRWallet(
+ return await launchDCRWallet(
mainWindow,
daemonIsAdvanced,
walletPath,
@@ -195,6 +193,7 @@ export const startWallet = (
);
} catch (e) {
logger.log("error", "error launching dcrwallet: " + e);
+ throw e;
}
};
diff --git a/app/main_dev/launch.js b/app/main_dev/launch.js
index 6498526a54..266074abbb 100644
--- a/app/main_dev/launch.js
+++ b/app/main_dev/launch.js
@@ -59,6 +59,13 @@ let dcrdSocket,
heightIsSynced,
selectedWallet = null;
+// These lists track resolve() functions for getinfo/getblockchaininfo daemon
+// calls. When the renderer requests one of those calls, the corresponding
+// resolve function is added to this list. When the response is received from
+// dcrd, the resolve is shifted, so this behaves as a FIFO.
+const getInfoResolves = [];
+const getBlockchainInfoResolves = [];
+
const callDEX = (func, params) => {
// TODO: this can be done globally once ipcRenderer doesn't import launch.js anymore.
const { getNativeFunction, getBufferPointer } = require("sbffi");
@@ -589,7 +596,7 @@ const DecodeDaemonIPCData = (data, cb) => {
}
};
-export const launchDCRWallet = (
+export const launchDCRWallet = async (
mainWindow,
daemonIsAdvanced,
walletPath,
@@ -628,18 +635,22 @@ export const launchDCRWallet = (
const dcrwExe = getExecutablePath("dcrwallet", argv.custombinpath);
if (!fs.existsSync(dcrwExe)) {
- logger.log(
- "error",
- "The dcrwallet executable does not exist. Expected to find it at " +
- dcrwExe
- );
- return;
+ const msg = `The dcrwallet executable does not exist. Expected to find it at ${dcrwExe}`;
+ logger.log("error", msg);
+ throw new Error(msg);
}
+ // Create a promise that will be resolved once the dcrwallet port is determined.
+ let portResolve, portReject;
+ const portPromise = new Promise((resolve, reject) => {
+ portResolve = resolve;
+ portReject = reject;
+ });
+
const notifyGrpcPort = (port) => {
dcrwPort = port;
- logger.log("info", "wallet grpc running on port", port);
- mainWindow.webContents.send("dcrwallet-port", port);
+ logger.log("info", "wallet grpc running on port %d", port);
+ portResolve(dcrwPort);
};
const decodeDcrwIPC = (data) =>
@@ -654,6 +665,7 @@ export const launchDCRWallet = (
"error",
"GRPC port not found on IPC channel to dcrwallet: " + intf
);
+ portReject("dcrwallet gRPC port not found");
}
}
if (mtype === "issuedclientcertificate") {
@@ -771,7 +783,8 @@ export const launchDCRWallet = (
logger.log("info", "dcrwallet started with pid:" + dcrwPID);
dcrwallet.unref();
- return dcrwPID;
+ const port = await portPromise;
+ return { pid: dcrwPID, port: port };
};
export const launchDCRLnd = (
@@ -1073,90 +1086,88 @@ export const connectRpcDaemon = async (mainWindow, rpcCreds) => {
rpc_port = rpcport;
}
- try {
- // During the first startup, the rpc.cert file might not exist for a few
- // seconds. In that case, we wait up to 30s before failing this call.
- let tries = 0;
- const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));
- while (tries++ < 30 && !fs.existsSync(rpc_cert)) await sleep(1000);
- if (!fs.existsSync(rpc_cert)) {
- return mainWindow.webContents.send("connectRpcDaemon-response", {
- error: new Error("rpc cert '" + rpc_cert + "' does not exist")
- });
- }
+ // Return early if already connected.
+ if (dcrdSocket && dcrdSocket.readyState === dcrdSocket.OPEN) {
+ return { connected: true };
+ }
- const cert = fs.readFileSync(rpc_cert);
- const url = `${rpc_host}:${rpc_port}`;
- if (dcrdSocket && dcrdSocket.readyState === dcrdSocket.OPEN) {
- return mainWindow.webContents.send("connectRpcDaemon-response", {
- connected: true
- });
- }
- dcrdSocket = new webSocket(`wss://${url}/ws`, {
- headers: {
- Authorization:
- "Basic " + Buffer.from(rpc_user + ":" + rpc_pass).toString("base64")
- },
- cert: cert,
- ecdhCurve: "secp521r1",
- ca: [cert]
- });
- dcrdSocket.on("open", function () {
- logger.log("info", "decrediton has connected to dcrd instance");
- return mainWindow.webContents.send("connectRpcDaemon-response", {
- connected: true
- });
- });
- dcrdSocket.on("error", function (error) {
- logger.log("error", `Error: ${error}`);
- return mainWindow.webContents.send("connectRpcDaemon-response", {
- connected: false,
- error: error.toString()
- });
- });
- dcrdSocket.on("message", function (data) {
- const parsedData = JSON.parse(data);
- const id = parsedData ? parsedData.id : "";
- switch (id) {
- case "getinfo":
- mainWindow.webContents.send(
- "check-getinfo-response",
- parsedData.result
- );
- break;
- case "getblockchaininfo": {
- const dataResults = parsedData.result || {};
- const blockCount = dataResults.blocks;
- const syncHeight = dataResults.syncheight;
- mainWindow.webContents.send("check-daemon-response", {
- blockCount,
- syncHeight
- });
- break;
- }
- }
- });
- dcrdSocket.on("close", () => {
- logger.log("info", "decrediton has disconnected to dcrd instance");
- });
- } catch (error) {
- return mainWindow.webContents.send("connectRpcDaemon-response", {
- connected: false,
- error
- });
+ // During the first startup, the rpc.cert file might not exist for a few
+ // seconds. In that case, we wait up to 30s before failing this call.
+ let tries = 0;
+ const sleep = (ms) => new Promise((ok) => setTimeout(ok, ms));
+ while (tries++ < 30 && !fs.existsSync(rpc_cert)) await sleep(1000);
+ if (!fs.existsSync(rpc_cert)) {
+ throw new Error(`rpc cert ${rpc_cert}' does not exist`);
}
+
+ const cert = fs.readFileSync(rpc_cert);
+ const url = `${rpc_host}:${rpc_port}`;
+
+ // Create a promise that will be resolved when the socket is opened.
+ let openResolve, openReject;
+ const openPromise = new Promise((resolve, reject) => {
+ openResolve = resolve;
+ openReject = reject;
+ });
+
+ // Attempt a new connection to dcrd.
+ dcrdSocket = new webSocket(`wss://${url}/ws`, {
+ headers: {
+ Authorization:
+ "Basic " + Buffer.from(rpc_user + ":" + rpc_pass).toString("base64")
+ },
+ cert: cert,
+ ecdhCurve: "secp521r1",
+ ca: [cert]
+ });
+ dcrdSocket.on("open", function () {
+ logger.log("info", "decrediton has connected to dcrd instance");
+ openResolve({ connected: true });
+ });
+ dcrdSocket.on("error", function (error) {
+ logger.log("error", `Error: ${error}`);
+ openReject(error);
+ });
+ dcrdSocket.on("message", function (data) {
+ const parsedData = JSON.parse(data);
+ const id = parsedData ? parsedData.id : "";
+ switch (id) {
+ case "getinfo":
+ getInfoResolves.shift()(parsedData.result);
+ break;
+ case "getblockchaininfo": {
+ const dataResults = parsedData.result || {};
+ const blockCount = dataResults.blocks;
+ const syncHeight = dataResults.syncheight;
+ getBlockchainInfoResolves.shift()({
+ blockCount,
+ syncHeight
+ });
+ break;
+ }
+ }
+ });
+ dcrdSocket.on("close", () => {
+ logger.log("info", "decrediton has disconnected to dcrd instance");
+ });
+
+ return openPromise;
};
export const getDaemonInfo = () =>
- dcrdSocket.send(
- '{"jsonrpc":"1.0","id":"getinfo","method":"getinfo","params":[]}'
- );
+ new Promise((resolve) => {
+ getInfoResolves.push(resolve);
+ dcrdSocket.send(
+ '{"jsonrpc":"1.0","id":"getinfo","method":"getinfo","params":[]}'
+ );
+ });
export const getBlockChainInfo = () =>
new Promise((resolve) => {
if (dcrdSocket && dcrdSocket.readyState === dcrdSocket.CLOSED) {
return resolve({});
}
+ getBlockchainInfoResolves.push(resolve);
dcrdSocket.send(
'{"jsonrpc":"1.0","id":"getblockchaininfo","method":"getblockchaininfo","params":[]}'
);
diff --git a/app/wallet-preload-shim.js b/app/wallet-preload-shim.js
new file mode 100644
index 0000000000..c3bafd2130
--- /dev/null
+++ b/app/wallet-preload-shim.js
@@ -0,0 +1 @@
+export const fs = window.fs;
diff --git a/app/wallet-preload.js b/app/wallet-preload.js
new file mode 100644
index 0000000000..d92ae02c00
--- /dev/null
+++ b/app/wallet-preload.js
@@ -0,0 +1,17 @@
+import * as fs from "wallet/fs";
+import { contextBridge } from "electron";
+
+// Elements in this object define the public API exported by the preload script.
+const api = {
+ fs: fs
+};
+
+try {
+ Object.keys(api).forEach((key) =>
+ contextBridge.exposeInMainWorld(key, api[key])
+ );
+} catch (error) {
+ // This happens when contextIsolation == false. Expose directly in the global
+ // window object.
+ Object.keys(api).forEach((key) => (window[key] = api[key]));
+}
diff --git a/app/wallet/app.js b/app/wallet/app.js
index 75914cb706..2c04fb4365 100644
--- a/app/wallet/app.js
+++ b/app/wallet/app.js
@@ -24,7 +24,9 @@ export const logOptionNoResponseData = (opts) => ({
// Formats a dynamic list of log arguments
const formatLogArgs = (msg, args) => {
const formatArg = (arg) => {
- if (isObject(arg) && isFunction(arg.toObject)) {
+ if (arg instanceof Error) {
+ return arg.toString();
+ } else if (isObject(arg) && isFunction(arg.toObject)) {
// requests/responses on the grpc system have a toObejct() func
return JSON.stringify(arg.toObject());
} else if (isUndefined(arg)) {
diff --git a/app/wallet/daemon.js b/app/wallet/daemon.js
index 1bee0107bb..2a0b628b23 100644
--- a/app/wallet/daemon.js
+++ b/app/wallet/daemon.js
@@ -3,20 +3,23 @@ import { ipcRenderer } from "electron";
import { withLog as log, logOptionNoResponseData } from "./app";
import { isString } from "lodash";
+// invoke calls ipcRenderer.invoke() with the given channel and args. If the
+// call returns an Error instance, then it throws an exception.
+const invoke = async (channel, ...args) => {
+ const res = await ipcRenderer.invoke(channel, ...args);
+ if (res instanceof Error) {
+ throw res;
+ }
+ return res;
+};
+
export const checkDecreditonVersion = log(
() => Promise.resolve(ipcRenderer.sendSync("check-version")),
"Check Decrediton release version"
);
export const startDaemon = log(
- (params, testnet) =>
- new Promise((resolve, reject) => {
- ipcRenderer.send("start-daemon", params, testnet);
- ipcRenderer.on("start-daemon-response", (event, started) => {
- if (started && started.err) reject(started.err);
- resolve(started);
- });
- }),
+ (params, testnet) => invoke("start-daemon", params, testnet),
"Start Daemon"
);
@@ -26,14 +29,11 @@ export const deleteDaemonData = log(
"Delete Daemon Data"
);
-export const cleanShutdown = () => {
- return new Promise((resolve) => {
- ipcRenderer.send("clean-shutdown");
- ipcRenderer.on("clean-shutdown-finished", (event, stopped) => {
- if (!stopped) throw "Error shutting down app";
- resolve(stopped);
- });
- });
+export const cleanShutdown = async () => {
+ // FIXME: clean-shutdown currently never fails. Revise this.
+ const stopped = await invoke("clean-shutdown");
+ if (!stopped) throw "Error shutting down app";
+ return stopped;
};
export const setIsWatchingOnly = log(
@@ -97,22 +97,7 @@ export const stopWallet = log(
export const startWallet = log(
(walletPath, testnet, rpcCreds) =>
- new Promise((resolve, reject) => {
- let port,
- pid = "";
-
- // resolveCheck must be done both on the dcrwallet-port event and on the
- // return of the sendSync call because we can't be certain which will happen first
- const resolveCheck = () => (pid && port ? resolve({ pid, port }) : null);
-
- ipcRenderer.once("dcrwallet-port", (e, p) => {
- port = p;
- resolveCheck();
- });
- pid = ipcRenderer.sendSync("start-wallet", walletPath, testnet, rpcCreds);
- if (!pid) reject("Error starting wallet");
- resolveCheck();
- }),
+ invoke("start-wallet", walletPath, testnet, rpcCreds),
"Start Wallet"
);
@@ -127,22 +112,17 @@ export const getPreviousWallet = log(
logOptionNoResponseData()
);
-export const getBlockCount = log(
- () =>
- new Promise((resolve) => {
- ipcRenderer.once("check-daemon-response", (e, info) => {
- const blockCount = isString(info.blockCount)
- ? parseInt(info.blockCount.trim())
- : info.blockCount;
- const syncHeight = isString(info.syncHeight)
- ? parseInt(info.syncHeight.trim())
- : info.syncHeight;
- resolve({ blockCount, syncHeight });
- });
- ipcRenderer.send("check-daemon");
- }),
- "Get Block Count"
-);
+export const getBlockCount = log(async () => {
+ const info = await invoke("check-daemon");
+ const blockCount = isString(info.blockCount)
+ ? parseInt(info.blockCount.trim())
+ : info.blockCount;
+ const syncHeight = isString(info.syncHeight)
+ ? parseInt(info.syncHeight.trim())
+ : info.syncHeight;
+
+ return { blockCount, syncHeight };
+}, "Get Block Count");
export const setHeightSynced = log(
() =>
@@ -155,17 +135,11 @@ export const setHeightSynced = log(
"set height is synced"
);
-export const getDaemonInfo = log(
- (rpcCreds) =>
- new Promise((resolve) => {
- ipcRenderer.once("check-getinfo-response", (e, info) => {
- const isTestnet = info ? info.testnet : null;
- resolve({ isTestnet });
- });
- ipcRenderer.send("daemon-getinfo", rpcCreds);
- }),
- "Get Daemon network info"
-);
+export const getDaemonInfo = log(async (rpcCreds) => {
+ const info = await invoke("daemon-getinfo", rpcCreds);
+ const isTestnet = info ? info.testnet : null;
+ return { isTestnet };
+}, "Get Daemon network info");
export const getAvailableWallets = log(
(network) =>
@@ -202,21 +176,13 @@ export const allowStakePoolHost = log(
"Allow StakePool Host"
);
-export const connectDaemon = log(
- (params) =>
- new Promise((resolve, reject) => {
- ipcRenderer.once("connectRpcDaemon-response", (e, info) => {
- if (info.connected) {
- resolve({ connected: true });
- }
- if (info.error) {
- reject({ connected: false, error: info.error });
- }
- });
- ipcRenderer.send("connect-daemon", params);
- }),
- "Connect Daemon"
-);
+export const connectDaemon = log(async (params) => {
+ try {
+ return await invoke("connect-daemon", params);
+ } catch (error) {
+ throw { connected: false, error: error.toString() };
+ }
+}, "Connect Daemon");
// TODO create a wallet/log and move those method not related to daemon to there.
diff --git a/app/wallet/fs.js b/app/wallet/fs.js
new file mode 100644
index 0000000000..534f2089bd
--- /dev/null
+++ b/app/wallet/fs.js
@@ -0,0 +1,41 @@
+import fs from "fs";
+import { makeRandomString } from "helpers/strings";
+import { ipcRenderer } from "electron";
+
+// This map decouples an fd (number) from an opaque identifier, so that the UI
+// can only write to files previously opened through these functions.
+const fds = {};
+
+export const openWritable = (filename) => {
+ if (fs.existsSync(filename)) {
+ const confirmOverwrite = ipcRenderer.sendSync(
+ "confirm-file-overwrite",
+ filename
+ );
+ if (!confirmOverwrite) throw new Error("User canceled file overwrite");
+ }
+
+ const fd = fs.openSync(filename, "w", 0o600);
+ const id = makeRandomString(32);
+ fds[id] = fd;
+ return id;
+};
+
+export const writeSync = (id, s) => {
+ const fd = fds[id];
+ if (!fd) {
+ throw new Error(`id ${id} not a previously opened file`);
+ }
+
+ return fs.writeSync(fd, s);
+};
+
+export const closeSync = (id) => {
+ const fd = fds[id];
+ if (!fd) {
+ throw new Error(`id ${id} not a previously opened file`);
+ }
+
+ delete fds[id];
+ return fs.closeSync(fd);
+};
diff --git a/app/wallet/ln/index.js b/app/wallet/ln/index.js
index 624d783023..c7e2f94d3c 100644
--- a/app/wallet/ln/index.js
+++ b/app/wallet/ln/index.js
@@ -4,6 +4,7 @@ import { lnrpc as pb } from "middleware/ln/rpc_pb";
import { lnrpc as wupb } from "middleware/ln/walletunlocker_pb";
import { strHashToRaw } from "helpers/byteActions";
import { ipcRenderer } from "electron";
+import { invoke } from "helpers/electronRenderer";
export const getLightningClient = client.getLightningClient;
export const getWatchtowerClient = client.getWatchtowerClient;
@@ -304,16 +305,10 @@ export const stopDaemon = (client) => {
};
export const removeDcrlnd = (walletName, testnet) =>
- new Promise((resolve, reject) => {
- const res = ipcRenderer.sendSync("ln-remove-dir", walletName, testnet);
- res instanceof Error ? reject(res) : resolve(res);
- });
+ invoke("ln-remove-dir", walletName, testnet);
export const scbInfo = (walletPath, testnet) =>
- new Promise((resolve, reject) => {
- const res = ipcRenderer.sendSync("ln-scb-info", walletPath, testnet);
- res instanceof Error ? reject(res) : resolve(res);
- });
+ invoke("ln-scb-info", walletPath, testnet);
export const exportBackup = (client, destPath) =>
new Promise((resolve, reject) => {
@@ -324,6 +319,18 @@ export const exportBackup = (client, destPath) =>
return;
}
+ // If this file already exists, show the confirmation modal.
+ if (fs.existsSync(destPath)) {
+ const confirmOverwrite = ipcRenderer.sendSync(
+ "confirm-file-overwrite",
+ destPath
+ );
+ if (!confirmOverwrite) {
+ reject("User canceled file overwrite");
+ return;
+ }
+ }
+
const data = resp.getMultiChanBackup().getMultiChanBackup();
try {
fs.writeFileSync(destPath, data);
diff --git a/package.json b/package.json
index 9ec616d88f..757236a7c9 100644
--- a/package.json
+++ b/package.json
@@ -15,15 +15,17 @@
"build-main": "cross-env NODE_ENV=production node -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.electron.js --progress=profile --color",
"build-renderer": "cross-env NODE_ENV=production node -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.production.js --progress=profile --color",
"build-trezor": "cross-env NODE_ENV=production node -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.trezor.js --progress=profile --color",
- "build": "npm run build-trezor && npm run build-main && npm run build-renderer",
+ "build-preload": "cross-env NODE_ENV=production node -r @babel/register ./node_modules/webpack/bin/webpack --config webpack.config.preload.js --progress=profile --color",
+ "build": "npm run build-trezor && npm run build-preload && npm run build-main && npm run build-renderer",
"dexsite": "node ./scripts/dexsite.js",
"rebuild-natives": "node_modules/.bin/electron-rebuild",
"rebuild-dexc": "cd modules/dex && npm run install",
"start": "cross-env NODE_ENV=production electron ./app/ --debug --custombinpath=./bin",
"start-hot": "cross-env HOT=1 NODE_ENV=development electron -r @babel/register -r @babel/polyfill ./app/main.development",
"start-hot-nosandbox": "cross-env HOT=1 NODE_ENV=development electron -r @babel/register -r @babel/polyfill ./app/main.development -r --no-sandbox",
+ "start-preload": "webpack -c webpack.config.preload.js --watch",
"postinstall": "electron-builder install-app-deps && npm run rebuild-dexc && npm rum dexsite",
- "dev": "npm run hot-server -- --start-hot",
+ "dev": "npm run build-preload && npm run hot-server -- --start-hot",
"dev-nosandbox": "npm run hot-server -- --start-hot-nosandbox",
"package": "npm run build && ./node_modules/.bin/electron-builder build --publish never",
"package-win": "npm run build && ./node_modules/.bin/electron-builder build --win --x64 --ia32",
@@ -47,7 +49,8 @@
"\\.module\\.css$": "identity-obj-proxy",
"\\.css$": "/test/mocks/styleMock.js",
"^grpc$": "/test/mocks/grpcMock.js",
- "^electron$": "/test/mocks/electronMock.js"
+ "^electron$": "/test/mocks/electronMock.js",
+ "^electron-store$": "/test/mocks/electronStore.js"
},
"transformIgnorePatterns": [
"/node_modules/",
diff --git a/server.js b/server.js
index 3908e3e1bf..b097cfc9a7 100644
--- a/server.js
+++ b/server.js
@@ -30,10 +30,16 @@ app.use(wdm);
app.use(webpackHotMiddleware(compiler));
+let preloadProc;
+
const server = app.listen(PORT, "localhost", serverError => {
if (serverError) {
return console.error(serverError);
}
+
+ // Start a webpack run to watch for changes to the preload script.
+ preloadProc = spawn("npm", ["run", "start-preload"], { shell: true, env: process.env, stdio: "inherit" });
+
if (argv["start-hot"]) {
spawn("npm", [ "run", "start-hot" ], { shell: true, env: process.env, stdio: "inherit" })
.on("close", code => process.exit(code))
@@ -47,6 +53,10 @@ const server = app.listen(PORT, "localhost", serverError => {
console.log(`Listening at http://localhost:${PORT}`);
});
+process.on("exit", () => {
+ preloadProc && preloadProc.kill();
+});
+
process.on("SIGTERM", () => {
console.log("Stopping dev server");
wdm.close();
diff --git a/test/mocks/electronStore.js b/test/mocks/electronStore.js
new file mode 100644
index 0000000000..9b51e56e35
--- /dev/null
+++ b/test/mocks/electronStore.js
@@ -0,0 +1,16 @@
+class MockElectronStore {
+ get(key) {
+ return this[key];
+ }
+ set(key, value) {
+ return (this[key] = value);
+ }
+ delete(key) {
+ delete this[key];
+ }
+ has(key) {
+ return !!this[key];
+ }
+}
+
+export default MockElectronStore;
diff --git a/webpack.config.preload.js b/webpack.config.preload.js
new file mode 100644
index 0000000000..6b74e43493
--- /dev/null
+++ b/webpack.config.preload.js
@@ -0,0 +1,74 @@
+/**
+ * Build config for trezor's iframe. This is used to contact trezor-bridge on a
+ * separate iframe in the wallet.
+ */
+
+const path = require("path");
+const webpack = require("webpack");
+const TerserPlugin = require("terser-webpack-plugin");
+
+module.exports = {
+ mode: "production",
+
+ target: "electron-preload",
+
+ entry: "./app/wallet-preload.js",
+
+ devtool: "source-map",
+
+ output: {
+ filename: "wallet-preload.js",
+ path: path.join(__dirname, "app/dist"),
+ publicPath: "./",
+ library: {
+ name: "_decrediton",
+ type: "var"
+ }
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.js?$/,
+ exclude: /node_modules/,
+ use: ["babel-loader"]
+ }
+ ]
+ },
+
+ resolve: {
+ modules: ["node_modules"]
+ },
+
+ plugins: [
+ new webpack.DefinePlugin({
+ "process.env.NODE_ENV": JSON.stringify("production"),
+ "process.env.NODE_DEBUG": false
+ })
+ ],
+
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ parallel: true,
+ extractComments: false,
+ terserOptions: {
+ ecma: 6,
+ mangle: {
+ reserved: [
+ "Array",
+ "BigInteger",
+ "Boolean",
+ "Buffer",
+ "ECPair",
+ "Function",
+ "Number",
+ "Point",
+ "Script"
+ ]
+ }
+ }
+ })
+ ]
+ }
+};