UNPKG

5.45 kBJavaScriptView Raw
1import _ from 'lodash';
2import Debug from 'debug';
3import { IN_BROWSER } from './constants';
4import { version } from '../package.json';
5import {
6 CraftAiBadRequestError,
7 CraftAiCredentialsError,
8 CraftAiInternalError,
9 CraftAiLongRequestTimeOutError,
10 CraftAiNetworkError,
11 CraftAiUnknownError
12} from './errors';
13
14const fetch = !IN_BROWSER && typeof fetch === 'undefined'
15 ? require('node-fetch')
16 : window.fetch;
17
18const debug = Debug('craft-ai:client');
19
20const USER_AGENT = `craft-ai-client-js/${version} [${IN_BROWSER ? navigator.userAgent : `Node.js ${process.version}`}]`;
21
22debug(`Client user agent set to '${USER_AGENT}'`);
23
24function parseBody(req, resBody) {
25 let resBodyUtf8;
26 try {
27 resBodyUtf8 = resBody.toString('utf-8');
28 }
29 catch (err) {
30 debug(`Invalid response format from ${req.method} ${req.path}: ${resBody}`, err);
31 throw new CraftAiInternalError(
32 'Internal Error, the craft ai server responded in an invalid format.', {
33 request: req
34 }
35 );
36 }
37 let resBodyJson;
38 try {
39 if (resBodyUtf8.length > 0) {
40 resBodyJson = JSON.parse(resBodyUtf8);
41 }
42 else {
43 resBodyJson = {};
44 }
45 }
46 catch (err) {
47 debug(`Invalid json response from ${req.method} ${req.path}: ${resBody}`, err);
48 throw new CraftAiInternalError(
49 'Internal Error, the craft ai server responded an invalid json document.', {
50 more: resBodyUtf8,
51 request: req
52 }
53 );
54 }
55 return resBodyJson;
56}
57
58function parseBulk(req, res, resBody) {
59 if (_.isArray(JSON.parse(resBody))) {
60 return { body: parseBody(req, resBody) };
61 }
62 else {
63 throw new CraftAiBadRequestError({
64 message: parseBody(req, resBody).message,
65 request: req
66 });
67 }
68}
69
70function parseResponse(req, res, resBody) {
71 switch (res.status) {
72 case 200:
73 case 201:
74 case 204:
75 return {
76 body: parseBody(req, resBody),
77 nextPageUrl: res.headers.get('x-craft-ai-next-page-url')
78 };
79 case 202:
80 throw new CraftAiLongRequestTimeOutError({
81 message: parseBody(req, resBody).message,
82 request: req
83 });
84 case 207:
85 return parseBulk(req, res, resBody);
86 case 401:
87 case 403:
88 throw new CraftAiCredentialsError({
89 message: parseBody(req, resBody).message,
90 request: req
91 });
92 case 400:
93 case 404:
94 return parseBulk(req, res, resBody);
95 case 413:
96 throw new CraftAiBadRequestError({
97 message: 'Given payload is too large',
98 request: req
99 });
100 case 500:
101 throw new CraftAiInternalError(parseBody(req, resBody).message, {
102 request: req
103 });
104 case 504:
105 throw new CraftAiInternalError({
106 message: 'Response has timed out',
107 request: req,
108 status: res.status
109 });
110 default:
111 throw new CraftAiUnknownError({
112 more: parseBody(req, resBody).message,
113 request: req,
114 status: res.status
115 });
116 }
117}
118
119function createHttpAgent(proxy = undefined) {
120 if (IN_BROWSER) {
121 return undefined;
122 }
123 else if (proxy) {
124 const HttpProxyAgent = require('http-proxy-agent');
125
126 return new HttpProxyAgent(proxy);
127 }
128 else {
129 const http = require('http');
130
131 return new http.Agent({
132 keepAlive: true
133 });
134 }
135}
136
137function createHttpsAgent(proxy = undefined) {
138 if (IN_BROWSER) {
139 return undefined;
140 }
141 else if (proxy) {
142 const HttpsProxyAgent = require('https-proxy-agent');
143
144 return new HttpsProxyAgent(proxy);
145 }
146 else {
147 const https = require('https');
148
149 return new https.Agent({
150 keepAlive: true
151 });
152 }
153}
154
155export default function createRequest(cfg) {
156 const defaultHeaders = {
157 'Authorization': `Bearer ${cfg.token}`,
158 'Content-Type': 'application/json; charset=utf-8',
159 'Accept': 'application/json'
160 };
161
162 if (!IN_BROWSER) {
163 // Don't set the user agent in browsers it can cause CORS issues
164 // e.g. Safari v10.1.2 (12603.3.8)
165 defaultHeaders['User-Agent'] = USER_AGENT;
166 }
167
168 const baseUrl = `${cfg.url}/api/v1/${cfg.owner}/${cfg.project}`;
169
170 const agent = baseUrl.slice(0, 5) === 'https' ? createHttpsAgent(cfg.proxy) : createHttpAgent(cfg.proxy);
171
172 return (req) => {
173 req = _.defaults(req || {}, {
174 agent,
175 method: 'GET',
176 path: '',
177 body: undefined,
178 query: {},
179 headers: {}
180 });
181
182 req.url = req.url || `${baseUrl}${req.path}`;
183
184 const queryStr = _(req.query)
185 .map((value, key) => ([key, value]))
186 .filter(([key, value]) => !_.isUndefined(value))
187 .map((keyVal) => keyVal.join('='))
188 .join('&');
189
190 if (queryStr.length > 0) {
191 req.url += `?${queryStr}`;
192 }
193 req.headers = _.defaults(req.headers, defaultHeaders);
194
195 req.body = req.body && JSON.stringify(req.body);
196
197 return fetch(req.url, req)
198 .catch((err) => {
199 debug(`Network error while executing ${req.method} ${req.path}`, err);
200 return Promise.reject(new CraftAiNetworkError({
201 more: err.message
202 }));
203 })
204 .then((res) => res.text()
205 .catch((err) => {
206 debug(`Invalid response from ${req.method} ${req.path}`, err);
207
208 throw new CraftAiInternalError('Internal Error, the craft ai server responded an invalid response, see err.more for details.', {
209 request: req,
210 more: err.message
211 });
212 })
213 .then((resBody) => parseResponse(req, res, resBody))
214 );
215 };
216}