Repository navigation
Expand file tree
/
Copy pathapi.ts
More file actions
116 lines (104 loc) · 3.91 KB
/
Copy pathapi.ts
File metadata and controls
116 lines (104 loc) · 3.91 KB
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
import { appState } from '../state/app'
export type ApiResult<T> = { data: T; error: null } | { data: null; error: string }
// JSON integers larger than Number.MAX_SAFE_INTEGER (2^53-1) lose precision when
// parsed as JS numbers. Convert any 16+ digit integer value to a string first.
const LARGE_INT_RE = /([:[,])\s*(-?\d{16,})(?=[,}\]\s])/g
function safeParseJson<T>(text: string): T {
return JSON.parse(text.replace(LARGE_INT_RE, '$1 "$2"')) as T
}
function buildHeaders(includeJson: boolean): HeadersInit {
const headers: Record<string, string> = {}
const key = appState.apiKey.get()
if (key) headers['X-API-KEY'] = key
if (includeJson) headers['Content-Type'] = 'application/json'
return headers
}
async function request<T>(path: string, init: RequestInit, skipBody = false): Promise<ApiResult<T>> {
const url = appState.baseUrl.get() + path
let response: Response
try {
response = await fetch(url, init)
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
return { data: null, error: `Network error: ${msg}` }
}
if (!response.ok) {
return { data: null, error: `HTTP ${response.status}: ${response.statusText}` }
}
if (skipBody) {
return { data: null as T, error: null }
}
let data: T
try {
const text = await response.text()
data = safeParseJson<T>(text)
} catch {
return { data: null, error: 'Invalid JSON response' }
}
return { data, error: null }
}
export async function get<T>(path: string): Promise<ApiResult<T>> {
return request<T>(path, { method: 'GET', headers: buildHeaders(false) })
}
export async function post<T>(path: string, body?: unknown): Promise<ApiResult<T>> {
return request<T>(path, {
method: 'POST',
headers: buildHeaders(true),
body: JSON.stringify(body),
})
}
export async function put<T>(path: string, body?: unknown): Promise<ApiResult<T>> {
return request<T>(path, {
method: 'PUT',
headers: buildHeaders(true),
body: JSON.stringify(body),
})
}
export async function del(path: string): Promise<ApiResult<never>> {
return request<never>(path, { method: 'DELETE', headers: buildHeaders(false) }, true)
}
export async function getEmpty(path: string): Promise<ApiResult<never>> {
return request<never>(path, { method: 'GET', headers: buildHeaders(false) }, true)
}
export async function postForm<T>(path: string, formData: FormData): Promise<ApiResult<T>> {
// No Content-Type header — browser sets multipart/form-data with boundary automatically
return request<T>(path, {
method: 'POST',
headers: buildHeaders(false),
body: formData,
})
}
export async function postBinary<T>(path: string, data: ArrayBuffer | Blob): Promise<ApiResult<T>> {
const url = appState.baseUrl.get() + path
const headers = { ...(buildHeaders(false) as Record<string, string>), 'Content-Type': 'application/octet-stream' }
let response: Response
try {
response = await fetch(url, { method: 'POST', headers, body: data })
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
return { data: null, error: `Network error: ${msg}` }
}
if (!response.ok) return { data: null, error: `HTTP ${response.status}: ${response.statusText}` }
let result: T
try {
result = safeParseJson<T>(await response.text())
} catch {
return { data: null, error: 'Invalid JSON response' }
}
return { data: result, error: null }
}
export async function getText(path: string): Promise<ApiResult<string>> {
const url = appState.baseUrl.get() + path
let response: Response
try {
response = await fetch(url, { method: 'GET', headers: buildHeaders(false) })
} catch (err) {
const msg = err instanceof Error ? err.message : String(err)
return { data: null, error: `Network error: ${msg}` }
}
if (!response.ok) {
return { data: null, error: `HTTP ${response.status}: ${response.statusText}` }
}
const text = await response.text()
return { data: text, error: null }
}