1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.getGot = void 0;
|
4 | const js_lib_1 = require("@naturalcycles/js-lib");
|
5 | const got_1 = require("got");
|
6 | const __1 = require("..");
|
7 | const 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 | */
|
15 | function 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 | }
|
43 | exports.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 | */
|
70 | function 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 | }
|
96 | function 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 | }
|
108 | function 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 | }
|
133 | function 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 | }
|
140 | function 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 | }
|