@trenskow/app 0.9.31 → 0.9.32

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/README.md CHANGED
@@ -248,7 +248,6 @@ When a request is incoming, the `context` object looks like this.
248
248
  | `path.current` | An array of strings that joined represents the path currently being processed. | Array of String |
249
249
  | `path.remaining` | An array of strings that joined represents the path that is above the currently processed path. Setting this will rewrite the remaining path (useful when serving single page applications to a browser). | Array of String |
250
250
  | `query` | An object holding the URL query parameters as an object ([keys has been converted to camel case](#query-parameters)). | Object |
251
- | `cookies` | An mutable object with cookie values. | Object |
252
251
  | `state` | A string indicating the current state of the request – possible values are `'routing'`, `'rendering'`, `'completed'` or `'aborted'`. | String |
253
252
  | `abort` | A function that aborts the request. It takes the parameters `(error, brutally)`, where `error` is the error that needs to be handled by the [renderer](#renderer) – and `brutally` which indicates if the connection should also be closed. | AsyncFunction |
254
253
  | `render` | A function that tells the application to stop processing the request and jump directly to the [renderer](#renderer). | Function |
@@ -277,11 +276,7 @@ The same goes for request headers like `Accept-Language: en` which is accessible
277
276
 
278
277
  ##### Query parameters
279
278
 
280
- Request with queries like `?my-parameter=value` is accessible through `context.query.myParameter` .
281
-
282
- ##### Cookies
283
-
284
- Cookies are available through `context.cookies.myCookieKey` and are passed to the client using `camel` (can be changed using the [`Application` options](#constructor)).
279
+ Request with quuries like `?my-parameter=value` is accessible through `context.query.myParameter` .
285
280
 
286
281
  ##### Mount paths
287
282
 
@@ -361,13 +356,11 @@ The `Application` class takes an "options" object as it's parameter.
361
356
  | `options.port` | The port at which to listen for incoming connections. | Number | | `0` (automatically assigned) |
362
357
  | `options.RequestType` | An object that inherits from the [`Request`](#request-2) class (an `http.IncomingMessage` subclass) that is used as the request object in routes. | class | | [`Request`](#Request) |
363
358
  | `options.ResponseType` | An object that inherits from the [`Response`](#response-2) class (`http.ServerResponse` subclass) that is used as the response object in routes. | class | | [`Response`](#Response) |
364
- | `options.path` | An object that represents path related options. | Object | | `{}` |
365
- | `options.path.matchMode` | Indicates [how to match requests to mounted paths](#mount-paths) (eg. should the path be converted to camel case). | `'loosely'` or `'strict'` | | `'loosely'` |
359
+ | `path` | An object that represents path related options. | Object | | `{}` |
360
+ | `path.matchMode` | Indicates [how to match requests to mounted paths](#mount-paths) (eg. should the path be converted to camel case). | `'loosely'` or `'strict'` | | `'loosely'` |
366
361
  | `options.server` | An object that represents how to instantiate the HTTP server. | Object | | `{}` |
367
362
  | `options.server.create` | A function that is able to create a server. | Function | | `http.createServer` |
368
363
  | `options.server.options` | An object to be passed as options when creating a server. | Object | | `{}` |
369
- | `options.casing` | An object that represents casing options. | Object | | {} |
370
- | `options.casing.cookies` | Client side cookie key value casing (available as in [@trenskow/caseit](https://github.com/trenskow/caseit)). | String | | `camel` |
371
364
 
372
365
  #### Events
373
366
 
@@ -16,7 +16,6 @@ import caseit from '@trenskow/caseit';
16
16
  import Request from './request.js';
17
17
  import Response from './response.js';
18
18
  import Endpoint from './endpoint.js';
19
- import Cookies from './cookies.js';
20
19
 
21
20
  import { isObject } from './util/index.js';
22
21
 
@@ -26,7 +25,6 @@ export default class Application extends EventEmitter {
26
25
  #_path;
27
26
  #_state;
28
27
  #_server;
29
- #_casing;
30
28
  #_rootEndpoint;
31
29
  #_renderer;
32
30
 
@@ -43,10 +41,7 @@ export default class Application extends EventEmitter {
43
41
  server = Object.assign({
44
42
  create: http.createServer,
45
43
  options: {}
46
- }, options.server || {}),
47
- casing = Object.assign({
48
- cookies: 'camel'
49
- }, options.casing || {})
44
+ }, options.server || {})
50
45
  } = options;
51
46
 
52
47
  this.#_port = port;
@@ -67,8 +62,6 @@ export default class Application extends EventEmitter {
67
62
  this.#_onIncomingRequest(req, res);
68
63
  });
69
64
 
70
- this.#_casing = casing;
71
-
72
65
  this.#_rootEndpoint = new Endpoint()
73
66
  .use(() => { throw new ApiError.NotFound(); });
74
67
 
@@ -321,13 +314,6 @@ export default class Application extends EventEmitter {
321
314
  enumerable: true
322
315
  });
323
316
 
324
- const cookies = new Cookies(context, this.#_casing.cookies);
325
-
326
- Object.defineProperty(context, 'cookies', {
327
- get: () => cookies._proxy,
328
- enumerable: true
329
- });
330
-
331
317
  const listeners = {
332
318
  eventEmitters: [
333
319
  [request.socket, ['end', 'error']],
@@ -384,8 +370,6 @@ export default class Application extends EventEmitter {
384
370
 
385
371
  }
386
372
 
387
- cookies._render();
388
-
389
373
  if (!['routing', 'rendering'].includes(state)) return response.end();
390
374
 
391
375
  await render();
package/lib/request.js CHANGED
@@ -28,18 +28,6 @@ export default class Request extends IncomingMessage {
28
28
 
29
29
  }
30
30
 
31
- get host() {
32
- return this.headers.xForwardedHost || super.host || this.socket.localAddress;
33
- }
34
-
35
- get protocol() {
36
- return this.headers.xForwardedProto || super.protocol || (this.socket.encrypted ? 'https' : 'http');
37
- }
38
-
39
- get port() {
40
- return this.headers.xForwardedPort || this.socket.localPort || (super.protocol === 'https' ? 443 : 80);
41
- }
42
-
43
31
  get origin() {
44
32
  return (this.headers.xForwardedFor || this.socket.remoteAddress || '').split(/, ?/);
45
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trenskow/app",
3
- "version": "0.9.31",
3
+ "version": "0.9.32",
4
4
  "description": "A small HTTP router.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/test/index.js CHANGED
@@ -439,83 +439,6 @@ describe('Application', () => {
439
439
 
440
440
  });
441
441
 
442
- it ('should come back with header to set cookie.', async () => {
443
-
444
- app.root(
445
- new Endpoint()
446
- .get(({ cookies }) => {
447
- cookies['testValue'] = 'Hello, World!';
448
- return 'Hello, World!';
449
- })
450
- );
451
-
452
- await request
453
- .get('/')
454
- .expect('Set-Cookie', 'testValue=Hello%2C%20World!; Path=/; Domain=::1; Secure')
455
- .expect(200, 'Hello, World!');
456
-
457
- });
458
-
459
- it ('should come back with an updated header to set cookie.', async () => {
460
-
461
- app.root(
462
- new Endpoint()
463
- .get(({ cookies }) => {
464
- cookies['testValue'] = {
465
- value: 'Hello, World!',
466
- path: '/test',
467
- expires: '30d'
468
- };
469
- return 'Hello, World!';
470
- })
471
- );
472
-
473
- await request
474
- .get('/')
475
- .set('Cookie', 'testValue=Hello')
476
- .expect('Set-Cookie', 'testValue=Hello%2C%20World!; Max-Age=2592000; Path=/test; Domain=::1; Secure')
477
- .expect(200, 'Hello, World!');
478
-
479
- });
480
-
481
- it ('should come back with an updated header to a deleted cookie.', async () => {
482
-
483
- app.root(
484
- new Endpoint()
485
- .get(({ cookies }) => {
486
- delete cookies['testValue'];
487
- return 'Hello, World!';
488
- })
489
- );
490
-
491
- await request
492
- .get('/')
493
- .set('Cookie', 'testValue=Hello')
494
- .expect('Set-Cookie', 'testValue=; Max-Age=0')
495
- .expect(200, 'Hello, World!');
496
-
497
- });
498
-
499
- it ('should come back with no new cookies if no cookies are set.', (done) => {
500
-
501
- app.root(
502
- new Endpoint()
503
- .get(() => 'Hello, World!')
504
- );
505
-
506
- request
507
- .get('/')
508
- .expect(200, 'Hello, World!')
509
- .end((error, response) => {
510
- if (error) return done(error);
511
- if (response.headers['set-cookie']) {
512
- return done(new Error('Response should not have set-cookie header.'));
513
- }
514
- done();
515
- });
516
-
517
- });
518
-
519
442
  after(async () => {
520
443
  await app.close({ awaitAllConnections: true });
521
444
  });
package/lib/cookies.js DELETED
@@ -1,179 +0,0 @@
1
- //
2
- // application.js
3
- // @trenskow/app
4
- //
5
- // Created by Kristian Trenskow on 2021/11/07
6
- // For license see LICENSE.
7
- //
8
-
9
- import caseit from '@trenskow/caseit';
10
- import { duration } from '@trenskow/units';
11
-
12
- export default class Cookies {
13
-
14
- #_cookies = {
15
- current: {},
16
- requested: {},
17
- };
18
-
19
- #_request;
20
- #_response;
21
- #_path;
22
- #_casing;
23
- #_proxy;
24
-
25
- constructor({ request, response, path }, casing = this.#_casing) {
26
-
27
- if (!request || !response) {
28
- throw new Error('Cookies must be initialized with a request and response.');
29
- }
30
-
31
- this.#_request = request;
32
- this.#_response = response;
33
- this.#_path = path;
34
- this.#_casing = casing;
35
-
36
- (this.#_request.headers?.cookie || '').split(/; ?/)
37
- .filter(cookie => cookie)
38
- .forEach(cookie => {
39
-
40
- const [name, value] = cookie.split('=')
41
- .map(part => decodeURIComponent(part.trim()));
42
-
43
- this.#_cookies.requested[caseit(name)] = (this.#_cookies.current[caseit(name)] = { value }).value;
44
-
45
- });
46
-
47
- }
48
-
49
- get _proxy() {
50
- return this.#_proxy || (this.#_proxy = new Proxy(this, {
51
- get: (target, prop) => {
52
-
53
- prop = caseit(prop);
54
-
55
- if (Object.hasOwn(target, prop) || Object.hasOwn(Object.getPrototypeOf(target), prop)) {
56
- return target[prop];
57
- }
58
-
59
- return target.#_cookies.current[prop]?.value;
60
-
61
- },
62
- set: (target, prop, value) => {
63
-
64
- prop = caseit(prop);
65
-
66
- if (typeof value === 'undefined') {
67
- delete target.#_cookies.current[prop];
68
- } else if (typeof value === 'undefined' || typeof value === 'string') {
69
- this.#_proxy[prop] = { value };
70
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
71
-
72
- let {
73
- value: cookieValue,
74
- expires,
75
- path = 'current',
76
- domain = 'current',
77
- secure = true,
78
- httpOnly = false
79
- } = value;
80
-
81
- if (typeof cookieValue === 'undefined') {
82
- this.#_proxy[prop] = undefined;
83
- return true;
84
- } else if (typeof cookieValue !== 'string') {
85
- throw new TypeError('Cookie values must be a string.');
86
- }
87
-
88
- if (path === 'current') {
89
- path = `${this.#_path.current.join('/')}/`;
90
- } else if (path === 'root') {
91
- path = '/';
92
- }
93
-
94
- if (domain === 'current') {
95
- domain = target.#_request.host;
96
- } else if (domain === 'root') {
97
- domain = target.#_request.host.split('.').slice(-2).join('.');
98
- }
99
-
100
- if (typeof expires !== 'undefined' && !(['number', 'string'].includes(typeof expires) || expires instanceof Date)) {
101
- throw new TypeError('Expires must be a number, string or Date.');
102
- }
103
-
104
- target.#_cookies.current[prop] = {
105
- value: cookieValue,
106
- expires,
107
- path,
108
- domain,
109
- secure,
110
- httpOnly
111
- };
112
-
113
- } else {
114
- throw new TypeError('Cookie values must be a strings, object or undefined.');
115
- }
116
-
117
- return true;
118
-
119
- },
120
- deleteProperty: (_, prop) => {
121
- this.#_proxy[prop] = undefined;
122
- return true;
123
- },
124
- }));
125
- }
126
-
127
- #_format(name, { value, expires, path, domain, secure, httpOnly }) {
128
-
129
- const parts = [`${encodeURIComponent(caseit(name, this.#_casing))}=${encodeURIComponent(value || '')}`];
130
-
131
- if (!value) {
132
- parts.push('Max-Age=0');
133
- } else if (expires) {
134
- if (typeof expires === 'number' || typeof expires === 'string') {
135
- parts.push(`Max-Age=${duration.s(expires)}`);
136
- } else if (expires instanceof Date) {
137
- parts.push(`Expires=${expires.toUTCString()}`);
138
- }
139
- }
140
-
141
- if (path) {
142
- parts.push(`Path=${path}`);
143
- }
144
-
145
- if (domain) {
146
- parts.push(`Domain=${domain}`);
147
- }
148
-
149
- if (secure) {
150
- parts.push('Secure');
151
- }
152
-
153
- if (httpOnly) {
154
- parts.push('HttpOnly');
155
- }
156
-
157
- return parts.join('; ');
158
-
159
- }
160
-
161
- _render() {
162
-
163
- const deletedCookies = Object.keys(this.#_cookies.requested)
164
- .filter(name => !Object.hasOwn(this.#_cookies.current, name))
165
- .map(name => this.#_format(name, { value: undefined }));
166
-
167
- const allCookies = Object.entries(this.#_cookies.current)
168
- .map(([name, cookie]) => this.#_format(name, cookie))
169
- .concat(deletedCookies);
170
-
171
- if (allCookies.length === 0) {
172
- return;
173
- }
174
-
175
- this.#_response.headers.setCookie = allCookies;
176
-
177
- }
178
-
179
- };