diff --git a/.yarnclean b/.yarnclean
new file mode 100644
index 00000000..ea50e9a3
--- /dev/null
+++ b/.yarnclean
@@ -0,0 +1,2 @@
+@types/react-native
+
diff --git a/package.json b/package.json
index a94f1866..0aa965dd 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,9 @@
},
"license": "MIT",
"scripts": {
+ "dev": "electron-webpack dev",
+ "compile": "electron-webpack",
"lint": "eslint --ext .ts,.tsx src",
- "dev": "webpack --mode=development",
"start": "qode --inspect ./dist/index.js",
"clean": "rm -rf dist ./deploy/darwin/build",
"build": "webpack -p",
@@ -18,12 +19,9 @@
},
"dependencies": {
"@mysteriumnetwork/terms": "^0.0.24",
- "@nodegui/nodegui": "^0.18.2",
- "@nodegui/qode": "^2.0.4",
- "@nodegui/react-nodegui": "^0.6.1",
"async-retry": "^1.3.1",
"byte-size": "^6.2.0",
- "eventsource": "^1.0.7",
+ "history": "^4.10.1",
"lodash": "^4.17.15",
"mkdirp": "^1.0.4",
"mobx": "^5.15.4",
@@ -34,30 +32,33 @@
"open": "^7.0.0",
"qrcode-generator": "^1.4.4",
"react": "^16.13.1",
- "showdown": "^1.9.1",
+ "react-dom": "^16.13.1",
+ "react-is": "^16.13.1",
+ "react-markdown": "^4.3.1",
+ "react-router-dom": "^5.1.2",
+ "styled-components": "^4.4.1",
"sudo-prompt": "^9.1.1"
},
"devDependencies": {
- "@babel/core": "^7.9.0",
- "@babel/plugin-proposal-class-properties": "^7.8.3",
- "@babel/plugin-proposal-decorators": "^7.8.3",
- "@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
- "@babel/preset-typescript": "^7.9.0",
- "@nodegui/packer": "^1.4.1",
"@types/async-retry": "^1.4.1",
- "@types/eventsource": "^1.1.2",
+ "@types/css-font-loading-module": "^0.0.4",
"@types/lodash": "^4.14.149",
"@types/node": "12.12.35",
"@types/node-fetch": "^2.5.4",
"@types/react": "^16.9.33",
+ "@types/react-dom": "^16.9.6",
+ "@types/react-router-dom": "^5.1.4",
"@types/showdown": "^1.9.3",
+ "@types/styled-components": "^5.1.0",
"@types/webpack-env": "^1.14.1",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
- "babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1",
+ "electron": "^8.2.2",
+ "electron-webpack": "^2.8.2",
+ "electron-webpack-ts": "^4.0.1",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-header": "^3.0.0",
@@ -69,10 +70,13 @@
"native-addon-loader": "^2.0.1",
"prettier": "^2.0.4",
"typescript": "^3.7.4",
+ "url-loader": "^4.1.0",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.10"
},
- "resolutions": {
- "@types/react": "^16.9.33"
+ "electronWebpack": {
+ "renderer": {
+ "webpackConfig": "webpack.renderer.additions.js"
+ }
}
}
diff --git a/src/app.tsx b/src/app.tsx
index 143f59d4..a221735b 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -5,42 +5,33 @@
* LICENSE file in the root directory of this source tree.
*/
import React from "react"
-import { observer } from "mobx-react-lite"
-import { View } from "@nodegui/react-nodegui"
-import { ConnectionStatus, IdentityRegistrationStatus } from "mysterium-vpn-js"
+import { HashRouter, Route, Switch } from "react-router-dom"
-import { useStores } from "./store"
-import { DaemonStatusType } from "./daemon/store"
-import { SelectProposalView } from "./views/consumer/select-proposal/select-proposal-view"
-import { winSize } from "./config"
-import { ConnectedView } from "./views/consumer/connected/connected-view"
-import { SelectIdentityView } from "./views/common/select-identity/select-identity-view"
-import { WalletView } from "./views/consumer/wallet/wallet-view"
import { LoadingView } from "./views/common/loading/loading-view"
import { AcceptTermsView } from "./views/common/accept-terms/accept-terms-view"
import { WelcomeView } from "./views/common/welcome/welcome-view"
+import { SelectIdentityView } from "./views/common/select-identity/select-identity-view"
-// To avoid hiccups on screen re-render, render all screens and use style to switch between them.
-// Hidden elements have zero width.
-const fitWindowIfVisible = (visible: boolean): string => {
- return `width: ${visible ? winSize.width : 0}; height: ${winSize.height};`
-}
-
-enum Nav {
- Loader,
- Welcome,
- AcceptTerms,
- SelectIdentity,
- SelectProposal,
- ConnectionActive,
- Wallet,
-}
-
-export const App = observer(() => {
- const root = useStores()
- const { daemon, connection, identity, config } = root
-
- // Poor man's navigation, but performs better than re-rendering the whole screen.
+export const App: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ /*// Poor man's navigation, but performs better than re-rendering the whole screen.
let screen: Nav
if (daemon.status == DaemonStatusType.Down) {
screen = Nav.Loader
@@ -71,5 +62,5 @@ export const App = observer(() => {
- )
-})
+ )*/
+}
diff --git a/src/config/store.ts b/src/config/store.ts
index 1a973bb2..824c69f3 100644
--- a/src/config/store.ts
+++ b/src/config/store.ts
@@ -39,6 +39,19 @@ export class ConfigStore {
}
},
)
+ reaction(
+ () => this.config,
+ () => {
+ if (!this.currentTermsAgreed() && this.root.welcome) {
+ console.log("welcome")
+ this.root.history.push("/welcome")
+ } else if (!this.currentTermsAgreed()) {
+ this.root.history.push("/terms")
+ } else {
+ this.root.history.push("/identity")
+ }
+ },
+ )
}
@action
diff --git a/src/daemon/store.ts b/src/daemon/store.ts
index 08d4f1ad..fa2714d6 100644
--- a/src/daemon/store.ts
+++ b/src/daemon/store.ts
@@ -6,10 +6,10 @@
*/
import tequilapi from "mysterium-vpn-js"
import { action, observable, reaction, when } from "mobx"
-import EventSource from "eventsource"
import { supervisor } from "../supervisor/supervisor"
import { sseConnect } from "../tequila-sse"
+import { RootStore } from "../store"
export enum DaemonStatusType {
Up = "UP",
@@ -27,29 +27,28 @@ export class DaemonStore {
eventSource?: EventSource
- constructor() {
+ root: RootStore
+
+ constructor(root: RootStore) {
+ this.root = root
setInterval(async () => {
await this.healthcheck()
}, 2000)
when(
() => this.status == DaemonStatusType.Down,
async () => {
+ this.root.history.push("/loading")
await this.start()
},
)
- reaction(
- () => this.status,
- async (status) => {
- if (status == DaemonStatusType.Down) {
- await this.start()
- }
- },
- )
reaction(
() => this.status,
async (status) => {
if (status == DaemonStatusType.Up) {
this.eventSource = sseConnect()
+ } else {
+ this.root.history.push("/loading")
+ await this.start()
}
},
)
diff --git a/src/declarations.d.ts b/src/declarations.d.ts
index 7ac8546f..64669182 100644
--- a/src/declarations.d.ts
+++ b/src/declarations.d.ts
@@ -15,3 +15,5 @@ declare module "byte-size" {
declare module "@mysteriumnetwork/terms" {
const TermsEndUser: string
}
+
+declare const __static: string
diff --git a/src/history.ts b/src/history.ts
new file mode 100644
index 00000000..62a9272b
--- /dev/null
+++ b/src/history.ts
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) 2020 BlockDev AG
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import { createHashHistory } from "history"
+
+export const history = createHashHistory()
diff --git a/src/index.tsx b/src/index.tsx
deleted file mode 100644
index ef273d15..00000000
--- a/src/index.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) 2020 BlockDev AG
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-import React from "react"
-import { Renderer } from "@nodegui/react-nodegui"
-import { QFontDatabase } from "@nodegui/nodegui"
-import "mobx-react-lite/batchingOptOut"
-
-import robotoLight from "../assets/fonts/Roboto-Light.ttf"
-import robotoMedium from "../assets/fonts/Roboto-Medium.ttf"
-
-import MainWindow from "./main-window"
-import { onProcessExit } from "./utils/on-process-exit"
-import { supervisor } from "./supervisor/supervisor"
-import { createSystemTray } from "./tray/tray"
-import { fixAssetPath } from "./utils/paths"
-
-process.title = "MysteriumVPN"
-
-class Root extends React.Component {
- render(): React.ReactNode {
- return
- }
-}
-
-;[robotoLight, robotoMedium].forEach((font) => {
- QFontDatabase.addApplicationFont(fixAssetPath(font))
-})
-
-Renderer.render()
-
-createSystemTray()
-
-// This is for hot reloading (this will be stripped off in production by webpack)
-if (module.hot) {
- module.hot.accept(["./main-window"], function () {
- Renderer.forceUpdate()
- })
-}
-
-onProcessExit(async () => await supervisor.killMyst())
diff --git a/src/main-window.tsx b/src/main-window.tsx
deleted file mode 100644
index 8c1f0d02..00000000
--- a/src/main-window.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (c) 2020 BlockDev AG
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-import React from "react"
-import { hot, Window } from "@nodegui/react-nodegui"
-import { QIcon, WidgetEventTypes } from "@nodegui/nodegui"
-
-import mystLogo from "../assets/logo.svg"
-
-import { winSize } from "./config"
-import { App } from "./app"
-import { textRegular } from "./ui-kit/typography"
-
-const winIcon = new QIcon(mystLogo)
-
-const mainWindowEventHandler = {
- [WidgetEventTypes.Close]: (): void => {
- process.emit("beforeExit", 0)
- },
-}
-
-// const statusBar = new QStatusBar()
-
-const MainWindow: React.FC = () => {
- //const winRef: MutableRefObject = useRef(null)
- // const setRef = useCallback((ref: QMainWindow) => {
- // if (ref !== null) {
- // ref.setStatusBar(statusBar)
- // }
- // winRef.current = ref
- // }, [])
- // const { daemon, connection, identity } = useStores()
- // useEffect(() =>
- // autorun(() => {
- // const daemonIcon = daemon.status == DaemonStatusType.Up ? "🟢" : "⚪️"
- // const connectionIcon = connection.status == ConnectionStatusType.CONNECTED ? "🟢" : "⚪️"
- // const id = identity.identity?.id ?? "⚪️"
- // statusBar.clearMessage()
- // statusBar.showMessage(`Connection: ${connectionIcon} | Daemon: ${daemonIcon} | ID: ${id}`, 0)
- // }),
- // )
-
- return (
-
-
-
- )
-}
-
-export default hot(MainWindow)
diff --git a/src/main/index.tsx b/src/main/index.tsx
new file mode 100644
index 00000000..68e58b4b
--- /dev/null
+++ b/src/main/index.tsx
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2020 BlockDev AG
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import * as path from "path"
+import { format as formatUrl } from "url"
+
+import { app, BrowserWindow } from "electron"
+
+import { winSize } from "../config"
+
+const isDevelopment = process.env.NODE_ENV !== "production"
+
+// global reference to win (necessary to prevent window from being garbage collected)
+let win: BrowserWindow | null
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const installExtensions = async (): Promise => {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const installer = require("electron-devtools-installer")
+ const forceDownload = !!process.env.UPGRADE_EXTENSIONS
+ const extensions = ["REACT_DEVELOPER_TOOLS"]
+
+ // eslint-disable-next-line prettier/prettier
+ return Promise.all(
+ extensions.map(name => installer.default(installer[name], forceDownload))
+ ).catch(console.log); // eslint-disable-line no-console
+}
+
+const createWindow = async (): Promise => {
+ if (isDevelopment) {
+ await installExtensions()
+ }
+
+ const window = new BrowserWindow({
+ title: "Mysterium VPN",
+ width: winSize.width,
+ height: winSize.height,
+ resizable: false,
+ maximizable: false,
+ webPreferences: { nodeIntegration: true },
+ })
+
+ if (isDevelopment) {
+ window.webContents.once("dom-ready", () => {
+ window.webContents.openDevTools()
+ })
+ }
+
+ if (isDevelopment) {
+ window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`)
+ } else {
+ window.loadURL(
+ formatUrl({
+ pathname: path.join(__dirname, "index.html"),
+ protocol: "file",
+ slashes: true,
+ }),
+ )
+ }
+
+ window.on("closed", () => {
+ win = null
+ })
+
+ window.webContents.on("devtools-opened", () => {
+ window.focus()
+ setImmediate(() => {
+ window.focus()
+ })
+ })
+
+ return window
+}
+
+// create main BrowserWindow when electron is ready
+app.on("ready", async () => {
+ win = await createWindow()
+})
+
+// quit application when all windows are closed
+app.on("window-all-closed", () => {
+ // on macOS it is common for applications to stay open until the user explicitly quits
+ if (process.platform !== "darwin") {
+ app.quit()
+ }
+})
+
+app.on("activate", async () => {
+ // on macOS it is common to re-create a window even after all windows have been closed
+ if (win === null) {
+ win = await createWindow()
+ }
+})
+
+// createSystemTray()
diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx
new file mode 100644
index 00000000..55fdc5d8
--- /dev/null
+++ b/src/renderer/index.tsx
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2020 BlockDev AG
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+import path from "path"
+import fs from "fs"
+
+import React from "react"
+import ReactDOM from "react-dom"
+import { createGlobalStyle } from "styled-components"
+
+import "mobx-react-lite/batchingForReactDom"
+import { App } from "../app"
+import { onProcessExit } from "../utils/on-process-exit"
+import { supervisor } from "../supervisor/supervisor"
+
+const robotoLightPath = path.join(__static, "/fonts/Roboto-Light.ttf")
+const robotoLightBuffer = fs.readFileSync(robotoLightPath)
+const robotoLight = new FontFace("Roboto", robotoLightBuffer, { weight: "normal" })
+robotoLight.load().then(() => {
+ document.fonts.add(robotoLight)
+})
+
+const robotoMediumPath = path.join(__static, "/fonts/Roboto-Medium.ttf")
+const robotoMediumBuffer = fs.readFileSync(robotoMediumPath)
+const robotoMedium = new FontFace("Roboto", robotoMediumBuffer, { weight: "bold" })
+robotoLight.load().then(() => {
+ document.fonts.add(robotoMedium)
+})
+
+// console.log(fileContents)
+
+const GlobalStyle = createGlobalStyle`
+ html, body, #app {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ font-family: Roboto;
+ font-weight: 300;
+ }
+`
+
+// Create main element
+// const container = document.createElement("div")
+// document.body.appendChild(container)
+
+const AppR: React.FC = () => {
+ return (
+
+
+
+
+ )
+}
+
+// Render components
+ReactDOM.render(, document.getElementById("app"))
+
+onProcessExit(async () => await supervisor.killMyst())
diff --git a/src/store.ts b/src/store.ts
index f34e06c2..d8a71183 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -6,6 +6,7 @@
*/
import React from "react"
import { action, configure, observable } from "mobx"
+import { History, LocationState } from "history"
import { DaemonStore } from "./daemon/store"
import { ConnectionStore } from "./connection/store"
@@ -13,10 +14,12 @@ import { IdentityStore } from "./identity/store"
import { ProposalStore } from "./proposals/store"
import { PaymentStore } from "./payment/store"
import { ConfigStore } from "./config/store"
+import { history } from "./history"
// import { enableLogging } from "mobx-logger"
export class RootStore {
+ history: History
daemon: DaemonStore
config: ConfigStore
connection: ConnectionStore
@@ -33,7 +36,8 @@ export class RootStore {
welcome = true
constructor() {
- this.daemon = new DaemonStore()
+ this.history = history
+ this.daemon = new DaemonStore(this)
this.config = new ConfigStore(this)
this.connection = new ConnectionStore(this)
this.identity = new IdentityStore(this)
@@ -55,6 +59,7 @@ export class RootStore {
@action
dismissWelcome = (): void => {
this.welcome = false
+ this.history.push("/terms")
}
@action
diff --git a/src/tequila-sse.ts b/src/tequila-sse.ts
index 20d3c0fa..2a028376 100644
--- a/src/tequila-sse.ts
+++ b/src/tequila-sse.ts
@@ -6,7 +6,6 @@
*/
import { EventEmitter } from "events"
-import EventSource from "eventsource"
import { parseSSEResponse, TEQUILAPI_SSE_URL } from "mysterium-vpn-js"
import { isDevelopment } from "./utils/mode"
diff --git a/src/ui-kit/mbutton/brand-button.tsx b/src/ui-kit/mbutton/brand-button.tsx
index dc85f9c2..fa2e7792 100644
--- a/src/ui-kit/mbutton/brand-button.tsx
+++ b/src/ui-kit/mbutton/brand-button.tsx
@@ -5,48 +5,27 @@
* LICENSE file in the root directory of this source tree.
*/
import React from "react"
-import { Button, useEventHandler } from "@nodegui/react-nodegui"
-
-import { textRegular } from "../typography"
+import styled from "styled-components"
import { CommonButtonProps } from "./props"
-export const BrandButton: React.FC = ({
- onClick,
- enabled = true,
- text = "",
- style = "",
- ...rest
-}) => {
- const clickHandler = useEventHandler({ ["clicked"]: () => onClick() }, [])
- const stateStyle = ((): string => {
- if (!enabled) {
- return "background: #ccc;"
- }
- return `background: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop: 0 #f1209b,stop: 0.03 #7c2463, stop: 0.97 #552462, stop: 1 #35154d);`
- })()
- return (
-