1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.wrapper = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
2 |
|
3 | 'use strict'
|
4 |
|
5 | const createInternal = require('./utils/internal.js').createInternal
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | const internal = createInternal()
|
12 |
|
13 | class BmResponse {
|
14 | constructor () {
|
15 | Object.assign(internal(this), {
|
16 | headers: {},
|
17 | payload: undefined,
|
18 | statusCode: 200
|
19 | })
|
20 | }
|
21 |
|
22 | get headers () /* : Headers */ {
|
23 | return Object.assign({}, internal(this).headers)
|
24 | }
|
25 |
|
26 | get payload () /* : any */ {
|
27 | return internal(this).payload
|
28 | }
|
29 |
|
30 | get statusCode () /* : number */ {
|
31 | return internal(this).statusCode
|
32 | }
|
33 |
|
34 | setHeader (
|
35 | key ,
|
36 | value
|
37 | ) {
|
38 | key = key.toLowerCase()
|
39 | internal(this).headers[key] = value
|
40 | return this
|
41 | }
|
42 |
|
43 | setPayload (
|
44 | payload
|
45 | ) {
|
46 | internal(this).payload = payload
|
47 | return this
|
48 | }
|
49 |
|
50 | setStatusCode (
|
51 | code
|
52 | ) {
|
53 | internal(this).statusCode = code
|
54 | return this
|
55 | }
|
56 | }
|
57 |
|
58 | module.exports = BmResponse
|
59 |
|
60 | },{"./utils/internal.js":3}],2:[function(require,module,exports){
|
61 |
|
62 | 'use strict'
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | const uniloc = require('uniloc')
|
73 |
|
74 | const BmResponse = require('../lib/bm-response.js')
|
75 |
|
76 | function executeHandler (
|
77 | handler /* : Handler */,
|
78 | request /* : BmRequest */
|
79 | ) /* : Promise<BmResponse> */ {
|
80 | const response = new BmResponse()
|
81 | return Promise.resolve()
|
82 | .then(() => handler(request, response))
|
83 | .then((result) => {
|
84 |
|
85 |
|
86 |
|
87 | if (result && result !== response) {
|
88 | if (Number.isFinite(result)) {
|
89 | response.setStatusCode(result)
|
90 | } else {
|
91 | response.setPayload(result)
|
92 | }
|
93 | }
|
94 | return response
|
95 | })
|
96 | }
|
97 |
|
98 | function getHandler (
|
99 | module /* : string */,
|
100 | method /* : string */
|
101 | ) /* : Promise<Handler | void> */ {
|
102 | try {
|
103 |
|
104 | let handler = require(module)
|
105 | if (handler && method && typeof handler[method] === 'function') {
|
106 | handler = handler[method]
|
107 | }
|
108 | return Promise.resolve(handler)
|
109 | } catch (err) {
|
110 | return Promise.reject(err)
|
111 | }
|
112 | }
|
113 |
|
114 | function findRouteConfig (
|
115 | route /* : string */,
|
116 | routeConfigs /* : RouteConfiguration[] */
|
117 | ) /* : RouteConfiguration */ {
|
118 | const unilocRoutes = routeConfigs.reduce((memo, r) => {
|
119 | memo[r.route] = `GET ${r.route.replace(/{/g, ':').replace(/}/g, '')}`
|
120 | return memo
|
121 | }, {})
|
122 | const unilocRouter = uniloc(unilocRoutes)
|
123 | const unilocRoute = unilocRouter.lookup(route, 'GET')
|
124 |
|
125 | const routeConfig = routeConfigs.find((routeConfig) => routeConfig.route === unilocRoute.name)
|
126 | if (!routeConfig) {
|
127 | throw new Error(`Route has not been implemented: ${route}`)
|
128 | }
|
129 |
|
130 | routeConfig.params = unilocRoute.options
|
131 | return routeConfig
|
132 | }
|
133 |
|
134 | module.exports = {
|
135 | findRouteConfig,
|
136 | executeHandler,
|
137 | getHandler
|
138 | }
|
139 |
|
140 | },{"../lib/bm-response.js":1,"uniloc":5}],3:[function(require,module,exports){
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | function createInternal () {
|
150 | const map = new WeakMap()
|
151 | return (object ) => {
|
152 | const values = map.get(object) || {}
|
153 | if (!map.has(object)) {
|
154 | map.set(object, values)
|
155 | }
|
156 | return values
|
157 | }
|
158 | }
|
159 |
|
160 | module.exports = {
|
161 | createInternal
|
162 | }
|
163 |
|
164 | },{}],4:[function(require,module,exports){
|
165 |
|
166 | 'use strict'
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | function keysToLowerCase (
|
178 | object /* : MapObject */
|
179 | ) /* : MapObject */ {
|
180 | return Object.keys(object).reduce((result, key) => {
|
181 | result[key.toLowerCase()] = object[key]
|
182 | return result
|
183 | }, {})
|
184 | }
|
185 |
|
186 | function normaliseMethod (
|
187 | method /* : string */
|
188 | ) /* : string */ {
|
189 | return method.toLowerCase()
|
190 | }
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 | function protocolFromHeaders (
|
198 | headers /* : Headers */
|
199 | ) /* : Protocol */ {
|
200 | if (headers['x-forwarded-proto'] === 'https') {
|
201 | return `https:`
|
202 | }
|
203 | if (headers.forwarded && ~headers.forwarded.indexOf('proto=https')) {
|
204 | return `https:`
|
205 | }
|
206 | if (headers['front-end-https'] === 'on') {
|
207 | return `https:`
|
208 | }
|
209 | return 'http:'
|
210 | }
|
211 |
|
212 | module.exports = {
|
213 | keysToLowerCase,
|
214 | normaliseMethod,
|
215 | protocolFromHeaders
|
216 | }
|
217 |
|
218 | },{}],5:[function(require,module,exports){
|
219 | (function(root) {
|
220 | function assert(condition, format) {
|
221 | if (!condition) {
|
222 | var args = [].slice.call(arguments, 2);
|
223 | var argIndex = 0;
|
224 | throw new Error(
|
225 | 'Unirouter Assertion Failed: ' +
|
226 | format.replace(/%s/g, function() { return args[argIndex++]; })
|
227 | );
|
228 | }
|
229 | }
|
230 |
|
231 | function pathParts(path) {
|
232 | return path == '' ? [] : path.split('/')
|
233 | }
|
234 |
|
235 | function routeParts(route) {
|
236 | var split = route.split(/\s+/)
|
237 | var method = split[0]
|
238 | var path = split[1]
|
239 |
|
240 |
|
241 | assert(
|
242 | split.length == 2,
|
243 | "Route `%s` separates method and path with a single block of whitespace", route
|
244 | )
|
245 |
|
246 |
|
247 | assert(
|
248 | /^[A-Z]+$/.test(method),
|
249 | "Route `%s` starts with an UPPERCASE method", route
|
250 | )
|
251 |
|
252 |
|
253 | assert(
|
254 | !/\/{2,}/.test(path),
|
255 | "Path `%s` has no adjacent `/` characters: `%s`", path
|
256 | )
|
257 | assert(
|
258 | path[0] == '/',
|
259 | "Path `%s` must start with the `/` character", path
|
260 | )
|
261 | assert(
|
262 | path == '/' || !/\/$/.test(path),
|
263 | "Path `%s` does not end with the `/` character", path
|
264 | )
|
265 | assert(
|
266 | path.indexOf('#') === -1 && path.indexOf('?') === -1,
|
267 | "Path `%s` does not contain the `#` or `?` characters", path
|
268 | )
|
269 |
|
270 | return pathParts(path.slice(1)).concat(method)
|
271 | }
|
272 |
|
273 |
|
274 | function LookupTree() {
|
275 | this.tree = {}
|
276 | }
|
277 |
|
278 | function lookupTreeReducer(tree, part) {
|
279 | return tree && (tree[part] || tree[':'])
|
280 | }
|
281 |
|
282 | LookupTree.prototype.find = function(parts) {
|
283 | return (parts.reduce(lookupTreeReducer, this.tree) || {})['']
|
284 | }
|
285 |
|
286 | LookupTree.prototype.add = function(parts, route) {
|
287 | var i, branch
|
288 | var branches = parts.map(function(part) { return part[0] == ':' ? ':' : part })
|
289 | var currentTree = this.tree
|
290 |
|
291 | for (i = 0; i < branches.length; i++) {
|
292 | branch = branches[i]
|
293 | if (!currentTree[branch]) {
|
294 | currentTree[branch] = {}
|
295 | }
|
296 | currentTree = currentTree[branch]
|
297 | }
|
298 |
|
299 | assert(
|
300 | !currentTree[branch],
|
301 | "Path `%s` conflicts with another path", parts.join('/')
|
302 | )
|
303 |
|
304 | currentTree[''] = route
|
305 | }
|
306 |
|
307 |
|
308 | function createRouter(routes, aliases) {
|
309 | var parts, name, route;
|
310 | var routesParams = {};
|
311 | var lookupTree = new LookupTree;
|
312 |
|
313 |
|
314 | aliases = aliases || {};
|
315 |
|
316 |
|
317 | for (name in routes) {
|
318 | if (routes.hasOwnProperty(name)) {
|
319 | route = routes[name]
|
320 |
|
321 | assert(
|
322 | typeof route == 'string',
|
323 | "Route '%s' must be a string", name
|
324 | )
|
325 | assert(
|
326 | name.indexOf('.') == -1,
|
327 | "Route names must not contain the '.' character", name
|
328 | )
|
329 |
|
330 | parts = routeParts(route)
|
331 |
|
332 | routesParams[name] = parts
|
333 | .map(function(part, i) { return part[0] == ':' && [part.substr(1), i] })
|
334 | .filter(function(x) { return x })
|
335 |
|
336 | lookupTree.add(parts, name)
|
337 | }
|
338 | }
|
339 |
|
340 |
|
341 | for (route in aliases) {
|
342 | if (aliases.hasOwnProperty(route)) {
|
343 | name = aliases[route]
|
344 |
|
345 | assert(
|
346 | routes[name],
|
347 | "Alias from '%s' to non-existent route '%s'.", route, name
|
348 | )
|
349 |
|
350 | lookupTree.add(routeParts(route), name);
|
351 | }
|
352 | }
|
353 |
|
354 |
|
355 | return {
|
356 | lookup: function(uri, method) {
|
357 | method = method ? method.toUpperCase() : 'GET'
|
358 |
|
359 | var i, x
|
360 |
|
361 | var split = uri
|
362 |
|
363 | .replace(/^\/|\/($|\?)/g, '')
|
364 |
|
365 | .replace(/#.*$/, '')
|
366 | .split('?', 2)
|
367 |
|
368 | var parts = pathParts(split[0]).map(decodeURIComponent).concat(method)
|
369 | var name = lookupTree.find(parts)
|
370 | var options = {}
|
371 | var params, queryParts
|
372 |
|
373 | params = routesParams[name] || []
|
374 | queryParts = split[1] ? split[1].split('&') : []
|
375 |
|
376 | for (i = 0; i != queryParts.length; i++) {
|
377 | x = queryParts[i].split('=')
|
378 | options[x[0]] = decodeURIComponent(x[1])
|
379 | }
|
380 |
|
381 |
|
382 | for (i = 0; i != params.length; i++) {
|
383 | x = params[i]
|
384 | options[x[0]] = parts[x[1]]
|
385 | }
|
386 |
|
387 | return {name: name, options: options}
|
388 | },
|
389 |
|
390 |
|
391 | generate: function(name, options) {
|
392 | options = options || {}
|
393 |
|
394 | var params = routesParams[name] || []
|
395 | var paramNames = params.map(function(x) { return x[0]; })
|
396 | var route = routes[name]
|
397 | var query = []
|
398 | var inject = []
|
399 | var key
|
400 |
|
401 | assert(route, "No route with name `%s` exists", name)
|
402 |
|
403 | var path = route.split(' ')[1]
|
404 |
|
405 | for (key in options) {
|
406 | if (options.hasOwnProperty(key)) {
|
407 | if (paramNames.indexOf(key) === -1) {
|
408 | assert(
|
409 | /^[a-zA-Z0-9-_]+$/.test(key),
|
410 | "Non-route parameters must use only the following characters: A-Z, a-z, 0-9, -, _"
|
411 | )
|
412 |
|
413 | query.push(key+'='+encodeURIComponent(options[key]))
|
414 | }
|
415 | else {
|
416 | inject.push(key)
|
417 | }
|
418 | }
|
419 | }
|
420 |
|
421 | assert(
|
422 | inject.sort().join() == paramNames.slice(0).sort().join(),
|
423 | "You must specify options for all route params when using `uri`."
|
424 | )
|
425 |
|
426 | var uri =
|
427 | paramNames.reduce(function pathReducer(injected, key) {
|
428 | return injected.replace(':'+key, encodeURIComponent(options[key]))
|
429 | }, path)
|
430 |
|
431 | if (query.length) {
|
432 | uri += '?' + query.join('&')
|
433 | }
|
434 |
|
435 | return uri
|
436 | }
|
437 | };
|
438 | }
|
439 |
|
440 |
|
441 | if (typeof module !== 'undefined' && module.exports) {
|
442 | module.exports = createRouter
|
443 | }
|
444 | else {
|
445 | root.unirouter = createRouter
|
446 | }
|
447 | })(this);
|
448 |
|
449 | },{}],6:[function(require,module,exports){
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 | 'use strict'
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 |
|
467 | const path = require('path')
|
468 |
|
469 | const handlers = require('../lib/handlers.js')
|
470 | const wrapper = require('../lib/wrapper.js')
|
471 |
|
472 |
|
473 | function normaliseLambdaRequest (
|
474 | event /* : LambdaEvent */
|
475 | ) /* : BmRequest */ {
|
476 | const headers = wrapper.keysToLowerCase(event.headers)
|
477 | let body = event.body
|
478 | try {
|
479 | body = JSON.parse(body)
|
480 | } catch (e) {
|
481 |
|
482 | }
|
483 | const host = headers['x-forwarded-host'] || headers.host
|
484 | return {
|
485 | body,
|
486 | headers,
|
487 | method: wrapper.normaliseMethod(event.httpMethod),
|
488 | route: event.path,
|
489 | url: {
|
490 | host,
|
491 | hostname: host,
|
492 | params: {},
|
493 | pathname: event.path,
|
494 | protocol: wrapper.protocolFromHeaders(headers),
|
495 | query: event.queryStringParameters || {}
|
496 | }
|
497 | }
|
498 | }
|
499 |
|
500 | function handler (
|
501 | event /* : LambdaEvent */,
|
502 | context /* : any */,
|
503 | cb /* : (error: null, response: {
|
504 | body: string,
|
505 | headers: Headers,
|
506 | statusCode: number
|
507 | }) => void */
|
508 | ) {
|
509 | const startTime = Date.now()
|
510 | const request = normaliseLambdaRequest(event)
|
511 | const internalHeaders = {}
|
512 | internalHeaders['Content-Type'] = 'application/json'
|
513 | const finish = (statusCode, body, customHeaders) => {
|
514 | const headers = Object.assign(internalHeaders, customHeaders)
|
515 | const endTime = Date.now()
|
516 | const requestTime = endTime - startTime
|
517 | console.log('BLINKM_ANALYTICS_EVENT', JSON.stringify({
|
518 | request: {
|
519 | method: request.method.toUpperCase(),
|
520 | query: request.url.query,
|
521 | port: 443,
|
522 | path: request.route,
|
523 | hostName: request.url.hostname,
|
524 | params: request.url.params,
|
525 | protocol: request.url.protocol
|
526 | },
|
527 | response: {
|
528 | statusCode: statusCode
|
529 | },
|
530 | requestTime: {
|
531 | startDateTime: new Date(startTime),
|
532 | startTimeStamp: startTime,
|
533 | endDateTime: new Date(endTime),
|
534 | endTimeStamp: endTime,
|
535 | ms: requestTime,
|
536 | s: requestTime / 1000
|
537 | }
|
538 | }, null, 2))
|
539 | cb(null, {
|
540 | body: JSON.stringify(body, null, 2),
|
541 | headers: wrapper.keysToLowerCase(headers),
|
542 | statusCode: statusCode
|
543 | })
|
544 | }
|
545 |
|
546 | return Promise.resolve()
|
547 |
|
548 | .then(() => require(path.join(__dirname, 'bm-server.json')))
|
549 | .then((config) => {
|
550 |
|
551 | let routeConfig
|
552 | try {
|
553 | routeConfig = handlers.findRouteConfig(event.path, config.routes)
|
554 | request.url.params = routeConfig.params || {}
|
555 | request.route = routeConfig.route
|
556 | } catch (error) {
|
557 | return finish(404, {
|
558 | error: 'Not Found',
|
559 | message: error.message,
|
560 | statusCode: 404
|
561 | })
|
562 | }
|
563 |
|
564 |
|
565 | if (request.headers.origin) {
|
566 | if (!config.cors) {
|
567 |
|
568 | return finish(405, {
|
569 | error: 'Method Not Allowed',
|
570 | message: 'OPTIONS method has not been implemented',
|
571 | statusCode: 405
|
572 | })
|
573 | }
|
574 | if (!config.cors.origins.some((origin) => origin === '*' || origin === request.headers.origin)) {
|
575 |
|
576 | return finish(200)
|
577 | }
|
578 |
|
579 | internalHeaders['Access-Control-Allow-Origin'] = request.headers.origin
|
580 | internalHeaders['Access-Control-Expose-Headers'] = config.cors.exposedHeaders.join(',')
|
581 |
|
582 | if (request.method === 'options' && request.headers['access-control-request-method']) {
|
583 | internalHeaders['Access-Control-Allow-Headers'] = config.cors.headers.join(',')
|
584 | internalHeaders['Access-Control-Allow-Methods'] = request.headers['access-control-request-method']
|
585 | internalHeaders['Access-Control-Max-Age'] = config.cors.maxAge
|
586 | }
|
587 |
|
588 | if (config.cors.credentials) {
|
589 | internalHeaders['Access-Control-Allow-Credentials'] = true
|
590 | }
|
591 | }
|
592 | if (request.method === 'options') {
|
593 |
|
594 |
|
595 | return finish(200)
|
596 | }
|
597 |
|
598 |
|
599 |
|
600 | const projectPath = path.join(__dirname, 'project')
|
601 | if (process.cwd() !== projectPath) {
|
602 | try {
|
603 | process.chdir(projectPath)
|
604 | } catch (err) {
|
605 | return Promise.reject(new Error(`Could not change current working directory to '${projectPath}': ${err}`))
|
606 | }
|
607 | }
|
608 |
|
609 | return handlers.getHandler(path.join(__dirname, routeConfig.module), request.method)
|
610 | .then((handler) => {
|
611 | if (typeof handler !== 'function') {
|
612 | return finish(405, {
|
613 | error: 'Method Not Allowed',
|
614 | message: `${request.method.toUpperCase()} method has not been implemented`,
|
615 | statusCode: 405
|
616 | })
|
617 | }
|
618 |
|
619 | return handlers.executeHandler(handler, request)
|
620 | .then((response) => finish(response.statusCode, response.payload, response.headers))
|
621 | })
|
622 | })
|
623 | .catch((error) => {
|
624 | if (error && error.stack) {
|
625 | console.error(error.stack)
|
626 | }
|
627 | if (error && error.isBoom && error.output && error.output.payload && error.output.statusCode) {
|
628 | if (error.data) {
|
629 | console.error('Boom Data: ', JSON.stringify(error.data, null, 2))
|
630 | }
|
631 | return finish(error.output.statusCode, error.output.payload, error.output.headers)
|
632 | }
|
633 | finish(500, {
|
634 | error: 'Internal Server Error',
|
635 | message: 'An internal server error occurred',
|
636 | statusCode: 500
|
637 | })
|
638 | })
|
639 | }
|
640 |
|
641 | module.exports = {
|
642 | handler,
|
643 | normaliseLambdaRequest
|
644 | }
|
645 |
|
646 | },{"../lib/handlers.js":2,"../lib/wrapper.js":4,"path":undefined}]},{},[6])(6)
|
647 | }); |
\ | No newline at end of file |