iopa
Version:
API-first, Internet of Things (IoT) stack for Typescript, official implementation of the Internet Open Protocols Alliance (IOPA) reference pattern
160 lines (140 loc) • 4.93 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 { IContextIopaLegacy, IContextIopa, IFactory } from '@iopa/types'
import { mergeContext } from '../util/shallow'
import FreeList from '../util/freelist'
import { ContextCore } from './context-core'
/** Represents IopaContext Factory of up to 100 items */
export default class Factory<
C,
T extends IContextIopa<C> & { init: Function; dispose?: Function }
> implements IFactory<C, T>
{
protected _factory: FreeList<T>
public constructor(
name: string = 'IopaContext',
size: number = 100,
factory: () => T = () => new ContextCore() as any
) {
this._factory = new FreeList(name, size, factory)
}
/** Creates a new IOPA Context */
public createContext(
urlOrRequest?: string | Request,
options?: Partial<IContextIopaLegacy<C>>
): T {
const optionsValid = _validOptions(options)
const context: T & {
init: Function
dispose?: Function
} & IContextIopa<C> = this._factory.alloc().init()
context.dispose = this._dispose.bind(this, context)
context.set('iopa.Id', `#${_nextSequence()}`)
context.set('server.Timestamp', Date.now())
if (urlOrRequest) {
if (typeof urlOrRequest === 'string') {
const url = urlOrRequest
context.set('iopa.OriginalUrl', url)
context.set('iopa.Url', new URL(url))
} else {
const url = urlOrRequest.url
context.set('iopa.OriginalUrl', url)
context.set('iopa.Url', new URL(url))
context.set('iopa.RawRequest', urlOrRequest)
context.set('iopa.RawRequestClone', urlOrRequest.clone())
}
}
context.createChild = this.createChildContext.bind(this, context)
mergeContext(context, optionsValid)
return context as T
}
/** Creates a new IOPA Context that is a child request/response of a parent Context */
protected createChildContext(
parentContext: T,
urlPath?: string,
defaults?: any
): T {
const defaultsValid = _validOptions(defaults)
const context = this.createContext() as unknown as T
context.set('server.Events', parentContext.get('server.Events'))
context.set('server.Capabilities', parentContext.get('server.Capabilities'))
context.app = parentContext.app
const parentUrl =
parentContext.get('iopa.Url') || new URL('https://schema.iopa.io')
const childUrl = `${parentUrl.protocol}//${parentUrl.host}/${
urlPath
? urlPath.replace(/^\//, '')
: parentUrl.pathname.replace(/^\//, '')
}`
context.set('iopa.OriginalUrl', childUrl)
context.set('iopa.Url', new URL(childUrl))
if (parentContext.get('iopa.RawRequest')) {
context.set(
'iopa.RawRequest',
new Request(childUrl, {
headers:
defaultsValid['iopa.Headers'] || parentContext.get('iopa.Headers'),
method:
defaultsValid['iopa.Method'] || parentContext.get('iopa.Method'),
body: null
})
)
context.set(
'iopa.RawRequestClone',
new Request(childUrl, {
headers:
defaultsValid['iopa.Headers'] || parentContext.get('iopa.Headers'),
method:
defaultsValid['iopa.Method'] || parentContext.get('iopa.Method'),
body: null
})
)
}
mergeContext(context, defaultsValid)
return context
}
/** Release the memory used by a given IOPA Context */
protected _dispose(
context: T & IContextIopa<C> & { init: Function; dispose?: Function }
): void {
if (context === null || context.get('server.AbortController') === null) {
return
}
Object.keys(context).forEach((prop) => {
context[prop] = null
})
this._factory.free(context)
}
}
/** Clean Options; allows overide for future validation */
function _validOptions(options?: any): any {
if (typeof options === 'string' || options instanceof String) {
const result = {}
result['iopa.Method'] = options
return result
}
return options || {}
}
const maxSequence: number = 2 ** 16
let _lastSequence: number = Math.floor(Math.random() * (maxSequence - 1))
export function _nextSequence(): string {
_lastSequence += 1
if (_lastSequence === maxSequence) {
_lastSequence = 1
}
return _lastSequence.toString()
}