iopa
Version:
API-first, Internet of Things (IoT) stack for Typescript, official implementation of the Internet Open Protocols Alliance (IOPA) reference pattern
135 lines (120 loc) • 3.87 kB
text/typescript
/*
* Internet Open Protocol Abstraction (IOPA)
* Copyright (c) 2016-2022 Internet Open Protocols Alliance
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { FC, IApp } from '@iopa/types'
/** Assures that the middleware is represented as IOPA middleware promise format (promise) fn(context, next) where next = (promise) function() */
export default function Middleware<P, C>(
app: IApp<P, C>,
middleware: any,
action: 'invoke' | 'dispatch' = 'invoke'
): FC<C> {
let args
if (typeof middleware === 'function') {
switch (middleware.length) {
//
// fn() with this=app and fn.invoke(next) with this = context
//
case 0:
middleware.call(app)
return (context, next) => {
return middleware.invoke.call(context, next)
}
break
//
// fn(next) or fn(app) and fn.invoke(context, next)
//
case 1:
args = _getParamNames(middleware)
if (arrayEqual(args, ['next'])) {
return (context, next) => {
return middleware.call(context, next)
}
}
// eslint-disable-next-line no-case-declarations
// eslint-disable-next-line
const mw = new middleware(app)
return mw.invoke.bind(mw)
//
// fn(req,res) or fn(context, next)
//
case 2:
args = _getParamNames(middleware)
if (arrayEqual(args, ['req', 'res'])) {
throw new Error(
"must require 'iopa-connect' to use Connect/Express style middleware"
)
} else {
return middleware
}
//
// fn(req,res,next)
//
case 3:
throw new Error(
"must require 'iopa-connect' to use Connect/Express style middleware"
)
//
// fn(err,req,res,next)
//
case 4:
throw new Error(
"must require 'iopa-connect' to use Connect/Express style middleware"
)
default:
throw new Error('unknown middleware')
}
} else {
console.error('middleware must be a function')
throw new Error('middleware must be called on a function')
}
}
// PRIVATE HELPER FUNCTIONS
const STRIP_COMMENTS: RegExp = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm
/** Gets the parameter names of a javascript function */
function _getParamNames(func: Function): RegExpMatchArray {
const fnStr = func.toString().replace(STRIP_COMMENTS, '')
let result = fnStr
.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')'))
.match(/([^\s,]+)/g)
if (result === null) {
result = []
}
return result
}
/** Check if two arrays have same elements */
function arrayEqual(array1: Array<any>, array2: Array<any>): boolean {
// if the other array is a falsy value, return
if (!array2) {
return false
}
// compare lengths - can save a lot of time
if (array1.length !== array2.length) {
return false
}
for (let i = 0, l = array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// recurse into the nested arrays
if (!array1[i].compare(array2[i])) {
return false
}
} else if (array1[i] !== array2[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false
}
}
return true
}