Passed
Push — task/upgrade-to-laravel-8 ( 4eedef )
by Grant
07:38
created

resources/assets/js/store/asyncAction.ts   A

Complexity

Total Complexity 5
Complexity/F 5

Size

Lines of Code 276
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 5
eloc 229
dl 0
loc 276
rs 10
c 0
b 0
f 0
mnd 4
bc 4
fnc 1
bpm 4
cpm 5
noi 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A asyncAction.ts ➔ jsonDateReplacer 0 6 3
1
import {
2
  RSAA,
3
  RSAAction,
4
  HTTPVerb,
5
  RequestError,
6
  ApiError,
7
  InternalError,
8
  getJSON,
9
} from "redux-api-middleware"; // RSAA = '@@redux-api-middleware/RSAA'
10
import dayjs from "dayjs";
11
import { ErrorAction } from "./createAction";
12
13
export const STARTED = "STARTED";
14
export const SUCCEEDED = "SUCCEEDED";
15
export const FAILED = "FAILED";
16
17
export interface StartedAction<T extends string, M> {
18
  type: T;
19
  meta: M;
20
  error?: boolean;
21
}
22
23
export interface SucceededAction<T extends string, P, M> {
24
  type: T;
25
  payload: P;
26
  meta: M;
27
  error?: boolean;
28
}
29
30
export interface FailedAction<T extends string, M = {}>
31
  extends ErrorAction<T, M> {
32
  type: T;
33
  payload: RequestError | ApiError | InternalError | Error; // TODO: remove normal error when all async actions using this
34
  meta: M;
35
  error: true;
36
}
37
38
/** The set of Flux Standard Actions which may result from an async request */
39
export type AsyncFsaActions<
40
  R extends string,
41
  S extends string,
42
  F extends string,
43
  P,
44
  M
45
> = StartedAction<R, M> | SucceededAction<S, P, M> | FailedAction<F, M>;
46
47
/** Internal definition, that doesn't quite represent final dispatched actions */
48
interface StartedActionCreator<T extends string, M> {
49
  type: T;
50
  meta: M;
51
}
52
53
/** Internal definition, that doesn't quite represent final dispatched actions */
54
interface SucceededActionCreator<T extends string, P, M> {
55
  type: T;
56
  payload: (action, state, res) => PromiseLike<P>;
57
  meta: M;
58
}
59
60
/** Internal definition, that doesn't quite represent final dispatched actions */
61
interface FailedActionCreator<T extends string, M> {
62
  type: T;
63
  meta: M;
64
}
65
66
/** A convenience type, for defining the return value of the rsa action generator. */
67
export type RSAActionTemplate<
68
  TStarted extends string,
69
  TSuccess extends string,
70
  TFailed extends string,
71
  TPayload,
72
  TMeta
73
> = RSAAction<
74
  TStarted,
75
  TSuccess,
76
  TFailed,
77
  PromiseLike<TPayload>,
78
  Error,
79
  {},
80
  TMeta,
81
  TMeta,
82
  TMeta,
83
  any
84
>;
85
86
// TODO: is this the best way to get this?
87
const csrfElement = document.head.querySelector('meta[name="csrf-token"]');
88
const csrfToken: string =
89
  csrfElement && csrfElement.getAttribute("content")
90
    ? (csrfElement.getAttribute("content") as string)
91
    : "";
92
93
function jsonDateReplacer(key, value): string | any {
94
  if (this[key] instanceof Date) {
95
    return dayjs(value).format("YYYY-MM-DDTHH:mm:ssZ");
96
  }
97
  return value;
98
}
99
100
/**
101
 *
102
 * @param endpoint
103
 * @param method
104
 * @param body
105
 * @param startedType
106
 * @param succeededType
107
 * @param failedType
108
 * @param parseResponse This function must accept the response body and return a payload.
109
 * @param metaData
110
 */
111
export const asyncAction = <
112
  TStarted extends string,
113
  TSuccess extends string,
114
  TFailed extends string,
115
  TPayload,
116
  TMeta
