IDEA: Track consistency of FE/API/FBOS data at start/stop of request.
parent
c17f21a596
commit
4496cf3040
|
@ -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,
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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 "*";
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue