You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					230 lines
				
				5.2 KiB
			
		
		
			
		
	
	
					230 lines
				
				5.2 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const assert = require('assert')
							 | 
						||
| 
								 | 
							
								const http = require('http')
							 | 
						||
| 
								 | 
							
								const Ajv = require('ajv')
							 | 
						||
| 
								 | 
							
								const Request = require('./lib/request')
							 | 
						||
| 
								 | 
							
								const Response = require('./lib/response')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const errorMessage = 'The dispatch function has already been invoked'
							 | 
						||
| 
								 | 
							
								const urlSchema = {
							 | 
						||
| 
								 | 
							
								  oneOf: [
							 | 
						||
| 
								 | 
							
								    { type: 'string' },
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								      type: 'object',
							 | 
						||
| 
								 | 
							
								      properties: {
							 | 
						||
| 
								 | 
							
								        protocol: { type: 'string' },
							 | 
						||
| 
								 | 
							
								        hostname: { type: 'string' },
							 | 
						||
| 
								 | 
							
								        pathname: { type: 'string' }
							 | 
						||
| 
								 | 
							
								        // port type => any
							 | 
						||
| 
								 | 
							
								        // query type => any
							 | 
						||
| 
								 | 
							
								      },
							 | 
						||
| 
								 | 
							
								      additionalProperties: true,
							 | 
						||
| 
								 | 
							
								      required: ['pathname']
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const ajv = new Ajv()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								ajv.addKeyword({
							 | 
						||
| 
								 | 
							
								  keyword: 'prototypedType',
							 | 
						||
| 
								 | 
							
								  validate: (_, data) =>
							 | 
						||
| 
								 | 
							
								    data && data.prototype && typeof data.prototype === 'object'
							 | 
						||
| 
								 | 
							
								})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const schema = {
							 | 
						||
| 
								 | 
							
								  type: 'object',
							 | 
						||
| 
								 | 
							
								  properties: {
							 | 
						||
| 
								 | 
							
								    url: urlSchema,
							 | 
						||
| 
								 | 
							
								    path: urlSchema,
							 | 
						||
| 
								 | 
							
								    cookies: {
							 | 
						||
| 
								 | 
							
								      type: 'object',
							 | 
						||
| 
								 | 
							
								      additionalProperties: true
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    headers: {
							 | 
						||
| 
								 | 
							
								      type: 'object',
							 | 
						||
| 
								 | 
							
								      additionalProperties: true
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    query: {
							 | 
						||
| 
								 | 
							
								      type: 'object',
							 | 
						||
| 
								 | 
							
								      additionalProperties: true
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    simulate: {
							 | 
						||
| 
								 | 
							
								      type: 'object',
							 | 
						||
| 
								 | 
							
								      properties: {
							 | 
						||
| 
								 | 
							
								        end: { type: 'boolean' },
							 | 
						||
| 
								 | 
							
								        split: { type: 'boolean' },
							 | 
						||
| 
								 | 
							
								        error: { type: 'boolean' },
							 | 
						||
| 
								 | 
							
								        close: { type: 'boolean' }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    authority: { type: 'string' },
							 | 
						||
| 
								 | 
							
								    remoteAddress: { type: 'string' },
							 | 
						||
| 
								 | 
							
								    method: { type: 'string', enum: http.METHODS.concat(http.METHODS.map(toLowerCase)) },
							 | 
						||
| 
								 | 
							
								    validate: { type: 'boolean' },
							 | 
						||
| 
								 | 
							
								    Request: { prototypedType: true }
							 | 
						||
| 
								 | 
							
								    // payload type => any
							 | 
						||
| 
								 | 
							
								  },
							 | 
						||
| 
								 | 
							
								  additionalProperties: true,
							 | 
						||
| 
								 | 
							
								  oneOf: [
							 | 
						||
| 
								 | 
							
								    { required: ['url'] },
							 | 
						||
| 
								 | 
							
								    { required: ['path'] }
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const optsValidator = ajv.compile(schema)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function inject (dispatchFunc, options, callback) {
							 | 
						||
| 
								 | 
							
								  if (typeof callback === 'undefined') {
							 | 
						||
| 
								 | 
							
								    return new Chain(dispatchFunc, options)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    return doInject(dispatchFunc, options, callback)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function makeRequest (dispatchFunc, server, req, res) {
							 | 
						||
| 
								 | 
							
								  req.once('error', function (err) {
							 | 
						||
| 
								 | 
							
								    if (this.destroyed) res.destroy(err)
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  req.once('close', function () {
							 | 
						||
| 
								 | 
							
								    if (this.destroyed && !this._error) res.destroy()
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return req.prepare(() => dispatchFunc.call(server, req, res))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function doInject (dispatchFunc, options, callback) {
							 | 
						||
| 
								 | 
							
								  options = (typeof options === 'string' ? { url: options } : options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (options.validate !== false) {
							 | 
						||
| 
								 | 
							
								    assert(typeof dispatchFunc === 'function', 'dispatchFunc should be a function')
							 | 
						||
| 
								 | 
							
								    const isOptionValid = optsValidator(options)
							 | 
						||
| 
								 | 
							
								    if (!isOptionValid) {
							 | 
						||
| 
								 | 
							
								      throw new Error(optsValidator.errors.map(e => e.message))
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const server = options.server || {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const RequestConstructor = options.Request
							 | 
						||
| 
								 | 
							
								    ? Request.CustomRequest
							 | 
						||
| 
								 | 
							
								    : Request
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof callback === 'function') {
							 | 
						||
| 
								 | 
							
								    const req = new RequestConstructor(options)
							 | 
						||
| 
								 | 
							
								    const res = new Response(req, callback)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return makeRequest(dispatchFunc, server, req, res)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    return new Promise((resolve, reject) => {
							 | 
						||
| 
								 | 
							
								      const req = new RequestConstructor(options)
							 | 
						||
| 
								 | 
							
								      const res = new Response(req, resolve, reject)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      makeRequest(dispatchFunc, server, req, res)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function Chain (dispatch, option) {
							 | 
						||
| 
								 | 
							
								  if (typeof option === 'string') {
							 | 
						||
| 
								 | 
							
								    this.option = { url: option }
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    this.option = Object.assign({}, option)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  this.dispatch = dispatch
							 | 
						||
| 
								 | 
							
								  this._hasInvoked = false
							 | 
						||
| 
								 | 
							
								  this._promise = null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (this.option.autoStart !== false) {
							 | 
						||
| 
								 | 
							
								    process.nextTick(() => {
							 | 
						||
| 
								 | 
							
								      if (!this._hasInvoked) {
							 | 
						||
| 
								 | 
							
								        this.end()
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const httpMethods = [
							 | 
						||
| 
								 | 
							
								  'delete',
							 | 
						||
| 
								 | 
							
								  'get',
							 | 
						||
| 
								 | 
							
								  'head',
							 | 
						||
| 
								 | 
							
								  'options',
							 | 
						||
| 
								 | 
							
								  'patch',
							 | 
						||
| 
								 | 
							
								  'post',
							 | 
						||
| 
								 | 
							
								  'put',
							 | 
						||
| 
								 | 
							
								  'trace'
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								httpMethods.forEach(method => {
							 | 
						||
| 
								 | 
							
								  Chain.prototype[method] = function (url) {
							 | 
						||
| 
								 | 
							
								    if (this._hasInvoked === true || this._promise) {
							 | 
						||
| 
								 | 
							
								      throw new Error(errorMessage)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.option.url = url
							 | 
						||
| 
								 | 
							
								    this.option.method = method.toUpperCase()
							 | 
						||
| 
								 | 
							
								    return this
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const chainMethods = [
							 | 
						||
| 
								 | 
							
								  'body',
							 | 
						||
| 
								 | 
							
								  'cookies',
							 | 
						||
| 
								 | 
							
								  'headers',
							 | 
						||
| 
								 | 
							
								  'payload',
							 | 
						||
| 
								 | 
							
								  'query'
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								chainMethods.forEach(method => {
							 | 
						||
| 
								 | 
							
								  Chain.prototype[method] = function (value) {
							 | 
						||
| 
								 | 
							
								    if (this._hasInvoked === true || this._promise) {
							 | 
						||
| 
								 | 
							
								      throw new Error(errorMessage)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.option[method] = value
							 | 
						||
| 
								 | 
							
								    return this
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Chain.prototype.end = function (callback) {
							 | 
						||
| 
								 | 
							
								  if (this._hasInvoked === true || this._promise) {
							 | 
						||
| 
								 | 
							
								    throw new Error(errorMessage)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  this._hasInvoked = true
							 | 
						||
| 
								 | 
							
								  if (typeof callback === 'function') {
							 | 
						||
| 
								 | 
							
								    doInject(this.dispatch, this.option, callback)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    this._promise = doInject(this.dispatch, this.option)
							 | 
						||
| 
								 | 
							
								    return this._promise
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Object.getOwnPropertyNames(Promise.prototype).forEach(method => {
							 | 
						||
| 
								 | 
							
								  if (method === 'constructor') return
							 | 
						||
| 
								 | 
							
								  Chain.prototype[method] = function (...args) {
							 | 
						||
| 
								 | 
							
								    if (!this._promise) {
							 | 
						||
| 
								 | 
							
								      if (this._hasInvoked === true) {
							 | 
						||
| 
								 | 
							
								        throw new Error(errorMessage)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this._hasInvoked = true
							 | 
						||
| 
								 | 
							
								      this._promise = doInject(this.dispatch, this.option)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return this._promise[method](...args)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								})
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function isInjection (obj) {
							 | 
						||
| 
								 | 
							
								  return (
							 | 
						||
| 
								 | 
							
								    obj instanceof Request ||
							 | 
						||
| 
								 | 
							
								    obj instanceof Response ||
							 | 
						||
| 
								 | 
							
								    (obj && obj.constructor && obj.constructor.name === '_CustomLMRRequest')
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function toLowerCase (m) { return m.toLowerCase() }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = inject
							 | 
						||
| 
								 | 
							
								module.exports.inject = inject
							 | 
						||
| 
								 | 
							
								module.exports.isInjection = isInjection
							 |