@momsfriendlydevco/cowboy 1.2.0 → 1.4.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 CHANGED
@@ -322,6 +322,11 @@ cors(options)
322
322
  Inject simple CORS headers to allow websites to use the endpoint from the browser frontend.
323
323
 
324
324
 
325
+ devOnly()
326
+ ---------
327
+ Allow access to the endpoint ONLY if Cloudflare is running in local development mode. Throws a 403 otherwise.
328
+
329
+
325
330
  validate(key, validator)
326
331
  ------------------------
327
332
  Validate the incoming `req.$KEY` object using [Joyful](https://github.com/MomsFriendlyDevCo/Joyful).
@@ -0,0 +1,10 @@
1
+ import {defineConfig, globalIgnores} from "eslint/config";
2
+ import RulesMFDC from '@momsfriendlydevco/eslint-config';
3
+
4
+ export default defineConfig([
5
+ globalIgnores([
6
+ '.*',
7
+ 'node_modules/',
8
+ ]),
9
+ ...RulesMFDC,
10
+ ])
package/lib/cowboy.js CHANGED
@@ -39,7 +39,7 @@ export class Cowboy {
39
39
 
40
40
  /**
41
41
  * List of middleware which will be called on all matching routes
42
- * @type Array<CowboyMiddleware>
42
+ * @type {Array<CowboyMiddleware>}
43
43
  */
44
44
  earlyMiddleware = [];
45
45
 
@@ -64,7 +64,7 @@ export class Cowboy {
64
64
  *
65
65
  * @param {String|Array<String>} methods A method matcher or array of available methods
66
66
  * @param {String|RegExp|Array<String|RegExp>} paths A prefix path to match
67
- * @param {CowboyMiddleware} middleware... Middleware to call in sequence
67
+ * @param {CowboyMiddleware...} middleware Middleware to call in sequence
68
68
  *
69
69
  * @returns {Cowboy} This chainable Cowboy router instance
70
70
  */
@@ -107,7 +107,8 @@ export class Cowboy {
107
107
 
108
108
  /**
109
109
  * Action an incoming route by resolving + walking down its middleware chain
110
- * @param {CloudflareRequest} req The incoming request
110
+ *
111
+ * @param {CloudflareRequest} cfReq The incoming request
111
112
  * @param {Object} [env] Optional environment passed from Cloudflare
112
113
  * @returns {Promise<CowboyResponse>} A promise which will eventually resolve when all middleware completes
113
114
  */
@@ -173,10 +174,13 @@ export class Cowboy {
173
174
  * @param {CloudflareEvent} event The Cloudflare event context passed
174
175
  * @param {Object} env Environment variables
175
176
  * @param {CloudflareContext} ctx The Cloudflare context to respond to
177
+ *
178
+ * @returns {Cowboy} This chainable Cowboy router instance
176
179
  */
177
180
  scheduled(event, env, ctx) {
178
181
  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);
182
+ this.schedule.handler.call(this, event, env, ctx);
183
+ return this;
180
184
  }
181
185
 
182
186
 
@@ -185,7 +189,7 @@ export class Cowboy {
185
189
  * This function exists as an easier way to remap body contents without
186
190
  *
187
191
  * @param {String} url The URL path to call
188
- * @param {Object} request Additional request options
192
+ * @param {Object} options Additional request options
189
193
  * @param {Object} env The environment object to use
190
194
  *
191
195
  * @returns {CloudflareResponse} The returned CloudflareResponse object
@@ -256,7 +260,7 @@ export class Cowboy {
256
260
  : e?.code && e?.message && typeof e.code == 'string' && typeof e.message == 'string' ? `${e.code}: ${e.message}` // Supabase error objects
257
261
  : 'An unknown error has occured';
258
262
 
259
- debug('Error thrown', e);
263
+ console.warn('Error thrown', e);
260
264
  debug('Extracted error text digest', {errorText});
261
265
 
262
266
  // Form: '404: Not found'
@@ -287,7 +291,7 @@ export class Cowboy {
287
291
  /**
288
292
  * Handle cron job scheduling
289
293
  *
290
- * @param {Function} cb The callback to install for all scheduled events. Called as `(event:CloudflareEvent, env:Object, ctx:CloudflareContext)`
294
+ * @param {Function} handler The callback to install for all scheduled events. Called as `(event:CloudflareEvent, env:Object, ctx:CloudflareContext)`
291
295
  * @returns {Cowboy} This chainable Cowboy router instance
292
296
  */
293
297
  schedule(handler) {
@@ -317,6 +321,9 @@ export class Cowboy {
317
321
 
318
322
  /**
319
323
  * Wrap an incoming Wrangler request
324
+ *
325
+ * @param {Object} [options] Additional initalization options to use in the Constructor
326
+ *
320
327
  * @returns {Object} A Wrangler compatible object
321
328
  */
322
329
  export default function cowboy(options) {
package/lib/debug.js CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Wrapper around the debug instance
3
+ * This function will only output when cowboy is in debug mode
4
+ *
5
+ * @param {*...} msg Message component to show
6
+ */
1
7
  export default function CowboyDebug(...msg) {
2
8
  if (!CowboyDebug.enabled) return;
3
9
  console.log('COWBOY-DEBUG', ...msg.map(m =>
package/lib/request.js CHANGED
@@ -2,7 +2,7 @@ import debug from '#lib/debug';
2
2
 
3
3
  /**
4
4
  * Tiny wrapper around Wrangler to wrap its default Request object in an Express-like structure
5
- * @extends CloudflareRequest
5
+ * @augments {CloudflareRequest}
6
6
  */
7
7
  export default class CowboyRequest {
8
8
  /**
@@ -93,33 +93,37 @@ export default class CowboyRequest {
93
93
  switch (type) {
94
94
  case 'json':
95
95
  case 'application/json':
96
- try {
97
- this.body = await this.body.json();
98
- break;
99
- } catch (e) {
100
- if (debug.enabled) debug('Failed to decode request body as JSON:', e.toString());
101
- throw new Error('Invalid JSON body');
96
+ if (this.headers['content-length'] == 0) { // Sending JSON but its blank
97
+ this.body = {};
98
+ } else { // Try to decode JSON in a wrapper
99
+ try {
100
+ this.body = await this.body.json();
101
+ } catch (e) {
102
+ if (debug.enabled) debug('Failed to decode request body as JSON:', e.toString());
103
+ throw new Error('Invalid JSON body');
104
+ }
102
105
  }
106
+ break;
103
107
  case 'formData':
104
108
  case 'multipart/form-data': // Decode as multi-part
105
109
  case 'application/x-www-form-urlencoded': // Decode as multi-part
106
110
  try {
107
111
  let formData = await this.body.formData();
108
112
  this.body = Object.fromEntries(formData.entries());
109
- break;
110
113
  } catch (e) {
111
114
  if (debug.enabled) debug('Failed to decode multi-part body:', e.toString());
112
115
  throw new Error('Invalid multi-part encoded body');
113
116
  }
117
+ break;
114
118
  case 'text':
115
119
  case 'text/plain': // Decode as plain text
116
120
  try {
117
121
  this.body = await this.body.text();
118
- break;
119
122
  } catch (e) {
120
123
  if (debug.enabled) debug('Failed to decode plain-text body:', e.toString());
121
124
  throw new Error('Invalid text body');
122
125
  }
126
+ break;
123
127
  default:
124
128
  debug('Empty Body Payload - assuming raw payload');
125
129
  this.text = await this.body.text();
package/lib/response.js CHANGED
@@ -52,6 +52,7 @@ export default class CowboyResponse {
52
52
  send(data, end = true) {
53
53
  if (this.code === null) this.code = 200; // Assume OK if not told otherwise
54
54
 
55
+ // eslint-disable-next-line unicorn/prefer-ternary
55
56
  if (
56
57
  typeof data == 'string'
57
58
  || data instanceof FormData
@@ -125,7 +126,7 @@ export default class CowboyResponse {
125
126
  console.log('Response', JSON.stringify({
126
127
  ...cfOptions,
127
128
  body:
128
- typeof this.body == 'string' && this.body.length > 30 ? this.body.substr(0, 50) + '…'
129
+ typeof this.body == 'string' && this.body.length > 30 ? this.body.substr(0, 50) + '…' // eslint-disable-line unicorn/prefer-string-slice
129
130
  : this.body,
130
131
  }, null, '\t'));
131
132
  return new this.CloudflareResponse(this.body, cfOptions);
package/lib/testkit.js CHANGED
@@ -109,7 +109,6 @@ export function start(options) {
109
109
 
110
110
  /**
111
111
  * Stop background wrangler instances
112
- * @returns {Promise} A promise which resolves when the operation has completed
113
112
  */
114
113
  export function stop() {
115
114
  if (!worker) return; // Worker not active anyway
@@ -125,11 +124,13 @@ export function stop() {
125
124
  */
126
125
  export function cowboyMocha(options) {
127
126
 
127
+ // eslint-disable-next-line no-undef
128
128
  before('start cowboy/testkit', function() {
129
129
  this.timeout(30 * 1000);
130
130
  return start(options);
131
131
  });
132
132
 
133
+ // eslint-disable-next-line no-undef
133
134
  after('stop cowboy/testkit', function() {
134
135
  this.timeout(5 * 1000);
135
136
  return stop();
@@ -2,26 +2,30 @@
2
2
  * Register a generic middleware to handle CORS requests
3
3
  *
4
4
  * @param {Object} [options] Additional options to mutate behaviour
5
- * @param {Boolean} [options.attachOptions=true] Attach an `options` method against all routes that don't already have one to pass the CORS pre-flight check
6
- * @param {Object} [options.headers] Generic CORS headers to inject
5
+ * @param {Boolean} [options.attachOptions=true] Automatically attach an `OPTIONS` method against all routes that don't already have one to pass the CORS pre-flight check
6
+ * @param {String} [options.origin='*'] Origin URL to allow
7
+ * @param {String} [options.headers='*'] Headers to allow
8
+ * @param {Array<String>} [options.methods=['GET','POST','OPTIONS']] Allowable HTTP methods to add CORS to
7
9
  *
8
10
  * @returns {CowboyMiddleware}
9
11
  */
10
12
  export default function CowboyMiddlewareCORS(options) {
11
13
  let settings = {
12
14
  attachOptions: true,
13
- headers: {
14
- 'Access-Control-Allow-Origin': '*',
15
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
16
- 'Access-Control-Allow-Headers': '*',
17
- 'Content-Type': 'application/json;charset=UTF-8',
18
- },
15
+ origin: '*',
16
+ headers: '*',
17
+ methods: ['GET', 'POST', 'OPTIONS'],
19
18
  ...options,
20
19
  };
21
20
 
22
21
  return (req, res) => {
23
22
  // Always inject CORS headers
24
- res.set(settings.headers);
23
+ res.set({
24
+ 'Access-Control-Allow-Origin': settings.origin,
25
+ 'Access-Control-Allow-Methods': settings.methods.join(', '),
26
+ 'Access-Control-Allow-Headers': settings.headers,
27
+ 'Content-Type': 'application/json;charset=UTF-8',
28
+ });
25
29
 
26
30
  // Inject various OPTIONS endpoints for CORS pre-flight
27
31
  if (settings.attachOptions && !req.router.loadedCors) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Only permit access if Cloudflare is running in development mode
3
+ * This should only permit access in local dev environments
4
+ *
5
+ * @returns {CowboyMiddleware} A CowboyMiddleware worker which will return 403 if not in development mode
6
+ */
7
+ export default function CowboyMiddlewareDevOnly() {
8
+ return (req, res, env) => {
9
+ const isDev = env.ENVIRONMENT === 'development';
10
+
11
+ if (!isDev) return res
12
+ .status(403)
13
+ .send('Endpoint responds in development mode only');
14
+ };
15
+ }
@@ -1,4 +1,5 @@
1
1
  import cors from '#middleware/cors';
2
+ import devOnly from '#middleware/devOnly';
2
3
  import parseJwt from '#middleware/parseJwt';
3
4
  import validate from '#middleware/validate';
4
5
  import validateBody from '#middleware/validateBody';
@@ -8,6 +9,7 @@ import validateQuery from '#middleware/validateQuery';
8
9
 
9
10
  export default {
10
11
  cors,
12
+ devOnly,
11
13
  parseJwt,
12
14
  validate,
13
15
  validateBody,
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * @param {Object} [options] Additional options to mutate behaviour
5
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
+ * @returns {CowboyMiddleware}
6
8
  */
7
9
  export default function CowboyMiddlewareParseJwt(options) {
8
10
  let settings = {
@@ -13,7 +15,7 @@ export default function CowboyMiddlewareParseJwt(options) {
13
15
  };
14
16
 
15
17
  return async (req, res) => {
16
- if (await !settings.isJwt(req, res)) return;
18
+ if (!(await settings.isJwt(req, res))) return;
17
19
 
18
20
  const base64 = req.text.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
19
21
  req.body = JSON.parse(atob(base64));
@@ -4,8 +4,8 @@ import joyful from '@momsfriendlydevco/joyful';
4
4
  * Run a Joi / Joyful validation function against a specific subkey within `req
5
5
  *
6
6
  * @param {String} subkey The subkey to run against
7
- * @param {Function|Object} Callback to use with Joyful to validate with
8
- * @returns {Void} Either a successful middleware cycle (if validatio succeeds) or a call to `res.status(400)` if failed
7
+ * @param {Function|Object} validator Callback to use with Joyful to validate with
8
+ * @returns {Void} Either a successful middleware cycle (if validation succeeds) or a call to `res.status(400)` if failed
9
9
  */
10
10
  export default function CowboyMiddlewareValidate(subkey, validator) {
11
11
  return (req, res) => {
@@ -1,5 +1,11 @@
1
1
  import CowboyMiddlewareValidate from '#middleware/validate';
2
2
 
3
+ /**
4
+ * Shorthand middleware to apply validation to the request body (`req.body`) object
5
+ *
6
+ * @param {Function|Object} validator Callback to use with Joyful to validate with
7
+ * @returns {CowboyMiddleware}
8
+ */
3
9
  export default function CowboyMiddlewareValidateBody(validator) {
4
10
  return CowboyMiddlewareValidate('body', validator);
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import CowboyMiddlewareValidate from '#middleware/validate';
2
2
 
3
+ /**
4
+ * Shorthand middleware to apply validation to the headers (`req.headers`) object
5
+ *
6
+ * @param {Function|Object} validator Callback to use with Joyful to validate with
7
+ * @returns {CowboyMiddleware}
8
+ */
3
9
  export default function CowboyMiddlewareValidateHeaders(validator) {
4
10
  return CowboyMiddlewareValidate('headers', validator);
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import CowboyMiddlewareValidate from '#middleware/validate';
2
2
 
3
+ /**
4
+ * Shorthand middleware to apply validation to the parameters (`req.params`) object
5
+ *
6
+ * @param {Function|Object} validator Callback to use with Joyful to validate with
7
+ * @returns {CowboyMiddleware}
8
+ */
3
9
  export default function CowboyMiddlewareValidateParams(validator) {
4
10
  return CowboyMiddlewareValidate('params', validator);
5
11
  }
@@ -1,5 +1,11 @@
1
1
  import CowboyMiddlewareValidate from '#middleware/validate';
2
2
 
3
+ /**
4
+ * Shorthand middleware to apply validation to the query (`req.query`) object
5
+ *
6
+ * @param {Function|Object} validator Callback to use with Joyful to validate with
7
+ * @returns {CowboyMiddleware}
8
+ */
3
9
  export default function CowboyMiddlewareValidateQuery(validator) {
4
10
  return CowboyMiddlewareValidate('query', validator);
5
11
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@momsfriendlydevco/cowboy",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Wrapper around Cloudflare Wrangler to provide a more Express-like experience",
5
5
  "scripts": {
6
- "lint": "eslint ."
6
+ "lint": "eslint"
7
7
  },
8
8
  "keywords": [
9
9
  "wrangler"
@@ -40,18 +40,7 @@
40
40
  "toml": "^3.0.0"
41
41
  },
42
42
  "devDependencies": {
43
- "@momsfriendlydevco/eslint-config": "^1.0.7",
44
- "eslint": "^8.51.0"
45
- },
46
- "eslintConfig": {
47
- "extends": "@momsfriendlydevco",
48
- "env": {
49
- "es6": true,
50
- "node": true
51
- },
52
- "parserOptions": {
53
- "ecmaVersion": 13,
54
- "sourceType": "module"
55
- }
43
+ "@momsfriendlydevco/eslint-config": "^2.3.1",
44
+ "eslint": "^9.33.0"
56
45
  }
57
46
  }