/
fetch.ts
137 lines (117 loc) · 4.19 KB
/
fetch.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { debug } from "../debug"
import * as node_fetch from "node-fetch"
import HttpProxyAgent from "http-proxy-agent"
import HttpsProxyAgent from "https-proxy-agent"
import AsyncRetry from "async-retry"
const d = debug("networking")
declare const global: any
const isJest = typeof jest !== "undefined"
const warn = isJest ? () => "" : console.warn
const shouldRetryRequest = (res: node_fetch.Response) => {
// Don't retry 4xx errors other than 401. All 4xx errors can probably be ignored once
// the Github API issue causing https://github.com/danger/peril/issues/440 is fixed
return res.status === 401 || (res.status >= 500 && res.status <= 599)
}
/**
* Adds retry handling to fetch requests
*
* @param {(string | fetch.Request)} url the request
* @param {fetch.RequestInit} [init] the usual options
* @returns {Promise<fetch.Response>} network-y promise
*/
export async function retryableFetch(
url: string | node_fetch.Request,
init: node_fetch.RequestInit
): Promise<node_fetch.Response> {
const retries = isJest ? 1 : 3
return AsyncRetry(
async (_, attempt) => {
const originalFetch = node_fetch.default
const res = await originalFetch(url, init)
// Throwing an error will trigger a retry
if (attempt <= retries && shouldRetryRequest(res)) {
throw new Error(`Request failed [${res.status}]: ${res.url}. Attempting retry.`)
}
return res
},
{
retries: retries,
onRetry: (error, attempt) => {
warn(error.message)
warn(`Retry ${attempt} of ${retries}.`)
},
}
)
}
/**
* Adds logging to every fetch request if a global var for `verbose` is set to true
*
* @param {(string | fetch.Request)} url the request
* @param {fetch.RequestInit} [init] the usual options
* @returns {Promise<fetch.Response>} network-y promise
*/
export function api(
url: string | node_fetch.Request,
init: node_fetch.RequestInit,
suppressErrorReporting?: boolean,
processEnv: NodeJS.ProcessEnv = process.env
): Promise<node_fetch.Response> {
const isTests = typeof jest !== "undefined"
if (isTests && !url.toString().includes("localhost")) {
const message = `No API calls in tests please: ${url}`
debugger
throw new Error(message)
}
if (global.verbose && global.verbose === true) {
const output = ["curl", "-i"]
if (init.method) {
output.push(`-X ${init.method}`)
}
const showToken = processEnv["DANGER_VERBOSE_SHOW_TOKEN"]
const token = processEnv["DANGER_GITHUB_API_TOKEN"] || processEnv["GITHUB_TOKEN"]
if (init.headers) {
for (const prop in init.headers) {
if (init.headers.hasOwnProperty(prop)) {
// Don't show the token for normal verbose usage
if (init.headers[prop].includes(token) && !showToken) {
output.push("-H", `"${prop}: [API TOKEN]"`)
continue
}
output.push("-H", `"${prop}: ${init.headers[prop]}"`)
}
}
}
if (init.method === "POST") {
// const body:string = init.body
// output.concat([init.body])
}
if (typeof url === "string") {
output.push(url)
}
d(output.join(" "))
}
let agent = init.agent
const proxy =
processEnv["HTTPS_PROXY"] || processEnv["https_proxy"] || processEnv["HTTP_PROXY"] || processEnv["http_proxy"]
if (!agent && proxy) {
let secure = url.toString().startsWith("https")
init.agent = secure ? new HttpsProxyAgent(proxy) : new HttpProxyAgent(proxy)
}
return retryableFetch(url, init).then(async (response: node_fetch.Response) => {
// Handle failing errors
if (!suppressErrorReporting && !response.ok) {
// we should not modify the response when an error occur to allow body stream to be read again if needed
let clonedResponse = response.clone()
warn(`Request failed [${clonedResponse.status}]: ${clonedResponse.url}`)
let responseBody = await clonedResponse.text()
try {
// tries to pretty print the JSON response when possible
const responseJSON = await JSON.parse(responseBody.toString())
warn(`Response: ${JSON.stringify(responseJSON, null, " ")}`)
} catch (e) {
warn(`Response: ${responseBody}`)
}
}
return response
})
}