@faasjs/http 0.0.2-beta.99 → 0.0.3-beta.2

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.js ADDED
@@ -0,0 +1,589 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ContentType: () => ContentType,
24
+ Cookie: () => Cookie,
25
+ Http: () => Http,
26
+ HttpError: () => HttpError,
27
+ Session: () => Session,
28
+ Validator: () => Validator,
29
+ useHttp: () => useHttp
30
+ });
31
+ module.exports = __toCommonJS(src_exports);
32
+ var import_func = require("@faasjs/func");
33
+
34
+ // ../deep_merge/src/index.ts
35
+ var shouldMerge = function(item) {
36
+ const type = Object.prototype.toString.call(item);
37
+ return type === "[object Object]" || type === "[object Array]";
38
+ };
39
+ function deepMerge(...sources) {
40
+ let acc = /* @__PURE__ */ Object.create(null);
41
+ for (const source of sources)
42
+ if (source instanceof Array) {
43
+ if (!(acc instanceof Array))
44
+ acc = [];
45
+ acc = [...new Set(source.concat(...acc))];
46
+ } else if (shouldMerge(source))
47
+ for (const [key, value] of Object.entries(source)) {
48
+ let val;
49
+ if (shouldMerge(value))
50
+ val = deepMerge(acc[key], value);
51
+ else
52
+ val = value;
53
+ acc = {
54
+ ...acc,
55
+ [key]: val
56
+ };
57
+ }
58
+ return acc;
59
+ }
60
+
61
+ // src/index.ts
62
+ var import_logger = require("@faasjs/logger");
63
+
64
+ // src/session.ts
65
+ var import_crypto = require("crypto");
66
+ var Session = class {
67
+ constructor(cookie, config) {
68
+ this.cookie = cookie;
69
+ this.config = Object.assign({
70
+ key: "key",
71
+ secret: (0, import_crypto.randomBytes)(128).toString("hex"),
72
+ salt: "salt",
73
+ signedSalt: "signedSalt",
74
+ keylen: 64,
75
+ iterations: 100,
76
+ digest: "sha256",
77
+ cipherName: "aes-256-cbc"
78
+ }, config);
79
+ this.secret = (0, import_crypto.pbkdf2Sync)(
80
+ this.config.secret,
81
+ this.config.salt,
82
+ this.config.iterations,
83
+ this.config.keylen / 2,
84
+ this.config.digest
85
+ );
86
+ this.signedSecret = (0, import_crypto.pbkdf2Sync)(
87
+ this.config.secret,
88
+ this.config.signedSalt,
89
+ this.config.iterations,
90
+ this.config.keylen,
91
+ this.config.digest
92
+ );
93
+ this.content = /* @__PURE__ */ Object.create(null);
94
+ }
95
+ invoke(cookie) {
96
+ try {
97
+ this.content = cookie ? this.decode(cookie) : /* @__PURE__ */ Object.create(null);
98
+ } catch (error) {
99
+ console.error(error);
100
+ this.content = /* @__PURE__ */ Object.create(null);
101
+ }
102
+ this.changed = false;
103
+ }
104
+ encode(text) {
105
+ if (typeof text !== "string")
106
+ text = JSON.stringify(text);
107
+ const iv = (0, import_crypto.randomBytes)(16);
108
+ const cipher = (0, import_crypto.createCipheriv)(this.config.cipherName, this.secret, iv);
109
+ const encrypted = Buffer.concat([cipher.update(text), cipher.final()]).toString("base64");
110
+ const main = Buffer.from([encrypted, iv.toString("base64")].join("--")).toString("base64");
111
+ const hmac = (0, import_crypto.createHmac)(this.config.digest, this.signedSecret);
112
+ hmac.update(main);
113
+ const digest = hmac.digest("hex");
114
+ return main + "--" + digest;
115
+ }
116
+ decode(text) {
117
+ text = decodeURIComponent(text);
118
+ const signedParts = text.split("--");
119
+ const hmac = (0, import_crypto.createHmac)(this.config.digest, this.signedSecret);
120
+ hmac.update(signedParts[0]);
121
+ const digest = hmac.digest("hex");
122
+ if (signedParts[1] !== digest)
123
+ throw Error("Not valid");
124
+ const message = Buffer.from(signedParts[0], "base64").toString();
125
+ const parts = message.split("--").map(function(part2) {
126
+ return Buffer.from(part2, "base64");
127
+ });
128
+ const cipher = (0, import_crypto.createDecipheriv)(this.config.cipherName, this.secret, parts[1]);
129
+ const part = Buffer.from(cipher.update(parts[0])).toString("utf8");
130
+ const final = cipher.final("utf8");
131
+ const decrypt = [part, final].join("");
132
+ return JSON.parse(decrypt);
133
+ }
134
+ read(key) {
135
+ return this.content[key];
136
+ }
137
+ write(key, value) {
138
+ if (value === null || typeof value === "undefined")
139
+ delete this.content[key];
140
+ else
141
+ this.content[key] = value;
142
+ this.changed = true;
143
+ return this;
144
+ }
145
+ update() {
146
+ if (this.changed)
147
+ this.cookie.write(this.config.key, this.encode(JSON.stringify(this.content)));
148
+ return this;
149
+ }
150
+ };
151
+
152
+ // src/cookie.ts
153
+ var Cookie = class {
154
+ constructor(config) {
155
+ this.config = deepMerge({
156
+ path: "/",
157
+ expires: 31536e3,
158
+ secure: true,
159
+ httpOnly: true,
160
+ session: {}
161
+ }, config);
162
+ this.session = new Session(this, this.config.session);
163
+ this.content = /* @__PURE__ */ Object.create(null);
164
+ this.setCookie = /* @__PURE__ */ Object.create(null);
165
+ }
166
+ invoke(cookie) {
167
+ this.content = /* @__PURE__ */ Object.create(null);
168
+ if (cookie)
169
+ cookie.split(";").forEach((x) => {
170
+ x = x.trim();
171
+ const k = /([^=]+)/.exec(x);
172
+ if (k !== null)
173
+ this.content[k[0]] = decodeURIComponent(x.replace(`${k[0]}=`, "").replace(/;$/, ""));
174
+ });
175
+ this.setCookie = /* @__PURE__ */ Object.create(null);
176
+ this.session.invoke(this.read(this.session.config.key));
177
+ return this;
178
+ }
179
+ read(key) {
180
+ return this.content[key];
181
+ }
182
+ write(key, value, opts) {
183
+ opts = Object.assign(this.config, opts || {});
184
+ let cookie;
185
+ if (value === null || typeof value === "undefined") {
186
+ opts.expires = "Thu, 01 Jan 1970 00:00:01 GMT";
187
+ cookie = `${key}=;`;
188
+ delete this.content[key];
189
+ } else {
190
+ cookie = `${key}=${encodeURIComponent(value)};`;
191
+ this.content[key] = value;
192
+ }
193
+ if (typeof opts.expires === "number")
194
+ cookie += `max-age=${opts.expires};`;
195
+ else if (typeof opts.expires === "string")
196
+ cookie += `expires=${opts.expires};`;
197
+ cookie += `path=${opts.path || "/"};`;
198
+ if (opts.domain)
199
+ cookie += `domain=${opts.domain};`;
200
+ if (opts.secure)
201
+ cookie += "Secure;";
202
+ if (opts.httpOnly)
203
+ cookie += "HttpOnly;";
204
+ if (opts.sameSite)
205
+ cookie += `SameSite=${opts.sameSite};`;
206
+ this.setCookie[key] = cookie;
207
+ return this;
208
+ }
209
+ headers() {
210
+ if (Object.keys(this.setCookie).length === 0)
211
+ return {};
212
+ else
213
+ return { "Set-Cookie": Object.values(this.setCookie) };
214
+ }
215
+ };
216
+
217
+ // src/validator.ts
218
+ var Validator = class {
219
+ constructor(config) {
220
+ this.paramsConfig = config.params;
221
+ this.cookieConfig = config.cookie;
222
+ this.sessionConfig = config.session;
223
+ this.before = config.before;
224
+ }
225
+ async valid(request, logger) {
226
+ if (this.before) {
227
+ const result = await this.before(request);
228
+ if (result)
229
+ throw new HttpError(result);
230
+ }
231
+ this.request = request;
232
+ if (this.paramsConfig && request.params) {
233
+ logger.debug("Valid Params");
234
+ this.validContent("params", request.params, "", this.paramsConfig, logger);
235
+ }
236
+ if (this.cookieConfig && request.cookie) {
237
+ logger.debug("Valid Cookie");
238
+ if (request.cookie == null)
239
+ throw Error("Not found Cookie");
240
+ this.validContent(
241
+ "cookie",
242
+ request.cookie.content,
243
+ "",
244
+ this.cookieConfig,
245
+ logger
246
+ );
247
+ }
248
+ if (this.sessionConfig && request.session) {
249
+ logger.debug("Valid Session");
250
+ if (request.session == null)
251
+ throw Error("Not found Session");
252
+ this.validContent(
253
+ "session",
254
+ request.session.content,
255
+ "",
256
+ this.sessionConfig,
257
+ logger
258
+ );
259
+ }
260
+ }
261
+ validContent(type, params, baseKey, config, logger) {
262
+ if (config.whitelist) {
263
+ const paramsKeys = Object.keys(params);
264
+ const rulesKeys = Object.keys(config.rules);
265
+ const diff = paramsKeys.filter((k) => !rulesKeys.includes(k));
266
+ if (diff.length > 0) {
267
+ if (config.whitelist === "error") {
268
+ const diffKeys = diff.map((k) => `${baseKey}${k}`);
269
+ const error = Error(
270
+ `[${type}] Not permitted keys: ${diffKeys.join(", ")}`
271
+ );
272
+ if (config.onError) {
273
+ const res = config.onError(`${type}.whitelist`, baseKey, diffKeys);
274
+ if (res)
275
+ throw new HttpError(res);
276
+ }
277
+ throw error;
278
+ } else if (config.whitelist === "ignore")
279
+ for (const key of diff)
280
+ delete params[key];
281
+ }
282
+ }
283
+ for (const key in config.rules) {
284
+ const rule = config.rules[key];
285
+ if (!rule)
286
+ continue;
287
+ let value = params[key];
288
+ if (rule.default) {
289
+ if (type === "cookie" || type === "session")
290
+ logger.warn("Cookie and Session not support default rule.");
291
+ else if (typeof value === "undefined" && rule.default) {
292
+ value = typeof rule.default === "function" ? rule.default(this.request) : rule.default;
293
+ params[key] = value;
294
+ }
295
+ }
296
+ if (rule.required) {
297
+ if (typeof value === "undefined" || value === null) {
298
+ const error = Error(`[${type}] ${baseKey}${key} is required.`);
299
+ if (config.onError) {
300
+ const res = config.onError(
301
+ `${type}.rule.required`,
302
+ `${baseKey}${key}`,
303
+ value
304
+ );
305
+ if (res)
306
+ throw new HttpError(res);
307
+ }
308
+ throw error;
309
+ }
310
+ }
311
+ if (typeof value !== "undefined" && value !== null) {
312
+ if (rule.type)
313
+ if (type === "cookie")
314
+ logger.warn("Cookie not support type rule");
315
+ else {
316
+ let typed = true;
317
+ switch (rule.type) {
318
+ case "array":
319
+ typed = Array.isArray(value);
320
+ break;
321
+ case "object":
322
+ typed = Object.prototype.toString.call(value) === "[object Object]";
323
+ break;
324
+ default:
325
+ typed = typeof value === rule.type;
326
+ break;
327
+ }
328
+ if (!typed) {
329
+ const error = Error(
330
+ `[${type}] ${baseKey}${key} must be a ${rule.type}.`
331
+ );
332
+ if (config.onError) {
333
+ const res = config.onError(
334
+ `${type}.rule.type`,
335
+ `${baseKey}${key}`,
336
+ value
337
+ );
338
+ if (res)
339
+ throw new HttpError(res);
340
+ }
341
+ throw error;
342
+ }
343
+ }
344
+ if (rule.regexp && (rule.type === "string" || !rule.type) && !rule.regexp.test(value)) {
345
+ const error = Error(
346
+ `[${type}] ${baseKey}${key} must match ${rule.regexp}.`
347
+ );
348
+ if (config.onError) {
349
+ const res = config.onError(
350
+ `${type}.rule.regexp`,
351
+ `${baseKey}${key}`,
352
+ value
353
+ );
354
+ if (res)
355
+ throw new HttpError(res);
356
+ }
357
+ throw error;
358
+ }
359
+ if (rule.in && !rule.in.includes(value)) {
360
+ const error = Error(
361
+ `[${type}] ${baseKey}${key} must be in ${rule.in.join(", ")}.`
362
+ );
363
+ if (config.onError) {
364
+ const res = config.onError(
365
+ `${type}.rule.in`,
366
+ `${baseKey}${key}`,
367
+ value
368
+ );
369
+ if (res)
370
+ throw new HttpError(res);
371
+ }
372
+ throw error;
373
+ }
374
+ if (rule.config) {
375
+ if (type === "cookie")
376
+ logger.warn("Cookie not support nest rule.");
377
+ else if (Array.isArray(value))
378
+ for (const val of value)
379
+ this.validContent(
380
+ type,
381
+ val,
382
+ baseKey ? `${baseKey}.${key}.` : `${key}.`,
383
+ rule.config,
384
+ logger
385
+ );
386
+ else if (typeof value === "object")
387
+ this.validContent(
388
+ type,
389
+ value,
390
+ baseKey ? `${baseKey}.${key}.` : `${key}.`,
391
+ rule.config,
392
+ logger
393
+ );
394
+ }
395
+ }
396
+ }
397
+ }
398
+ };
399
+
400
+ // src/index.ts
401
+ var import_zlib = require("zlib");
402
+ var ContentType = {
403
+ plain: "text/plain",
404
+ html: "text/html",
405
+ xml: "application/xml",
406
+ csv: "text/csv",
407
+ css: "text/css",
408
+ javascript: "application/javascript",
409
+ json: "application/json",
410
+ jsonp: "application/javascript"
411
+ };
412
+ var HttpError = class extends Error {
413
+ constructor({
414
+ statusCode,
415
+ message
416
+ }) {
417
+ super(message);
418
+ if (Error.captureStackTrace)
419
+ Error.captureStackTrace(this, HttpError);
420
+ this.statusCode = statusCode || 500;
421
+ this.message = message;
422
+ }
423
+ };
424
+ var Name = "http";
425
+ var Http = class {
426
+ constructor(config) {
427
+ this.type = Name;
428
+ this.name = Name;
429
+ this.name = (config == null ? void 0 : config.name) || this.type;
430
+ this.config = (config == null ? void 0 : config.config) || /* @__PURE__ */ Object.create(null);
431
+ if (config == null ? void 0 : config.validator)
432
+ this.validatorOptions = config.validator;
433
+ this.headers = /* @__PURE__ */ Object.create(null);
434
+ this.cookie = new Cookie(this.config.cookie || {});
435
+ this.session = this.cookie.session;
436
+ }
437
+ async onDeploy(data, next) {
438
+ var _a;
439
+ data.dependencies["@faasjs/http"] = "*";
440
+ await next();
441
+ const logger = new import_logger.Logger(this.name);
442
+ logger.debug("Generate api gateway's config");
443
+ logger.debug("%j", data);
444
+ const config = data.config.plugins ? deepMerge(data.config.plugins[this.name || this.type], { config: this.config }) : { config: this.config };
445
+ if (!config.config.path) {
446
+ config.config.path = "/" + ((_a = data.name) == null ? void 0 : _a.replace(/_/g, "/").replace(/\/index$/, ""));
447
+ if (config.config.path === "/index")
448
+ config.config.path = "/";
449
+ if (config.config.ignorePathPrefix) {
450
+ config.config.path = config.config.path.replace(new RegExp("^" + config.config.ignorePathPrefix), "");
451
+ if (config.config.path === "")
452
+ config.config.path = "/";
453
+ }
454
+ }
455
+ logger.debug("Api gateway's config: %j", config);
456
+ const Provider = require(config.provider.type).Provider;
457
+ const provider = new Provider(config.provider.config);
458
+ await provider.deploy(this.type, data, config);
459
+ }
460
+ async onMount(data, next) {
461
+ data.logger.debug("[onMount] merge config");
462
+ if (data.config.plugins && data.config.plugins[this.name || this.type])
463
+ this.config = deepMerge(this.config, data.config.plugins[this.name || this.type].config);
464
+ data.logger.debug("[onMount] prepare cookie & session");
465
+ this.cookie = new Cookie(this.config.cookie || {});
466
+ this.session = this.cookie.session;
467
+ if (this.validatorOptions) {
468
+ data.logger.debug("[onMount] prepare validator");
469
+ this.validator = new Validator(this.validatorOptions);
470
+ }
471
+ await next();
472
+ }
473
+ async onInvoke(data, next) {
474
+ var _a, _b;
475
+ this.headers = data.event.headers || /* @__PURE__ */ Object.create(null);
476
+ this.body = data.event.body;
477
+ this.params = data.event.queryString || /* @__PURE__ */ Object.create(null);
478
+ this.response = { headers: /* @__PURE__ */ Object.create(null) };
479
+ if (data.event.body) {
480
+ if ((_a = this.headers["content-type"]) == null ? void 0 : _a.includes("application/json")) {
481
+ data.logger.debug("[onInvoke] Parse params from json body");
482
+ this.params = Object.assign(this.params, JSON.parse(data.event.body));
483
+ } else {
484
+ data.logger.debug("[onInvoke] Parse params from raw body");
485
+ this.params = data.event.body || /* @__PURE__ */ Object.create(null);
486
+ }
487
+ data.logger.debug("[onInvoke] Params: %j", this.params);
488
+ }
489
+ this.cookie.invoke(this.headers.cookie);
490
+ if (this.headers.cookie) {
491
+ data.logger.debug("[onInvoke] Cookie: %j", this.cookie.content);
492
+ data.logger.debug("[onInvoke] Session: %j", this.session.content);
493
+ }
494
+ try {
495
+ if (this.validator) {
496
+ data.logger.debug("[onInvoke] Valid request");
497
+ await this.validator.valid({
498
+ headers: this.headers,
499
+ params: this.params,
500
+ cookie: this.cookie,
501
+ session: this.session
502
+ }, data.logger);
503
+ }
504
+ await next();
505
+ } catch (error) {
506
+ data.response = error;
507
+ }
508
+ this.session.update();
509
+ if (data.response)
510
+ if (data.response instanceof Error || ((_b = data.response.constructor) == null ? void 0 : _b.name) === "Error") {
511
+ data.logger.error(data.response);
512
+ this.response.body = JSON.stringify({ error: { message: data.response.message } });
513
+ try {
514
+ this.response.statusCode = data.response.statusCode || 500;
515
+ } catch (error) {
516
+ this.response.statusCode = 500;
517
+ }
518
+ } else if (Object.prototype.toString.call(data.response) === "[object Object]" && data.response.statusCode && data.response.headers)
519
+ this.response = data.response;
520
+ else
521
+ this.response.body = JSON.stringify({ data: data.response });
522
+ if (!this.response.statusCode)
523
+ this.response.statusCode = this.response.body ? 200 : 201;
524
+ this.response.headers = Object.assign({
525
+ "Content-Type": "application/json; charset=utf-8",
526
+ "Cache-Control": "no-cache, no-store"
527
+ }, this.cookie.headers(), this.response.headers);
528
+ data.response = Object.assign({}, data.response, this.response);
529
+ const originBody = data.response.body;
530
+ data.response.originBody = originBody;
531
+ if (originBody && !data.response.isBase64Encoded && typeof originBody !== "string")
532
+ data.response.body = JSON.stringify(originBody);
533
+ if (!data.response.body || data.response.isBase64Encoded || typeof data.response.body !== "string" || data.response.body.length < 1024)
534
+ return;
535
+ const acceptEncoding = this.headers["accept-encoding"] || this.headers["Accept-Encoding"];
536
+ if (!acceptEncoding || !/(br|gzip|deflate)/.test(acceptEncoding))
537
+ return;
538
+ try {
539
+ if (acceptEncoding.includes("br")) {
540
+ data.response.headers["Content-Encoding"] = "br";
541
+ data.response.body = (0, import_zlib.brotliCompressSync)(originBody).toString("base64");
542
+ } else if (acceptEncoding.includes("gzip")) {
543
+ data.response.headers["Content-Encoding"] = "gzip";
544
+ data.response.body = (0, import_zlib.gzipSync)(originBody).toString("base64");
545
+ } else if (acceptEncoding.includes("deflate")) {
546
+ data.response.headers["Content-Encoding"] = "deflate";
547
+ data.response.body = (0, import_zlib.deflateSync)(originBody).toString("base64");
548
+ } else
549
+ throw Error("No matched compression.");
550
+ data.response.isBase64Encoded = true;
551
+ } catch (error) {
552
+ console.error(error);
553
+ data.response.body = originBody;
554
+ delete data.response.headers["Content-Encoding"];
555
+ }
556
+ }
557
+ setHeader(key, value) {
558
+ this.response.headers[key] = value;
559
+ return this;
560
+ }
561
+ setContentType(type, charset = "utf-8") {
562
+ if (ContentType[type])
563
+ this.setHeader("Content-Type", `${ContentType[type]}; charset=${charset}`);
564
+ else
565
+ this.setHeader("Content-Type", `${type}; charset=${charset}`);
566
+ return this;
567
+ }
568
+ setStatusCode(code) {
569
+ this.response.statusCode = code;
570
+ return this;
571
+ }
572
+ setBody(body) {
573
+ this.response.body = body;
574
+ return this;
575
+ }
576
+ };
577
+ function useHttp(config) {
578
+ return (0, import_func.usePlugin)(new Http(config));
579
+ }
580
+ // Annotate the CommonJS export names for ESM import in node:
581
+ 0 && (module.exports = {
582
+ ContentType,
583
+ Cookie,
584
+ Http,
585
+ HttpError,
586
+ Session,
587
+ Validator,
588
+ useHttp
589
+ });