@wooksjs/event-http 0.4.26 → 0.4.28

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.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createEventContext, useEventContext, useEventId, attachHook, useEventLogger } from '@wooksjs/event-core';
2
2
  import { URLSearchParams } from 'url';
3
- import { Readable } from 'stream';
4
- import { WooksAdapterBase } from 'wooks';
5
3
  import http from 'http';
4
+ import { WooksAdapterBase } from 'wooks';
5
+ import { Readable } from 'stream';
6
6
 
7
7
  function createHttpContext(data, options) {
8
8
  return createEventContext({
@@ -17,40 +17,99 @@ function useHttpContext() {
17
17
  return useEventContext('HTTP');
18
18
  }
19
19
 
20
+ function escapeRegex(s) {
21
+ return s.replace(/[$()*+./?[\\\]^{|}-]/g, '\\$&');
22
+ }
23
+ function safeDecode(f, v) {
24
+ try {
25
+ return f(v);
26
+ }
27
+ catch (error) {
28
+ return v;
29
+ }
30
+ }
31
+ function safeDecodeURIComponent(uri) {
32
+ if (!uri.includes('%')) {
33
+ return uri;
34
+ }
35
+ return safeDecode(decodeURIComponent, uri);
36
+ }
37
+
38
+ function convertTime(time, unit = 'ms') {
39
+ if (typeof time === 'number') {
40
+ return time / units[unit];
41
+ }
42
+ const rg = /(\d+)(\w+)/g;
43
+ let t = 0;
44
+ let r;
45
+ while ((r = rg.exec(time))) {
46
+ t += Number(r[1]) * (units[r[2]] || 0);
47
+ }
48
+ return t / units[unit];
49
+ }
50
+ const units = {
51
+ ms: 1,
52
+ s: 1000,
53
+ m: 1000 * 60,
54
+ h: 1000 * 60 * 60,
55
+ d: 1000 * 60 * 60 * 24,
56
+ w: 1000 * 60 * 60 * 24 * 7,
57
+ M: 1000 * 60 * 60 * 24 * 30,
58
+ Y: 1000 * 60 * 60 * 24 * 365,
59
+ };
60
+
61
+ function renderCookie(key, data) {
62
+ let attrs = '';
63
+ for (const [a, v] of Object.entries(data.attrs)) {
64
+ const func = cookieAttrFunc[a];
65
+ if (typeof func === 'function') {
66
+ const val = func(v);
67
+ attrs += val ? `; ${val}` : '';
68
+ }
69
+ else {
70
+ throw new TypeError(`Unknown Set-Cookie attribute ${a}`);
71
+ }
72
+ }
73
+ return `${key}=${encodeURIComponent(data.value)}${attrs}`;
74
+ }
75
+ const cookieAttrFunc = {
76
+ expires: (v) => `Expires=${typeof v === 'string' || typeof v === 'number' ? new Date(v).toUTCString() : v.toUTCString()}`,
77
+ maxAge: (v) => `Max-Age=${convertTime(v, 's').toString()}`,
78
+ domain: (v) => `Domain=${v}`,
79
+ path: (v) => `Path=${v}`,
80
+ secure: (v) => (v ? 'Secure' : ''),
81
+ httpOnly: (v) => (v ? 'HttpOnly' : ''),
82
+ sameSite: (v) => v ? `SameSite=${typeof v === 'string' ? v : 'Strict'}` : '',
83
+ };
84
+
20
85
  const xForwardedFor = 'x-forwarded-for';
21
86
  function useRequest() {
22
87
  const { store } = useHttpContext();
23
88
  const { init } = store('request');
24
89
  const event = store('event');
25
- const { req } = event.value;
26
- const rawBody = () => init('rawBody', () => {
27
- return new Promise((resolve, reject) => {
28
- let body = Buffer.from('');
29
- req.on('data', function (chunk) {
30
- body = Buffer.concat([body, chunk]);
31
- });
32
- req.on('error', function (err) {
33
- reject(err);
34
- });
35
- req.on('end', function () {
36
- resolve(body);
37
- });
90
+ const req = event.get('req');
91
+ const rawBody = () => init('rawBody', () => new Promise((resolve, reject) => {
92
+ let body = Buffer.from('');
93
+ req.on('data', chunk => {
94
+ body = Buffer.concat([body, chunk]);
38
95
  });
39
- });
96
+ req.on('error', err => {
97
+ reject(err);
98
+ });
99
+ req.on('end', () => {
100
+ resolve(body);
101
+ });
102
+ }));
40
103
  const reqId = useEventId().getId;
41
104
  const forwardedIp = () => init('forwardedIp', () => {
42
- if (typeof req.headers[xForwardedFor] === 'string' &&
43
- req.headers[xForwardedFor]) {
44
- return req.headers[xForwardedFor]
45
- .split(',')
46
- .shift()
47
- ?.trim();
105
+ if (typeof req.headers[xForwardedFor] === 'string' && req.headers[xForwardedFor]) {
106
+ return req.headers[xForwardedFor].split(',').shift()?.trim();
48
107
  }
49
108
  else {
50
109
  return '';
51
110
  }
52
111
  });
53
- const remoteIp = () => init('remoteIp', () => req.socket?.remoteAddress || req.connection?.remoteAddress || '');
112
+ const remoteIp = () => init('remoteIp', () => req.socket.remoteAddress || req.connection.remoteAddress || '');
54
113
  function getIp(options) {
55
114
  if (options?.trustProxy) {
56
115
  return forwardedIp() || getIp();
@@ -59,16 +118,10 @@ function useRequest() {
59
118
  return remoteIp();
60
119
  }
61
120
  }
62
- const getIpList = () => init('ipList', () => {
63
- return {
64
- remoteIp: req.socket?.remoteAddress ||
65
- req.connection?.remoteAddress ||
66
- '',
67
- forwarded: (req.headers[xForwardedFor] || '')
68
- .split(',')
69
- .map((s) => s.trim()),
70
- };
71
- });
121
+ const getIpList = () => init('ipList', () => ({
122
+ remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || '',
123
+ forwarded: (req.headers[xForwardedFor] || '').split(',').map(s => s.trim()),
124
+ }));
72
125
  return {
73
126
  rawRequest: req,
74
127
  url: req.url,
@@ -111,13 +164,73 @@ function useSetHeader(name) {
111
164
  return hook(name);
112
165
  }
113
166
 
167
+ function useCookies() {
168
+ const { store } = useHttpContext();
169
+ const { cookie } = useHeaders();
170
+ const { init } = store('cookies');
171
+ const getCookie = (name) => init(name, () => {
172
+ if (cookie) {
173
+ const result = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, 'i').exec(cookie);
174
+ return result?.[1] ? safeDecodeURIComponent(result[1]) : null;
175
+ }
176
+ else {
177
+ return null;
178
+ }
179
+ });
180
+ return {
181
+ rawCookies: cookie,
182
+ getCookie,
183
+ };
184
+ }
185
+ function useSetCookies() {
186
+ const { store } = useHttpContext();
187
+ const cookiesStore = store('setCookies');
188
+ function setCookie(name, value, attrs) {
189
+ cookiesStore.set(name, {
190
+ value,
191
+ attrs: attrs || {},
192
+ });
193
+ }
194
+ function cookies() {
195
+ return cookiesStore
196
+ .entries()
197
+ .filter(a => !!a[1])
198
+ .map(([key, value]) => renderCookie(key, value));
199
+ }
200
+ return {
201
+ setCookie,
202
+ getCookie: cookiesStore.get,
203
+ removeCookie: cookiesStore.del,
204
+ clearCookies: cookiesStore.clear,
205
+ cookies,
206
+ };
207
+ }
208
+ function useSetCookie(name) {
209
+ const { setCookie, getCookie } = useSetCookies();
210
+ const valueHook = attachHook({
211
+ name,
212
+ type: 'cookie',
213
+ }, {
214
+ get: () => getCookie(name)?.value,
215
+ set: (value) => {
216
+ setCookie(name, value, getCookie(name)?.attrs);
217
+ },
218
+ });
219
+ return attachHook(valueHook, {
220
+ get: () => getCookie(name)?.attrs,
221
+ set: (attrs) => {
222
+ setCookie(name, getCookie(name)?.value || '', attrs);
223
+ },
224
+ }, 'attrs');
225
+ }
226
+
114
227
  function useAccept() {
115
228
  const { store } = useHttpContext();
116
229
  const { accept } = useHeaders();
117
230
  const accepts = (mime) => {
118
231
  const { set, get, has } = store('accept');
119
232
  if (!has(mime)) {
120
- return set(mime, !!(accept && (accept === '*/*' || accept.indexOf(mime) >= 0)));
233
+ return set(mime, !!(accept && (accept === '*/*' || accept.includes(mime))));
121
234
  }
122
235
  return get(mime);
123
236
  };
@@ -169,62 +282,39 @@ function useAuthorization() {
169
282
  };
170
283
  }
171
284
 
172
- function convertTime(time, unit = 'ms') {
173
- if (typeof time === 'number')
174
- return time / units[unit];
175
- const rg = /(\d+)(\w+)/g;
176
- let t = 0;
177
- let r;
178
- while ((r = rg.exec(time))) {
179
- t += Number(r[1]) * (units[r[2]] || 0);
180
- }
181
- return t / units[unit];
182
- }
183
- const units = {
184
- ms: 1,
185
- s: 1000,
186
- m: 1000 * 60,
187
- h: 1000 * 60 * 60,
188
- d: 1000 * 60 * 60 * 24,
189
- w: 1000 * 60 * 60 * 24 * 7,
190
- M: 1000 * 60 * 60 * 24 * 30,
191
- Y: 1000 * 60 * 60 * 24 * 365,
192
- };
193
-
194
285
  function renderCacheControl(data) {
195
286
  let attrs = '';
196
287
  for (const [a, v] of Object.entries(data)) {
197
- if (typeof v === 'undefined')
288
+ if (v === undefined) {
198
289
  continue;
290
+ }
199
291
  const func = cacheControlFunc[a];
200
292
  if (typeof func === 'function') {
201
293
  const val = func(v);
202
294
  if (val) {
203
- attrs += attrs ? ', ' + val : val;
295
+ attrs += attrs ? `, ${val}` : val;
204
296
  }
205
297
  }
206
298
  else {
207
- throw new Error('Unknown Cache-Control attribute ' + a);
299
+ throw new TypeError(`Unknown Cache-Control attribute ${a}`);
208
300
  }
209
301
  }
210
302
  return attrs;
211
303
  }
212
304
  const cacheControlFunc = {
213
- mustRevalidate: (v) => v ? 'must-revalidate' : '',
305
+ mustRevalidate: (v) => (v ? 'must-revalidate' : ''),
214
306
  noCache: (v) => v ? (typeof v === 'string' ? `no-cache="${v}"` : 'no-cache') : '',
215
307
  noStore: (v) => (v ? 'no-store' : ''),
216
308
  noTransform: (v) => (v ? 'no-transform' : ''),
217
309
  public: (v) => (v ? 'public' : ''),
218
310
  private: (v) => v ? (typeof v === 'string' ? `private="${v}"` : 'private') : '',
219
- proxyRevalidate: (v) => v ? 'proxy-revalidate' : '',
220
- maxAge: (v) => 'max-age=' + convertTime(v, 's').toString(),
221
- sMaxage: (v) => 's-maxage=' + convertTime(v, 's').toString(),
311
+ proxyRevalidate: (v) => (v ? 'proxy-revalidate' : ''),
312
+ maxAge: (v) => `max-age=${convertTime(v, 's').toString()}`,
313
+ sMaxage: (v) => `s-maxage=${convertTime(v, 's').toString()}`,
222
314
  };
223
315
 
224
316
  const renderAge = (v) => convertTime(v, 's').toString();
225
- const renderExpires = (v) => typeof v === 'string' || typeof v === 'number'
226
- ? new Date(v).toUTCString()
227
- : v.toUTCString();
317
+ const renderExpires = (v) => typeof v === 'string' || typeof v === 'number' ? new Date(v).toUTCString() : v.toUTCString();
228
318
  const renderPragmaNoCache = (v) => (v ? 'no-cache' : '');
229
319
  function useSetCacheControl() {
230
320
  const { setHeader } = useSetHeaders();
@@ -251,15 +341,16 @@ function useSetCacheControl() {
251
341
  function useResponse() {
252
342
  const { store } = useHttpContext();
253
343
  const event = store('event');
254
- const { res } = event.value;
344
+ const res = event.get('res');
255
345
  const responded = store('response').hook('responded');
256
346
  const statusCode = store('status').hook('code');
257
347
  function status(code) {
258
348
  return (statusCode.value = code ? code : statusCode.value);
259
349
  }
260
350
  const rawResponse = (options) => {
261
- if (!options || !options.passthrough)
351
+ if (!options || !options.passthrough) {
262
352
  responded.value = true;
353
+ }
263
354
  return res;
264
355
  };
265
356
  return {
@@ -276,108 +367,6 @@ function useStatus() {
276
367
  return store('status').hook('code');
277
368
  }
278
369
 
279
- function renderCookie(key, data) {
280
- let attrs = '';
281
- for (const [a, v] of Object.entries(data.attrs)) {
282
- const func = cookieAttrFunc[a];
283
- if (typeof func === 'function') {
284
- const val = func(v);
285
- attrs += val ? '; ' + val : '';
286
- }
287
- else {
288
- throw new Error('Unknown Set-Cookie attribute ' + a);
289
- }
290
- }
291
- return `${key}=${encodeURIComponent(data.value)}${attrs}`;
292
- }
293
- const cookieAttrFunc = {
294
- expires: (v) => 'Expires=' +
295
- (typeof v === 'string' || typeof v === 'number'
296
- ? new Date(v).toUTCString()
297
- : v.toUTCString()),
298
- maxAge: (v) => 'Max-Age=' + convertTime(v, 's').toString(),
299
- domain: (v) => 'Domain=' + v,
300
- path: (v) => 'Path=' + v,
301
- secure: (v) => (v ? 'Secure' : ''),
302
- httpOnly: (v) => (v ? 'HttpOnly' : ''),
303
- sameSite: (v) => v ? 'SameSite=' + (typeof v === 'string' ? v : 'Strict') : '',
304
- };
305
-
306
- function escapeRegex(s) {
307
- return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
308
- }
309
- function safeDecode(f, v) {
310
- try {
311
- return f(v);
312
- }
313
- catch (e) {
314
- return v;
315
- }
316
- }
317
- function safeDecodeURIComponent(uri) {
318
- if (uri.indexOf('%') < 0)
319
- return uri;
320
- return safeDecode(decodeURIComponent, uri);
321
- }
322
-
323
- function useCookies() {
324
- const { store } = useHttpContext();
325
- const { cookie } = useHeaders();
326
- const { init } = store('cookies');
327
- const getCookie = (name) => init(name, () => {
328
- if (cookie) {
329
- const result = new RegExp(`(?:^|; )${escapeRegex(name)}=(.*?)(?:;?$|; )`, 'i').exec(cookie);
330
- return result && result[1]
331
- ? safeDecodeURIComponent(result[1])
332
- : null;
333
- }
334
- else {
335
- return null;
336
- }
337
- });
338
- return {
339
- rawCookies: cookie,
340
- getCookie,
341
- };
342
- }
343
- function useSetCookies() {
344
- const { store } = useHttpContext();
345
- const cookiesStore = store('setCookies');
346
- function setCookie(name, value, attrs) {
347
- cookiesStore.set(name, {
348
- value,
349
- attrs: attrs || {},
350
- });
351
- }
352
- function cookies() {
353
- return cookiesStore
354
- .entries()
355
- .filter((a) => !!a[1])
356
- .map(([key, value]) => renderCookie(key, value));
357
- }
358
- return {
359
- setCookie,
360
- getCookie: cookiesStore.get,
361
- removeCookie: cookiesStore.del,
362
- clearCookies: cookiesStore.clear,
363
- cookies,
364
- };
365
- }
366
- function useSetCookie(name) {
367
- const { setCookie, getCookie } = useSetCookies();
368
- const valueHook = attachHook({
369
- name,
370
- type: 'cookie',
371
- }, {
372
- get: () => getCookie(name)?.value,
373
- set: (value) => setCookie(name, value, getCookie(name)?.attrs),
374
- });
375
- return attachHook(valueHook, {
376
- get: () => getCookie(name)?.attrs,
377
- set: (attrs) => setCookie(name, getCookie(name)?.value || '', attrs),
378
- }, 'attrs');
379
- }
380
-
381
370
  class WooksURLSearchParams extends URLSearchParams {
382
371
  toJson() {
383
372
  const json = {};
@@ -418,89 +407,91 @@ class BaseHttpResponseRenderer {
418
407
  if (typeof response.body === 'string' ||
419
408
  typeof response.body === 'boolean' ||
420
409
  typeof response.body === 'number') {
421
- if (!response.getContentType())
410
+ if (!response.getContentType()) {
422
411
  response.setContentType('text/plain');
412
+ }
423
413
  return response.body.toString();
424
414
  }
425
- if (typeof response.body === 'undefined') {
415
+ if (response.body === undefined) {
426
416
  return '';
427
417
  }
428
418
  if (response.body instanceof Uint8Array) {
429
419
  return response.body;
430
420
  }
431
421
  if (typeof response.body === 'object') {
432
- if (!response.getContentType())
422
+ if (!response.getContentType()) {
433
423
  response.setContentType('application/json');
424
+ }
434
425
  return JSON.stringify(response.body);
435
426
  }
436
- throw new Error('Unsupported body format "' + typeof response.body + '"');
427
+ throw new Error(`Unsupported body format "${typeof response.body}"`);
437
428
  }
438
429
  }
439
430
 
440
431
  const httpStatusCodes = {
441
- [100]: 'Continue',
442
- [101]: 'Switching protocols',
443
- [102]: 'Processing',
444
- [103]: 'Early Hints',
445
- [200]: 'OK',
446
- [201]: 'Created',
447
- [202]: 'Accepted',
448
- [203]: 'Non-Authoritative Information',
449
- [204]: 'No Content',
450
- [205]: 'Reset Content',
451
- [206]: 'Partial Content',
452
- [207]: 'Multi-Status',
453
- [208]: 'Already Reported',
454
- [226]: 'IM Used',
455
- [300]: 'Multiple Choices',
456
- [301]: 'Moved Permanently',
457
- [302]: 'Found (Previously "Moved Temporarily")',
458
- [303]: 'See Other',
459
- [304]: 'Not Modified',
460
- [305]: 'Use Proxy',
461
- [306]: 'Switch Proxy',
462
- [307]: 'Temporary Redirect',
463
- [308]: 'Permanent Redirect',
464
- [400]: 'Bad Request',
465
- [401]: 'Unauthorized',
466
- [402]: 'Payment Required',
467
- [403]: 'Forbidden',
468
- [404]: 'Not Found',
469
- [405]: 'Method Not Allowed',
470
- [406]: 'Not Acceptable',
471
- [407]: 'Proxy Authentication Required',
472
- [408]: 'Request Timeout',
473
- [409]: 'Conflict',
474
- [410]: 'Gone',
475
- [411]: 'Length Required',
476
- [412]: 'Precondition Failed',
477
- [413]: 'Payload Too Large',
478
- [414]: 'URI Too Long',
479
- [415]: 'Unsupported Media Type',
480
- [416]: 'Range Not Satisfiable',
481
- [417]: 'Expectation Failed',
482
- [418]: 'I\'m a Teapot',
483
- [421]: 'Misdirected Request',
484
- [422]: 'Unprocessable Entity',
485
- [423]: 'Locked',
486
- [424]: 'Failed Dependency',
487
- [425]: 'Too Early',
488
- [426]: 'Upgrade Required',
489
- [428]: 'Precondition Required',
490
- [429]: 'Too Many Requests',
491
- [431]: 'Request Header Fields Too Large',
492
- [451]: 'Unavailable For Legal Reasons',
493
- [500]: 'Internal Server Error',
494
- [501]: 'Not Implemented',
495
- [502]: 'Bad Gateway',
496
- [503]: 'Service Unavailable',
497
- [504]: 'Gateway Timeout',
498
- [505]: 'HTTP Version Not Supported',
499
- [506]: 'Variant Also Negotiates',
500
- [507]: 'Insufficient Storage',
501
- [508]: 'Loop Detected',
502
- [510]: 'Not Extended',
503
- [511]: 'Network Authentication Required',
432
+ 100: 'Continue',
433
+ 101: 'Switching protocols',
434
+ 102: 'Processing',
435
+ 103: 'Early Hints',
436
+ 200: 'OK',
437
+ 201: 'Created',
438
+ 202: 'Accepted',
439
+ 203: 'Non-Authoritative Information',
440
+ 204: 'No Content',
441
+ 205: 'Reset Content',
442
+ 206: 'Partial Content',
443
+ 207: 'Multi-Status',
444
+ 208: 'Already Reported',
445
+ 226: 'IM Used',
446
+ 300: 'Multiple Choices',
447
+ 301: 'Moved Permanently',
448
+ 302: 'Found (Previously "Moved Temporarily")',
449
+ 303: 'See Other',
450
+ 304: 'Not Modified',
451
+ 305: 'Use Proxy',
452
+ 306: 'Switch Proxy',
453
+ 307: 'Temporary Redirect',
454
+ 308: 'Permanent Redirect',
455
+ 400: 'Bad Request',
456
+ 401: 'Unauthorized',
457
+ 402: 'Payment Required',
458
+ 403: 'Forbidden',
459
+ 404: 'Not Found',
460
+ 405: 'Method Not Allowed',
461
+ 406: 'Not Acceptable',
462
+ 407: 'Proxy Authentication Required',
463
+ 408: 'Request Timeout',
464
+ 409: 'Conflict',
465
+ 410: 'Gone',
466
+ 411: 'Length Required',
467
+ 412: 'Precondition Failed',
468
+ 413: 'Payload Too Large',
469
+ 414: 'URI Too Long',
470
+ 415: 'Unsupported Media Type',
471
+ 416: 'Range Not Satisfiable',
472
+ 417: 'Expectation Failed',
473
+ 418: "I'm a Teapot",
474
+ 421: 'Misdirected Request',
475
+ 422: 'Unprocessable Entity',
476
+ 423: 'Locked',
477
+ 424: 'Failed Dependency',
478
+ 425: 'Too Early',
479
+ 426: 'Upgrade Required',
480
+ 428: 'Precondition Required',
481
+ 429: 'Too Many Requests',
482
+ 431: 'Request Header Fields Too Large',
483
+ 451: 'Unavailable For Legal Reasons',
484
+ 500: 'Internal Server Error',
485
+ 501: 'Not Implemented',
486
+ 502: 'Bad Gateway',
487
+ 503: 'Service Unavailable',
488
+ 504: 'Gateway Timeout',
489
+ 505: 'HTTP Version Not Supported',
490
+ 506: 'Variant Also Negotiates',
491
+ 507: 'Insufficient Storage',
492
+ 508: 'Loop Detected',
493
+ 510: 'Not Extended',
494
+ 511: 'Network Authentication Required',
504
495
  };
505
496
  var EHttpStatusCode;
506
497
  (function (EHttpStatusCode) {
@@ -569,6 +560,109 @@ var EHttpStatusCode;
569
560
  EHttpStatusCode[EHttpStatusCode["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
570
561
  })(EHttpStatusCode || (EHttpStatusCode = {}));
571
562
 
563
+ const preStyles = 'font-family: monospace;' +
564
+ 'width: 100%;' +
565
+ 'max-width: 900px;' +
566
+ 'padding: 10px;' +
567
+ 'margin: 20px auto;' +
568
+ 'border-radius: 8px;' +
569
+ 'background-color: #494949;' +
570
+ 'box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);';
571
+ class HttpErrorRenderer extends BaseHttpResponseRenderer {
572
+ renderHtml(response) {
573
+ const data = response.body || {};
574
+ response.setContentType('text/html');
575
+ const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
576
+ return ('<html style="background-color: #333; color: #bbb;">' +
577
+ `<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` +
578
+ `<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` +
579
+ `<center><h4>${data.message}</h1></center><hr color="#666">` +
580
+ `<center style="color: #666;"> Wooks v${"0.4.28"} </center>` +
581
+ `${keys.length > 0
582
+ ? `<pre style="${preStyles}">${JSON.stringify({
583
+ ...data,
584
+ statusCode: undefined,
585
+ message: undefined,
586
+ error: undefined,
587
+ }, null, ' ')}</pre>`
588
+ : ''}` +
589
+ '</body></html>');
590
+ }
591
+ renderText(response) {
592
+ const data = response.body || {};
593
+ response.setContentType('text/plain');
594
+ const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
595
+ return (`${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` +
596
+ `\n\n${keys.length > 0
597
+ ? `${JSON.stringify({
598
+ ...data,
599
+ statusCode: undefined,
600
+ message: undefined,
601
+ error: undefined,
602
+ }, null, ' ')}`
603
+ : ''}`);
604
+ }
605
+ renderJson(response) {
606
+ const data = response.body || {};
607
+ response.setContentType('application/json');
608
+ const keys = Object.keys(data).filter(key => !['statusCode', 'error', 'message'].includes(key));
609
+ return (`{"statusCode":${escapeQuotes(data.statusCode)},` +
610
+ `"error":"${escapeQuotes(data.error)}",` +
611
+ `"message":"${escapeQuotes(data.message)}"` +
612
+ `${keys.length > 0
613
+ ? `,${keys.map(k => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`).join(',')}`
614
+ : ''}}`);
615
+ }
616
+ render(response) {
617
+ const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
618
+ response.status = response.body?.statusCode || 500;
619
+ if (acceptsJson()) {
620
+ return this.renderJson(response);
621
+ }
622
+ else if (acceptsHtml()) {
623
+ return this.renderHtml(response);
624
+ }
625
+ else if (acceptsText()) {
626
+ return this.renderText(response);
627
+ }
628
+ else {
629
+ return this.renderJson(response);
630
+ }
631
+ }
632
+ }
633
+ function escapeQuotes(s) {
634
+ return (typeof s === 'number' ? s : s || '').toString().replace(/"/g, '\\"');
635
+ }
636
+
637
+ class HttpError extends Error {
638
+ constructor(code = 500, _body = '') {
639
+ super(typeof _body === 'string' ? _body : _body.message);
640
+ this.code = code;
641
+ this._body = _body;
642
+ this.name = 'HttpError';
643
+ }
644
+ get body() {
645
+ return typeof this._body === 'string'
646
+ ? {
647
+ statusCode: this.code,
648
+ message: this.message,
649
+ error: httpStatusCodes[this.code],
650
+ }
651
+ : {
652
+ ...this._body,
653
+ statusCode: this.code,
654
+ message: this.message,
655
+ error: httpStatusCodes[this.code],
656
+ };
657
+ }
658
+ attachRenderer(renderer) {
659
+ this.renderer = renderer;
660
+ }
661
+ getRenderer() {
662
+ return this.renderer;
663
+ }
664
+ }
665
+
572
666
  const defaultStatus = {
573
667
  GET: EHttpStatusCode.OK,
574
668
  POST: EHttpStatusCode.Created,
@@ -649,7 +743,7 @@ class BaseHttpResponse {
649
743
  ...this._headers,
650
744
  };
651
745
  const setCookie = [...newCookies, ...cookies()];
652
- if (setCookie && setCookie.length) {
746
+ if (setCookie.length > 0) {
653
747
  this._headers['set-cookie'] = setCookie;
654
748
  }
655
749
  return this;
@@ -675,7 +769,7 @@ class BaseHttpResponse {
675
769
  async respond() {
676
770
  const { rawResponse, hasResponded } = useResponse();
677
771
  const { method, rawRequest } = useRequest();
678
- const logger = useEventLogger('http-response');
772
+ const logger = useEventLogger('http-response') || console;
679
773
  if (hasResponded()) {
680
774
  this.panic('The response was already sent.', logger);
681
775
  }
@@ -696,7 +790,7 @@ class BaseHttpResponse {
696
790
  }
697
791
  else {
698
792
  return new Promise((resolve, reject) => {
699
- stream.on('error', (e) => {
793
+ stream.on('error', e => {
700
794
  stream.destroy();
701
795
  res.end();
702
796
  reject(e);
@@ -709,8 +803,7 @@ class BaseHttpResponse {
709
803
  });
710
804
  }
711
805
  }
712
- else if (globalThis.Response &&
713
- this.body instanceof Response) {
806
+ else if (globalThis.Response && this.body instanceof Response) {
714
807
  this.mergeFetchStatus(this.body.status);
715
808
  if (method === 'HEAD') {
716
809
  res.end();
@@ -733,10 +826,12 @@ class BaseHttpResponse {
733
826
  else {
734
827
  const renderedBody = this.renderer.render(this);
735
828
  this.mergeStatus(renderedBody);
736
- res.writeHead(this.status, {
829
+ res
830
+ .writeHead(this.status, {
737
831
  'content-length': Buffer.byteLength(renderedBody),
738
832
  ...this._headers,
739
- }).end(method !== 'HEAD' ? renderedBody : '');
833
+ })
834
+ .end(method === 'HEAD' ? '' : renderedBody);
740
835
  }
741
836
  }
742
837
  }
@@ -747,124 +842,18 @@ async function respondWithFetch(fetchBody, res) {
747
842
  res.write(chunk);
748
843
  }
749
844
  }
750
- catch (e) {
845
+ catch (error) {
751
846
  }
752
847
  }
753
848
  res.end();
754
849
  }
755
850
 
756
- const preStyles = 'font-family: monospace;' +
757
- 'width: 100%;' +
758
- 'max-width: 900px;' +
759
- 'padding: 10px;' +
760
- 'margin: 20px auto;' +
761
- 'border-radius: 8px;' +
762
- 'background-color: #494949;' +
763
- 'box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);';
764
- class HttpErrorRenderer extends BaseHttpResponseRenderer {
765
- renderHtml(response) {
766
- const data = response.body || {};
767
- response.setContentType('text/html');
768
- const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
769
- return ('<html style="background-color: #333; color: #bbb;">' +
770
- `<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` +
771
- `<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` +
772
- `<center><h4>${data.message}</h1></center><hr color="#666">` +
773
- `<center style="color: #666;"> Wooks v${"0.4.26"} </center>` +
774
- `${keys.length
775
- ? `<pre style="${preStyles}">${JSON.stringify({
776
- ...data,
777
- statusCode: undefined,
778
- message: undefined,
779
- error: undefined,
780
- }, null, ' ')}</pre>`
781
- : ''}` +
782
- '</body></html>');
783
- }
784
- renderText(response) {
785
- const data = response.body || {};
786
- response.setContentType('text/plain');
787
- const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
788
- return (`${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` +
789
- `\n\n${keys.length
790
- ? `${JSON.stringify({
791
- ...data,
792
- statusCode: undefined,
793
- message: undefined,
794
- error: undefined,
795
- }, null, ' ')}`
796
- : ''}`);
797
- }
798
- renderJson(response) {
799
- const data = response.body || {};
800
- response.setContentType('application/json');
801
- const keys = Object.keys(data).filter((key) => !['statusCode', 'error', 'message'].includes(key));
802
- return (`{"statusCode":${escapeQuotes(data.statusCode)},` +
803
- `"error":"${escapeQuotes(data.error)}",` +
804
- `"message":"${escapeQuotes(data.message)}"` +
805
- `${keys.length
806
- ? ',' +
807
- keys
808
- .map((k) => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`)
809
- .join(',')
810
- : ''}}`);
811
- }
812
- render(response) {
813
- const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
814
- response.status = response.body?.statusCode || 500;
815
- if (acceptsJson()) {
816
- return this.renderJson(response);
817
- }
818
- else if (acceptsHtml()) {
819
- return this.renderHtml(response);
820
- }
821
- else if (acceptsText()) {
822
- return this.renderText(response);
823
- }
824
- else {
825
- return this.renderJson(response);
826
- }
827
- }
828
- }
829
- function escapeQuotes(s) {
830
- return (typeof s === 'number' ? s : s || '')
831
- .toString()
832
- .replace(/[\""]/g, '\\"');
833
- }
834
-
835
- class HttpError extends Error {
836
- constructor(code = 500, _body = '') {
837
- super(typeof _body === 'string' ? _body : _body.message);
838
- this.code = code;
839
- this._body = _body;
840
- }
841
- get body() {
842
- return typeof this._body === 'string'
843
- ? {
844
- statusCode: this.code,
845
- message: this.message,
846
- error: httpStatusCodes[this.code],
847
- }
848
- : {
849
- ...this._body,
850
- statusCode: this.code,
851
- message: this.message,
852
- error: httpStatusCodes[this.code],
853
- };
854
- }
855
- attachRenderer(renderer) {
856
- this.renderer = renderer;
857
- }
858
- getRenderer() {
859
- return this.renderer;
860
- }
861
- }
862
-
863
851
  function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRenderer = new HttpErrorRenderer()) {
864
852
  function createResponse(data) {
865
853
  const { hasResponded } = useResponse();
866
- if (hasResponded())
854
+ if (hasResponded()) {
867
855
  return null;
856
+ }
868
857
  if (data instanceof Error) {
869
858
  const r = new BaseHttpResponse(errorRenderer);
870
859
  let httpError;
@@ -921,20 +910,22 @@ class WooksHttp extends WooksAdapterBase {
921
910
  options(path, handler) {
922
911
  return this.on('OPTIONS', path, handler);
923
912
  }
924
- async listen(...args) {
913
+ async listen(port, hostname, backlog, listeningListener) {
925
914
  const server = (this.server = http.createServer(this.getServerCb()));
926
915
  return new Promise((resolve, reject) => {
927
916
  server.once('listening', resolve);
928
917
  server.once('error', reject);
929
- server.listen(...args);
918
+ server.listen(port, hostname, backlog, listeningListener);
930
919
  });
931
920
  }
932
921
  close(server) {
933
922
  const srv = server || this.server;
934
923
  return new Promise((resolve, reject) => {
935
- srv?.close((err) => {
936
- if (err)
937
- return reject(err);
924
+ srv?.close(err => {
925
+ if (err) {
926
+ reject(err);
927
+ return;
928
+ }
938
929
  resolve(srv);
939
930
  });
940
931
  });
@@ -946,7 +937,7 @@ class WooksHttp extends WooksAdapterBase {
946
937
  this.server = server;
947
938
  }
948
939
  respond(data) {
949
- void this.responder.respond(data)?.catch((e) => {
940
+ void this.responder.respond(data)?.catch(e => {
950
941
  this.logger.error('Uncought response exception', e);
951
942
  });
952
943
  }
@@ -958,10 +949,10 @@ class WooksHttp extends WooksAdapterBase {
958
949
  try {
959
950
  await this.processHandlers(handlers || [this.opts?.onNotFound]);
960
951
  }
961
- catch (e) {
962
- this.logger.error('Internal error, please report', e);
952
+ catch (error) {
953
+ this.logger.error('Internal error, please report', error);
963
954
  restoreCtx();
964
- this.respond(e);
955
+ this.respond(error);
965
956
  clearCtx();
966
957
  }
967
958
  }
@@ -986,11 +977,11 @@ class WooksHttp extends WooksAdapterBase {
986
977
  clearCtx();
987
978
  break;
988
979
  }
989
- catch (e) {
990
- this.logger.error(`Uncought route handler exception: ${store('event').get('req').url || ''}`, e);
980
+ catch (error) {
981
+ this.logger.error(`Uncought route handler exception: ${store('event').get('req')?.url || ''}`, error);
991
982
  if (isLastHandler) {
992
983
  restoreCtx();
993
- this.respond(e);
984
+ this.respond(error);
994
985
  clearCtx();
995
986
  }
996
987
  }