-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathapiService.ts
121 lines (107 loc) · 3.89 KB
/
apiService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { useAuthentication } from "@/lib/auth"
import { getFallbackError } from "@/lib/errorResponseMapper"
import { createFetch, UseFetchReturn } from "@vueuse/core"
/**
* The same as UseFetchReturn, but without the methods to get more specific useFetch instances.
*/
export type SimpleUseFetchReturn<T> = Omit<
UseFetchReturn<T>,
| "get"
| "post"
| "put"
| "delete"
| "patch"
| "head"
| "options"
| "json"
| "text"
| "blob"
| "arrayBuffer"
| "formData"
>
/* -------------------------------------------------- *
* Reactive API fetch *
* -------------------------------------------------- */
/** Fetch data from the backend api using useFetch. */
export const useApiFetch = createFetch({
baseUrl: "/api/v1",
options: {
async beforeFetch({ options, url, cancel }) {
// useFetch requires the URL to always be there, but in some cases we
// can't construct a meaningful URL (e.g. because a required param is
// missing). For those cases we introduce INVALID_URL as a convention
// to tell useFetch to cancel the request.
if (url.endsWith(INVALID_URL)) {
cancel()
return
}
// Only set the Content-Type if the body is not FormData
if (!(options.body instanceof FormData)) {
options.headers = {
Accept: "application/json",
"Content-Type": "application/json",
...options.headers,
}
} else {
// Let the browser handle the Content-Type for FormData
options.headers = {
Accept: "application/json",
...options.headers,
}
}
// Authorize requests
const { addAuthorizationHeader, tryRefresh } = useAuthentication()
const hasValidSession = await tryRefresh()
if (!hasValidSession) cancel()
options.headers = addAuthorizationHeader(options.headers)
return { options }
},
onFetchError(fetchContext) {
// this error is sometimes throws when previous requests are automatically aborted as
// some of the data changed and refetch is true. It seems to only be throws when the request
// is aborted before it was actually send.
// We ignore this error as it (for some odd reason) isn't replaced once the second request finishes
// successfully
if (fetchContext.error.name === "AbortError") {
return {
...fetchContext,
error: null,
}
}
// In the useFetch implementation, `fetchContext.error` is the only thing
// that will be provided to the UI by default. This is only an error
// object though and access to the response data of an error is not possible.
//
// There is an option to replace the normal `data` property returned by
// useFetch with the error response, but that would mean that `data` can
// have different contents depending on the success/failure of the request,
// and would require lots of type checking wherever we need to access
// the response data.
//
// Since we're only ever interested in the data and never the error object,
// we'll replace the `fetchContext.error` with the response data so we can
// access it in the UI.
const baseError = fetchContext.data ?? getFallbackError()
fetchContext.error = {
...baseError,
status: fetchContext.response?.status ?? null,
}
return fetchContext
},
},
})
/**
* Special string that can be used in places where you want to express that
* a URL should not be fetched or used in any way, but you are still required
* to provide a string value (e.g. when providing a computed URL to useFetch).
* Example:
*
* ```ts
* const url = computed(() => someCondition ? 'example.com' : INVALID_URL)
* useFetch(url, {
* beforeFetch(ctx) {
* if (url === INVALID_URL) ctx.abort()
* }
* })
*/
export const INVALID_URL = "__invalid_url__"