Browse Source

refactoring

update-deps
Alex Mikhalev 7 years ago
parent
commit
8a6d501cda
  1. 10
      client/components/App.tsx
  2. 4
      client/components/DeviceView.tsx
  3. 10
      client/components/NavBar.tsx
  4. 4
      client/components/ProgramTable.tsx
  5. 2
      client/pages/LoginPage.tsx
  6. 2
      client/pages/LogoutPage.tsx
  7. 4
      client/pages/ProgramPage.tsx
  8. 7
      client/sprinklersRpc/WebSocketRpcClient.ts
  9. 40
      client/state/AppState.ts
  10. 35
      client/state/HttpApi.ts
  11. 40
      client/state/TokenStore.ts

10
client/components/App.tsx

@ -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
client/components/DeviceView.tsx

@ -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 (

10
client/components/NavBar.tsx

@ -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>

4
client/components/ProgramTable.tsx

@ -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}>

2
client/pages/LoginPage.tsx

@ -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");

2
client/pages/LogoutPage.tsx

@ -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" />
); );
} }

4
client/pages/ProgramPage.tsx

@ -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) => {

7
client/sprinklersRpc/WebSocketRpcClient.ts

@ -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);

40
client/state/AppState.ts

@ -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();
} }
} }

35
client/state/HttpApi.ts

@ -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");
}
} }

40
client/state/TokenStore.ts

@ -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…
Cancel
Save