@momsfriendlydevco/cowboy 1.0.18 → 1.1.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/README.md +10 -0
- package/lib/cowboy.js +38 -3
- package/lib/request.js +13 -1
- package/lib/response.js +24 -1
- package/middleware/index.js +2 -0
- package/middleware/parseJwt.js +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -136,6 +136,11 @@ This function will, in order:
|
|
|
136
136
|
6. Return the final response - if it the function did not already explicitly do so
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
Cowboy.proxy(path, request, env)
|
|
140
|
+
--------------------------------
|
|
141
|
+
Forward from one route to another as if the second route was called first.
|
|
142
|
+
|
|
143
|
+
|
|
139
144
|
CowboyRequest
|
|
140
145
|
-------------
|
|
141
146
|
```javascript
|
|
@@ -376,3 +381,8 @@ cowboy()
|
|
|
376
381
|
(req, res, env) => /* ... */
|
|
377
382
|
)
|
|
378
383
|
```
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
parseJwt()
|
|
387
|
+
----------
|
|
388
|
+
Parse the incoming request as a JWT string and decode its contents into `req.body`.
|
package/lib/cowboy.js
CHANGED
|
@@ -166,6 +166,41 @@ export class Cowboy {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Call a router function as if it were invoked directly
|
|
171
|
+
* This function exists as an easier way to remap body contents without
|
|
172
|
+
*
|
|
173
|
+
* @param {String} url The URL path to call
|
|
174
|
+
* @param {Object} request Additional request options
|
|
175
|
+
* @param {Object} env The environment object to use
|
|
176
|
+
*
|
|
177
|
+
* @returns {CloudflareResponse} The returned CloudflareResponse object
|
|
178
|
+
*/
|
|
179
|
+
async proxy(url, options, env) {
|
|
180
|
+
if (!url || !options || !env) throw new Error('Url + options + env must be specified to proxy()');
|
|
181
|
+
|
|
182
|
+
return this.fetch(new Request(new URL(url, 'http://localhost').toString(), {
|
|
183
|
+
...options,
|
|
184
|
+
body: (()=> {
|
|
185
|
+
if ( // Being handed a native Cloudflare request body?
|
|
186
|
+
!options.body
|
|
187
|
+
|| options.body instanceof FormData
|
|
188
|
+
|| options.body instanceof ReadableStream
|
|
189
|
+
|| options.body instanceof URLSearchParams
|
|
190
|
+
|| typeof options.body == 'string'
|
|
191
|
+
) {
|
|
192
|
+
return options.body;
|
|
193
|
+
} else if (typeof options.body == 'object') { // Convert POJO into Formdata
|
|
194
|
+
let body = new FormData();
|
|
195
|
+
Object.entries(options.body)
|
|
196
|
+
.forEach(([key, val]) => body.append(key, val))
|
|
197
|
+
return body;
|
|
198
|
+
}
|
|
199
|
+
})(),
|
|
200
|
+
}), env);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
169
204
|
async execMiddleware({middleware, req, res, env}) {
|
|
170
205
|
let middlewareStack = middleware
|
|
171
206
|
.map(m => {
|
|
@@ -183,7 +218,7 @@ export class Cowboy {
|
|
|
183
218
|
while (middlewareStack.length > 0) {
|
|
184
219
|
let middleware = middlewareStack.shift();
|
|
185
220
|
try {
|
|
186
|
-
response = await middleware(req, res, env);
|
|
221
|
+
response = await middleware.call(this, req, res, env);
|
|
187
222
|
if (response?.hasSent) { // Stop middleware chain as some intermediate has signalled the chain should end
|
|
188
223
|
response = res;
|
|
189
224
|
break;
|
|
@@ -193,7 +228,7 @@ export class Cowboy {
|
|
|
193
228
|
} catch (e) {
|
|
194
229
|
let errorText = typeof e == 'string' ? e : e.toString();
|
|
195
230
|
|
|
196
|
-
debug('Error thrown',
|
|
231
|
+
debug('Error thrown', e);
|
|
197
232
|
|
|
198
233
|
// Form: '404: Not found'
|
|
199
234
|
if (/^(\d{3}):/.test(errorText)) {
|
|
@@ -226,8 +261,8 @@ export class Cowboy {
|
|
|
226
261
|
* @returns {Cowboy} This chainable Cowboy router instance
|
|
227
262
|
*/
|
|
228
263
|
init() {
|
|
229
|
-
debug('INIT!');
|
|
230
264
|
if (this.doneInit) return this; // Already completed init
|
|
265
|
+
debug('INIT!');
|
|
231
266
|
|
|
232
267
|
if (this.settings.patchAxios) {
|
|
233
268
|
// TODO: Patch Axios somehow
|
package/lib/request.js
CHANGED
|
@@ -28,6 +28,14 @@ export default class CowboyRequest {
|
|
|
28
28
|
/**
|
|
29
29
|
* Raw body payload provided by cfReq
|
|
30
30
|
* This gets translated into a usable object after a call to `parseBody()`
|
|
31
|
+
* @type {Buffer}
|
|
32
|
+
*/
|
|
33
|
+
text = null;
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Body payload provided by cfReq (
|
|
38
|
+
* Defaults to the same value as `text` until its parsed by `parseBody()`
|
|
31
39
|
* @type {*}
|
|
32
40
|
*/
|
|
33
41
|
body = null;
|
|
@@ -60,6 +68,9 @@ export default class CowboyRequest {
|
|
|
60
68
|
// Slurp the headers
|
|
61
69
|
this.headers = Object.fromEntries(cfReq.headers.entries());
|
|
62
70
|
|
|
71
|
+
// Hold the raw data
|
|
72
|
+
this.text = cfReq.text.bind(cfReq),
|
|
73
|
+
|
|
63
74
|
// Hold the body element - this wont be decoded until parseBody() is called
|
|
64
75
|
this.body = {
|
|
65
76
|
json: cfReq.json.bind(cfReq),
|
|
@@ -110,7 +121,8 @@ export default class CowboyRequest {
|
|
|
110
121
|
throw new Error('Invalid text body');
|
|
111
122
|
}
|
|
112
123
|
default:
|
|
113
|
-
debug('Empty Body Payload');
|
|
124
|
+
debug('Empty Body Payload - assuming raw payload');
|
|
125
|
+
this.text = await this.body.text();
|
|
114
126
|
this.body = {};
|
|
115
127
|
}
|
|
116
128
|
}
|
package/lib/response.js
CHANGED
|
@@ -25,6 +25,24 @@ export default class CowboyResponse {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* ExpressJS-like type setter and shortcut function
|
|
30
|
+
* Recognises various shorthand types or defaults to setting a MIME type
|
|
31
|
+
* @param {String} type The type string to set, can be a shorthand string or a mime type
|
|
32
|
+
* @returns {CowboyResponse} This chainable instance
|
|
33
|
+
*/
|
|
34
|
+
type(type) {
|
|
35
|
+
switch (type) {
|
|
36
|
+
case 'html': return this.set('Content-Type', 'text/html');
|
|
37
|
+
case 'json': return this.set('Content-Type', 'application/json');
|
|
38
|
+
case 'text': return this.set('Content-Type', 'text/plain');
|
|
39
|
+
default:
|
|
40
|
+
if (!/\//.test(type)) throw new Error(`Shorthand type "${type}" is not recognised and does not look like a valid mime type`);
|
|
41
|
+
return this.set('Content-Type', type);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
28
46
|
/**
|
|
29
47
|
* Send data and (optionally) mark the response as complete
|
|
30
48
|
* @param {*} data The data to transmit
|
|
@@ -34,7 +52,12 @@ export default class CowboyResponse {
|
|
|
34
52
|
send(data, end = true) {
|
|
35
53
|
if (this.code === null) this.code = 200; // Assume OK if not told otherwise
|
|
36
54
|
|
|
37
|
-
if (
|
|
55
|
+
if (
|
|
56
|
+
typeof data == 'string'
|
|
57
|
+
|| data instanceof FormData
|
|
58
|
+
|| data instanceof ReadableStream
|
|
59
|
+
|| data instanceof URLSearchParams
|
|
60
|
+
) {
|
|
38
61
|
this.body = data;
|
|
39
62
|
} else {
|
|
40
63
|
this.body = JSON.stringify(data);
|
package/middleware/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import cors from '#middleware/cors';
|
|
2
|
+
import parseJwt from '#middleware/parseJwt';
|
|
2
3
|
import validate from '#middleware/validate';
|
|
3
4
|
import validateBody from '#middleware/validateBody';
|
|
4
5
|
import validateHeaders from '#middleware/validateHeaders';
|
|
@@ -7,6 +8,7 @@ import validateQuery from '#middleware/validateQuery';
|
|
|
7
8
|
|
|
8
9
|
export default {
|
|
9
10
|
cors,
|
|
11
|
+
parseJwt,
|
|
10
12
|
validate,
|
|
11
13
|
validateBody,
|
|
12
14
|
validateHeaders,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a parsing middleware layer which accepts a JWT body and decodes the object into req.body
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
5
|
+
* @param {Function} [options.isJwt] Async function, called as `(req, res)` to determine if the input is a JWT payload, defaults to checking the content-type header
|
|
6
|
+
*/
|
|
7
|
+
export default function CowboyMiddlewareParseJwt(options) {
|
|
8
|
+
let settings = {
|
|
9
|
+
async isJwt(req, res) { // eslint-disable-line no-unused-vars
|
|
10
|
+
return req.headers['content-type'] == 'application/jwt';
|
|
11
|
+
},
|
|
12
|
+
...options,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
return async (req, res) => {
|
|
16
|
+
if (await !settings.isJwt(req, res)) return;
|
|
17
|
+
|
|
18
|
+
const base64 = req.text.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
19
|
+
req.body = JSON.parse(atob(base64));
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momsfriendlydevco/cowboy",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Wrapper around Cloudflare Wrangler to provide a more Express-like experience",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "eslint ."
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@momsfriendlydevco/joyful": "^1.0.1",
|
|
39
|
-
"@momsfriendlydevco/path-match": "^1.
|
|
39
|
+
"@momsfriendlydevco/path-match": "^1.1.0",
|
|
40
40
|
"toml": "^3.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|