moeralib 0.15.0
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/LICENSE +201 -0
- package/README.md +8 -0
- package/lib/naming/index.js +14 -0
- package/lib/naming/naming.js +203 -0
- package/lib/naming/schemas.mjs +184 -0
- package/lib/naming/types.js +2 -0
- package/lib/naming/validate.js +9 -0
- package/lib/naming/validators.js +2065 -0
- package/lib/node/caller.js +267 -0
- package/lib/node/cartes.js +55 -0
- package/lib/node/index.js +13 -0
- package/lib/node/node.js +1405 -0
- package/lib/node/schemas.mjs +4582 -0
- package/lib/node/types.js +3 -0
- package/lib/node/validate.js +9 -0
- package/lib/node/validators.js +60225 -0
- package/lib/schema.js +20 -0
- package/lib/schemas-compile.mjs +42 -0
- package/lib/universal-location.js +164 -0
- package/lib/util.js +42 -0
- package/nodejs-moera-api/nodejs-moera-api +4 -0
- package/nodejs-moera-api/nodejsmoeraapi.py +578 -0
- package/package.json +65 -0
- package/src/naming/index.ts +12 -0
- package/src/naming/naming.ts +234 -0
- package/src/naming/schemas.mjs +194 -0
- package/src/naming/types.ts +39 -0
- package/src/naming/validate.ts +6 -0
- package/src/naming/validators.d.ts +3 -0
- package/src/naming/validators.js +2084 -0
- package/src/node/caller.ts +311 -0
- package/src/node/cartes.ts +51 -0
- package/src/node/index.ts +3 -0
- package/src/node/node.ts +1285 -0
- package/src/node/schemas.mjs +4715 -0
- package/src/node/types.ts +1544 -0
- package/src/node/validate.ts +6 -0
- package/src/node/validators.d.ts +3 -0
- package/src/node/validators.js +60484 -0
- package/src/schema.ts +30 -0
- package/src/schemas-compile.mjs +51 -0
- package/src/universal-location.ts +212 -0
- package/src/util.ts +42 -0
- package/tsconfig.json +112 -0
- package/typings/naming/index.d.ts +2 -0
- package/typings/naming/index.d.ts.map +1 -0
- package/typings/naming/naming.d.ts +31 -0
- package/typings/naming/naming.d.ts.map +1 -0
- package/typings/naming/schemas.d.mts +271 -0
- package/typings/naming/schemas.d.mts.map +1 -0
- package/typings/naming/types.d.ts +35 -0
- package/typings/naming/types.d.ts.map +1 -0
- package/typings/naming/validate.d.ts +3 -0
- package/typings/naming/validate.d.ts.map +1 -0
- package/typings/naming/validators.d.ts +73 -0
- package/typings/naming/validators.d.ts.map +1 -0
- package/typings/node/caller.d.ts +52 -0
- package/typings/node/caller.d.ts.map +1 -0
- package/typings/node/cartes.d.ts +13 -0
- package/typings/node/cartes.d.ts.map +1 -0
- package/typings/node/index.d.ts +4 -0
- package/typings/node/index.d.ts.map +1 -0
- package/typings/node/node.d.ts +176 -0
- package/typings/node/node.d.ts.map +1 -0
- package/typings/node/schemas.d.mts +6205 -0
- package/typings/node/schemas.d.mts.map +1 -0
- package/typings/node/types.d.ts +1340 -0
- package/typings/node/types.d.ts.map +1 -0
- package/typings/node/validate.d.ts +3 -0
- package/typings/node/validate.d.ts.map +1 -0
- package/typings/node/validators.d.ts +920 -0
- package/typings/node/validators.d.ts.map +1 -0
- package/typings/schema.d.ts +18 -0
- package/typings/schema.d.ts.map +1 -0
- package/typings/schemas-compile.d.mts +2 -0
- package/typings/schemas-compile.d.mts.map +1 -0
- package/typings/universal-location.d.ts +25 -0
- package/typings/universal-location.d.ts.map +1 -0
- package/typings/util.d.ts +6 -0
- package/typings/util.d.ts.map +1 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import cloneDeep from 'lodash.clonedeep';
|
|
2
|
+
|
|
3
|
+
import { Body, BodyFormat, Result, SourceFormat } from "./types";
|
|
4
|
+
import { validateSchema } from "./validate";
|
|
5
|
+
import { urlWithParameters } from "../util";
|
|
6
|
+
import { formatSchemaErrors } from "../schema";
|
|
7
|
+
|
|
8
|
+
export class MoeraNodeError extends Error {
|
|
9
|
+
|
|
10
|
+
constructor(name: string, message: string) {
|
|
11
|
+
super(name + ": Node error: " + message);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class MoeraNodeApiError extends MoeraNodeError {
|
|
17
|
+
|
|
18
|
+
errorCode: string;
|
|
19
|
+
|
|
20
|
+
constructor(name: string, result: Result) {
|
|
21
|
+
super(name, result.message);
|
|
22
|
+
this.errorCode = result.errorCode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class MoeraNodeConnectionError extends Error {
|
|
28
|
+
|
|
29
|
+
constructor(message: string) {
|
|
30
|
+
super("Node connection error: " + message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class MoeraCallError extends Error {
|
|
36
|
+
|
|
37
|
+
constructor(message: string) {
|
|
38
|
+
super(message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Structure = Partial<Record<string, any>>;
|
|
44
|
+
|
|
45
|
+
function encodeBody(decoded: Body | null, format: BodyFormat | SourceFormat | null): string | null {
|
|
46
|
+
if (decoded == null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (format === "application") {
|
|
50
|
+
return decoded.text ?? null;
|
|
51
|
+
}
|
|
52
|
+
return JSON.stringify(decoded);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function encodeBodies(data: Structure | Structure[]): Structure | Structure[] {
|
|
56
|
+
if (Array.isArray(data)) {
|
|
57
|
+
return data.map(item => encodeBodies(item));
|
|
58
|
+
}
|
|
59
|
+
const encoded = cloneDeep(data);
|
|
60
|
+
if (data.body != null) {
|
|
61
|
+
encoded.body = encodeBody(data.body, data.bodyFormat);
|
|
62
|
+
}
|
|
63
|
+
if (data.bodyPreview != null) {
|
|
64
|
+
encoded.body = encodeBody(data.bodyPreview, data.bodyFormat);
|
|
65
|
+
}
|
|
66
|
+
if (data.bodySrc != null) {
|
|
67
|
+
encoded.bodySrc = encodeBody(data.bodySrc, data.bodySrcFormat);
|
|
68
|
+
}
|
|
69
|
+
return encoded;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function decodeBody(name: string, encoded: string, format: BodyFormat | SourceFormat | null): Body {
|
|
73
|
+
if (format === "application") {
|
|
74
|
+
return {text: encoded};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let body: Body;
|
|
78
|
+
try {
|
|
79
|
+
body = JSON.parse(encoded);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (e instanceof SyntaxError) {
|
|
82
|
+
throw new MoeraNodeError(name, "Invalid body encoding: " + e.message);
|
|
83
|
+
}
|
|
84
|
+
throw e;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const {valid, errors} = validateSchema("Body", body);
|
|
88
|
+
if (!valid) {
|
|
89
|
+
throw new MoeraNodeError(name, "Invalid body: " + formatSchemaErrors(errors));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function decodeBodies(name: string, data: Structure | Structure[]): Structure | Structure[] {
|
|
96
|
+
if (Array.isArray(data)) {
|
|
97
|
+
return data.map(item => decodeBodies(name, item));
|
|
98
|
+
}
|
|
99
|
+
const decoded = cloneDeep(data);
|
|
100
|
+
if (data.stories != null) {
|
|
101
|
+
decoded.stories = data.stories.map((item: Structure) => decodeBodies(name, item));
|
|
102
|
+
}
|
|
103
|
+
if (data.comments != null) {
|
|
104
|
+
decoded.comments = data.comments.map((item: Structure) => decodeBodies(name, item));
|
|
105
|
+
}
|
|
106
|
+
if (data.comment != null) {
|
|
107
|
+
decoded.comment = decodeBodies(name, data.comment);
|
|
108
|
+
}
|
|
109
|
+
if (data.posting != null) {
|
|
110
|
+
decoded.posting = decodeBodies(name, data.posting);
|
|
111
|
+
}
|
|
112
|
+
if (data.body != null) {
|
|
113
|
+
decoded.body = decodeBody(name, data.body, data.bodyFormat);
|
|
114
|
+
}
|
|
115
|
+
if (data.bodyPreview != null) {
|
|
116
|
+
decoded.bodyPreview = decodeBody(name, data.bodyPreview, data.bodyFormat);
|
|
117
|
+
}
|
|
118
|
+
if (data.bodySrc != null) {
|
|
119
|
+
decoded.bodySrc = decodeBody(name, data.bodySrc, data.bodySrcFormat);
|
|
120
|
+
}
|
|
121
|
+
return decoded;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type NodeAuth = "none" | "peer" | "admin" | "root-admin";
|
|
125
|
+
|
|
126
|
+
export interface CarteSource {
|
|
127
|
+
getCarte: () => Promise<string>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function moeraRoot(url: string): string {
|
|
131
|
+
if (!url.startsWith("http:") && !url.startsWith("https:")) {
|
|
132
|
+
url = "https://" + url;
|
|
133
|
+
}
|
|
134
|
+
if (url.endsWith("/")) {
|
|
135
|
+
url = url.slice(0, -1);
|
|
136
|
+
}
|
|
137
|
+
if (url.endsWith("/api")) {
|
|
138
|
+
url = url.slice(0, -4);
|
|
139
|
+
}
|
|
140
|
+
if (!url.endsWith("/moera")) {
|
|
141
|
+
url += "/moera";
|
|
142
|
+
}
|
|
143
|
+
return url;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface CallOptions {
|
|
147
|
+
params?: Partial<Record<string, string | number | boolean | null>> | null
|
|
148
|
+
method: string;
|
|
149
|
+
body?: Structure | Structure[] | Buffer | null;
|
|
150
|
+
contentType?: string | null;
|
|
151
|
+
auth?: boolean;
|
|
152
|
+
schema: string;
|
|
153
|
+
bodies?: boolean;
|
|
154
|
+
srcBodies?: boolean;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export class Caller {
|
|
158
|
+
|
|
159
|
+
root: string | null = null;
|
|
160
|
+
private _rootSecret: string | null = null;
|
|
161
|
+
private _token: string | null = null;
|
|
162
|
+
private _carte: string | null = null;
|
|
163
|
+
private _carteSource: CarteSource | null = null;
|
|
164
|
+
private _authMethod: NodeAuth = "none";
|
|
165
|
+
|
|
166
|
+
nodeUrl(url: string): void {
|
|
167
|
+
this.root = moeraRoot(url);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
rootSecret(secret: string): void {
|
|
171
|
+
this._rootSecret = secret;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
token(token: string): void {
|
|
175
|
+
this._token = token
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
carte(carte: string): void {
|
|
179
|
+
this._carte = carte
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
carteSource(carteSource: CarteSource): void {
|
|
183
|
+
this._carteSource = carteSource;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
authMethod(authMethod: NodeAuth): void {
|
|
187
|
+
this._authMethod = authMethod;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
noAuth(): void {
|
|
191
|
+
this.authMethod("none");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
auth(): void {
|
|
195
|
+
this.authMethod("peer");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
authAdmin(): void {
|
|
199
|
+
this.authMethod("admin");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
authRootAdmin(): void {
|
|
203
|
+
this.authMethod("root-admin");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async call(
|
|
207
|
+
name: string,
|
|
208
|
+
location: string,
|
|
209
|
+
{
|
|
210
|
+
params = null,
|
|
211
|
+
method = "GET",
|
|
212
|
+
body = null,
|
|
213
|
+
contentType = null,
|
|
214
|
+
auth = true,
|
|
215
|
+
schema,
|
|
216
|
+
bodies = false,
|
|
217
|
+
srcBodies = false
|
|
218
|
+
}: CallOptions
|
|
219
|
+
): Promise<any> {
|
|
220
|
+
let bodyEncoded: string | null = null;
|
|
221
|
+
if (body != null) {
|
|
222
|
+
if (srcBodies) {
|
|
223
|
+
body = encodeBodies(body);
|
|
224
|
+
}
|
|
225
|
+
bodyEncoded = JSON.stringify(body);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const headers: HeadersInit = {
|
|
229
|
+
"Accept": "application/json",
|
|
230
|
+
"Content-Type": contentType ?? "application/json"
|
|
231
|
+
}
|
|
232
|
+
let bearer: string | null = null;
|
|
233
|
+
if (auth) {
|
|
234
|
+
switch (this._authMethod) {
|
|
235
|
+
case "peer":
|
|
236
|
+
if (this._carteSource != null) {
|
|
237
|
+
bearer = "carte:" + (await this._carteSource.getCarte());
|
|
238
|
+
} else if (this._carte != null) {
|
|
239
|
+
bearer = "carte:" + this._carte;
|
|
240
|
+
} else {
|
|
241
|
+
throw new MoeraCallError("Carte is not set");
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case "admin":
|
|
245
|
+
if (this._token == null) {
|
|
246
|
+
throw new MoeraCallError("Token is not set");
|
|
247
|
+
}
|
|
248
|
+
bearer = "token:" + this._token;
|
|
249
|
+
break;
|
|
250
|
+
case "root-admin":
|
|
251
|
+
if (this._rootSecret == null) {
|
|
252
|
+
throw new MoeraCallError("Root secret is not set");
|
|
253
|
+
}
|
|
254
|
+
bearer = "secret:" + this._rootSecret;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (bearer != null) {
|
|
259
|
+
headers["Authorization"] = `Bearer ${bearer}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (this.root == null) {
|
|
263
|
+
throw new MoeraCallError("Node URL is not set");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let url = urlWithParameters(this.root + "/api" + location, params);
|
|
267
|
+
let response;
|
|
268
|
+
try {
|
|
269
|
+
response = await fetch(url, {method, headers, body: bodyEncoded});
|
|
270
|
+
} catch (e) {
|
|
271
|
+
throw new MoeraNodeConnectionError(String(e));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let data: any;
|
|
275
|
+
try {
|
|
276
|
+
if (schema === "blob" && response.ok) {
|
|
277
|
+
data = await response.blob();
|
|
278
|
+
} else {
|
|
279
|
+
data = await response.json();
|
|
280
|
+
}
|
|
281
|
+
} catch (e) {
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
throw new MoeraNodeError(name, "Server returned error status");
|
|
284
|
+
} else {
|
|
285
|
+
throw new MoeraNodeError(name, "Server returned empty result");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const {valid} = validateSchema("Result", data);
|
|
291
|
+
if (!valid) {
|
|
292
|
+
throw new MoeraNodeError(name, "Server returned error status");
|
|
293
|
+
}
|
|
294
|
+
throw new MoeraNodeApiError(name, data);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (schema !== "blob") {
|
|
298
|
+
const result = validateSchema(schema, data);
|
|
299
|
+
const {valid, errors} = result;
|
|
300
|
+
if (!valid) {
|
|
301
|
+
throw new MoeraNodeError(name, "Server returned incorrect response" + formatSchemaErrors(errors));
|
|
302
|
+
}
|
|
303
|
+
if (bodies) {
|
|
304
|
+
data = decodeBodies(name, data);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CarteInfo } from "./types";
|
|
2
|
+
import { CarteSource } from "./caller";
|
|
3
|
+
import { MoeraNode } from "./node";
|
|
4
|
+
|
|
5
|
+
export class MoeraCartesError extends Error {
|
|
6
|
+
|
|
7
|
+
constructor(message: string) {
|
|
8
|
+
super(message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function isAdminCarte(carte: CarteInfo): boolean {
|
|
14
|
+
return carte.permissions == null || carte.permissions.includes("other");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class MoeraCarteSource implements CarteSource {
|
|
18
|
+
|
|
19
|
+
private node: MoeraNode;
|
|
20
|
+
private cartes: CarteInfo[] = [];
|
|
21
|
+
|
|
22
|
+
constructor(node: MoeraNode) {
|
|
23
|
+
this.node = node;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async renew(): Promise<void> {
|
|
27
|
+
this.cartes = (await this.node.getCartes()).cartes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getCarte(): Promise<string> {
|
|
31
|
+
for (const renewed of [false, true]) {
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
this.cartes = this.cartes.filter(c => c.deadline > now && isAdminCarte(c));
|
|
34
|
+
if (this.cartes.length === 0) {
|
|
35
|
+
if (renewed) {
|
|
36
|
+
throw new MoeraCartesError("Could not obtain a valid carte from the node");
|
|
37
|
+
}
|
|
38
|
+
await this.renew();
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const c of this.cartes) {
|
|
42
|
+
if (c.beginning <= now) {
|
|
43
|
+
return c.carte;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw new MoeraCartesError("Could not obtain a carte valid for now");
|
|
47
|
+
}
|
|
48
|
+
throw new MoeraCartesError("Could not obtain a carte valid for now");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|