117
>(
118
  endpoint: string,
119
  method: HTTPVerb,
120
  body: object | string | null,
121
  startedType: TStarted,
122
  succeededType: TSuccess,
123
  failedType: TFailed,
124
  parseResponse: (response: any) => TPayload,
125
  metaData: TMeta,
126
): RSAActionTemplate<TStarted, TSuccess, TFailed, TPayload, TMeta> => {
127
  const basicHeaders = {
128
    "X-CSRF-TOKEN": csrfToken,
129
    Accept: "application/json",
130
  };
131
  const jsonBodyHeader = { "Content-Type": "application/json" }; // informs server that the body is a json encoded string
132
  const headers =
133
    body === null ? basicHeaders : { ...basicHeaders, ...jsonBodyHeader };
134
135
  let stringBody: string | null = null;
136
  if (body instanceof Object) {
137
    // We must stringify any object bodies, and ensure dates are formatted as server expects.
138
    stringBody = JSON.stringify(body, jsonDateReplacer);
139
  } else {
140
    stringBody = body;
141
  }
142
143
  return {
144
    [RSAA]: {
145
      endpoint,
146
      method,
147
      headers,
148
      ...(stringBody && { body: stringBody }),
149
      fetch, // Ensure the global fetch function is being used
150
      credentials: "include", // TODO: this may be removed when we change to token auth
151
      types: [
152
        {
153
          type: startedType,
154
          meta: metaData,
155
        },
156
        {
157
          type: succeededType,
158
          payload: (action, state, res: Response): PromiseLike<TPayload> =>
159
            getJSON(res).then(parseResponse),
160
          meta: metaData,
161
        },
162
        {
163
          type: failedType,
164
          meta: metaData,
165
        },
166
      ],
167
    },
168
  };
169
};
170
171
export const asyncGet = <
172
  TStarted extends string,
173
  TSuccess extends string,
174
  TFailed extends string,
175
  TPayload,
176
  TMeta
177
>(
178
  endpoint: string,
179
  startedType: TStarted,
180
  succeededType: TSuccess,
181
  failedType: TFailed,
182
  parseResponse: (response: any) => TPayload,
183
  metaData: TMeta,
184
): RSAActionTemplate<TStarted, TSuccess, TFailed, TPayload, TMeta> =>
185
  asyncAction(
186
    endpoint,
187
    "GET",
188
    null,
189
    startedType,
190
    succeededType,
191
    failedType,
192
    parseResponse,
193
    metaData,
194
  );
195
196
export const asyncPut = <
197
  TBody extends object | string,
198
  TStarted extends string,
199
  TSuccess extends string,
200
  TFailed extends string,
201
  TPayload,
202
  TMeta
203
>(
204
  endpoint: string,
205
  body: TBody,
206
  startedType: TStarted,
207
  succeededType: TSuccess,
208
  failedType: TFailed,
209
  parseResponse: (response: any) => TPayload,
210
  metaData: TMeta,
211
): RSAActionTemplate<TStarted, TSuccess, TFailed, TPayload, TMeta> =>
212
  asyncAction(
213
    endpoint,
214
    "PUT",
215
    body,
216
    startedType,
217
    succeededType,
218
    failedType,
219
    parseResponse,
220
    metaData,
221
  );
222
223
export const asyncPost = <
224
  TBody extends object | string,
225
  TStarted extends string,
226
  TSuccess extends string,
227
  TFailed extends string,
228
  TPayload,
229
  TMeta
230
>(
231
  endpoint: string,
232
  body: TBody,
233
  startedType: TStarted,
234
  succeededType: TSuccess,
235
  failedType: TFailed,
236
  parseResponse: (response: any) => TPayload,
237
  metaData: TMeta,
238
): RSAActionTemplate<TStarted, TSuccess, TFailed, TPayload, TMeta> =>
239
  asyncAction(
240
    endpoint,
241
    "POST",
242
    body,
243
    startedType,
244
    succeededType,
245
    failedType,
246
    parseResponse,
247
    metaData,
248
  );
249
250
export const asyncDelete = <
251
  TStarted extends string,
252
  TSuccess extends string,
253
  TFailed extends string,
254
  TPayload,
255
  TMeta
256
>(
257
  endpoint: string,
258
  startedType: TStarted,
259
  succeededType: TSuccess,
260
  failedType: TFailed,
261
  parseResponse: (response: any) => TPayload,
262
  metaData: TMeta,
263
): RSAActionTemplate<TStarted, TSuccess, TFailed, TPayload, TMeta> =>
264
  asyncAction(
265
    endpoint,
266
    "DELETE",
267
    null,
268
    startedType,
269
    succeededType,
270
    failedType,
271
    parseResponse,
272
    metaData,
273
  );
274
275
export default asyncAction;
276