UNPKG

5.21 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 parseResponse(req, res, resBody) {
59 switch (res.status) {
60 case 200:
61 case 201:
62 case 204:
63 return {
64 body: parseBody(req, resBody),
65 nextPageUrl: res.headers.get('x-craft-ai-next-page-url')
66 };
67 case 202:
68 throw new CraftAiLongRequestTimeOutError({
69 message: parseBody(req, resBody).message,
70 request: req
71 });
72 case 401:
73 case 403:
74 throw new CraftAiCredentialsError({
75 message: parseBody(req, resBody).message,
76 request: req
77 });
78 case 400:
79 case 404:
80 throw new CraftAiBadRequestError({
81 message: parseBody(req, resBody).message,
82 request: req
83 });
84 case 413:
85 throw new CraftAiBadRequestError({
86 message: 'Given payload is too large',
87 request: req
88 });
89 case 500:
90 throw new CraftAiInternalError(parseBody(req, resBody).message, {
91 request: req
92 });
93 case 504:
94 throw new CraftAiInternalError({
95 message: 'Response has timed out',
96 request: req,
97 status: res.status
98 });
99 default:
100 throw new CraftAiUnknownError({
101 more: parseBody(req, resBody).message,
102 request: req,
103 status: res.status
104 });
105 }
106}
107
108function createHttpAgent(proxy = undefined) {
109 if (IN_BROWSER) {
110 return undefined;
111 }
112 else if (proxy) {
113 const HttpProxyAgent = require('http-proxy-agent');
114
115 return new HttpProxyAgent(proxy);
116 }
117 else {
118 const http = require('http');
119
120 return new http.Agent({
121 keepAlive: true
122 });
123 }
124}
125
126function createHttpsAgent(proxy = undefined) {
127 if (IN_BROWSER) {
128 return undefined;
129 }
130 else if (proxy) {
131 const HttpsProxyAgent = require('https-proxy-agent');
132
133 return new HttpsProxyAgent(proxy);
134 }
135 else {
136 const https = require('https');
137
138 return new https.Agent({
139 keepAlive: true
140 });
141 }
142}
143
144export default function createRequest(cfg) {
145 const defaultHeaders = {
146 'Authorization': `Bearer ${cfg.token}`,
147 'Content-Type': 'application/json; charset=utf-8',
148 'Accept': 'application/json'
149 };
150
151 if (!IN_BROWSER) {
152 // Don't set the user agent in browsers it can cause CORS issues
153 // e.g. Safari v10.1.2 (12603.3.8)
154 defaultHeaders['User-Agent'] = USER_AGENT;
155 }
156
157 const baseUrl = `${cfg.url}/api/v1/${cfg.owner}/${cfg.project}`;
158
159 const agent = baseUrl.slice(0, 5) === 'https' ? createHttpsAgent(cfg.proxy) : createHttpAgent(cfg.proxy);
160
161 return (req) => {
162 req = _.defaults(req || {}, {
163 agent,
164 method: 'GET',
165 path: '',
166 body: undefined,
167 query: {},
168 headers: {}
169 });
170
171 req.url = req.url || `${baseUrl}${req.path}`;
172
173 const queryStr = _(req.query)
174 .map((value, key) => ([key, value]))
175 .filter(([key, value]) => !_.isUndefined(value))
176 .map((keyVal) => keyVal.join('='))
177 .join('&');
178
179 if (queryStr.length > 0) {
180 req.url += `?${queryStr}`;
181 }
182 req.headers = _.defaults(req.headers, defaultHeaders);
183
184 req.body = req.body && JSON.stringify(req.body);
185
186 return fetch(req.url, req)
187 .catch((err) => {
188 debug(`Network error while executing ${req.method} ${req.path}`, err);
189 return Promise.reject(new CraftAiNetworkError({
190 more: err.message
191 }));
192 })
193 .then((res) => res.text()
194 .catch((err) => {
195 debug(`Invalid response from ${req.method} ${req.path}`, err);
196
197 throw new CraftAiInternalError('Internal Error, the craft ai server responded an invalid response, see err.more for details.', {
198 request: req,
199 more: err.message
200 });
201 })
202 .then((resBody) => parseResponse(req, res, resBody))
203 );
204 };
205}