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 * as p from "@client/pages";
|
||||
import * as rp from "@client/routePaths";
|
||||
import * as route from "@client/routePaths";
|
||||
|
||||
// tslint:disable:ordered-imports
|
||||
import "font-awesome/css/font-awesome.css";
|
||||
@ -18,8 +18,8 @@ function NavContainer() {
|
||||
<NavBar/>
|
||||
|
||||
<Switch>
|
||||
<Route path={rp.device(":deviceId")} component={p.DevicePage}/>
|
||||
<Route path={rp.messagesTest} component={p.MessagesTestPage}/>
|
||||
<Route path={route.device(":deviceId")} component={p.DevicePage}/>
|
||||
<Route path={route.messagesTest} component={p.MessagesTestPage}/>
|
||||
<Redirect to="/"/>
|
||||
</Switch>
|
||||
|
||||
@ -31,8 +31,8 @@ function NavContainer() {
|
||||
export default function App() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={rp.login} component={p.LoginPage}/>
|
||||
<Route path={rp.logout} component={p.LogoutPage}/>
|
||||
<Route path={route.login} component={p.LoginPage}/>
|
||||
<Route path={route.logout} component={p.LogoutPage}/>
|
||||
<NavContainer/>
|
||||
</Switch>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
|
||||
|
||||
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 { ConnectionState as ConState } from "@common/sprinklersRpc";
|
||||
import { Route, RouteComponentProps, withRouter } from "react-router";
|
||||
@ -67,7 +67,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<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>
|
||||
);
|
||||
return (
|
||||
|
@ -3,7 +3,7 @@ import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
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";
|
||||
|
||||
interface NavItemProps {
|
||||
@ -26,17 +26,17 @@ function NavBar({ appState }: { appState: AppState }) {
|
||||
let loginMenu;
|
||||
if (appState.isLoggedIn) {
|
||||
loginMenu = (
|
||||
<NavItem to={rp.logout}>Logout</NavItem>
|
||||
<NavItem to={route.logout}>Logout</NavItem>
|
||||
);
|
||||
} else {
|
||||
loginMenu = (
|
||||
<NavItem to={rp.login}>Login</NavItem>
|
||||
<NavItem to={route.login}>Login</NavItem>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Menu>
|
||||
<NavItem to={rp.device("grinklers")}>Device grinklers</NavItem>
|
||||
<NavItem to={rp.messagesTest}>Messages test</NavItem>
|
||||
<NavItem to={route.device("grinklers")}>Device grinklers</NavItem>
|
||||
<NavItem to={route.messagesTest}>Messages test</NavItem>
|
||||
<Menu.Menu position="right">
|
||||
{loginMenu}
|
||||
</Menu.Menu>
|
||||
|
@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
|
||||
import { Button, ButtonProps, Form, Icon, Table } from "semantic-ui-react";
|
||||
|
||||
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||
import * as rp from "@client/routePaths";
|
||||
import * as route from "@client/routePaths";
|
||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||
|
||||
@observer
|
||||
@ -21,7 +21,7 @@ class ProgramRows extends React.Component<{
|
||||
const { name, running, enabled, schedule, sequence } = program;
|
||||
|
||||
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 = (
|
||||
<Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}>
|
||||
|
@ -30,7 +30,7 @@ class LoginPageState {
|
||||
login(appState: AppState) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
appState.tokenStore.grantPassword(this.username, this.password)
|
||||
appState.httpApi.grantPassword(this.username, this.password)
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
log.info("logged in");
|
||||
|
@ -7,7 +7,7 @@ export function LogoutPage() {
|
||||
function consumeState(appState: AppState) {
|
||||
appState.tokenStore.clear();
|
||||
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 { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||
import * as rp from "@client/routePaths";
|
||||
import * as route from "@client/routePaths";
|
||||
import { AppState, injectState } from "@client/state";
|
||||
import log from "@common/logger";
|
||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||
@ -189,7 +189,7 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
||||
|
||||
private close = () => {
|
||||
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) => {
|
||||
|
@ -105,7 +105,6 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
||||
}
|
||||
|
||||
start() {
|
||||
log.debug({ url: this.webSocketUrl }, "connecting to websocket");
|
||||
this._connect();
|
||||
}
|
||||
|
||||
@ -222,6 +221,12 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
||||
}
|
||||
|
||||
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.onopen = this.onOpen.bind(this);
|
||||
this.socket.onclose = this.onClose.bind(this);
|
||||
|
@ -25,28 +25,32 @@ export default class AppState {
|
||||
|
||||
async start() {
|
||||
syncHistoryWithStore(this.history, this.routerStore);
|
||||
|
||||
this.tokenStore.loadLocalStorage();
|
||||
|
||||
if (!this.httpApi.tokenStore.accessToken.isValid) {
|
||||
if (this.httpApi.tokenStore.refreshToken.isValid) {
|
||||
try {
|
||||
await this.httpApi.tokenStore.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");
|
||||
} else {
|
||||
log.error({ err }, "could not refresh access token");
|
||||
// TODO: some kind of error page?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.checkToken();
|
||||
await this.sprinklersRpc.start();
|
||||
}
|
||||
|
||||
async checkToken() {
|
||||
const { tokenStore: { accessToken, refreshToken } } = this.httpApi;
|
||||
if (accessToken.isValid) { // if the access token is valid, we are good
|
||||
return;
|
||||
}
|
||||
if (!refreshToken.isValid) { // if the refresh token is not valid, need to login again
|
||||
this.history.push("/login");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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");
|
||||
} 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 ApiError from "@common/ApiError";
|
||||
import { ErrorCode } from "@common/ErrorCode";
|
||||
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
|
||||
import log from "@common/logger";
|
||||
|
||||
export { ApiError };
|
||||
|
||||
@ -22,7 +24,7 @@ export default class HttpApi {
|
||||
}
|
||||
this.baseUrl = baseUrl;
|
||||
|
||||
this.tokenStore = new TokenStore(this);
|
||||
this.tokenStore = new TokenStore();
|
||||
}
|
||||
|
||||
async makeRequest(url: string, options?: RequestInit, body?: any): Promise<any> {
|
||||
@ -49,4 +51,35 @@ export default class HttpApi {
|
||||
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 HttpApi, { ApiError } from "@client/state/HttpApi";
|
||||
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";
|
||||
|
||||
@ -13,12 +8,6 @@ export class TokenStore {
|
||||
@observable accessToken: Token = new Token();
|
||||
@observable refreshToken: Token = new Token();
|
||||
|
||||
private api: HttpApi;
|
||||
|
||||
constructor(api: HttpApi) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.accessToken.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() {
|
||||
return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() };
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user