IDEA: Track consistency of FE/API/FBOS data at start/stop of request.

pull/529/head
Rick Carlino 2017-11-12 22:03:41 -06:00
parent c17f21a596
commit 4496cf3040
4 changed files with 72 additions and 5 deletions

View File

@ -54,8 +54,8 @@ function loginErr() {
* have a JSON Web Token attached to their "Authorization" header,
* thereby granting access to the API. */
export function setToken(auth: AuthState): ReduxAction<AuthState> {
axios.interceptors.response.use(responseFulfilled, responseRejected);
axios.interceptors.request.use(requestFulfilled(auth));
axios.interceptors.response.use(responseFulfilled, responseRejected);
return {
type: Actions.REPLACE_TOKEN,

View File

@ -0,0 +1,63 @@
import { AxiosRequestConfig } from "axios";
import { get, set } from "lodash";
import { uuid } from "farmbot";
const WHERE_WE_STORE_REQUEST_ID = "__FARMBOT_REQUEST_ID__";
function getRequestId(conf: AxiosRequestConfig): string {
return get(conf, WHERE_WE_STORE_REQUEST_ID);
}
export function setRequestId(conf: AxiosRequestConfig): string {
const u = uuid();
set(conf, [WHERE_WE_STORE_REQUEST_ID], u);
return u;
}
/**
* PROBLEM: You save a sequence and click "RUN" very fast. The remote device
* did not have time to download the new sequence and so it crashes
* with a "sequence not found error".
*
* This is strictly a frontend consern. We really don't want the FE to
* serve as a mediator between the device and the end user. It causes
* all sorts of consistency issues and hard-to-catch errors. This is a
* local client issue and we want the issue to stay local instead of
* applying hacks to different parts of the stack.
*
* SOLUTION:
*
* - When you send an AJAX request, put a UUID onto a heap of "outbound
* request occurences"
* - When the request comes back, regardless of the outcome, remove that
* particular UUID from the heap. ("graveful removal")
* - If the request takes longer than 5 seconds, remove it from the heap also
* to prevent accidental lockups. ("forceful removal")
* - If the outbound heap has `0` records, you can (probably) assume that the
* Bot, API and client are in a consistent state.
*
* LONG TERM SOLUTION:
*
* - We should consider moving CRUD operations into FarmBotJS and provide
* developers a unified API that handles these things.
* - If data operations were RPCs instead of REST calls, we would not need
* this and we could track data operations the same way a `exec_sequence`
* and friends.
*/
export function startTracking(conf: AxiosRequestConfig) {
const id = setRequestId(conf);
if (id) {
console.log(`START TRACKING ${conf.url}`);
} else {
throw new Error("NO ID");
}
}
export function stopTracking(conf: AxiosRequestConfig) {
const id = getRequestId(conf);
if (id) {
console.log(`STOP TRACKING ${conf.url}`);
} else {
throw new Error("NO ID");
}
}

View File

@ -69,14 +69,14 @@ export function notifyBotOfChanges(url: string | undefined, action: DataChangeTy
* notifications. */
function inferUpdateId(url: string) {
try {
let ids = url
const ids = url
.split("/")
.filter(x => !x.includes(",")) // Don't allow batch endpoints to participate.
.map(x => parseInt(x, 10))
.filter(x => !_.isNaN(x));
let id: number | undefined = ids[0];
let isNum = _.isNumber(id);
let onlyOne = ids.length === 1;
const id: number | undefined = ids[0];
const isNum = _.isNumber(id);
const onlyOne = ids.length === 1;
return (isNum && onlyOne) ? ("" + id) : "*";
} catch (error) { // Don't crash - just keep moving along. This is a temp patch.
return "*";

View File

@ -13,10 +13,12 @@ import * as _ from "lodash";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Content } from "./constants";
import { dispatchNetworkUp, dispatchNetworkDown } from "./connectivity/index";
import { startTracking, stopTracking } from "./connectivity/data_consistency";
export function responseFulfilled(input: AxiosResponse): AxiosResponse {
const method = input.config.method;
dispatchNetworkUp("user.api");
stopTracking(input.config);
if (method && METHODS.includes(method)) {
notifyBotOfChanges(input.config.url, METHOD_MAP[method]);
}
@ -24,6 +26,7 @@ export function responseFulfilled(input: AxiosResponse): AxiosResponse {
}
export function responseRejected(x: SafeError | undefined) {
stopTracking((x as any).config);
if (x && isSafeError(x)) {
dispatchNetworkUp("user.api");
const a = ![451, 401, 422].includes(x.response.status);
@ -67,6 +70,7 @@ export function requestFulfilled(auth: AuthState) {
const headers = (config.headers as
{ Authorization: string | undefined });
headers.Authorization = auth.token.encoded || "CANT_FIND_TOKEN";
startTracking(config);
}
return config;
};