1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const chalk = require("chalk");
|
4 | const lodash = require("lodash");
|
5 | const util = require("util");
|
6 | const guards_1 = require("../guards");
|
7 | const color_1 = require("./color");
|
8 | const errors_1 = require("./errors");
|
9 | const http_1 = require("./utils/http");
|
10 | const FORMAT_ERROR_BODY_MAX_LENGTH = 1000;
|
11 | exports.CONTENT_TYPE_JSON = 'application/json';
|
12 | exports.ERROR_UNKNOWN_CONTENT_TYPE = 'UNKNOWN_CONTENT_TYPE';
|
13 | exports.ERROR_UNKNOWN_RESPONSE_FORMAT = 'UNKNOWN_RESPONSE_FORMAT';
|
14 | class Client {
|
15 | constructor(config) {
|
16 | this.config = config;
|
17 | }
|
18 | async make(method, path) {
|
19 | const url = path.startsWith('http://') || path.startsWith('https://') ? path : `${this.config.getAPIUrl()}${path}`;
|
20 | const { req } = await http_1.createRequest(method, url, this.config.getHTTPConfig());
|
21 | req
|
22 | .set('Content-Type', exports.CONTENT_TYPE_JSON)
|
23 | .set('Accept', exports.CONTENT_TYPE_JSON);
|
24 | return { req };
|
25 | }
|
26 | async do(req) {
|
27 | const res = await req;
|
28 | const r = transformAPIResponse(res);
|
29 | if (guards_1.isAPIResponseError(r)) {
|
30 | throw new errors_1.FatalException('API request was successful, but the response output format was that of an error.\n' +
|
31 | formatAPIResponse(req, r));
|
32 | }
|
33 | return r;
|
34 | }
|
35 | paginate(args) {
|
36 | return new Paginator({ client: this, ...args });
|
37 | }
|
38 | }
|
39 | exports.Client = Client;
|
40 | class Paginator {
|
41 | constructor({ client, reqgen, guard, state, max }) {
|
42 | const defaultState = { page: 1, done: false, loaded: 0 };
|
43 | this.client = client;
|
44 | this.reqgen = reqgen;
|
45 | this.guard = guard;
|
46 | this.max = max;
|
47 | if (!state) {
|
48 | state = { page_size: 100, ...defaultState };
|
49 | }
|
50 | this.state = lodash.assign({}, state, defaultState);
|
51 | }
|
52 | next() {
|
53 | if (this.state.done) {
|
54 | return { done: true };
|
55 | }
|
56 | return {
|
57 | done: false,
|
58 | value: (async () => {
|
59 | const { req } = await this.reqgen();
|
60 | req.query(lodash.pick(this.state, ['page', 'page_size']));
|
61 | const res = await this.client.do(req);
|
62 | if (!this.guard(res)) {
|
63 | throw createFatalAPIFormat(req, res);
|
64 | }
|
65 | this.state.loaded += res.data.length;
|
66 | if (res.data.length === 0 ||
|
67 | (typeof this.max === 'number' && this.state.loaded >= this.max) ||
|
68 | (typeof this.state.page_size === 'number' && res.data.length < this.state.page_size)
|
69 | ) {
|
70 | this.state.done = true;
|
71 | }
|
72 | this.state.page++;
|
73 | return res;
|
74 | })(),
|
75 | };
|
76 | }
|
77 | [Symbol.iterator]() {
|
78 | return this;
|
79 | }
|
80 | }
|
81 | exports.Paginator = Paginator;
|
82 | class TokenPaginator {
|
83 | constructor({ client, reqgen, guard, state, max }) {
|
84 | const defaultState = { done: false, loaded: 0 };
|
85 | this.client = client;
|
86 | this.reqgen = reqgen;
|
87 | this.guard = guard;
|
88 | this.max = max;
|
89 | if (!state) {
|
90 | state = { ...defaultState };
|
91 | }
|
92 | this.state = lodash.assign({}, state, defaultState);
|
93 | }
|
94 | next() {
|
95 | if (this.state.done) {
|
96 | return { done: true };
|
97 | }
|
98 | return {
|
99 | done: false,
|
100 | value: (async () => {
|
101 | const { req } = await this.reqgen();
|
102 | if (this.state.page_token) {
|
103 | req.query({ page_token: this.state.page_token });
|
104 | }
|
105 | const res = await this.client.do(req);
|
106 | if (!this.isPageTokenResponseMeta(res.meta)) {
|
107 | throw createFatalAPIFormat(req, res);
|
108 | }
|
109 | const nextPageToken = res.meta.next_page_token;
|
110 | if (!this.guard(res)) {
|
111 | throw createFatalAPIFormat(req, res);
|
112 | }
|
113 | this.state.loaded += res.data.length;
|
114 | if (res.data.length === 0 ||
|
115 | (typeof this.max === 'number' && this.state.loaded >= this.max) ||
|
116 | !nextPageToken
|
117 | ) {
|
118 | this.state.done = true;
|
119 | }
|
120 | this.state.page_token = nextPageToken;
|
121 | return res;
|
122 | })(),
|
123 | };
|
124 | }
|
125 | isPageTokenResponseMeta(meta) {
|
126 | return meta
|
127 | && (!meta.prev_page_token || typeof meta.prev_page_token === 'string')
|
128 | && (!meta.next_page_token || typeof meta.next_page_token === 'string');
|
129 | }
|
130 | [Symbol.iterator]() {
|
131 | return this;
|
132 | }
|
133 | }
|
134 | exports.TokenPaginator = TokenPaginator;
|
135 | class ResourceClient {
|
136 | applyModifiers(req, modifiers) {
|
137 | if (!modifiers) {
|
138 | return;
|
139 | }
|
140 | if (modifiers.fields) {
|
141 | req.query({ fields: modifiers.fields });
|
142 | }
|
143 | }
|
144 | applyAuthentication(req, token) {
|
145 | req.set('Authorization', `Bearer ${token}`);
|
146 | }
|
147 | }
|
148 | exports.ResourceClient = ResourceClient;
|
149 | function transformAPIResponse(r) {
|
150 | if (r.status === 204) {
|
151 | r.body = { meta: { status: 204, version: '', request_id: '' } };
|
152 | }
|
153 | if (r.status !== 204 && r.type !== exports.CONTENT_TYPE_JSON) {
|
154 | throw exports.ERROR_UNKNOWN_CONTENT_TYPE;
|
155 | }
|
156 | const j = r.body;
|
157 | if (!j.meta) {
|
158 | throw exports.ERROR_UNKNOWN_RESPONSE_FORMAT;
|
159 | }
|
160 | return j;
|
161 | }
|
162 | exports.transformAPIResponse = transformAPIResponse;
|
163 | function createFatalAPIFormat(req, res) {
|
164 | return new errors_1.FatalException('API request was successful, but the response format was unrecognized.\n' +
|
165 | formatAPIResponse(req, res));
|
166 | }
|
167 | exports.createFatalAPIFormat = createFatalAPIFormat;
|
168 | function formatSuperAgentError(e) {
|
169 | const res = e.response;
|
170 | const req = res.request;
|
171 | const statusCode = e.response.status;
|
172 | let f = '';
|
173 | try {
|
174 | const r = transformAPIResponse(res);
|
175 | f += formatAPIResponse(req, r);
|
176 | }
|
177 | catch (e) {
|
178 | f += (`HTTP Error ${statusCode}: ${req.method.toUpperCase()} ${req.url}\n` +
|
179 | '\n' + (res.text ? res.text.substring(0, FORMAT_ERROR_BODY_MAX_LENGTH) : '<no buffered body>'));
|
180 | if (res.text && res.text.length > FORMAT_ERROR_BODY_MAX_LENGTH) {
|
181 | f += ` ...\n\n[ truncated ${res.text.length - FORMAT_ERROR_BODY_MAX_LENGTH} characters ]`;
|
182 | }
|
183 | }
|
184 | return color_1.failure(color_1.strong(f));
|
185 | }
|
186 | exports.formatSuperAgentError = formatSuperAgentError;
|
187 | function formatAPIResponse(req, r) {
|
188 | return formatResponseError(req, r.meta.status, guards_1.isAPIResponseSuccess(r) ? r.data : r.error);
|
189 | }
|
190 | function formatResponseError(req, status, body) {
|
191 | return color_1.failure(`Request: ${req.method} ${req.url}\n` +
|
192 | (status ? `Response: ${status}\n` : '') +
|
193 | (body ? `Body: \n${util.inspect(body, { colors: chalk.level > 0 })}` : ''));
|
194 | }
|
195 | exports.formatResponseError = formatResponseError;
|