@momsfriendlydevco/cowboy 1.0.19 → 1.1.1
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 +30 -0
- package/lib/cowboy.js +65 -3
- package/lib/request.js +13 -1
- package/middleware/index.js +2 -0
- package/middleware/parseJwt.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,21 @@ export default cowboy()
|
|
|
63
63
|
req => widgetStore.delete(req.params.id)
|
|
64
64
|
)
|
|
65
65
|
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Cron schedule handling
|
|
69
|
+
----------------------
|
|
70
|
+
Cron scheduling is a little basic at the moment but likely to improve in the future.
|
|
71
|
+
To set up a Cron handler simply install it by calling `.schedule(callback)`:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import cowboy from '@momsfriendlydevco/cowboy';
|
|
75
|
+
|
|
76
|
+
export default cowboy()
|
|
77
|
+
.schedule(async (event, env, ctx) => {
|
|
78
|
+
// Handle cron code here
|
|
79
|
+
})
|
|
80
|
+
```
|
|
66
81
|
```
|
|
67
82
|
|
|
68
83
|
Debugging
|
|
@@ -136,6 +151,16 @@ This function will, in order:
|
|
|
136
151
|
6. Return the final response - if it the function did not already explicitly do so
|
|
137
152
|
|
|
138
153
|
|
|
154
|
+
Cowboy.proxy(path, request, env)
|
|
155
|
+
--------------------------------
|
|
156
|
+
Forward from one route to another as if the second route was called first.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
Cowboy.schedule(callback)
|
|
160
|
+
-------------------------
|
|
161
|
+
Install a scheduled Cron handler function.
|
|
162
|
+
|
|
163
|
+
|
|
139
164
|
CowboyRequest
|
|
140
165
|
-------------
|
|
141
166
|
```javascript
|
|
@@ -376,3 +401,8 @@ cowboy()
|
|
|
376
401
|
(req, res, env) => /* ... */
|
|
377
402
|
)
|
|
378
403
|
```
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
parseJwt()
|
|
407
|
+
----------
|
|
408
|
+
Parse the incoming request as a JWT string and decode its contents into `req.body`.
|
package/lib/cowboy.js
CHANGED
|
@@ -166,6 +166,55 @@ export class Cowboy {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Set up Cloudflare response to "scheduled" call
|
|
171
|
+
* This is really just a map to the last handler we installed to .schedule(cb) - for now
|
|
172
|
+
*
|
|
173
|
+
* @param {CloudflareEvent} event The Cloudflare event context passed
|
|
174
|
+
* @param {Object} env Environment variables
|
|
175
|
+
* @param {CloudflareContext} ctx The Cloudflare context to respond to
|
|
176
|
+
*/
|
|
177
|
+
scheduled(event, env, ctx) {
|
|
178
|
+
if (!this.schedule.handler) throw new Error('Attemped to access Cowboy.scheduled without first calling .schedule() to set something up!');
|
|
179
|
+
return this.schedule.handler.call(this, event, env, ctx);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Call a router function as if it were invoked directly
|
|
185
|
+
* This function exists as an easier way to remap body contents without
|
|
186
|
+
*
|
|
187
|
+
* @param {String} url The URL path to call
|
|
188
|
+
* @param {Object} request Additional request options
|
|
189
|
+
* @param {Object} env The environment object to use
|
|
190
|
+
*
|
|
191
|
+
* @returns {CloudflareResponse} The returned CloudflareResponse object
|
|
192
|
+
*/
|
|
193
|
+
async proxy(url, options, env) {
|
|
194
|
+
if (!url || !options || !env) throw new Error('Url + options + env must be specified to proxy()');
|
|
195
|
+
|
|
196
|
+
return this.fetch(new Request(new URL(url, 'http://localhost').toString(), {
|
|
197
|
+
...options,
|
|
198
|
+
body: (()=> {
|
|
199
|
+
if ( // Being handed a native Cloudflare request body?
|
|
200
|
+
!options.body
|
|
201
|
+
|| options.body instanceof FormData
|
|
202
|
+
|| options.body instanceof ReadableStream
|
|
203
|
+
|| options.body instanceof URLSearchParams
|
|
204
|
+
|| typeof options.body == 'string'
|
|
205
|
+
) {
|
|
206
|
+
return options.body;
|
|
207
|
+
} else if (typeof options.body == 'object') { // Convert POJO into Formdata
|
|
208
|
+
let body = new FormData();
|
|
209
|
+
Object.entries(options.body)
|
|
210
|
+
.forEach(([key, val]) => body.append(key, val))
|
|
211
|
+
return body;
|
|
212
|
+
}
|
|
213
|
+
})(),
|
|
214
|
+
}), env);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
|
|
169
218
|
async execMiddleware({middleware, req, res, env}) {
|
|
170
219
|
let middlewareStack = middleware
|
|
171
220
|
.map(m => {
|
|
@@ -183,7 +232,7 @@ export class Cowboy {
|
|
|
183
232
|
while (middlewareStack.length > 0) {
|
|
184
233
|
let middleware = middlewareStack.shift();
|
|
185
234
|
try {
|
|
186
|
-
response = await middleware(req, res, env);
|
|
235
|
+
response = await middleware.call(this, req, res, env);
|
|
187
236
|
if (response?.hasSent) { // Stop middleware chain as some intermediate has signalled the chain should end
|
|
188
237
|
response = res;
|
|
189
238
|
break;
|
|
@@ -193,7 +242,7 @@ export class Cowboy {
|
|
|
193
242
|
} catch (e) {
|
|
194
243
|
let errorText = typeof e == 'string' ? e : e.toString();
|
|
195
244
|
|
|
196
|
-
debug('Error thrown',
|
|
245
|
+
debug('Error thrown', e);
|
|
197
246
|
|
|
198
247
|
// Form: '404: Not found'
|
|
199
248
|
if (/^(\d{3}):/.test(errorText)) {
|
|
@@ -220,14 +269,27 @@ export class Cowboy {
|
|
|
220
269
|
options(path, ...middleware) { return this.route('OPTIONS', path, ...middleware) }
|
|
221
270
|
|
|
222
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Handle cron job scheduling
|
|
274
|
+
*
|
|
275
|
+
* @param {Function} cb The callback to install for all scheduled events. Called as `(event:CloudflareEvent, env:Object, ctx:CloudflareContext)`
|
|
276
|
+
* @returns {Cowboy} This chainable Cowboy router instance
|
|
277
|
+
*/
|
|
278
|
+
schedule(handler) {
|
|
279
|
+
console.info('Installed schedule event handler. Access via http://localhost:8787/__scheduled');
|
|
280
|
+
this.schedule.handler = handler;
|
|
281
|
+
return this;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
223
285
|
/**
|
|
224
286
|
* Generial Init() sequence
|
|
225
287
|
* This will be run automatically on setup or the first fetch()
|
|
226
288
|
* @returns {Cowboy} This chainable Cowboy router instance
|
|
227
289
|
*/
|
|
228
290
|
init() {
|
|
229
|
-
debug('INIT!');
|
|
230
291
|
if (this.doneInit) return this; // Already completed init
|
|
292
|
+
debug('INIT!');
|
|
231
293
|
|
|
232
294
|
if (this.settings.patchAxios) {
|
|
233
295
|
// 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/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
|
+
}
|