@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 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', errorText);
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
  }
@@ -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.19",
3
+ "version": "1.1.1",
4
4
  "description": "Wrapper around Cloudflare Wrangler to provide a more Express-like experience",
5
5
  "scripts": {
6
6
  "lint": "eslint ."