@faasjs/http 0.0.2-beta.3 → 0.0.2-beta.319

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