@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 +5 -0
- package/eslint.config.js +10 -0
- package/lib/cowboy.js +14 -7
- package/lib/debug.js +6 -0
- package/lib/request.js +13 -9
- package/lib/response.js +2 -1
- package/lib/testkit.js +2 -1
- package/middleware/cors.js +13 -9
- package/middleware/devOnly.js +15 -0
- package/middleware/index.js +2 -0
- package/middleware/parseJwt.js +3 -1
- package/middleware/validate.js +2 -2
- package/middleware/validateBody.js +6 -0
- package/middleware/validateHeaders.js +6 -0
- package/middleware/validateParams.js +6 -0
- package/middleware/validateQuery.js +6 -0
- package/package.json +4 -15
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).
|
package/eslint.config.js
ADDED
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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}
|
|
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
|
-
* @
|
|
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
|
-
|
|
97
|
-
this.body =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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();
|
package/middleware/cors.js
CHANGED
|
@@ -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]
|
|
6
|
-
* @param {
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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(
|
|
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
|
+
}
|
package/middleware/index.js
CHANGED
|
@@ -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,
|
package/middleware/parseJwt.js
CHANGED
|
@@ -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
|
|
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));
|
package/middleware/validate.js
CHANGED
|
@@ -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
|
|
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.
|
|
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": "^
|
|
44
|
-
"eslint": "^
|
|
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
|
}
|