UNPKG

32.2 kBPlain TextView Raw
1// tslint:disable
2import fetch from 'node-fetch';
3import { RequestInit, Response } from 'node-fetch';
4import AbortController from 'abort-controller';
5import { createReturnTypeValidator, ClassValidator, ValidationError } from './common';
6import {
7 schema,
8 InternalServerError,
9 UnauthorizedError,
10 NotFoundError,
11 ExistsError,
12 Session,
13 ServerOnlyContext,
14 SubDomain,
15 CNameDomain,
16 Environment,
17 Application,
18 Template,
19 ExternalAccount,
20 User,
21 InvocationDetails,
22 LogRecord,
23 LogReadOpts,
24 LogsResponse,
25 CreateTicketResponse,
26 TryTemplateResponse,
27 Lycan,
28 FunctionRunnerSize,
29 Domain,
30} from './interfaces';
31
32export interface Options extends Pick<RequestInit, 'agent' | 'redirect' | 'follow' | 'compress'> {
33 fetchImplementation?: typeof fetch;
34 timeoutMs?: number;
35 headers?: Record<string, string>;
36}
37
38export class RequestError extends Error {
39 public readonly name = 'RequestError';
40 constructor(
41 message: string,
42 /**
43 * The original error causing this request to fail
44 * Inherits Error in case of network or parse errors
45 * In case of an invalid HTTP response it will contain an object with the body/trimmed text of the response
46 */
47 public readonly cause: any,
48 public readonly method: string,
49 public readonly options: any
50 ) {
51 super(message);
52 }
53}
54
55export class TimeoutError extends Error {
56 public readonly name = 'TimeoutError';
57 constructor(message: string, public readonly method: string, public readonly options: any) {
58 super(message);
59 }
60}
61
62export {
63 ValidationError,
64};
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82export interface Lycan {
83 createTicket(): Promise<CreateTicketResponse>;
84 claimTicket(ticket: string): Promise<string>;
85 listTemplates(): Promise<Array<Template>>;
86 tryTemplate(id: string): Promise<TryTemplateResponse>;
87 whoami(): Promise<User>;
88 listApps(): Promise<Array<Application>>;
89 deployInitial(env: string, name: string, digest: string, envVars: Array<[string, string]>): Promise<Application>;
90 deploy(appId: string, env: string, digest: string, envVars: Array<[string, string]>): Promise<Application>;
91 claimApp(token: string): Promise<Application>;
92 getLogs(appId: string, env: string, opts: LogReadOpts): Promise<LogsResponse>;
93 destroyApp(appId: string): Promise<void>;
94}
95
96export class LycanClient {
97 public static readonly methods = [
98 'createTicket',
99 'claimTicket',
100 'listTemplates',
101 'tryTemplate',
102 'whoami',
103 'listApps',
104 'deployInitial',
105 'deploy',
106 'claimApp',
107 'getLogs',
108 'destroyApp',
109 ];
110 public static readonly validators: ClassValidator = createReturnTypeValidator(schema, 'Lycan');
111
112 protected readonly props = schema.definitions.Lycan.properties;
113
114 public readonly validators: ClassValidator; // We don't have class name in method scope because mustache sux
115
116 public constructor(public readonly serverUrl: string, protected readonly options: Options = {}) {
117 this.validators = LycanClient.validators;
118 }
119
120 public async createTicket(options?: Options): Promise<CreateTicketResponse> {
121 const body = {
122 };
123
124 const mergedOptions = {
125 serverUrl: this.serverUrl,
126 ...this.options,
127 ...options,
128 };
129
130 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
131
132 const fetchImpl = fetchImplementation || fetch;
133
134 let timeout: NodeJS.Timeout | undefined;
135 if (timeoutMs) {
136 const controller = new AbortController();
137 timeout = setTimeout(() => controller.abort(), timeoutMs);
138 (fetchOptions as any).signal = controller.signal;
139 }
140
141 let response: Response;
142 let responseBody: any;
143 let responseText: string | undefined;
144 let isJSON: boolean;
145 try {
146 response = await fetchImpl(`${serverUrl}/createTicket`, {
147 ...fetchOptions,
148 headers: {
149 ...headers,
150 'Content-Type': 'application/json',
151 },
152 body: JSON.stringify(body),
153 method: 'POST',
154 });
155 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
156 if (isJSON) {
157 responseBody = await response.json();
158 } else {
159 responseText = await response.text();
160 }
161 } catch (err) {
162 if (err.message === 'The user aborted a request.') {
163 timeout = undefined;
164 throw new TimeoutError('Request aborted due to timeout', 'createTicket', mergedOptions);
165 }
166 throw new RequestError(err.message, err, 'createTicket', mergedOptions);
167 } finally {
168 if (timeout) clearTimeout(timeout);
169 }
170 if (response.status >= 200 && response.status < 300) {
171 const validator = this.validators.createTicket;
172 const wrapped = { returns: responseBody }; // wrapped for coersion
173 if (!validator(wrapped)) {
174 throw new ValidationError('Failed to validate response', validator.errors);
175 }
176 return wrapped.returns as CreateTicketResponse;
177 } else if (!isJSON) {
178 // fall through to throw
179 } else if (response.status === 400) {
180 if (responseBody.name === 'ValidationError') {
181 throw new ValidationError(responseBody.message, responseBody.errors);
182 }
183 } else if (response.status === 500) {
184 throw new InternalServerError(responseBody.message);
185 }
186 throw new RequestError(`${response.status} - ${response.statusText}`,
187 { responseText: responseText && responseText.slice(0, 256), responseBody },
188 'createTicket',
189 mergedOptions);
190 }
191
192 public async claimTicket(ticket: string, options?: Options): Promise<string> {
193 const body = {
194 ticket,
195 };
196
197 const mergedOptions = {
198 serverUrl: this.serverUrl,
199 ...this.options,
200 ...options,
201 };
202
203 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
204
205 const fetchImpl = fetchImplementation || fetch;
206
207 let timeout: NodeJS.Timeout | undefined;
208 if (timeoutMs) {
209 const controller = new AbortController();
210 timeout = setTimeout(() => controller.abort(), timeoutMs);
211 (fetchOptions as any).signal = controller.signal;
212 }
213
214 let response: Response;
215 let responseBody: any;
216 let responseText: string | undefined;
217 let isJSON: boolean;
218 try {
219 response = await fetchImpl(`${serverUrl}/claimTicket`, {
220 ...fetchOptions,
221 headers: {
222 ...headers,
223 'Content-Type': 'application/json',
224 },
225 body: JSON.stringify(body),
226 method: 'POST',
227 });
228 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
229 if (isJSON) {
230 responseBody = await response.json();
231 } else {
232 responseText = await response.text();
233 }
234 } catch (err) {
235 if (err.message === 'The user aborted a request.') {
236 timeout = undefined;
237 throw new TimeoutError('Request aborted due to timeout', 'claimTicket', mergedOptions);
238 }
239 throw new RequestError(err.message, err, 'claimTicket', mergedOptions);
240 } finally {
241 if (timeout) clearTimeout(timeout);
242 }
243 if (response.status >= 200 && response.status < 300) {
244 const validator = this.validators.claimTicket;
245 const wrapped = { returns: responseBody }; // wrapped for coersion
246 if (!validator(wrapped)) {
247 throw new ValidationError('Failed to validate response', validator.errors);
248 }
249 return wrapped.returns as string;
250 } else if (!isJSON) {
251 // fall through to throw
252 } else if (response.status === 400) {
253 if (responseBody.name === 'ValidationError') {
254 throw new ValidationError(responseBody.message, responseBody.errors);
255 }
256 } else if (response.status === 500) {
257 if (responseBody.name === 'NotFoundError') {
258 throw new NotFoundError(responseBody.message);
259 }
260 throw new InternalServerError(responseBody.message);
261 }
262 throw new RequestError(`${response.status} - ${response.statusText}`,
263 { responseText: responseText && responseText.slice(0, 256), responseBody },
264 'claimTicket',
265 mergedOptions);
266 }
267
268 public async listTemplates(options?: Options): Promise<Array<Template>> {
269 const body = {
270 };
271
272 const mergedOptions = {
273 serverUrl: this.serverUrl,
274 ...this.options,
275 ...options,
276 };
277
278 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
279
280 const fetchImpl = fetchImplementation || fetch;
281
282 let timeout: NodeJS.Timeout | undefined;
283 if (timeoutMs) {
284 const controller = new AbortController();
285 timeout = setTimeout(() => controller.abort(), timeoutMs);
286 (fetchOptions as any).signal = controller.signal;
287 }
288
289 let response: Response;
290 let responseBody: any;
291 let responseText: string | undefined;
292 let isJSON: boolean;
293 try {
294 response = await fetchImpl(`${serverUrl}/listTemplates`, {
295 ...fetchOptions,
296 headers: {
297 ...headers,
298 'Content-Type': 'application/json',
299 },
300 body: JSON.stringify(body),
301 method: 'POST',
302 });
303 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
304 if (isJSON) {
305 responseBody = await response.json();
306 } else {
307 responseText = await response.text();
308 }
309 } catch (err) {
310 if (err.message === 'The user aborted a request.') {
311 timeout = undefined;
312 throw new TimeoutError('Request aborted due to timeout', 'listTemplates', mergedOptions);
313 }
314 throw new RequestError(err.message, err, 'listTemplates', mergedOptions);
315 } finally {
316 if (timeout) clearTimeout(timeout);
317 }
318 if (response.status >= 200 && response.status < 300) {
319 const validator = this.validators.listTemplates;
320 const wrapped = { returns: responseBody }; // wrapped for coersion
321 if (!validator(wrapped)) {
322 throw new ValidationError('Failed to validate response', validator.errors);
323 }
324 return wrapped.returns as Array<Template>;
325 } else if (!isJSON) {
326 // fall through to throw
327 } else if (response.status === 400) {
328 if (responseBody.name === 'ValidationError') {
329 throw new ValidationError(responseBody.message, responseBody.errors);
330 }
331 } else if (response.status === 500) {
332 throw new InternalServerError(responseBody.message);
333 }
334 throw new RequestError(`${response.status} - ${response.statusText}`,
335 { responseText: responseText && responseText.slice(0, 256), responseBody },
336 'listTemplates',
337 mergedOptions);
338 }
339
340 public async tryTemplate(id: string, options?: Options): Promise<TryTemplateResponse> {
341 const body = {
342 id,
343 };
344
345 const mergedOptions = {
346 serverUrl: this.serverUrl,
347 ...this.options,
348 ...options,
349 };
350
351 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
352
353 const fetchImpl = fetchImplementation || fetch;
354
355 let timeout: NodeJS.Timeout | undefined;
356 if (timeoutMs) {
357 const controller = new AbortController();
358 timeout = setTimeout(() => controller.abort(), timeoutMs);
359 (fetchOptions as any).signal = controller.signal;
360 }
361
362 let response: Response;
363 let responseBody: any;
364 let responseText: string | undefined;
365 let isJSON: boolean;
366 try {
367 response = await fetchImpl(`${serverUrl}/tryTemplate`, {
368 ...fetchOptions,
369 headers: {
370 ...headers,
371 'Content-Type': 'application/json',
372 },
373 body: JSON.stringify(body),
374 method: 'POST',
375 });
376 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
377 if (isJSON) {
378 responseBody = await response.json();
379 } else {
380 responseText = await response.text();
381 }
382 } catch (err) {
383 if (err.message === 'The user aborted a request.') {
384 timeout = undefined;
385 throw new TimeoutError('Request aborted due to timeout', 'tryTemplate', mergedOptions);
386 }
387 throw new RequestError(err.message, err, 'tryTemplate', mergedOptions);
388 } finally {
389 if (timeout) clearTimeout(timeout);
390 }
391 if (response.status >= 200 && response.status < 300) {
392 const validator = this.validators.tryTemplate;
393 const wrapped = { returns: responseBody }; // wrapped for coersion
394 if (!validator(wrapped)) {
395 throw new ValidationError('Failed to validate response', validator.errors);
396 }
397 return wrapped.returns as TryTemplateResponse;
398 } else if (!isJSON) {
399 // fall through to throw
400 } else if (response.status === 400) {
401 if (responseBody.name === 'ValidationError') {
402 throw new ValidationError(responseBody.message, responseBody.errors);
403 }
404 } else if (response.status === 500) {
405 if (responseBody.name === 'NotFoundError') {
406 throw new NotFoundError(responseBody.message);
407 }
408 throw new InternalServerError(responseBody.message);
409 }
410 throw new RequestError(`${response.status} - ${response.statusText}`,
411 { responseText: responseText && responseText.slice(0, 256), responseBody },
412 'tryTemplate',
413 mergedOptions);
414 }
415
416 public async whoami(options?: Options): Promise<User> {
417 const body = {
418 };
419
420 const mergedOptions = {
421 serverUrl: this.serverUrl,
422 ...this.options,
423 ...options,
424 };
425
426 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
427
428 const fetchImpl = fetchImplementation || fetch;
429
430 let timeout: NodeJS.Timeout | undefined;
431 if (timeoutMs) {
432 const controller = new AbortController();
433 timeout = setTimeout(() => controller.abort(), timeoutMs);
434 (fetchOptions as any).signal = controller.signal;
435 }
436
437 let response: Response;
438 let responseBody: any;
439 let responseText: string | undefined;
440 let isJSON: boolean;
441 try {
442 response = await fetchImpl(`${serverUrl}/whoami`, {
443 ...fetchOptions,
444 headers: {
445 ...headers,
446 'Content-Type': 'application/json',
447 },
448 body: JSON.stringify(body),
449 method: 'POST',
450 });
451 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
452 if (isJSON) {
453 responseBody = await response.json();
454 } else {
455 responseText = await response.text();
456 }
457 } catch (err) {
458 if (err.message === 'The user aborted a request.') {
459 timeout = undefined;
460 throw new TimeoutError('Request aborted due to timeout', 'whoami', mergedOptions);
461 }
462 throw new RequestError(err.message, err, 'whoami', mergedOptions);
463 } finally {
464 if (timeout) clearTimeout(timeout);
465 }
466 if (response.status >= 200 && response.status < 300) {
467 const validator = this.validators.whoami;
468 const wrapped = { returns: responseBody }; // wrapped for coersion
469 if (!validator(wrapped)) {
470 throw new ValidationError('Failed to validate response', validator.errors);
471 }
472 return wrapped.returns as User;
473 } else if (!isJSON) {
474 // fall through to throw
475 } else if (response.status === 400) {
476 if (responseBody.name === 'ValidationError') {
477 throw new ValidationError(responseBody.message, responseBody.errors);
478 }
479 } else if (response.status === 500) {
480 if (responseBody.name === 'UnauthorizedError') {
481 throw new UnauthorizedError(responseBody.message);
482 }
483 throw new InternalServerError(responseBody.message);
484 }
485 throw new RequestError(`${response.status} - ${response.statusText}`,
486 { responseText: responseText && responseText.slice(0, 256), responseBody },
487 'whoami',
488 mergedOptions);
489 }
490
491 public async listApps(options?: Options): Promise<Array<Application>> {
492 const body = {
493 };
494
495 const mergedOptions = {
496 serverUrl: this.serverUrl,
497 ...this.options,
498 ...options,
499 };
500
501 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
502
503 const fetchImpl = fetchImplementation || fetch;
504
505 let timeout: NodeJS.Timeout | undefined;
506 if (timeoutMs) {
507 const controller = new AbortController();
508 timeout = setTimeout(() => controller.abort(), timeoutMs);
509 (fetchOptions as any).signal = controller.signal;
510 }
511
512 let response: Response;
513 let responseBody: any;
514 let responseText: string | undefined;
515 let isJSON: boolean;
516 try {
517 response = await fetchImpl(`${serverUrl}/listApps`, {
518 ...fetchOptions,
519 headers: {
520 ...headers,
521 'Content-Type': 'application/json',
522 },
523 body: JSON.stringify(body),
524 method: 'POST',
525 });
526 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
527 if (isJSON) {
528 responseBody = await response.json();
529 } else {
530 responseText = await response.text();
531 }
532 } catch (err) {
533 if (err.message === 'The user aborted a request.') {
534 timeout = undefined;
535 throw new TimeoutError('Request aborted due to timeout', 'listApps', mergedOptions);
536 }
537 throw new RequestError(err.message, err, 'listApps', mergedOptions);
538 } finally {
539 if (timeout) clearTimeout(timeout);
540 }
541 if (response.status >= 200 && response.status < 300) {
542 const validator = this.validators.listApps;
543 const wrapped = { returns: responseBody }; // wrapped for coersion
544 if (!validator(wrapped)) {
545 throw new ValidationError('Failed to validate response', validator.errors);
546 }
547 return wrapped.returns as Array<Application>;
548 } else if (!isJSON) {
549 // fall through to throw
550 } else if (response.status === 400) {
551 if (responseBody.name === 'ValidationError') {
552 throw new ValidationError(responseBody.message, responseBody.errors);
553 }
554 } else if (response.status === 500) {
555 if (responseBody.name === 'UnauthorizedError') {
556 throw new UnauthorizedError(responseBody.message);
557 }
558 throw new InternalServerError(responseBody.message);
559 }
560 throw new RequestError(`${response.status} - ${response.statusText}`,
561 { responseText: responseText && responseText.slice(0, 256), responseBody },
562 'listApps',
563 mergedOptions);
564 }
565
566 public async deployInitial(env: string, name: string, digest: string, envVars: Array<[string, string]>, options?: Options): Promise<Application> {
567 const body = {
568 env,
569 name,
570 digest,
571 envVars,
572 };
573
574 const mergedOptions = {
575 serverUrl: this.serverUrl,
576 ...this.options,
577 ...options,
578 };
579
580 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
581
582 const fetchImpl = fetchImplementation || fetch;
583
584 let timeout: NodeJS.Timeout | undefined;
585 if (timeoutMs) {
586 const controller = new AbortController();
587 timeout = setTimeout(() => controller.abort(), timeoutMs);
588 (fetchOptions as any).signal = controller.signal;
589 }
590
591 let response: Response;
592 let responseBody: any;
593 let responseText: string | undefined;
594 let isJSON: boolean;
595 try {
596 response = await fetchImpl(`${serverUrl}/deployInitial`, {
597 ...fetchOptions,
598 headers: {
599 ...headers,
600 'Content-Type': 'application/json',
601 },
602 body: JSON.stringify(body),
603 method: 'POST',
604 });
605 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
606 if (isJSON) {
607 responseBody = await response.json();
608 } else {
609 responseText = await response.text();
610 }
611 } catch (err) {
612 if (err.message === 'The user aborted a request.') {
613 timeout = undefined;
614 throw new TimeoutError('Request aborted due to timeout', 'deployInitial', mergedOptions);
615 }
616 throw new RequestError(err.message, err, 'deployInitial', mergedOptions);
617 } finally {
618 if (timeout) clearTimeout(timeout);
619 }
620 if (response.status >= 200 && response.status < 300) {
621 const validator = this.validators.deployInitial;
622 const wrapped = { returns: responseBody }; // wrapped for coersion
623 if (!validator(wrapped)) {
624 throw new ValidationError('Failed to validate response', validator.errors);
625 }
626 return wrapped.returns as Application;
627 } else if (!isJSON) {
628 // fall through to throw
629 } else if (response.status === 400) {
630 if (responseBody.name === 'ValidationError') {
631 throw new ValidationError(responseBody.message, responseBody.errors);
632 }
633 } else if (response.status === 500) {
634 if (responseBody.name === 'UnauthorizedError') {
635 throw new UnauthorizedError(responseBody.message);
636 }
637 if (responseBody.name === 'ExistsError') {
638 throw new ExistsError(responseBody.message);
639 }
640 throw new InternalServerError(responseBody.message);
641 }
642 throw new RequestError(`${response.status} - ${response.statusText}`,
643 { responseText: responseText && responseText.slice(0, 256), responseBody },
644 'deployInitial',
645 mergedOptions);
646 }
647
648 public async deploy(appId: string, env: string, digest: string, envVars: Array<[string, string]>, options?: Options): Promise<Application> {
649 const body = {
650 appId,
651 env,
652 digest,
653 envVars,
654 };
655
656 const mergedOptions = {
657 serverUrl: this.serverUrl,
658 ...this.options,
659 ...options,
660 };
661
662 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
663
664 const fetchImpl = fetchImplementation || fetch;
665
666 let timeout: NodeJS.Timeout | undefined;
667 if (timeoutMs) {
668 const controller = new AbortController();
669 timeout = setTimeout(() => controller.abort(), timeoutMs);
670 (fetchOptions as any).signal = controller.signal;
671 }
672
673 let response: Response;
674 let responseBody: any;
675 let responseText: string | undefined;
676 let isJSON: boolean;
677 try {
678 response = await fetchImpl(`${serverUrl}/deploy`, {
679 ...fetchOptions,
680 headers: {
681 ...headers,
682 'Content-Type': 'application/json',
683 },
684 body: JSON.stringify(body),
685 method: 'POST',
686 });
687 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
688 if (isJSON) {
689 responseBody = await response.json();
690 } else {
691 responseText = await response.text();
692 }
693 } catch (err) {
694 if (err.message === 'The user aborted a request.') {
695 timeout = undefined;
696 throw new TimeoutError('Request aborted due to timeout', 'deploy', mergedOptions);
697 }
698 throw new RequestError(err.message, err, 'deploy', mergedOptions);
699 } finally {
700 if (timeout) clearTimeout(timeout);
701 }
702 if (response.status >= 200 && response.status < 300) {
703 const validator = this.validators.deploy;
704 const wrapped = { returns: responseBody }; // wrapped for coersion
705 if (!validator(wrapped)) {
706 throw new ValidationError('Failed to validate response', validator.errors);
707 }
708 return wrapped.returns as Application;
709 } else if (!isJSON) {
710 // fall through to throw
711 } else if (response.status === 400) {
712 if (responseBody.name === 'ValidationError') {
713 throw new ValidationError(responseBody.message, responseBody.errors);
714 }
715 } else if (response.status === 500) {
716 if (responseBody.name === 'UnauthorizedError') {
717 throw new UnauthorizedError(responseBody.message);
718 }
719 if (responseBody.name === 'NotFoundError') {
720 throw new NotFoundError(responseBody.message);
721 }
722 throw new InternalServerError(responseBody.message);
723 }
724 throw new RequestError(`${response.status} - ${response.statusText}`,
725 { responseText: responseText && responseText.slice(0, 256), responseBody },
726 'deploy',
727 mergedOptions);
728 }
729
730 public async claimApp(token: string, options?: Options): Promise<Application> {
731 const body = {
732 token,
733 };
734
735 const mergedOptions = {
736 serverUrl: this.serverUrl,
737 ...this.options,
738 ...options,
739 };
740
741 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
742
743 const fetchImpl = fetchImplementation || fetch;
744
745 let timeout: NodeJS.Timeout | undefined;
746 if (timeoutMs) {
747 const controller = new AbortController();
748 timeout = setTimeout(() => controller.abort(), timeoutMs);
749 (fetchOptions as any).signal = controller.signal;
750 }
751
752 let response: Response;
753 let responseBody: any;
754 let responseText: string | undefined;
755 let isJSON: boolean;
756 try {
757 response = await fetchImpl(`${serverUrl}/claimApp`, {
758 ...fetchOptions,
759 headers: {
760 ...headers,
761 'Content-Type': 'application/json',
762 },
763 body: JSON.stringify(body),
764 method: 'POST',
765 });
766 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
767 if (isJSON) {
768 responseBody = await response.json();
769 } else {
770 responseText = await response.text();
771 }
772 } catch (err) {
773 if (err.message === 'The user aborted a request.') {
774 timeout = undefined;
775 throw new TimeoutError('Request aborted due to timeout', 'claimApp', mergedOptions);
776 }
777 throw new RequestError(err.message, err, 'claimApp', mergedOptions);
778 } finally {
779 if (timeout) clearTimeout(timeout);
780 }
781 if (response.status >= 200 && response.status < 300) {
782 const validator = this.validators.claimApp;
783 const wrapped = { returns: responseBody }; // wrapped for coersion
784 if (!validator(wrapped)) {
785 throw new ValidationError('Failed to validate response', validator.errors);
786 }
787 return wrapped.returns as Application;
788 } else if (!isJSON) {
789 // fall through to throw
790 } else if (response.status === 400) {
791 if (responseBody.name === 'ValidationError') {
792 throw new ValidationError(responseBody.message, responseBody.errors);
793 }
794 } else if (response.status === 500) {
795 if (responseBody.name === 'UnauthorizedError') {
796 throw new UnauthorizedError(responseBody.message);
797 }
798 if (responseBody.name === 'NotFoundError') {
799 throw new NotFoundError(responseBody.message);
800 }
801 throw new InternalServerError(responseBody.message);
802 }
803 throw new RequestError(`${response.status} - ${response.statusText}`,
804 { responseText: responseText && responseText.slice(0, 256), responseBody },
805 'claimApp',
806 mergedOptions);
807 }
808
809 public async getLogs(appId: string, env: string, opts: LogReadOpts, options?: Options): Promise<LogsResponse> {
810 const body = {
811 appId,
812 env,
813 opts,
814 };
815
816 const mergedOptions = {
817 serverUrl: this.serverUrl,
818 ...this.options,
819 ...options,
820 };
821
822 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
823
824 const fetchImpl = fetchImplementation || fetch;
825
826 let timeout: NodeJS.Timeout | undefined;
827 if (timeoutMs) {
828 const controller = new AbortController();
829 timeout = setTimeout(() => controller.abort(), timeoutMs);
830 (fetchOptions as any).signal = controller.signal;
831 }
832
833 let response: Response;
834 let responseBody: any;
835 let responseText: string | undefined;
836 let isJSON: boolean;
837 try {
838 response = await fetchImpl(`${serverUrl}/getLogs`, {
839 ...fetchOptions,
840 headers: {
841 ...headers,
842 'Content-Type': 'application/json',
843 },
844 body: JSON.stringify(body),
845 method: 'POST',
846 });
847 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
848 if (isJSON) {
849 responseBody = await response.json();
850 } else {
851 responseText = await response.text();
852 }
853 } catch (err) {
854 if (err.message === 'The user aborted a request.') {
855 timeout = undefined;
856 throw new TimeoutError('Request aborted due to timeout', 'getLogs', mergedOptions);
857 }
858 throw new RequestError(err.message, err, 'getLogs', mergedOptions);
859 } finally {
860 if (timeout) clearTimeout(timeout);
861 }
862 if (response.status >= 200 && response.status < 300) {
863 const validator = this.validators.getLogs;
864 const wrapped = { returns: responseBody }; // wrapped for coersion
865 if (!validator(wrapped)) {
866 throw new ValidationError('Failed to validate response', validator.errors);
867 }
868 return wrapped.returns as LogsResponse;
869 } else if (!isJSON) {
870 // fall through to throw
871 } else if (response.status === 400) {
872 if (responseBody.name === 'ValidationError') {
873 throw new ValidationError(responseBody.message, responseBody.errors);
874 }
875 } else if (response.status === 500) {
876 if (responseBody.name === 'UnauthorizedError') {
877 throw new UnauthorizedError(responseBody.message);
878 }
879 if (responseBody.name === 'NotFoundError') {
880 throw new NotFoundError(responseBody.message);
881 }
882 throw new InternalServerError(responseBody.message);
883 }
884 throw new RequestError(`${response.status} - ${response.statusText}`,
885 { responseText: responseText && responseText.slice(0, 256), responseBody },
886 'getLogs',
887 mergedOptions);
888 }
889
890 public async destroyApp(appId: string, options?: Options): Promise<void> {
891 const body = {
892 appId,
893 };
894
895 const mergedOptions = {
896 serverUrl: this.serverUrl,
897 ...this.options,
898 ...options,
899 };
900
901 const { fetchImplementation, timeoutMs, headers, serverUrl, ...fetchOptions } = mergedOptions;
902
903 const fetchImpl = fetchImplementation || fetch;
904
905 let timeout: NodeJS.Timeout | undefined;
906 if (timeoutMs) {
907 const controller = new AbortController();
908 timeout = setTimeout(() => controller.abort(), timeoutMs);
909 (fetchOptions as any).signal = controller.signal;
910 }
911
912 let response: Response;
913 let responseBody: any;
914 let responseText: string | undefined;
915 let isJSON: boolean;
916 try {
917 response = await fetchImpl(`${serverUrl}/destroyApp`, {
918 ...fetchOptions,
919 headers: {
920 ...headers,
921 'Content-Type': 'application/json',
922 },
923 body: JSON.stringify(body),
924 method: 'POST',
925 });
926 isJSON = (response.headers.get('content-type') || '').startsWith('application/json');
927 if (isJSON) {
928 responseBody = await response.json();
929 } else {
930 responseText = await response.text();
931 }
932 } catch (err) {
933 if (err.message === 'The user aborted a request.') {
934 timeout = undefined;
935 throw new TimeoutError('Request aborted due to timeout', 'destroyApp', mergedOptions);
936 }
937 throw new RequestError(err.message, err, 'destroyApp', mergedOptions);
938 } finally {
939 if (timeout) clearTimeout(timeout);
940 }
941 if (response.status >= 200 && response.status < 300) {
942 const validator = this.validators.destroyApp;
943 const wrapped = { returns: responseBody }; // wrapped for coersion
944 if (!validator(wrapped)) {
945 throw new ValidationError('Failed to validate response', validator.errors);
946 }
947 return wrapped.returns as void;
948 } else if (!isJSON) {
949 // fall through to throw
950 } else if (response.status === 400) {
951 if (responseBody.name === 'ValidationError') {
952 throw new ValidationError(responseBody.message, responseBody.errors);
953 }
954 } else if (response.status === 500) {
955 if (responseBody.name === 'UnauthorizedError') {
956 throw new UnauthorizedError(responseBody.message);
957 }
958 if (responseBody.name === 'NotFoundError') {
959 throw new NotFoundError(responseBody.message);
960 }
961 throw new InternalServerError(responseBody.message);
962 }
963 throw new RequestError(`${response.status} - ${response.statusText}`,
964 { responseText: responseText && responseText.slice(0, 256), responseBody },
965 'destroyApp',
966 mergedOptions);
967 }
968}
969