refactoring
This commit is contained in:
parent
b59fbb456b
commit
8a6d501cda
@ -5,7 +5,7 @@ import { Container } from "semantic-ui-react";
|
|||||||
|
|
||||||
import { MessagesView, NavBar } from "@client/components";
|
import { MessagesView, NavBar } from "@client/components";
|
||||||
import * as p from "@client/pages";
|
import * as p from "@client/pages";
|
||||||
import * as rp from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
|
|
||||||
// tslint:disable:ordered-imports
|
// tslint:disable:ordered-imports
|
||||||
import "font-awesome/css/font-awesome.css";
|
import "font-awesome/css/font-awesome.css";
|
||||||
@ -18,8 +18,8 @@ function NavContainer() {
|
|||||||
<NavBar/>
|
<NavBar/>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={rp.device(":deviceId")} component={p.DevicePage}/>
|
<Route path={route.device(":deviceId")} component={p.DevicePage}/>
|
||||||
<Route path={rp.messagesTest} component={p.MessagesTestPage}/>
|
<Route path={route.messagesTest} component={p.MessagesTestPage}/>
|
||||||
<Redirect to="/"/>
|
<Redirect to="/"/>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
@ -31,8 +31,8 @@ function NavContainer() {
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={rp.login} component={p.LoginPage}/>
|
<Route path={route.login} component={p.LoginPage}/>
|
||||||
<Route path={rp.logout} component={p.LogoutPage}/>
|
<Route path={route.logout} component={p.LogoutPage}/>
|
||||||
<NavContainer/>
|
<NavContainer/>
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import * as React from "react";
|
|||||||
import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
|
import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
|
||||||
|
|
||||||
import * as p from "@client/pages";
|
import * as p from "@client/pages";
|
||||||
import * as rp from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
import { AppState, injectState } from "@client/state";
|
import { AppState, injectState } from "@client/state";
|
||||||
import { ConnectionState as ConState } from "@common/sprinklersRpc";
|
import { ConnectionState as ConState } from "@common/sprinklersRpc";
|
||||||
import { Route, RouteComponentProps, withRouter } from "react-router";
|
import { Route, RouteComponentProps, withRouter } from "react-router";
|
||||||
@ -67,7 +67,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
|
|||||||
</Grid.Column>
|
</Grid.Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<ProgramTable device={device} routerStore={routerStore} />
|
<ProgramTable device={device} routerStore={routerStore} />
|
||||||
<Route path={rp.program(":deviceId", ":programId")} component={p.ProgramPage} />
|
<Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -3,7 +3,7 @@ import * as React from "react";
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Menu } from "semantic-ui-react";
|
import { Menu } from "semantic-ui-react";
|
||||||
|
|
||||||
import * as rp from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
import { AppState, ConsumeState, injectState } from "@client/state";
|
import { AppState, ConsumeState, injectState } from "@client/state";
|
||||||
|
|
||||||
interface NavItemProps {
|
interface NavItemProps {
|
||||||
@ -26,17 +26,17 @@ function NavBar({ appState }: { appState: AppState }) {
|
|||||||
let loginMenu;
|
let loginMenu;
|
||||||
if (appState.isLoggedIn) {
|
if (appState.isLoggedIn) {
|
||||||
loginMenu = (
|
loginMenu = (
|
||||||
<NavItem to={rp.logout}>Logout</NavItem>
|
<NavItem to={route.logout}>Logout</NavItem>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
loginMenu = (
|
loginMenu = (
|
||||||
<NavItem to={rp.login}>Login</NavItem>
|
<NavItem to={route.login}>Login</NavItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<NavItem to={rp.device("grinklers")}>Device grinklers</NavItem>
|
<NavItem to={route.device("grinklers")}>Device grinklers</NavItem>
|
||||||
<NavItem to={rp.messagesTest}>Messages test</NavItem>
|
<NavItem to={route.messagesTest}>Messages test</NavItem>
|
||||||
<Menu.Menu position="right">
|
<Menu.Menu position="right">
|
||||||
{loginMenu}
|
{loginMenu}
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
|
@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
|||||||
import { Button, ButtonProps, Form, Icon, Table } from "semantic-ui-react";
|
import { Button, ButtonProps, Form, Icon, Table } from "semantic-ui-react";
|
||||||
|
|
||||||
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||||
import * as rp from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -21,7 +21,7 @@ class ProgramRows extends React.Component<{
|
|||||||
const { name, running, enabled, schedule, sequence } = program;
|
const { name, running, enabled, schedule, sequence } = program;
|
||||||
|
|
||||||
const buttonStyle: ButtonProps = { size: "small", compact: false };
|
const buttonStyle: ButtonProps = { size: "small", compact: false };
|
||||||
const detailUrl = rp.program(device.id, program.id);
|
const detailUrl = route.program(device.id, program.id);
|
||||||
|
|
||||||
const stopStartButton = (
|
const stopStartButton = (
|
||||||
<Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}>
|
<Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}>
|
||||||
|
@ -30,7 +30,7 @@ class LoginPageState {
|
|||||||
login(appState: AppState) {
|
login(appState: AppState) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
appState.tokenStore.grantPassword(this.username, this.password)
|
appState.httpApi.grantPassword(this.username, this.password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
log.info("logged in");
|
log.info("logged in");
|
||||||
|
@ -7,7 +7,7 @@ export function LogoutPage() {
|
|||||||
function consumeState(appState: AppState) {
|
function consumeState(appState: AppState) {
|
||||||
appState.tokenStore.clear();
|
appState.tokenStore.clear();
|
||||||
return (
|
return (
|
||||||
<Redirect to="/" />
|
<Redirect to="/login" />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import { RouteComponentProps } from "react-router";
|
|||||||
import { Button, CheckboxProps, Form, Icon, Input, InputOnChangeData, Menu, Modal } from "semantic-ui-react";
|
import { Button, CheckboxProps, Form, Icon, Input, InputOnChangeData, Menu, Modal } from "semantic-ui-react";
|
||||||
|
|
||||||
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||||
import * as rp from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
import { AppState, injectState } from "@client/state";
|
import { AppState, injectState } from "@client/state";
|
||||||
import log from "@common/logger";
|
import log from "@common/logger";
|
||||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||||
@ -189,7 +189,7 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
|
|
||||||
private close = () => {
|
private close = () => {
|
||||||
const { deviceId } = this.props.match.params;
|
const { deviceId } = this.props.match.params;
|
||||||
this.props.history.push({ pathname: rp.device(deviceId), search: "" });
|
this.props.history.push({ pathname: route.device(deviceId), search: "" });
|
||||||
}
|
}
|
||||||
|
|
||||||
private onNameChange = (e: any, p: InputOnChangeData) => {
|
private onNameChange = (e: any, p: InputOnChangeData) => {
|
||||||
|
@ -105,7 +105,6 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
log.debug({ url: this.webSocketUrl }, "connecting to websocket");
|
|
||||||
this._connect();
|
this._connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +221,12 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _connect() {
|
private _connect() {
|
||||||
|
if (this.socket != null &&
|
||||||
|
(this.socket.readyState === WebSocket.CLOSED)) {
|
||||||
|
this.tryAuthenticate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug({ url: this.webSocketUrl }, "connecting to websocket");
|
||||||
this.socket = new WebSocket(this.webSocketUrl);
|
this.socket = new WebSocket(this.webSocketUrl);
|
||||||
this.socket.onopen = this.onOpen.bind(this);
|
this.socket.onopen = this.onOpen.bind(this);
|
||||||
this.socket.onclose = this.onClose.bind(this);
|
this.socket.onclose = this.onClose.bind(this);
|
||||||
|
@ -25,28 +25,32 @@ export default class AppState {
|
|||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
syncHistoryWithStore(this.history, this.routerStore);
|
syncHistoryWithStore(this.history, this.routerStore);
|
||||||
|
|
||||||
this.tokenStore.loadLocalStorage();
|
this.tokenStore.loadLocalStorage();
|
||||||
|
|
||||||
if (!this.httpApi.tokenStore.accessToken.isValid) {
|
await this.checkToken();
|
||||||
if (this.httpApi.tokenStore.refreshToken.isValid) {
|
await this.sprinklersRpc.start();
|
||||||
try {
|
}
|
||||||
await this.httpApi.tokenStore.grantRefresh();
|
|
||||||
} catch (err) {
|
async checkToken() {
|
||||||
if (err instanceof ApiError && err.code === ErrorCode.BadToken) {
|
const { tokenStore: { accessToken, refreshToken } } = this.httpApi;
|
||||||
log.warn({ err }, "refresh is bad for some reason, erasing");
|
if (accessToken.isValid) { // if the access token is valid, we are good
|
||||||
this.tokenStore.clear();
|
return;
|
||||||
this.history.push("/login");
|
}
|
||||||
} else {
|
if (!refreshToken.isValid) { // if the refresh token is not valid, need to login again
|
||||||
log.error({ err }, "could not refresh access token");
|
this.history.push("/login");
|
||||||
// TODO: some kind of error page?
|
return;
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
} else {
|
await this.httpApi.grantRefresh();
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError && err.code === ErrorCode.BadToken) {
|
||||||
|
log.warn({ err }, "refresh is bad for some reason, erasing");
|
||||||
|
this.tokenStore.clear();
|
||||||
this.history.push("/login");
|
this.history.push("/login");
|
||||||
|
} else {
|
||||||
|
log.error({ err }, "could not refresh access token");
|
||||||
|
// TODO: some kind of error page?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sprinklersRpc.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { TokenStore } from "@client/state/TokenStore";
|
import { TokenStore } from "@client/state/TokenStore";
|
||||||
import ApiError from "@common/ApiError";
|
import ApiError from "@common/ApiError";
|
||||||
import { ErrorCode } from "@common/ErrorCode";
|
import { ErrorCode } from "@common/ErrorCode";
|
||||||
|
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
|
||||||
|
import log from "@common/logger";
|
||||||
|
|
||||||
export { ApiError };
|
export { ApiError };
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ export default class HttpApi {
|
|||||||
}
|
}
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
|
|
||||||
this.tokenStore = new TokenStore(this);
|
this.tokenStore = new TokenStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
async makeRequest(url: string, options?: RequestInit, body?: any): Promise<any> {
|
async makeRequest(url: string, options?: RequestInit, body?: any): Promise<any> {
|
||||||
@ -49,4 +51,35 @@ export default class HttpApi {
|
|||||||
return responseBody;
|
return responseBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async grantPassword(username: string, password: string) {
|
||||||
|
const request: TokenGrantPasswordRequest = {
|
||||||
|
grant_type: "password", username, password,
|
||||||
|
};
|
||||||
|
const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
|
||||||
|
method: "POST",
|
||||||
|
}, request);
|
||||||
|
this.tokenStore.accessToken.token = response.access_token;
|
||||||
|
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||||
|
this.tokenStore.saveLocalStorage();
|
||||||
|
const { accessToken } = this.tokenStore;
|
||||||
|
log.debug({ aud: accessToken.claims!.aud }, "got password grant tokens");
|
||||||
|
}
|
||||||
|
|
||||||
|
async grantRefresh() {
|
||||||
|
const { refreshToken } = this.tokenStore;
|
||||||
|
if (!refreshToken.isValid) {
|
||||||
|
throw new ApiError("can not grant refresh with invalid refresh_token");
|
||||||
|
}
|
||||||
|
const request: TokenGrantRefreshRequest = {
|
||||||
|
grant_type: "refresh", refresh_token: refreshToken.token!,
|
||||||
|
};
|
||||||
|
const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
|
||||||
|
method: "POST",
|
||||||
|
}, request);
|
||||||
|
this.tokenStore.accessToken.token = response.access_token;
|
||||||
|
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||||
|
this.tokenStore.saveLocalStorage();
|
||||||
|
const { accessToken } = this.tokenStore;
|
||||||
|
log.debug({ aud: accessToken.claims!.aud }, "got refresh grant tokens");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
|
|
||||||
import HttpApi, { ApiError } from "@client/state/HttpApi";
|
|
||||||
import { Token } from "@client/state/Token";
|
import { Token } from "@client/state/Token";
|
||||||
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
|
|
||||||
import logger from "@common/logger";
|
|
||||||
|
|
||||||
const log = logger.child({ source: "TokenStore"});
|
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = "TokenStore";
|
const LOCAL_STORAGE_KEY = "TokenStore";
|
||||||
|
|
||||||
@ -13,12 +8,6 @@ export class TokenStore {
|
|||||||
@observable accessToken: Token = new Token();
|
@observable accessToken: Token = new Token();
|
||||||
@observable refreshToken: Token = new Token();
|
@observable refreshToken: Token = new Token();
|
||||||
|
|
||||||
private api: HttpApi;
|
|
||||||
|
|
||||||
constructor(api: HttpApi) {
|
|
||||||
this.api = api;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.accessToken.token = null;
|
this.accessToken.token = null;
|
||||||
this.refreshToken.token = null;
|
this.refreshToken.token = null;
|
||||||
@ -37,35 +26,6 @@ export class TokenStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async grantPassword(username: string, password: string) {
|
|
||||||
const request: TokenGrantPasswordRequest = {
|
|
||||||
grant_type: "password", username, password,
|
|
||||||
};
|
|
||||||
const response: TokenGrantResponse = await this.api.makeRequest("/token/grant", {
|
|
||||||
method: "POST",
|
|
||||||
}, request);
|
|
||||||
this.accessToken.token = response.access_token;
|
|
||||||
this.refreshToken.token = response.refresh_token;
|
|
||||||
this.saveLocalStorage();
|
|
||||||
log.debug({ aud: this.accessToken.claims!.aud }, "got password grant tokens");
|
|
||||||
}
|
|
||||||
|
|
||||||
async grantRefresh() {
|
|
||||||
if (!this.refreshToken.isValid) {
|
|
||||||
throw new ApiError("can not grant refresh with invalid refresh_token");
|
|
||||||
}
|
|
||||||
const request: TokenGrantRefreshRequest = {
|
|
||||||
grant_type: "refresh", refresh_token: this.refreshToken.token!,
|
|
||||||
};
|
|
||||||
const response: TokenGrantResponse = await this.api.makeRequest("/token/grant", {
|
|
||||||
method: "POST",
|
|
||||||
}, request);
|
|
||||||
this.accessToken.token = response.access_token;
|
|
||||||
this.refreshToken.token = response.refresh_token;
|
|
||||||
this.saveLocalStorage();
|
|
||||||
log.debug({ aud: this.accessToken.claims!.aud }, "got refresh grant tokens");
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() };
|
return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() };
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user