diff --git a/app/components/ProgramTable.tsx b/app/components/ProgramTable.tsx index 89eddc6..a0dabb2 100644 --- a/app/components/ProgramTable.tsx +++ b/app/components/ProgramTable.tsx @@ -1,4 +1,4 @@ -import { flatMap } from "lodash"; +import flatMap from "lodash-es/flatMap"; import { observer } from "mobx-react"; import * as moment from "moment"; import * as React from "react"; diff --git a/app/components/SectionRunnerView.tsx b/app/components/SectionRunnerView.tsx index 8c878c4..91e7150 100644 --- a/app/components/SectionRunnerView.tsx +++ b/app/components/SectionRunnerView.tsx @@ -87,7 +87,7 @@ class SectionRunView extends React.Component<{ const duration = Duration.fromSeconds(run.duration); const cancel = run.cancel; const description = `'${section.name}' for ${duration.toString()}`; - let running: boolean = false; + let running: boolean = false; // tslint:disable-line:no-unused-variable let paused: boolean = false; let progressBar: React.ReactNode | undefined; if (run.startTime != null) { @@ -123,10 +123,10 @@ export default class SectionRunnerView extends React.Component<{ const queueView = queue.map((run) => ); if (current) { - queueView.unshift(); + queueView.unshift(); } if (queueView.length === 0) { - queueView.push(No items in queue); + queueView.push(No items in queue); } return ( diff --git a/app/sprinklers/websocket.ts b/app/sprinklers/websocket.ts index 92c84b3..b93a252 100644 --- a/app/sprinklers/websocket.ts +++ b/app/sprinklers/websocket.ts @@ -12,6 +12,7 @@ import { action, autorun, observable } from "mobx"; const log = logger.child({ source: "websocket" }); const TIMEOUT_MS = 5000; +const RECONNECT_TIMEOUT_MS = 5000; export class WSSprinklersDevice extends s.SprinklersDevice { readonly api: WebSocketApiClient; @@ -39,7 +40,6 @@ export class WSSprinklersDevice extends s.SprinklersDevice { export class WebSocketApiClient implements s.ISprinklersApi { readonly webSocketUrl: string; - socket!: WebSocket; device: WSSprinklersDevice; nextDeviceRequestId = Math.round(Math.random() * 1000000); @@ -47,6 +47,9 @@ export class WebSocketApiClient implements s.ISprinklersApi { @observable connectionState: s.ConnectionState = new s.ConnectionState(); + private socket: WebSocket | null = null; + private reconnectTimer: number | null = null; + get connected(): boolean { return this.connectionState.isConnected; } @@ -60,11 +63,18 @@ export class WebSocketApiClient implements s.ISprinklersApi { start() { log.debug({ url: this.webSocketUrl }, "connecting to websocket"); - this.socket = new WebSocket(this.webSocketUrl); - this.socket.onopen = this.onOpen.bind(this); - this.socket.onclose = this.onClose.bind(this); - this.socket.onerror = this.onError.bind(this); - this.socket.onmessage = this.onMessage.bind(this); + this._connect(); + } + + stop() { + if (this.reconnectTimer != null) { + clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + if (this.socket != null) { + this.socket.close(); + this.socket = null; + } } getDevice(name: string): s.SprinklersDevice { @@ -80,6 +90,15 @@ export class WebSocketApiClient implements s.ISprinklersApi { // args must all be JSON serializable makeDeviceCall(deviceName: string, request: requests.Request): Promise { + if (this.socket == null) { + const res: requests.Response = { + type: request.type, + result: "error", + code: ErrorCode.ServerDisconnected, + message: "the server is not connected", + }; + throw res; + } const requestData = seralizeRequest(request); const id = this.nextDeviceRequestId++; const data: ws.IDeviceCallRequest = { @@ -97,10 +116,10 @@ export class WebSocketApiClient implements s.ISprinklersApi { reject(resData.data); } }; - timeoutHandle = setTimeout(() => { + timeoutHandle = window.setTimeout(() => { delete this.deviceResponseCallbacks[id]; - const res: requests.RunSectionResponse = { - type: "runSection", + const res: requests.Response = { + type: request.type, result: "error", code: ErrorCode.Timeout, message: "the request timed out", @@ -112,6 +131,18 @@ export class WebSocketApiClient implements s.ISprinklersApi { return promise; } + private _reconnect = () => { + this._connect(); + } + + private _connect() { + this.socket = new WebSocket(this.webSocketUrl); + this.socket.onopen = this.onOpen.bind(this); + this.socket.onclose = this.onClose.bind(this); + this.socket.onerror = this.onError.bind(this); + this.socket.onmessage = this.onMessage.bind(this); + } + private onOpen() { log.info("established websocket connection"); this.connectionState.clientToServer = true; @@ -127,6 +158,7 @@ export class WebSocketApiClient implements s.ISprinklersApi { log.info({ reason: event.reason, wasClean: event.wasClean }, "disconnected from websocket"); this.onDisconnect(); + this.reconnectTimer = window.setTimeout(this._reconnect, RECONNECT_TIMEOUT_MS); } private onError(event: Event) { diff --git a/app/webpack.config.js b/app/webpack.config.js index 418bab6..0151d22 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -4,9 +4,13 @@ const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin"); const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); -const cssnext = require("postcss-cssnext"); +const DashboardPlugin = require("webpack-dashboard/plugin"); +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const HappyPack = require("happypack"); +const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin"); -const { getClientEnvironment } = require("../env"); +const {getClientEnvironment} = require("../env"); const paths = require("../paths"); // Webpack uses `publicPath` to determine where the app is being served from. @@ -35,14 +39,8 @@ const postCssConfig = { ident: "postcss", plugins: () => [ require("postcss-flexbugs-fixes"), - cssnext({ - browsers: [ - ">1%", - "last 4 versions", - "Firefox ESR", - "not ie < 9", // React doesn"t support IE8 anyway - ], - flexbox: "no-2009", + require("postcss-preset-env")({ + stage: 0, }), ], }, @@ -59,10 +57,14 @@ const rules = (env) => { // "style" loader turns CSS into JS modules that inject