diff --git a/app/main.js b/app/main.js index 35a50f224..69fad7d92 100644 --- a/app/main.js +++ b/app/main.js @@ -27,7 +27,7 @@ const {version: localVersion} = require(app.getAppPath() + '/package.json'); const VERSION_CHECK_INTERVAL = 30 * 60 * 1000; const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest'; - +const DAEMON_PATH = process.env.LBRY_DAEMON || path.join(__dirname, 'dist', 'lbrynet-daemon'); let client = jayson.client.http({ host: 'localhost', @@ -207,13 +207,8 @@ function handleDaemonSubprocessExited() { function launchDaemon() { assert(!daemonSubprocess, 'Tried to launch daemon twice'); - if (process.env.LBRY_DAEMON) { - executable = process.env.LBRY_DAEMON; - } else { - executable = path.join(__dirname, 'dist', 'lbrynet-daemon'); - } - console.log('Launching daemon:', executable) - daemonSubprocess = child_process.spawn(executable) + console.log('Launching daemon:', DAEMON_PATH) + daemonSubprocess = child_process.spawn(DAEMON_PATH) // Need to handle the data event instead of attaching to // process.stdout because the latter doesn't work. I believe on // windows it buffers stdout and we don't get any meaningful output diff --git a/app/package.json b/app/package.json index aee4e6085..ced806d22 100644 --- a/app/package.json +++ b/app/package.json @@ -18,5 +18,8 @@ }, "devDependencies": { "electron-rebuild": "^1.5.11" + }, + "lbrySettings": { + "lbrynetDaemonVersion": "0.14.1" } } diff --git a/build/DAEMON_URL b/build/DAEMON_URL deleted file mode 100644 index e7d65abfa..000000000 --- a/build/DAEMON_URL +++ /dev/null @@ -1 +0,0 @@ -https://github.com/lbryio/lbry/releases/download/v0.14.1/lbrynet-daemon-v0.14.1-OSNAME.zip diff --git a/build/build.sh b/build/build.sh index c5ccba34f..9fa139877 100755 --- a/build/build.sh +++ b/build/build.sh @@ -79,11 +79,14 @@ if $OSX; then else OSNAME="linux" fi -DAEMON_URL="$(cat "$BUILD_DIR/DAEMON_URL" | sed "s/OSNAME/${OSNAME}/")" +DAEMON_VER=$(node -e "console.log(require(\"$ROOT/app/package.json\").lbrySettings.lbrynetDaemonVersion)") +DAEMON_URL="https://github.com/lbryio/lbry/releases/download/v${DAEMON_VER}/lbrynet-daemon-v${DAEMON_VER}-${OSNAME}.zip" wget --quiet "$DAEMON_URL" -O "$BUILD_DIR/daemon.zip" unzip "$BUILD_DIR/daemon.zip" -d "$ROOT/app/dist/" rm "$BUILD_DIR/daemon.zip" + + ################### # Build the app # ################### diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index ae894ca90..c29520720 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -16,9 +16,9 @@ import { doFileList } from "actions/file_info"; const { remote, ipcRenderer, shell } = require("electron"); const path = require("path"); -const app = require("electron").remote.app; const { download } = remote.require("electron-dl"); const fs = remote.require("fs"); +const { lbrySettings: config } = require("../../../app/package.json"); const queryStringFromParams = params => { return Object.keys(params).map(key => `${key}=${params[key]}`).join("&"); @@ -137,8 +137,9 @@ export function doDownloadUpgrade() { return function(dispatch, getState) { const state = getState(); // Make a new directory within temp directory so the filename is guaranteed to be available - const dir = fs.mkdtempSync(app.getPath("temp") + require("path").sep); - const upgradeFilename = selectUpgradeFilename(state); + const dir = fs.mkdtempSync( + remote.app.getPath("temp") + require("path").sep + ); let options = { onProgress: p => dispatch(doUpdateDownloadProgress(Math.round(p * 100))), @@ -222,6 +223,18 @@ export function doCheckUpgradeAvailable() { }; } +export function doCheckDaemonVersion() { + return function(dispatch, getState) { + lbry.version().then(({ lbrynet_version }) => { + dispatch({ + type: config.lbrynetDaemonVersion == lbrynet_version + ? types.DAEMON_VERSION_MATCH + : types.DAEMON_VERSION_MISMATCH, + }); + }); + }; +} + export function doAlertError(errorList) { return function(dispatch, getState) { const state = getState(); @@ -269,3 +282,10 @@ export function doClearCache() { return Promise.resolve(); }; } + +export function doQuitAndLaunchDaemonHelp() { + return function(dispatch, getState) { + shell.openExternal("https://lbry.io/faq/incompatible-protocol-version"); + remote.app.quit(); + }; +} diff --git a/ui/js/app.js b/ui/js/app.js index 41701ee18..c5bfdcfbd 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -2,7 +2,9 @@ import store from "store.js"; import lbry from "./lbry.js"; const env = ENV; -const config = require(`./config/${env}`); +const config = { + ...require(`./config/${env}`), +}; const language = lbry.getClientSetting("language") ? lbry.getClientSetting("language") : "en"; diff --git a/ui/js/component/app/index.js b/ui/js/component/app/index.js index 0653e6266..3749741bb 100644 --- a/ui/js/component/app/index.js +++ b/ui/js/component/app/index.js @@ -6,6 +6,7 @@ import { doCheckUpgradeAvailable, doAlertError, doRecordScroll, + doCheckDaemonVersion, } from "actions/app"; import { doUpdateBalance } from "actions/wallet"; import App from "./view"; diff --git a/ui/js/component/modalIncompatibleDaemon/index.jsx b/ui/js/component/modalIncompatibleDaemon/index.jsx new file mode 100644 index 000000000..27ddecd8d --- /dev/null +++ b/ui/js/component/modalIncompatibleDaemon/index.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { connect } from "react-redux"; +import { doQuit, doSkipWrongDaemonNotice } from "actions/app"; +import { doQuitAndLaunchDaemonHelp } from "actions/app"; +import ModalIncompatibleDaemon from "./view"; + +const select = state => ({}); + +const perform = dispatch => ({ + quitAndLaunchDaemonHelp: () => dispatch(doQuitAndLaunchDaemonHelp()), +}); + +export default connect(select, perform)(ModalIncompatibleDaemon); diff --git a/ui/js/component/modalIncompatibleDaemon/view.jsx b/ui/js/component/modalIncompatibleDaemon/view.jsx new file mode 100644 index 000000000..851a2ec88 --- /dev/null +++ b/ui/js/component/modalIncompatibleDaemon/view.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Modal } from "component/modal"; + +class ModalIncompatibleDaemon extends React.PureComponent { + render() { + const { quitAndLaunchDaemonHelp } = this.props; + + return ( + + {__( + "This browser is running with an incompatible version of the LBRY protocol and your install must be repaired." + )} + + ); + } +} + +export default ModalIncompatibleDaemon; diff --git a/ui/js/component/splash/index.js b/ui/js/component/splash/index.js new file mode 100644 index 000000000..1a87f476b --- /dev/null +++ b/ui/js/component/splash/index.js @@ -0,0 +1,19 @@ +import React from "react"; +import { connect } from "react-redux"; + +import { selectCurrentModal, selectDaemonVersionMatched } from "selectors/app"; +import { doCheckDaemonVersion } from "actions/app"; +import SplashScreen from "./view"; + +const select = state => { + return { + modal: selectCurrentModal(state), + daemonVersionMatched: selectDaemonVersionMatched(state), + }; +}; + +const perform = dispatch => ({ + checkDaemonVersion: () => dispatch(doCheckDaemonVersion()), +}); + +export default connect(select, perform)(SplashScreen); diff --git a/ui/js/component/splash.js b/ui/js/component/splash/view.jsx similarity index 59% rename from ui/js/component/splash.js rename to ui/js/component/splash/view.jsx index eb67340e1..7cccd5bf0 100644 --- a/ui/js/component/splash.js +++ b/ui/js/component/splash/view.jsx @@ -1,6 +1,9 @@ import React from "react"; -import lbry from "../lbry.js"; -import LoadScreen from "./load_screen.js"; +import lbry from "../../lbry.js"; +import LoadScreen from "../load_screen.js"; +import ModalIncompatibleDaemon from "../modalIncompatibleDaemon"; +import ModalUpgrade from "component/modalUpgrade"; +import ModalDownloading from "component/modalDownloading"; export class SplashScreen extends React.PureComponent { static propTypes = { @@ -14,6 +17,7 @@ export class SplashScreen extends React.PureComponent { this.state = { details: __("Starting daemon"), message: __("Connecting"), + isRunning: false, isLagging: false, }; } @@ -35,10 +39,16 @@ export class SplashScreen extends React.PureComponent { message: __("Testing Network"), details: __("Waiting for name resolution"), isLagging: false, + isRunning: true, }); lbry.resolve({ uri: "lbry://one" }).then(() => { - this.props.onLoadDone(); + // Only leave the load screen if the daemon version matched; + // otherwise we'll notify the user at the end of the load screen. + + if (this.props.daemonVersionMatched) { + this.props.onReadyToLaunch(); + } }); return; } @@ -54,6 +64,7 @@ export class SplashScreen extends React.PureComponent { componentDidMount() { lbry .connect() + .then(this.props.checkDaemonVersion) .then(() => { this.updateStatus(); }) @@ -69,12 +80,24 @@ export class SplashScreen extends React.PureComponent { } render() { + const { modal } = this.props; + return ( - +
+ + {/* Temp hack: don't show any modals on splash screen daemon is running; + daemon doesn't let you quit during startup, so the "Quit" buttons + in the modals won't work. */} + {modal == "incompatibleDaemon" && + this.state.isRunning && + } + {modal == "upgrade" && this.state.isRunning && } + {modal == "downloading" && this.state.isRunning && } +
); } } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 7d38568f3..ff20168a4 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -7,6 +7,8 @@ export const REMOVE_SNACKBAR_SNACK = "REMOVE_SNACKBAR_SNACK"; export const WINDOW_FOCUSED = "WINDOW_FOCUSED"; export const DAEMON_READY = "DAEMON_READY"; +export const DAEMON_VERSION_MATCH = "DAEMON_VERSION_MATCH"; +export const DAEMON_VERSION_MISMATCH = "DAEMON_VERSION_MISMATCH"; // Upgrades export const UPGRADE_CANCELLED = "UPGRADE_CANCELLED"; diff --git a/ui/js/constants/modal_types.js b/ui/js/constants/modal_types.js index 787dabaaf..ec805832c 100644 --- a/ui/js/constants/modal_types.js +++ b/ui/js/constants/modal_types.js @@ -1,2 +1,3 @@ export const WELCOME = "welcome"; export const CONFIRM_FILE_REMOVE = "confirmFileRemove"; +export const INCOMPATIBLE_DAEMON = "incompatibleDaemon"; diff --git a/ui/js/main.js b/ui/js/main.js index 041611c30..d8952cd63 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,7 +5,7 @@ import App from "component/app/index.js"; import SnackBar from "component/snackBar"; import { Provider } from "react-redux"; import store from "store.js"; -import SplashScreen from "component/splash.js"; +import SplashScreen from "component/splash"; import AuthOverlay from "component/authOverlay"; import { doChangePath, doNavigate, doDaemonReady } from "actions/app"; import { toQueryString } from "util/query_params"; @@ -126,7 +126,12 @@ var init = function() { if (window.sessionStorage.getItem("loaded") == "y") { onDaemonReady(); } else { - ReactDOM.render(, canvas); + ReactDOM.render( + + + , + canvas + ); } }; diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index fe8c9adae..cf0849504 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -1,4 +1,5 @@ import * as types from "constants/action_types"; +import * as modalTypes from "constants/modal_types"; import lbry from "lbry"; const currentPath = () => { @@ -17,6 +18,7 @@ const defaultState = { currentPath: currentPath(), platform: process.platform, upgradeSkipped: sessionStorage.getItem("upgradeSkipped"), + daemonVersionMatched: null, daemonReady: false, hasSignature: false, badgeNumber: 0, @@ -28,6 +30,19 @@ reducers[types.DAEMON_READY] = function(state, action) { }); }; +reducers[types.DAEMON_VERSION_MATCH] = function(state, action) { + return Object.assign({}, state, { + daemonVersionMatched: true, + }); +}; + +reducers[types.DAEMON_VERSION_MISMATCH] = function(state, action) { + return Object.assign({}, state, { + daemonVersionMatched: false, + modal: modalTypes.INCOMPATIBLE_DAEMON, + }); +}; + reducers[types.CHANGE_PATH] = function(state, action) { return Object.assign({}, state, { currentPath: action.data.path, diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f6acd6d07..7cb83a4d7 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -177,6 +177,11 @@ export const selectDaemonReady = createSelector( state => state.daemonReady ); +export const selectDaemonVersionMatched = createSelector( + _selectState, + state => state.daemonVersionMatched +); + export const selectSnackBar = createSelector( _selectState, state => state.snackBar || {} diff --git a/ui/js/utils.js b/ui/js/utils.js index 783f85113..b366954d1 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -1,3 +1,5 @@ +const { remote } = require("electron"); + /** * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value * is not set yet.