@metacall/protocol 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/login.js +15 -13
- package/dist/protocol.d.ts +13 -10
- package/dist/protocol.js +193 -108
- package/dist/signup.js +15 -13
- package/package.json +3 -4
- package/src/index.ts +2 -2
- package/src/login.ts +20 -13
- package/src/protocol.ts +264 -158
- package/src/signup.ts +22 -13
- package/tsconfig.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/login.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const axios_1 = __importDefault(require("axios"));
|
|
7
3
|
const url_1 = require("url");
|
|
8
|
-
exports.default = (email, password, baseURL) => {
|
|
4
|
+
exports.default = async (email, password, baseURL) => {
|
|
9
5
|
const request = {
|
|
10
6
|
email,
|
|
11
7
|
password
|
|
12
8
|
};
|
|
13
|
-
if (!baseURL.includes('localhost'))
|
|
14
|
-
request['g-recaptcha-response'] = 'empty'; //TODO: Review the captcha
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
if (!baseURL.includes('localhost')) {
|
|
10
|
+
request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
|
|
11
|
+
}
|
|
12
|
+
const res = await fetch(baseURL + '/login', {
|
|
13
|
+
method: 'POST',
|
|
17
14
|
headers: {
|
|
18
15
|
Accept: 'application/json, text/plain, */*',
|
|
19
16
|
Host: new url_1.URL(baseURL).host,
|
|
20
|
-
Origin: baseURL
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.
|
|
17
|
+
Origin: baseURL,
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify(request)
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
throw new Error(res.statusText);
|
|
24
|
+
}
|
|
25
|
+
return res.text();
|
|
24
26
|
};
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { AxiosError } from 'axios';
|
|
2
1
|
import { Create, Deployment, LogType, MetaCallJSON } from './deployment';
|
|
3
2
|
import { Plans } from './plan';
|
|
3
|
+
export declare class ProtocolError extends Error {
|
|
4
|
+
status?: number;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
constructor(message: string, status?: number, data?: unknown);
|
|
7
|
+
}
|
|
4
8
|
/**
|
|
5
|
-
* Type guard for protocol-specific errors
|
|
9
|
+
* Type guard for protocol-specific errors.
|
|
6
10
|
* @param err - The unknown error to check.
|
|
7
11
|
* @returns True if the error is an ProtocolError, false otherwise.
|
|
8
12
|
*/
|
|
9
|
-
export declare const isProtocolError: (err: unknown) =>
|
|
10
|
-
export { AxiosError as ProtocolError };
|
|
13
|
+
export declare const isProtocolError: (err: unknown) => err is ProtocolError;
|
|
11
14
|
declare type SubscriptionMap = Record<string, number>;
|
|
12
15
|
export interface SubscriptionDeploy {
|
|
13
16
|
id: string;
|
|
@@ -19,7 +22,7 @@ export declare enum ResourceType {
|
|
|
19
22
|
Package = "Package",
|
|
20
23
|
Repository = "Repository"
|
|
21
24
|
}
|
|
22
|
-
export interface
|
|
25
|
+
export interface Resource {
|
|
23
26
|
id: string;
|
|
24
27
|
}
|
|
25
28
|
export interface Branches {
|
|
@@ -66,8 +69,8 @@ export interface API {
|
|
|
66
69
|
listSubscriptionsDeploys(): Promise<SubscriptionDeploy[]>;
|
|
67
70
|
inspect(): Promise<Deployment[]>;
|
|
68
71
|
inspectByName(suffix: string): Promise<Deployment>;
|
|
69
|
-
upload(name: string, blob: unknown, jsons?: MetaCallJSON[], runners?: string[]): Promise<
|
|
70
|
-
add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<
|
|
72
|
+
upload(name: string, blob: unknown, jsons?: MetaCallJSON[], runners?: string[]): Promise<Resource>;
|
|
73
|
+
add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<Resource>;
|
|
71
74
|
deploy(name: string, env: {
|
|
72
75
|
name: string;
|
|
73
76
|
value: string;
|
|
@@ -82,8 +85,8 @@ export interface API {
|
|
|
82
85
|
}
|
|
83
86
|
declare const _default: (token: string, baseURL: string) => API;
|
|
84
87
|
export default _default;
|
|
85
|
-
export declare const MaxRetries =
|
|
86
|
-
export declare const MaxRetryInterval =
|
|
88
|
+
export declare const MaxRetries = 100;
|
|
89
|
+
export declare const MaxRetryInterval = 5000;
|
|
87
90
|
export declare const MaxFuncLength = 64;
|
|
88
91
|
/**
|
|
89
92
|
* Executes an asynchronous function with automatic retry logic.
|
|
@@ -122,4 +125,4 @@ export declare const MaxFuncLength = 64;
|
|
|
122
125
|
* );
|
|
123
126
|
* ```
|
|
124
127
|
*/
|
|
125
|
-
export declare const waitFor: <T>(fn: () => Promise<T>, maxRetries?: number, interval?: number) => Promise<T>;
|
|
128
|
+
export declare const waitFor: <T>(fn: (cancel: (message: string) => void) => Promise<T>, maxRetries?: number, interval?: number) => Promise<T>;
|
package/dist/protocol.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this is just a client that implements all the rest API from the FaaS, so each function it contains is an endpoint in the FaaS for deploying and similar
|
|
3
|
+
This is just a client that implements all the rest API from the FaaS,
|
|
4
|
+
so each function it contains is an endpoint in the FaaS for deploying:
|
|
7
5
|
|
|
8
6
|
refresh: updates the auth token
|
|
9
7
|
validate: validates the auth token
|
|
@@ -18,41 +16,26 @@
|
|
|
18
16
|
branchList: get the branches of a repository
|
|
19
17
|
fileList: get files of a repository by branch
|
|
20
18
|
*/
|
|
21
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
22
|
-
if (k2 === undefined) k2 = k;
|
|
23
|
-
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
24
|
-
}) : (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
o[k2] = m[k];
|
|
27
|
-
}));
|
|
28
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
-
}) : function(o, v) {
|
|
31
|
-
o["default"] = v;
|
|
32
|
-
});
|
|
33
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod;
|
|
35
|
-
var result = {};
|
|
36
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
37
|
-
__setModuleDefault(result, mod);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
-
};
|
|
43
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.waitFor = exports.MaxFuncLength = exports.MaxRetryInterval = exports.MaxRetries = exports.InvokeType = exports.ResourceType = exports.
|
|
45
|
-
const
|
|
46
|
-
Object.defineProperty(exports, "ProtocolError", { enumerable: true, get: function () { return axios_1.AxiosError; } });
|
|
47
|
-
const form_data_1 = __importDefault(require("form-data"));
|
|
20
|
+
exports.waitFor = exports.MaxFuncLength = exports.MaxRetryInterval = exports.MaxRetries = exports.InvokeType = exports.ResourceType = exports.isProtocolError = exports.ProtocolError = void 0;
|
|
21
|
+
const stream_1 = require("stream");
|
|
48
22
|
const url_1 = require("url");
|
|
49
23
|
const deployment_1 = require("./deployment");
|
|
24
|
+
class ProtocolError extends Error {
|
|
25
|
+
constructor(message, status, data) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'ProtocolError';
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.data = data;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.ProtocolError = ProtocolError;
|
|
50
33
|
/**
|
|
51
|
-
* Type guard for protocol-specific errors
|
|
34
|
+
* Type guard for protocol-specific errors.
|
|
52
35
|
* @param err - The unknown error to check.
|
|
53
36
|
* @returns True if the error is an ProtocolError, false otherwise.
|
|
54
37
|
*/
|
|
55
|
-
const isProtocolError = (err) =>
|
|
38
|
+
const isProtocolError = (err) => err instanceof ProtocolError;
|
|
56
39
|
exports.isProtocolError = isProtocolError;
|
|
57
40
|
var ResourceType;
|
|
58
41
|
(function (ResourceType) {
|
|
@@ -64,33 +47,91 @@ var InvokeType;
|
|
|
64
47
|
InvokeType["Call"] = "call";
|
|
65
48
|
InvokeType["Await"] = "await";
|
|
66
49
|
})(InvokeType = exports.InvokeType || (exports.InvokeType = {}));
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
class Request {
|
|
51
|
+
constructor(token, baseURL) {
|
|
52
|
+
this.token = token;
|
|
53
|
+
this.baseURL = baseURL;
|
|
54
|
+
this.impl = {
|
|
55
|
+
url: '',
|
|
56
|
+
headers: new Headers({
|
|
57
|
+
Authorization: 'jwt ' + this.token
|
|
58
|
+
}),
|
|
59
|
+
method: 'GET',
|
|
60
|
+
body: undefined
|
|
75
61
|
};
|
|
76
|
-
}
|
|
62
|
+
}
|
|
63
|
+
url(path) {
|
|
64
|
+
this.impl.url = new url_1.URL(path, this.baseURL).toString();
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
headers(headers = {}) {
|
|
68
|
+
this.impl.headers = new Headers({
|
|
69
|
+
Authorization: 'jwt ' + this.token,
|
|
70
|
+
...headers
|
|
71
|
+
});
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
method(method) {
|
|
75
|
+
this.impl.method = method;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
bodyRaw(body) {
|
|
79
|
+
this.impl.body = body;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
body(body) {
|
|
83
|
+
this.impl.body = JSON.stringify(body);
|
|
84
|
+
this.impl.headers.set('Content-Type', 'application/json');
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
async execute() {
|
|
88
|
+
const config = {
|
|
89
|
+
method: this.impl.method,
|
|
90
|
+
headers: this.impl.headers
|
|
91
|
+
};
|
|
92
|
+
if (this.impl.body !== undefined) {
|
|
93
|
+
config.body = this.impl.body;
|
|
94
|
+
}
|
|
95
|
+
const res = await fetch(this.impl.url, config);
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
const data = await res.text().catch(() => null);
|
|
98
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}${data ? ` - ${data}` : ''}`);
|
|
99
|
+
}
|
|
100
|
+
return res;
|
|
101
|
+
}
|
|
102
|
+
async asJson() {
|
|
103
|
+
const res = await this.execute();
|
|
104
|
+
return res.json();
|
|
105
|
+
}
|
|
106
|
+
async asText() {
|
|
107
|
+
const res = await this.execute();
|
|
108
|
+
return res.text();
|
|
109
|
+
}
|
|
110
|
+
async asStatus() {
|
|
111
|
+
const res = await this.execute();
|
|
112
|
+
return res.status;
|
|
113
|
+
}
|
|
114
|
+
async asResponse() {
|
|
115
|
+
return await this.execute();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.default = (token, baseURL) => {
|
|
119
|
+
const request = (url = baseURL) => new Request(token, url);
|
|
120
|
+
const hostname = new url_1.URL(baseURL).hostname;
|
|
77
121
|
const api = {
|
|
78
|
-
refresh: () =>
|
|
79
|
-
|
|
80
|
-
.
|
|
81
|
-
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.get(getURL('/validate'), getConfig())
|
|
86
|
-
.then(res => res.data),
|
|
87
|
-
deployEnabled: () => axios_1.default
|
|
88
|
-
.get(getURL('/api/account/deploy-enabled'), getConfig())
|
|
89
|
-
.then(res => res.data),
|
|
122
|
+
refresh: () => request().url('/api/account/refresh-token').asText(),
|
|
123
|
+
ready: () => request()
|
|
124
|
+
.url('/api/readiness')
|
|
125
|
+
.asStatus()
|
|
126
|
+
.then(status => status === 200),
|
|
127
|
+
validate: () => request().url('/validate').asJson(),
|
|
128
|
+
deployEnabled: () => request().url('/api/account/deploy-enabled').asJson(),
|
|
90
129
|
listSubscriptions: async () => {
|
|
91
|
-
const
|
|
130
|
+
const subscriptionsList = await request()
|
|
131
|
+
.url('/api/billing/list-subscriptions')
|
|
132
|
+
.asJson();
|
|
92
133
|
const subscriptions = {};
|
|
93
|
-
for (const id of
|
|
134
|
+
for (const id of subscriptionsList) {
|
|
94
135
|
if (subscriptions[id] === undefined) {
|
|
95
136
|
subscriptions[id] = 1;
|
|
96
137
|
}
|
|
@@ -100,12 +141,10 @@ exports.default = (token, baseURL) => {
|
|
|
100
141
|
}
|
|
101
142
|
return subscriptions;
|
|
102
143
|
},
|
|
103
|
-
listSubscriptionsDeploys: () =>
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
inspect: () =>
|
|
107
|
-
.get(getURL('/api/inspect'), getConfig())
|
|
108
|
-
.then(res => res.data),
|
|
144
|
+
listSubscriptionsDeploys: () => request()
|
|
145
|
+
.url('/api/billing/list-subscriptions')
|
|
146
|
+
.asJson(),
|
|
147
|
+
inspect: () => request().url('/api/inspect').asJson(),
|
|
109
148
|
inspectByName: async (suffix) => {
|
|
110
149
|
const deployments = await api.inspect();
|
|
111
150
|
const deploy = deployments.find(deploy => deploy.suffix == suffix);
|
|
@@ -114,79 +153,118 @@ exports.default = (token, baseURL) => {
|
|
|
114
153
|
}
|
|
115
154
|
return deploy;
|
|
116
155
|
},
|
|
117
|
-
upload: async (name,
|
|
118
|
-
const fd = new
|
|
156
|
+
upload: async (name, data, jsons = [], runners = []) => {
|
|
157
|
+
const fd = new FormData();
|
|
119
158
|
fd.append('id', name);
|
|
120
159
|
fd.append('type', 'application/x-zip-compressed');
|
|
121
160
|
fd.append('jsons', JSON.stringify(jsons));
|
|
122
161
|
fd.append('runners', JSON.stringify(runners));
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
if (data instanceof Blob) {
|
|
163
|
+
fd.append('raw', data, `${name}.zip`);
|
|
164
|
+
}
|
|
165
|
+
else if (data instanceof stream_1.Readable) {
|
|
166
|
+
// This is terrible but NodeJS does not ensure that streaming and zero
|
|
167
|
+
// copy will be performed anyway, as the sizes are not really big (150mb is the limit)
|
|
168
|
+
// we can do this nasty intermediate buffer creation and forget about it
|
|
169
|
+
const chunks = [];
|
|
170
|
+
for await (const chunk of data) {
|
|
171
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
172
|
+
}
|
|
173
|
+
const buffer = Buffer.concat(chunks);
|
|
174
|
+
fd.append('raw', new Blob([buffer]), `${name}.zip`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw Error(`Type ${typeof data} not supported, use Blob or Readable`);
|
|
178
|
+
}
|
|
179
|
+
return await request()
|
|
180
|
+
.url('/api/package/create')
|
|
181
|
+
.method('POST')
|
|
182
|
+
.bodyRaw(fd)
|
|
183
|
+
.asJson();
|
|
130
184
|
},
|
|
131
|
-
add: (url, branch, jsons = []) =>
|
|
132
|
-
.
|
|
185
|
+
add: (url, branch, jsons = []) => request()
|
|
186
|
+
.url('/api/repository/add')
|
|
187
|
+
.method('POST')
|
|
188
|
+
.body({
|
|
133
189
|
url,
|
|
134
190
|
branch,
|
|
135
191
|
jsons
|
|
136
|
-
}
|
|
137
|
-
.
|
|
138
|
-
branchList: (url) =>
|
|
139
|
-
.
|
|
192
|
+
})
|
|
193
|
+
.asJson(),
|
|
194
|
+
branchList: (url) => request()
|
|
195
|
+
.url('/api/repository/branchlist')
|
|
196
|
+
.method('POST')
|
|
197
|
+
.body({
|
|
140
198
|
url
|
|
141
|
-
}
|
|
142
|
-
.
|
|
143
|
-
deploy: (name, env, plan, resourceType, release = Date.now().toString(16), version = 'v1') =>
|
|
144
|
-
.
|
|
199
|
+
})
|
|
200
|
+
.asJson(),
|
|
201
|
+
deploy: (name, env, plan, resourceType, release = Date.now().toString(16), version = 'v1') => request()
|
|
202
|
+
.url('/api/deploy/create')
|
|
203
|
+
.method('POST')
|
|
204
|
+
.body({
|
|
145
205
|
resourceType,
|
|
146
206
|
suffix: name,
|
|
147
207
|
release,
|
|
148
208
|
env,
|
|
149
209
|
plan,
|
|
150
210
|
version
|
|
151
|
-
}
|
|
152
|
-
.
|
|
153
|
-
deployDelete: (prefix, suffix, version = 'v1') =>
|
|
154
|
-
.
|
|
211
|
+
})
|
|
212
|
+
.asJson(),
|
|
213
|
+
deployDelete: (prefix, suffix, version = 'v1') => request()
|
|
214
|
+
.url('/api/deploy/delete')
|
|
215
|
+
.method('POST')
|
|
216
|
+
.body({
|
|
155
217
|
prefix,
|
|
156
218
|
suffix,
|
|
157
219
|
version
|
|
158
|
-
}
|
|
159
|
-
.
|
|
160
|
-
logs: (container, type = deployment_1.LogType.Deploy, suffix, prefix, version = 'v1') =>
|
|
161
|
-
.
|
|
220
|
+
})
|
|
221
|
+
.asJson(),
|
|
222
|
+
logs: (container, type = deployment_1.LogType.Deploy, suffix, prefix, version = 'v1') => request()
|
|
223
|
+
.url('/api/deploy/logs')
|
|
224
|
+
.method('POST')
|
|
225
|
+
.body({
|
|
162
226
|
container,
|
|
163
227
|
type,
|
|
164
228
|
suffix,
|
|
165
229
|
prefix,
|
|
166
230
|
version
|
|
167
|
-
}
|
|
168
|
-
.
|
|
169
|
-
fileList: (url, branch) =>
|
|
170
|
-
.
|
|
231
|
+
})
|
|
232
|
+
.asJson(),
|
|
233
|
+
fileList: (url, branch) => request()
|
|
234
|
+
.url('/api/repository/filelist')
|
|
235
|
+
.method('POST')
|
|
236
|
+
.body({
|
|
171
237
|
url,
|
|
172
238
|
branch
|
|
173
|
-
}
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
239
|
+
})
|
|
240
|
+
.asJson()
|
|
241
|
+
.then(res => res['files']),
|
|
242
|
+
invoke: async (type, prefix, suffix, version = 'v1', name, args) => {
|
|
243
|
+
const req = (() => {
|
|
244
|
+
if (hostname === 'localhost') {
|
|
245
|
+
// Old API in commercial FaaS and current API of FaaS reimplementation
|
|
246
|
+
return request().url(`/${prefix}/${suffix}/${version}/${type}/${name}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// New API used by commercial FaaS
|
|
250
|
+
return request(`https://${version}-${suffix}-${prefix}.api.metacall.io`).url(`/${type}/${name}`);
|
|
251
|
+
}
|
|
252
|
+
})();
|
|
253
|
+
if (args === undefined) {
|
|
254
|
+
req.method('GET');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
req.method('POST').body(args);
|
|
258
|
+
}
|
|
259
|
+
return await req.asJson();
|
|
182
260
|
},
|
|
183
261
|
call: (prefix, suffix, version = 'v1', name, args) => api.invoke(InvokeType.Call, prefix, suffix, version, name, args),
|
|
184
262
|
await: (prefix, suffix, version = 'v1', name, args) => api.invoke(InvokeType.Await, prefix, suffix, version, name, args)
|
|
185
263
|
};
|
|
186
264
|
return api;
|
|
187
265
|
};
|
|
188
|
-
exports.MaxRetries =
|
|
189
|
-
exports.MaxRetryInterval =
|
|
266
|
+
exports.MaxRetries = 100;
|
|
267
|
+
exports.MaxRetryInterval = 5000;
|
|
190
268
|
exports.MaxFuncLength = 64;
|
|
191
269
|
/**
|
|
192
270
|
* Executes an asynchronous function with automatic retry logic.
|
|
@@ -227,9 +305,14 @@ exports.MaxFuncLength = 64;
|
|
|
227
305
|
*/
|
|
228
306
|
const waitFor = async (fn, maxRetries = exports.MaxRetries, interval = exports.MaxRetryInterval) => {
|
|
229
307
|
let retry = 0;
|
|
308
|
+
let cancellation = undefined;
|
|
309
|
+
const cancel = (message) => {
|
|
310
|
+
retry = exports.MaxRetries;
|
|
311
|
+
cancellation = `Operation cancelled with message: ${message}`;
|
|
312
|
+
};
|
|
230
313
|
for (;;) {
|
|
231
314
|
try {
|
|
232
|
-
return await fn();
|
|
315
|
+
return await fn(cancel);
|
|
233
316
|
}
|
|
234
317
|
catch (error) {
|
|
235
318
|
retry++;
|
|
@@ -239,11 +322,13 @@ const waitFor = async (fn, maxRetries = exports.MaxRetries, interval = exports.M
|
|
|
239
322
|
(fnStr.length > exports.MaxFuncLength
|
|
240
323
|
? fnStr.slice(0, exports.MaxFuncLength) + '...'
|
|
241
324
|
: fnStr);
|
|
242
|
-
const message =
|
|
243
|
-
?
|
|
244
|
-
: error
|
|
325
|
+
const message = cancellation !== undefined
|
|
326
|
+
? cancellation
|
|
327
|
+
: exports.isProtocolError(error)
|
|
245
328
|
? error.message
|
|
246
|
-
:
|
|
329
|
+
: error instanceof Error
|
|
330
|
+
? error.message
|
|
331
|
+
: String(error);
|
|
247
332
|
throw new Error(`Failed to execute '${func}' after ${maxRetries} retries: ${message}`);
|
|
248
333
|
}
|
|
249
334
|
await new Promise(r => setTimeout(r, interval));
|
package/dist/signup.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const axios_1 = __importDefault(require("axios"));
|
|
7
3
|
const url_1 = require("url");
|
|
8
|
-
exports.default = (email, password, alias, baseURL) => {
|
|
4
|
+
exports.default = async (email, password, alias, baseURL) => {
|
|
9
5
|
const request = {
|
|
10
6
|
email,
|
|
11
7
|
password,
|
|
12
8
|
alias
|
|
13
9
|
};
|
|
14
|
-
if (!baseURL.includes('localhost'))
|
|
15
|
-
request['g-recaptcha-response'] = 'empty';
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
if (!baseURL.includes('localhost')) {
|
|
11
|
+
request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
|
|
12
|
+
}
|
|
13
|
+
const res = await fetch(baseURL + '/signup', {
|
|
14
|
+
method: 'POST',
|
|
18
15
|
headers: {
|
|
19
16
|
Accept: 'application/json, text/plain, */*',
|
|
20
17
|
Host: new url_1.URL(baseURL).host,
|
|
21
|
-
Origin: baseURL
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
18
|
+
Origin: baseURL,
|
|
19
|
+
'Content-Type': 'application/json'
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify(request)
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(res.statusText);
|
|
25
|
+
}
|
|
26
|
+
return res.text();
|
|
25
27
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metacall/protocol",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "Tool for deploying into MetaCall FaaS platform.",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./*": "./dist/*.js",
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
+
"pretest": "npm --prefix ./src/test/resources/integration/api install",
|
|
21
22
|
"test": "npm run --silent build && mocha dist/test",
|
|
22
|
-
"unit": "npm run --silent test -- --ignore
|
|
23
|
+
"unit": "npm run --silent test -- --ignore **/**.integration.**",
|
|
23
24
|
"prepublishOnly": "npm run --silent build",
|
|
24
25
|
"postinstall": "node -e \"require('fs').existsSync('githooks') && require('./githooks/configure.js').configure()\"",
|
|
25
26
|
"build": "npm run --silent lint && tsc",
|
|
@@ -85,8 +86,6 @@
|
|
|
85
86
|
}
|
|
86
87
|
},
|
|
87
88
|
"dependencies": {
|
|
88
|
-
"axios": "^1.13.5",
|
|
89
|
-
"form-data": "^3.0.0",
|
|
90
89
|
"ignore-walk": "^3.0.4",
|
|
91
90
|
"jsonwebtoken": "^9.0.0"
|
|
92
91
|
},
|
package/src/index.ts
CHANGED
package/src/login.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import { URL } from 'url';
|
|
3
2
|
|
|
4
3
|
interface Request {
|
|
@@ -7,7 +6,7 @@ interface Request {
|
|
|
7
6
|
'g-recaptcha-response'?: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export default (
|
|
9
|
+
export default async (
|
|
11
10
|
email: string,
|
|
12
11
|
password: string,
|
|
13
12
|
baseURL: string
|
|
@@ -17,16 +16,24 @@ export default (
|
|
|
17
16
|
password
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
if (!baseURL.includes('localhost'))
|
|
21
|
-
request['g-recaptcha-response'] = 'empty'; //TODO: Review the captcha
|
|
19
|
+
if (!baseURL.includes('localhost')) {
|
|
20
|
+
request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
.
|
|
23
|
+
const res = await fetch(baseURL + '/login', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
Accept: 'application/json, text/plain, */*',
|
|
27
|
+
Host: new URL(baseURL).host,
|
|
28
|
+
Origin: baseURL,
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify(request)
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
throw new Error(res.statusText);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return res.text();
|
|
32
39
|
};
|
package/src/protocol.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
this is just a client that implements all the rest API from the FaaS, so each function it contains is an endpoint in the FaaS for deploying and similar
|
|
2
|
+
This is just a client that implements all the rest API from the FaaS,
|
|
3
|
+
so each function it contains is an endpoint in the FaaS for deploying:
|
|
6
4
|
|
|
7
5
|
refresh: updates the auth token
|
|
8
6
|
validate: validates the auth token
|
|
@@ -18,22 +16,30 @@
|
|
|
18
16
|
fileList: get files of a repository by branch
|
|
19
17
|
*/
|
|
20
18
|
|
|
21
|
-
import
|
|
22
|
-
import FormData from 'form-data';
|
|
19
|
+
import { Readable } from 'stream';
|
|
23
20
|
import { URL } from 'url';
|
|
24
21
|
import { Create, Deployment, LogType, MetaCallJSON } from './deployment';
|
|
25
22
|
import { Plans } from './plan';
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
export class ProtocolError extends Error {
|
|
25
|
+
status?: number;
|
|
26
|
+
data?: unknown;
|
|
27
|
+
|
|
28
|
+
constructor(message: string, status?: number, data?: unknown) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'ProtocolError';
|
|
31
|
+
this.status = status;
|
|
32
|
+
this.data = data;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
|
|
28
36
|
/**
|
|
29
|
-
* Type guard for protocol-specific errors
|
|
37
|
+
* Type guard for protocol-specific errors.
|
|
30
38
|
* @param err - The unknown error to check.
|
|
31
39
|
* @returns True if the error is an ProtocolError, false otherwise.
|
|
32
40
|
*/
|
|
33
|
-
export const isProtocolError = (err: unknown):
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export { AxiosError as ProtocolError };
|
|
41
|
+
export const isProtocolError = (err: unknown): err is ProtocolError =>
|
|
42
|
+
err instanceof ProtocolError;
|
|
37
43
|
|
|
38
44
|
type SubscriptionMap = Record<string, number>;
|
|
39
45
|
|
|
@@ -49,7 +55,7 @@ export enum ResourceType {
|
|
|
49
55
|
Repository = 'Repository'
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export interface
|
|
58
|
+
export interface Resource {
|
|
53
59
|
id: string;
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -106,12 +112,8 @@ export interface API {
|
|
|
106
112
|
blob: unknown,
|
|
107
113
|
jsons?: MetaCallJSON[],
|
|
108
114
|
runners?: string[]
|
|
109
|
-
): Promise<
|
|
110
|
-
add(
|
|
111
|
-
url: string,
|
|
112
|
-
branch: string,
|
|
113
|
-
jsons: MetaCallJSON[]
|
|
114
|
-
): Promise<AddResponse>;
|
|
115
|
+
): Promise<Resource>;
|
|
116
|
+
add(url: string, branch: string, jsons: MetaCallJSON[]): Promise<Resource>;
|
|
115
117
|
deploy(
|
|
116
118
|
name: string,
|
|
117
119
|
env: { name: string; value: string }[],
|
|
@@ -158,50 +160,132 @@ export interface API {
|
|
|
158
160
|
): Promise<Result>;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
interface RequestImpl {
|
|
164
|
+
url: string;
|
|
165
|
+
headers: Headers;
|
|
166
|
+
method: string;
|
|
167
|
+
body?: BodyInit;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class Request {
|
|
171
|
+
private token: string;
|
|
172
|
+
private baseURL: string;
|
|
173
|
+
private impl: RequestImpl;
|
|
174
|
+
|
|
175
|
+
constructor(token: string, baseURL: string) {
|
|
176
|
+
this.token = token;
|
|
177
|
+
this.baseURL = baseURL;
|
|
178
|
+
this.impl = {
|
|
179
|
+
url: '',
|
|
180
|
+
headers: new Headers({
|
|
181
|
+
Authorization: 'jwt ' + this.token
|
|
182
|
+
}),
|
|
183
|
+
method: 'GET',
|
|
184
|
+
body: undefined
|
|
169
185
|
};
|
|
170
|
-
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
url(path: string): Request {
|
|
189
|
+
this.impl.url = new URL(path, this.baseURL).toString();
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
headers(headers = {}): Request {
|
|
194
|
+
this.impl.headers = new Headers({
|
|
195
|
+
Authorization: 'jwt ' + this.token,
|
|
196
|
+
...headers
|
|
197
|
+
});
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
method(method: string): Request {
|
|
202
|
+
this.impl.method = method;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
bodyRaw(body: BodyInit): Request {
|
|
207
|
+
this.impl.body = body;
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
body(body: unknown): Request {
|
|
212
|
+
this.impl.body = JSON.stringify(body);
|
|
213
|
+
this.impl.headers.set('Content-Type', 'application/json');
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private async execute(): Promise<Response> {
|
|
218
|
+
const config: RequestInit = {
|
|
219
|
+
method: this.impl.method,
|
|
220
|
+
headers: this.impl.headers
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (this.impl.body !== undefined) {
|
|
224
|
+
config.body = this.impl.body;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const res = await fetch(this.impl.url, config);
|
|
228
|
+
|
|
229
|
+
if (!res.ok) {
|
|
230
|
+
const data = await res.text().catch(() => null);
|
|
231
|
+
throw new Error(
|
|
232
|
+
`HTTP ${res.status}: ${res.statusText}${
|
|
233
|
+
data ? ` - ${data}` : ''
|
|
234
|
+
}`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return res;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async asJson<T>(): Promise<T> {
|
|
242
|
+
const res = await this.execute();
|
|
243
|
+
return res.json() as Promise<T>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async asText(): Promise<string> {
|
|
247
|
+
const res = await this.execute();
|
|
248
|
+
return res.text();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async asStatus(): Promise<number> {
|
|
252
|
+
const res = await this.execute();
|
|
253
|
+
return res.status;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async asResponse(): Promise<Response> {
|
|
257
|
+
return await this.execute();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default (token: string, baseURL: string): API => {
|
|
262
|
+
const request = (url = baseURL) => new Request(token, url);
|
|
263
|
+
const hostname = new URL(baseURL).hostname;
|
|
171
264
|
|
|
172
265
|
const api: API = {
|
|
173
266
|
refresh: (): Promise<string> =>
|
|
174
|
-
|
|
175
|
-
.get<string>(getURL('/api/account/refresh-token'), getConfig())
|
|
176
|
-
.then(res => res.data),
|
|
267
|
+
request().url('/api/account/refresh-token').asText(),
|
|
177
268
|
|
|
178
269
|
ready: (): Promise<boolean> =>
|
|
179
|
-
|
|
180
|
-
.
|
|
181
|
-
.
|
|
270
|
+
request()
|
|
271
|
+
.url('/api/readiness')
|
|
272
|
+
.asStatus()
|
|
273
|
+
.then(status => status === 200),
|
|
182
274
|
|
|
183
275
|
validate: (): Promise<boolean> =>
|
|
184
|
-
|
|
185
|
-
.get<boolean>(getURL('/validate'), getConfig())
|
|
186
|
-
.then(res => res.data),
|
|
276
|
+
request().url('/validate').asJson<boolean>(),
|
|
187
277
|
|
|
188
278
|
deployEnabled: (): Promise<boolean> =>
|
|
189
|
-
|
|
190
|
-
.get<boolean>(
|
|
191
|
-
getURL('/api/account/deploy-enabled'),
|
|
192
|
-
getConfig()
|
|
193
|
-
)
|
|
194
|
-
.then(res => res.data),
|
|
279
|
+
request().url('/api/account/deploy-enabled').asJson<boolean>(),
|
|
195
280
|
|
|
196
281
|
listSubscriptions: async (): Promise<SubscriptionMap> => {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
282
|
+
const subscriptionsList = await request()
|
|
283
|
+
.url('/api/billing/list-subscriptions')
|
|
284
|
+
.asJson<string[]>();
|
|
201
285
|
|
|
202
286
|
const subscriptions: SubscriptionMap = {};
|
|
203
287
|
|
|
204
|
-
for (const id of
|
|
288
|
+
for (const id of subscriptionsList) {
|
|
205
289
|
if (subscriptions[id] === undefined) {
|
|
206
290
|
subscriptions[id] = 1;
|
|
207
291
|
} else {
|
|
@@ -213,17 +297,12 @@ export default (token: string, baseURL: string): API => {
|
|
|
213
297
|
},
|
|
214
298
|
|
|
215
299
|
listSubscriptionsDeploys: (): Promise<SubscriptionDeploy[]> =>
|
|
216
|
-
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
getConfig()
|
|
220
|
-
)
|
|
221
|
-
.then(res => res.data),
|
|
300
|
+
request()
|
|
301
|
+
.url('/api/billing/list-subscriptions')
|
|
302
|
+
.asJson<SubscriptionDeploy[]>(),
|
|
222
303
|
|
|
223
304
|
inspect: (): Promise<Deployment[]> =>
|
|
224
|
-
|
|
225
|
-
.get<Deployment[]>(getURL('/api/inspect'), getConfig())
|
|
226
|
-
.then(res => res.data),
|
|
305
|
+
request().url('/api/inspect').asJson<Deployment[]>(),
|
|
227
306
|
|
|
228
307
|
inspectByName: async (suffix: string): Promise<Deployment> => {
|
|
229
308
|
const deployments = await api.inspect();
|
|
@@ -239,52 +318,67 @@ export default (token: string, baseURL: string): API => {
|
|
|
239
318
|
|
|
240
319
|
upload: async (
|
|
241
320
|
name: string,
|
|
242
|
-
|
|
321
|
+
data: Blob | Readable,
|
|
243
322
|
jsons: MetaCallJSON[] = [],
|
|
244
323
|
runners: string[] = []
|
|
245
|
-
): Promise<
|
|
324
|
+
): Promise<Resource> => {
|
|
246
325
|
const fd = new FormData();
|
|
326
|
+
|
|
247
327
|
fd.append('id', name);
|
|
248
328
|
fd.append('type', 'application/x-zip-compressed');
|
|
249
329
|
fd.append('jsons', JSON.stringify(jsons));
|
|
250
330
|
fd.append('runners', JSON.stringify(runners));
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
331
|
+
|
|
332
|
+
if (data instanceof Blob) {
|
|
333
|
+
fd.append('raw', data, `${name}.zip`);
|
|
334
|
+
} else if (data instanceof Readable) {
|
|
335
|
+
// This is terrible but NodeJS does not ensure that streaming and zero
|
|
336
|
+
// copy will be performed anyway, as the sizes are not really big (150mb is the limit)
|
|
337
|
+
// we can do this nasty intermediate buffer creation and forget about it
|
|
338
|
+
const chunks: Uint8Array[] = [];
|
|
339
|
+
for await (const chunk of data) {
|
|
340
|
+
chunks.push(
|
|
341
|
+
typeof chunk === 'string' ? Buffer.from(chunk) : chunk
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
const buffer = Buffer.concat(chunks);
|
|
345
|
+
fd.append('raw', new Blob([buffer]), `${name}.zip`);
|
|
346
|
+
} else {
|
|
347
|
+
throw Error(
|
|
348
|
+
`Type ${typeof data} not supported, use Blob or Readable`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return await request()
|
|
353
|
+
.url('/api/package/create')
|
|
354
|
+
.method('POST')
|
|
355
|
+
.bodyRaw(fd)
|
|
356
|
+
.asJson<Resource>();
|
|
261
357
|
},
|
|
358
|
+
|
|
262
359
|
add: (
|
|
263
360
|
url: string,
|
|
264
361
|
branch: string,
|
|
265
362
|
jsons: MetaCallJSON[] = []
|
|
266
|
-
): Promise<
|
|
267
|
-
|
|
268
|
-
.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.then(res => res.data),
|
|
363
|
+
): Promise<Resource> =>
|
|
364
|
+
request()
|
|
365
|
+
.url('/api/repository/add')
|
|
366
|
+
.method('POST')
|
|
367
|
+
.body({
|
|
368
|
+
url,
|
|
369
|
+
branch,
|
|
370
|
+
jsons
|
|
371
|
+
})
|
|
372
|
+
.asJson<Resource>(),
|
|
373
|
+
|
|
278
374
|
branchList: (url: string): Promise<Branches> =>
|
|
279
|
-
|
|
280
|
-
.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
)
|
|
287
|
-
.then(res => res.data),
|
|
375
|
+
request()
|
|
376
|
+
.url('/api/repository/branchlist')
|
|
377
|
+
.method('POST')
|
|
378
|
+
.body({
|
|
379
|
+
url
|
|
380
|
+
})
|
|
381
|
+
.asJson<Branches>(),
|
|
288
382
|
|
|
289
383
|
deploy: (
|
|
290
384
|
name: string,
|
|
@@ -294,37 +388,33 @@ export default (token: string, baseURL: string): API => {
|
|
|
294
388
|
release: string = Date.now().toString(16),
|
|
295
389
|
version = 'v1'
|
|
296
390
|
): Promise<Create> =>
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
)
|
|
310
|
-
.then(res => res.data),
|
|
391
|
+
request()
|
|
392
|
+
.url('/api/deploy/create')
|
|
393
|
+
.method('POST')
|
|
394
|
+
.body({
|
|
395
|
+
resourceType,
|
|
396
|
+
suffix: name,
|
|
397
|
+
release,
|
|
398
|
+
env,
|
|
399
|
+
plan,
|
|
400
|
+
version
|
|
401
|
+
})
|
|
402
|
+
.asJson<Create>(),
|
|
311
403
|
|
|
312
404
|
deployDelete: (
|
|
313
405
|
prefix: string,
|
|
314
406
|
suffix: string,
|
|
315
407
|
version = 'v1'
|
|
316
408
|
): Promise<string> =>
|
|
317
|
-
|
|
318
|
-
.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
)
|
|
327
|
-
.then(res => res.data),
|
|
409
|
+
request()
|
|
410
|
+
.url('/api/deploy/delete')
|
|
411
|
+
.method('POST')
|
|
412
|
+
.body({
|
|
413
|
+
prefix,
|
|
414
|
+
suffix,
|
|
415
|
+
version
|
|
416
|
+
})
|
|
417
|
+
.asJson<string>(),
|
|
328
418
|
|
|
329
419
|
logs: (
|
|
330
420
|
container: string,
|
|
@@ -333,33 +423,30 @@ export default (token: string, baseURL: string): API => {
|
|
|
333
423
|
prefix: string,
|
|
334
424
|
version = 'v1'
|
|
335
425
|
): Promise<string> =>
|
|
336
|
-
|
|
337
|
-
.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
)
|
|
348
|
-
.then(res => res.data),
|
|
426
|
+
request()
|
|
427
|
+
.url('/api/deploy/logs')
|
|
428
|
+
.method('POST')
|
|
429
|
+
.body({
|
|
430
|
+
container,
|
|
431
|
+
type,
|
|
432
|
+
suffix,
|
|
433
|
+
prefix,
|
|
434
|
+
version
|
|
435
|
+
})
|
|
436
|
+
.asJson<string>(),
|
|
349
437
|
|
|
350
438
|
fileList: (url: string, branch: string): Promise<string[]> =>
|
|
351
|
-
|
|
352
|
-
.
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
invoke: <Result, Args = unknown>(
|
|
439
|
+
request()
|
|
440
|
+
.url('/api/repository/filelist')
|
|
441
|
+
.method('POST')
|
|
442
|
+
.body({
|
|
443
|
+
url,
|
|
444
|
+
branch
|
|
445
|
+
})
|
|
446
|
+
.asJson<{ [k: string]: string[] }>()
|
|
447
|
+
.then(res => res['files']),
|
|
448
|
+
|
|
449
|
+
invoke: async <Result, Args = unknown>(
|
|
363
450
|
type: InvokeType,
|
|
364
451
|
prefix: string,
|
|
365
452
|
suffix: string,
|
|
@@ -367,17 +454,27 @@ export default (token: string, baseURL: string): API => {
|
|
|
367
454
|
name: string,
|
|
368
455
|
args?: Args
|
|
369
456
|
): Promise<Result> => {
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
457
|
+
const req = (() => {
|
|
458
|
+
if (hostname === 'localhost') {
|
|
459
|
+
// Old API in commercial FaaS and current API of FaaS reimplementation
|
|
460
|
+
return request().url(
|
|
461
|
+
`/${prefix}/${suffix}/${version}/${type}/${name}`
|
|
462
|
+
);
|
|
463
|
+
} else {
|
|
464
|
+
// New API used by commercial FaaS
|
|
465
|
+
return request(
|
|
466
|
+
`https://${version}-${suffix}-${prefix}.api.metacall.io`
|
|
467
|
+
).url(`/${type}/${name}`);
|
|
468
|
+
}
|
|
469
|
+
})();
|
|
374
470
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
471
|
+
if (args === undefined) {
|
|
472
|
+
req.method('GET');
|
|
473
|
+
} else {
|
|
474
|
+
req.method('POST').body(args);
|
|
475
|
+
}
|
|
379
476
|
|
|
380
|
-
return req.
|
|
477
|
+
return await req.asJson<Result>();
|
|
381
478
|
},
|
|
382
479
|
|
|
383
480
|
call: <Result, Args = unknown>(
|
|
@@ -402,8 +499,8 @@ export default (token: string, baseURL: string): API => {
|
|
|
402
499
|
return api;
|
|
403
500
|
};
|
|
404
501
|
|
|
405
|
-
export const MaxRetries =
|
|
406
|
-
export const MaxRetryInterval =
|
|
502
|
+
export const MaxRetries = 100;
|
|
503
|
+
export const MaxRetryInterval = 5000;
|
|
407
504
|
export const MaxFuncLength = 64;
|
|
408
505
|
|
|
409
506
|
/**
|
|
@@ -444,15 +541,21 @@ export const MaxFuncLength = 64;
|
|
|
444
541
|
* ```
|
|
445
542
|
*/
|
|
446
543
|
export const waitFor = async <T>(
|
|
447
|
-
fn: () => Promise<T>,
|
|
544
|
+
fn: (cancel: (message: string) => void) => Promise<T>,
|
|
448
545
|
maxRetries: number = MaxRetries,
|
|
449
546
|
interval: number = MaxRetryInterval
|
|
450
547
|
): Promise<T> => {
|
|
451
548
|
let retry = 0;
|
|
549
|
+
let cancellation = undefined;
|
|
550
|
+
|
|
551
|
+
const cancel = (message: string) => {
|
|
552
|
+
retry = MaxRetries;
|
|
553
|
+
cancellation = `Operation cancelled with message: ${message}`;
|
|
554
|
+
};
|
|
452
555
|
|
|
453
556
|
for (;;) {
|
|
454
557
|
try {
|
|
455
|
-
return await fn();
|
|
558
|
+
return await fn(cancel);
|
|
456
559
|
} catch (error) {
|
|
457
560
|
retry++;
|
|
458
561
|
if (retry >= maxRetries) {
|
|
@@ -462,11 +565,14 @@ export const waitFor = async <T>(
|
|
|
462
565
|
(fnStr.length > MaxFuncLength
|
|
463
566
|
? fnStr.slice(0, MaxFuncLength) + '...'
|
|
464
567
|
: fnStr);
|
|
465
|
-
const message =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
568
|
+
const message =
|
|
569
|
+
cancellation !== undefined
|
|
570
|
+
? cancellation
|
|
571
|
+
: isProtocolError(error)
|
|
572
|
+
? error.message
|
|
573
|
+
: error instanceof Error
|
|
574
|
+
? error.message
|
|
575
|
+
: String(error);
|
|
470
576
|
|
|
471
577
|
throw new Error(
|
|
472
578
|
`Failed to execute '${func}' after ${maxRetries} retries: ${message}`
|
package/src/signup.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import { URL } from 'url';
|
|
3
2
|
interface Request {
|
|
4
3
|
email: string;
|
|
@@ -7,7 +6,7 @@ interface Request {
|
|
|
7
6
|
'g-recaptcha-response'?: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export default (
|
|
9
|
+
export default async (
|
|
11
10
|
email: string,
|
|
12
11
|
password: string,
|
|
13
12
|
alias: string,
|
|
@@ -18,15 +17,25 @@ export default (
|
|
|
18
17
|
password,
|
|
19
18
|
alias
|
|
20
19
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
|
|
21
|
+
if (!baseURL.includes('localhost')) {
|
|
22
|
+
request['g-recaptcha-response'] = 'empty'; // TODO: Review the captcha
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const res = await fetch(baseURL + '/signup', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
Accept: 'application/json, text/plain, */*',
|
|
29
|
+
Host: new URL(baseURL).host,
|
|
30
|
+
Origin: baseURL,
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify(request)
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
throw new Error(res.statusText);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return res.text();
|
|
32
41
|
};
|