UNPKG

5.76 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getGot = void 0;
4const js_lib_1 = require("@naturalcycles/js-lib");
5const got_1 = require("got");
6const __1 = require("..");
7const colors_1 = require("../colors");
8/**
9 * Returns instance of Got with "reasonable defaults":
10 *
11 * 1. Error handler hook that prints helpful errors.
12 * 2. Hooks that log start/end of request (optional, false by default).
13 * 3. Reasonable defaults(tm), e.g non-infinite Timeout
14 */
15function getGot(opt = {}) {
16 return got_1.default.extend({
17 // Most-important is to set to anything non-empty (so, requests don't "hang" by default).
18 // Should be long enough to handle for slow responses from scaled cloud APIs in times of spikes
19 // Ideally should be LESS than default Request timeout in backend-lib (so, it has a chance to error
20 // before server times out with 503).
21 timeout: 90000,
22 ...opt,
23 hooks: {
24 ...opt.hooks,
25 beforeError: [
26 ...(opt.hooks?.beforeError || []),
27 // User hooks go BEFORE
28 gotErrorHook(opt),
29 ],
30 beforeRequest: [
31 gotBeforeRequestHook(opt),
32 // User hooks go AFTER
33 ...(opt.hooks?.beforeRequest || []),
34 ],
35 afterResponse: [
36 ...(opt.hooks?.afterResponse || []),
37 // User hooks go BEFORE
38 gotAfterResponseHook(opt),
39 ],
40 },
41 });
42}
43exports.getGot = getGot;
44/**
45 * Without this hook (default behaviour):
46 *
47 * HTTPError: Response code 422 (Unprocessable Entity)
48 * at EventEmitter.<anonymous> (.../node_modules/got/dist/source/as-promise.js:118:31)
49 * at processTicksAndRejections (internal/process/task_queues.js:97:5) {
50 * name: 'HTTPError'
51 *
52 *
53 * With this hook:
54 *
55 * HTTPError 422 GET http://a.com/err?q=1 in 8 ms
56 * {
57 * message: 'Reference already exists',
58 * documentation_url: 'https://developer.github.com/v3/git/refs/#create-a-reference'
59 * }
60 *
61 * Features:
62 * 1. Includes original method and URL (including e.g searchParams) in the error message.
63 * 2. Includes response.body in the error message (limited length).
64 * 3. Auto-detects and parses JSON response body (limited length).
65 * 4. Includes time spent (gotBeforeRequestHook must also be enabled).
66 * UPD: excluded now to allow automatic Sentry error grouping
67 *
68 * todo (try): Return error as familiar/convenient js-lib's HttpError (not got's HTTPError)
69 */
70function gotErrorHook(opt = {}) {
71 const { maxResponseLength = 10000 } = opt;
72 return err => {
73 if (err instanceof got_1.HTTPError) {
74 const { statusCode } = err.response;
75 const { method, url, prefixUrl } = err.options;
76 const shortUrl = getShortUrl(opt, url, prefixUrl);
77 // const { started } = context as GotRequestContext
78 // Auto-detect and prettify JSON response (if any)
79 let body = js_lib_1._jsonParseIfPossible(err.response.body);
80 // Detect HttpErrorResponse
81 if (js_lib_1._isHttpErrorResponse(body)) {
82 body = body.error;
83 }
84 body = __1.inspectAny(body, {
85 maxLen: maxResponseLength,
86 colors: false,
87 });
88 // timings are not part of err.message to allow automatic error grouping in Sentry
89 err.message = [[statusCode, method, shortUrl].filter(Boolean).join(' '), body]
90 .filter(Boolean)
91 .join('\n');
92 }
93 return err;
94 };
95}
96function gotBeforeRequestHook(opt) {
97 return options => {
98 options.context = {
99 ...options.context,
100 started: Date.now(),
101 };
102 if (opt.logStart) {
103 const shortUrl = getShortUrl(opt, options.url, options.prefixUrl);
104 console.log([colors_1.dimGrey(' >>'), colors_1.dimGrey(options.method), colors_1.grey(shortUrl)].join(' '));
105 }
106 };
107}
108function gotAfterResponseHook(opt = {}) {
109 return resp => {
110 const success = resp.statusCode >= 200 && resp.statusCode < 400;
111 if (opt.logFinished) {
112 const { started } = resp.request.options.context;
113 const { url, prefixUrl, method } = resp.request.options;
114 const shortUrl = getShortUrl(opt, url, prefixUrl);
115 console.log([
116 colors_1.dimGrey(' <<'),
117 coloredHttpCode(resp.statusCode),
118 colors_1.dimGrey(method),
119 colors_1.grey(shortUrl),
120 started && colors_1.dimGrey('in ' + js_lib_1._since(started)),
121 ]
122 .filter(Boolean)
123 .join(' '));
124 // console.log(`afterResp! ${resp.request.options.method} ${resp.url}`, { context: resp.request.options.context })
125 }
126 // Error responses are not logged, cause they're included in Error message already
127 if (opt.logResponse && success) {
128 console.log(__1.inspectAny(js_lib_1._jsonParseIfPossible(resp.body), { maxLen: opt.maxResponseLength }));
129 }
130 return resp;
131 };
132}
133function coloredHttpCode(statusCode) {
134 if (statusCode < 400)
135 return colors_1.dimGrey(statusCode); // default
136 if (statusCode < 500)
137 return colors_1.yellow(statusCode);
138 return colors_1.red(statusCode);
139}
140function getShortUrl(opt, url, prefixUrl) {
141 let shortUrl = url.toString();
142 if (opt.logWithSearchParams === false) {
143 shortUrl = shortUrl.split('?')[0];
144 }
145 if (opt.logWithPrefixUrl === false && prefixUrl && shortUrl.startsWith(prefixUrl)) {
146 shortUrl = shortUrl.slice(prefixUrl.length);
147 }
148 return shortUrl;
149}